Wordfence Security – Firewall & Malware Scan - Version 7.4.0

Version Description

  • August 22, 2019 =
  • Improvement: Added a MySQL-based configuration and data storage for the WAF to expand the number of hosting environments supported. For more detail, see: https://www.wordfence.com/help/firewall/mysqli-storage-engine/
  • Improvement: Updated bundled GeoIP database.
  • Fix: Fixed several console notices when running via the CLI.
Download this release

Release Info

Developer wfryan
Plugin Icon 128x128 Wordfence Security – Firewall & Malware Scan
Version 7.4.0
Comparing to
See all releases

Code changes from version 7.3.6 to 7.4.0

Files changed (77) hide show
  1. css/{activity-report-widget.1564590761.css → activity-report-widget.1566486436.css} +0 -0
  2. css/{diff.1564590761.css → diff.1566486436.css} +0 -0
  3. css/{dt_table.1564590761.css → dt_table.1566486436.css} +0 -0
  4. css/{fullLog.1564590761.css → fullLog.1566486436.css} +0 -0
  5. css/{iptraf.1564590761.css → iptraf.1566486436.css} +0 -0
  6. css/{jquery-ui-timepicker-addon.1564590761.css → jquery-ui-timepicker-addon.1566486436.css} +0 -0
  7. css/{jquery-ui.min.1564590761.css → jquery-ui.min.1566486436.css} +0 -0
  8. css/{jquery-ui.structure.min.1564590761.css → jquery-ui.structure.min.1566486436.css} +0 -0
  9. css/{jquery-ui.theme.min.1564590761.css → jquery-ui.theme.min.1566486436.css} +0 -0
  10. css/{main.1564590761.css → main.1566486436.css} +0 -0
  11. css/{phpinfo.1564590761.css → phpinfo.1566486436.css} +0 -0
  12. css/{wf-adminbar.1564590761.css → wf-adminbar.1566486436.css} +0 -0
  13. css/{wf-colorbox.1564590761.css → wf-colorbox.1566486436.css} +0 -0
  14. css/{wf-font-awesome.1564590761.css → wf-font-awesome.1566486436.css} +0 -0
  15. css/{wf-global.1564590761.css → wf-global.1566486436.css} +0 -0
  16. css/{wf-ionicons.1564590761.css → wf-ionicons.1566486436.css} +0 -0
  17. css/{wf-onboarding.1564590761.css → wf-onboarding.1566486436.css} +0 -0
  18. css/{wf-roboto-font.1564590761.css → wf-roboto-font.1566486436.css} +0 -0
  19. css/{wfselect2.min.1564590761.css → wfselect2.min.1566486436.css} +0 -0
  20. css/{wordfenceBox.1564590761.css → wordfenceBox.1566486436.css} +0 -0
  21. js/{Chart.bundle.min.1564590761.js → Chart.bundle.min.1566486436.js} +0 -0
  22. js/{admin.1564590761.js → admin.1566486436.js} +0 -0
  23. js/{admin.ajaxWatcher.1564590761.js → admin.ajaxWatcher.1566486436.js} +0 -0
  24. js/{admin.liveTraffic.1564590761.js → admin.liveTraffic.1566486436.js} +0 -0
  25. js/{date.1564590761.js → date.1566486436.js} +0 -0
  26. js/{jquery-ui-timepicker-addon.1564590761.js → jquery-ui-timepicker-addon.1566486436.js} +0 -0
  27. js/{jquery.colorbox-min.1564590761.js → jquery.colorbox-min.1566486436.js} +0 -0
  28. js/{jquery.colorbox.1564590761.js → jquery.colorbox.1566486436.js} +0 -0
  29. js/{jquery.dataTables.min.1564590761.js → jquery.dataTables.min.1566486436.js} +0 -0
  30. js/{jquery.qrcode.min.1564590761.js → jquery.qrcode.min.1566486436.js} +0 -0
  31. js/{jquery.tmpl.min.1564590761.js → jquery.tmpl.min.1566486436.js} +0 -0
  32. js/{jquery.tools.min.1564590761.js → jquery.tools.min.1566486436.js} +0 -0
  33. js/{knockout-3.3.0.1564590761.js → knockout-3.3.0.1566486436.js} +0 -0
  34. js/{wfdashboard.1564590761.js → wfdashboard.1566486436.js} +0 -0
  35. js/{wfdropdown.1564590761.js → wfdropdown.1566486436.js} +0 -0
  36. js/{wfglobal.1564590761.js → wfglobal.1566486436.js} +0 -0
  37. js/{wfpopover.1564590761.js → wfpopover.1566486436.js} +0 -0
  38. js/{wfselect2.min.1564590761.js → wfselect2.min.1566486436.js} +0 -0
  39. lib/GeoLite2-Country.mmdb +0 -0
  40. lib/wfConfig.php +17 -1
  41. lib/wfCrawl.php +5 -0
  42. lib/wfUtils.php +7 -4
  43. lib/wordfenceClass.php +81 -29
  44. modules/login-security/css/{admin-global.1564590761.css → admin-global.1566486436.css} +0 -0
  45. modules/login-security/css/{admin.1564590761.css → admin.1566486436.css} +0 -0
  46. modules/login-security/css/{colorbox.1564590761.css → colorbox.1566486436.css} +0 -0
  47. modules/login-security/css/{font-awesome.1564590761.css → font-awesome.1566486436.css} +0 -0
  48. modules/login-security/css/{ionicons.1564590761.css → ionicons.1566486436.css} +0 -0
  49. modules/login-security/css/{jquery-ui-timepicker-addon.1564590761.css → jquery-ui-timepicker-addon.1566486436.css} +0 -0
  50. modules/login-security/css/{jquery-ui.min.1564590761.css → jquery-ui.min.1566486436.css} +0 -0
  51. modules/login-security/css/{jquery-ui.structure.min.1564590761.css → jquery-ui.structure.min.1566486436.css} +0 -0
  52. modules/login-security/css/{jquery-ui.theme.min.1564590761.css → jquery-ui.theme.min.1566486436.css} +0 -0
  53. modules/login-security/css/{login.1564590761.css → login.1566486436.css} +0 -0
  54. modules/login-security/js/{admin-global.1564590761.js → admin-global.1566486436.js} +0 -0
  55. modules/login-security/js/{admin.1564590761.js → admin.1566486436.js} +0 -0
  56. modules/login-security/js/{jquery-ui-timepicker-addon.1564590761.js → jquery-ui-timepicker-addon.1566486436.js} +0 -0
  57. modules/login-security/js/{jquery.colorbox.1564590761.js → jquery.colorbox.1566486436.js} +0 -0
  58. modules/login-security/js/{jquery.colorbox.min.1564590761.js → jquery.colorbox.min.1566486436.js} +0 -0
  59. modules/login-security/js/{jquery.qrcode.min.1564590761.js → jquery.qrcode.min.1566486436.js} +0 -0
  60. modules/login-security/js/{jquery.tmpl.min.1564590761.js → jquery.tmpl.min.1566486436.js} +0 -0
  61. modules/login-security/js/{login.1564590761.js → login.1566486436.js} +0 -0
  62. modules/login-security/wordfence-login-security.php +1 -1
  63. readme.txt +6 -1
  64. vendor/wordfence/wf-waf/src/init.php +1 -0
  65. vendor/wordfence/wf-waf/src/lib/parser/parser.php +7 -0
  66. vendor/wordfence/wf-waf/src/lib/rules.php +89 -0
  67. vendor/wordfence/wf-waf/src/lib/storage.php +8 -0
  68. vendor/wordfence/wf-waf/src/lib/storage/file.php +40 -2
  69. vendor/wordfence/wf-waf/src/lib/storage/mysql.php +1120 -0
  70. vendor/wordfence/wf-waf/src/lib/utils.php +155 -49
  71. vendor/wordfence/wf-waf/src/lib/waf.php +69 -18
  72. views/waf/option-rules.php +3 -0
  73. views/waf/waf-install.php +1 -1
  74. views/waf/waf-uninstall.php +6 -1
  75. waf/bootstrap.php +86 -22
  76. waf/wfWAFIPBlocksController.php +1 -1
  77. wordfence.php +6 -4
