PluginProbe ʕ •ᴥ•ʔ
Wordfence Security – Firewall, Malware Scan, and Login Security / 2.0.5
Wordfence Security – Firewall, Malware Scan, and Login Security v2.0.5
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 / wfScanEngine.php
wordfence / lib Last commit date
Diff 14 years ago .htaccess 14 years ago Diff.php 14 years ago IPTraf.php 14 years ago diffResult.php 14 years ago dropAll.php 14 years ago email_genericAlert.php 14 years ago email_newIssues.php 14 years ago email_unlockRequest.php 14 years ago menu_activity.php 14 years ago menu_blockedIPs.php 14 years ago menu_config.php 14 years ago menu_options.php 14 years ago menu_scan.php 14 years ago sysinfo.php 14 years ago viewFullActivityLog.php 14 years ago wf503.php 14 years ago wfAPI.php 14 years ago wfAction.php 14 years ago wfBrowscap.php 14 years ago wfBrowscapCache.php 14 years ago wfConfig.php 14 years ago wfCrawl.php 14 years ago wfDB.php 14 years ago wfDict.php 14 years ago wfIssues.php 14 years ago wfLockedOut.php 14 years ago wfLog.php 14 years ago wfModTracker.php 14 years ago wfRate.php 14 years ago wfScanEngine.php 14 years ago wfSchema.php 14 years ago wfUnlockMsg.php 14 years ago wfUtils.php 14 years ago wfViewResult.php 14 years ago wordfenceClass.php 14 years ago wordfenceConstants.php 14 years ago wordfenceHash.php 14 years ago wordfenceScanner.php 14 years ago wordfenceURLHoover.php 14 years ago
wfScanEngine.php
772 lines
1 <?php
2 require_once('wordfenceClass.php');
3 require_once('wordfenceHash.php');
4 require_once('wfAPI.php');
5 require_once('wordfenceScanner.php');
6 require_once('wfIssues.php');
7 require_once('wfDB.php');
8 require_once('wfUtils.php');
9 class wfScanEngine {
10 private $i = false;
11 private $api = false;
12 private $dbh = false;
13 private $wp_version = false;
14 private $apiKey = false;
15 private $errorStopped = false;
16 private $dictWords = array();
17 private $startTime = 0;
18 public function __construct(){
19 $this->startTime = time();
20 $this->i = new wfIssues();
21 $this->wp_version = wfUtils::getWPVersion();
22 $this->apiKey = wfConfig::get('apiKey');
23 $this->api = new wfAPI($this->apiKey, $this->wp_version);
24 include('wfDict.php'); //$dictWords
25 $this->dictWords = $dictWords;
26 }
27 public function go(){
28 $this->status(1, 'info', "Initializing scan. Memory available: " . @ini_get('memory_limit') );
29 $this->i->deleteNew();
30
31 try {
32 $this->doScan();
33 if(! $this->errorStopped){
34 wfConfig::set('lastScanCompleted', 'ok');
35 }
36 //updating this scan ID will trigger the scan page to load/reload the results.
37 $this->i->setScanTimeNow();
38 //scan ID only incremented at end of scan to make UI load new results
39 $this->emailNewIssues();
40 } catch(Exception $e){
41 $this->errorStop($e->getMessage());
42 }
43 wordfence::scheduleNextScan(true);
44 }
45 public function emailNewIssues(){
46 $this->i->emailNewIssues();
47 }
48 private function doScan(){
49 $this->status(1, 'info', "Contacting Wordfence to initiate scan");
50 $this->api->call('log_scan', array(), array());
51 if($this->api->errorMsg){
52 $this->errorStop($this->api->errorMsg);
53 return;
54 }
55 $unknownFiles = $this->scanKnownFiles();
56 if($this->errorStopped){
57 return;
58 }
59 if(wfConfig::get('scansEnabled_fileContents')){
60 $this->scanFileContents($unknownFiles);
61 if($this->errorStopped){
62 return;
63 }
64 }
65 if(wfConfig::get('scansEnabled_posts')){
66 $this->scanPosts();
67 if($this->errorStopped){
68 return;
69 }
70 }
71 if(wfConfig::get('scansEnabled_comments')){
72 $this->scanComments();
73 if($this->errorStopped){ return; }
74 }
75 if(wfConfig::get('scansEnabled_passwds')){
76 $this->scanAllPasswords();
77 if($this->errorStopped){ return; }
78 }
79 if(wfConfig::get('scansEnabled_diskSpace')){
80 $this->scanDiskSpace();
81 if($this->errorStopped){ return; }
82 }
83 if(wfConfig::get('scansEnabled_dns')){
84 $this->scanDNSChanges();
85 if($this->errorStopped){ return; }
86 }
87 if(wfConfig::get('scansEnabled_oldVersions')){
88 $this->scanOldVersions();
89 if($this->errorStopped){ return; }
90 }
91 $summary = $this->i->getSummaryItems();
92 $this->status(1, 'info', "Scan Complete. Scanned " . $summary['totalFiles'] . " files, " . $summary['totalPlugins'] . " plugins, " . $summary['totalThemes'] . " themes, " . ($summary['totalPages'] + $summary['totalPosts']) . " pages, " . $summary['totalComments'] . " comments and " . $summary['totalRows'] . " records in " . (time() - $this->startTime) . " seconds.");
93 if($this->i->totalIssues > 0){
94 $this->status(10, 'info', "SUM_FINAL:Scan complete. You have " . $this->i->totalIssues . " new issues to fix. See below for details.");
95 } else {
96 $this->status(10, 'info', "SUM_FINAL:Scan complete. Congratulations, there were no problems found.");
97 }
98 return;
99 }
100 private function scanKnownFiles(){
101 $malwareScanEnabled = $coreScanEnabled = $pluginScanEnabled = $themeScanEnabled = false;
102 $statusIDX = array(
103 'core' => false,
104 'plugin' => false,
105 'theme' => false,
106 'unknown' => false
107 );
108 if(wfConfig::get('scansEnabled_core')){
109 $coreScanEnabled = true;
110 $statusIDX['core'] = wordfence::statusStart("Comparing core WordPress files against originals in repository");
111 } else {
112 wordfence::statusDisabled("Skipping core scan");
113 }
114 if(wfConfig::get('isPaid')){
115 if(wfConfig::get('scansEnabled_plugins')){
116 $pluginScanEnabled = true;
117 $statusIDX['plugin'] = wordfence::statusStart("Premium: Comparing plugin files against originals in repository");
118 } else {
119 wordfence::statusDisabled("Skipping comparing plugin files against originals in repository");
120 }
121 } else {
122 wordfence::statusPaidOnly("Skipping comparing plugin files against originals in repository");
123 }
124 if(wfConfig::get('isPaid')){
125 if(wfConfig::get('scansEnabled_themes')){
126 $themeScanEnabled = true;
127 $statusIDX['theme'] = wordfence::statusStart("Premium: Comparing theme files against originals in repository");
128 } else {
129 wordfence::statusDisabled("Skipping comparing theme files against originals in repository");
130 }
131 } else {
132 wordfence::statusPaidOnly("Skipping comparing theme files against originals in repository");
133 }
134
135 if(wfConfig::get('scansEnabled_malware')){
136 $statusIDX['unknown'] = wordfence::statusStart("Scanning for known malware files");
137 $malwareScanEnabled = true;
138 } else {
139 wordfence::statusDisabled("Skipping malware scan");
140 $this->status(2, 'info', "Skipping malware scan because it's disabled.");
141 }
142 $summaryUpdateRequired = $this->i->summaryUpdateRequired();
143 if((! $summaryUpdateRequired) && (! ($coreScanEnabled || $pluginScanEnabled || $themeScanEnabled || $malwareScanEnabled))){
144 $this->status(2, 'info', "Finishing this stage because we don't have to do a summary update and we don't need to do a core, plugin, theme or malware scan.");
145 return array();
146 }
147
148 //CORE SCAN
149 $hasher = new wordfenceHash(strlen(ABSPATH));
150 $baseWPStuff = array( '.htaccess', 'index.php', 'license.txt', 'readme.html', 'wp-activate.php', 'wp-admin', 'wp-app.php', 'wp-blog-header.php', 'wp-comments-post.php', 'wp-config-sample.php', 'wp-content', 'wp-cron.php', 'wp-includes', 'wp-links-opml.php', 'wp-load.php', 'wp-login.php', 'wp-mail.php', 'wp-pass.php', 'wp-register.php', 'wp-settings.php', 'wp-signup.php', 'wp-trackback.php', 'xmlrpc.php');
151 $baseContents = scandir(ABSPATH);
152 $scanOutside = wfConfig::get('other_scanOutside');
153 if($scanOutside){
154 wordfence::status(2, 'info', "Including files that are outside the WordPress installation in the scan.");
155 }
156 foreach($baseContents as $file){ //Only include base files less than a meg that are files.
157 $fullFile = rtrim(ABSPATH, '/') . '/' . $file;
158 if($scanOutside){
159 $includeInScan[] = $file;
160 } else if(in_array($file, $baseWPStuff) || (is_file($fullFile) && is_readable($fullFile) && filesize($fullFile) < 1000000) ){
161 $includeInScan[] = $file;
162 }
163 }
164 $this->status(2, 'info', "Hashing your WordPress files for comparison against originals.");
165 $hashes = $hasher->hashPaths(ABSPATH, $includeInScan);
166 $this->status(2, 'info', "Done hash. Updating summary items.");
167 $this->i->updateSummaryItem('totalData', wfUtils::formatBytes($hasher->totalData));
168 $this->i->updateSummaryItem('totalFiles', $hasher->totalFiles);
169 $this->i->updateSummaryItem('totalDirs', $hasher->totalDirs);
170 $this->i->updateSummaryItem('linesOfPHP', $hasher->linesOfPHP);
171 $this->i->updateSummaryItem('linesOfJCH', $hasher->linesOfJCH);
172
173 if(! function_exists( 'get_plugins')){
174 require_once ABSPATH . '/wp-admin/includes/plugin.php';
175 }
176 $this->status(2, 'info', "Getting plugin list from WordPress");
177 $plugins = get_plugins();
178 $this->status(2, 'info', "Found " . sizeof($plugins) . " plugins");
179 $this->i->updateSummaryItem('totalPlugins', sizeof($plugins));
180 if(! function_exists( 'get_themes')){
181 require_once ABSPATH . '/wp-includes/theme.php';
182 }
183 $this->status(2, 'info', "Getting theme list from WordPress");
184 $themes = get_themes();
185 $this->status(2, 'info', "Found " . sizeof($themes) . " themes");
186 $this->i->updateSummaryItem('totalThemes', sizeof($themes));
187 //Return now because we needed to do a summary update but don't have any other work to do.
188 if(! ($coreScanEnabled || $pluginScanEnabled || $themeScanEnabled || $malwareScanEnabled)){
189 $this->status(2, 'info', "Finishing up because we have done our required summary update and don't need to do a core, plugin, theme or malware scan.");
190 return array();
191 }
192 $this->status(2, 'info', "Reading theme information from each theme's style.css file");
193 foreach($themes as $themeName => $themeData){
194 $cssFile = $themeData['Stylesheet Dir'] . '/style.css';
195 $cssData = @file_get_contents($cssFile);
196 if($cssData){
197 if(preg_match('/Theme URI:\s*([^\r\n]+)/', $cssData, $matches)){ $themes[$themeName]['Theme URI'] = $matches[1]; }
198 if(preg_match('/License:\s*([^\r\n]+)/', $cssData, $matches)){ $themes[$themeName]['License'] = $matches[1]; }
199 if(preg_match('/License URI:\s*([^\r\n]+)/', $cssData, $matches)){ $themes[$themeName]['License URI'] = $matches[1]; }
200 }
201 }
202 $this->status(2, 'info', "Sending request to Wordfence servers to do main scan.");
203 $scanData = array(
204 'pluginScanEnabled' => $pluginScanEnabled,
205 'themeScanEnabled' => $themeScanEnabled,
206 'coreScanEnabled' => $coreScanEnabled,
207 'malwareScanEnabled' => $malwareScanEnabled,
208 'plugins' => $plugins,
209 'themes' => $themes,
210 'hashes' => wordfenceHash::bin2hex($hashes)
211 );
212 $result1 = $this->api->call('main_scan', array(), array(
213 'data' => json_encode($scanData)
214 ));
215 if($this->api->errorMsg){
216 $this->errorStop($this->api->errorMsg);
217 wordfence::statusEndErr();
218 return;
219 }
220 if(empty($result1['errorMsg']) === false){
221 $this->errorStop($result['errorMsg']);
222 wordfence::statusEndErr();
223 return;
224 }
225 if(! $result1){
226 $this->errorStop("We received an empty response from the Wordfence server when scanning core, plugin and theme files.");
227 wordfence::statusEndErr();
228 return;
229 }
230 $this->status(2, 'info', "Processing scan results");
231 $haveIssues = array(
232 'core' => false,
233 'plugin' => false,
234 'theme' => false,
235 'unknown' => false
236 );
237 foreach($result1['results'] as $issue){
238 $this->status(2, 'info', "Adding issue: " . $issue['shortMsg']);
239 if($this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data'])){
240 $haveIssues[$issue['data']['cType']] = true;
241 }
242 }
243 foreach($haveIssues as $type => $have){
244 if($statusIDX[$type] !== false){
245 wordfence::statusEnd($statusIDX[$type], $have);
246 }
247 }
248 return $result1['unknownFiles'];
249 }
250 private function scanFileContents($unknownFiles){
251 $statusIDX = wordfence::statusStart('Scanning file contents for infections and vulnerabilities');
252 $statusIDX2 = wordfence::statusStart('Scanning files for URLs in Google\'s Safe Browsing List');
253 if(! is_array($unknownFiles)){
254 $unknownFiles = array();
255 }
256 $this->status(2, 'info', "Getting list of changed files since last scan.");
257 $scanner = new wordfenceScanner($this->apiKey, $this->wp_version);
258 $this->status(2, 'info', "Starting scan of file contents");
259 $result2 = $scanner->scan(ABSPATH, $unknownFiles);
260 $this->status(2, 'info', "Done file contents scan");
261 if($scanner->errorMsg){
262 $this->errorStop($scanner->errorMsg);
263 }
264 $haveIssues = false;
265 $haveIssuesGSB = false;
266 foreach($result2 as $issue){
267 $this->status(2, 'info', "Adding issue: " . $issue['shortMsg']);
268 if($this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data'])){
269 if(empty($issue['data']['gsb']) === false){
270 $haveIssuesGSB = true;
271 } else {
272 $haveIssues = true;
273 }
274 }
275 }
276 wordfence::statusEnd($statusIDX, $haveIssues);
277 wordfence::statusEnd($statusIDX2, $haveIssuesGSB);
278 }
279 private function scanPosts(){
280 $statusIDX = wordfence::statusStart('Scanning posts for URL\'s in Google\'s Safe Browsing List');
281 $blogsToScan = $this->getBlogsToScan('posts');
282 $wfdb = new wfDB();
283 $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
284 $postDat = array();
285 foreach($blogsToScan as $blog){
286 $q1 = $wfdb->query("select ID from " . $blog['table'] . " where post_type IN ('page', 'post') and post_status = 'publish'");
287 while($idRow = mysql_fetch_assoc($q1)){
288 $row = $wfdb->querySingleRec("select ID, post_title, post_type, post_date, post_content from " . $blog['table'] . " where ID=%d", $idRow['ID']);
289 $h->hoover($blog['blog_id'] . '-' . $row['ID'], $row['post_title'] . ' ' . $row['post_content']);
290 $postDat[$blog['blog_id'] . '-' . $row['ID']] = array(
291 'contentMD5' => md5($row['post_content']),
292 'title' => $row['post_title'],
293 'type' => $row['post_type'],
294 'postDate' => $row['post_date'],
295 'isMultisite' => $blog['isMultisite'],
296 'domain' => $blog['domain'],
297 'path' => $blog['path'],
298 'blog_id' => $blog['blog_id']
299 );
300
301 }
302 }
303 $this->status(2, 'info', "Examining URLs found in posts we scanned for dangerous websites");
304 $hooverResults = $h->getBaddies();
305 $this->status(2, 'info', "Done examining URls");
306 if($h->errorMsg){
307 $this->errorStop($h->errorMsg);
308 wordfence::statusEndErr();
309 return;
310
311 }
312 $haveIssues = false;
313 foreach($hooverResults as $idString => $hresults){
314 $arr = explode('-', $idString);
315 $blogID = $arr[0];
316 $postID = $arr[1];
317 $uctype = ucfirst($postDat[$idString]['type']);
318 $type = $postDat[$idString]['type'];
319 foreach($hresults as $result){
320 if($result['badList'] == 'goog-malware-shavar'){
321 $shortMsg = "$uctype contains a suspected malware URL.";
322 $longMsg = "This $type contains a suspected malware URL listed on Google's list of malware sites. The URL is: " . $result['URL'] . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.";
323 } else if($result['badList'] == 'googpub-phish-shavar'){
324 $shortMsg = "$uctype contains a suspected phishing site URL.";
325 $longMsg = "This $type contains a URL that is a suspected phishing site that is currently listed on Google's list of known phishing sites. The URL is: " . $result['URL'];
326 } else {
327 //A list type that may be new and the plugin has not been upgraded yet.
328 continue;
329 }
330 $this->status(2, 'info', "Adding issue: $shortMsg");
331 if(is_multisite()){
332 switch_to_blog($blogID);
333 }
334 $ignoreP = $idString;
335 $ignoreC = $idString . $postDat[$idString]['contentMD5'];
336 if($this->addIssue('postBadURL', 1, $ignoreP, $ignoreC, $shortMsg, $longMsg, array(
337 'postID' => $postID,
338 'badURL' => $result['URL'],
339 'postTitle' => $postDat[$idString]['title'],
340 'type' => $postDat[$idString]['type'],
341 'uctype' => $uctype,
342 'permalink' => get_permalink($postID),
343 'editPostLink' => get_edit_post_link($postID),
344 'postDate' => $postDat[$idString]['postDate'],
345 'isMultisite' => $postDat[$idString]['isMultisite'],
346 'domain' => $postDat[$idString]['domain'],
347 'path' => $postDat[$idString]['path'],
348 'blog_id' => $blogID
349 ))){
350 $haveIssues = true;
351 }
352 if(is_multisite()){
353 restore_current_blog();
354 }
355 }
356 }
357 wordfence::statusEnd($statusIDX, $haveIssues);
358 }
359 public function isBadComment($author, $email, $url, $IP, $content){
360 $content = $author . ' ' . $email . ' ' . $url . ' ' . $IP . ' ' . $content;
361 $cDesc = '';
362 if($author){
363 $cDesc = "Author: $author ";
364 }
365 if($email){
366 $cDesc .= "Email: $email ";
367 }
368 $cDesc = "Source IP: $IP ";
369 $this->status(2, 'info', "Scanning comment with $cDesc");
370
371 $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
372 $h->hoover(1, $content);
373 $hooverResults = $h->getBaddies();
374 if($h->errorMsg){
375 return false;
376 }
377 if(sizeof($hooverResults) > 0 && isset($hooverResults[1])){
378 $hresults = $hooverResults[1];
379 foreach($hresults as $result){
380 if($result['badList'] == 'goog-malware-shavar'){
381 $this->status(2, 'info', "Marking comment as spam for containing a malware URL. Comment has $cDesc");
382 return true;
383 } else if($result['badList'] == 'googpub-phish-shavar'){
384 $this->status(2, 'info', "Marking comment as spam for containing a phishing URL. Comment has $cDesc");
385 return true;
386 } else {
387 //A list type that may be new and the plugin has not been upgraded yet.
388 continue;
389 }
390 }
391 }
392 $this->status(2, 'info', "Scanned comment with $cDesc");
393 return false;
394 }
395 private function scanComments(){
396 $statusIDX = wordfence::statusStart('Scanning comments for URL\'s in Google\'s Safe Browsing List');
397 global $wpdb;
398 $wfdb = new wfDB();
399 $commentDat = array();
400 $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
401 $blogsToScan = $this->getBlogsToScan('comments');
402 foreach($blogsToScan as $blog){
403 $q1 = $wfdb->query("select comment_ID from " . $blog['table'] . " where comment_approved=1");
404 if( ! $q1){
405 wordfence::statusEndErr();
406 return;
407 }
408 if(! (mysql_num_rows($q1) > 0)){
409 continue;
410 }
411
412 while($idRow = mysql_fetch_assoc($q1)){
413 $row = $wfdb->querySingleRec("select comment_ID, comment_date, comment_type, comment_author, comment_author_url, comment_content from " . $blog['table'] . " where comment_ID=%d", $idRow['comment_ID']);
414 $h->hoover($blog['blog_id'] . '-' . $row['comment_ID'], $row['comment_author_url'] . ' ' . $row['comment_author'] . ' ' . $row['comment_content']);
415 $commentDat[$blog['blog_id'] . '-' . $row['comment_ID']] = array(
416 'contentMD5' => md5($row['comment_content'] . $row['comment_author'] . $row['comment_author_url']),
417 'author' => $row['comment_author'],
418 'type' => ($row['comment_type'] ? $row['comment_type'] : 'comment'),
419 'date' => $row['comment_date'],
420 'isMultisite' => $blog['isMultisite'],
421 'domain' => $blog['domain'],
422 'path' => $blog['path'],
423 'blog_id' => $blog['blog_id']
424 );
425 }
426 }
427 $hooverResults = $h->getBaddies();
428 if($h->errorMsg){
429 $this->errorStop($h->errorMsg);
430 wordfence::statusEndErr();
431 return;
432 }
433 $haveIssues = false;
434 foreach($hooverResults as $idString => $hresults){
435 $arr = explode('-', $idString);
436 $blogID = $arr[0];
437 $commentID = $arr[1];
438 $uctype = ucfirst($commentDat[$idString]['type']);
439 $type = $commentDat[$idString]['type'];
440 foreach($hresults as $result){
441 if($result['badList'] == 'goog-malware-shavar'){
442 $shortMsg = "$uctype contains a suspected malware URL.";
443 $longMsg = "This $type contains a suspected malware URL listed on Google's list of malware sites. The URL is: " . $result['URL'] . " - More info available at <a href=\"http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=" . urlencode($result['URL']) . "&client=googlechrome&hl=en-US\" target=\"_blank\">Google Safe Browsing diagnostic page</a>.";
444 } else if($result['badList'] == 'googpub-phish-shavar'){
445 $shortMsg = "$uctype contains a suspected phishing site URL.";
446 $longMsg = "This $type contains a URL that is a suspected phishing site that is currently listed on Google's list of known phishing sites. The URL is: " . $result['URL'];
447 } else {
448 //A list type that may be new and the plugin has not been upgraded yet.
449 continue;
450 }
451 if(is_multisite()){
452 switch_to_blog($blogID);
453 }
454 $ignoreP = $idString;
455 $ignoreC = $idString . '-' . $commentDat[$idString]['contentMD5'];
456 if($this->addIssue('commentBadURL', 1, $ignoreP, $ignoreC, $shortMsg, $longMsg, array(
457 'commentID' => $commentID,
458 'badURL' => $result['URL'],
459 'author' => $commentDat[$idString]['author'],
460 'type' => $type,
461 'uctype' => $uctype,
462 'editCommentLink' => get_edit_comment_link($commentID),
463 'commentDate' => $commentDat[$idString]['date'],
464 'isMultisite' => $commentDat[$idString]['isMultisite'],
465 'domain' => $commentDat[$idString]['domain'],
466 'path' => $commentDat[$idString]['path'],
467 'blog_id' => $blogID
468 ))){
469 $haveIssues = true;
470 }
471 if(is_multisite()){
472 restore_current_blog();
473 }
474 }
475 }
476 wordfence::statusEnd($statusIDX, $haveIssues);
477 }
478 public function getBlogsToScan($table){
479 $wfdb = new wfDB();
480 global $wpdb;
481 $prefix = $wpdb->base_prefix;
482 $blogsToScan = array();
483 if(is_multisite()){
484 $q1 = $wfdb->query("select blog_id, domain, path from $prefix"."blogs where deleted=0 order by blog_id asc");
485 while($row = mysql_fetch_assoc($q1)){
486 $row['isMultisite'] = true;
487 if($row['blog_id'] == 1){
488 $row['table'] = $prefix . $table;
489 } else {
490 $row['table'] = $prefix . $row['blog_id'] . '_' . $table;
491 }
492 array_push($blogsToScan, $row);
493 }
494 } else {
495 array_push($blogsToScan, array(
496 'isMultisite' => false,
497 'table' => $prefix . $table,
498 'blog_id' => '1',
499 'domain' => '',
500 'path' => '',
501 ));
502 }
503 return $blogsToScan;
504 }
505 private function highestCap($caps){
506 foreach(array('administrator', 'editor', 'author', 'contributor', 'subscriber') as $cap){
507 if(empty($caps[$cap]) === false && $caps[$cap]){
508 return $cap;
509 }
510 }
511 return '';
512 }
513 private function isEditor($caps){
514 foreach(array('contributor', 'author', 'editor', 'administrator') as $cap){
515 if(empty($caps[$cap]) === false && $caps[$cap]){
516 return true;
517 }
518 }
519 return false;
520 }
521 private function scanAllPasswords(){
522 $statusIDX = wordfence::statusStart('Scanning for weak passwords');
523 global $wpdb;
524 $ws = $wpdb->get_results("SELECT ID, user_login FROM $wpdb->users");
525 $haveIssues = false;
526 foreach($ws as $user){
527 $this->status(2, 'info', "Checking password strength for: " . $user->user_login);
528 $isWeak = $this->scanUserPassword($user->ID);
529 if($isWeak){ $haveIssues = true; }
530 }
531 wordfence::statusEnd($statusIDX, $haveIssues);
532 }
533 public function scanUserPassword($userID){
534 require_once( ABSPATH . 'wp-includes/class-phpass.php');
535 $hasher = new PasswordHash(8, TRUE);
536 $userDat = get_userdata($userID);
537 $this->status(2, 'info', "Checking password strength of user '" . $userDat->user_login . "'");
538 $shortMsg = "";
539 $longMsg = "";
540 $level = 1;
541 $highCap = $this->highestCap($userDat->wp_capabilities);
542 if($this->isEditor($userDat->wp_capabilities)){
543 $shortMsg = "A user with '" . $highCap . "' access has an easy password.";
544 $longMsg = "A user with the a role of '" . $highCap . "' has a password that is easy to guess. Please change this password yourself or ask the user to change it.";
545 $level = 1;
546 $words = $this->dictWords;
547 } else {
548 $shortMsg = "A user with 'subscriber' access has a very easy password.";
549 $longMsg = "A user with 'subscriber' access has a password that is very easy to guess. Please either change it or ask the user to change their password.";
550 $level = 2;
551 $words = array($userDat->user_login);
552 }
553 $haveIssue = false;
554 for($i = 0; $i < sizeof($words); $i++){
555 if($hasher->CheckPassword($words[$i], $userDat->user_pass)){
556 $this->status(2, 'info', "Adding issue " . $shortMsg);
557 if($this->addIssue('easyPassword', $level, $userDat->ID, $userDat->ID . '-' . $userDat->user_pass, $shortMsg, $longMsg, array(
558 'ID' => $userDat->ID,
559 'user_login' => $userDat->user_login,
560 'user_email' => $userDat->user_email,
561 'first_name' => $userDat->first_name,
562 'last_name' => $userDat->last_name,
563 'editUserLink' => wfUtils::editUserLink($userDat->ID)
564 ))){
565 $haveIssue = true;
566 }
567 break;
568 }
569 }
570 $this->status(2, 'info', "Completed checking password strength of user '" . $userDat->user_login . "'");
571 return $haveIssue;
572 }
573 private function scanDiskSpace(){
574 $statusIDX = wordfence::statusStart("Scanning to check available disk space");
575 $total = disk_total_space('.');
576 $free = disk_free_space('.');
577 $this->status(2, 'info', "Total space: $total Free space: $free");
578 if( (! $total) || (! $free )){ //If we get zeros it's probably not reading right. If free is zero then we're out of space and already in trouble.
579 wordfence::statusEnd($statusIDX, false);
580 return;
581 }
582 $level = false;
583 $spaceLeft = sprintf('%.2f', ($free / $total * 100));
584 $this->status(2, 'info', "The disk has $spaceLeft percent space available");
585 if($spaceLeft < 3){
586 $level = 1;
587 } else if($spaceLeft < 5){
588 $level = 2;
589 } else {
590 wordfence::statusEnd($statusIDX, false);
591 return;
592 }
593 if($this->addIssue('diskSpace', $level, 'diskSpace' . $level, 'diskSpace' . $level, "You have $spaceLeft" . "% disk space remaining", "You only have $spaceLeft" . "% of your disk space remaining. Please free up disk space or your website may stop serving requests.", array(
594 'spaceLeft' => $spaceLeft ))){
595 wordfence::statusEnd($statusIDX, true);
596 } else {
597 wordfence::statusEnd($statusIDX, true);
598 }
599 }
600 private function scanDNSChanges(){
601 if(! function_exists('dns_get_record')){
602 $this->status(1, 'info', "Skipping DNS scan because this system does not support dns_get_record()");
603 return;
604 }
605 $statusIDX = wordfence::statusStart("Scanning DNS for unauthorized changes");
606 $haveIssues = false;
607 $home = get_home_url();
608 if(preg_match('/https?:\/\/([^\/]+)/i', $home, $matches)){
609 $host = strtolower($matches[1]);
610 $this->status(2, 'info', "Starting DNS scan for $host");
611
612 $cnameArrRec = dns_get_record($host, DNS_CNAME);
613 $cnameArr = array();
614 $cnamesWeMustTrack = array();
615 foreach($cnameArrRec as $elem){
616 $this->status(2, 'info', "Scanning CNAME DNS record for " . $elem['host']);
617 if($elem['host'] == $host){
618 array_push($cnameArr, $elem);
619 $cnamesWeMustTrack[] = $elem['target'];
620 }
621 }
622 function wfAnonFunc1($a){ return $a['host'] . ' points to ' . $a['target']; }
623 $cnameArr = array_map('wfAnonFunc1', $cnameArr);
624 sort($cnameArr, SORT_STRING);
625 $currentCNAME = implode(', ', $cnameArr);
626 $loggedCNAME = wfConfig::get('wf_dnsCNAME');
627 $dnsLogged = wfConfig::get('wf_dnsLogged', false);
628 $msg = "A change in your DNS records may indicate that a hacker has hacked into your DNS administration system and has pointed your email or website to their own server for malicious purposes. It could also indicate that your domain has expired. If you made this change yourself you can mark it 'resolved' and safely ignore it.";
629 if($dnsLogged && $loggedCNAME != $currentCNAME){
630 if($this->addIssue('dnsChange', 2, 'dnsChanges', 'dnsChanges', "Your DNS records have changed", "We have detected a change in the CNAME records of your DNS configuration for the domain $host. A CNAME record is an alias that is used to point a domain name to another domain name. For example foo.example.com can point to bar.example.com which then points to an IP address of 10.1.1.1. $msg", array(
631 'type' => 'CNAME',
632 'host' => $host,
633 'oldDNS' => $loggedCNAME,
634 'newDNS' => $currentCNAME
635 ))){
636 $haveIssues = true;
637 }
638 }
639 wfConfig::set('wf_dnsCNAME', $currentCNAME);
640
641 $aArrRec = dns_get_record($host, DNS_A);
642 $aArr = array();
643 foreach($aArrRec as $elem){
644 $this->status(2, 'info', "Scanning DNS A record for " . $elem['host']);
645 if($elem['host'] == $host || in_array($elem['host'], $cnamesWeMustTrack) ){
646 array_push($aArr, $elem);
647 }
648 }
649 function wfAnonFunc2($a){ return $a['host'] . ' points to ' . $a['ip']; }
650 $aArr = array_map('wfAnonFunc2', $aArr);
651 sort($aArr, SORT_STRING);
652 $currentA = implode(', ', $aArr);
653 $loggedA = wfConfig::get('wf_dnsA');
654 $dnsLogged = wfConfig::get('wf_dnsLogged', false);
655 if($dnsLogged && $loggedA != $currentA){
656 if($this->addIssue('dnsChange', 2, 'dnsChanges', 'dnsChanges', "Your DNS records have changed", "We have detected a change in the A records of your DNS configuration that may affect the domain $host. An A record is a record in DNS that points a domain name to an IP address. $msg", array(
657 'type' => 'A',
658 'host' => $host,
659 'oldDNS' => $loggedA,
660 'newDNS' => $currentA
661 ))){
662 $haveIssues = true;
663 }
664 }
665 wfConfig::set('wf_dnsA', $currentA);
666
667
668
669 $mxArrRec = dns_get_record($host, DNS_MX);
670 $mxArr = array();
671 foreach($mxArrRec as $elem){
672 $this->status(2, 'info', "Scanning DNS MX record for " . $elem['host']);
673 if($elem['host'] == $host){
674 array_push($mxArr, $elem);
675 }
676 }
677 function wfAnonFunc3($a){ return $a['target']; }
678 $mxArr = array_map('wfAnonFunc3', $mxArr);
679 sort($mxArr, SORT_STRING);
680 $currentMX = implode(', ', $mxArr);
681 $loggedMX = wfConfig::get('wf_dnsMX');
682 if($dnsLogged && $loggedMX != $currentMX){
683 if($this->addIssue('dnsChange', 2, 'dnsChanges', 'dnsChanges', "Your DNS records have changed", "We have detected a change in the email server (MX) records of your DNS configuration for the domain $host. $msg", array(
684 'type' => 'MX',
685 'host' => $host,
686 'oldDNS' => $loggedMX,
687 'newDNS' => $currentMX
688 ))){
689 $haveIssues = true;
690 }
691
692 }
693 wfConfig::set('wf_dnsMX', $currentMX);
694
695 wfConfig::set('wf_dnsLogged', 1);
696 }
697 wordfence::statusEnd($statusIDX, $haveIssues);
698 }
699 private function scanOldVersions(){
700 $statusIDX = wordfence::statusStart("Scanning for old themes, plugins and core files");
701 if(! function_exists( 'get_preferred_from_update_core')){
702 require_once(ABSPATH . 'wp-admin/includes/update.php');
703 }
704 $cur = get_preferred_from_update_core();
705 $haveIssues = false;
706 if(isset( $cur->response ) && $cur->response == 'upgrade'){
707 if($this->addIssue('wfUpgrade', 1, 'wfUpgrade' . $cur->current, 'wfUpgrade' . $cur->current, "Your WordPress version is out of date", "WordPress version " . $cur->current . " is now available. Please upgrade immediately to get the latest security updates from WordPress.", array(
708 'currentVersion' => $this->wp_version,
709 'newVersion' => $cur->current
710 ))){
711 $haveIssues = true;
712 }
713 }
714 $update_plugins = get_site_transient( 'update_plugins' );
715 if(isset($update_plugins) && (! empty($update_plugins->response))){
716 if(isset($update_plugins) && $update_plugins->response){
717 foreach($update_plugins->response as $plugin => $vals){
718 if(! function_exists( 'get_plugin_data')){
719 require_once ABSPATH . '/wp-admin/includes/plugin.php';
720 }
721 $pluginFile = wfUtils::getPluginBaseDir() . $plugin;
722 $data = get_plugin_data($pluginFile);
723 $data['newVersion'] = $vals->new_version;
724 $key = 'wfPluginUpgrade' . ' ' . $plugin . ' ' . $data['newVersion'] . ' ' . $data['Version'];
725 if($this->addIssue('wfPluginUpgrade', 1, $key, $key, "The Plugin \"" . $data['Name'] . "\" needs an upgrade.", "You need to upgrade \"" . $data['Name'] . "\" to the newest version to ensure you have any security fixes the developer has released.", $data)){
726 $haveIssues = true;
727 }
728 }
729 }
730 }
731 $update_themes = get_site_transient( 'update_themes' );
732 if(isset($update_themes) && (! empty($update_themes->response))){
733 if(! function_exists( 'get_themes')){
734 require_once ABSPATH . '/wp-includes/theme.php';
735 }
736 $themes = get_themes();
737 foreach($update_themes->response as $theme => $vals){
738 foreach($themes as $name => $themeData){
739 if(strtolower($name) == $theme){
740 $tData = array(
741 'newVersion' => $vals['new_version'],
742 'package' => $vals['package'],
743 'URL' => $vals['url'],
744 'name' => $themeData['Name'],
745 'version' => $themeData['Version']
746 );
747 $key = 'wfThemeUpgrade' . ' ' . $theme . ' ' . $tData['version'] . ' ' . $tData['newVersion'];
748 if($this->addIssue('wfThemeUpgrade', 1, $key, $key, "The Theme \"" . $themeData['Name'] . "\" needs an upgrade.", "You need to upgrade \"" . $themeData['Name'] . "\" to the newest version to ensure you have any security fixes the developer has released.", $tData)){
749 $haveIssues = true;
750 }
751 }
752 }
753
754 }
755 }
756 wordfence::statusEnd($statusIDX, $haveIssues);
757 }
758 private function errorStop($msg){
759 $this->errorStopped = true;
760 $this->status(1, 'error', $msg);
761 wfConfig::set('lastScanCompleted', $msg);
762 }
763 public function status($level, $type, $msg){
764 wordfence::status($level, $type, $msg);
765 }
766 private function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData){
767 return $this->i->addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
768 }
769 }
770
771 ?>
772