: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$response = array('uninstallationFailed' => 1, 'html' => $html, 'serverConfiguration' => $_POST['serverConfiguration']);
if (isset($credentials) && is_array($credentials)) {
$salt = wp_salt('logged_in');
$json = json_encode($credentials);
$encrypted = wfUtils::encrypt($json);
$signature = hash_hmac('sha256', $encrypted, $salt);
$response['credentials'] = $encrypted;
$response['credentialsSignature'] = $signature;
$helper->performAutoPrependFileRemoval($wp_filesystem);
$nonce = bin2hex(wfWAFUtils::random_bytes(32));
wfConfig::set('wafStatusCallbackNonce', $nonce);
$verifyURL = add_query_arg(array('action' => 'wordfence_wafStatus', 'nonce' => $nonce), $ajaxURL);
$response = wp_remote_get($verifyURL, array('headers' => array('Referer' => false/*, 'Cookie' => 'XDEBUG_SESSION=1'*/)));
$subdirectory = WFWAF_SUBDIRECTORY_INSTALL;
if (!is_wp_error($response)) {
$wafStatus = @json_decode(wp_remote_retrieve_body($response), true);
if (isset($wafStatus['active']) && isset($wafStatus['subdirectory'])) {
$active = $wafStatus['active'] && !$wafStatus['subdirectory'];
$subdirectory = $wafStatus['subdirectory'];
$html = wfView::create('waf/waf-modal-wrapper', array(
'title' => __('Uninstallation Complete', 'wordfence'),
'html' => wfView::create('waf/waf-uninstall-success', array('active' => $active, 'subdirectory' => $subdirectory))->render(),
'footerButtonTitle' => __('Close', 'wordfence'),
return array('ok' => 1, 'html' => $html);
catch (wfWAFAutoPrependHelperException $e) {
$installError = "<p>" . $e->getMessage() . "</p>";
$html = wfView::create('waf/waf-modal-wrapper', array(
'title' => __('Uninstallation Failed', 'wordfence'),
'helpHTML' => wp_kses(sprintf(/* translators: Support URL. */ __('If you cannot complete the uninstall process, <a target="_blank" rel="noopener noreferrer" href="%s">click here for help<span class="screen-reader-text"> (opens in new tab)</span></a>', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_FIREWALL_WAF_REMOVE_MANUALLY)), array('a' => array('href' => array(), 'target' => array(), 'rel' => array()), 'span' => array('class' => array()))),
'footerButtonTitle' => __('Cancel', 'wordfence'),
return array('uninstallationFailed' => 1, 'html' => $html);
public static function actionUserRegistration($user_id) {
if (wfUtils::isAdmin($user_id) && ($request = self::getLog()->getCurrentRequest())) {
//self::getLog()->canLogHit = true;
$request->action = 'user:adminCreate';
public static function actionPasswordReset($user = null, $new_pass = null) {
if ($request = self::getLog()->getCurrentRequest()) {
//self::getLog()->canLogHit = true;
$request->action = 'user:passwordReset';
public static function trimWfHits($force = false) {
if(!$force && self::isApiDelayed())
$lastAggregation = wfConfig::get('lastBlockAggregation', 0);
$table_wfHits = wfDB::networkTable('wfHits');
$count = $wfdb->querySingle("select count(*) as cnt from {$table_wfHits}");
$liveTrafficMaxRows = absint(wfConfig::get('liveTraf_maxRows', 2000));
if ($count > $liveTrafficMaxRows * 10) {
self::_aggregateBlockStats($lastAggregation);
$wfdb->truncate($table_wfHits); //So we don't slow down sites that have very large wfHits tables
else if ($count > $liveTrafficMaxRows) {
self::_aggregateBlockStats($lastAggregation);
$wfdb->queryWrite("delete from {$table_wfHits} order by id asc limit %d", ($count - $liveTrafficMaxRows) + ($liveTrafficMaxRows * .2));
else if ($lastAggregation < (time() - 86400)) {
self::_aggregateBlockStats($lastAggregation);
$maxAge = wfConfig::get('liveTraf_maxAge', 30);
if ($maxAge <= 0 || $maxAge > 30) {
$wfdb->queryWrite("DELETE FROM {$table_wfHits} WHERE ctime < %d", time() - ($maxAge * 86400));
private static function _aggregateBlockStats($since = false) {
if (!wfConfig::get('other_WFNet', true)) {
$since = wfConfig::get('lastBlockAggregation', 0);
$hitsTable = wfDB::networkTable('wfHits');
$query = $wpdb->prepare("SELECT COUNT(*) AS cnt, CASE WHEN (jsRun = 1 OR userID > 0) THEN 1 ELSE 0 END AS isHuman, statusCode FROM {$hitsTable} WHERE ctime > %d GROUP BY isHuman, statusCode", $since);
$rows = $wpdb->get_results($query, ARRAY_A);
$api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
$api->call('aggregate_stats', array(), array('stats' => json_encode($rows)));
wfConfig::set('lastBlockAggregation', time());
private static function isApiDelayed() {
return wfConfig::get('apiDelayedUntil', 0) > time();
private static function delaySendAttackData($until) {
wfConfig::set('apiDelayedUntil', $until);
self::scheduleSendAttackData($until);
private static function scheduleSendAttackData($timeToSend = null) {
if ($timeToSend === null) {
$timeToSend = time() + (60 * 5);
$notMainSite = is_multisite() && !is_main_site();
switch_to_blog($current_site->blog_id);
if (!wp_next_scheduled('wordfence_processAttackData')) {
wp_schedule_single_event($timeToSend, 'wordfence_processAttackData');
private static function truncateWafFailures() {
wfDB::shared()->truncate(wfDB::networkTable('wfWafFailures'));
private static function loadWafFailures(&$purgeCallable = null) {
$table = wfDB::networkTable('wfWafFailures');
throwable AS latest_throwable,
UNIX_TIMESTAMP(latest_occurrence) AS latest_occurrence,
MAX(timestamp) AS latest_occurrence,
) aggregate ON failures.id = aggregate.max_id
$results = $wpdb->get_results($query);
foreach ($results as $row) {
$maxId = max($maxId, $row->id);
$purgeCallable = function() { /* Nothing to delete */ };
$purgeCallable = function() use ($table, $maxId, $wpdb) {
"DELETE FROM {$table} WHERE id <= %d",
public static function processAttackData() {
$table_wfHits = wfDB::networkTable('wfHits');
if (!defined('DONOTCACHEDB')) { define('DONOTCACHEDB', true); }
$waf = wfWAF::getInstance();
if ($waf->getStorageEngine()->getConfig('attackDataKey', false) === false) {
$waf->getStorageEngine()->setConfig('attackDataKey', mt_rand(0, 0xfff));
//Send alert email if needed
if (wfConfig::get('wafAlertOnAttacks')) {
$alertInterval = wfConfig::get('wafAlertInterval', 0);
$cutoffTime = max(time() - $alertInterval, wfConfig::get('wafAlertLastSendTime'));
$wafAlertWhitelist = wfConfig::get('wafAlertWhitelist', '');
$wafAlertWhitelist = preg_split("/[,\r\n]+/", $wafAlertWhitelist);
foreach ($wafAlertWhitelist as $index => &$entry) {
if (empty($entry) || (!preg_match('/^(?:\d{1,3}(?:\.|$)){4}/', $entry) && !preg_match('/^((?:[\da-f]{1,4}(?::|)){0,8})(::)?((?:[\da-f]{1,4}(?::|)){0,8})$/i', $entry))) {
unset($wafAlertWhitelist[$index]);
$packed = @wfUtils::inet_pton($entry);
unset($wafAlertWhitelist[$index]);
$entry = bin2hex($packed);
$wafAlertWhitelist = array_filter($wafAlertWhitelist);
$attackDataQuery = $wpdb->prepare(
"SELECT * FROM {$table_wfHits}
WHERE action = 'blocked:waf' " .
(count($wafAlertWhitelist) ? "AND HEX(IP) NOT IN (" . implode(", ", array_fill(0, count($wafAlertWhitelist), '%s')) . ")" : "")
. " AND attackLogTime > %f
ORDER BY attackLogTime DESC
array_merge($wafAlertWhitelist, array(sprintf('%.6f', $cutoffTime))));
$attackDataCountQuery = str_replace(
"ORDER BY attackLogTime DESC",
array( "SELECT COUNT(*) FROM", "", "" ), $attackDataQuery
$attackData = $wpdb->get_results($attackDataQuery);
$attackCount = $wpdb->get_var($attackDataCountQuery);
unset( $attackDataQuery, $attackDataCountQuery );
$threshold = (int) wfConfig::get('wafAlertThreshold');
if ($attackCount >= $threshold) {
$durationMessage = wfUtils::makeDuration($alertInterval);
/* translators: 1. Number of attacks/blocks. 2. Time since. */
__('The Wordfence Web Application Firewall has blocked %1$d attacks over the last %2$s.', 'wordfence'),
$message .= __('Wordfence is blocking these attacks, and we\'re sending this notice to make you aware that there is a higher volume of the attacks than usual. Additionally, the Wordfence Real-Time IP Blocklist can block known attackers\' IP addresses automatically for Premium users, including any probing requests that may not be malicious on their own. All Wordfence users can also opt to block the attacking IPs manually if desired. As always, be sure to watch your scan results and keep your plugins, themes and WordPress core version updated.', 'wordfence');
$message .= __('Below is a sample of these recent attacks:', 'wordfence');
$dateMax = $ipMax = $countryMax = 0;
foreach ($attackData as $row) {
$actionData = json_decode($row->actionData, true);
if (!is_array($actionData) || !isset($actionData['paramKey']) || !isset($actionData['paramValue'])) {
if (isset($actionData['failedRules']) && $actionData['failedRules'] == 'blocked') {
$row->longDescription = __("Blocked because the IP is blocklisted", 'wordfence');
$row->longDescription = sprintf(/* translators: Reason for firewall action. */ __("Blocked for %s", 'wordfence'), $row->actionDescription);
$paramKey = base64_decode($actionData['paramKey']);
$paramValue = base64_decode($actionData['paramValue']);
if (strlen($paramValue) > 100) {
$paramValue = substr($paramValue, 0, 100) . '...';
if (preg_match('/([a-z0-9_]+\.[a-z0-9_]+)(?:\[(.+?)\](.*))?/i', $paramKey, $matches)) {
case 'request.queryString':
$row->longDescription = sprintf(
/* translators: 1. Reason for firewall action. 2. Input parameter. 2. Input parameter value. */
__('Blocked for %1$s in query string: %2$s = %3$s', 'wordfence'), $row->actionDescription, $matches[2], $paramValue);
$row->longDescription = sprintf(
/* translators: 1. Reason for firewall action. 2. Input parameter. 2. Input parameter value. */
__('Blocked for %1$s in POST body: %2$s = %3$s', 'wordfence'), $row->actionDescription, $matches[2], $paramValue);
$row->longDescription = sprintf(
/* translators: 1. Reason for firewall action. 2. Input parameter. 2. Input parameter value. */
__('Blocked for %1$s in cookie: %2$s = %3$s', 'wordfence'), $row->actionDescription, $matches[2], $paramValue);
case 'request.fileNames':
$row->longDescription = sprintf(
/* translators: 1. Reason for firewall action. 2. Input parameter. 2. Input parameter value. */
__('Blocked for %1$s in file: %2$s = %3$s', 'wordfence'), $row->actionDescription, $matches[2], $paramValue);
$date = date_i18n('F j, Y g:ia', floor($row->attackLogTime)); $dateMax = max(strlen($date), $dateMax);
$ip = wfUtils::inet_ntop($row->IP); $ipMax = max(strlen($ip), $ipMax);
$country = wfUtils::countryCode2Name(wfUtils::IP2Country($ip)); $country = (empty($country) ? 'Unknown' : $country); $countryMax = max(strlen($country), $countryMax);
$attackTable[] = array('date' => $date, 'IP' => $ip, 'country' => $country, 'message' => $row->longDescription);
foreach ($attackTable as $row) {
$date = str_pad($row['date'], $dateMax + 2);
$ip = str_pad($row['IP'] . " ({$row['country']})", $ipMax + $countryMax + 8);
$attackMessage = $row['message'];
$message .= "\n" . $date . $ip . $attackMessage;
$alertCallback = array(new wfIncreasedAttackRateAlert($message), 'send');
do_action('wordfence_security_event', 'increasedAttackRate', array(
'attackCount' => $attackCount,
'attackTable' => $attackTable,
'duration' => $alertInterval,
'ip' => wfUtils::getIP(),
wfConfig::set('wafAlertLastSendTime', time());
if (wfConfig::get('other_WFNet', true)) {
$response = wp_remote_get(sprintf(WFWAF_API_URL_SEC . "waf-rules/%d.txt", $waf->getStorageEngine()->getConfig('attackDataKey')), array('headers' => array('Referer' => false)));
if (!is_wp_error($response)) {
$okToSendBody = wp_remote_retrieve_body($response);
if ($okToSendBody === 'ok') {
$lastSendTime = wfConfig::get('lastAttackDataSendTime');
$lastSendId = wfConfig::get('lastAttackDataSendId');
$query=$wpdb->prepare("SELECT * FROM {$table_wfHits}
WHERE action in ('blocked:waf', 'learned:waf', 'logged:waf', 'blocked:waf-always')
LIMIT %d", sprintf('%.6f', $lastSendTime), $limit);
$count_query = str_replace(
array( "SELECT COUNT(*) FROM", "" ), $query
$query=$wpdb->prepare("SELECT * FROM {$table_wfHits}
WHERE action in ('blocked:waf', 'learned:waf', 'logged:waf', 'blocked:waf-always')
ORDER BY id LIMIT %d", $lastSendId, $limit);
$count_query = str_replace(
"ORDER BY id LIMIT " . $limit,
array( "SELECT COUNT(*) FROM", "" ), $query
$attackData = $wpdb->get_results($query);
$totalRows = $wpdb->get_var($count_query);
if ($attackData) { // Build JSON to send
$attackDataToUpdate = array();
foreach ($attackData as $attackDataRow) {
$actionData = (array) wfRequestModel::unserializeActionData($attackDataRow->actionData);
$attackDataRow->attackLogTime,
wfUtils::inet_ntop($attackDataRow->IP),
(array_key_exists('learningMode', $actionData) ? $actionData['learningMode'] : 0),
(array_key_exists('paramKey', $actionData) ? base64_encode($actionData['paramKey']) : false),
(array_key_exists('paramValue', $actionData) ? base64_encode($actionData['paramValue']) : false),
(array_key_exists('failedRules', $actionData) ? $actionData['failedRules'] : ''),
strpos($attackDataRow->URL, 'https') === 0 ? 1 : 0,
(array_key_exists('fullRequest', $actionData) ? $actionData['fullRequest'] : ''),
if (array_key_exists('fullRequest', $actionData)) {
unset($actionData['fullRequest']);
$attackDataToUpdate[$attackDataRow->id] = array(
'actionData' => wfRequestModel::serializeActionData($actionData),
if ($attackDataRow->attackLogTime > $lastSendTime) {
$lastSendTime = $attackDataRow->attackLogTime;
$bodyLimit=self::ATTACK_DATA_BODY_LIMIT;
array_splice($dataToSend, floor(count($dataToSend)/2));
$bodyData=json_encode($dataToSend);
} while(strlen($bodyData)>$bodyLimit&&count($dataToSend)>1);
$homeurl = wfUtils::wpHomeURL();
$siteurl = wfUtils::wpSiteURL();
$installType = wfUtils::wafInstallationType();
$response = wp_remote_post(WFWAF_API_URL_SEC . "?" . http_build_query(array(
'action' => 'send_waf_attack_data',
'k' => $waf->getStorageEngine()->getConfig('apiKey', null, 'synced'),
'lang' => get_site_option('WPLANG'),
'Content-Type' => 'application/json',
} while(wp_remote_retrieve_response_code($response)===413&&count($dataToSend)>1);
if (!is_wp_error($response) && ($body = wp_remote_retrieve_body($response))) {
$jsonData = json_decode($body, true);
if (is_array($jsonData) && array_key_exists('success', $jsonData)) {
wfConfig::set('lastAttackDataSendTime', $lastSendTime);
$lastSendIndex=count($dataToSend)-1;
$lastSendId = $attackData[$lastSendIndex]->id;
wfConfig::set('lastAttackDataSendId', $lastSendId);
// Successfully sent data, remove the full request from the table to reduce storage size
foreach ($attackDataToUpdate as $hitID => $dataToUpdate) {
if ($hitID <= $lastSendId) {
$wpdb->update($table_wfHits, $dataToUpdate, array(
if (count($dataToSend) < $totalRows) {
self::scheduleSendAttackData();
if (array_key_exists('data', $jsonData) && array_key_exists('watchedIPList', $jsonData['data'])) {
$waf->getStorageEngine()->setConfig('watchedIPs', $jsonData['data']['watchedIPList'], 'transient');
//Delay interactions for 30 minutes if an error occurs
self::delaySendAttackData(time() + 30*60);
//Send false positives and WAF failures
$lastSendTime = wfConfig::get('lastFalsePositiveSendTime');
$whitelistedURLParams = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('whitelistedURLParams', array(), 'livewaf');
$wafFailures = self::loadWafFailures($purgeWafFailures);
if (count($whitelistedURLParams) || !empty($wafFailures)) {
$falsePositives = array();
$mostRecentWhitelisting = $lastSendTime;
foreach ($whitelistedURLParams as $urlParamKey => $rules) {
list($path, $paramKey) = explode('|', $urlParamKey);
foreach ($rules as $ruleID => $whitelistedData) {
if ($whitelistedData['timestamp'] > $lastSendTime && (!isset($whitelistedData['disabled']) || !$whitelistedData['disabled'])) {
if (isset($whitelistedData['source'])) {
$source = $whitelistedData['source'];
else if ($whitelistedData['description'] == 'Allowlisted via false positive dialog') {
$source = 'false-positive';
else if ($whitelistedData['description'] == 'Allowlisted via Live Traffic') {
$source = 'live-traffic';
else if ($whitelistedData['description'] == 'Allowlisted while in Learning Mode.') {
$source = 'learning-mode';
else { //A user-entered description or Whitelisted via Firewall Options page