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
wordfenceClass.php
1245 lines
| 1 | <?php |
| 2 | require_once('wordfenceConstants.php'); |
| 3 | require_once('wfScanEngine.php'); |
| 4 | require_once('wfCrawl.php'); |
| 5 | require_once 'Diff.php'; |
| 6 | require_once 'Diff/Renderer/Html/SideBySide.php'; |
| 7 | require_once 'wfAPI.php'; |
| 8 | require_once 'wfIssues.php'; |
| 9 | require_once('wfDB.php'); |
| 10 | require_once('wfUtils.php'); |
| 11 | require_once('wfLog.php'); |
| 12 | require_once('wfConfig.php'); |
| 13 | require_once('wfSchema.php'); |
| 14 | class wordfence { |
| 15 | protected static $lastURLError = false; |
| 16 | protected static $curlContent = ""; |
| 17 | protected static $curlDataWritten = 0; |
| 18 | protected static $hasher = ''; |
| 19 | protected static $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; |
| 20 | protected static $ignoreList = false; |
| 21 | public static $newVisit = false; |
| 22 | private static $wfLog = false; |
| 23 | private static $hitID = 0; |
| 24 | private static $statusStartMsgs = array(); |
| 25 | public static function installPlugin(){ |
| 26 | $schema = new wfSchema(); |
| 27 | $schema->createAll(); //if not exists |
| 28 | wfConfig::setDefaults(); //If not set |
| 29 | |
| 30 | $api = new wfAPI('', wfUtils::getWPVersion()); |
| 31 | $keyData = $api->call('get_anon_api_key'); |
| 32 | if($api->errorMsg){ |
| 33 | die("Error fetching free API key from Wordfence: " . $api->errorMsg); |
| 34 | } |
| 35 | if($keyData['ok'] && $keyData['apiKey']){ |
| 36 | wfConfig::set('apiKey', $keyData['apiKey']); |
| 37 | } else { |
| 38 | die("Could not understand the response we received from the Wordfence servers when applying for a free API key."); |
| 39 | } |
| 40 | |
| 41 | |
| 42 | if( !wp_next_scheduled( 'wordfence_daily_cron' )){ |
| 43 | wp_schedule_event(time(), 'daily', 'wordfence_daily_cron'); |
| 44 | } |
| 45 | if( !wp_next_scheduled( 'wordfence_hourly_cron' )){ |
| 46 | wp_schedule_event(time(), 'hourly', 'wordfence_daily_cron'); |
| 47 | } |
| 48 | update_option('wordfenceActivated', 1); |
| 49 | $db = new wfDB(); |
| 50 | |
| 51 | //Upgrading from 1.5.6 or earlier needs: |
| 52 | $db->createKeyIfNotExists($prefix . 'wfStatus', 'level', 'k2'); |
| 53 | |
| 54 | if(wfConfig::get('isPaid') == 'free'){ |
| 55 | wfConfig::set('isPaid', ''); |
| 56 | } |
| 57 | wfConfig::set('alertEmailMsgCount', 0); |
| 58 | } |
| 59 | public static function uninstallPlugin(){ |
| 60 | update_option('wordfenceActivated', 0); |
| 61 | } |
| 62 | public static function hourlyCron(){ |
| 63 | global $wpdb; $p = $wpdb->base_prefix; |
| 64 | $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion()); |
| 65 | $patData = $api->call('get_known_vuln_pattern'); |
| 66 | if(is_array($patData) && $patData['pat']){ |
| 67 | if(@preg_match($patData['pat'], 'wordfence_test_vuln_match')){ |
| 68 | wfConfig::set('vulnRegex', $pat); |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | if(wfConfig::get('other_WFNet')){ |
| 73 | $wfdb = new wfDB(); |
| 74 | $q1 = $wfdb->query("select URI from $p"."wfVulnScanners where ctime > unix_timestamp() - 3600 limit 1000"); |
| 75 | $URIs = array(); |
| 76 | while($rec = mysql_fetch_assoc($q1)){ |
| 77 | array_push($URIs, $rec['URI']); |
| 78 | } |
| 79 | $wfdb->query("truncate table $p"."wfVulnScanners"); |
| 80 | $this->api->call('send_net_404s', array(), array( 'URIs' => json_encode($URIs) )); |
| 81 | |
| 82 | $q2 = $wfdb->query("select INET_NTOA(IP) as IP from $p"."wfVulnScanners where ctime > unix_timestamp() - 3600"); |
| 83 | $wfdb->query("truncate table $p"."wfVulnScanners"); |
| 84 | $scanCont = ""; |
| 85 | while($rec = mysql_fetch_assoc($q2)){ |
| 86 | $scanCont .= pack('N', ip2long($rec['IP'])); |
| 87 | } |
| 88 | |
| 89 | $q3 = $wfdb->query("select INET_NTOA(IP) as IP from $p"."wfLockedOut where blockedTime > unix_timestamp() - 3600"); |
| 90 | $lockCont = ""; |
| 91 | while($rec = mysql_fetch_assoc($q3)){ |
| 92 | $lockCont .= pack('N', ip2long($rec['IP'])); |
| 93 | } |
| 94 | $cont = pack('N', strlen($lockCont) / 4) . $lockCont . pack('N', strlen($scanCont) / 4) . $scanCont; |
| 95 | |
| 96 | $resp = $this->api->binCall('get_net_bad_ips', $cont); |
| 97 | if($resp['code'] == 200){ |
| 98 | $len = strlen($resp['data']); |
| 99 | $reason = "WFSN: Blocked by Wordfence Security Network"; |
| 100 | $wfdb->query("delete from $p"."wfBlocks where wfsn=1"); |
| 101 | if($len > 0 && $len % 4 == 0){ |
| 102 | for($i = 0; $i < $len; $i += 4){ |
| 103 | list($ipLong) = array_values(unpack('N', substr($resp['data'], $i, 4))); |
| 104 | $IPStr = long2ip($ipLong); |
| 105 | self::getLog()->blockIP($IPStr, $reason, true); |
| 106 | } |
| 107 | } |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | public static function dailyCron(){ |
| 112 | $wfdb = new wfDB(); |
| 113 | global $wpdb; $p = $wpdb->base_prefix; |
| 114 | $wfdb->query("delete from $p"."wfLocs where ctime < unix_timestamp() - %d", WORDFENCE_MAX_IPLOC_AGE); |
| 115 | $wfdb->query("truncate table $p"."wfBadLeechers"); //only uses date that's less than 1 minute old |
| 116 | $wfdb->query("delete from $p"."wfBlocks where blockedTime + %s < unix_timestamp()", wfConfig::get('blockedTime')); |
| 117 | $wfdb->query("delete from $p"."wfCrawlers where lastUpdate < unix_timestamp() - (86400 * 7)"); |
| 118 | |
| 119 | if(wfConfig::get('liveTraf_hitsMaxSize') && wfConfig::get('liveTraf_hitsMaxSize') > 0){ |
| 120 | $gotTableSize = false; |
| 121 | $tableSizeQ = $wfdb->query("show table status like '$p"."wfHits'"); |
| 122 | if($tableSizeQ){ |
| 123 | $tableSizeRec = mysql_fetch_assoc($tableSizeQ); |
| 124 | if($tableSizeRec && isset($tableSizeRec['Data_length']) && $tableSizeRec['Data_length'] > 0){ |
| 125 | $gotTableSize = true; |
| 126 | if($tableSizeRec['Data_length'] > (wfConfig::get('liveTraf_hitsMaxSize') * 1024 * 1024) ){ //convert to bytes |
| 127 | $count = $wfdb->querySingle("select count(*) as cnt from $p"."wfHits"); |
| 128 | $wfdb->query("delete from $p"."wfHits order by id asc limit %d", floor($count / 10)); //Delete 10% of rows. If we're still bigger than max, then next delete will reduce by further 10% and so on. |
| 129 | } |
| 130 | } |
| 131 | } else { |
| 132 | error_log("Wordfence could not get wfHits table data size for cleanup. Query returned false."); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | |
| 137 | |
| 138 | $maxRows = 1000; //affects stuff further down too |
| 139 | foreach(array('wfLeechers', 'wfScanners') as $table){ |
| 140 | //This is time based per IP so shouldn't get too big |
| 141 | $wfdb->query("delete from $p"."$table where eMin < ((unix_timestamp() - (86400 * 2)) / 60)"); |
| 142 | } |
| 143 | $wfdb->query("delete from $p"."wfLockedOut where blockedTime + %s < unix_timestamp()", wfConfig::get('loginSec_lockoutMins') * 60); |
| 144 | $count2 = $wfdb->querySingle("select count(*) as cnt from $p"."wfLogins"); |
| 145 | if($count2 > 100000){ |
| 146 | $wfdb->query("truncate table $p"."wfLogins"); //in case of Dos |
| 147 | } else if($count2 > $maxRows){ |
| 148 | $wfdb->query("delete from $p"."wfLogins order by ctime asc limit %d", ($count2 - $maxRows)); |
| 149 | } |
| 150 | $wfdb->query("delete from $p"."wfReverseCache where unix_timestamp() - lastUpdate > 86400"); |
| 151 | $count3 = $wfdb->querySingle("select count(*) as cnt from $p"."wfThrottleLog"); |
| 152 | if($count3 > 100000){ |
| 153 | $wfdb->query("truncate table $p"."wfThrottleLog"); //in case of DoS |
| 154 | } else if($count3 > $maxRows){ |
| 155 | $wfdb->query("delete from $p"."wfThrottleLog order by endTime asc limit %d", ($count3 - $maxRows)); |
| 156 | } |
| 157 | $count4 = $wfdb->querySingle("select count(*) as cnt from $p"."wfStatus"); |
| 158 | if($count4 > 100000){ //max status events we keep. This determines how much gets emailed to us when users sends us a debug report. |
| 159 | $wfdb->query("delete from $p"."wfStatus where level != 10 order by ctime asc limit %d", ($count4 - 100000)); |
| 160 | $count5 = $wfdb->querySingle("select count(*) as cnt from $p"."wfStatus where level=10"); |
| 161 | if($count5 > 100){ |
| 162 | $wfdb->query("delete from $p"."wfStatus where level = 10 order by ctime asc limit %d", ($count5 - 100) ); |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | } |
| 167 | public static function install_actions(){ |
| 168 | if(defined('MULTISITE') && MULTISITE === true){ |
| 169 | global $blog_id; |
| 170 | if($blog_id == 1 && get_option('wordfenceActivated') != 1){ return; } //Because the plugin is active once installed, even before it's network activated, for site 1 (WordPress team, why?!) |
| 171 | } |
| 172 | |
| 173 | //Upgrading from 2.0.3 we changed isPaid from 'free' or 'paid' to true and false |
| 174 | if(wfConfig::get('isPaid') == 'free'){ |
| 175 | wfConfig::set('isPaid', ''); |
| 176 | } |
| 177 | //end |
| 178 | |
| 179 | add_action('wordfence_daily_cron', 'wordfence::dailyCron'); |
| 180 | add_action('wordfence_hourly_cron', 'wordfence::hourlyCron'); |
| 181 | add_action('plugins_loaded', 'wordfence::veryFirstAction'); |
| 182 | add_action('wordfence_scheduled_scan','wordfence::startScan'); |
| 183 | add_action('init', 'wordfence::initAction'); |
| 184 | add_action('template_redirect', 'wordfence::templateRedir'); |
| 185 | add_action('shutdown', 'wordfence::shutdownAction'); |
| 186 | add_action('wp_authenticate','wordfence::authAction'); |
| 187 | add_action('login_init','wordfence::loginInitAction'); |
| 188 | add_action('wp_login','wordfence::loginAction'); |
| 189 | add_action('wp_logout','wordfence::logoutAction'); |
| 190 | add_action('profile_update', 'wordfence::profileUpdateAction', '99', 2); |
| 191 | add_action('lostpassword_post', 'wordfence::lostPasswordPost', '1'); |
| 192 | add_filter('pre_comment_approved', 'wordfence::preCommentApprovedFilter', '99', 2); |
| 193 | add_filter('authenticate', 'wordfence::authenticateFilter', 99, 3); |
| 194 | //html|xhtml|atom|rss2|rdf|comment|export |
| 195 | add_filter('get_the_generator_html', 'wordfence::genFilter', 99, 2); |
| 196 | add_filter('get_the_generator_xhtml', 'wordfence::genFilter', 99, 2); |
| 197 | add_filter('get_the_generator_atom', 'wordfence::genFilter', 99, 2); |
| 198 | add_filter('get_the_generator_rss2', 'wordfence::genFilter', 99, 2); |
| 199 | add_filter('get_the_generator_rdf', 'wordfence::genFilter', 99, 2); |
| 200 | add_filter('get_the_generator_comment', 'wordfence::genFilter', 99, 2); |
| 201 | add_filter('get_the_generator_export', 'wordfence::genFilter', 99, 2); |
| 202 | if(is_admin()){ |
| 203 | add_action('admin_init', 'wordfence::admin_init'); |
| 204 | if(is_multisite()){ |
| 205 | if(wfUtils::isAdminPageMU()){ |
| 206 | add_action('network_admin_menu', 'wordfence::admin_menus'); |
| 207 | } //else don't show menu |
| 208 | } else { |
| 209 | add_action('admin_menu', 'wordfence::admin_menus'); |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | public static function ajaxReceiver(){ |
| 214 | if(! self::isAdmin()){ |
| 215 | die(json_encode(array('errorMsg' => "You appear to have logged out or you are not an admin. Please sign-out and sign-in again."))); |
| 216 | } |
| 217 | $func = $_POST['action']; |
| 218 | $nonce = $_POST['nonce']; |
| 219 | if(! wp_verify_nonce($nonce, 'wp-ajax')){ |
| 220 | die(json_encode(array('errorMsg' => "Your browser sent an invalid security token to Wordfence. Please try reloading this page or signing out and in again."))); |
| 221 | } |
| 222 | //func is e.g. wordfence_ticker so need to munge it |
| 223 | $func = str_replace('wordfence_', '', $func); |
| 224 | $returnArr = call_user_func('wordfence::ajax_' . $func . '_callback'); |
| 225 | if($returnArr === false){ |
| 226 | $returnArr = array('errorMsg' => "Wordfence encountered an internal error executing that request."); |
| 227 | } |
| 228 | |
| 229 | if(! is_array($returnArr)){ |
| 230 | error_log("Function $func did not return an array and did not generate an error."); |
| 231 | $returnArr = array(); |
| 232 | } |
| 233 | if(isset($returnARr['nonce'])){ |
| 234 | error_log("Wordfence ajax function return an array with 'nonce' already set. This could be a bug."); |
| 235 | } |
| 236 | $returnArr['nonce'] = wp_create_nonce('wp-ajax'); |
| 237 | die(json_encode($returnArr)); |
| 238 | } |
| 239 | public static function lostPasswordPost(){ |
| 240 | if(self::isLockedOut(wfUtils::getIP())){ |
| 241 | require('wfLockedOut.php'); |
| 242 | } |
| 243 | $email = $_POST['user_login']; |
| 244 | if(empty($email)){ return; } |
| 245 | $user = get_user_by('email', $_POST['user_login']); |
| 246 | if($user){ |
| 247 | if(wfConfig::get('alertOn_lostPasswdForm')){ |
| 248 | wordfence::alert("Password recovery attempted", "Someone tried to recover the password for user with email address: $email\nTheir IP address was: " . wfUtils::getIP() . "\nTheir hostname was: " . self::getLog()->reverseLookup(wfUtils::getIP())); |
| 249 | } |
| 250 | } |
| 251 | if(wfConfig::get('loginSecurityEnabled')){ |
| 252 | $tKey = 'wffgt_' . wfUtils::inet_aton(wfUtils::getIP()); |
| 253 | $forgotAttempts = get_transient($tKey); |
| 254 | if($forgotAttempts){ |
| 255 | $forgotAttempts++; |
| 256 | } else { |
| 257 | $forgotAttempts = 1; |
| 258 | } |
| 259 | if($forgotAttempts >= wfConfig::get('loginSec_maxForgotPasswd')){ |
| 260 | self::lockOutIP(wfUtils::getIP(), "Exceeded the maximum number of tries to recover their password which is set at: " . wfConfig::get('loginSec_maxForgotPasswd')); |
| 261 | require('wfLockedOut.php'); |
| 262 | } |
| 263 | set_transient($tKey, $forgotAttempts, wfConfig::get('loginSec_countFailMins') * 60); |
| 264 | } |
| 265 | } |
| 266 | public static function lockOutIP($IP, $reason){ |
| 267 | if(wfConfig::get('alertOn_loginLockout')){ |
| 268 | wordfence::alert("User locked out from signing in", "A user with IP address $IP has been locked out from the signing in or using the password recovery form for the following reason: $reason"); |
| 269 | } |
| 270 | self::getLog()->lockOutIP(wfUtils::getIP(), $reason); |
| 271 | } |
| 272 | public static function isLockedOut($IP){ |
| 273 | return self::getLog()->isIPLockedOut($IP); |
| 274 | } |
| 275 | public static function veryFirstAction(){ |
| 276 | $wfFunc = $_GET['_wfsf']; |
| 277 | if($wfFunc == 'unlockEmail'){ |
| 278 | $email = trim($_POST['email']); |
| 279 | global $wpdb; |
| 280 | $ws = $wpdb->get_results("SELECT ID, user_login FROM $wpdb->users"); |
| 281 | $users = array(); |
| 282 | foreach($ws as $user){ |
| 283 | $userDat = get_userdata($user->ID); |
| 284 | if($userDat->user_level > 7){ |
| 285 | if($email == $userDat->user_email){ |
| 286 | $found = true; |
| 287 | break; |
| 288 | } |
| 289 | } |
| 290 | } |
| 291 | if(! $found){ |
| 292 | foreach(wfConfig::getAlertEmails() as $alertEmail){ |
| 293 | if($alertEmail == $email){ |
| 294 | $found = true; |
| 295 | break; |
| 296 | } |
| 297 | } |
| 298 | } |
| 299 | if($found){ |
| 300 | $key = wfUtils::bigRandomHex(); |
| 301 | $IP = wfUtils::getIP(); |
| 302 | set_transient('wfunlock_' . $key, $IP, 1800); |
| 303 | $content = wfUtils::tmpl('email_unlockRequest.php', array( |
| 304 | 'siteName' => get_bloginfo('name', 'raw'), |
| 305 | 'siteURL' => wfUtils::getSiteBaseURL(), |
| 306 | 'unlockHref' => wfUtils::getSiteBaseURL() . '?_wfsf=unlockAccess&key=' . $key, |
| 307 | 'key' => $key, |
| 308 | 'IP' => $IP |
| 309 | )); |
| 310 | wp_mail($email, "Unlock email requested", $content, "Content-Type: text/html"); |
| 311 | } |
| 312 | echo "<html><body><h1>Your request was received</h1><p>We received a request to email \"$email\" instructions to unlock their access. If that is the email address of a site administrator or someone on the Wordfence alert list, then they have been emailed instructions on how to regain access to this sytem. The instructions we sent will expire 30 minutes from now.</body></html>"; |
| 313 | exit(); |
| 314 | } else if($wfFunc == 'unlockAccess'){ |
| 315 | if(! preg_match('/^\d+\.\d+\.\d+\.\d+$/', get_transient('wfunlock_' . $_GET['key']))){ |
| 316 | echo "Invalid key provided for authentication."; |
| 317 | exit(); |
| 318 | } |
| 319 | /* You can enable this for paranoid security leve. |
| 320 | if(get_transient('wfunlock_' . $_GET['key']) != wfUtils::getIP()){ |
| 321 | echo "You can only use this link from the IP address you used to generate the unlock email."; |
| 322 | exit(); |
| 323 | } |
| 324 | */ |
| 325 | $wfLog = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion()); |
| 326 | if($_GET['func'] == 'unlockMyIP'){ |
| 327 | $wfLog->unblockIP(wfUtils::getIP()); |
| 328 | $wfLog->unlockOutIP(wfUtils::getIP()); |
| 329 | header('Location: ' . wp_login_url()); |
| 330 | exit(); |
| 331 | } else if($_GET['func'] == 'unlockAllIPs'){ |
| 332 | $wfLog->unblockAllIPs(); |
| 333 | $wfLog->unlockAllIPs(); |
| 334 | header('Location: ' . wp_login_url()); |
| 335 | exit(); |
| 336 | } else if($_GET['func'] == 'disableRules'){ |
| 337 | wfConfig::set('firewallEnabled', 0); |
| 338 | wfConfig::set('loginSecurityEnabled', 0); |
| 339 | $wfLog->unblockAllIPs(); |
| 340 | $wfLog->unlockAllIPs(); |
| 341 | header('Location: ' . wp_login_url()); |
| 342 | exit(); |
| 343 | } else { |
| 344 | echo "Invalid function specified. Please check the link we emailed you and make sure it was not cut-off by your email reader."; |
| 345 | exit(); |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | $wfLog = self::getLog(); |
| 350 | $wfLog->firewallBadIPs(); |
| 351 | } |
| 352 | public static function loginAction($username){ |
| 353 | if(sizeof($_POST) < 1){ return; } //only execute if login form is posted |
| 354 | if(! $username){ return; } |
| 355 | $user = get_user_by('login', $username); |
| 356 | $userID = $user ? $user->ID : 0; |
| 357 | self::getLog()->logLogin('loginOK', 0, $username); |
| 358 | if(user_can($userID, 'update_core')){ |
| 359 | if(wfConfig::get('alertOn_adminLogin')){ |
| 360 | wordfence::alert("Admin Login", "A user with username \"$username\" who has administrator access signed in to your WordPress site."); |
| 361 | } |
| 362 | } else { |
| 363 | if(wfConfig::get('alertOn_nonAdminLogin')){ |
| 364 | wordfence::alert("User login", "A non-admin user with username \"$username\" signed in to your WordPress site."); |
| 365 | } |
| 366 | } |
| 367 | } |
| 368 | public static function authenticateFilter($authResult){ |
| 369 | if(wfConfig::get('loginSecurityEnabled')){ |
| 370 | if(is_wp_error($authResult) && $authResult->get_error_code() == 'invalid_username' && wfConfig::get('loginSec_lockInvalidUsers')){ |
| 371 | self::lockOutIP(wfUtils::getIP(), "Used an invalid username to try to sign in."); |
| 372 | require('wfLockedOut.php'); |
| 373 | } |
| 374 | $tKey = 'wflginfl_' . wfUtils::inet_aton(wfUtils::getIP()); |
| 375 | if(is_wp_error($authResult) && ($authResult->get_error_code() == 'invalid_username' || $authResult->get_error_code() == 'incorrect_password') ){ |
| 376 | $tries = get_transient($tKey); |
| 377 | if($tries){ |
| 378 | $tries++; |
| 379 | } else { |
| 380 | $tries = 1; |
| 381 | } |
| 382 | if($tries >= wfConfig::get('loginSec_maxFailures')){ |
| 383 | self::lockOutIP(wfUtils::getIP(), "Exceeded the maximum number of login failures which is: " . wfConfig::get('loginSec_maxFailures')); |
| 384 | require('wfLockedOut.php'); |
| 385 | } |
| 386 | set_transient($tKey, $tries, wfConfig::get('loginSec_countFailMins') * 60); |
| 387 | } else if(get_class($authResult) == 'WP_User'){ |
| 388 | delete_transient($tKey); //reset counter on success |
| 389 | } |
| 390 | } |
| 391 | if(is_wp_error($authResult) && ($authResult->get_error_code() == 'invalid_username' || $authResult->get_error_code() == 'incorrect_password') && wfConfig::get('loginSec_maskLoginErrors')){ |
| 392 | return new WP_Error( 'incorrect_password', sprintf( __( '<strong>ERROR</strong>: The username or password you entered is incorrect. <a href="%2$s" title="Password Lost and Found">Lost your password</a>?' ), $username, wp_lostpassword_url() ) ); |
| 393 | } |
| 394 | return $authResult; |
| 395 | } |
| 396 | public static function logoutAction(){ |
| 397 | $userID = get_current_user_id(); |
| 398 | $userDat = get_user_by('id', $userID); |
| 399 | self::getLog()->logLogin('logout', 0, $userDat->user_login); |
| 400 | } |
| 401 | public static function loginInitAction(){ |
| 402 | if(self::isLockedOut(wfUtils::getIP())){ |
| 403 | require('wfLockedOut.php'); |
| 404 | } |
| 405 | } |
| 406 | public static function authAction($username){ |
| 407 | if(self::isLockedOut(wfUtils::getIP())){ |
| 408 | require('wfLockedOut.php'); |
| 409 | } |
| 410 | if(! $username){ return; } |
| 411 | $userDat = get_user_by('login', $username); |
| 412 | if($userDat){ |
| 413 | require_once( ABSPATH . 'wp-includes/class-phpass.php'); |
| 414 | $hasher = new PasswordHash(8, TRUE); |
| 415 | if(! $hasher->CheckPassword($_POST['pwd'], $userDat->user_pass)){ |
| 416 | self::getLog()->logLogin('loginFailValidUsername', 1, $username); |
| 417 | } |
| 418 | } else { |
| 419 | self::getLog()->logLogin('loginFailInvalidUsername', 1, $username); |
| 420 | } |
| 421 | } |
| 422 | public static function getWPFileContent($file, $cType, $cName, $cVersion){ |
| 423 | if($cType == 'plugin'){ |
| 424 | $file = realpath(ABSPATH . $file); |
| 425 | $file = substr($file, strlen(realpath(dirname(__FILE__) . '/../../'))); |
| 426 | $file = preg_replace('/^\/[^\/]+\//', '', $file); |
| 427 | } else if($cType == 'theme'){ |
| 428 | $themeDir = substr(WP_CONTENT_DIR, strlen(ABSPATH)) . get_theme_roots(); |
| 429 | $file = preg_replace('#' . $themeDir . '/[^/]+/#', '', $file); |
| 430 | } else if($cType == 'core'){ |
| 431 | |
| 432 | } else { |
| 433 | return array('errorMsg' => "An invalid type was specified to get file."); |
| 434 | } |
| 435 | |
| 436 | $transKey = 'wf_wpFileContent_' . $file . '_' . $cType . '_' . $cName . '_' . $cVersion; |
| 437 | $transKey = preg_replace('/[^a-zA-Z0-9\_]+/', '_', $transKey); |
| 438 | $content = get_site_transient($transKey); |
| 439 | if($content){ |
| 440 | return array('fileContent' => $content); |
| 441 | } |
| 442 | $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion()); |
| 443 | $dat = $api->call('get_wp_file_content', array( |
| 444 | 'file' => $file, |
| 445 | 'cType' => $cType, |
| 446 | 'cName' => $cName, |
| 447 | 'cVersion' => $cVersion |
| 448 | )); |
| 449 | if($api->errorMsg){ |
| 450 | return array('errorMsg' => $api->errorMsg); |
| 451 | } |
| 452 | if($dat['contents']){ |
| 453 | set_site_transient($transKey, $dat['contents'], WORDFENCE_TRANSIENTS_TIMEOUT); |
| 454 | return array('fileContent' => $dat['contents']); |
| 455 | } else { |
| 456 | return array('errorMsg' => "We could not fetch a core WordPress file from the Wordfence API."); |
| 457 | } |
| 458 | } |
| 459 | public static function ajax_sendActivityLog_callback(){ |
| 460 | $content = "SITE: " . site_url() . "\nPLUGIN VERSION: " . wfUtils::myVersion() . "\nWP VERSION: " . wfUtils::getWPVersion() . "\nAPI KEY: " . wfConfig::get('apiKey') . "\nADMIN EMAIL: " . get_option('admin_email') . "\nLOG:\n\n"; |
| 461 | $wfdb = new wfDB(); |
| 462 | global $wpdb; |
| 463 | $p = $wpdb->base_prefix; |
| 464 | $q = $wfdb->query("select ctime, level, type, msg from $p"."wfStatus order by ctime desc limit 10000"); |
| 465 | while($r = mysql_fetch_assoc($q)){ |
| 466 | if($r['type'] == 'error'){ |
| 467 | $content .= "\n"; |
| 468 | } |
| 469 | $content .= date(DATE_RFC822, $r['ctime']) . '::' . sprintf('%.4f', $r['ctime']) . ':' . $r['level'] . ':' . $r['type'] . '::' . $r['msg'] . "\n"; |
| 470 | } |
| 471 | $content .= "\n\n"; |
| 472 | |
| 473 | ob_start(); |
| 474 | phpinfo(); |
| 475 | $phpinfo = ob_get_contents(); |
| 476 | ob_get_clean(); |
| 477 | |
| 478 | $content .= $phpinfo; |
| 479 | |
| 480 | wp_mail($_POST['email'], "Wordfence Activity Log", $content); |
| 481 | return array('ok' => 1); |
| 482 | } |
| 483 | public static function ajax_saveConfig_callback(){ |
| 484 | $opts = wfConfig::parseOptions(); |
| 485 | $emails = array(); |
| 486 | foreach(explode(',', preg_replace('/[\r\n\s\t]+/', '', $opts['alertEmails'])) as $email){ |
| 487 | if(strlen($email) > 0){ |
| 488 | array_push($emails, $email); |
| 489 | } |
| 490 | } |
| 491 | if(sizeof($emails) > 0){ |
| 492 | $badEmails = array(); |
| 493 | foreach($emails as $email){ |
| 494 | if(! preg_match('/^[^@]+@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,8})$/i', $email)){ |
| 495 | array_push($badEmails, $email); |
| 496 | } |
| 497 | } |
| 498 | if(sizeof($badEmails) > 0){ |
| 499 | return array('errorMsg' => "The following emails are invalid: " . implode(', ', $badEmails)); |
| 500 | } |
| 501 | } |
| 502 | $opts['apiKey'] = trim($opts['apiKey']); |
| 503 | if(! preg_match('/^[a-fA-F0-9]+$/', $opts['apiKey'])){ |
| 504 | return array('errorMsg' => "Please enter a valid API key for Wordfence before saving your options."); |
| 505 | } |
| 506 | $validUsers = array(); |
| 507 | $invalidUsers = array(); |
| 508 | foreach(explode(',', preg_replace('/[\r\n\s\t]+/', '', $opts['liveTraf_ignoreUsers'])) as $val){ |
| 509 | if(strlen($val) > 0){ |
| 510 | if(get_user_by('login', $val)){ |
| 511 | array_push($validUsers, $val); |
| 512 | } else { |
| 513 | array_push($invalidUsers, $val); |
| 514 | } |
| 515 | } |
| 516 | } |
| 517 | if(sizeof($invalidUsers) > 0){ |
| 518 | return array('errorMsg' => "The following users you selected to ignore in live traffic reports are not valid on this system: " . implode(', ', $invalidUsers)); |
| 519 | } |
| 520 | if(sizeof($validUsers) > 0){ |
| 521 | $opts['liveTraf_ignoreUsers'] = implode(',', $validUsers); |
| 522 | } else { |
| 523 | $opts['liveTraf_ignoreUsers'] = ''; |
| 524 | } |
| 525 | |
| 526 | $validIPs = array(); |
| 527 | $invalidIPs = array(); |
| 528 | foreach(explode(',', preg_replace('/[\r\n\s\t]+/', '', $opts['liveTraf_ignoreIPs'])) as $val){ |
| 529 | if(strlen($val) > 0){ |
| 530 | if(preg_match('/^\d+\.\d+\.\d+\.\d+$/', $val)){ |
| 531 | array_push($validIPs, $val); |
| 532 | } else { |
| 533 | array_push($invalidIPs, $val); |
| 534 | } |
| 535 | } |
| 536 | } |
| 537 | if(sizeof($invalidIPs) > 0){ |
| 538 | return array('errorMsg' => "The following IPs you selected to ignore in live traffic reports are not valid: " . implode(', ', $invalidIPs)); |
| 539 | } |
| 540 | if(sizeof($validIPs) > 0){ |
| 541 | $opts['liveTraf_ignoreIPs'] = implode(',', $validIPs); |
| 542 | } |
| 543 | $reload = ''; |
| 544 | $paidKeyMsg = false; |
| 545 | if($opts['apiKey'] != wfConfig::get('apiKey')){ |
| 546 | $api = new wfAPI($opts['apiKey'], wfUtils::getWPVersion()); |
| 547 | $res = $api->call('check_api_key', array(), array()); |
| 548 | if($res['ok'] && isset($res['isPaid'])){ |
| 549 | wfConfig::set('apiKey', $opts['apiKey']); |
| 550 | $reload = 'reload'; |
| 551 | wfConfig::set('isPaid', $res['isPaid']); |
| 552 | if($res['isPaid']){ |
| 553 | $paidKeyMsg = true; |
| 554 | } |
| 555 | } else if($res['errorMsg']){ |
| 556 | return array('errorMsg' => $res['errorMsg']); |
| 557 | } else { |
| 558 | return array('errorMsg' => "We could not change your API key. Please try again in a few minutes."); |
| 559 | } |
| 560 | } |
| 561 | |
| 562 | if(preg_match('/[a-zA-Z0-9\d]+/', $opts['liveTraf_ignoreUA'])){ |
| 563 | $opts['liveTraf_ignoreUA'] = trim($opts['liveTraf_ignoreUA']); |
| 564 | } else { |
| 565 | $opts['liveTraf_ignoreUA'] = ''; |
| 566 | } |
| 567 | if(! $opts['other_WFNet']){ |
| 568 | $wfdb = new wfDB(); |
| 569 | global $wpdb; |
| 570 | $p = $wpdb->base_prefix; |
| 571 | $wfdb->query("delete from $p"."wfBlocks where wfsn=1"); |
| 572 | } |
| 573 | foreach($opts as $key => $val){ |
| 574 | wfConfig::set($key, $val); |
| 575 | } |
| 576 | |
| 577 | //Clears next scan if scans are disabled. Schedules next scan if enabled. |
| 578 | $err = self::scheduleNextScan(); |
| 579 | if($err){ |
| 580 | return array('errorMsg' => $err); |
| 581 | } else { |
| 582 | return array('ok' => 1, 'reload' => $reload, 'paidKeyMsg' => $paidKeyMsg ); |
| 583 | } |
| 584 | } |
| 585 | public static function ajax_clearAllBlocked_callback(){ |
| 586 | $op = $_POST['op']; |
| 587 | $wfLog = self::getLog(); |
| 588 | if($op == 'blocked'){ |
| 589 | $wfLog->unblockAllIPs(); |
| 590 | } else if($op == 'locked'){ |
| 591 | $wfLog->unlockAllIPs(); |
| 592 | } |
| 593 | return array('ok' => 1); |
| 594 | } |
| 595 | public static function ajax_unlockOutIP_callback(){ |
| 596 | $IP = $_POST['IP']; |
| 597 | self::getLog()->unlockOutIP($IP); |
| 598 | return array('ok' => 1); |
| 599 | } |
| 600 | public static function ajax_unblockIP_callback(){ |
| 601 | $IP = $_POST['IP']; |
| 602 | self::getLog()->unblockIP($IP); |
| 603 | return array('ok' => 1); |
| 604 | } |
| 605 | public static function ajax_loadStaticPanel_callback(){ |
| 606 | $mode = $_POST['mode']; |
| 607 | $wfLog = self::getLog(); |
| 608 | if($mode == 'topScanners' || $mode == 'topLeechers'){ |
| 609 | $results = $wfLog->getLeechers($mode); |
| 610 | } else if($mode == 'blockedIPs'){ |
| 611 | $results = $wfLog->getBlockedIPs(); |
| 612 | } else if($mode == 'lockedOutIPs'){ |
| 613 | $results = $wfLog->getLockedOutIPs(); |
| 614 | } else if($mode == 'throttledIPs'){ |
| 615 | $results = $wfLog->getThrottledIPs(); |
| 616 | } |
| 617 | return array('ok' => 1, 'results' => $results); |
| 618 | } |
| 619 | public static function ajax_blockIP_callback(){ |
| 620 | $IP = $_POST['IP']; |
| 621 | if($IP == wfUtils::getIP()){ |
| 622 | return array('err' => 1, 'errorMsg' => "You can't block your own IP address."); |
| 623 | } |
| 624 | if(wfConfig::get('neverBlockBG') != 'treatAsOtherCrawlers'){ //Either neverBlockVerified or neverBlockUA is selected which means the user doesn't want to block google |
| 625 | if(wfCrawl::verifyCrawlerPTR('/googlebot\.com$/i', $IP)){ |
| 626 | return array('err' => 1, 'errorMsg' => "The IP address you're trying to block belongs to Google. Your options are currently set to not block these crawlers. Change this in Wordfence options if you want to manually block Google."); |
| 627 | } |
| 628 | } |
| 629 | self::getLog()->blockIP($IP, $_POST['reason']); |
| 630 | return array('ok' => 1); |
| 631 | } |
| 632 | public static function ajax_reverseLookup_callback(){ |
| 633 | $ips = explode(',', $_POST['ips']); |
| 634 | $res = array(); |
| 635 | foreach($ips as $ip){ |
| 636 | $res[$ip] = self::getLog()->reverseLookup($ip); |
| 637 | } |
| 638 | return array('ok' => 1, 'ips' => $res); |
| 639 | } |
| 640 | public static function ajax_deleteIssue_callback(){ |
| 641 | $wfIssues = new wfIssues(); |
| 642 | $issueID = $_POST['id']; |
| 643 | $wfIssues->deleteIssue($issueID); |
| 644 | return array('ok' => 1); |
| 645 | } |
| 646 | public static function ajax_updateAllIssues_callback(){ |
| 647 | $op = $_POST['op']; |
| 648 | $i = new wfIssues(); |
| 649 | if($op == 'deleteIgnored'){ |
| 650 | $i->deleteIgnored(); |
| 651 | } else if($op == 'deleteNew'){ |
| 652 | $i->deleteNew(); |
| 653 | } else if($op == 'ignoreAllNew'){ |
| 654 | $i->ignoreAllNew(); |
| 655 | } else { |
| 656 | return array('errorMsg' => "An invalid operation was called."); |
| 657 | } |
| 658 | return array('ok' => 1); |
| 659 | } |
| 660 | public static function ajax_updateIssueStatus_callback(){ |
| 661 | $wfIssues = new wfIssues(); |
| 662 | $status = $_POST['status']; |
| 663 | $issueID = $_POST['id']; |
| 664 | if(! preg_match('/^(?:new|delete|ignoreP|ignoreC)$/', $status)){ |
| 665 | return array('errorMsg' => "An invalid status was specified when trying to update that issue."); |
| 666 | } |
| 667 | $wfIssues->updateIssue($issueID, $status); |
| 668 | return array('ok' => 1); |
| 669 | } |
| 670 | public static function ajax_loadIssues_callback(){ |
| 671 | $i = new wfIssues(); |
| 672 | $iss = $i->getIssues(); |
| 673 | return array( |
| 674 | 'issuesLists' => $iss, |
| 675 | 'summary' => $i->getSummaryItems(), |
| 676 | 'lastScanCompleted' => wfConfig::get('lastScanCompleted') |
| 677 | ); |
| 678 | } |
| 679 | public static function ajax_ticker_callback(){ |
| 680 | $wfdb = new wfDB(); |
| 681 | global $wpdb; |
| 682 | $p = $wpdb->base_prefix; |
| 683 | |
| 684 | $serverTime = $wfdb->querySingle("select unix_timestamp()"); |
| 685 | $issues = new wfIssues(); |
| 686 | $jsonData = array( |
| 687 | 'serverTime' => $serverTime, |
| 688 | 'msg' => $wfdb->querySingle("select msg from $p"."wfStatus where level < 3 order by ctime desc limit 1") |
| 689 | ); |
| 690 | $events = array(); |
| 691 | $alsoGet = $_POST['alsoGet']; |
| 692 | if(preg_match('/^logList_(404|hit|human|crawler|gCrawler|loginLogout)$/', $alsoGet, $m)){ |
| 693 | $type = $m[1]; |
| 694 | $newestEventTime = $_POST['otherParams']; |
| 695 | $listType = 'hits'; |
| 696 | if($type == 'loginLogout'){ |
| 697 | $listType = 'logins'; |
| 698 | } |
| 699 | $events = self::getLog()->getHits($listType, $type, $newestEventTime); |
| 700 | } |
| 701 | $jsonData['events'] = $events; |
| 702 | $jsonData['alsoGet'] = $alsoGet; //send it back so we don't load data if panel has changed |
| 703 | return $jsonData; |
| 704 | } |
| 705 | public static function ajax_activityLogUpdate_callback(){ |
| 706 | $issues = new wfIssues(); |
| 707 | return array( |
| 708 | 'ok' => 1, |
| 709 | 'items' => self::getLog()->getStatusEvents($_POST['lastctime']), |
| 710 | 'currentScanID' => $issues->getScanTime() |
| 711 | ); |
| 712 | } |
| 713 | public static function ajax_deleteFile_callback(){ |
| 714 | $issueID = $_POST['issueID']; |
| 715 | $wfIssues = new wfIssues(); |
| 716 | $issue = $wfIssues->getIssueByID($issueID); |
| 717 | if(! $issue){ |
| 718 | return array('errorMsg' => "Could not delete file because we could not find that issue."); |
| 719 | } |
| 720 | if(! $issue['data']['file']){ |
| 721 | return array('errorMsg' => "Could not delete file because that issue does not appear to be a file related issue."); |
| 722 | } |
| 723 | $file = $issue['data']['file']; |
| 724 | $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $file); |
| 725 | $localFile = realpath($localFile); |
| 726 | if(strpos($localFile, ABSPATH) !== 0){ |
| 727 | return array('errorMsg' => "An invalid file was requested for deletion."); |
| 728 | } |
| 729 | $filesize = filesize($localFile); |
| 730 | if(@unlink($localFile)){ |
| 731 | $wfIssues->updateIssue($issueID, 'delete'); |
| 732 | return array( |
| 733 | 'ok' => 1, |
| 734 | 'localFile' => $localFile, |
| 735 | 'file' => $file, |
| 736 | 'filesize' => $filesize |
| 737 | ); |
| 738 | } else { |
| 739 | $err = error_get_last(); |
| 740 | return array('errorMsg' => "Could not delete file $file. The error was: " . $err['message']); |
| 741 | } |
| 742 | } |
| 743 | public static function ajax_restoreFile_callback(){ |
| 744 | $issueID = $_POST['issueID']; |
| 745 | $wfIssues = new wfIssues(); |
| 746 | $issue = $wfIssues->getIssueByID($issueID); |
| 747 | if(! $issue){ |
| 748 | return array('errorMsg' => "We could not find that issue in our database."); |
| 749 | } |
| 750 | $dat = $issue['data']; |
| 751 | $result = self::getWPFileContent($dat['file'], $dat['cType'], $dat['cName'], $dat['cVersion']); |
| 752 | $file = $dat['file']; |
| 753 | if($result['errorMsg']){ |
| 754 | return $result; |
| 755 | } else if(! $result['fileContent']){ |
| 756 | return array('errorMsg' => "We could not get the original file to do a repair."); |
| 757 | } |
| 758 | |
| 759 | if(preg_match('/\.\./', $file)){ |
| 760 | return array('errorMsg' => "An invalid file was specified for repair."); |
| 761 | } |
| 762 | $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $file); |
| 763 | $fh = fopen($localFile, 'w'); |
| 764 | if(! $fh){ |
| 765 | $err = error_get_last(); |
| 766 | return array('errorMsg' => "We could not write to that file. The error was: " . $err['message']); |
| 767 | } |
| 768 | flock($fh, LOCK_EX); |
| 769 | $bytes = fwrite($fh, $result['fileContent']); |
| 770 | flock($fh, LOCK_UN); |
| 771 | fclose($fh); |
| 772 | if($bytes < 1){ |
| 773 | return array('errorMsg' => "We could not write to that file. ($bytes bytes written) You may not have permission to modify files on your WordPress server."); |
| 774 | } |
| 775 | $wfIssues->updateIssue($issueID, 'delete'); |
| 776 | return array( |
| 777 | 'ok' => 1, |
| 778 | 'file' => $localFile |
| 779 | ); |
| 780 | } |
| 781 | public static function ajax_activate_callback(){ |
| 782 | $key = trim($_POST['key']); |
| 783 | $email = trim($_POST['email']); |
| 784 | $key = preg_replace('/[^a-fA-F0-9]+/', '', $key); |
| 785 | if(strlen($key) < 10){ |
| 786 | return array("errorAlert" => "You entered an invalid API key." ); |
| 787 | } |
| 788 | if(! preg_match('/.+\@.+/', $email)){ |
| 789 | return array("errorAlert" => "Please enter a valid email address where Wordfence can send alerts."); |
| 790 | } |
| 791 | |
| 792 | wfConfig::set('apiKey', $key); |
| 793 | wfConfig::set('alertEmails', $email); |
| 794 | $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion()); |
| 795 | $result = $api->call('activate', array(), array()); |
| 796 | if($api->errorMsg){ |
| 797 | wfConfig::set('apiKey', ''); |
| 798 | return array("errorMsg" => $api->errorMsg ); |
| 799 | } |
| 800 | if($result['ok'] && isset($result['isPaid'])){ |
| 801 | wfConfig::set('isPaid', $result['isPaid']); |
| 802 | $err = self::startScan(); |
| 803 | if($err){ |
| 804 | return array('errorMsg' => $err); |
| 805 | } else { |
| 806 | return array("ok" => 1); |
| 807 | } |
| 808 | } else { |
| 809 | return array('errorAlert' => "An unknown error occured trying to activate Wordfence. Please try again in a few minutes." ); |
| 810 | } |
| 811 | } |
| 812 | public static function ajax_scan_callback(){ |
| 813 | self::status(4, 'info', "Ajax request received to start scan."); |
| 814 | $err = self::startScan(); |
| 815 | if($err){ |
| 816 | return array('errorMsg' => $err); |
| 817 | } else { |
| 818 | return array("ok" => 1); |
| 819 | } |
| 820 | } |
| 821 | public static function startScan(){ |
| 822 | self::status(4, 'info', "Entering start scan routine"); |
| 823 | $cron_url = plugins_url('wordfence/wfscan.php'); |
| 824 | self::status(4, 'info', "Cron URL is: " . $cron_url); |
| 825 | $cronKey = wfUtils::bigRandomHex(); |
| 826 | self::status(4, 'info', "cronKey is: " . $cronKey); |
| 827 | wfConfig::set('currentCronKey', time() . ',' . $cronKey); |
| 828 | self::status(4, 'info', "cronKey is set"); |
| 829 | $result = wp_remote_post( $cron_url, array( |
| 830 | 'timeout' => 0.5, |
| 831 | 'blocking' => true, |
| 832 | 'sslverify' => false, |
| 833 | 'headers' => array( |
| 834 | 'x-wordfence-cronkey' => $cronKey |
| 835 | ) |
| 836 | ) ); |
| 837 | $procResp = self::processResponse($result); |
| 838 | if($procResp){ return $procResp; } |
| 839 | //If the currentCronKey was eaten, then cron executed so return |
| 840 | wfConfig::clearCache(); if(! wfConfig::get('currentCronKey')){ |
| 841 | self::status(4, 'info', "cronkey is empty so cron executed. Returning."); |
| 842 | return false; |
| 843 | } |
| 844 | |
| 845 | //This second request is for hosts that don't know their own name. i.e. they don't have example.com in their hosts file or DNS pointing to their own IP address or loopback address. So we throw a hail mary to loopback. |
| 846 | self::status(4, 'info', "cronkey is still set so sleeping for 0.2 seconds and checking again before trying another approach"); |
| 847 | usleep(200000); |
| 848 | wfConfig::clearCache(); |
| 849 | if(wfConfig::get('currentCronKey')){ //cron key is still set, so cron hasn't executed yet. Maybe the request didn't go through |
| 850 | self::status(4, 'info', "cronkey is still set so about to manually set host header and try again"); |
| 851 | $cron_url = preg_replace('/^(https?):\/\/[^\/]+/', '$1://127.0.0.1', $cron_url); |
| 852 | self::status(4, 'info', "cron url is: $cron_url"); |
| 853 | $siteURL = site_url(); |
| 854 | self::status(4, 'info', "siteURL is: $siteURL"); |
| 855 | if(preg_match('/^https?:\/\/([^\/]+)/i', site_url(), $matches)){ |
| 856 | $host = $matches[1]; |
| 857 | self::status(4, 'info', "Extracted host $host from siteURL and trying remote post with manual host header set."); |
| 858 | $result = wp_remote_post( $cron_url, array( |
| 859 | 'timeout' => 0.5, |
| 860 | 'blocking' => true, |
| 861 | 'sslverify' => false, |
| 862 | 'headers' => array( |
| 863 | 'x-wordfence-cronkey' => $cronKey, |
| 864 | 'Host' => $host |
| 865 | ) |
| 866 | ) ); |
| 867 | $procResp = self::processResponse($result); |
| 868 | if($procResp){ return $procResp; } |
| 869 | } |
| 870 | } |
| 871 | return false; |
| 872 | } |
| 873 | public function processResponse($result){ |
| 874 | if((! is_wp_error($result)) && is_array($result) && empty($result['body']) === false){ |
| 875 | if(strpos($result['body'], 'WFSOURCEVISIBLE') !== false){ |
| 876 | self::status(4, 'info', "wfscan.php source is visible."); |
| 877 | $msg = "Wordfence can't run because the source code of your WordPress plugin files is visible from the Internet. This is a serious security risk which you need to fix. Please look for .htaccess files in your WordPress root directory and your wp-content/ and wp-content/plugins/ directories that may contain malicious code designed to reveal your site source code to a hacker."; |
| 878 | $htfiles = array(); |
| 879 | if(file_exists(ABSPATH . 'wp-content/.htaccess')){ |
| 880 | array_push($htfiles, '<a href="' . wfUtils::getSiteBaseURL() . '?_wfsf=view&nonce=' . wp_create_nonce('wp-ajax') . '&file=wp-content/.htaccess" target="_blank">wp-content/.htaccess</a>'); |
| 881 | } |
| 882 | if(file_exists(ABSPATH . 'wp-content/plugins/.htaccess')){ |
| 883 | array_push($htfiles, '<a href="' . wfUtils::getSiteBaseURL() . '?_wfsf=view&nonce=' . wp_create_nonce('wp-ajax') . '&file=wp-content/plugins/.htaccess" target="_blank">wp-content/plugins/.htaccess</a>'); |
| 884 | } |
| 885 | if(sizeof($htfiles) > 0){ |
| 886 | $msg .= "<br /><br />Click to view the .htaccess files below that may be the cause of this problem:<br />" . implode('<br />', $htfiles); |
| 887 | } |
| 888 | return $msg; |
| 889 | |
| 890 | } else if(strpos($result['body'], '{') !== false && strpos($result['body'], 'errorMsg') !== false){ |
| 891 | self::status(4, 'info', "Got response from cron containing json"); |
| 892 | $resp = json_decode($result['body'], true); |
| 893 | if(empty($resp['errorMsg']) === false){ |
| 894 | self::status(4, 'info', "Got an error message from cron: " . $resp['errorMsg']); |
| 895 | return $resp['errorMsg']; |
| 896 | } |
| 897 | } |
| 898 | } |
| 899 | return false; |
| 900 | } |
| 901 | public static function templateRedir(){ |
| 902 | $wfFunc = get_query_var('_wfsf'); |
| 903 | $wfLog = self::getLog(); |
| 904 | if($wfLog->logHitOK()){ |
| 905 | if(is_404() ){ |
| 906 | $wfLog->logLeechAndBlock('404'); |
| 907 | } else { |
| 908 | $wfLog->logLeechAndBlock('hit'); |
| 909 | } |
| 910 | if(wfConfig::get('liveTrafficEnabled')){ |
| 911 | self::$hitID = $wfLog->logHit(); |
| 912 | add_action('wp_head', 'wordfence::wp_head'); |
| 913 | } |
| 914 | } |
| 915 | |
| 916 | if(! ($wfFunc == 'diff' || $wfFunc == 'view' || $wfFunc == 'sysinfo' || $wfFunc == 'IPTraf' || $wfFunc == 'viewActivityLog')){ |
| 917 | return; |
| 918 | } |
| 919 | if(! self::isAdmin()){ |
| 920 | return; |
| 921 | } |
| 922 | |
| 923 | $nonce = $_GET['nonce']; |
| 924 | if(! wp_verify_nonce($nonce, 'wp-ajax')){ |
| 925 | echo "Bad security token. Please sign out and sign-in again."; |
| 926 | exit(0); |
| 927 | } |
| 928 | if($wfFunc == 'diff'){ |
| 929 | self::wfFunc_diff(); |
| 930 | } else if($wfFunc == 'view'){ |
| 931 | self::wfFunc_view(); |
| 932 | } else if($wfFunc == 'sysinfo'){ |
| 933 | require('sysinfo.php'); |
| 934 | } else if($wfFunc == 'IPTraf'){ |
| 935 | self::wfFunc_IPTraf(); |
| 936 | } else if($wfFunc == 'viewActivityLog'){ |
| 937 | self::wfFunc_viewActivityLog(); |
| 938 | } |
| 939 | exit(0); |
| 940 | } |
| 941 | public static function wp_head(){ |
| 942 | echo '<script type="text/javascript">var wfHTImg = new Image(); wfHTImg.src="' . wfUtils::getBaseURL() . 'visitor.php?hid=' . wfUtils::encrypt(self::$hitID) . '";</script>'; |
| 943 | } |
| 944 | public static function shutdownAction(){ |
| 945 | } |
| 946 | public static function wfFunc_viewActivityLog(){ |
| 947 | require('viewFullActivityLog.php'); |
| 948 | exit(0); |
| 949 | } |
| 950 | public static function wfFunc_IPTraf(){ |
| 951 | $IP = $_GET['IP']; |
| 952 | $reverseLookup = self::getLog()->reverseLookup($IP); |
| 953 | if(! preg_match('/^\d+\.\d+\.\d+\.\d+$/', $IP)){ |
| 954 | echo "An invalid IP address was specified."; |
| 955 | exit(0); |
| 956 | } |
| 957 | $wfLog = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion()); |
| 958 | $results = array_merge( |
| 959 | $wfLog->getHits('hits', 'hit', 0, 10000, $IP), |
| 960 | $wfLog->getHits('hits', '404', 0, 10000, $IP) |
| 961 | ); |
| 962 | usort($results, 'wordfence::iptrafsort'); |
| 963 | for($i = 0; $i < sizeof($results); $i++){ |
| 964 | if(array_key_exists($i + 1, $results)){ |
| 965 | $results[$i]['timeSinceLastHit'] = sprintf('%.4f', $results[$i]['ctime'] - $results[$i + 1]['ctime']); |
| 966 | } else { |
| 967 | $results[$i]['timeSinceLastHit'] = ''; |
| 968 | } |
| 969 | } |
| 970 | require('IPTraf.php'); |
| 971 | exit(0); |
| 972 | } |
| 973 | public static function iptrafsort($b, $a){ |
| 974 | if($a['ctime'] == $b['ctime']){ return 0; } |
| 975 | return ($a['ctime'] < $b['ctime']) ? -1 : 1; |
| 976 | } |
| 977 | public static function wfFunc_view(){ |
| 978 | $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $_GET['file']); |
| 979 | if(strpos($localFile, '..') !== false){ |
| 980 | echo "Invalid file requested. (Relative paths not allowed)"; |
| 981 | exit(); |
| 982 | } |
| 983 | $lang = false; |
| 984 | $cont = @file_get_contents($localFile); |
| 985 | $isEmpty = false; |
| 986 | if(! $cont){ |
| 987 | if(file_exists($localFile) && filesize($localFile) === 0){ |
| 988 | $isEmpty = true; |
| 989 | } else { |
| 990 | $err = error_get_last(); |
| 991 | echo "We could not open the requested file for reading. The error was: " . $err['message']; |
| 992 | exit(0); |
| 993 | } |
| 994 | } |
| 995 | $fileMTime = @filemtime($localFile); |
| 996 | $fileMTime = date('l jS \of F Y h:i:s A', $fileMTime); |
| 997 | $fileSize = @filesize($localFile); |
| 998 | $fileSize = number_format($fileSize, 0, '', ',') . ' bytes'; |
| 999 | |
| 1000 | require 'wfViewResult.php'; |
| 1001 | exit(0); |
| 1002 | } |
| 1003 | public static function wfFunc_diff(){ |
| 1004 | $result = self::getWPFileContent($_GET['file'], $_GET['cType'], $_GET['cName'], $_GET['cVersion']); |
| 1005 | if($result['errorMsg']){ |
| 1006 | echo $result['errorMsg']; |
| 1007 | exit(0); |
| 1008 | } else if(! $result['fileContent']){ |
| 1009 | echo "We could not get the contents of the original file to do a comparison."; |
| 1010 | exit(0); |
| 1011 | } |
| 1012 | |
| 1013 | $localFile = realpath(ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $_GET['file'])); |
| 1014 | if(strpos($localFile, ABSPATH) !== 0){ |
| 1015 | echo "An invalid file was requested for comparison."; |
| 1016 | exit(0); |
| 1017 | } |
| 1018 | $diffOptions = array(); |
| 1019 | $localContents = file_get_contents($localFile); |
| 1020 | |
| 1021 | $diff = new Diff( |
| 1022 | //Treat DOS and Unix files the same |
| 1023 | preg_split("/(?:\r\n|\n)/", $result['fileContent']), |
| 1024 | preg_split("/(?:\r\n|\n)/", $localContents), |
| 1025 | array() |
| 1026 | ); |
| 1027 | $renderer = new Diff_Renderer_Html_SideBySide; |
| 1028 | $diffResult = $diff->Render($renderer); |
| 1029 | require 'diffResult.php'; |
| 1030 | exit(0); |
| 1031 | } |
| 1032 | public static function initAction(){ |
| 1033 | global $wp; |
| 1034 | $wp->add_query_var('_wfsf'); |
| 1035 | //add_rewrite_rule('wfStaticFunc/([a-zA-Z0-9]+)/?$', 'index.php?wfStaticFunc=' . $matches[1], 'top'); |
| 1036 | $cookieName = 'wfvt_' . crc32(site_url()); |
| 1037 | $c = isset($_COOKIES[$cookieName]) ? isset($_COOKIES[$cookieName]) : false; |
| 1038 | if($c){ |
| 1039 | self::$newVisit = false; |
| 1040 | } else { |
| 1041 | self::$newVisit = true; |
| 1042 | } |
| 1043 | setcookie($cookieName, uniqid(), time() + 1800, '/'); |
| 1044 | } |
| 1045 | public static function admin_init(){ |
| 1046 | if(! self::isAdmin()){ return; } |
| 1047 | |
| 1048 | foreach(array('activate', 'scan', 'sendActivityLog', 'restoreFile', 'deleteFile', 'removeExclusion', 'activityLogUpdate', 'ticker', 'loadIssues', 'updateIssueStatus', 'deleteIssue', 'updateAllIssues', 'reverseLookup', 'unlockOutIP', 'unblockIP', 'blockIP', 'loadStaticPanel', 'saveConfig', 'clearAllBlocked') as $func){ |
| 1049 | add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver'); |
| 1050 | } |
| 1051 | wp_enqueue_style('wordfence-main-style', WP_PLUGIN_URL . '/wordfence/css/main.css', '', wfUtils::myVersion()); |
| 1052 | wp_enqueue_style('wordfence-colorbox-style', WP_PLUGIN_URL . '/wordfence/css/colorbox.css', '', wfUtils::myVersion()); |
| 1053 | wp_enqueue_style('wordfence-dttable-style', WP_PLUGIN_URL . '/wordfence/css/dt_table.css', '', wfUtils::myVersion()); |
| 1054 | |
| 1055 | wp_enqueue_script('json2'); |
| 1056 | wp_enqueue_script('jquery.tmpl', wfUtils::getBaseURL() . 'js/jquery.tmpl.min.js', array('jquery'), wfUtils::myVersion()); |
| 1057 | wp_enqueue_script('jquery.colorbox', wfUtils::getBaseURL() . 'js/jquery.colorbox-min.js', array('jquery'), wfUtils::myVersion()); |
| 1058 | wp_enqueue_script('jquery.dataTables', wfUtils::getBaseURL() . 'js/jquery.dataTables.min.js', array('jquery'), wfUtils::myVersion()); |
| 1059 | //wp_enqueue_script('jquery.tools', wfUtils::getBaseURL() . 'js/jquery.tools.min.js', array('jquery')); |
| 1060 | wp_enqueue_script('wordfenceAdminjs', wfUtils::getBaseURL() . 'js/admin.js', array('jquery'), wfUtils::myVersion()); |
| 1061 | wp_localize_script('wordfenceAdminjs', 'WordfenceAdminVars', array( |
| 1062 | 'ajaxURL' => admin_url('admin-ajax.php'), |
| 1063 | 'firstNonce' => wp_create_nonce('wp-ajax'), |
| 1064 | 'siteBaseURL' => wfUtils::getSiteBaseURL(), |
| 1065 | 'debugOn' => wfConfig::get('debugOn', 0) |
| 1066 | )); |
| 1067 | |
| 1068 | } |
| 1069 | public static function configure_warning(){ |
| 1070 | if(! preg_match('/WordfenceSecOpt/', $_SERVER['REQUEST_URI'])){ |
| 1071 | $numRun = wfConfig::get('alertEmailMsgCount', 0); |
| 1072 | if($numRun <= 3){ |
| 1073 | echo '<div id="wordfenceConfigWarning" class="updated fade"><p><strong>Please set up an email address to receive Wordfence security alerts</strong> on the <a href="admin.php?page=WordfenceSecOpt">Wordfence Options Page</a>. This message will appear ' . (3 - $numRun) . ' more times.</p></div>'; |
| 1074 | wfConfig::set('alertEmailMsgCount', ++$numRun); |
| 1075 | } |
| 1076 | |
| 1077 | } |
| 1078 | } |
| 1079 | public static function admin_menus(){ |
| 1080 | if(! self::isAdmin()){ return; } |
| 1081 | if(! wfConfig::get('alertEmails')){ |
| 1082 | if(wfUtils::isAdminPageMU()){ |
| 1083 | add_action('network_admin_notices', 'wordfence::configure_warning'); |
| 1084 | } else { |
| 1085 | add_action('admin_notices', 'wordfence::configure_warning'); |
| 1086 | } |
| 1087 | } |
| 1088 | add_submenu_page("Wordfence", "Scan", "Scan", "activate_plugins", "Wordfence", 'wordfence::menu_scan'); |
| 1089 | add_menu_page('Wordfence', 'Wordfence', 'activate_plugins', 'Wordfence', 'wordfence::menu_scan', WP_PLUGIN_URL . '/wordfence/images/wordfence-logo-16x16.png', 'div'); |
| 1090 | if(wfConfig::get('liveTrafficEnabled')){ |
| 1091 | add_submenu_page("Wordfence", "Live Traffic", "Live Traffic", "activate_plugins", "WordfenceActivity", 'wordfence::menu_activity'); |
| 1092 | } |
| 1093 | add_submenu_page('Wordfence', 'Blocked IPs', 'Blocked IPs', 'activate_plugins', 'WordfenceBlockedIPs', 'wordfence::menu_blockedIPs'); |
| 1094 | add_submenu_page("Wordfence", "Options", "Options", "activate_plugins", "WordfenceSecOpt", 'wordfence::menu_options'); |
| 1095 | } |
| 1096 | public static function menu_options(){ |
| 1097 | require 'menu_options.php'; |
| 1098 | } |
| 1099 | public static function menu_blockedIPs(){ |
| 1100 | require 'menu_blockedIPs.php'; |
| 1101 | } |
| 1102 | public static function menu_config(){ |
| 1103 | require 'menu_config.php'; |
| 1104 | } |
| 1105 | public static function menu_activity(){ |
| 1106 | require 'menu_activity.php'; |
| 1107 | } |
| 1108 | public static function menu_scan(){ |
| 1109 | require 'menu_scan.php'; |
| 1110 | } |
| 1111 | public static function isAdmin(){ |
| 1112 | if(is_multisite()){ |
| 1113 | if(current_user_can('manage_network')){ |
| 1114 | return true; |
| 1115 | } |
| 1116 | } else { |
| 1117 | if(current_user_can('update_core')){ |
| 1118 | return true; |
| 1119 | } |
| 1120 | } |
| 1121 | return false; |
| 1122 | } |
| 1123 | public static function status($level /* 1 has highest visibility */, $type /* info|error */, $msg){ |
| 1124 | if($type != 'info' && $type != 'error'){ error_log("Invalid status type: $type"); return; } |
| 1125 | self::getLog()->addStatus($level, $type, $msg); |
| 1126 | } |
| 1127 | public static function profileUpdateAction($userID, $newDat){ |
| 1128 | if(wfConfig::get('other_pwStrengthOnUpdate')){ |
| 1129 | $oldDat = get_userdata($userID); |
| 1130 | if($newDat->user_pass != $oldDat->user_pass){ |
| 1131 | $wf = new wfScanEngine(); |
| 1132 | $wf->scanUserPassword($userID); |
| 1133 | $wf->emailNewIssues(); |
| 1134 | } |
| 1135 | } |
| 1136 | } |
| 1137 | public static function genFilter($gen, $type){ |
| 1138 | if(wfConfig::get('other_hidegetWPVersion')){ |
| 1139 | return ''; |
| 1140 | } else { |
| 1141 | return $gen; |
| 1142 | } |
| 1143 | } |
| 1144 | public static function preCommentApprovedFilter($approved, $cData){ |
| 1145 | if( $approved == 1 && (! is_user_logged_in()) && wfConfig::get('other_noAnonMemberComments') ){ |
| 1146 | $user = get_user_by('email', trim($cData['comment_author_email'])); |
| 1147 | if($user){ |
| 1148 | return 0; //hold for moderation if the user is not signed in but used a members email |
| 1149 | } |
| 1150 | } |
| 1151 | |
| 1152 | if(($approved == 1 || $approved == 0) && wfConfig::get('other_scanComments')){ |
| 1153 | $wf = new wfScanEngine(); |
| 1154 | if($wf->isBadComment($cData['comment_author'], $cData['comment_author_email'], $cData['comment_author_url'], $cData['comment_author_IP'], $cData['comment_content'])){ |
| 1155 | return 'spam'; |
| 1156 | } |
| 1157 | } |
| 1158 | return $approved; |
| 1159 | } |
| 1160 | public static function getMyHomeURL(){ |
| 1161 | return admin_url('admin.php?page=Wordfence', 'http'); |
| 1162 | } |
| 1163 | public static function getMyOptionsURL(){ |
| 1164 | return admin_url('admin.php?page=WordfenceSecOpt', 'http'); |
| 1165 | } |
| 1166 | |
| 1167 | public static function alert($subject, $alertMsg){ |
| 1168 | $content = wfUtils::tmpl('email_genericAlert.php', array( |
| 1169 | 'subject' => $subject, |
| 1170 | 'blogName' => get_bloginfo('name', 'raw'), |
| 1171 | 'alertMsg' => $alertMsg, |
| 1172 | 'date' => date('l jS \of F Y \a\t h:i:s A'), |
| 1173 | 'myHomeURL' => self::getMyHomeURL(), |
| 1174 | 'myOptionsURL' => self::getMyOptionsURL() |
| 1175 | )); |
| 1176 | $emails = wfConfig::getAlertEmails(); |
| 1177 | if(sizeof($emails) < 1){ return; } |
| 1178 | $subject = "[Wordfence Alert] " . $subject; |
| 1179 | wp_mail(implode(',', $emails), $subject, $content); |
| 1180 | } |
| 1181 | public static function scheduleNextScan($force = false){ |
| 1182 | if(wfConfig::get('scheduledScansEnabled')){ |
| 1183 | $nextScan = wp_next_scheduled('wordfence_scheduled_scan'); |
| 1184 | if((! $force) && $nextScan && $nextScan - time() > 0){ |
| 1185 | //scan is already scheduled for the future |
| 1186 | return; |
| 1187 | } |
| 1188 | $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion()); |
| 1189 | $result = $api->call('get_next_scan_time', array(), array()); |
| 1190 | if(empty($result['errorMsg']) === false){ |
| 1191 | return $result['errorMsg']; |
| 1192 | } |
| 1193 | $secsToGo = 3600 * 6; //In case we can't contact the API, schedule next scan 6 hours from now. |
| 1194 | if(is_array($result) && $result['secsToGo'] > 1){ |
| 1195 | $secsToGo = $result['secsToGo']; |
| 1196 | } |
| 1197 | wp_clear_scheduled_hook('wordfence_scheduled_scan'); |
| 1198 | wp_schedule_single_event(time() + $secsToGo, 'wordfence_scheduled_scan'); |
| 1199 | } else { |
| 1200 | wp_clear_scheduled_hook('wordfence_scheduled_scan'); |
| 1201 | } |
| 1202 | } |
| 1203 | private static function getLog(){ |
| 1204 | if(! self::$wfLog){ |
| 1205 | $wfLog = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion()); |
| 1206 | self::$wfLog = $wfLog; |
| 1207 | } |
| 1208 | return self::$wfLog; |
| 1209 | } |
| 1210 | public static function statusStart($msg){ |
| 1211 | self::$statusStartMsgs[] = $msg; |
| 1212 | self::status(10, 'info', 'SUM_START:' . $msg); |
| 1213 | return sizeof(self::$statusStartMsgs) - 1; |
| 1214 | } |
| 1215 | public static function statusEnd($idx, $haveIssues){ |
| 1216 | if($haveIssues){ |
| 1217 | self::status(10, 'info', 'SUM_ENDBAD:' . self::$statusStartMsgs[$idx]); |
| 1218 | } else { |
| 1219 | self::status(10, 'info', 'SUM_ENDOK:' . self::$statusStartMsgs[$idx]); |
| 1220 | } |
| 1221 | self::$statusStartMsgs[$idx] = ''; |
| 1222 | } |
| 1223 | public static function statusEndErr(){ |
| 1224 | for($i = 0; $i < sizeof(self::$statusStartMsgs); $i++){ |
| 1225 | if(empty(self::$statusStartMsgs[$i]) === false){ |
| 1226 | self::status(10, 'info', 'SUM_ENDERR:' . self::$statusStartMsgs[$i]); |
| 1227 | self::$statusStartMsgs[$i] = ''; |
| 1228 | } |
| 1229 | } |
| 1230 | } |
| 1231 | public static function statusDisabled($msg){ |
| 1232 | self::status(10, 'info', "SUM_DISABLED:" . $msg); |
| 1233 | } |
| 1234 | public static function statusPaidOnly($msg){ |
| 1235 | self::status(10, 'info', "SUM_PAIDONLY:" . $msg); |
| 1236 | } |
| 1237 | public static function wfSchemaExists(){ |
| 1238 | $db = new wfDB(); |
| 1239 | global $wpdb; $prefix = $wpdb->base_prefix; |
| 1240 | $exists = $db->querySingle("show tables like '$prefix"."wfConfig'"); |
| 1241 | return $exists ? true : false; |
| 1242 | } |
| 1243 | } |
| 1244 | ?> |
| 1245 |