View file File name : file.php Content :<?php if (defined('WFWAF_VERSION') && !defined('WFWAF_RUN_COMPLETE')) { class wfWAFStorageFile implements wfWAFStorageInterface { const LOG_FILE_HEADER = "<?php exit('Access denied'); __halt_compiler(); ?>\n"; const LOG_INFO_HEADER = "******************************************************************\nThis file is used by the Wordfence Web Application Firewall. Read \nmore at https://docs.wordfence.com/en/Web_Application_Firewall_FAQ\n******************************************************************\n"; const IP_BLOCK_RECORD_SIZE = 24; private $rules; private $failScores; private $variables; private $whitelistedParams; private $blacklistedParams; public static function allowFileWriting() { if (defined('WFWAF_ALWAYS_ALLOW_FILE_WRITING') && WFWAF_ALWAYS_ALLOW_FILE_WRITING) { return true; } if (wfWAFUtils::isCli()) { return false; } return true; } public static function atomicFilePutContents($file, $content, $prefix = 'config') { if (!wfWAFStorageFile::allowFileWriting()) { return false; } $tmpFile = @tempnam(dirname($file), $prefix . '.tmp.'); if (!$tmpFile) { $tmpFile = @tempnam(sys_get_temp_dir(), $prefix . '.tmp.'); } if (!$tmpFile) { throw new wfWAFStorageFileException('Unable to save temporary file for atomic writing.'); } $tmpHandle = @fopen($tmpFile, 'w'); if (!$tmpHandle) { throw new wfWAFStorageFileException('Unable to save temporary file ' . $tmpFile . ' for atomic writing.'); } self::lock($tmpHandle, LOCK_EX); fwrite($tmpHandle, $content); fflush($tmpHandle); self::lock($tmpHandle, LOCK_UN); fclose($tmpHandle); chmod($tmpFile, self::permissions()); // Attempt to verify file has finished writing (sometimes the disk will lie for better benchmarks) $tmpContents = file_get_contents($tmpFile); if ($tmpContents !== $content) { throw new wfWAFStorageFileException('Unable to verify temporary file contents for atomic writing.'); } if (!@rename($tmpFile, $file)) { $backFile = @tempnam(dirname($file), $prefix . '.bak.'); if (!$backFile) { $backFile = @tempnam(sys_get_temp_dir(), $prefix . '.bak.'); } if (!$backFile) { throw new wfWAFStorageFileException('Unable to save temporary file for atomic writing.'); } if (WFWAF_DEBUG) { rename($file, $backFile); rename($tmpFile, $file); unlink($backFile); unlink($tmpFile); } else { @rename($file, $backFile); @rename($tmpFile, $file); @unlink($backFile); @unlink($tmpFile); } } } public static function lock($handle, $lock, $wouldLock = 1) { $locked = flock($handle, $lock, $wouldLock); if (!$locked) { error_log('Lock not acquired ' . $locked); } return $locked; } public static function permissions() { if (defined('WFWAF_LOG_FILE_MODE')) { return WFWAF_LOG_FILE_MODE; } static $_cachedPermissions = null; if ($_cachedPermissions === null) { if (defined('WFWAF_LOG_PATH')) { $template = rtrim(WFWAF_LOG_PATH, '/') . '/template.php'; if (file_exists($template)) { $stat = @stat($template); if ($stat !== false) { $mode = $stat[2]; $updatedMode = 0600; if (($mode & 0020) == 0020) { $updatedMode = $updatedMode | 0060; } $_cachedPermissions = $updatedMode; return $updatedMode; } } } return 0660; } return $_cachedPermissions; } /** * @var resource */ private $ipCacheFileHandle; /** * @var string|null */ private $attackDataFile; /** * @var wfWAFAttackDataStorageFileEngine */ private $attackDataEngine; /** * @var string|null */ private $ipCacheFile; private $configFile; private $rulesFile; private $rulesDSLCacheFile; private $dataChanged = array(); private $data = array(); /** * @var resource[] */ private $configFileHandles = array(); private $uninstalled; private $attackDataRows; private $attackDataNewerThan; /** * @param string|null $attackDataFile * @param string|null $ipCacheFile * @param string|null $configFile * @param string|null $rulesFile * @param null $rulesDSLCacheFile */ public function __construct($attackDataFile = null, $ipCacheFile = null, $configFile = null, $rulesFile = null, $rulesDSLCacheFile = null) { $this->setAttackDataFile($attackDataFile); $this->setIPCacheFile($ipCacheFile); $this->setConfigFile($configFile); $this->setRulesFile($rulesFile); $this->setRulesDSLCacheFile($rulesDSLCacheFile); } /** * @param float $olderThan * @return bool * @throws wfWAFStorageFileException */ public function hasPreviousAttackData($olderThan) { $this->open(); $timestamp = $this->getAttackDataEngine()->getOldestTimestamp(); return $timestamp && $timestamp < $olderThan; } /** * @param float $newerThan * @return bool * @throws wfWAFStorageFileException */ public function hasNewerAttackData($newerThan) { $this->open(); $timestamp = $this->getAttackDataEngine()->getNewestTimestamp(); return $timestamp && $timestamp > $newerThan; } /** * @return mixed|string|void * @throws wfWAFStorageFileException */ public function getAttackData() { $this->open(); $this->attackDataRows = array(); $this->getAttackDataEngine()->scanRows(array($this, '_getAttackDataRowsSerialized')); return wfWAFUtils::json_encode($this->attackDataRows); } /** * @return array * @throws wfWAFStorageFileException */ public function getAttackDataArray() { $this->open(); $this->attackDataRows = array(); $this->getAttackDataEngine()->scanRows(array($this, '_getAttackDataRows')); return $this->attackDataRows; } /** * @param resource $fileHandle * @param int $offset * @param int $length */ public function _getAttackDataRowsSerialized($fileHandle, $offset, $length) { fseek($fileHandle, $offset); self::lock($fileHandle, LOCK_SH); $binary = fread($fileHandle, $length); self::lock($fileHandle, LOCK_UN); $row = wfWAFAttackDataStorageFileEngineRow::unpack($binary); $data = wfWAFUtils::json_decode($row->getData(), true); if (is_array($data)) { array_unshift($data, $row->getTimestamp()); $this->attackDataRows[] = $data; } } /** * @param resource $fileHandle * @param int $offset * @param int $length */ public function _getAttackDataRows($fileHandle, $offset, $length) { fseek($fileHandle, $offset); self::lock($fileHandle, LOCK_SH); $binary = fread($fileHandle, $length); self::lock($fileHandle, LOCK_UN); $row = wfWAFAttackDataStorageFileEngineRow::unpack($binary); $data = $this->unserializeRow($row->getData()); array_unshift($data, $row->getTimestamp()); $this->attackDataRows[] = $data; } /** * @param $newerThan * @return array * @throws wfWAFStorageFileException */ public function getNewestAttackDataArray($newerThan) { $this->open(); $this->attackDataRows = array(); $this->attackDataNewerThan = $newerThan; $this->getAttackDataEngine()->scanRowsReverse(array($this, '_getAttackDataRowsNewerThan')); return $this->attackDataRows; } /** * @param resource $fileHandle * @param int $offset * @param int $length * @return bool */ public function _getAttackDataRowsNewerThan($fileHandle, $offset, $length) { fseek($fileHandle, $offset); self::lock($fileHandle, LOCK_SH); $binaryTimestamp = fread($fileHandle, 8); self::lock($fileHandle, LOCK_UN); $timestamp = wfWAFAttackDataStorageFileEngine::unpackMicrotime($binaryTimestamp); if ($timestamp > $this->attackDataNewerThan) { $binary = $binaryTimestamp . fread($fileHandle, $length - 8); $row = wfWAFAttackDataStorageFileEngineRow::unpack($binary); $data = $this->unserializeRow($row->getData()); if (is_array($data)) { array_unshift($data, $row->getTimestamp()); $this->attackDataRows[] = $data; } return true; } return false; } /** * @return bool * @throws wfWAFStorageFileException */ public function truncateAttackData() { if (!wfWAFStorageFile::allowFileWriting()) { return false; } $this->open(); $this->getAttackDataEngine()->truncate(); return $this->getAttackDataEngine()->getRowCount() === 0; } /** * @return bool * @throws wfWAFStorageFileException */ public function isAttackDataFull() { $this->open(); return $this->getAttackDataEngine()->getRowCount() === wfWAFAttackDataStorageFileEngine::MAX_ROWS; } /** * @param array $failedRules * @param string $failedParamKey * @param string $failedParamValue * @param wfWAFRequestInterface $request * @param mixed $_ * @return mixed */ public function logAttack($failedRules, $failedParamKey, $failedParamValue, $request, $_ = null) { if (!wfWAFStorageFile::allowFileWriting()) { return false; } $this->open(); $row = array( $request->getTimestamp(), $request->getIP(), (int) $this->isInLearningMode(), $failedParamKey, $failedParamValue, ); $failedRulesString = ''; if (is_array($failedRules)) { /** * @var int $index * @var wfWAFRule|int $rule */ foreach ($failedRules as $index => $rule) { if ($rule instanceof wfWAFRule) { $failedRulesString .= $rule->getRuleID() . '|'; } else { $failedRulesString .= $rule . '|'; } } $failedRulesString = wfWAFUtils::substr($failedRulesString, 0, -1); } $row[] = $failedRulesString; $row[] = $request->getProtocol() === 'https' ? 1 : 0; $row[] = (string) $request; // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection $args = func_get_args(); $row = array_merge($row, array_slice($args, 4)); if (($rowString = $this->serializeRow($row)) !== false) { $attackRow = new wfWAFAttackDataStorageFileEngineRow(microtime(false), $rowString); $this->getAttackDataEngine()->addRow($attackRow); } } /** * @param int $timestamp * @param string $ip * @return mixed|void * @throws wfWAFStorageFileException */ public function blockIP($timestamp, $ip, $type = wfWAFStorageInterface::IP_BLOCKS_SINGLE) { if (!wfWAFStorageFile::allowFileWriting()) { return false; } $this->open(); if (!$this->isIPBlocked($ip)) { self::lock($this->ipCacheFileHandle, LOCK_EX); fseek($this->ipCacheFileHandle, 0, SEEK_END); fwrite($this->ipCacheFileHandle, wfWAFUtils::inet_pton($ip) . pack('V', $timestamp) . pack('V', $type)); fflush($this->ipCacheFileHandle); self::lock($this->ipCacheFileHandle, LOCK_UN); } } /** * @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, self::IP_BLOCK_RECORD_SIZE); if (wfWAFUtils::strlen($ipStr) < self::IP_BLOCK_RECORD_SIZE) { break; } $ip2 = wfWAFUtils::substr($ipStr, 0, 16); $unpacked = @unpack('V', wfWAFUtils::substr($ipStr, 16, 4)); if (is_array($unpacked)) { $t = array_shift($unpacked); if ($ipBin === $ip2 && $t >= time()) { self::lock($this->ipCacheFileHandle, LOCK_UN); return true; } } } self::lock($this->ipCacheFileHandle, LOCK_UN); return false; } public function pathForConfig($category = '') { $category = strtolower(preg_replace('/[^a-z0-9]/i', '', $category)); $path = $this->_normalizeSlashes($this->getConfigFile()); $components = explode('/', $path); $last = $components[count($components) - 1]; if (preg_match('/^([^.]+)(\..+$|$)/', $last, $matches) && !empty($category)) { $last = $matches[1] . '-' . $category . $matches[2]; } $components[count($components) - 1] = $last; $path = implode('/', $components); return $path; } /** * @return bool */ public function isOpened($category = '') { return isset($this->configFileHandles[$category]) && is_resource($this->configFileHandles[$category]); } /** * @throws wfWAFStorageFileException */ public function open($category = '') { if ($this->isOpened($category)) { return; } if ($this->uninstalled) { throw new wfWAFStorageFileException('Unable to open WAF file storage, WAF has been uninstalled.'); } if (!empty($category)) { //A non-empty category only opens that config file rather than including the rest of the WAF files. $this->_open($this->pathForConfig($category), $this->configFileHandles, $category, self::LOG_FILE_HEADER . self::LOG_INFO_HEADER . serialize($this->getDefaultConfiguration($category)), true); return; } $files = array( array($this->getIPCacheFile(), 'ipCacheFileHandle', false, self::LOG_FILE_HEADER, true), array($this->pathForConfig($category), 'configFileHandles', $category, self::LOG_FILE_HEADER . self::LOG_INFO_HEADER . serialize($this->getDefaultConfiguration($category)), false), ); foreach ($files as $file) { list($filePath, $fileHandle, $arrayKey, $defaultContents, $remakeIfCorrupt) = $file; $this->_open($filePath, $this->$fileHandle, $arrayKey, $defaultContents, $remakeIfCorrupt); } $this->setAttackDataEngine(new wfWAFAttackDataStorageFileEngine($this->getAttackDataFile())); $this->getAttackDataEngine()->open(); } private function _open($filePath, &$fileHandle, $arrayKey, $defaultContents, $remakeIfCorrupt = false) { if (!file_exists($filePath)) { @file_put_contents($filePath, $defaultContents, LOCK_EX); } if (wfWAFStorageFile::allowFileWriting()) { @chmod($filePath, self::permissions()); } if ($arrayKey !== false) { $fileHandle[$arrayKey] = @fopen($filePath, 'r+'); $handle = $fileHandle[$arrayKey]; } else { $fileHandle = @fopen($filePath, 'r+'); $handle = $fileHandle; } if (!$handle && $remakeIfCorrupt && wfWAFStorageFile::allowFileWriting()) { @file_put_contents($filePath, $defaultContents, LOCK_EX); @chmod($filePath, self::permissions()); if ($arrayKey !== false) { $fileHandle[$arrayKey] = @fopen($filePath, 'r+'); $handle = $fileHandle[$arrayKey]; } else { $fileHandle = @fopen($filePath, 'r+'); $handle = $fileHandle; } } if (!$handle) { throw new wfWAFStorageFileException('Unable to open ' . $filePath . ' for reading and writing.'); } } /** * */ public function close($category = '') { if (!$this->isOpened($category)) { return; } fclose($this->configFileHandles[$category]); unset($this->configFileHandles[$category]); if (!empty($category)) { //A non-empty category only closes that config file rather than including the rest of the WAF files. return; } fclose($this->ipCacheFileHandle); $this->ipCacheFileHandle = null; $this->getAttackDataEngine()->close(); } /** * Clean up old expired IP blocks. */ public function vacuum() { if (!wfWAFStorageFile::allowFileWriting()) { return false; } $this->open(); $readPointer = wfWAFUtils::strlen(self::LOG_FILE_HEADER); $writePointer = wfWAFUtils::strlen(self::LOG_FILE_HEADER); fseek($this->ipCacheFileHandle, $readPointer, SEEK_SET); self::lock($this->ipCacheFileHandle, LOCK_EX); $ipCacheRow = fread($this->ipCacheFileHandle, self::IP_BLOCK_RECORD_SIZE); while (!feof($this->ipCacheFileHandle)) { $unpacked = @unpack('V', wfWAFUtils::substr($ipCacheRow, 16, 4)); if (is_array($unpacked)) { $expires = array_shift($unpacked); if ($expires >= time()) { fseek($this->ipCacheFileHandle, $writePointer, SEEK_SET); fwrite($this->ipCacheFileHandle, $ipCacheRow); $writePointer += self::IP_BLOCK_RECORD_SIZE; } } $readPointer += self::IP_BLOCK_RECORD_SIZE; fseek($this->ipCacheFileHandle, $readPointer, SEEK_SET); $ipCacheRow = fread($this->ipCacheFileHandle, self::IP_BLOCK_RECORD_SIZE); } ftruncate($this->ipCacheFileHandle, $writePointer); fflush($this->ipCacheFileHandle); self::lock($this->ipCacheFileHandle, LOCK_UN); } /** * Remove all existing IP blocks. */ public function purgeIPBlocks($types = wfWAFStorageInterface::IP_BLOCKS_ALL) { if (!wfWAFStorageFile::allowFileWriting()) { return false; } $this->open(); $readPointer = wfWAFUtils::strlen(self::LOG_FILE_HEADER); $writePointer = wfWAFUtils::strlen(self::LOG_FILE_HEADER); fseek($this->ipCacheFileHandle, $readPointer, SEEK_SET); self::lock($this->ipCacheFileHandle, LOCK_EX); if ($types !== wfWAFStorageInterface::IP_BLOCKS_ALL) { $ipCacheRow = fread($this->ipCacheFileHandle, self::IP_BLOCK_RECORD_SIZE); while (!feof($this->ipCacheFileHandle)) { $unpacked = @unpack('Vexpires/Vtype', wfWAFUtils::substr($ipCacheRow, 16, 8)); if (is_array($unpacked)) { $type = $unpacked['type']; if (($type & $types) == 0) { fseek($this->ipCacheFileHandle, $writePointer, SEEK_SET); fwrite($this->ipCacheFileHandle, $ipCacheRow); $writePointer += self::IP_BLOCK_RECORD_SIZE; } } $readPointer += self::IP_BLOCK_RECORD_SIZE; fseek($this->ipCacheFileHandle, $readPointer, SEEK_SET); $ipCacheRow = fread($this->ipCacheFileHandle, self::IP_BLOCK_RECORD_SIZE); } } ftruncate($this->ipCacheFileHandle, $writePointer); fflush($this->ipCacheFileHandle); self::lock($this->ipCacheFileHandle, LOCK_UN); } /** * @param string $key * @param mixed $default * @return mixed */ public function getConfig($key, $default = null, $category = '') { if (!isset($this->data[$category]) || $this->data[$category] === false) { $this->fetchConfigData($category); } return (is_array($this->data[$category]) && array_key_exists($key, $this->data[$category])) ? $this->data[$category][$key] : $default; } /** * @param string $key * @param mixed $value */ public function setConfig($key, $value, $category = '') { if (!isset($this->data[$category]) || $this->data[$category] === false) { $this->fetchConfigData($category); } if (is_array($this->data[$category])) { if (!isset($this->dataChanged[$category]) && ( (array_key_exists($key, $this->data[$category]) && $this->data[$category][$key] !== $value) || !array_key_exists($key, $this->data[$category]) ) ) { $this->dataChanged[$category] = array($key, true); register_shutdown_function(array($this, 'saveConfig'), $category); } $this->data[$category][$key] = $value; } } /** * @param string $key */ public function unsetConfig($key, $category = '') { if (!isset($this->data[$category]) || $this->data[$category] === false) { $this->fetchConfigData($category); } if (!isset($this->dataChanged[$category]) && is_array($this->data[$category]) && array_key_exists($key, $this->data[$category])) { $this->dataChanged[$category] = array($key, true); register_shutdown_function(array($this, 'saveConfig'), $category); } unset($this->data[$category][$key]); } /** * @throws wfWAFStorageFileException */ public function fetchConfigData($category = '', $redoing = false) { unset($this->configFileHandles[$category]); $this->open($category); self::lock($this->configFileHandles[$category], LOCK_SH); $i = 0; // Attempt to read contents of the config file. This could be in the middle of a write, so we account for it and // wait for the operation to complete. fseek($this->configFileHandles[$category], wfWAFUtils::strlen(self::LOG_FILE_HEADER), SEEK_SET); $serializedData = ''; while (!feof($this->configFileHandles[$category])) { $serializedData .= fread($this->configFileHandles[$category], 1024); } if (wfWAFUtils::substr($serializedData, 0, 1) == '*') { $serializedData = wfWAFUtils::substr($serializedData, wfWAFUtils::strlen(self::LOG_INFO_HEADER)); } $this->data[$category] = @unserialize($serializedData); if ($this->data[$category] === false) { if (!empty($category) && !$redoing) { $this->regenerateConfigFile($category); $this->fetchConfigData($category, true); return; } throw new wfWAFStorageFileConfigException('Error reading Wordfence Firewall config data, configuration file could be corrupted or inaccessible. Path: ' . $this->pathForConfig($category)); } self::lock($this->configFileHandles[$category], LOCK_UN); } /** * @throws wfWAFStorageFileException */ public function saveConfig($category = '') { if (!wfWAFStorageFile::allowFileWriting()) { return false; } if (WFWAF_DEBUG) { error_log('Saving WAF config for change in key ' . $this->dataChanged[$category][0] . ', value: ' . ((is_object($this->data[$category][$this->dataChanged[$category][0]]) || $this->dataChanged[$category][0] === 'cron') ? gettype($this->data[$category][$this->dataChanged[$category][0]]) : var_export($this->data[$category][$this->dataChanged[$category][0]], true))); } if ($this->uninstalled) { return; } if (WFWAF_IS_WINDOWS) { self::lock($this->configFileHandles[$category], LOCK_UN); fclose($this->configFileHandles[$category]); file_put_contents($this->pathForConfig($category), self::LOG_FILE_HEADER . self::LOG_INFO_HEADER . serialize($this->data[$category]), LOCK_EX); } else { wfWAFStorageFile::atomicFilePutContents($this->pathForConfig($category), self::LOG_FILE_HEADER . self::LOG_INFO_HEADER . serialize($this->data[$category])); } if (WFWAF_IS_WINDOWS) { $this->configFileHandles[$category] = fopen($this->pathForConfig($category), 'r+'); } } /** * */ public function uninstall() { $this->uninstalled = true; $this->close(); foreach ($this->configFileHandles as $key => $handle) { $this->close($key); } $this->removeConfigFiles(); @unlink($this->getAttackDataFile()); @unlink($this->getIPCacheFile()); @unlink($this->getRulesDSLCacheFile()); } public function fileList() { $fileList = array(); $fileList[] = $this->getAttackDataFile(); $fileList[] = $this->getIPCacheFile(); if (defined('WFWAF_DEBUG') && WFWAF_DEBUG) { $fileList[] = $this->getRulesDSLCacheFile(); } $fileList[] = $this->getConfigFile(); $configDir = dirname($this->getConfigFile()); $dir = opendir($configDir); if ($dir) { $escapedPath = preg_quote($this->_normalizeSlashes($this->getConfigFile()), '/'); $components = explode('\\/', $escapedPath); $pattern = $components[count($components) - 1]; if (preg_match('/^(.+?)(\\\..+$|$)/i', $pattern, $matches)) { $pattern = $matches[1] . '\\-[a-z0-9]+' . $matches[2]; //Results in a pattern like config\-[a-z0-9]\.php } while ($path = readdir($dir)) { if ($path == '.' || $path == '..') { continue; } if (is_dir($configDir . '/' . $path)) { continue; } if (preg_match('/^' . $pattern . '$/i', $path)) { $fileList[] = $configDir . '/' . $path; } } closedir($dir); } return $fileList; } public function removeConfigFiles() { @unlink($this->getConfigFile()); $configDir = dirname($this->getConfigFile()); $dir = opendir($configDir); if ($dir) { $escapedPath = preg_quote($this->_normalizeSlashes($this->getConfigFile()), '/'); $components = explode('\\/', $escapedPath); $pattern = $components[count($components) - 1]; if (preg_match('/^(.+?)(\\\..+$|$)/i', $pattern, $matches)) { $pattern = $matches[1] . '\\-[a-z0-9]+' . $matches[2]; //Results in a pattern like config\-[a-z0-9]\.php } while ($path = readdir($dir)) { if ($path == '.' || $path == '..') { continue; } if (is_dir($configDir . '/' . $path)) { continue; } if (preg_match('/^' . $pattern . '$/i', $path)) { @unlink($configDir . '/' . $path); } } closedir($dir); } } public function regenerateConfigFile($category = '') { $path = $this->pathForConfig($category); if (file_exists($path)) { @unlink($path); } $this->_open($path, $this->configFileHandles, $category, self::LOG_FILE_HEADER . self::LOG_INFO_HEADER . serialize($this->getDefaultConfiguration($category)), false); } protected function _normalizeSlashes($path) { return str_replace('\\', '/', $path); //The same sanitation performed by WordPress -- PHP can handle both, but it simplifies path processing } /** * @return bool */ public function isInLearningMode() { if ($this->getConfig('wafStatus', '') == 'learning-mode') { if ($this->getConfig('learningModeGracePeriodEnabled', false)) { if ($this->getConfig('learningModeGracePeriod', 0) > time()) { return true; } } else { return true; } } return false; } public function isDisabled() { return $this->getConfig('wafStatus', '') === 'disabled' || $this->getConfig('wafDisabled', 0); } /** * @return array */ public function getDefaultConfiguration($category = '') { if (empty($category)) { return array( 'wafStatus' => 'learning-mode', 'learningModeGracePeriodEnabled' => 1, 'learningModeGracePeriod' => time() + (86400 * 7), 'authKey' => wfWAFUtils::getRandomString(64), ); } return array(); } /** * @return mixed */ public function getConfigFile() { return $this->configFile; } /** * @param mixed $configFile */ public function setConfigFile($configFile) { $this->configFile = $configFile; } /** * @return string|null */ public function getAttackDataFile() { return $this->attackDataFile; } /** * @param string|null $attackDataFile */ public function setAttackDataFile($attackDataFile) { $this->attackDataFile = $attackDataFile; } /** * @return string|null */ public function getIPCacheFile() { return $this->ipCacheFile; } /** * @param string|null $ipCacheFile */ public function setIPCacheFile($ipCacheFile) { $this->ipCacheFile = $ipCacheFile; } /** * @return mixed */ public function getRulesDSLCacheFile() { return $this->rulesDSLCacheFile; } /** * @param mixed $rulesDSLCacheFile */ public function setRulesDSLCacheFile($rulesDSLCacheFile) { $this->rulesDSLCacheFile = $rulesDSLCacheFile; } /** * param key, param value, request string * * @var array */ private $rowsToB64 = array(3, 4, 7); /** * @param $row * @return bool|string */ private function serializeRow($row) { foreach ($this->rowsToB64 as $index) { if (array_key_exists($index, $row)) { $row[$index] = base64_encode((string) $row[$index]); } } $row = wfWAFUtils::json_encode($row); if (is_string($row) && wfWAFUtils::strlen($row) > 0) { return $row; } return false; } /** * @param $row * @return array|bool|mixed|object */ private function unserializeRow($row) { if ($row) { $json = wfWAFUtils::json_decode($row, true); if (is_array($json)) { foreach ($this->rowsToB64 as $index) { if (array_key_exists($index, $json)) { $json[$index] = base64_decode((string) $json[$index]); } } return $json; } } return false; } /** * @return wfWAFAttackDataStorageFileEngine */ public function getAttackDataEngine() { return $this->attackDataEngine; } /** * @param wfWAFAttackDataStorageFileEngine $attackDataEngine */ public function setAttackDataEngine($attackDataEngine) { $this->attackDataEngine = $attackDataEngine; } public function getRules() { throw new wfWAFStorageFileException('wfWAFStorageFile::getRules not implemented.'); } public function setRules($rules) { throw new wfWAFStorageFileException('wfWAFStorageFile::getRules not implemented.'); } public function needsInitialRules() { if (file_exists($this->getRulesFile())) { return is_writeable($this->getRulesFile()) && !filesize($this->getRulesFile()); } else { return is_writeable(dirname($this->getRulesFile())); } } /** * @return mixed */ public function getRulesFile() { return $this->rulesFile; } /** * @param mixed $rulesFile */ public function setRulesFile($rulesFile) { $this->rulesFile = $rulesFile; } public function getDescription() { return __('file system', 'wordfence'); } } class wfWAFAttackDataStorageFileEngine { const MAX_ROWS = 10000; const MAX_READ_LENGTH = 51200; const FILE_SIGNATURE = "wfWAF\x00\x00\x00"; /** * @param string|float|null $microtime * @return string */ public static function packMicrotime($microtime = null) { if ($microtime === null) { $microtime = microtime(); } if (is_string($microtime)) { list($msec, $sec) = explode(' ', $microtime, 2); } else if (is_float($microtime)) { list($sec, $msec) = explode('.', (string) $microtime, 2); $msec = '0.' . $msec; } else { throw new InvalidArgumentException(__METHOD__ . ' $microtime expected to be string or float, received ' . gettype($microtime)); } $msec = $msec * 1000000; return pack('V*', $sec, $msec); } /** * @param string $binary * @return string */ public static function unpackMicrotime($binary) { if (!is_string($binary) || wfWAFUtils::strlen($binary) !== 8) { throw new InvalidArgumentException(__METHOD__ . ' $binary expected to be string with length of 8, received ' . gettype($binary) . (is_string($binary) ? ' of length ' . wfWAFUtils::strlen($binary) : '')); } list(, $attackLogSeconds, $attackLogMicroseconds) = @unpack('V*', $binary); return sprintf('%d.%s', $attackLogSeconds, str_pad($attackLogMicroseconds, 6, '0', STR_PAD_LEFT)); } public static function getCompressionAlgos() { static $compressionFunctions; if ($compressionFunctions === null) { $compressionFunctions = array( new wfWAFStorageFileCompressionGZDeflate(), new wfWAFStorageFileCompressionGZCompress(), new wfWAFStorageFileCompressionGZEncode(), ); } return $compressionFunctions; } /** * @param string $decompressed * @return mixed */ public static function compress($decompressed) { if (empty($decompressed)) return $decompressed; $compressionAlgos = self::getCompressionAlgos(); /** @var wfWAFStorageFileCompressionAlgo $algo */ foreach ($compressionAlgos as $algo) { if ($algo->isUsable() && ($compressed = $algo->testCompression($decompressed)) !== false) { return $compressed; } } return $decompressed; } /** * @param string $compressed * @return mixed */ public static function decompress($compressed) { if (empty($compressed)) return $compressed; $compressionAlgos = self::getCompressionAlgos(); /** @var wfWAFStorageFileCompressionAlgo $algo */ foreach ($compressionAlgos as $algo) { if ($algo->isUsable() && ($decompressed = $algo->decompress($compressed)) !== false) { return $decompressed; } } return $compressed; } private $file; private $fileHandle; private $header = array(); private $offsetTable = array(); /** * wfWAFStorageFileEngine constructor. * @param string $file */ public function __construct($file) { $this->file = $file; } /** * @throws wfWAFStorageFileException */ public function open() { if (is_resource($this->fileHandle)) { return; } if (!file_exists($this->file)) { @file_put_contents($this->file, $this->getDefaultHeader(), LOCK_EX); } if (wfWAFStorageFile::allowFileWriting()) { @chmod($this->file, wfWAFStorageFile::permissions()); } $this->fileHandle = @fopen($this->file, 'r+'); if (!$this->fileHandle) { throw new wfWAFStorageFileException('Unable to open ' . $this->file . ' for reading and writing.'); } } /** * */ public function close() { if (is_resource($this->fileHandle)) { fclose($this->fileHandle); } $this->fileHandle = null; $this->header = array(); $this->offsetTable = array(); } /** * @param int $offset * @return int */ private function seek($offset) { return fseek($this->fileHandle, $offset, SEEK_SET); } /** * @return int */ private function seekToData() { return $this->seek(wfWAFUtils::strlen($this->getHeaderLength())); } /** * @param int $length * @return string */ private function read($length) { if ($length > self::MAX_READ_LENGTH) { $length = self::MAX_READ_LENGTH; } return fread($this->fileHandle, $length); } /** * @param string $data * @return int */ private function write($data) { return fwrite($this->fileHandle, $data); } /** * @return bool */ private function lockRead() { return wfWAFStorageFile::lock($this->fileHandle, LOCK_SH); } /** * @return bool */ private function lockWrite() { return wfWAFStorageFile::lock($this->fileHandle, LOCK_EX); } /** * @return bool */ private function unlock() { return wfWAFStorageFile::lock($this->fileHandle, LOCK_UN); } /** * @return int */ public function getHeaderLength() { return wfWAFUtils::strlen($this->getDefaultHeader()); } /** * @return string */ public function getDefaultHeader() { /** * 51 PHP die() header * 8 Signature * 8 oldest 64bit timestamp * 8 newest 64bit timestamp * 4 row count * 1600 offset table * 1 last length */ $headerLength = wfWAFUtils::strlen(wfWAFStorageFile::LOG_FILE_HEADER) + wfWAFUtils::strlen(self::FILE_SIGNATURE) + 8 + 8 + 4 + (self::MAX_ROWS * 4); return wfWAFStorageFile::LOG_FILE_HEADER . self::FILE_SIGNATURE . str_repeat("\x00", 8 + 8 + 4) . pack('V', $headerLength) . str_repeat("\x00", self::MAX_ROWS * 4); } /** * @throws wfWAFStorageFileException */ public function unpackHeader() { if ($this->header) { return $this->header; } $this->open(); $this->header = array(); $this->seek(0); $this->lockRead(); $this->header['phpHeader'] = $this->read(wfWAFUtils::strlen(wfWAFStorageFile::LOG_FILE_HEADER)); $this->header['signature'] = $this->read(wfWAFUtils::strlen(self::FILE_SIGNATURE)); if ($this->header['phpHeader'] !== wfWAFStorageFile::LOG_FILE_HEADER || $this->header['signature'] !== self::FILE_SIGNATURE) { $this->unlock(); $this->truncate(); $this->lockRead(); $this->seek(0); $this->lockRead(); $this->header['phpHeader'] = $this->read(wfWAFUtils::strlen(wfWAFStorageFile::LOG_FILE_HEADER)); $this->header['signature'] = $this->read(wfWAFUtils::strlen(self::FILE_SIGNATURE)); } $this->header['oldestTimestamp'] = self::unpackMicrotime($this->read(8)); $this->header['newestTimestamp'] = self::unpackMicrotime($this->read(8)); list(, $this->header['rowCount']) = @unpack('V', $this->read(4)); $this->header['offsetTable'] = $this->unpackOffsetTable(); $this->unlock(); return $this->header; } /** * @return array */ private function unpackOffsetTable() { if ($this->offsetTable) { return $this->offsetTable; } $rowCount = min($this->header['rowCount'], self::MAX_ROWS); $this->seek(wfWAFUtils::strlen(wfWAFStorageFile::LOG_FILE_HEADER) + wfWAFUtils::strlen(self::FILE_SIGNATURE) + 8 + 8 + 4); $offsetTableBinary = $this->read(($rowCount + 1) * 4); $this->offsetTable = array_values(@unpack('V*', $offsetTableBinary)); return $this->offsetTable; } /** * @param callable $callback */ public function scanRows($callback) { if (!is_callable($callback)) { throw new InvalidArgumentException(__METHOD__ . ' $callback expected to be callable, received ' . gettype($callback)); } $this->open(); $header = $this->unpackHeader(); $this->seekToData(); for ($index = 0; $index < $header['rowCount'] && $index < self::MAX_ROWS; $index++) { $offset = $header['offsetTable'][$index]; $length = $header['offsetTable'][$index + 1] - $offset; if ($length > self::MAX_READ_LENGTH) { $length = self::MAX_READ_LENGTH; } $result = call_user_func($callback, $this->fileHandle, $offset, $length); if ($result === false) { break; } } } /** * @param callable $callback */ public function scanRowsReverse($callback) { if (!is_callable($callback)) { throw new InvalidArgumentException(__METHOD__ . ' $callback expected to be callable, received ' . gettype($callback)); } $this->open(); $header = $this->unpackHeader(); // $this->seekToData(); for ($index = min($header['rowCount'], self::MAX_ROWS) - 1; $index >= 0; $index--) { $offset = $header['offsetTable'][$index]; $length = $header['offsetTable'][$index + 1] - $offset; if ($length > self::MAX_READ_LENGTH) { $length = self::MAX_READ_LENGTH; } $result = call_user_func($callback, $this->fileHandle, $offset, $length); if ($result === false) { break; } } } /** * @param $index * @return wfWAFAttackDataStorageFileEngineRow * @throws wfWAFStorageFileException */ public function getRow($index) { $this->open(); $this->header = array(); $this->offsetTable = array(); $header = $this->unpackHeader(); $this->seekToData(); if ($index < $header['rowCount'] && $index >= 0) { $offset = $header['offsetTable'][$index]; $length = $header['offsetTable'][$index + 1] - $offset; } else { return false; } $this->seek($offset); $this->lockRead(); $binary = $this->read($length); $this->unlock(); return wfWAFAttackDataStorageFileEngineRow::unpack($binary); } /** * @return mixed * @throws wfWAFStorageFileException */ public function getRowCount() { $this->open(); $header = $this->unpackHeader(); return $header['rowCount']; } /** * @param wfWAFAttackDataStorageFileEngineRow $row * @return bool * @throws wfWAFStorageFileException */ public function addRow($row) { if (!wfWAFStorageFile::allowFileWriting()) { return false; } $this->open(); $this->seek(wfWAFUtils::strlen(wfWAFStorageFile::LOG_FILE_HEADER) + wfWAFUtils::strlen(self::FILE_SIGNATURE) + 8 + 8); $this->lockRead(); list(, $rowCount) = @unpack('V', $this->read(4)); $this->unlock(); if ($rowCount >= self::MAX_ROWS) { return false; } $this->lockWrite(); //Re-read the row count in case it changed between releasing the shared lock and getting the exclusive $this->seek(wfWAFUtils::strlen(wfWAFStorageFile::LOG_FILE_HEADER) + wfWAFUtils::strlen(self::FILE_SIGNATURE) + 8 + 8); list(, $rowCount) = @unpack('V', $this->read(4)); //Start the write $this->header = array(); $this->offsetTable = array(); $this->seek(wfWAFUtils::strlen(wfWAFStorageFile::LOG_FILE_HEADER) + wfWAFUtils::strlen(self::FILE_SIGNATURE) + 8 + 8 + 4 + ($rowCount * 4)); list(, $nextRowOffset) = @unpack('V', $this->read(4)); $rowString = $row->pack(); // Update offset table $this->seek(wfWAFUtils::strlen(wfWAFStorageFile::LOG_FILE_HEADER) + wfWAFUtils::strlen(self::FILE_SIGNATURE) + 8 + 8 + 4 + (($rowCount + 1) * 4)); $this->write(pack('V', $nextRowOffset + wfWAFUtils::strlen($rowString))); // Update rowCount $this->seek(wfWAFUtils::strlen(wfWAFStorageFile::LOG_FILE_HEADER) + wfWAFUtils::strlen(self::FILE_SIGNATURE) + 8 + 8); $this->write(pack('V', $rowCount + 1)); // Write data $this->seek($nextRowOffset); $packedTimestamp = wfWAFUtils::substr($rowString, 0, 8); $this->write($rowString); // Update oldest timestamp if ($rowCount === 0) { $this->seek(wfWAFUtils::strlen(wfWAFStorageFile::LOG_FILE_HEADER) + wfWAFUtils::strlen(self::FILE_SIGNATURE)); $this->write($packedTimestamp); } // Update newest timestamp $this->seek(wfWAFUtils::strlen(wfWAFStorageFile::LOG_FILE_HEADER) + wfWAFUtils::strlen(self::FILE_SIGNATURE) + 8); $this->write($packedTimestamp); $this->unlock(); $this->header = array(); $this->offsetTable = array(); return true; } /** * */ public function truncate() { if (!wfWAFStorageFile::allowFileWriting()) { return false; } $defaultHeader = $this->getDefaultHeader(); $this->close(); if (WFWAF_IS_WINDOWS) { file_put_contents($this->getFile(), $defaultHeader, LOCK_EX); } else { wfWAFStorageFile::atomicFilePutContents($this->getFile(), $defaultHeader, 'attack'); } $this->header = array(); $this->offsetTable = array(); $this->open(); } /** * @return mixed * @throws wfWAFStorageFileException */ public function getOldestTimestamp() { $this->open(); if ($this->getRowCount() === 0) { return false; } $header = $this->unpackHeader(); return $header['oldestTimestamp']; } /** * @return mixed * @throws wfWAFStorageFileException */ public function getNewestTimestamp() { $this->open(); if ($this->getRowCount() === 0) { return false; } $header = $this->unpackHeader(); return $header['newestTimestamp']; } /** * @return string */ public function getFile() { return $this->file; } /** * @param string $file */ public function setFile($file) { $this->file = $file; } } interface wfWAFAttackDataStorageFileEngineScanRowCallback { public function scanRow($handle, $offset, $length); } class wfWAFAttackDataStorageFileEngineResultSet implements wfWAFAttackDataStorageFileEngineScanRowCallback { private $rows = array(); public function scanRow($handle, $offset, $length) { fseek($handle, $offset); $binary = fread($handle, $length); $this->rows = wfWAFAttackDataStorageFileEngineRow::unpack($binary); } /** * @return array */ public function getRows() { return $this->rows; } } class wfWAFAttackDataStorageFileEngineScanRowAttackDataNewer implements wfWAFAttackDataStorageFileEngineScanRowCallback { /** * @var int */ private $newerThan; /** * wfWAFStorageFileEngineScanRowAttackDataNewer constructor. * @param int $newerThan */ public function __construct($newerThan) { $this->newerThan = $newerThan; } /** * @param resource $handle * @param int $offset * @param int $length * @return bool */ public function scanRow($handle, $offset, $length) { $attackLogTimeBin = fread($handle, 8); list(, $attackLogSeconds, $attackLogMicroseconds) = @unpack('VV', $attackLogTimeBin); $attackLogTime = $attackLogSeconds . '.' . $attackLogMicroseconds; return $this->newerThan < $attackLogTime; } } class wfWAFAttackDataStorageFileEngineScanRowAttackDataOlder implements wfWAFAttackDataStorageFileEngineScanRowCallback { /** * @var int */ private $olderThan; /** * wfWAFStorageFileEngineScanRowAttackDataNewer constructor. * @param int $olderThan */ public function __construct($olderThan) { $this->olderThan = $olderThan; } /** * @param resource $handle * @param int $offset * @param int $length * @return bool */ public function scanRow($handle, $offset, $length) { $attackLogTimeBin = fread($handle, 8); list(, $attackLogSeconds, $attackLogMicroseconds) = @unpack('VV', $attackLogTimeBin); $attackLogTime = $attackLogSeconds . '.' . $attackLogMicroseconds; return $this->olderThan > $attackLogTime; } } class wfWAFAttackDataStorageFileEngineRow { /** * @param string $binary * @return wfWAFAttackDataStorageFileEngineRow */ public static function unpack($binary) { $attackLogTime = wfWAFAttackDataStorageFileEngine::unpackMicrotime(wfWAFUtils::substr($binary, 0, 8)); $data = wfWAFAttackDataStorageFileEngine::decompress(wfWAFUtils::substr($binary, 8)); return new self($attackLogTime, $data); } /** * @var float|string */ private $timestamp; /** * @var string */ private $data; /** * @param float $timestamp * @param string $data */ public function __construct($timestamp, $data) { $this->timestamp = $timestamp; $this->data = $data; } /** * @return string */ public function pack() { return wfWAFAttackDataStorageFileEngine::packMicrotime($this->getTimestamp()) . wfWAFAttackDataStorageFileEngine::compress($this->getData()); } /** * @return float|string */ public function getTimestamp() { return $this->timestamp; } /** * @param float|string $timestamp */ public function setTimestamp($timestamp) { $this->timestamp = $timestamp; } /** * @return string */ public function getData() { return $this->data; } /** * @param string $data */ public function setData($data) { $this->data = $data; } } abstract class wfWAFStorageFileCompressionAlgo { abstract public function isUsable(); abstract public function compress($string); abstract public function decompress($binary); /** * @param string $string * @return bool */ public function testCompression($string) { $compressed = $this->compress($string); if ($string === $this->decompress($compressed)) { return $compressed; } return false; } } class wfWAFStorageFileCompressionGZDeflate extends wfWAFStorageFileCompressionAlgo { public function isUsable() { return function_exists('gzinflate') && function_exists('gzdeflate'); } public function compress($string) { return @gzdeflate($string); } public function decompress($binary) { return @gzinflate($binary); } } class wfWAFStorageFileCompressionGZCompress extends wfWAFStorageFileCompressionAlgo { public function isUsable() { return function_exists('gzuncompress') && function_exists('gzcompress'); } public function compress($string) { return @gzcompress($string); } public function decompress($binary) { return @gzuncompress($binary); } } class wfWAFStorageFileCompressionGZEncode extends wfWAFStorageFileCompressionAlgo { public function isUsable() { return function_exists('gzencode') && function_exists('gzdecode'); } public function compress($string) { return @gzencode($string); } public function decompress($binary) { return @gzdecode($binary); } } class wfWAFStorageFileException extends wfWAFException { } class wfWAFStorageFileConfigException extends wfWAFStorageFileException { } }