css/{activity-report-widget.1564590761.css → activity-report-widget.1566486436.css} RENAMED
File without changes
css/{diff.1564590761.css → diff.1566486436.css} RENAMED
File without changes
css/{dt_table.1564590761.css → dt_table.1566486436.css} RENAMED
File without changes
css/{fullLog.1564590761.css → fullLog.1566486436.css} RENAMED
File without changes
css/{iptraf.1564590761.css → iptraf.1566486436.css} RENAMED
File without changes
css/{jquery-ui-timepicker-addon.1564590761.css → jquery-ui-timepicker-addon.1566486436.css} RENAMED
File without changes
css/{jquery-ui.min.1564590761.css → jquery-ui.min.1566486436.css} RENAMED
File without changes
css/{jquery-ui.structure.min.1564590761.css → jquery-ui.structure.min.1566486436.css} RENAMED
File without changes
css/{jquery-ui.theme.min.1564590761.css → jquery-ui.theme.min.1566486436.css} RENAMED
File without changes
css/{main.1564590761.css → main.1566486436.css} RENAMED
File without changes
css/{phpinfo.1564590761.css → phpinfo.1566486436.css} RENAMED
File without changes
css/{wf-adminbar.1564590761.css → wf-adminbar.1566486436.css} RENAMED
File without changes
css/{wf-colorbox.1564590761.css → wf-colorbox.1566486436.css} RENAMED
File without changes
css/{wf-font-awesome.1564590761.css → wf-font-awesome.1566486436.css} RENAMED
File without changes
css/{wf-global.1564590761.css → wf-global.1566486436.css} RENAMED
File without changes
css/{wf-ionicons.1564590761.css → wf-ionicons.1566486436.css} RENAMED
File without changes
css/{wf-onboarding.1564590761.css → wf-onboarding.1566486436.css} RENAMED
File without changes
css/{wf-roboto-font.1564590761.css → wf-roboto-font.1566486436.css} RENAMED
File without changes
css/{wfselect2.min.1564590761.css → wfselect2.min.1566486436.css} RENAMED
File without changes
css/{wordfenceBox.1564590761.css → wordfenceBox.1566486436.css} RENAMED
File without changes
js/{Chart.bundle.min.1564590761.js → Chart.bundle.min.1566486436.js} RENAMED
File without changes
js/{admin.1564590761.js → admin.1566486436.js} RENAMED
File without changes
js/{admin.ajaxWatcher.1564590761.js → admin.ajaxWatcher.1566486436.js} RENAMED
File without changes
js/{admin.liveTraffic.1564590761.js → admin.liveTraffic.1566486436.js} RENAMED
File without changes
js/{date.1564590761.js → date.1566486436.js} RENAMED
File without changes
js/{jquery-ui-timepicker-addon.1564590761.js → jquery-ui-timepicker-addon.1566486436.js} RENAMED
File without changes
js/{jquery.colorbox-min.1564590761.js → jquery.colorbox-min.1566486436.js} RENAMED
File without changes
js/{jquery.colorbox.1564590761.js → jquery.colorbox.1566486436.js} RENAMED
File without changes
js/{jquery.dataTables.min.1564590761.js → jquery.dataTables.min.1566486436.js} RENAMED
File without changes
js/{jquery.qrcode.min.1564590761.js → jquery.qrcode.min.1566486436.js} RENAMED
File without changes
js/{jquery.tmpl.min.1564590761.js → jquery.tmpl.min.1566486436.js} RENAMED
File without changes
js/{jquery.tools.min.1564590761.js → jquery.tools.min.1566486436.js} RENAMED
File without changes
js/{knockout-3.3.0.1564590761.js → knockout-3.3.0.1566486436.js} RENAMED
File without changes
js/{wfdashboard.1564590761.js → wfdashboard.1566486436.js} RENAMED
File without changes
js/{wfdropdown.1564590761.js → wfdropdown.1566486436.js} RENAMED
File without changes
js/{wfglobal.1564590761.js → wfglobal.1566486436.js} RENAMED
File without changes
js/{wfpopover.1564590761.js → wfpopover.1566486436.js} RENAMED
File without changes
js/{wfselect2.min.1564590761.js → wfselect2.min.1566486436.js} RENAMED
File without changes
lib/GeoLite2-Country.mmdb CHANGED
Binary file
lib/wfConfig.php CHANGED
@@ -491,6 +491,8 @@ class wfConfig {
491
wfWAF::getInstance()->getStorageEngine()->setConfig($key, $val, 'synced');
492
} catch (wfWAFStorageFileException $e) {
493
error_log($e->getMessage());
494
}
495
}
496
@@ -880,7 +882,7 @@ class wfConfig {
880
}
881
public static function liveTrafficEnabled(&$overriden = null){
882
$enabled = self::get('liveTrafficEnabled');
883
- if (WORDFENCE_DISABLE_LIVE_TRAFFIC || function_exists('wpe_site')) {
884
$enabled = false;
885
if ($overriden !== null) {
886
$overriden = true;
@@ -1419,6 +1421,20 @@ Options -ExecCGI
1419
if (method_exists(wfWAF::getInstance()->getStorageEngine(), 'purgeIPBlocks')) {
1420
wfWAF::getInstance()->getStorageEngine()->purgeIPBlocks(wfWAFStorageInterface::IP_BLOCKS_BLACKLIST);
1421
}
1422
$saved = true;
1423
break;
1424
}
491
wfWAF::getInstance()->getStorageEngine()->setConfig($key, $val, 'synced');
492
} catch (wfWAFStorageFileException $e) {
493
error_log($e->getMessage());
494
+ } catch (wfWAFStorageEngineMySQLiException $e) {
495
+ error_log($e->getMessage());
496
}
497
}
498
882
}
883
public static function liveTrafficEnabled(&$overriden = null){
884
$enabled = self::get('liveTrafficEnabled');
885
+ if (WORDFENCE_DISABLE_LIVE_TRAFFIC || WF_IS_WP_ENGINE) {
886
$enabled = false;
887
if ($overriden !== null) {
888
$overriden = true;
1421
if (method_exists(wfWAF::getInstance()->getStorageEngine(), 'purgeIPBlocks')) {
1422
wfWAF::getInstance()->getStorageEngine()->purgeIPBlocks(wfWAFStorageInterface::IP_BLOCKS_BLACKLIST);
1423
}
1424
+ if ($value) {
1425
+ $cron = wfWAF::getInstance()->getStorageEngine()->getConfig('cron', array(), 'livewaf');
1426
+ if (!is_array($cron)) {
1427
+ $cron = array();
1428
+ }
1429
+ foreach ($cron as $cronKey => $cronJob) {
1430
+ if ($cronJob instanceof wfWAFCronFetchBlacklistPrefixesEvent) {
1431
+ unset($cron[$cronKey]);
1432
+ }
1433
+ }
1434
+ $cron[] = new wfWAFCronFetchBlacklistPrefixesEvent(time() - 1);
1435
+ wfWAF::getInstance()->getStorageEngine()->setConfig('cron', $cron, 'livewaf');
1436
+ }
1437
+
1438
$saved = true;
1439
break;
1440
}
lib/wfCrawl.php CHANGED
@@ -115,6 +115,11 @@ class wfCrawl {
115
if ($ip === null) {
116
$ip = wfUtils::getIP();
117
}
118
if (array_key_exists($ip, $verified)) {
119
return $verified[$ip];
120
}
115
if ($ip === null) {
116
$ip = wfUtils::getIP();
117
}
118
+
119
+ if ($ip === null || $ip === false) { //Likely a CLI execution
120
+ return false;
121
+ }
122
+
123
if (array_key_exists($ip, $verified)) {
124
return $verified[$ip];
125
}
lib/wfUtils.php CHANGED
@@ -2337,19 +2337,22 @@ class wfUtils {
2337
}
2338
2339
public static function wafInstallationType() {
2340
try {
2341
$status = (defined('WFWAF_ENABLED') && !WFWAF_ENABLED) ? 'disabled' : wfWaf::getInstance()->getStorageEngine()->getConfig('wafStatus');
2342
if (defined('WFWAF_ENABLED') && !WFWAF_ENABLED) {
2343
- return "{$status}|const";
2344
}
2345
else if (defined('WFWAF_SUBDIRECTORY_INSTALL') && WFWAF_SUBDIRECTORY_INSTALL) {
2346
- return "{$status}|subdir";
2347
}
2348
else if (defined('WFWAF_AUTO_PREPEND') && WFWAF_AUTO_PREPEND) {
2349
- return "{$status}|extended";
2350
}
2351
2352
- return "{$status}|basic";
2353
}
2354
catch (Exception $e) {
2355
//Do nothing
2337
}
2338
2339
public static function wafInstallationType() {
2340
+ $storage = 'file';
2341
+ if (defined('WFWAF_STORAGE_ENGINE')) { $storage = WFWAF_STORAGE_ENGINE; }
2342
+
2343
try {
2344
$status = (defined('WFWAF_ENABLED') && !WFWAF_ENABLED) ? 'disabled' : wfWaf::getInstance()->getStorageEngine()->getConfig('wafStatus');
2345
if (defined('WFWAF_ENABLED') && !WFWAF_ENABLED) {
2346
+ return "{$status}|const|{$storage}";
2347
}
2348
else if (defined('WFWAF_SUBDIRECTORY_INSTALL') && WFWAF_SUBDIRECTORY_INSTALL) {
2349
+ return "{$status}|subdir|{$storage}";
2350
}
2351
else if (defined('WFWAF_AUTO_PREPEND') && WFWAF_AUTO_PREPEND) {
2352
+ return "{$status}|extended|{$storage}";
2353
}
2354
2355
+ return "{$status}|basic|{$storage}";
2356
}
2357
catch (Exception $e) {
2358
//Do nothing
lib/wordfenceClass.php CHANGED
@@ -156,6 +156,8 @@ class wordfence {
156
}
157
} catch (wfWAFStorageFileException $e) {
158
error_log($e->getMessage());
159
}
160
}
161
}
@@ -2213,7 +2215,11 @@ SQL
2213
2214
if (empty($_GET['wordfence_syncAttackData'])) {
2215
$table_wfHits = wfDB::networkTable('wfHits');
2216
- $lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$table_wfHits}");
2217
if (get_site_option('wordfence_lastSyncAttackData', 0) < time() - 4) {
2218
if ($waf->getStorageEngine()->hasNewerAttackData($lastAttackMicroseconds)) {
2219
if (get_site_option('wordfence_syncingAttackData') <= time() - 60) {
@@ -2271,6 +2277,8 @@ SQL
2271
}
2272
} catch (wfWAFStorageFileException $e) {
2273
// We don't have anywhere to write files in this scenario.
2274
}
2275
}
2276
@@ -4406,6 +4414,11 @@ SQL
4406
'error' => __('An error occurred while saving the configuration.', 'wordfence'),
4407
);
4408
}
4409
catch (Exception $e) {
4410
return array(
4411
'error' => $e->getMessage(),
@@ -5684,6 +5697,8 @@ HTML;
5684
}
5685
} catch (wfWAFStorageFileException $e) {
5686
error_log($e->getMessage());
5687
}
5688
}
5689
@@ -6387,6 +6402,17 @@ JQUERY;
6387
$logPath = str_replace(ABSPATH, '~/', WFWAF_LOG_PATH);
6388
$storageExceptionMessage = 'We were unable to write to ' . $logPath . ' which the WAF uses for storage. Please
6389
update permissions on the parent directory so the web server can write to it.';
6390
}
6391
6392
require(dirname(__FILE__) . '/menu_options.php');
@@ -6441,6 +6467,17 @@ JQUERY;
6441
$logPath = str_replace(ABSPATH, '~/', WFWAF_LOG_PATH);
6442
$storageExceptionMessage = 'We were unable to write to ' . $logPath . ' which the WAF uses for storage. Please
6443
update permissions on the parent directory so the web server can write to it.';
6444
}
6445
6446
if (isset($_GET['subpage']) && $_GET['subpage'] == 'waf_options') {
@@ -7400,33 +7437,35 @@ to your httpd.conf if using Apache, or find documentation on how to disable dire
7400
7401
$whitelistedURLParams = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('whitelistedURLParams', array(), 'livewaf');
7402
$data['whitelistedURLParams'] = array();
7403
- foreach ($whitelistedURLParams as $urlParamKey => $rules) {
7404
- list($path, $paramKey) = explode('|', $urlParamKey);
7405
- $whitelistData = null;
7406
- foreach ($rules as $ruleID => $whitelistedData) {
7407
- if ($whitelistData === null) {
7408
- $whitelistData = $whitelistedData;
7409
- continue;
7410
- }
7411
- if ($ruleID === 'all') {
7412
- $whitelistData = $whitelistedData;
7413
- break;
7414
}
7415
- }
7416
7417
- if (is_array($whitelistData) && array_key_exists('userID', $whitelistData) && function_exists('get_user_by')) {
7418
- $user = get_user_by('id', $whitelistData['userID']);
7419
- if ($user) {
7420
- $whitelistData['username'] = $user->user_login;
7421
}
7422
- }
7423
7424
- $data['whitelistedURLParams'][] = array(
7425
- 'path' => $path,
7426
- 'paramKey' => $paramKey,
7427
- 'ruleID' => array_keys($rules),
7428
- 'data' => $whitelistData,
7429
- );
7430
}
7431
7432
$data['disabledRules'] = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('disabledRules');
@@ -7456,7 +7495,7 @@ to your httpd.conf if using Apache, or find documentation on how to disable dire
7456
7457
$currentAutoPrependFile = ini_get('auto_prepend_file');
7458
$currentAutoPrepend = null;
7459
- if (isset($_POST['currentAutoPrepend'])) {
7460
$currentAutoPrepend = $_POST['currentAutoPrepend'];
7461
}
7462
@@ -7659,7 +7698,7 @@ to your httpd.conf if using Apache, or find documentation on how to disable dire
7659
return $response;
7660
}
7661
else { //.user.ini and .htaccess modified if applicable and waiting period elapsed or otherwise ready to advance to next step
7662
- if (WFWAF_AUTO_PREPEND && !WFWAF_SUBDIRECTORY_INSTALL) { //.user.ini modified, but the WAF is still enabled
7663
$retryAttempted = (isset($_POST['retryAttempted']) && $_POST['retryAttempted']);
7664
$userIniError = '<p class="wf-error">';
7665
$userIniError .= __('Extended Protection Mode has not been disabled. This may be because <code>auto_prepend_file</code> is configured somewhere else or the value is still cached by PHP.', 'wordfence');
@@ -8109,20 +8148,29 @@ ALERTMSG;
8109
$log = self::getLog();
8110
$waf = wfWAF::getInstance();
8111
$table_wfHits = wfDB::networkTable('wfHits');
8112
- $lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$table_wfHits}");
8113
if ($waf->getStorageEngine()->hasNewerAttackData($lastAttackMicroseconds)) {
8114
$attackData = $waf->getStorageEngine()->getNewestAttackDataArray($lastAttackMicroseconds);
8115
if ($attackData) {
8116
foreach ($attackData as $request) {
8117
- if (count($request) !== 9 && count($request) !== 10 /* with metadata */) {
8118
continue;
8119
}
8120
8121
list($logTimeMicroseconds, $requestTime, $ip, $learningMode, $paramKey, $paramValue, $failedRules, $ssl, $requestString) = $request;
8122
$metadata = null;
8123
- if (count($request) == 10) {
8124
$metadata = $request[9];
8125
}
8126
8127
// Skip old entries and hits in learning mode, since they'll get picked up anyways.
8128
if ($logTimeMicroseconds <= $lastAttackMicroseconds || $learningMode) {
@@ -8132,6 +8180,10 @@ ALERTMSG;
8132
$statusCode = 403;
8133
8134
$hit = new wfRequestModel();
8135
$hit->attackLogTime = $logTimeMicroseconds;
8136
$hit->ctime = $requestTime;
8137
$hit->IP = wfUtils::inet_pton($ip);
156
}
157
} catch (wfWAFStorageFileException $e) {
158
error_log($e->getMessage());
159
+ } catch (wfWAFStorageEngineMySQLiException $e) {
160
+ error_log($e->getMessage());
161
}
162
}
163
}
2215
2216
if (empty($_GET['wordfence_syncAttackData'])) {
2217
$table_wfHits = wfDB::networkTable('wfHits');
2218
+ if ($waf->getStorageEngine() instanceof wfWAFStorageMySQL) {
2219
+ $lastAttackMicroseconds = floatval($waf->getStorageEngine()->getConfig('lastAttackDataTruncateTime'));
2220
+ } else {
2221
+ $lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$table_wfHits}");
2222
+ }
2223
if (get_site_option('wordfence_lastSyncAttackData', 0) < time() - 4) {
2224
if ($waf->getStorageEngine()->hasNewerAttackData($lastAttackMicroseconds)) {
2225
if (get_site_option('wordfence_syncingAttackData') <= time() - 60) {
2277
}
2278
} catch (wfWAFStorageFileException $e) {
2279
// We don't have anywhere to write files in this scenario.
2280
+ } catch (wfWAFStorageEngineMySQLiException $e) {
2281
+ // Ignore and continue
2282
}
2283
}
2284
4414
'error' => __('An error occurred while saving the configuration.', 'wordfence'),
4415
);
4416
}
4417
+ catch (wfWAFStorageEngineMySQLiException $e) {
4418
+ return array(
4419
+ 'error' => __('An error occurred while saving the configuration.', 'wordfence'),
4420
+ );
4421
+ }
4422
catch (Exception $e) {
4423
return array(
4424
'error' => $e->getMessage(),
5697
}
5698
} catch (wfWAFStorageFileException $e) {
5699
error_log($e->getMessage());
5700
+ } catch (wfWAFStorageEngineMySQLiException $e) {
5701
+ error_log($e->getMessage());
5702
}
5703
}
5704
6402
$logPath = str_replace(ABSPATH, '~/', WFWAF_LOG_PATH);
6403
$storageExceptionMessage = 'We were unable to write to ' . $logPath . ' which the WAF uses for storage. Please
6404
update permissions on the parent directory so the web server can write to it.';
6405
+ } catch (wfWAFStorageEngineMySQLiException $e) {
6406
+ $wafData = array(
6407
+ 'learningMode' => false,
6408
+ 'rules' => array(),
6409
+ 'whitelistedURLParams' => array(),
6410
+ 'disabledRules' => array(),
6411
+ 'isPaid' => (bool) wfConfig::get('isPaid', 0),
6412
+ );
6413
+ $logPath = null;
6414
+ $storageExceptionMessage = 'An error occured when fetching the WAF configuration from the database. <pre>' .
6415
+ esc_html($e->getMessage()) . '</pre>';
6416
}
6417
6418
require(dirname(__FILE__) . '/menu_options.php');
6467
$logPath = str_replace(ABSPATH, '~/', WFWAF_LOG_PATH);
6468
$storageExceptionMessage = 'We were unable to write to ' . $logPath . ' which the WAF uses for storage. Please
6469
update permissions on the parent directory so the web server can write to it.';
6470
+ } catch (wfWAFStorageEngineMySQLiException $e) {
6471
+ $wafData = array(
6472
+ 'learningMode' => false,
6473
+ 'rules' => array(),
6474
+ 'whitelistedURLParams' => array(),
6475
+ 'disabledRules' => array(),
6476
+ 'isPaid' => (bool) wfConfig::get('isPaid', 0),
6477
+ );
6478
+ $logPath = null;
6479
+ $storageExceptionMessage = 'An error occured when fetching the WAF configuration from the database. <pre>' .
6480
+ esc_html($e->getMessage()) . '</pre>';
6481
}
6482
6483
if (isset($_GET['subpage']) && $_GET['subpage'] == 'waf_options') {
7437
7438
$whitelistedURLParams = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('whitelistedURLParams', array(), 'livewaf');
7439
$data['whitelistedURLParams'] = array();
7440
+ if (is_array($whitelistedURLParams)) {
7441
+ foreach ($whitelistedURLParams as $urlParamKey => $rules) {
7442
+ list($path, $paramKey) = explode('|', $urlParamKey);
7443
+ $whitelistData = null;
7444
+ foreach ($rules as $ruleID => $whitelistedData) {
7445
+ if ($whitelistData === null) {
7446
+ $whitelistData = $whitelistedData;
7447
+ continue;
7448
+ }
7449
+ if ($ruleID === 'all') {
7450
+ $whitelistData = $whitelistedData;
7451
+ break;
7452
+ }
7453
}
7454
7455
+ if (is_array($whitelistData) && array_key_exists('userID', $whitelistData) && function_exists('get_user_by')) {
7456
+ $user = get_user_by('id', $whitelistData['userID']);
7457
+ if ($user) {
7458
+ $whitelistData['username'] = $user->user_login;
7459
+ }
7460
}
7461
7462
+ $data['whitelistedURLParams'][] = array(
7463
+ 'path' => $path,
7464
+ 'paramKey' => $paramKey,
7465
+ 'ruleID' => array_keys($rules),
7466
+ 'data' => $whitelistData,
7467
+ );
7468
+ }
7469
}
7470
7471
$data['disabledRules'] = (array) wfWAF::getInstance()->getStorageEngine()->getConfig('disabledRules');
7495
7496
$currentAutoPrependFile = ini_get('auto_prepend_file');
7497
$currentAutoPrepend = null;
7498
+ if (isset($_POST['currentAutoPrepend']) && !WF_IS_WP_ENGINE) {
7499
$currentAutoPrepend = $_POST['currentAutoPrepend'];
7500
}
7501
7698
return $response;
7699
}
7700
else { //.user.ini and .htaccess modified if applicable and waiting period elapsed or otherwise ready to advance to next step
7701
+ if (WFWAF_AUTO_PREPEND && !WFWAF_SUBDIRECTORY_INSTALL && !WF_IS_WP_ENGINE) { //.user.ini modified, but the WAF is still enabled
7702
$retryAttempted = (isset($_POST['retryAttempted']) && $_POST['retryAttempted']);
7703
$userIniError = '<p class="wf-error">';
7704
$userIniError .= __('Extended Protection Mode has not been disabled. This may be because <code>auto_prepend_file</code> is configured somewhere else or the value is still cached by PHP.', 'wordfence');
8148
$log = self::getLog();
8149
$waf = wfWAF::getInstance();
8150
$table_wfHits = wfDB::networkTable('wfHits');
8151
+ if ($waf->getStorageEngine() instanceof wfWAFStorageMySQL) {
8152
+ $lastAttackMicroseconds = floatval($waf->getStorageEngine()->getConfig('lastAttackDataTruncateTime'));
8153
+ } else {
8154
+ $lastAttackMicroseconds = $wpdb->get_var("SELECT MAX(attackLogTime) FROM {$table_wfHits}");
8155
+ }
8156
+
8157
if ($waf->getStorageEngine()->hasNewerAttackData($lastAttackMicroseconds)) {
8158
$attackData = $waf->getStorageEngine()->getNewestAttackDataArray($lastAttackMicroseconds);
8159
if ($attackData) {
8160
foreach ($attackData as $request) {
8161
+ if (count($request) !== 9 && count($request) !== 10 /* with metadata */ && count($request) !== 11) {
8162
continue;
8163
}
8164
8165
list($logTimeMicroseconds, $requestTime, $ip, $learningMode, $paramKey, $paramValue, $failedRules, $ssl, $requestString) = $request;
8166
$metadata = null;
8167
+ $recordID = null;
8168
+ if (array_key_exists(9, $request)) {
8169
$metadata = $request[9];
8170
}
8171
+ if (array_key_exists(10, $request)) {
8172
+ $recordID = $request[10];
8173
+ }
8174
8175
// Skip old entries and hits in learning mode, since they'll get picked up anyways.
8176
if ($logTimeMicroseconds <= $lastAttackMicroseconds || $learningMode) {
8180
$statusCode = 403;
8181
8182
$hit = new wfRequestModel();
8183
+ if (is_numeric($recordID)) {
8184
+ $hit->id = $recordID;
8185
+ }
8186
+
8187
$hit->attackLogTime = $logTimeMicroseconds;
8188
$hit->ctime = $requestTime;
8189
$hit->IP = wfUtils::inet_pton($ip);
modules/login-security/css/{admin-global.1564590761.css → admin-global.1566486436.css} RENAMED
File without changes
modules/login-security/css/{admin.1564590761.css → admin.1566486436.css} RENAMED
File without changes
modules/login-security/css/{colorbox.1564590761.css → colorbox.1566486436.css} RENAMED
File without changes
modules/login-security/css/{font-awesome.1564590761.css → font-awesome.1566486436.css} RENAMED
File without changes
modules/login-security/css/{ionicons.1564590761.css → ionicons.1566486436.css} RENAMED
File without changes
modules/login-security/css/{jquery-ui-timepicker-addon.1564590761.css → jquery-ui-timepicker-addon.1566486436.css} RENAMED
File without changes
modules/login-security/css/{jquery-ui.min.1564590761.css → jquery-ui.min.1566486436.css} RENAMED
File without changes
modules/login-security/css/{jquery-ui.structure.min.1564590761.css → jquery-ui.structure.min.1566486436.css} RENAMED
File without changes
modules/login-security/css/{jquery-ui.theme.min.1564590761.css → jquery-ui.theme.min.1566486436.css} RENAMED
File without changes
modules/login-security/css/{login.1564590761.css → login.1566486436.css} RENAMED
File without changes
modules/login-security/js/{admin-global.1564590761.js → admin-global.1566486436.js} RENAMED
File without changes
modules/login-security/js/{admin.1564590761.js → admin.1566486436.js} RENAMED
File without changes
modules/login-security/js/{jquery-ui-timepicker-addon.1564590761.js → jquery-ui-timepicker-addon.1566486436.js} RENAMED
File without changes
modules/login-security/js/{jquery.colorbox.1564590761.js → jquery.colorbox.1566486436.js} RENAMED
File without changes
modules/login-security/js/{jquery.colorbox.min.1564590761.js → jquery.colorbox.min.1566486436.js} RENAMED
File without changes
modules/login-security/js/{jquery.qrcode.min.1564590761.js → jquery.qrcode.min.1566486436.js} RENAMED
File without changes
modules/login-security/js/{jquery.tmpl.min.1564590761.js → jquery.tmpl.min.1566486436.js} RENAMED
File without changes
modules/login-security/js/{login.1564590761.js → login.1566486436.js} RENAMED
File without changes
modules/login-security/wordfence-login-security.php CHANGED
@@ -27,7 +27,7 @@ else {
27
define('WORDFENCE_LS_FROM_CORE', ($wfCoreActive && isset($wfCoreLoading) && $wfCoreLoading));
28
29
define('WORDFENCE_LS_VERSION', '1.0.3');
30
- define('WORDFENCE_LS_BUILD_NUMBER', '1564590761');
31
32
if (!defined('WORDFENCE_LS_EMAIL_VALIDITY_DURATION_MINUTES')) { define('WORDFENCE_LS_EMAIL_VALIDITY_DURATION_MINUTES', 15); }
33
27
define('WORDFENCE_LS_FROM_CORE', ($wfCoreActive && isset($wfCoreLoading) && $wfCoreLoading));
28
29
define('WORDFENCE_LS_VERSION', '1.0.3');
30
+ define('WORDFENCE_LS_BUILD_NUMBER', '1566486436');
31
32
if (!defined('WORDFENCE_LS_EMAIL_VALIDITY_DURATION_MINUTES')) { define('WORDFENCE_LS_EMAIL_VALIDITY_DURATION_MINUTES', 15); }
33
readme.txt CHANGED
@@ -4,7 +4,7 @@ Tags: security, firewall, malware scanner, web application firewall, two factor
4
Requires at least: 3.9
5
Requires PHP: 5.3
6
Tested up to: 5.2
7
- Stable tag: 7.3.6
8
9
Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
10
@@ -185,6 +185,11 @@ Secure your website with Wordfence.
185
186
== Changelog ==
187
188
= 7.3.6 - July 31, 2019 =
189
* Improvement: Multiple "php.ini file in core directory" issues are now consolidated into a single issue for clearer scan results.
190
* Improvement: The AJAX error detection for false positive WAF blocks now better detects and processes the response for presenting the whitelisting prompt.
4
Requires at least: 3.9
5
Requires PHP: 5.3
6
Tested up to: 5.2
7
+ Stable tag: 7.4.0
8
9
Secure your website with the most comprehensive WordPress security plugin. Firewall, malware scan, blocking, live traffic, login security & more.
10
185
186
== Changelog ==
187
188
+ = 7.4.0 - August 22, 2019 =
189
+ * Improvement: Added a MySQL-based configuration and data storage for the WAF to expand the number of hosting environments supported. For more detail, see: https://www.wordfence.com/help/firewall/mysqli-storage-engine/
190
+ * Improvement: Updated bundled GeoIP database.
191
+ * Fix: Fixed several console notices when running via the CLI.
192
+
193
= 7.3.6 - July 31, 2019 =
194
* Improvement: Multiple "php.ini file in core directory" issues are now consolidated into a single issue for clearer scan results.
195
* Improvement: The AJAX error detection for false positive WAF blocks now better detects and processes the response for presenting the whitelisting prompt.
vendor/wordfence/wf-waf/src/init.php CHANGED
@@ -21,6 +21,7 @@ require_once WFWAF_LIB_PATH . 'xmlrpc.php';
21
22
require_once WFWAF_LIB_PATH . 'storage.php';
23
require_once WFWAF_LIB_PATH . 'storage/file.php';
24
25
require_once WFWAF_LIB_PATH . 'config.php';
26
21
22
require_once WFWAF_LIB_PATH . 'storage.php';
23
require_once WFWAF_LIB_PATH . 'storage/file.php';
24
+ require_once WFWAF_LIB_PATH . 'storage/mysql.php';
25
26
require_once WFWAF_LIB_PATH . 'config.php';
27
vendor/wordfence/wf-waf/src/lib/parser/parser.php CHANGED
@@ -797,6 +797,13 @@ class wfWAFRuleVariable {
797
$this->value = $value;
798
}
799
800
public function render() {
801
return sprintf('new %s($this, %s, %s)', get_class($this),
802
var_export($this->getName(), true), var_export($this->getValue(), true));
797
$this->value = $value;
798
}
799
800
+ public function __sleep() {
801
+ return array(
802
+ 'name',
803
+ 'value',
804
+ );
805
+ }
806
+
807
public function render() {
808
return sprintf('new %s($this, %s, %s)', get_class($this),
809
var_export($this->getName(), true), var_export($this->getValue(), true));
vendor/wordfence/wf-waf/src/lib/rules.php CHANGED
@@ -28,6 +28,7 @@ class wfWAFRule implements wfWAFRuleInterface {
28
private $description;
29
private $whitelist;
30
private $action;
31
private $comparisonGroup;
32
/**
33
* @var wfWAF
@@ -100,6 +101,19 @@ class wfWAFRule implements wfWAFRuleInterface {
100
$this->setComparisonGroup($comparisonGroup);
101
}
102
103
/**
104
* @return string
105
*/
@@ -295,6 +309,9 @@ RULE
295
*/
296
public function setWAF($waf) {
297
$this->waf = $waf;
298
}
299
}
300
@@ -335,6 +352,14 @@ class wfWAFRuleLogicalOperator implements wfWAFRuleInterface {
335
$this->setComparison($comparison);
336
}
337
338
/**
339
* @return string
340
* @throws wfWAFRuleLogicalOperatorException
@@ -504,6 +529,15 @@ class wfWAFRuleComparison implements wfWAFRuleInterface {
504
$this->setSubjects($subjects);
505
}
506
507
/**
508
* @param string|array $subject
509
* @return string
@@ -1174,6 +1208,16 @@ class wfWAFRuleComparison implements wfWAFRuleInterface {
1174
*/
1175
public function setWAF($waf) {
1176
$this->waf = $waf;
1177
}
1178
1179
/**
@@ -1196,6 +1240,8 @@ class wfWAFRuleComparisonGroup implements wfWAFRuleInterface {
1196
private $items = array();
1197
private $failedComparisons = array();
1198
private $result = false;
1199
/**
1200
* @var wfWAFRule
1201
*/
@@ -1208,6 +1254,12 @@ class wfWAFRuleComparisonGroup implements wfWAFRuleInterface {
1208
}
1209
}
1210
1211
public function add($item) {
1212
$this->items[] = $item;
1213
}
@@ -1373,6 +1425,25 @@ class wfWAFRuleComparisonGroup implements wfWAFRuleInterface {
1373
public function setRule($rule) {
1374
$this->rule = $rule;
1375
}
1376
}
1377
1378
class wfWAFRuleComparisonFailure {
@@ -1410,6 +1481,17 @@ class wfWAFRuleComparisonFailure {
1410
$this->setMatches($matches);
1411
}
1412
1413
/**
1414
* @return mixed
1415
*/
@@ -1532,6 +1614,13 @@ class wfWAFRuleComparisonSubject {
1532
$this->filters = $filters;
1533
}
1534
1535
/**
1536
* @return mixed|null
1537
*/
28
private $description;
29
private $whitelist;
30
private $action;
31
+ /** @var wfWAFRuleComparisonGroup */
32
private $comparisonGroup;
33
/**
34
* @var wfWAF
101
$this->setComparisonGroup($comparisonGroup);
102
}
103
104
+ public function __sleep() {
105
+ return array(
106
+ 'ruleID',
107
+ 'type',
108
+ 'category',
109
+ 'score',
110
+ 'description',
111
+ 'whitelist',
112
+ 'action',
113
+ 'comparisonGroup',
114
+ );
115
+ }
116
+
117
/**
118
* @return string
119
*/
309
*/
310
public function setWAF($waf) {
311
$this->waf = $waf;
312
+ if ($this->comparisonGroup) {
313
+ $this->comparisonGroup->setWAF($waf);
314
+ }
315
}
316
}
317
352
$this->setComparison($comparison);
353
}
354
355
+ public function __sleep() {
356
+ return array(
357
+ 'operator',
358
+ 'currentValue',
359
+ 'comparison',
360
+ );
361
+ }
362
+
363
/**
364
* @return string
365
* @throws wfWAFRuleLogicalOperatorException
529
$this->setSubjects($subjects);
530
}
531
532
+ public function __sleep() {
533
+ return array(
534
+ 'rule',
535
+ 'action',
536
+ 'expected',
537
+ 'subjects',
538
+ );
539
+ }
540
+
541
/**
542
* @param string|array $subject
543
* @return string
1208
*/
1209
public function setWAF($waf) {
1210
$this->waf = $waf;
1211
+ if (is_array($this->subjects)) {
1212
+ foreach ($this->subjects as $subject) {
1213
+ if (is_object($subject) && method_exists($subject, 'setWAF')) {
1214
+ $subject->setWAF($waf);
1215
+ }
1216
+ }
1217
+ }
1218
+ if (is_object($this->expected) && method_exists($this->expected, 'setWAF')) {
1219
+ $this->expected->setWAF($waf);
1220
+ }
1221
}
1222
1223
/**
1240
private $items = array();
1241
private $failedComparisons = array();
1242
private $result = false;
1243
+ private $waf;
1244
+
1245
/**
1246
* @var wfWAFRule
1247
*/
1254
}
1255
}
1256
1257
+ public function __sleep() {
1258
+ return array(
1259
+ 'items',
1260
+ );
1261
+ }
1262
+
1263
public function add($item) {
1264
$this->items[] = $item;
1265
}
1425
public function setRule($rule) {
1426
$this->rule = $rule;
1427
}
1428
+
1429
+ /**
1430
+ * @return mixed
1431
+ */
1432
+ public function getWAF() {
1433
+ return $this->waf;
1434
+ }
1435
+
1436
+ /**
1437
+ * @param mixed $waf
1438
+ */
1439
+ public function setWAF($waf) {
1440
+ $this->waf = $waf;
1441
+ foreach ($this->items as $item) {
1442
+ if (is_object($item) && method_exists($item, 'setWAF')) {
1443
+ $item->setWAF($waf);
1444
+ }
1445
+ }
1446
+ }
1447
}
1448
1449
class wfWAFRuleComparisonFailure {
1481
$this->setMatches($matches);
1482
}
1483
1484
+ public function __sleep() {
1485
+ return array(
1486
+ 'paramKey',
1487
+ 'expected',
1488
+ 'action',
1489
+ 'multiplier',
1490
+ 'paramValue',
1491
+ 'matches',
1492
+ );
1493
+ }
1494
+
1495
/**
1496
* @return mixed
1497
*/
1614
$this->filters = $filters;
1615
}
1616
1617
+ public function __sleep() {
1618
+ return array(
1619
+ 'subject',
1620
+ 'filters',
1621
+ );
1622
+ }
1623
+
1624
/**
1625
* @return mixed|null
1626
*/
vendor/wordfence/wf-waf/src/lib/storage.php CHANGED
@@ -67,5 +67,13 @@ interface wfWAFStorageInterface {
67
public function getRulesDSLCacheFile();
68
69
public function isAttackDataFull();
70
}
71
}
67
public function getRulesDSLCacheFile();
68
69
public function isAttackDataFull();
70
+
71
+ public function vacuum();
72
+
73
+ public function getRules();
74
+
75
+ public function setRules($rules);
76
+
77
+ public function needsInitialRules();
78
}
79
}
vendor/wordfence/wf-waf/src/lib/storage/file.php CHANGED
@@ -6,7 +6,12 @@ class wfWAFStorageFile implements wfWAFStorageInterface {
6
const LOG_FILE_HEADER = "<?php exit('Access denied'); __halt_compiler(); ?>\n";
7
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";
8
const IP_BLOCK_RECORD_SIZE = 24;
9
-
10
public static function allowFileWriting() {
11
if (defined('WFWAF_ALWAYS_ALLOW_FILE_WRITING') && WFWAF_ALWAYS_ALLOW_FILE_WRITING) {
12
return true;
@@ -123,6 +128,7 @@ class wfWAFStorageFile implements wfWAFStorageInterface {
123
*/
124
private $ipCacheFile;
125
private $configFile;
126
private $rulesDSLCacheFile;
127
private $dataChanged = array();
128
private $data = array();
@@ -139,12 +145,14 @@ class wfWAFStorageFile implements wfWAFStorageInterface {
139
* @param string|null $attackDataFile
140
* @param string|null $ipCacheFile
141
* @param string|null $configFile
142
* @param null $rulesDSLCacheFile
143
*/
144
- public function __construct($attackDataFile = null, $ipCacheFile = null, $configFile = null, $rulesDSLCacheFile = null) {
145
$this->setAttackDataFile($attackDataFile);
146
$this->setIPCacheFile($ipCacheFile);
147
$this->setConfigFile($configFile);
148
$this->setRulesDSLCacheFile($rulesDSLCacheFile);
149
}
150
@@ -884,6 +892,36 @@ class wfWAFStorageFile implements wfWAFStorageInterface {
884
public function setAttackDataEngine($attackDataEngine) {
885
$this->attackDataEngine = $attackDataEngine;
886
}
887
}
888
889
class wfWAFAttackDataStorageFileEngine {
6
const LOG_FILE_HEADER = "<?php exit('Access denied'); __halt_compiler(); ?>\n";
7
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";
8
const IP_BLOCK_RECORD_SIZE = 24;
9
+ private $rules;
10
+ private $failScores;
11
+ private $variables;
12
+ private $whitelistedParams;
13
+ private $blacklistedParams;
14
+
15
public static function allowFileWriting() {
16
if (defined('WFWAF_ALWAYS_ALLOW_FILE_WRITING') && WFWAF_ALWAYS_ALLOW_FILE_WRITING) {
17
return true;
128
*/
129
private $ipCacheFile;
130
private $configFile;
131
+ private $rulesFile;
132
private $rulesDSLCacheFile;
133
private $dataChanged = array();
134
private $data = array();
145
* @param string|null $attackDataFile
146
* @param string|null $ipCacheFile
147
* @param string|null $configFile
148
+ * @param string|null $rulesFile
149
* @param null $rulesDSLCacheFile
150
*/
151
+ public function __construct($attackDataFile = null, $ipCacheFile = null, $configFile = null, $rulesFile = null, $rulesDSLCacheFile = null) {
152
$this->setAttackDataFile($attackDataFile);
153
$this->setIPCacheFile($ipCacheFile);
154
$this->setConfigFile($configFile);
155
+ $this->setRulesFile($rulesFile);
156
$this->setRulesDSLCacheFile($rulesDSLCacheFile);
157
}
158
892
public function setAttackDataEngine($attackDataEngine) {
893
$this->attackDataEngine = $attackDataEngine;
894
}
895
+
896
+ public function getRules() {
897
+ throw new wfWAFStorageFileException('wfWAFStorageFile::getRules not implemented.');
898
+ }
899
+
900
+ public function setRules($rules) {
901
+ throw new wfWAFStorageFileException('wfWAFStorageFile::getRules not implemented.');
902
+ }
903
+
904
+ public function needsInitialRules() {
905
+ if (file_exists($this->getRulesFile())) {
906
+ return is_writeable($this->getRulesFile()) && !filesize($this->getRulesFile());
907
+ } else {
908
+ return is_writeable(dirname($this->getRulesFile()));
909
+ }
910
+ }
911
+
912
+ /**
913
+ * @return mixed
914
+ */
915
+ public function getRulesFile() {
916
+ return $this->rulesFile;
917
+ }
918
+
919
+ /**
920
+ * @param mixed $rulesFile
921
+ */
922
+ public function setRulesFile($rulesFile) {
923
+ $this->rulesFile = $rulesFile;
924
+ }
925
}
926
927
class wfWAFAttackDataStorageFileEngine {
vendor/wordfence/wf-waf/src/lib/storage/mysql.php ADDED
@@ -0,0 +1,1120 @@
1
+ <?php
2
+
3
+ /**
4
+ *
5
+ */
6
+ class wfWAFStorageMySQL implements wfWAFStorageInterface {
7
+
8
+ private $_usingLowercase;
9
+
10
+ /**
11
+ * @var wfWAFStorageEngineDatabase
12
+ */
13
+ private $db;
14
+ /**
15
+ * @var string
16
+ */
17
+ private $tablePrefix;
18
+ private $uninstalled;
19
+ private $dataChanged = false;
20
+ private $data = array();
21
+ private $dataToSave = array();
22
+
23
+ public $installing = false;
24
+
25
+ /**
26
+ * @param wfWAFStorageEngineDatabase $engine
27
+ * @param string $tablePrefix
28
+ */
29
+ public function __construct($engine, $tablePrefix = 'wp_') {
30
+ $this->db = $engine;
31
+ $this->tablePrefix = $tablePrefix;
32
+ }
33
+
34
+ public function usingLowercase() {
35
+ if ($this->_usingLowercase === null) {
36
+ $table = $this->tablePrefix . 'wfConfig';
37
+ $tableExists = $this->getDb()->get_var("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND BINARY TABLE_NAME='$table'");
38
+ $this->_usingLowercase = $tableExists !== $table;
39
+ }
40
+ return $this->_usingLowercase;
41
+ }
42
+
43
+ /**
44
+ * Returns the table with the site (single site installations) or network (multisite) prefix added.
45
+ *
46
+ * @param string $table
47
+ * @param bool $applyCaseConversion Whether or not to convert the table case to what is actually in use.
48
+ * @return string
49
+ */
50
+ public function networkTable($table, $applyCaseConversion = true) {
51
+ if ($this->usingLowercase() && $applyCaseConversion) {
52
+ $table = strtolower($table);
53
+ }
54
+ return $this->tablePrefix . $table;
55
+ }
56
+
57
+ /**
58
+ * Check if there's attack before a certain timestamp.
59
+ *
60
+ * @param int $olderThan
61
+ * @return bool
62
+ */
63
+ public function hasPreviousAttackData($olderThan) {
64
+ $table = $this->networkTable('wfHits');
65
+ $lastAttackDataTruncateTime = floatval($this->getConfig('lastAttackDataTruncateTime'));
66
+ $count = $this->db->get_var('SELECT count(*) FROM ' . $table . ' where attackLogTime < ? and attackLogTime > ?', array(
67
+ sprintf('%.6f', $olderThan),
68
+ $lastAttackDataTruncateTime,
69
+ ));
70
+ return $count > 0;
71
+ }
72
+
73
+ /**
74
+ * Check if there's attack data after a given timestamp.
75
+ *
76
+ * @param int $newerThan
77
+ * @return bool
78
+ */
79
+ public function hasNewerAttackData($newerThan) {
80
+ $table = $this->networkTable('wfHits');
81
+ $lastAttackDataTruncateTime = floatval($this->getConfig('lastAttackDataTruncateTime'));
82
+ $count = $this->db->get_var('SELECT count(*) FROM ' . $table . ' where attackLogTime > ?', array(
83
+ sprintf('%.6f', max($newerThan, $lastAttackDataTruncateTime)),
84
+ ));
85
+ return $count > 0;
86
+ }
87
+
88
+ /**
89
+ * Get all attack data.
90
+ *
91
+ *
92
+ */
93
+ public function getAttackData() {
94
+ $table = $this->networkTable('wfHits');
95
+ $lastAttackDataTruncateTime = floatval($this->getConfig('lastAttackDataTruncateTime'));
96
+ $results = $this->db->get_results('SELECT * FROM ' . $table . ' WHERE attackLogTime > ?', array(
97
+ $lastAttackDataTruncateTime,
98
+ ));
99
+
100
+ $data = array();
101
+ foreach ($results as $row) {
102
+ $actionData = wfWAFUtils::json_decode($row['actionData'], true);
103
+ $data[] = array(
104
+ $row['attackLogTime'],
105
+ $row['ctime'],
106
+ wfWAFUtils::inet_ntop($row['IP']),
107
+ (array_key_exists('learningMode', $actionData) ? $actionData['learningMode'] : 0),
108
+ (array_key_exists('paramKey', $actionData) ? $actionData['paramKey'] : false),
109
+ (array_key_exists('paramValue', $actionData) ? $actionData['paramValue'] : false),
110
+ (array_key_exists('failedRules', $actionData) ? $actionData['failedRules'] : ''),
111
+ strpos($row['URL'], 'https') === 0 ? 1 : 0,
112
+ (array_key_exists('fullRequest', $actionData) ? $actionData['fullRequest'] : ''),
113
+ );
114
+ }
115
+ return wfWAFUtils::json_encode($data);
116
+ }
117
+
118
+ /**
119
+ * Get all attack data in array format.
120
+ */
121
+ public function getAttackDataArray() {
122
+ return $this->getNewestAttackDataArray(floatval($this->getConfig('lastAttackDataTruncateTime')));
123
+ }
124
+
125
+ /**
126
+ * Get attack data after a certain timestamp in array format.
127
+ *
128
+ * @param int $newerThan
129
+ * @return array
130
+ */
131
+ public function getNewestAttackDataArray($newerThan) {
132
+ $table = $this->networkTable('wfHits');
133
+ $results = $this->db->get_results('SELECT * FROM ' . $table . ' WHERE attackLogTime > ?', array(
134
+ $newerThan,
135
+ ));
136
+
137
+ $data = array();
138
+ foreach ($results as $row) {
139
+ $actionData = wfWAFUtils::json_decode($row['actionData'], true);
140
+ $data[] = array(
141
+ $row['attackLogTime'],
142
+ $row['ctime'],
143
+ wfWAFUtils::inet_ntop($row['IP']),
144
+ (array_key_exists('learningMode', $actionData) ? $actionData['learningMode'] : 0),
145
+ (array_key_exists('paramKey', $actionData) ? base64_decode($actionData['paramKey']) : false),
146
+ (array_key_exists('paramValue', $actionData) ? base64_decode($actionData['paramValue']) : false),
147
+ (array_key_exists('failedRules', $actionData) ? $actionData['failedRules'] : ''),
148
+ strpos($row['URL'], 'https') === 0 ? 1 : 0,
149
+ (array_key_exists('fullRequest', $actionData) ? base64_decode($actionData['fullRequest']) : ''),
150
+ (array_key_exists('requestMetadata', $actionData) ? $actionData['requestMetadata'] : ''),
151
+ $row['id'],
152
+ );
153
+ }
154
+ return $data;
155
+ }
156
+
157
+ /**
158
+ * I don't think this will be needed for what it's used for in the plugin.
159
+ */
160
+ public function truncateAttackData() {
161
+ $this->setConfig('lastAttackDataTruncateTime', microtime(true));
162
+ return true;
163
+ }
164
+
165
+ /**
166
+ * Insert request into wfHits.
167
+ *
168
+ * @param array $failedRules
169
+ * @param string $failedParamKey
170
+ * @param string $failedParamValue
171
+ * @param wfWAFRequestInterface $request
172
+ * @param mixed $_
173
+ * @return mixed
174
+ */
175
+ public function logAttack($failedRules, $failedParamKey, $failedParamValue, $request, $_ = null) {
176
+ $table = $this->networkTable('wfHits');
177
+
178
+ $failedRulesString = '';
179
+ $actionDescription = '';
180
+ if (is_array($failedRules)) {
181
+ /**
182
+ * @var int $index
183
+ * @var wfWAFRule|int $rule
184
+ */
185
+ foreach ($failedRules as $index => $rule) {
186
+ if ($rule instanceof wfWAFRule) {
187
+ $failedRulesString .= $rule->getRuleID() . '|';
188
+ $actionDescription .= $rule->getDescription() . ', ';
189
+ } else {
190
+ $failedRulesString .= $rule . '|';
191
+ $actionDescription .= $rule . ', ';
192
+ }
193
+ }
194
+ $failedRulesString = wfWAFUtils::substr($failedRulesString, 0, -1);
195
+ $actionDescription = wfWAFUtils::substr($actionDescription, 0, -2);
196
+ }
197
+ if (preg_match('/\blogged\b/i', $failedRulesString)) {
198
+ $statusCode = 200;
199
+ $action = 'logged:waf';
200
+ } else {
201
+ $statusCode = 403;
202
+ $action = 'blocked:waf';
203
+ }
204
+
205
+ $ua = '';
206
+ $referer = '';
207
+ $headers = $request->getHeaders();
208
+ if (is_array($headers)) {
209
+ if (array_key_exists('User-Agent', $headers)) {
210
+ $ua = $headers['User-Agent'];
211
+ }
212
+ if (array_key_exists('Referer', $headers)) {
213
+ $referer = $headers['Referer'];
214
+ }
215
+ }
216
+
217
+ $row = array(
218
+ 'attackLogTime' => microtime(true),
219
+ 'ctime' => $request->getTimestamp(),
220
+ 'IP' => wfWAFUtils::inet_pton($request->getIP()),
221
+ 'statusCode' => $statusCode,
222
+ 'URL' => $request->getProtocol() . '://' . $request->getHost() . $request->getURI(),
223
+ 'isGoogle' => 0,
224
+ 'userID' => 0,
225
+ 'newVisit' => 0,
226
+ 'referer' => $referer,
227
+ 'UA' => $ua,
228
+ 'action' => $action,
229
+ 'actionDescription' => $actionDescription,
230
+ 'actionData' => wfWAFUtils::json_encode(array(
231
+ 'failedRules' => $failedRulesString,
232
+ 'paramKey' => base64_encode($failedParamKey),
233
+ 'paramValue' => base64_encode($failedParamValue),
234
+ 'path' => base64_encode($request->getPath()),
235
+ 'fullRequest' => base64_encode($request),
236
+ 'requestMetadata' => $request->getMetadata(),
237
+ )),
238
+ );
239
+
240
+ try {
241
+ return $this->db->insert($table, $row);
242
+ } catch (wfWAFStorageEngineMySQLiException $e) { // Let the firewall block the request without logging.
243
+ error_log($e->getMessage());
244
+ return false;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Insert IP into wfBlocks.
250
+ *
251
+ * @param float $timestamp
252
+ * @param string $ip
253
+ * @param int $type
254
+ * @return mixed
255
+ */
256
+ public function blockIP($timestamp, $ip, $type = wfWAFStorageInterface::IP_BLOCKS_SINGLE) {
257
+ $blockedIPs = $this->getConfig('wfWAFBlockedIPs');
258
+ if (!$blockedIPs) {
259
+ $blockedIPs = array();
260
+ }
261
+ $blockedIPs[$ip] = array($timestamp, $type);
262
+ $this->setConfig('wfWAFBlockedIPs', $blockedIPs);
263
+ return true;
264
+ }
265
+
266
+ /**
267
+ * Check if the IP is in wfBlocks.
268
+ *
269
+ * @param string $ip
270
+ * @return bool
271
+ */
272
+ public function isIPBlocked($ip) {
273
+ $blockedIPs = $this->getConfig('wfWAFBlockedIPs');
274
+ if (!$blockedIPs) {
275
+ $blockedIPs = array();
276
+ }
277
+ return array_key_exists($ip, $blockedIPs) && is_array($blockedIPs[$ip]) && $blockedIPs[$ip][0] >= time();
278
+ }
279
+
280
+ /**
281
+ * Remove all blocked IPs.
282
+ *
283
+ * @param int $types
284
+ */
285
+ public function purgeIPBlocks($types = wfWAFStorageInterface::IP_BLOCKS_ALL) {
286
+ if ($types === wfWAFStorageInterface::IP_BLOCKS_ALL) {
287
+ $this->unsetConfig('wfWAFBlockedIPs');
288
+ } else {
289
+ $blockedIPs = $this->getConfig('wfWAFBlockedIPs');
290
+ if (!$blockedIPs) {
291
+ $blockedIPs = array();
292
+ }
293
+ foreach ($blockedIPs as $key => $values) {
294
+ list($timestamp, $type) = $values;
295
+ if (($type & $types) > 0 || $timestamp < time()) {
296
+ unset($blockedIPs[$key]);
297
+ }
298
+ }
299
+ $this->setConfig('wfWAFBlockedIPs', $blockedIPs);
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Query config item from wfConfig table.
305
+ *
306
+ * @param $key
307
+ * @param null $default
308
+ * @param string $category
309
+ * @return mixed
310
+ */
311
+ public function getConfig($key, $default = null, $category = '') {
312
+ if (!$this->data) {
313
+ $this->autoloadConfig();
314
+ }
315
+
316
+ if (array_key_exists($category, $this->data) && array_key_exists($key, $this->data[$category])) {
317
+ return $this->data[$category][$key];
318
+ }
319
+
320
+ $table = $this->getStorageTable($category);
321
+ $val = $this->db->get_var('SELECT val FROM ' . $table . ' WHERE name = ?', array(
322
+ $key,
323
+ ));
324
+ if ($val !== null) {
325
+ if (in_array($key, $this->getSerializedParams())) {
326
+ $value = @unserialize($val);
327
+ $this->data[$category][$key] = $value;
328
+ return $value;
329
+ }
330
+ $this->data[$category][$key] = $val;
331
+ return $val;
332
+ }
333
+ return $default;
334
+ }
335
+
336
+ /**
337
+ * Insert/update wfConfig table for WAF option.
338
+ *
339
+ * @param $key
340
+ * @param $value
341
+ * @param string $category
342
+ */
343
+ public function setConfig($key, $value, $category = '') {
344
+ if (!array_key_exists($category, $this->data)) {
345
+ $this->data[$category] = array();
346
+ }
347
+
348
+ $changedConfigValue = (array_key_exists($key, $this->data[$category]) && $this->data[$category][$key] != $value) ||
349
+ !array_key_exists($key, $this->data[$category]);
350
+
351
+ if (!$this->dataChanged && $changedConfigValue) {
352
+ $this->dataChanged = array($category, $key, true);
353
+ register_shutdown_function(array($this, 'saveConfig'));
354
+ }
355
+ if ($changedConfigValue) {
356
+ $this->dataToSave[$category][$key] = $value;
357
+ }
358
+
359
+ $this->data[$category][$key] = $value;
360
+ }
361
+
362
+ /**
363
+ * Delete config item from wfConfig table.
364
+ *
365
+ * @param $key
366
+ * @param string $category
367
+ */
368
+ public function unsetConfig($key, $category = '') {
369
+ unset($this->data[$category][$key]);
370
+ $table = $this->getStorageTable($category);
371
+ $this->db->delete($table, array(
372
+ 'name' => $key,
373
+ ));
374
+ }
375
+
376
+ /**
377
+ *
378
+ */
379
+ public function saveConfig() {
380
+ if ($this->uninstalled) {
381
+ return;
382
+ }
383
+
384
+ try {
385
+ foreach ($this->dataToSave as $category => $data) {
386
+ foreach ($data as $key => $value) {
387
+ if (in_array($key, $this->getSerializedParams())) {
388
+ $value = serialize($value);
389
+ }
390
+ $table = $this->getStorageTable($category);
391
+ $this->db->query("INSERT INTO {$table} (name, val, autoload) values (?, ?, 'no') ON DUPLICATE KEY UPDATE val = ?", array(
392
+ $key,
393
+ $value,
394
+ $value,
395
+ ));
396
+ }
397
+ }
398
+ } catch (wfWAFStorageEngineMySQLiException $e) {
399
+ error_log($e);
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Remove related WAF specific configuration.
405
+ */
406
+ public function uninstall() {
407
+ try {
408
+ $this->getDb()->query("DROP TABLE IF EXISTS " . $this->networkTable('wfwafconfig'));
409
+ } catch (wfWAFStorageEngineMySQLiException $e) {
410
+ error_log($e);
411
+ }
412
+ $this->uninstalled = true;
413
+ }
414
+
415
+ /**
416
+ * Pull from wfConfig.
417
+ */
418
+ public function isInLearningMode() {
419
+ if ($this->getConfig('wafStatus', '') == 'learning-mode') {
420
+ if ($this->getConfig('learningModeGracePeriodEnabled', false)) {
421
+ if ($this->getConfig('learningModeGracePeriod', 0) > time()) {
422
+ return true;
423
+ } else {
424
+ // Reached the end of the grace period, activate the WAF.
425
+ $this->setConfig('wafStatus', 'enabled');
426
+ $this->setConfig('learningModeGracePeriodEnabled', 0);
427
+ $this->unsetConfig('learningModeGracePeriod');
428
+ }
429
+ } else {
430
+ return true;
431
+ }
432
+ }
433
+ return false;
434
+ }
435
+
436
+ /**
437
+ * Pull from wfConfig.
438
+ */
439
+ public function isDisabled() {
440
+ return $this->getConfig('wafStatus', '') === 'disabled' || $this->getConfig('wafDisabled', 0);
441
+ }
442
+
443
+ /**
444
+ * Return hardcoded path maybe?
445
+ */
446
+ public function getRulesDSLCacheFile() {
447
+
448
+ }
449
+
450
+ /**
451
+ * Probably not.
452
+ */
453
+ public function isAttackDataFull() {
454
+ return false;
455
+ }
456
+
457
+ /**
458
+ *
459
+ */
460
+ public function vacuum() {
461
+ $this->purgeIPBlocks(wfWAFStorageInterface::IP_BLOCKS_ALL);
462
+ }
463
+
464
+ /**
465
+ * @return wfWAFStorageEngineDatabase
466
+ */
467
+ public function getDb() {
468
+ return $this->db;
469
+ }
470
+
471
+ /**
472
+ *
473
+ */
474
+ public function setDefaults() {
475
+ $defaults = $this->getDefaultConfiguration();
476
+ foreach ($defaults as $key => $value) {
477
+ $val = $this->getConfig($key);
478
+ if ($val === null) {
479
+ $this->setConfig($key, $value);
480
+ }
481
+ }
482
+ }
483
+
484
+ /**
485
+ *
486
+ */
487
+ public function runMigrations() {
488
+ // $currentVersion = $this->getConfig('version');
489
+ // if (!$currentVersion || version_compare($currentVersion, WFWAF_VERSION) === -1) {
490
+ //
491
+ // }
492
+
493
+ $this->getDb()->query("CREATE TABLE IF NOT EXISTS " . $this->networkTable('wfwafconfig') .
494
+ " (
495
+ `name` varchar(100) NOT NULL,
496
+ `val` longblob,
497
+ `autoload` enum('no','yes') NOT NULL DEFAULT 'yes',
498
+ PRIMARY KEY (`name`)
499
+ ) DEFAULT CHARSET=utf8
500
+ ");
501
+ }
502
+
503
+ /**
504
+ * @return array
505
+ */
506
+ public function getDefaultConfiguration() {
507
+ return array(
508
+ 'wafStatus' => 'learning-mode',
509
+ 'learningModeGracePeriodEnabled' => 1,
510
+ 'learningModeGracePeriod' => time() + (86400 * 7),
511
+ 'authKey' => wfWAFUtils::getRandomString(64),
512
+ );
513
+ }
514
+
515
+ /**
516
+ * @return array
517
+ */
518
+ public function getSerializedParams() {
519
+ return array(
520
+ 'cron',
521
+ 'whitelistedURLParams',
522
+ 'disabledRules',
523
+ 'wfWAFBlockedIPs',
524
+ 'wafRules',
525
+ );
526
+ }
527
+
528
+ /**
529
+ * @return array
530
+ */
531
+ public function getAutoloadParams() {
532
+ return array(
533
+ '' => array(
534
+ 'wafStatus',
535
+ 'learningModeGracePeriodEnabled',
536
+ 'learningModeGracePeriod',
537
+ 'authKey',
538
+ 'version',
539
+ 'advancedBlockingEnabled',
540
+ 'disabledRules',
541
+ 'patternBlocks',
542
+ 'countryBlocks',
543
+ 'otherBlocks',
544
+ 'lockouts',
545
+ 'wafRules',
546
+ 'avoid_php_input',
547
+ 'wafDisabled',
548
+ 'wfWAFBlockedIPs',
549
+ 'disableWAFBlacklistBlocking',
550
+ ),
551
+ 'livewaf' => array(
552
+ 'cron',
553
+ 'whitelistedURLParams',
554
+ 'whitelistedURLs',
555
+ ),
556
+ 'transient' => array(
557
+ 'watchedIPs',
558
+ 'blockedPrefixes',
559
+ ),
560
+ 'synced' => array(
561
+ 'timeoffset_wf',
562
+ 'apiKey',
563
+ 'isPaid',
564
+ 'siteURL',
565
+ 'homeURL',
566
+ 'whitelistedIPs',
567
+ 'howGetIPs',
568
+ 'howGetIPs_trusted_proxies',
569
+ 'other_WFNet',
570
+ 'pluginABSPATH',
571
+ 'serverIPs',
572
+ 'disableWAFIPBlocking',
573
+ 'advancedBlockingEnabled',
574
+ ),
575
+ );
576
+ }
577
+
578
+ protected function autoloadConfig() {
579
+ $params = $this->getAutoloadParams();
580
+ foreach ($params as $category => $autoloadParams) {
581
+ // Set default keys to null to prevent re-querying the table for config keypairs that aren't in the table.
582
+ foreach ($autoloadParams as $autoloadParam) {
583
+ $this->data[$category][$autoloadParam] = null;
584
+ }
585
+
586
+ $table = $this->getStorageTable($category);
587
+ $whereIn = str_repeat('?,', count($autoloadParams) - 1) . '?';
588
+ $results = $this->db->get_results('SELECT * FROM ' . $table . ' WHERE name IN (' . $whereIn . ')', $autoloadParams);
589
+ $serializedParams = $this->getSerializedParams();
590
+ foreach ($results as $row) {
591
+ if (in_array($row['name'], $serializedParams)) {
592
+ $this->data[$category][$row['name']] = @unserialize($row['val']);
593
+ } else {
594
+ $this->data[$category][$row['name']] = $row['val'];
595
+ }
596
+ }
597
+ }
598
+ }
599
+
600
+ public function getRules() {
601
+ return $this->getConfig('wafRules');
602
+ }
603
+
604
+ public function setRules($rules) {
605
+ $this->setConfig('wafRules', $rules);
606
+ }
607
+
608
+ public function needsInitialRules() {
609
+ $rules = $this->getRules();
610
+ return !$rules;
611
+ }
612
+
613
+ public function getStorageTable($category) {
614
+ switch ($category) {
615
+ case 'livewaf':
616
+ case 'transient':
617
+ $table = $this->networkTable('wfwafconfig');
618
+ break;
619
+ default:
620
+ $table = $this->networkTable('wfConfig');
621
+ break;
622
+ }
623
+ return $table;
624
+ }
625
+ }
626
+
627
+ interface wfWAFStorageEngineDatabase {
628
+
629
+ public function connect($user, $password, $database, $host, $port = null, $socket = null);
630
+
631
+ public function setCharset($charset, $collation);
632
+
633
+ public function close();
634
+
635
+ public function insert($table, $data);
636
+
637
+ public function update($table, $data, $where);
638
+
639
+ public function delete($table, $where);
640
+
641
+ public function query($sql, $data = array());
642
+
643
+ public function get_var($query = null, $data = array(), $x = 0, $y = 0);
644
+
645
+ public function get_row($query = null, $data = array(), $y = 0);
646
+
647
+ public function get_results($query = null, $data = array());
648
+ }
649
+
650
+ class wfWAFStorageEngineMySQLi implements wfWAFStorageEngineDatabase {
651
+
652
+ /**
653
+ * @var string
654
+ */
655
+ private $user;
656
+ /**
657
+ * @var string
658
+ */
659
+ private $password;
660
+ /**
661
+ * @var string
662
+ */
663
+ private $database;
664
+ /**
665
+ * @var string
666
+ */
667
+ private $host;
668
+ /**
669
+ * @var int|null
670
+ */
671
+ private $port;
672
+ /**
673
+ * @var string|null
674
+ */
675
+ private $socket;
676
+
677
+ /** @var mysqli */
678
+ private $dbh;
679
+
680
+ private $lastStatement;
681
+
682
+ public $installing = false;
683
+
684
+ /**
685
+ *
686
+ */
687
+ public function __construct() {
688
+
689
+ }
690
+
691
+ /**
692
+ * @param string $user
693
+ * @param string $password
694
+ * @param string $database
695
+ * @param string $host
696
+ * @param null|int $port
697
+ * @param mixed $socket
698
+ * @return mysqli
699
+ * @throws wfWAFStorageEngineMySQLiException
700
+ */
701
+ public function connect($user, $password, $database, $host, $port = null, $socket = null) {
702
+ $this->dbh = @mysqli_connect($host, $user, $password, $database, $port, $socket);
703
+ if (!$this->dbh) {
704
+ $error = error_get_last();
705
+ throw new wfWAFStorageEngineMySQLiException('Unable to connect to database: ' . $error['message'], $error['type']);
706
+ }
707
+
708
+ return $this->dbh;
709
+ }
710
+
711
+ public function setCharset($charset, $collation) {
712
+ $result = $this->determineCharset($charset, $collation);
713
+ $charset = $result['charset'];
714
+ $collation = $result['collation'];
715
+ $this->setConnectionCharset($charset, $collation);
716
+ }
717
+
718
+ protected function determineCharset($charset, $collation) {
719
+ if ('utf8' === $charset && $this->hasCap('utf8mb4')) {
720
+ $charset = 'utf8mb4';
721
+ }
722
+
723
+ if ('utf8mb4' === $charset && !$this->hasCap('utf8mb4')) {
724
+ $charset = 'utf8';
725
+ $collation = str_replace('utf8mb4_', 'utf8_', $collation);
726
+ }
727
+
728
+ if ('utf8mb4' === $charset) {
729
+ // _general_ is outdated, so we can upgrade it to _unicode_, instead.
730
+ if (!$collation || 'utf8_general_ci' === $collation) {
731
+ $collation = 'utf8mb4_unicode_ci';
732
+ } else {
733
+ $collation = str_replace('utf8_', 'utf8mb4_', $collation);
734
+ }
735
+ }
736
+
737
+ // _unicode_520_ is a better collation, we should use that when it's available.
738
+ if ($this->hasCap('utf8mb4_520') && 'utf8mb4_unicode_ci' === $collation) {
739
+ $collation = 'utf8mb4_unicode_520_ci';
740
+ }
741
+
742
+ return compact('charset', 'collation');
743
+ }
744
+
745
+ /**
746
+ * Determine if a database supports a particular feature.
747
+ *
748
+ * @param string $dbCap The feature to check for. Accepts 'collation',
749
+ * 'group_concat', 'subqueries', 'set_charset',
750
+ * 'utf8mb4', or 'utf8mb4_520'.
751
+ * @return int|false Whether the database feature is supported, false otherwise.
752
+ */
753
+ public function hasCap($dbCap) {
754
+ $version = $this->dbVersion();
755
+
756
+ switch (strtolower($dbCap)) {
757
+ case 'collation' : // @since 2.5.0
758
+ case 'group_concat' : // @since 2.7.0
759
+ case 'subqueries' : // @since 2.7.0
760
+ return version_compare($version, '4.1', '>=');
761
+ case 'set_charset' :
762
+ return version_compare($version, '5.0.7', '>=');
763
+ case 'utf8mb4' : // @since 4.1.0
764
+ if (version_compare($version, '5.5.3', '<')) {
765
+ return false;
766
+ }
767
+ $client_version = mysqli_get_client_info();
768
+
769
+ /*
770
+ * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
771
+ * mysqlnd has supported utf8mb4 since 5.0.9.
772
+ */
773
+ if (false !== strpos($client_version, 'mysqlnd')) {
774
+ $client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version);
775
+ return version_compare($client_version, '5.0.9', '>=');
776
+ } else {
777
+ return version_compare($client_version, '5.5.3', '>=');
778
+ }
779
+ case 'utf8mb4_520' : // @since 4.6.0
780
+ return version_compare($version, '5.6', '>=');
781
+ }
782
+
783
+ return false;
784
+ }
785
+
786
+ public function setConnectionCharset($charset, $collation) {
787
+ if ($this->hasCap('collation') && !empty($charset)) {
788
+ $setCharsetSucceeded = false;
789
+
790
+ if (function_exists('mysqli_set_charset') && $this->hasCap('set_charset')) {
791
+ $setCharsetSucceeded = mysqli_set_charset($this->dbh, $charset);
792
+ }
793
+
794
+ if ($setCharsetSucceeded) {
795
+ $query = "SET NAMES {$this->escape($charset)}";
796
+ if ($collation) {
797
+ $query .= " COLLATE {$this->escape($collation)}";
798
+ }
799
+ $this->query($query);
800
+ }
801
+ }
802
+ }
803
+
804
+ /**
805
+ * Retrieves the MySQL server version.
806
+ *
807
+ * @return null|string Null on failure, version number on success.
808
+ */
809
+ public function dbVersion() {
810
+ $serverInfo = mysqli_get_server_info($this->dbh);
811
+ return preg_replace('/[^0-9.].*/', '', $serverInfo);
812
+ }
813
+
814
+ /**
815
+ *
816
+ */
817
+ public function close() {
818
+ mysqli_close($this->dbh);
819
+ }
820
+
821
+ /**
822
+ * @param string $table
823
+ * @param array $data
824
+ * @return bool|int|string
825
+ */
826
+ public function insert($table, $data) {
827
+ $sql = $this->buildInsertSQL($table, $data);
828
+ if ($stmt = $this->query($sql, $data)) {
829
+ $insertID = mysqli_insert_id($this->dbh);
830
+ $stmt->close();
831
+ return $insertID;
832
+ }
833
+ return false;
834
+ }
835
+
836
+ /**
837
+ * @param string $table
838
+ * @param array $data
839
+ * @param array $where
840
+ * @return bool|int
841
+ * @throws wfWAFStorageEngineMySQLiException
842
+ */
843
+ public function update($table, $data, $where) {
844
+ if (!$data) {
845
+ throw new wfWAFStorageEngineMySQLiException('Values to update must supplied to \wfWAFStorageEngineMySQLi::update.');
846
+ }
847
+ if (!$where) {
848
+ throw new wfWAFStorageEngineMySQLiException('A where clause must supplied to \wfWAFStorageEngineMySQLi::update.');
849
+ }
850
+ $sql = $this->buildUpdateSQL($table, $data, $where);
851
+ if ($stmt = $this->query($sql, array_merge(array_values($data), array_values($where)))) {
852
+ $affectedRows = mysqli_affected_rows($this->dbh);
853
+ $stmt->close();
854
+ return $affectedRows;
855
+ }
856
+ return false;
857
+
858
+ }
859
+
860
+ /**
861
+ * @param string $table
862
+ * @param array $where
863
+ * @return bool|int
864
+ * @throws wfWAFStorageEngineMySQLiException
865
+ */
866
+ public function delete($table, $where) {
867
+ if (!$where) {
868
+ throw new wfWAFStorageEngineMySQLiException('A where clause must supplied to \wfWAFStorageEngineMySQLi::delete.');
869
+ }
870
+ $sql = $this->buildDeleteSQL($table, $where);
871
+ if ($stmt = $this->query($sql, $where)) {
872
+ $affectedRows = mysqli_affected_rows($this->dbh);
873
+ $stmt->close();
874
+ return $affectedRows;
875
+ }
876
+ return false;
877
+ }
878
+
879
+ /**
880
+ * @param $sql
881
+ * @param array $data
882
+ * @return mysqli_stmt
883
+ * @throws wfWAFStorageEngineMySQLiException
884
+ */
885
+ public function query($sql, $data = array()) {
886
+ if ($this->installing) {
887
+ return false;
888
+ }
889
+
890
+ $stmt = mysqli_prepare($this->dbh, $sql);
891
+ if (!$stmt) {
892
+ throw new wfWAFStorageEngineMySQLiException(
893
+ sprintf('MySQL error[%d]: %s', mysqli_errno($this->dbh), mysqli_error($this->dbh)),
894
+ mysqli_errno($this->dbh)
895
+ );
896
+ }
897
+
898
+ $bindFormats = '';
899
+ $bindData = array();
900
+ $bindCounter = 0;
901
+ foreach ($data as $value) {
902
+ switch (gettype($value)) {
903
+ case 'integer':
904
+ case 'boolean':
905
+ $bindFormats .= 'i';
906
+ ${"bindVar{$bindCounter}"} = (int) $value;
907
+ $bindData[] = &${"bindVar{$bindCounter}"};
908
+ break;
909
+
910
+ case 'string':
911
+ $bindFormats .= 's';
912
+ ${"bindVar{$bindCounter}"} = $value;
913
+ $bindData[] = &${"bindVar{$bindCounter}"};
914
+ break;
915
+
916
+ case 'double':
917
+ case 'float':
918
+ $bindFormats .= 'd';
919
+ ${"bindVar{$bindCounter}"} = $value;
920
+ $bindData[] = &${"bindVar{$bindCounter}"};
921
+ break;
922
+
923
+ default:
924
+ $bindFormats .= 'b';
925
+ ${"bindVar{$bindCounter}"} = $value;
926
+ $bindData[] = &${"bindVar{$bindCounter}"};
927
+ break;
928
+ }
929
+ $bindCounter++;
930
+ }
931
+
932
+ if ($bindData) {
933
+ array_unshift($bindData, $bindFormats);
934
+ call_user_func_array(array($stmt, 'bind_param'), $bindData);
935
+ }
936
+
937
+ $stmt->execute();
938
+ if ($stmt->errno > 0) {
939
+ throw new wfWAFStorageEngineMySQLiException('MySQL error [' . $stmt->errno . ']: ' . $stmt->error, $stmt->errno);
940
+ }
941
+
942
+ return $stmt;
943
+ }
944
+
945
+ /**
946
+ * @param mysqli_stmt $stmt
947
+ * @return array
948
+ */
949
+ public function statementToArray($stmt) {
950
+ if (!$stmt) {
951
+ return array();
952
+ }
953
+
954
+ $result = $stmt->get_result();
955
+
956
+ $return = array();
957
+ while ($row = $result->fetch_array(MYSQLI_BOTH)) {
958
+ $return[] = $row;
959
+ }
960
+ return $return;
961
+ }
962
+
963
+ /**
964
+ * @param string $query
965
+ * @param array $data
966
+ * @param int $x
967
+ * @param int $y
968
+ * @return null|mixed
969
+ */
970
+ public function get_var($query = null, $data = array(), $x = 0, $y = 0) {
971
+ $this->lastStatement = $this->query($query, $data);
972
+ $results = $this->statementToArray($this->lastStatement);
973
+
974
+ if (isset($results[$y][$x])) {
975
+ return $results[$y][$x];
976
+ }
977
+
978
+ return null;
979
+ }
980
+
981
+ /**
982
+ * @param string $query
983
+ * @param array $data
984
+ * @param int $y
985
+ * @return mixed|null
986
+ */
987
+ public function get_row($query = null, $data = array(), $y = 0) {
988
+ $stmt = $this->query($query, $data);
989
+ $results = $this->statementToArray($stmt);
990
+
991
+ if (isset($results[$y])) {
992
+ return $results[$y];
993
+ }
994
+
995
+ return null;
996
+ }
997
+
998
+ /**
999
+ * @param string $query
1000
+ * @param array $data
1001
+ * @return array
1002
+ */
1003
+ public function get_results($query = null, $data = array()) {
1004
+ $stmt = $this->query($query, $data);
1005
+ return $this->statementToArray($stmt);
1006
+ }
1007
+
1008
+ /**
1009
+ * @param mixed $value
1010
+ * @return string
1011
+ */
1012
+ public function escape($value) {
1013
+ return sprintf("'%s'", mysqli_real_escape_string($this->dbh, $value));
1014
+ }
1015
+
1016
+ /**
1017
+ * @param string $table
1018
+ * @param array $data
1019