PluginProbe ʕ •ᴥ•ʔ
Wordfence Security – Firewall, Malware Scan, and Login Security / 2.0.5
Wordfence Security – Firewall, Malware Scan, and Login Security v2.0.5
8.2.2 8.2.1 8.2.0 3.7.1 3.7.2 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.8.8 3.8.9 3.9.1 4.0.1 4.0.2 4.0.3 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.0.9 5.1.1 5.1.2 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.2.1 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.1 5.3.10 5.3.11 5.3.12 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.3.8 5.3.9 6.0.1 6.0.10 6.0.11 6.0.12 6.0.14 6.0.15 6.0.16 6.0.17 6.0.18 6.0.19 6.0.2 6.0.20 6.0.21 6.0.22 6.0.23 6.0.24 6.0.25 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.0.8 6.0.9 6.1.1 6.1.10 6.1.11 6.1.12 6.1.14 6.1.15 6.1.16 6.1.17 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.1.9 6.2.0 6.2.1 6.2.10 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.2.8 6.2.9 6.3.0 6.3.1 6.3.10 6.3.11 6.3.12 6.3.14 6.3.15 6.3.16 6.3.17 6.3.18 6.3.19 6.3.2 6.3.20 6.3.21 6.3.22 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.3.8 6.3.9 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1.0 7.1.1 7.1.10 7.1.11 7.1.12 7.1.14 7.1.15 7.1.16 7.1.17 7.1.18 7.1.19 7.1.2 7.1.20 7.1.3 7.1.4 7.1.5 7.1.6 7.1.7 7.1.8 7.1.9 7.10.0 7.10.1 7.10.2 7.10.3 7.10.4 7.10.5 7.10.6 7.10.7 7.11.0 7.11.1 7.11.2 7.11.3 7.11.4 7.11.5 7.11.6 7.11.7 7.2.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3.1 7.3.2 7.3.3 7.3.4 7.3.5 7.3.6 7.4.0 7.4.1 7.4.10 7.4.11 7.4.12 7.4.14 7.4.2 7.4.3 trunk 7.4.4 1.1 7.4.5 1.2 7.4.6 1.3 7.4.7 1.3.1 7.4.8 1.3.2 7.4.9 1.3.3 7.5.0 1.4.2 7.5.1 1.4.3 7.5.10 1.4.4 7.5.11 1.4.5 7.5.2 1.4.6 7.5.3 1.4.7 7.5.4 1.4.8 7.5.5 1.5.1 7.5.6 1.5.2 7.5.7 1.5.3 7.5.8 1.5.4 7.5.9 1.5.5 7.6.0 1.5.6 7.6.1 2.0.1 7.6.2 2.0.2 7.7.0 2.0.3 7.7.1 2.0.5 7.8.0 2.0.6 7.8.1 2.0.7 7.8.2 2.1.0 7.9.0 2.1.1 7.9.1 2.1.2 7.9.2 2.1.3 7.9.3 2.1.4 8.0.0 2.1.5 8.0.1 3.0.2 8.0.2 3.0.3 8.0.3 3.0.4 8.0.4 3.0.5 8.0.5 3.0.6 8.1.0 3.0.7 8.1.1 3.0.8 8.1.2 3.0.9 8.1.3 3.1.0 8.1.4 3.1.1 v1.4.1 3.1.2 3.1.4 3.1.6 3.2.1 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.1 3.4.4 3.4.5 3.5.1 3.5.2 3.6.1 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9
wordfence / lib / wordfenceClass.php
wordfence / lib Last commit date
Diff 14 years ago .htaccess 14 years ago Diff.php 14 years ago IPTraf.php 14 years ago diffResult.php 14 years ago dropAll.php 14 years ago email_genericAlert.php 14 years ago email_newIssues.php 14 years ago email_unlockRequest.php 14 years ago menu_activity.php 14 years ago menu_blockedIPs.php 14 years ago menu_config.php 14 years ago menu_options.php 14 years ago menu_scan.php 14 years ago sysinfo.php 14 years ago viewFullActivityLog.php 14 years ago wf503.php 14 years ago wfAPI.php 14 years ago wfAction.php 14 years ago wfBrowscap.php 14 years ago wfBrowscapCache.php 14 years ago wfConfig.php 14 years ago wfCrawl.php 14 years ago wfDB.php 14 years ago wfDict.php 14 years ago wfIssues.php 14 years ago wfLockedOut.php 14 years ago wfLog.php 14 years ago wfModTracker.php 14 years ago wfRate.php 14 years ago wfScanEngine.php 14 years ago wfSchema.php 14 years ago wfUnlockMsg.php 14 years ago wfUtils.php 14 years ago wfViewResult.php 14 years ago wordfenceClass.php 14 years ago wordfenceConstants.php 14 years ago wordfenceHash.php 14 years ago wordfenceScanner.php 14 years ago wordfenceURLHoover.php 14 years ago
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