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 |