PluginProbe ʕ •ᴥ•ʔ
Wordfence Security – Firewall, Malware Scan, and Login Security / 5.2.7
Wordfence Security – Firewall, Malware Scan, and Login Security v5.2.7
8.2.2 8.2.1 8.2.0 3.7.1 3.7.2 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.8.8 3.8.9 3.9.1 4.0.1 4.0.2 4.0.3 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.0.9 5.1.1 5.1.2 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.2.1 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.1 5.3.10 5.3.11 5.3.12 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.3.8 5.3.9 6.0.1 6.0.10 6.0.11 6.0.12 6.0.14 6.0.15 6.0.16 6.0.17 6.0.18 6.0.19 6.0.2 6.0.20 6.0.21 6.0.22 6.0.23 6.0.24 6.0.25 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.0.8 6.0.9 6.1.1 6.1.10 6.1.11 6.1.12 6.1.14 6.1.15 6.1.16 6.1.17 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.1.9 6.2.0 6.2.1 6.2.10 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.2.8 6.2.9 6.3.0 6.3.1 6.3.10 6.3.11 6.3.12 6.3.14 6.3.15 6.3.16 6.3.17 6.3.18 6.3.19 6.3.2 6.3.20 6.3.21 6.3.22 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.3.8 6.3.9 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1.0 7.1.1 7.1.10 7.1.11 7.1.12 7.1.14 7.1.15 7.1.16 7.1.17 7.1.18 7.1.19 7.1.2 7.1.20 7.1.3 7.1.4 7.1.5 7.1.6 7.1.7 7.1.8 7.1.9 7.10.0 7.10.1 7.10.2 7.10.3 7.10.4 7.10.5 7.10.6 7.10.7 7.11.0 7.11.1 7.11.2 7.11.3 7.11.4 7.11.5 7.11.6 7.11.7 7.2.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3.1 7.3.2 7.3.3 7.3.4 7.3.5 7.3.6 7.4.0 7.4.1 7.4.10 7.4.11 7.4.12 7.4.14 7.4.2 7.4.3 trunk 7.4.4 1.1 7.4.5 1.2 7.4.6 1.3 7.4.7 1.3.1 7.4.8 1.3.2 7.4.9 1.3.3 7.5.0 1.4.2 7.5.1 1.4.3 7.5.10 1.4.4 7.5.11 1.4.5 7.5.2 1.4.6 7.5.3 1.4.7 7.5.4 1.4.8 7.5.5 1.5.1 7.5.6 1.5.2 7.5.7 1.5.3 7.5.8 1.5.4 7.5.9 1.5.5 7.6.0 1.5.6 7.6.1 2.0.1 7.6.2 2.0.2 7.7.0 2.0.3 7.7.1 2.0.5 7.8.0 2.0.6 7.8.1 2.0.7 7.8.2 2.1.0 7.9.0 2.1.1 7.9.1 2.1.2 7.9.2 2.1.3 7.9.3 2.1.4 8.0.0 2.1.5 8.0.1 3.0.2 8.0.2 3.0.3 8.0.3 3.0.4 8.0.4 3.0.5 8.0.5 3.0.6 8.1.0 3.0.7 8.1.1 3.0.8 8.1.2 3.0.9 8.1.3 3.1.0 8.1.4 3.1.1 v1.4.1 3.1.2 3.1.4 3.1.6 3.2.1 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.1 3.4.4 3.4.5 3.5.1 3.5.2 3.6.1 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9
wordfence / lib / wordfenceHash.php
wordfence / lib Last commit date
Diff 14 years ago whois 12 years ago .htaccess 14 years ago Diff.php 14 years ago GeoIP.dat 11 years ago IPTraf.php 11 years ago conntest.php 11 years ago dashboard.php 11 years ago diffResult.php 11 years ago email_genericAlert.php 11 years ago email_newIssues.php 11 years ago email_unlockRequest.php 11 years ago menuHeader.php 11 years ago menu_activity.php 11 years ago menu_blockedIPs.php 11 years ago menu_countryBlocking.php 11 years ago menu_options.php 11 years ago menu_rangeBlocking.php 11 years ago menu_scan.php 11 years ago menu_scanSchedule.php 11 years ago menu_sitePerf.php 11 years ago menu_sitePerfStats.php 11 years ago menu_twoFactor.php 11 years ago menu_whois.php 11 years ago pageTitle.php 13 years ago schedWeekEntry.php 12 years ago sysinfo.php 11 years ago unknownFiles.php 11 years ago viewFullActivityLog.php 11 years ago wf503.php 12 years ago wfAPI.php 11 years ago wfAction.php 14 years ago wfArray.php 13 years ago wfBrowscap.php 11 years ago wfBrowscapCache.php 11 years ago wfBulkCountries.php 13 years ago wfCache.php 11 years ago wfConfig.php 11 years ago wfCountryMap.php 13 years ago wfCrawl.php 11 years ago wfDB.php 11 years ago wfDict.php 14 years ago wfGeoIP.php 13 years ago wfIssues.php 11 years ago wfLockedOut.php 13 years ago wfLog.php 11 years ago wfRate.php 14 years ago wfScan.php 11 years ago wfScanEngine.php 11 years ago wfSchema.php 11 years ago wfUnlockMsg.php 11 years ago wfUtils.php 11 years ago wfViewResult.php 11 years ago wordfenceClass.php 11 years ago wordfenceConstants.php 11 years ago wordfenceHash.php 11 years ago wordfenceScanner.php 11 years ago wordfenceURLHoover.php 11 years ago
wordfenceHash.php
371 lines
1 <?php
2 require_once('wordfenceClass.php');
3 class wordfenceHash {
4 private $engine = false;
5 private $db = false;
6 private $startTime = false;
7
8 //Begin serialized vars
9 public $striplen = 0;
10 public $totalFiles = 0;
11 public $totalDirs = 0;
12 public $totalData = 0; //To do a sanity check, don't use 'du' because it gets sparse files wrong and reports blocks used on disk. Use : find . -type f -ls | awk '{total += $7} END {print total}'
13 public $linesOfPHP = 0;
14 public $linesOfJCH = 0; //lines of HTML, CSS and javascript
15 public $stoppedOnFile = false;
16 private $coreEnabled = false;
17 private $pluginsEnabled = false;
18 private $themesEnabled = false;
19 private $malwareEnabled = false;
20 private $knownFiles = false;
21 private $malwareData = "";
22 private $haveIssues = array();
23 private $status = array();
24 private $possibleMalware = array();
25 private $path = false;
26 private $only = false;
27 private $totalForks = 0;
28
29 public function __construct($striplen, $path, $only, $themes, $plugins, $engine){
30 $this->striplen = $striplen;
31 $this->path = $path;
32 $this->only = $only;
33
34 $this->startTime = microtime(true);
35
36 if(wfConfig::get('scansEnabled_core')){
37 $this->coreEnabled = true;
38 }
39 if(wfConfig::get('scansEnabled_plugins')){
40 $this->pluginsEnabled = true;
41 }
42 if(wfConfig::get('scansEnabled_themes')){
43 $this->themesEnabled = true;
44 }
45 if(wfConfig::get('scansEnabled_malware')){
46 $this->malwareEnabled = true;
47 }
48 $this->db = new wfDB();
49
50 //Doing a delete for now. Later we can optimize this to only scan modified files.
51 //$this->db->queryWrite("update " . $this->db->prefix() . "wfFileMods set oldMD5 = newMD5");
52 $this->db->queryWrite("delete from " . $this->db->prefix() . "wfFileMods");
53 $fetchCoreHashesStatus = wordfence::statusStart("Fetching core, theme and plugin file signatures from Wordfence");
54 $dataArr = $engine->api->binCall('get_known_files', json_encode(array(
55 'plugins' => $plugins,
56 'themes' => $themes
57 )) );
58 if($dataArr['code'] != 200){
59 wordfence::statusEndErr();
60 throw new Exception("Got error response from Wordfence servers: " . $dataArr['code']);
61 }
62 $this->knownFiles = @json_decode($dataArr['data'], true);
63 if(! is_array($this->knownFiles)){
64 wordfence::statusEndErr();
65 throw new Exception("Invalid response from Wordfence servers.");
66 }
67 wordfence::statusEnd($fetchCoreHashesStatus, false, true);
68 if($this->malwareEnabled){
69 $malwarePrefixStatus = wordfence::statusStart("Fetching list of known malware files from Wordfence");
70 $malwareData = $engine->api->getStaticURL('/malwarePrefixes.bin');
71 if(! $malwareData){
72 wordfence::statusEndErr();
73 throw new Exception("Could not fetch malware signatures from Wordfence servers.");
74 }
75 if(strlen($malwareData) % 4 != 0){
76 wordfence::statusEndErr();
77 throw new Exception("Malware data received from Wordfence servers was not valid.");
78 }
79 $this->malwareData = array();
80 for($i = 0; $i < strlen($malwareData); $i += 4){
81 $this->malwareData[substr($malwareData, $i, 4)] = '1';
82 }
83 wordfence::statusEnd($malwarePrefixStatus, false, true);
84 }
85
86 if($this->path[strlen($this->path) - 1] != '/'){
87 $this->path .= '/';
88 }
89 if(! is_readable($path)){
90 throw new Exception("Could not read directory " . $this->path . " to do scan.");
91 exit();
92 }
93 $this->haveIssues = array(
94 'core' => false,
95 'themes' => false,
96 'plugins' => false,
97 'malware' => false
98 );
99 if($this->coreEnabled){ $this->status['core'] = wordfence::statusStart("Comparing core WordPress files against originals in repository"); } else { wordfence::statusDisabled("Skipping core scan"); }
100 if($this->themesEnabled){ $this->status['themes'] = wordfence::statusStart("Comparing open source themes against WordPress.org originals"); } else { wordfence::statusDisabled("Skipping theme scan"); }
101 if($this->pluginsEnabled){ $this->status['plugins'] = wordfence::statusStart("Comparing plugins against WordPress.org originals"); } else { wordfence::statusDisabled("Skipping plugin scan"); }
102 if($this->malwareEnabled){ $this->status['malware'] = wordfence::statusStart("Scanning for known malware files"); } else { wordfence::statusDisabled("Skipping malware scan"); }
103 }
104 public function __sleep(){
105 return array('striplen', 'totalFiles', 'totalDirs', 'totalData', 'linesOfPHP', 'linesOfJCH', 'stoppedOnFile', 'coreEnabled', 'pluginsEnabled', 'themesEnabled', 'malwareEnabled', 'knownFiles', 'malwareData', 'haveIssues', 'status', 'possibleMalware', 'path', 'only', 'totalForks');
106 }
107 public function __wakeup(){
108 $this->db = new wfDB();
109 $this->startTime = microtime(true);
110 $this->totalForks++;
111 }
112 public function run($engine){ //base path and 'only' is a list of files and dirs in the bast that are the only ones that should be processed. Everything else in base is ignored. If only is empty then everything is processed.
113 if($this->totalForks > 1000){
114 throw new Exception("Wordfence file scanner detected a possible infinite loop. Exiting on file: " . $this->stoppedOnFile);
115 }
116 $this->engine = $engine;
117 $files = scandir($this->path);
118 foreach($files as $file){
119 if($file == '.' || $file == '..'){ continue; }
120 if(sizeof($this->only) > 0 && (! in_array($file, $this->only))){
121 continue;
122 }
123 $file = $this->path . $file;
124 wordfence::status(4, 'info', "Hashing item in base dir: $file");
125 $this->_dirHash($file);
126 }
127 wordfence::status(2, 'info', "Analyzed " . $this->totalFiles . " files containing " . wfUtils::formatBytes($this->totalData) . " of data.");
128 if($this->coreEnabled){ wordfence::statusEnd($this->status['core'], $this->haveIssues['core']); }
129 if($this->themesEnabled){ wordfence::statusEnd($this->status['themes'], $this->haveIssues['themes']); }
130 if($this->pluginsEnabled){ wordfence::statusEnd($this->status['plugins'], $this->haveIssues['plugins']); }
131 if(sizeof($this->possibleMalware) > 0){
132 $malwareResp = $engine->api->binCall('check_possible_malware', json_encode($this->possibleMalware));
133 if($malwareResp['code'] != 200){
134 wordfence::statusEndErr();
135 throw new Exception("Invalid response from Wordfence API during check_possible_malware");
136 }
137 $malwareList = json_decode($malwareResp['data'], true);
138 if(is_array($malwareList) && sizeof($malwareList) > 0){
139 for($i = 0; $i < sizeof($malwareList); $i++){
140 $file = $malwareList[$i][0];
141 $md5 = $malwareList[$i][1];
142 $name = $malwareList[$i][2];
143 $this->haveIssues['malware'] = true;
144 $this->engine->addIssue(
145 'file',
146 1,
147 $this->path . $file,
148 $md5,
149 'This file is suspected malware: ' . $file,
150 "This file's signature matches a known malware file. The title of the malware is '" . $name . "'. Immediately inspect this file using the 'View' option below and consider deleting it from your server.",
151 array(
152 'file' => $file,
153 'cType' => 'unknown',
154 'canDiff' => false,
155 'canFix' => false,
156 'canDelete' => true
157 )
158 );
159 }
160 }
161 }
162 if($this->malwareEnabled){ wordfence::statusEnd($this->status['malware'], $this->haveIssues['malware']); }
163 }
164 private function _dirHash($path){
165 if(substr($path, -3, 3) == '/..' || substr($path, -2, 2) == '/.'){
166 return;
167 }
168 if(! is_readable($path)){ return; } //Applies to files and dirs
169 if(is_dir($path)){
170 $this->totalDirs++;
171 if($path[strlen($path) - 1] != '/'){
172 $path .= '/';
173 }
174 $cont = scandir($path);
175 for($i = 0; $i < sizeof($cont); $i++){
176 if($cont[$i] == '.' || $cont[$i] == '..'){ continue; }
177 $file = $path . $cont[$i];
178 if(is_file($file)){
179 $this->processFile($file);
180 } else if(is_dir($file)) {
181 $this->_dirHash($file);
182 }
183 }
184 } else {
185 if(is_file($path)){
186 $this->processFile($path);
187 }
188 }
189 }
190 private function processFile($realFile){
191 $file = substr($realFile, $this->striplen);
192 if( (! $this->stoppedOnFile) && microtime(true) - $this->startTime > $this->engine->maxExecTime){ //max X seconds but don't allow fork if we're looking for the file we stopped on. Search mode is VERY fast.
193 $this->stoppedOnFile = $file;
194 wordfence::status(4, 'info', "Calling fork() from wordfenceHash::processFile with maxExecTime: " . $this->engine->maxExecTime);
195 $this->engine->fork();
196 //exits
197 }
198
199 //Put this after the fork, that way we will at least scan one more file after we fork if it takes us more than 10 seconds to search for the stoppedOnFile
200 if($this->stoppedOnFile && $file != $this->stoppedOnFile){
201 return;
202 } else if($this->stoppedOnFile && $file == $this->stoppedOnFile){
203 $this->stoppedOnFile = false; //Continue scanning
204 }
205
206 if(wfUtils::fileTooBig($realFile)){
207 wordfence::status(4, 'info', "Skipping file larger than max size: $realFile");
208 return;
209 }
210 if(function_exists('memory_get_usage')){
211 wordfence::status(4, 'info', "Scanning: $realFile (Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
212 } else {
213 wordfence::status(4, 'info', "Scanning: $realFile");
214 }
215 $wfHash = self::wfHash($realFile);
216 if($wfHash){
217 $md5 = strtoupper($wfHash[0]);
218 $shac = strtoupper($wfHash[1]);
219 $knownFile = 0;
220 if($this->malwareEnabled && $this->isMalwarePrefix($md5)){
221 $this->possibleMalware[] = array($file, $md5);
222 }
223 if(isset($this->knownFiles['core'][$file])){
224 if(strtoupper($this->knownFiles['core'][$file]) == $shac){
225 $knownFile = 1;
226 } else {
227 if($this->coreEnabled){
228 $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $file);
229 $fileContents = @file_get_contents($localFile);
230 if($fileContents && (! preg_match('/<\?' . 'php[\r\n\s\t]*\/\/[\r\n\s\t]*Silence is golden\.[\r\n\s\t]*(?:\?>)?[\r\n\s\t]*$/s', $fileContents))){ //<?php
231 if(! $this->isSafeFile($shac)){
232
233 $this->haveIssues['core'] = true;
234 $this->engine->addIssue(
235 'file',
236 1,
237 'coreModified' . $file . $md5,
238 'coreModified' . $file,
239 'WordPress core file modified: ' . $file,
240 "This WordPress core file has been modified and differs from the original file distributed with this version of WordPress.",
241 array(
242 'file' => $file,
243 'cType' => 'core',
244 'canDiff' => true,
245 'canFix' => true,
246 'canDelete' => false
247 )
248 );
249 }
250 }
251 }
252 }
253 } else if(isset($this->knownFiles['plugins'][$file])){
254 if(in_array($shac, $this->knownFiles['plugins'][$file])){
255 $knownFile = 1;
256 } else {
257 if($this->pluginsEnabled){
258 if(! $this->isSafeFile($shac)){
259 $itemName = $this->knownFiles['plugins'][$file][0];
260 $itemVersion = $this->knownFiles['plugins'][$file][1];
261 $cKey = $this->knownFiles['plugins'][$file][2];
262 $this->haveIssues['plugins'] = true;
263 $this->engine->addIssue(
264 'file',
265 2,
266 'modifiedplugin' . $file . $md5,
267 'modifiedplugin' . $file,
268 'Modified plugin file: ' . $file,
269 "This file belongs to plugin \"$itemName\" version \"$itemVersion\" and has been modified from the file that is distributed by WordPress.org for this version. Please use the link to see how the file has changed. If you have modified this file yourself, you can safely ignore this warning. If you see a lot of changed files in a plugin that have been made by the author, then try uninstalling and reinstalling the plugin to force an upgrade. Doing this is a workaround for plugin authors who don't manage their code correctly. [See our FAQ on www.wordfence.com for more info]",
270 array(
271 'file' => $file,
272 'cType' => 'plugin',
273 'canDiff' => true,
274 'canFix' => true,
275 'canDelete' => false,
276 'cName' => $itemName,
277 'cVersion' => $itemVersion,
278 'cKey' => $cKey
279 )
280 );
281 }
282 }
283
284 }
285 } else if(isset($this->knownFiles['themes'][$file])){
286 if(in_array($shac, $this->knownFiles['themes'][$file])){
287 $knownFile = 1;
288 } else {
289 if($this->themesEnabled){
290 if(! $this->isSafeFile($shac)){
291 $itemName = $this->knownFiles['themes'][$file][0];
292 $itemVersion = $this->knownFiles['themes'][$file][1];
293 $cKey = $this->knownFiles['themes'][$file][2];
294 $this->haveIssues['themes'] = true;
295 $this->engine->addIssue(
296 'file',
297 2,
298 'modifiedtheme' . $file . $md5,
299 'modifiedtheme' . $file,
300 'Modified theme file: ' . $file,
301 "This file belongs to theme \"$itemName\" version \"$itemVersion\" and has been modified from the original distribution. It is common for site owners to modify their theme files, so if you have modified this file yourself you can safely ignore this warning.",
302 array(
303 'file' => $file,
304 'cType' => 'theme',
305 'canDiff' => true,
306 'canFix' => true,
307 'canDelete' => false,
308 'cName' => $itemName,
309 'cVersion' => $itemVersion,
310 'cKey' => $cKey
311 )
312 );
313 }
314 }
315
316 }
317 }
318 // knownFile means that the file is both part of core or a known plugin or theme AND that we recognize the file's hash.
319 // we could split this into files who's path we recognize and file's who's path we recognize AND who have a valid sig.
320 // But because we want to scan files who's sig we don't recognize, regardless of known path or not, we only need one "knownFile" field.
321 $this->db->queryWrite("insert into " . $this->db->prefix() . "wfFileMods (filename, filenameMD5, knownFile, oldMD5, newMD5) values ('%s', unhex(md5('%s')), %d, '', unhex('%s')) ON DUPLICATE KEY UPDATE newMD5=unhex('%s'), knownFile=%d", $file, $file, $knownFile, $md5, $md5, $knownFile);
322
323 //Now that we know we can open the file, lets update stats
324 if(preg_match('/\.(?:js|html|htm|css)$/i', $realFile)){
325 $this->linesOfJCH += sizeof(file($realFile));
326 } else if(preg_match('/\.php$/i', $realFile)){
327 $this->linesOfPHP += sizeof(file($realFile));
328 }
329 $this->totalFiles++;
330 $this->totalData += filesize($realFile); //We already checked if file overflows int in the fileTooBig routine above
331 if($this->totalFiles % 100 === 0){
332 wordfence::status(2, 'info', "Analyzed " . $this->totalFiles . " files containing " . wfUtils::formatBytes($this->totalData) . " of data so far");
333 }
334 } else {
335 //wordfence::status(2, 'error', "Could not gen hash for file (probably because we don't have permission to access the file): $realFile");
336 }
337 }
338 public static function wfHash($file){
339 wfUtils::errorsOff();
340 $md5 = @md5_file($file, false);
341 wfUtils::errorsOn();
342
343 if(! $md5){ return false; }
344 $fp = @fopen($file, "rb");
345 if(! $fp){
346 return false;
347 }
348 $ctx = hash_init('sha256');
349 while (!feof($fp)) {
350 hash_update($ctx, str_replace( array("\n","\r","\t"," ") ,"",fread($fp, 65536)));
351 }
352 $shac = hash_final($ctx, false);
353 return array($md5, $shac);
354 }
355 private function isMalwarePrefix($hexMD5){
356 $binPrefix = pack("H*", substr($hexMD5, 0, 8));
357 if(isset($this->malwareData[$binPrefix])){
358 return true;
359 }
360 return false;
361 }
362 private function isSafeFile($shac){
363 $result = $this->engine->api->call('is_safe_file', array(), array('shac' => strtoupper($shac)));
364 if(isset($result['isSafe']) && $result['isSafe'] == 1){
365 return true;
366 }
367 return false;
368 }
369 }
370 ?>
371