Wordfence Security – Firewall & Malware Scan - Version 3.0.6

Version Description

  • Improved malware and phishing URL detection.
  • Upgraded to Wordfence API version 1.9
  • Fixed issue that caused large files to slow or crash a scan.
  • Added workaround for PHP's broken filesize() function on 32 bit systems.
  • Added an improved test mode for URL scanner for better unit testing on our end.
  • Suppressed warnings issued when a reverse DNS lookup fails.
  • Added improved debug output to becomeAdmin() function in scans to help diagnose scans not starting.
Download this release

Release Info

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

Code changes from version 3.0.5 to 3.0.6

js/admin.js CHANGED
@@ -503,7 +503,7 @@ window['wordfenceAdmin'] = {
503
var cb = false;
504
var self = this;
505
if(res.ok){
506
- this.loadIssues(function(){ self.colorbox('400px', "Success deleting file", "The file " + res.file + " containing " + res.filesize + " bytes was successfully deleted."); });
507
} else if(res.cerrorMsg){
508
this.loadIssues(function(){ self.colorbox('400px', 'An error occurred', res.cerrorMsg); });
509
}
503
var cb = false;
504
var self = this;
505
if(res.ok){
506
+ this.loadIssues(function(){ self.colorbox('400px', "Success deleting file", "The file " + res.file + " was successfully deleted."); });
507
} else if(res.cerrorMsg){
508
this.loadIssues(function(){ self.colorbox('400px', 'An error occurred', res.cerrorMsg); });
509
}
lib/wfAPI.php CHANGED
@@ -46,7 +46,7 @@ class wfAPI {
46
$this->curlContent = "";
47
$curl = curl_init($url);
48
curl_setopt ($curl, CURLOPT_TIMEOUT, 300);
49
- curl_setopt ($curl, CURLOPT_USERAGENT, "Wordfence.com UA " . WORDFENCE_VERSION);
50
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, TRUE);
51
curl_setopt ($curl, CURLOPT_HEADER, 0);
52
curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, false);
46
$this->curlContent = "";
47
$curl = curl_init($url);
48
curl_setopt ($curl, CURLOPT_TIMEOUT, 300);
49
+ curl_setopt ($curl, CURLOPT_USERAGENT, "Wordfence.com UA " . (defined('WORDFENCE_VERSION') ? WORDFENCE_VERSION : '[Unknown version]') );
50
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, TRUE);
51
curl_setopt ($curl, CURLOPT_HEADER, 0);
52
curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, false);
lib/wfScanEngine.php CHANGED
@@ -177,7 +177,7 @@ class wfScanEngine {
177
$fullFile = rtrim(ABSPATH, '/') . '/' . $file;
178
if($scanOutside){
179
$includeInScan[] = $file;
180
- } else if(in_array($file, $baseWPStuff) || (is_file($fullFile) && is_readable($fullFile) && filesize($fullFile) < 1000000) ){
181
$includeInScan[] = $file;
182
}
183
}
@@ -621,6 +621,33 @@ class wfScanEngine {
621
$this->status(2, 'info', "Completed checking password strength of user '" . $userDat->user_login . "'");
622
return $haveIssue;
623
}
624
private function scan_diskSpace(){
625
$this->statusIDX['diskSpace'] = wordfence::statusStart("Scanning to check available disk space");
626
wfUtils::errorsOff();
177
$fullFile = rtrim(ABSPATH, '/') . '/' . $file;
178
if($scanOutside){
179
$includeInScan[] = $file;
180
+ } else if(in_array($file, $baseWPStuff) || (is_file($fullFile) && is_readable($fullFile) && (! wfUtils::fileTooBig($fullFile)) ) ){
181
$includeInScan[] = $file;
182
}
183
}
621
$this->status(2, 'info', "Completed checking password strength of user '" . $userDat->user_login . "'");
622
return $haveIssue;
623
}
624
+ /*
625
+ private function scan_sitePages(){
626
+ if(is_multisite()){ return; } //Multisite not supported by this function yet
627
+ $this->statusIDX['sitePages'] = wordfence::statusStart("Scanning externally for malware");
628
+ $resp = wp_remote_get(site_url());
629
+ if(is_array($resp) && isset($resp['body']) && strlen($rep['body']) > 0){
630
+ $this->hoover = new wordfenceURLHoover($this->apiKey, $this->wp_version);
631
+ $this->hoover->hoover(1, $rep['body']);
632
+ $hooverResults = $this->hoover->getBaddies();
633
+ if($this->hoover->errorMsg){
634
+ wordfence::statusEndErr();
635
+ throw new Exception($this->hoover->errorMsg);
636
+ }
637
+ $badURLs = array();
638
+ foreach($hooverResults as $idString => $hresults){
639
+ foreach($hresults as $result){
640
+ if(! in_array($result['URL'], $badURLs)){
641
+ $badURLs[] = $result['URL'];
642
+ }
643
+ }
644
+ }
645
+ if(sizeof($badURLs) > 0){
646
+ $this->addIssue('badSitePage', 1, 'badSitePage1', 'badSitePage1', "Your home page contains a malware URL");
647
+ }
648
+ }
649
+ }
650
+ */
651
private function scan_diskSpace(){
652
$this->statusIDX['diskSpace'] = wordfence::statusStart("Scanning to check available disk space");
653
wfUtils::errorsOff();
lib/wfUtils.php CHANGED
@@ -354,7 +354,7 @@ class wfUtils {
354
$host = $db->querySingle("select host from " . $reverseTable . " where IP=%s and unix_timestamp() - lastUpdate < %d", $IPn, WORDFENCE_REVERSE_LOOKUP_CACHE_TIME);
355
if(! $host){
356
$ptr = implode(".", array_reverse(explode(".",$IP))) . ".in-addr.arpa";
357
- $host = dns_get_record($ptr, DNS_PTR);
358
if($host == null){
359
$host = 'NONE';
360
} else {
@@ -380,6 +380,32 @@ class wfUtils {
380
@ini_set('display_errors', self::$lastDisplayErrors);
381
if(class_exists('wfScan')){ wfScan::$errorHandlingOn = true; }
382
}
383
}
384
385
354
$host = $db->querySingle("select host from " . $reverseTable . " where IP=%s and unix_timestamp() - lastUpdate < %d", $IPn, WORDFENCE_REVERSE_LOOKUP_CACHE_TIME);
355
if(! $host){
356
$ptr = implode(".", array_reverse(explode(".",$IP))) . ".in-addr.arpa";
357
+ $host = @dns_get_record($ptr, DNS_PTR);
358
if($host == null){
359
$host = 'NONE';
360
} else {
380
@ini_set('display_errors', self::$lastDisplayErrors);
381
if(class_exists('wfScan')){ wfScan::$errorHandlingOn = true; }
382
}
383
+ public static function fileTooBig($file){
384
+ $fh = @fopen($file, 'r');
385
+ if(! $fh){ return false; }
386
+ $offset = WORDFENCE_MAX_FILE_SIZE_TO_PROCESS + 1;
387
+ $tooBig = false;
388
+ if(fseek($fh, $offset, SEEK_SET) === 0){
389
+ if(strlen(fread($fh, 1)) === 1){
390
+ $tooBig = true;
391
+ }
392
+ } //Otherwise we couldn't seek there so it must be smaller
393
+ fclose($fh);
394
+ return $tooBig;
395
+ }
396
+ public static function fileOver2Gigs($file){
397
+ $fh = @fopen($file, 'r');
398
+ if(! $fh){ return false; }
399
+ $offset = 2147483647;
400
+ $tooBig = false;
401
+ if(fseek($fh, $offset, SEEK_SET) === 0){
402
+ if(strlen(fread($fh, 1)) === 1){
403
+ $tooBig = true;
404
+ }
405
+ } //Otherwise we couldn't seek there so it must be smaller
406
+ fclose($fh);
407
+ return $tooBig;
408
+ }
409
}
410
411
lib/wordfenceClass.php CHANGED
@@ -818,14 +818,12 @@ class wordfence {
818
if(strpos($localFile, ABSPATH) !== 0){
819
return array('errorMsg' => "An invalid file was requested for deletion.");
820
}
821
- $filesize = filesize($localFile);
822
if(@unlink($localFile)){
823
$wfIssues->updateIssue($issueID, 'delete');
824
return array(
825
'ok' => 1,
826
'localFile' => $localFile,
827
- 'file' => $file,
828
- 'filesize' => $filesize
829
);
830
} else {
831
$err = error_get_last();
@@ -1037,7 +1035,7 @@ class wordfence {
1037
$cont = @file_get_contents($localFile);
1038
$isEmpty = false;
1039
if(! $cont){
1040
- if(file_exists($localFile) && filesize($localFile) === 0){
1041
$isEmpty = true;
1042
} else {
1043
$err = error_get_last();
@@ -1047,8 +1045,12 @@ class wordfence {
1047
}
1048
$fileMTime = @filemtime($localFile);
1049
$fileMTime = date('l jS \of F Y h:i:s A', $fileMTime);
1050
- $fileSize = @filesize($localFile);
1051
- $fileSize = number_format($fileSize, 0, '', ',') . ' bytes';
1052
1053
require 'wfViewResult.php';
1054
exit(0);
818
if(strpos($localFile, ABSPATH) !== 0){
819
return array('errorMsg' => "An invalid file was requested for deletion.");
820
}
821
if(@unlink($localFile)){
822
$wfIssues->updateIssue($issueID, 'delete');
823
return array(
824
'ok' => 1,
825
'localFile' => $localFile,
826
+ 'file' => $file
827
);
828
} else {
829
$err = error_get_last();
1035
$cont = @file_get_contents($localFile);
1036
$isEmpty = false;
1037
if(! $cont){
1038
+ if(file_exists($localFile) && filesize($localFile) === 0){ //There's a remote possibility that very large files on 32 bit systems will return 0 here, but it's about 1 in 2 billion
1039
$isEmpty = true;
1040
} else {
1041
$err = error_get_last();
1045
}
1046
$fileMTime = @filemtime($localFile);
1047
$fileMTime = date('l jS \of F Y h:i:s A', $fileMTime);
1048
+ if(wfUtils::fileOver2Gigs($localFile)){
1049
+ $fileSize = "Greater than 2 Gigs";
1050
+ } else {
1051
+ $fileSize = @filesize($localFile); //Checked if over 2 gigs above
1052
+ $fileSize = number_format($fileSize, 0, '', ',') . ' bytes';
1053
+ }
1054
1055
require 'wfViewResult.php';
1056
exit(0);
lib/wordfenceConstants.php CHANGED
@@ -1,5 +1,5 @@
1
<?php
2
- define('WORDFENCE_API_VERSION', 1.8);
3
define('WORDFENCE_API_URL_SEC', 'https://noc1.wordfence.com/');
4
define('WORDFENCE_API_URL_NONSEC', 'http://noc1.wordfence.com/');
5
define('WORDFENCE_MAX_SCAN_TIME', 600);
1
<?php
2
+ define('WORDFENCE_API_VERSION', 1.9);
3
define('WORDFENCE_API_URL_SEC', 'https://noc1.wordfence.com/');
4
define('WORDFENCE_API_URL_NONSEC', 'http://noc1.wordfence.com/');
5
define('WORDFENCE_MAX_SCAN_TIME', 600);
lib/wordfenceHash.php CHANGED
@@ -139,16 +139,15 @@ class wordfenceHash {
139
$this->fileQ = array();
140
}
141
private function processFile($file){
142
- if(@filesize($file) > WORDFENCE_MAX_FILE_SIZE_TO_PROCESS){
143
- wordfence::status(2, 'info', "Skipping file larger than 50 megs: $file");
144
return;
145
}
146
-
147
if(function_exists('memory_get_usage')){
148
wordfence::status(4, 'info', "Scanning: $file (Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
149
- } else {
150
wordfence::status(4, 'info', "Scanning: $file");
151
- }
152
$wfHash = $this->wfHash($file);
153
if($wfHash){
154
$packetFile = substr($file, $this->striplen);
@@ -165,7 +164,7 @@ class wordfenceHash {
165
$this->linesOfPHP += sizeof(file($file));
166
}
167
$this->totalFiles++;
168
- $this->totalData += filesize($file);
169
if(microtime(true) - $this->lastStatusTime > 1){
170
$this->writeHashingStatus();
171
}
139
$this->fileQ = array();
140
}
141
private function processFile($file){
142
+ if(wfUtils::fileTooBig($file)){
143
+ wordfence::status(4, 'info', "Skipping file larger than max size: $file");
144
return;
145
}
146
if(function_exists('memory_get_usage')){
147
wordfence::status(4, 'info', "Scanning: $file (Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
148
+ } else {
149
wordfence::status(4, 'info', "Scanning: $file");
150
+ }
151
$wfHash = $this->wfHash($file);
152
if($wfHash){
153
$packetFile = substr($file, $this->striplen);
164
$this->linesOfPHP += sizeof(file($file));
165
}
166
$this->totalFiles++;
167
+ $this->totalData += filesize($file); //We already checked if file overflows int in the fileTooBig routine above
168
if(microtime(true) - $this->lastStatusTime > 1){
169
$this->writeHashingStatus();
170
}
lib/wordfenceScanner.php CHANGED
@@ -64,7 +64,13 @@ class wordfenceScanner {
64
if(preg_match('/^(?:jpg|jpeg|mp3|avi|m4v|gif|png)#x2F;', $fileExt)){
65
continue;
66
}
67
- $fsize = filesize($this->path . $file);
68
if($fsize > 1000000){
69
$fsize = sprintf('%.2f', ($fsize / 1000000)) . "M";
70
} else {
64
if(preg_match('/^(?:jpg|jpeg|mp3|avi|m4v|gif|png)#x2F;', $fileExt)){
65
continue;
66
}
67
+ if(wfUtils::fileTooBig($this->path . $file)){
68
+ //We should not need this check because files > 2 gigs are not hashed and therefore won't be received back as unknowns from the API server
69
+ //But we do it anyway to be safe.
70
+ wordfence::status(2, 'error', "Encountered file that is too large: $file - Skipping.");
71
+ continue;
72
+ }
73
+ $fsize = filesize($this->path . $file); //Checked if too big above
74
if($fsize > 1000000){
75
$fsize = sprintf('%.2f', ($fsize / 1000000)) . "M";
76
} else {
lib/wordfenceURLHoover.php CHANGED
@@ -8,6 +8,9 @@ class wordfenceURLHoover {
8
private $table = '';
9
private $apiKey = false;
10
private $wordpressVersion = false;
11
private $dRegex = 'aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw|xn--lgbbat1ad8j|xn--fiqs8s|xn--fiqz9s|xn--wgbh1c|xn--j6w193g|xn--h2brj9c|xn--mgbbh1a71e|xn--fpcrj9c3d|xn--gecrj9c|xn--s9brj9c|xn--xkc2dl3a5ee0h|xn--45brj9c|xn--mgba3a4f16a|xn--mgbayh7gpa|xn--mgbc0a9azcg|xn--ygbi2ammx|xn--wgbl6a|xn--p1ai|xn--mgberp4a5d4ar|xn--90a3ac|xn--yfro4i67o|xn--clchc0ea0b2g2a9gcd|xn--3e0b707e|xn--fzc2c9e2c|xn--xkc2al3hye2a|xn--mgbtf8fl|xn--kprw13d|xn--kpry57d|xn--o3cw4h|xn--pgbs0dh|xn--mgbaam7a8h|xn--54b7fta0cc|xn--90ae|xn--node|xn--4dbrk0ce|xn--80ao21a|xn--mgb9awbf|xn--mgbai9azgqp6j|xn--j1amh|xn--mgb2ddes|xn--kgbechtv|xn--hgbk6aj7f53bba|xn--0zwm56d|xn--g6w251d|xn--80akhbyknj4f|xn--11b5bs3a9aj6g|xn--jxalpdlp|xn--9t4b11yi5a|xn--deba0ad|xn--zckzah|xn--hlcj6aya9esc7a';
12
private $api = false;
13
private $db = false;
@@ -20,37 +23,61 @@ class wordfenceURLHoover {
20
$this->api = new wfAPI($this->apiKey, $this->wordpressVersion);
21
$this->db = new wfDB();
22
}
23
- public function __construct($apiKey, $wordpressVersion){
24
$this->hostsToAdd = new wfArray(array('owner', 'host', 'path', 'hostKey'));
25
$this->apiKey = $apiKey;
26
$this->wordpressVersion = $wordpressVersion;
27
$this->api = new wfAPI($apiKey, $wordpressVersion);
28
- $this->db = new wfDB();
29
global $wpdb;
30
- $this->table = $wpdb->base_prefix . 'wfHoover';
31
$this->db->query("truncate table $this->table");
32
}
33
public function hoover($id, $data){
34
if(strpos($data, '.') === false){
35
return;
36
}
37
- if(! preg_match('/[a-zA-Z0-9\-]+\.(?:' . $this->dRegex . ')/i', $data)){
38
- return;
39
- }
40
try {
41
@preg_replace("/(?<=^|[^a-zA-Z0-9\-])((?:[a-zA-Z0-9\-]+\.)+)(" . $this->dRegex . ")((?:$|[^a-zA-Z0-9\-\.\'\"])[^\r\n\s\t\"\'\$\{\}<>]*)/ie", "\$this->" . "addHost(\$id, '$1$2', '$3')", $data);
42
} catch(Exception $e){ error_log("Regex error 1: $e"); }
43
- preg_replace("/(?<=[^\d]|^)(\d{8,10}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})([^\d\'\"][^\r\n\s\t\"\'\$\{\}<>]*)/e", "\$this->" . "addIP(\$id, \"$1\",\"$2\")", $data);
44
$this->writeHosts();
45
}
46
private function dbg($msg){ if($this->debug){ error_log("DEBUG: $msg\n"); } }
47
public function addHost($id, $host, $path){
48
- $path = preg_replace_callback('/([^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,;\=]+)/', 'wordfenceURLHoover::urlenc', $path);
49
$host = strtolower($host);
50
- $this->intAddHost($id, $host, $path);
51
}
52
public function addIP($id, $ipdata, $path){
53
- $path = preg_replace_callback('/([^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,;\=]+)/', 'wordfenceURLHoover::urlenc', $path);
54
if(strstr($ipdata, '.') === false && $ipdata >= 16777216 && $ipdata <= 4026531840){
55
$ipdata = long2ip($ipdata);
56
}
@@ -67,51 +94,51 @@ class wordfenceURLHoover {
67
}
68
}
69
if($isValid && $ipdata){
70
- $this->intAddHost($id, $ipdata, $path);
71
}
72
}
73
public static function urlenc($m){
74
return urlencode($m[1]);
75
}
76
- private function intAddHost($id, $host, $path){
77
- if(strpos($path, '/') !== 0){
78
- $path = '/';
79
- }
80
- $this->hostsToAdd->push(array('owner' => $id, 'host' => $host, 'path' => $path, 'hostKey' => $this->makeHostKey($host)));
81
- if($this->hostsToAdd->size() > 1000){ $this->writeHosts(); }
82
- return true;
83
- }
84
private function writeHosts(){
85
if($this->hostsToAdd->size() < 1){ return; }
86
- $sql = "insert into " . $this->table . " (owner, host, path, hostKey) values ";
87
- while($elem = $this->hostsToAdd->shift()){
88
- $sql .= sprintf("('%s', '%s', '%s', '%s'),",
89
- mysql_real_escape_string($elem['owner']),
90
- mysql_real_escape_string($elem['host']),
91
- mysql_real_escape_string($elem['path']),
92
- mysql_real_escape_string($elem['hostKey'])
93
- );
94
- }
95
- $sql = rtrim($sql, ',');
96
- $this->db->query($sql);
97
- }
98
- private function makeHostKey($host){
99
- $hostParts = explode('.', $host);
100
- $hostKey = '';
101
- if(sizeof($hostParts) == 2){
102
- $hostKey = substr(hash('sha256', $hostParts[0] . '.' . $hostParts[1] . '/', true), 0, 4);
103
- } else if(sizeof($hostParts) > 2){
104
- $hostKey = substr(hash('sha256', $hostParts[sizeof($hostParts) - 3] . '.' . $hostParts[sizeof($hostParts) - 2] . '.' . $hostParts[sizeof($hostParts) - 1] . '/', true), 0, 4);
105
}
106
- return $hostKey;
107
}
108
public function getBaddies(){
109
$allHostKeys = array();
110
$stime = microtime(true);
111
$allHostKeys = array();
112
- $q1 = $this->db->query("select distinct hostKey as hostKey from $this->table");
113
- while($hRec = mysql_fetch_assoc($q1)){
114
- array_push($allHostKeys, $hRec['hostKey']);
115
}
116
//Now call API and check if any hostkeys are bad.
117
//This is a shortcut, because if no hostkeys are bad it saves us having to check URLs
@@ -119,6 +146,11 @@ class wordfenceURLHoover {
119
//Hostkeys are 4 byte sha256 prefixes
120
//Returned value is 2 byte shorts which are array indexes for bad keys that were passed in the original list
121
$this->dbg("Checking " . sizeof($allHostKeys) . " hostkeys");
122
$resp = $this->api->binCall('check_host_keys', implode('', $allHostKeys));
123
$this->dbg("Done hostkey check");
124
@@ -135,7 +167,9 @@ class wordfenceURLHoover {
135
$idx = $idxArr[1];
136
if(isset($allHostKeys[$idx]) ){
137
array_push($badHostKeys, $allHostKeys[$idx]);
138
} else {
139
$this->errorMsg = "Bad allHostKeys index: $idx";
140
return false;
141
}
@@ -150,20 +184,39 @@ class wordfenceURLHoover {
150
//need to figure out which id's have bad hostkeys
151
//need to feed in all URL's from those id's where the hostkey matches a URL
152
foreach($badHostKeys as $badHostKey){
153
- $q1 = $this->db->query("select owner, host, path from $this->table where hostKey='%s'", $badHostKey);
154
- while($rec = mysql_fetch_assoc($q1)){
155
- $url = 'http://' . $rec['host'] . $rec['path'];
156
- if(! isset($urlsToCheck[$rec['owner']])){
157
- $urlsToCheck[$rec['owner']] = array();
158
}
159
- if(! in_array($url, $urlsToCheck[$rec['owner']])){
160
- $urlsToCheck[$rec['owner']][] = $url;
161
}
162
}
163
}
164
165
if(sizeof($urlsToCheck) > 0){
166
$this->dbg("Checking " . sizeof($urlsToCheck) . " URLs");
167
$badURLs = $this->api->call('check_bad_urls', array(), array( 'toCheck' => json_encode($urlsToCheck)) );
168
$this->dbg("Done URL check");
169
if(is_array($badURLs) && sizeof($badURLs) > 0){
8
private $table = '';
9
private $apiKey = false;
10
private $wordpressVersion = false;
11
+ private $useDB = true;
12
+ private $hostKeys = array();
13
+ private $hostList = array();
14
private $dRegex = 'aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw|xn--lgbbat1ad8j|xn--fiqs8s|xn--fiqz9s|xn--wgbh1c|xn--j6w193g|xn--h2brj9c|xn--mgbbh1a71e|xn--fpcrj9c3d|xn--gecrj9c|xn--s9brj9c|xn--xkc2dl3a5ee0h|xn--45brj9c|xn--mgba3a4f16a|xn--mgbayh7gpa|xn--mgbc0a9azcg|xn--ygbi2ammx|xn--wgbl6a|xn--p1ai|xn--mgberp4a5d4ar|xn--90a3ac|xn--yfro4i67o|xn--clchc0ea0b2g2a9gcd|xn--3e0b707e|xn--fzc2c9e2c|xn--xkc2al3hye2a|xn--mgbtf8fl|xn--kprw13d|xn--kpry57d|xn--o3cw4h|xn--pgbs0dh|xn--mgbaam7a8h|xn--54b7fta0cc|xn--90ae|xn--node|xn--4dbrk0ce|xn--80ao21a|xn--mgb9awbf|xn--mgbai9azgqp6j|xn--j1amh|xn--mgb2ddes|xn--kgbechtv|xn--hgbk6aj7f53bba|xn--0zwm56d|xn--g6w251d|xn--80akhbyknj4f|xn--11b5bs3a9aj6g|xn--jxalpdlp|xn--9t4b11yi5a|xn--deba0ad|xn--zckzah|xn--hlcj6aya9esc7a';
15
private $api = false;
16
private $db = false;
23
$this->api = new wfAPI($this->apiKey, $this->wordpressVersion);
24
$this->db = new wfDB();
25
}
26
+ public function __construct($apiKey, $wordpressVersion, $db = false){
27
$this->hostsToAdd = new wfArray(array('owner', 'host', 'path', 'hostKey'));
28
$this->apiKey = $apiKey;
29
$this->wordpressVersion = $wordpressVersion;
30
$this->api = new wfAPI($apiKey, $wordpressVersion);
31
+ if($db){
32
+ $this->db = $db;
33
+ } else {
34
+ $this->db = new wfDB();
35
+ }
36
global $wpdb;
37
+ if(isset($wpdb)){
38
+ $this->table = $wpdb->base_prefix . 'wfHoover';
39
+ } else {
40
+ $this->table = 'wp_wfHoover';
41
+ }
42
$this->db->query("truncate table $this->table");
43
}
44
public function hoover($id, $data){
45
if(strpos($data, '.') === false){
46
return;
47
}
48
try {
49
@preg_replace("/(?<=^|[^a-zA-Z0-9\-])((?:[a-zA-Z0-9\-]+\.)+)(" . $this->dRegex . ")((?:$|[^a-zA-Z0-9\-\.\'\"])[^\r\n\s\t\"\'\$\{\}<>]*)/ie", "\$this->" . "addHost(\$id, '$1$2', '$3')", $data);
50
} catch(Exception $e){ error_log("Regex error 1: $e"); }
51
+ @preg_replace("/(?<=[^\d]|^)(\d{8,10}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})([^\d\'\"][^\r\n\s\t\"\'\$\{\}<>]*)/e", "\$this->" . "addIP(\$id, \"$1\",\"$2\")", $data);
52
$this->writeHosts();
53
}
54
private function dbg($msg){ if($this->debug){ error_log("DEBUG: $msg\n"); } }
55
public function addHost($id, $host, $path){
56
+ if(strpos($path, '/') !== 0){
57
+ $path = '/';
58
+ } else {
59
+ $path = preg_replace_callback('/([^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,;\=]+)/', 'wordfenceURLHoover::urlenc', $path);
60
+ }
61
$host = strtolower($host);
62
+ $hostParts = explode('.', $host);
63
+ if(sizeof($hostParts) == 2){
64
+ $hostKey = substr(hash('sha256', $hostParts[0] . '.' . $hostParts[1] . '/', true), 0, 4);
65
+ $this->hostsToAdd->push(array('owner' => $id, 'host' => $host, 'path' => $path, 'hostKey' => $hostKey));
66
+ } else if(sizeof($hostParts) > 2){
67
+ $hostKeyThreeParts = substr(hash('sha256',$hostParts[sizeof($hostParts) - 3] . '.' . $hostParts[sizeof($hostParts) - 2] . '.' . $hostParts[sizeof($hostParts) - 1] . '/', true), 0, 4);
68
+ $hostKeyTwoParts = substr(hash('sha256', $hostParts[sizeof($hostParts) - 2] . '.' . $hostParts[sizeof($hostParts) - 1] . '/', true), 0, 4);
69
+ $this->hostsToAdd->push(array('owner' => $id, 'host' => $host, 'path' => $path, 'hostKey' => $hostKeyThreeParts));
70
+ $this->hostsToAdd->push(array('owner' => $id, 'host' => $host, 'path' => $path, 'hostKey' => $hostKeyTwoParts));
71
+ }
72
+ if($this->hostsToAdd->size() > 1000){ $this->writeHosts(); }
73
}
74
public function addIP($id, $ipdata, $path){
75
+ $this->dbg("Add IP called with $ipdata $path");
76
+ if(strpos($path, '/') !== 0){
77
+ $path = '/';
78
+ } else {
79
+ $path = preg_replace_callback('/([^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,;\=]+)/', 'wordfenceURLHoover::urlenc', $path);
80
+ }
81
if(strstr($ipdata, '.') === false && $ipdata >= 16777216 && $ipdata <= 4026531840){
82
$ipdata = long2ip($ipdata);
83
}
94
}
95
}
96
if($isValid && $ipdata){
97
+ $hostKey = substr(hash('sha256', $ipdata . '/', true), 0, 4);
98
+ $this->hostsToAdd->push(array('owner' => $id, 'host' => $ipdata, 'path' => $path, 'hostKey' => $hostKey));
99
+ if($this->hostsToAdd->size() > 1000){ $this->writeHosts(); }
100
}
101
}
102
public static function urlenc($m){
103
return urlencode($m[1]);
104
}
105
private function writeHosts(){
106
if($this->hostsToAdd->size() < 1){ return; }
107
+ if($this->useDB){
108
+ $sql = "insert into " . $this->table . " (owner, host, path, hostKey) values ";
109
+ while($elem = $this->hostsToAdd->shift()){
110
+ $sql .= sprintf("('%s', '%s', '%s', '%s'),",
111
+ mysql_real_escape_string($elem['owner']),
112
+ mysql_real_escape_string($elem['host']),
113
+ mysql_real_escape_string($elem['path']),
114
+ mysql_real_escape_string($elem['hostKey'])
115
+ );
116
+ }
117
+ $sql = rtrim($sql, ',');
118
+ $this->db->query($sql);
119
+ } else {
120
+ while($elem = $this->hostsToAdd->shift()){
121
+ $this->hostKeys[] = $elem['hostKey'];
122
+ $this->hostList[] = array(
123
+ 'owner' => $elem['owner'],
124
+ 'host' => $elem['host'],
125
+ 'path' => $elem['path'],
126
+ 'hostKey' => $elem['hostKey']
127
+ );
128
+ }
129
}
130
}
131
public function getBaddies(){
132
$allHostKeys = array();
133
$stime = microtime(true);
134
$allHostKeys = array();
135
+ if($this->useDB){
136
+ $q1 = $this->db->query("select distinct hostKey as hostKey from $this->table");
137
+ while($hRec = mysql_fetch_assoc($q1)){
138
+ array_push($allHostKeys, $hRec['hostKey']);
139
+ }
140
+ } else {
141
+ $allHostKeys = $this->hostKeys;
142
}
143
//Now call API and check if any hostkeys are bad.
144
//This is a shortcut, because if no hostkeys are bad it saves us having to check URLs
146
//Hostkeys are 4 byte sha256 prefixes
147
//Returned value is 2 byte shorts which are array indexes for bad keys that were passed in the original list
148
$this->dbg("Checking " . sizeof($allHostKeys) . " hostkeys");
149
+ if($this->debug){
150
+ foreach($allHostKeys as $key){
151
+ $this->dbg("Checking hostkey: " . bin2hex($key));
152
+ }
153
+ }
154
$resp = $this->api->binCall('check_host_keys', implode('', $allHostKeys));
155
$this->dbg("Done hostkey check");
156
167
$idx = $idxArr[1];
168
if(isset($allHostKeys[$idx]) ){
169
array_push($badHostKeys, $allHostKeys[$idx]);
170
+ $this->dbg("Got bad hostkey for record: " . var_export($allHostKeys[$idx], true));
171
} else {
172
+ $this->dbg("Bad allHostKeys index: $idx");
173
$this->errorMsg = "Bad allHostKeys index: $idx";
174
return false;
175
}
184
//need to figure out which id's have bad hostkeys
185
//need to feed in all URL's from those id's where the hostkey matches a URL
186
foreach($badHostKeys as $badHostKey){
187
+ if($this->useDB){
188
+ $q1 = $this->db->query("select owner, host, path from $this->table where hostKey='%s'", $badHostKey);
189
+ while($rec = mysql_fetch_assoc($q1)){
190
+ $url = 'http://' . $rec['host'] . $rec['path'];
191
+ if(! isset($urlsToCheck[$rec['owner']])){
192
+ $urlsToCheck[$rec['owner']] = array();
193
+ }
194
+ if(! in_array($url, $urlsToCheck[$rec['owner']])){
195
+ $urlsToCheck[$rec['owner']][] = $url;
196
+ }
197
}
198
+ } else {
199
+ foreach($this->hostList as $rec){
200
+ if($rec['hostKey'] == $badHostKey){
201
+ $url = 'http://' . $rec['host'] . $rec['path'];
202
+ if(! isset($urlsToCheck[$rec['owner']])){
203
+ $urlsToCheck[$rec['owner']] = array();
204
+ }
205
+ if(! in_array($url, $urlsToCheck[$rec['owner']])){
206
+ $urlsToCheck[$rec['owner']][] = $url;
207
+ }
208
+ }
209
}
210
}
211
}
212
213
if(sizeof($urlsToCheck) > 0){
214
$this->dbg("Checking " . sizeof($urlsToCheck) . " URLs");
215
+ if($this->debug){
216
+ foreach($urlsToCheck as $url){
217
+ $this->dbg("Checking URL: " . var_export($url, true));
218
+ }
219
+ }
220
$badURLs = $this->api->call('check_bad_urls', array(), array( 'toCheck' => json_encode($urlsToCheck)) );
221
$this->dbg("Done URL check");
222
if(is_array($badURLs) && sizeof($badURLs) > 0){
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: mmaunder
3
Tags: wordpress, security, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure
4
Requires at least: 3.3.1
5
Tested up to: 3.4.1
6
- Stable tag: 3.0.5
7
8
Wordfence Security is a free enterprise class security plugin that includes a firewall, virus scanning, real-time traffic with geolocation and more.
9
@@ -152,6 +152,15 @@ or a theme, because often these have been updated to fix a security hole.
152
5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
154
== Changelog ==
155
= 3.0.5 =
156
* Fixed "The key used to start a scan has expired." error and added data to help diagnose future issues like this.
157
* Removed HTTPHeaders from wfHits table which was using a lot of disk space and not used much.
@@ -431,6 +440,9 @@ or a theme, because often these have been updated to fix a security hole.
431
* Initial public release of Wordfence.
432
433
== Upgrade Notice ==
434
= 3.0.3 =
435
Upgrade immediately. This release fixes an issue that caused Wordfence to show all your core files
436
missing under certain conditions. It was usually caused by high load on our scanning server and the
3
Tags: wordpress, security, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure
4
Requires at least: 3.3.1
5
Tested up to: 3.4.1
6
+ Stable tag: 3.0.6
7
8
Wordfence Security is a free enterprise class security plugin that includes a firewall, virus scanning, real-time traffic with geolocation and more.
9
152
5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
154
== Changelog ==
155
+ = 3.0.6 =
156
+ * Improved malware and phishing URL detection.
157
+ * Upgraded to Wordfence API version 1.9
158
+ * Fixed issue that caused large files to slow or crash a scan.
159
+ * Added workaround for PHP's broken filesize() function on 32 bit systems.
160
+ * Added an improved test mode for URL scanner for better unit testing on our end.
161
+ * Suppressed warnings issued when a reverse DNS lookup fails.
162
+ * Added improved debug output to becomeAdmin() function in scans to help diagnose scans not starting.
163
+
164
= 3.0.5 =
165
* Fixed "The key used to start a scan has expired." error and added data to help diagnose future issues like this.
166
* Removed HTTPHeaders from wfHits table which was using a lot of disk space and not used much.
440
* Initial public release of Wordfence.
441
442
== Upgrade Notice ==
443
+ = 3.0.6 =
444
+ Upgrade immediately. Improves malware URL detection by 20% or more.
445
+
446
= 3.0.3 =
447
Upgrade immediately. This release fixes an issue that caused Wordfence to show all your core files
448
missing under certain conditions. It was usually caused by high load on our scanning server and the
wfscan.php CHANGED
@@ -73,6 +73,7 @@ class wfScan {
73
ini_set('max_execution_time', 1800); //30 mins
74
wordfence::status(4, 'info', "Becoming admin for scan");
75
self::becomeAdmin();
76
77
$isFork = ($_GET['isFork'] == '1' ? true : false);
78
@@ -153,10 +154,14 @@ class wfScan {
153
exit();
154
}
155
public static function becomeAdmin(){
156
global $wpdb;
157
$ws = $wpdb->get_results("SELECT ID, user_login FROM $wpdb->users");
158
$users = array();
159
foreach($ws as $user){
160
$userDat = get_userdata($user->ID);
161
array_push($users, array(
162
'id' => $user->ID,
@@ -164,8 +169,11 @@ class wfScan {
164
'level' => $userDat->user_level
165
));
166
}
167
usort($users, 'wfScan::usort');
168
wp_set_current_user($users[0]['id'], $users[0]['user_login']);
169
}
170
public static function usort($b, $a){
171
if($a['level'] == $b['level']){ return 0; }
73
ini_set('max_execution_time', 1800); //30 mins
74
wordfence::status(4, 'info', "Becoming admin for scan");
75
self::becomeAdmin();
76
+ wordfence::status(4, 'info', "Done become admin");
77
78
$isFork = ($_GET['isFork'] == '1' ? true : false);
79
154
exit();
155
}
156
public static function becomeAdmin(){
157
+ wordfence::status('4', 'info', "Starting become admin");
158
global $wpdb;
159
+ wordfence::status('4', 'info', "About to query");
160
$ws = $wpdb->get_results("SELECT ID, user_login FROM $wpdb->users");
161
+ wordfence::status('4', 'info', "Done query");
162
$users = array();
163
foreach($ws as $user){
164
+ wordfence::status('4', 'info', "Processing user");
165
$userDat = get_userdata($user->ID);
166
array_push($users, array(
167
'id' => $user->ID,
169
'level' => $userDat->user_level
170
));
171
}
172
+ wordfence::status('4', 'info', "Done users and about to sort");
173
usort($users, 'wfScan::usort');
174
+ wordfence::status('4', 'info', "Done sort and setting user");
175
wp_set_current_user($users[0]['id'], $users[0]['user_login']);
176
+ wordfence::status('4', 'info', "Done setting user");
177
}
178
public static function usort($b, $a){
179
if($a['level'] == $b['level']){ return 0; }
wordfence.php CHANGED
@@ -4,10 +4,10 @@ Plugin Name: Wordfence Security
4
Plugin URI: http://wordfence.com/
5
Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
Author: Mark Maunder
7
- Version: 3.0.5
8
Author URI: http://wordfence.com/
9
*/
10
- define('WORDFENCE_VERSION', '3.0.5');
11
if(! defined('WORDFENCE_VERSIONONLY_MODE')){
12
if((int) @ini_get('memory_limit') < 64){
13
@ini_set('memory_limit', '64M'); //Some hosts have ini set at as little as 32 megs. 64 is the min sane amount of memory.
4
Plugin URI: http://wordfence.com/
5
Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
Author: Mark Maunder
7
+ Version: 3.0.6
8
Author URI: http://wordfence.com/
9
*/
10
+ define('WORDFENCE_VERSION', '3.0.6');
11
if(! defined('WORDFENCE_VERSIONONLY_MODE')){
12
if((int) @ini_get('memory_limit') < 64){
13
@ini_set('memory_limit', '64M'); //Some hosts have ini set at as little as 32 megs. 64 is the min sane amount of memory.