/** * Expects an array of items. The items are either IPs or IPs separated by comma, space or tab. Or an array of IP's. * We then examine all IP's looking for a public IP and storing private IP's in an array. If we find no public IPs we return the first private addr we found. * * @param array $arr * @return bool|mixed */ private function _getCleanIPAndServerVar($arr) { $privates = array(); //Store private addrs until end as last resort. foreach ($arr as $entry) { list($item, $var) = $entry; if (is_array($item)) { foreach ($item as $j) { // try verifying the IP is valid before stripping the port off if (!$this->_isValidIP($j)) { $j = preg_replace('/:\\d+$/', '', $j); //Strip off port } if ($this->_isValidIP($j)) { if ($this->_isIPv6MappedIPv4($j)) { $j = wfWAFUtils::inet_ntop(wfWAFUtils::inet_pton($j)); } if ($this->_isPrivateIP($j)) { $privates[] = array($j, $var); } else { return array($j, $var); } } } continue; //This was an array so we can skip to the next item } $skipToNext = false; foreach (array(',', ' ', "\t") as $char) { if (strpos($item, $char) !== false) { $sp = explode($char, $item); foreach ($sp as $j) { $j = trim($j); if (!$this->_isValidIP($j)) { $j = preg_replace('/:\\d+$/', '', $j); //Strip off port } if ($this->_isValidIP($j)) { if ($this->_isIPv6MappedIPv4($j)) { $j = wfWAFUtils::inet_ntop(wfWAFUtils::inet_pton($j)); } if ($this->_isPrivateIP($j)) { $privates[] = array($j, $var); } else { return array($j, $var); } } } $skipToNext = true; break; } } if ($skipToNext) { continue; } //Skip to next item because this one had a comma, space or tab so was delimited and we didn't find anything. if (!$this->_isValidIP($item)) { $item = preg_replace('/:\\d+$/', '', $item); //Strip off port } if ($this->_isValidIP($item)) { if ($this->_isIPv6MappedIPv4($item)) { $item = wfWAFUtils::inet_ntop(wfWAFUtils::inet_pton($item)); } if ($this->_isPrivateIP($item)) { $privates[] = array($item, $var); } else { return array($item, $var); } } } if (sizeof($privates) > 0) { return $privates[0]; //Return the first private we found so that we respect the order the IP's were passed to this function. } return false; }
/** * @param string $ip * @return bool */ public function isIPBlocked($ip) { $this->open(); $ipBin = wfWAFUtils::inet_pton($ip); fseek($this->ipCacheFileHandle, wfWAFUtils::strlen(self::LOG_FILE_HEADER), SEEK_SET); self::lock($this->ipCacheFileHandle, LOCK_SH); while (!feof($this->ipCacheFileHandle)) { $ipStr = fread($this->ipCacheFileHandle, 20); $ip2 = wfWAFUtils::substr($ipStr, 0, 16); if ($ipBin === $ip2 && unpack('V', wfWAFUtils::substr($ipStr, 16, 4)) >= time()) { self::lock($this->ipCacheFileHandle, LOCK_UN); return true; } } self::lock($this->ipCacheFileHandle, LOCK_UN); return false; }
function geoip_name_by_addr_v6($gi, $addr) { if ($addr == NULL) { return 0; } $ipnum = wfWAFUtils::inet_pton($addr); return _get_org_v6($gi, $ipnum); }
/** * Return a set of where clauses to use in MySQL. * * @param string $column * @return false|null|string */ public function toSQL($column = 'ip') { /** @var wpdb $wpdb */ global $wpdb; $ip_string = $this->getIPString(); if (strpos($ip_string, '.') !== false && preg_match('/\\[\\d+\\-\\d+\\]/', $ip_string)) { $whiteParts = explode('.', $ip_string); $sql = "(SUBSTR({$column}, 1, 12) = LPAD(CHAR(0xff, 0xff), 12, CHAR(0)) AND "; for ($i = 0, $j = 24; $i <= 3; $i++, $j -= 8) { // MySQL can only perform bitwise operations on integers $conv = sprintf('CAST(CONV(HEX(SUBSTR(%s, 13, 8)), 16, 10) as UNSIGNED INTEGER)', $column); if (preg_match('/^\\[(\\d+)\\-(\\d+)\\]$/', $whiteParts[$i], $m)) { $sql .= $wpdb->prepare("{$conv} >> {$j} & 0xFF BETWEEN %d AND %d", $m[1], $m[2]); } else { $sql .= $wpdb->prepare("{$conv} >> {$j} & 0xFF = %d", $whiteParts[$i]); } $sql .= ' AND '; } $sql = substr($sql, 0, -5) . ')'; return $sql; } else { if (strpos($ip_string, ':') !== false) { $ip_string = strtolower(self::expandIPv6Range($ip_string)); if (preg_match('/\\[[a-f0-9]+\\-[a-f0-9]+\\]/i', $ip_string)) { $whiteParts = explode(':', $ip_string); $sql = '('; for ($i = 0; $i <= 7; $i++) { // MySQL can only perform bitwise operations on integers $conv = sprintf('CAST(CONV(HEX(SUBSTR(%s, %d, 8)), 16, 10) as UNSIGNED INTEGER)', $column, $i < 4 ? 1 : 9); $j = 16 * (3 - $i % 4); if (preg_match('/^\\[([a-f0-9]+)\\-([a-f0-9]+)\\]$/i', $whiteParts[$i], $m)) { $sql .= $wpdb->prepare("{$conv} >> {$j} & 0xFFFF BETWEEN 0x%x AND 0x%x", hexdec($m[1]), hexdec($m[2])); } else { $sql .= $wpdb->prepare("{$conv} >> {$j} & 0xFFFF = 0x%x", hexdec($whiteParts[$i])); } $sql .= ' AND '; } $sql = substr($sql, 0, -5) . ')'; return $sql; } } } return $wpdb->prepare("({$column} = %s)", wfWAFUtils::inet_pton($ip_string)); }
/** * @param wfWAFRequest $request * @return bool|string If not blocked, returns false. Otherwise a string of the reason it was blocked or true. */ public function shouldBlockRequest($request) { // Checking the user whitelist is done before reaching this call $ip = $request->getIP(); //Check the system whitelist if ($this->checkForWhitelisted($ip)) { return false; } //Let the plugin handle these $wfFunc = $request->getQueryString('_wfsf'); if ($wfFunc == 'unlockEmail' || $wfFunc == 'unlockAccess') { // Can't check validity here, let it pass through to plugin level where it can return false; } $logHuman = $request->getQueryString('wordfence_logHuman'); if ($logHuman !== null) { return false; } //Start block checks $ipNum = wfWAFUtils::inet_pton($ip); $hostname = null; $ua = $request->getHeaders('User-Agent'); if ($ua === null) { $ua = ''; } $referer = $request->getHeaders('Referer'); if ($referer === null) { $referer = ''; } $isPaid = false; try { $isPaid = wfWAF::getInstance()->getStorageEngine()->getConfig('isPaid'); $pluginABSPATH = wfWAF::getInstance()->getStorageEngine()->getConfig('pluginABSPATH'); $patternBlocksJSON = wfWAF::getInstance()->getStorageEngine()->getConfig('patternBlocks'); $countryBlocksJSON = wfWAF::getInstance()->getStorageEngine()->getConfig('countryBlocks'); $otherBlocksJSON = wfWAF::getInstance()->getStorageEngine()->getConfig('otherBlocks'); } catch (Exception $e) { // Do nothing } if (isset($_SERVER['SCRIPT_FILENAME']) && (strpos($_SERVER['SCRIPT_FILENAME'], $pluginABSPATH . "wp-admin/") === 0 || strpos($_SERVER['SCRIPT_FILENAME'], $pluginABSPATH . "wp-content/") === 0 || strpos($_SERVER['SCRIPT_FILENAME'], $pluginABSPATH . "wp-includes/") === 0)) { return false; //Rely on WordPress's own access control and blocking at the plugin level } // Pattern Blocks from the Advanced Blocking page (IP Range, UA, Referer) $patternBlocks = @wfWAFUtils::json_decode($patternBlocksJSON, true); if (is_array($patternBlocks)) { // Instead of a long block of if/else statements, using bitshifting to generate an expected value and a found value $ipRangeOffset = 1; $uaPatternOffset = 2; $refPatternOffset = 3; foreach ($patternBlocks as $b) { $expectedBits = 0; $foundBits = 0; if (!empty($b['ipRange'])) { $expectedBits |= 1 << $ipRangeOffset; list($start_range, $end_range) = explode('-', $b['ipRange']); if (preg_match('/[\\.:]/', $start_range)) { $start_range = wfWAFUtils::inet_pton($start_range); $end_range = wfWAFUtils::inet_pton($end_range); } else { $start_range = wfWAFUtils::inet_pton(long2ip($start_range)); $end_range = wfWAFUtils::inet_pton(long2ip($end_range)); } if (strcmp($ipNum, $start_range) >= 0 && strcmp($ipNum, $end_range) <= 0) { $foundBits |= 1 << $ipRangeOffset; } } if (!empty($b['hostnamePattern'])) { $expectedBits |= 1 << $ipRangeOffset; if ($hostname === null) { $hostname = wfWAFUtils::reverseLookup($ip); } if (preg_match(wfWAFUtils::patternToRegex($b['hostnamePattern']), $hostname)) { $foundBits |= 1 << $ipRangeOffset; } } if (!empty($b['uaPattern'])) { $expectedBits |= 1 << $uaPatternOffset; if (wfWAFUtils::isUABlocked($b['uaPattern'], $ua)) { $foundBits |= 1 << $uaPatternOffset; } } if (!empty($b['refPattern'])) { $expectedBits |= 1 << $refPatternOffset; if (wfWAFUtils::isRefererBlocked($b['refPattern'], $referer)) { $foundBits |= 1 << $refPatternOffset; } } if ($foundBits === $expectedBits && $expectedBits > 0) { return array('action' => self::WFWAF_BLOCK_UAREFIPRANGE, 'id' => $b['id']); } } } // End Pattern Blocks // Country Blocking if ($isPaid) { $countryBlocks = @wfWAFUtils::json_decode($countryBlocksJSON, true); if (is_array($countryBlocks)) { $blockedCountries = $countryBlocks['countries']; $bareRequestURI = wfWAFUtils::extractBareURI($request->getURI()); $bareBypassRedirURI = wfWAFUtils::extractBareURI($countryBlocks['bypassRedirURL']); $skipCountryBlocking = false; if ($bareBypassRedirURI && $bareRequestURI == $bareBypassRedirURI) { // Run this before country blocking because even if the user isn't blocked we need to set the bypass cookie so they can bypass future blocks. if ($countryBlocks['bypassRedirDest']) { setcookie('wfCBLBypass', $countryBlocks['cookieVal'], time() + 86400 * 365, '/', null, null, true); return array('action' => self::WFWAF_BLOCK_COUNTRY_BYPASS_REDIR); } } $bareBypassViewURI = wfWAFUtils::extractBareURI($countryBlocks['bypassViewURL']); if ($bareBypassViewURI && $bareBypassViewURI == $bareRequestURI) { setcookie('wfCBLBypass', $countryBlocks['cookieVal'], time() + 86400 * 365, '/', null, null, true); $skipCountryBlocking = true; } $bypassCookieSet = false; $bypassCookie = $request->getCookies('wfCBLBypass'); if (isset($bypassCookie) && $bypassCookie == $countryBlocks['cookieVal']) { $bypassCookieSet = true; } if (!$skipCountryBlocking && $blockedCountries && !$bypassCookieSet) { $isAuthRequest = strpos($bareRequestURI, '/wp-login.php') !== false; $isXMLRPC = strpos($bareRequestURI, '/xmlrpc.php') !== false; $isUserLoggedIn = wfWAF::getInstance()->parseAuthCookie() !== false; // If everything is checked, make sure this always runs. if ($countryBlocks['loggedInBlocked'] && $countryBlocks['loginFormBlocked'] && $countryBlocks['restOfSiteBlocked']) { if ($blocked = $this->checkForBlockedCountry($countryBlocks, $ip, $bareRequestURI)) { return $blocked; } } // Block logged in users. if ($countryBlocks['loggedInBlocked'] && $isUserLoggedIn) { if ($blocked = $this->checkForBlockedCountry($countryBlocks, $ip, $bareRequestURI)) { return $blocked; } } // Block the login form itself and any attempt to authenticate. if ($countryBlocks['loginFormBlocked'] && $isAuthRequest) { if ($blocked = $this->checkForBlockedCountry($countryBlocks, $ip, $bareRequestURI)) { return $blocked; } } // Block requests that aren't to the login page, xmlrpc.php, or a user already logged in. if ($countryBlocks['restOfSiteBlocked'] && !$isAuthRequest && !$isXMLRPC && !$isUserLoggedIn) { if ($blocked = $this->checkForBlockedCountry($countryBlocks, $ip, $bareRequestURI)) { return $blocked; } } // XMLRPC is inaccesible when public portion of the site and auth is disabled. if ($countryBlocks['loginFormBlocked'] && $countryBlocks['restOfSiteBlocked'] && $isXMLRPC) { if ($blocked = $this->checkForBlockedCountry($countryBlocks, $ip, $bareRequestURI)) { return $blocked; } } // Any bypasses and other block possibilities will be checked at the plugin level once WordPress loads } } } // End Country Blocking // Other Blocks $otherBlocks = @wfWAFUtils::json_decode($otherBlocksJSON, true); if (is_array($otherBlocks)) { $blockedTime = $otherBlocks['blockedTime']; $blocks = $otherBlocks['blocks']; $bareRequestURI = wfWAFUtils::extractBareURI($request->getURI()); $isAuthRequest = strpos($bareRequestURI, '/wp-login.php') !== false; foreach ($blocks as $b) { if (!$b['permanent'] && $b['blockedTime'] + $blockedTime < time()) { continue; } if (base64_decode($b['IP']) != $ipNum) { continue; } if ($isAuthRequest) { return array('action' => self::WFWAF_BLOCK_WFSN); } return array('action' => empty($b['reason']) ? '' : $b['reason']); } } // End Other Blocks return false; }