Wordfence Security – Firewall & Malware Scan - Version 3.8.1

Version Description

  • Added Cellphone Sign-in (Two Factor Authentication) for paid members. Stop brute-force attacks permanently! See new "Cellphone Sign-in" menu option.
  • Added ability to enforce strong passwords when accounts are created or users change their password. See Wordfence 'options' page under 'Login Security Options'.
  • Added new backdoor/malware signatures including detection for spamming scripts, youtube spam scripts and a new attack shell.
  • Fixed issue: Under some conditions, files not part of core or a known theme or plugin would be excluded from a scan.
  • Fixes from Juliette R. F. Remove warnings for unset variables. Fix options 'save' spinner spinning infinitely on some platforms. Removed redundant error handling code.
  • Added ability to downgrade a paid license to free.
Download this release

Release Info

Developer mmaunder
Plugin Icon 128x128 Wordfence Security – Firewall & Malware Scan
Version 3.8.1
Comparing to
See all releases

Code changes from version 3.7.2 to 3.8.1

css/main.css CHANGED
@@ -295,8 +295,8 @@ input.wfStartScanButton { width: 160px; text-align: left; padding-left: 20px; }
295
  #paidCover {
296
  }
297
  .paidInnerMsg {
298
- width: 400px;
299
- margin: 200px auto 0 auto;
300
  color: #000;
301
  font-size: 18px;
302
  font-family: Georgia, Times;
295
  #paidCover {
296
  }
297
  .paidInnerMsg {
298
+ width: 500px;
299
+ margin: 150px auto 0 auto;
300
  color: #000;
301
  font-size: 18px;
302
  font-family: Georgia, Times;
js/admin.js CHANGED
@@ -83,8 +83,17 @@ window['wordfenceAdmin'] = {
83
  startTicker = true;
84
  if(! this.tourClosed){
85
  var self = this;
86
- this.tour('wfWelcomeContent4', 'wfHeading', 'top', 'left', "Learn how to Block Countries", function(){ self.tourRedir('WordfenceCountryBlocking'); });
87
  }
 
 
 
 
 
 
 
 
 
88
  } else if(jQuery('#wordfenceMode_countryBlocking').length > 0){
89
  this.mode = 'countryBlocking';
90
  startTicker = false;
@@ -144,6 +153,13 @@ window['wordfenceAdmin'] = {
144
  tourFinish: function(){
145
  this.ajax('wordfence_tourClosed', {}, function(res){});
146
  },
 
 
 
 
 
 
 
147
  tour: function(contentID, elemID, edge, align, buttonLabel, buttonCallback){
148
  var self = this;
149
  if(this.currentPointer){
@@ -1054,10 +1070,10 @@ window['wordfenceAdmin'] = {
1054
  jQuery('.wfAjax24').hide();
1055
  if(res.ok){
1056
  if(res['paidKeyMsg']){
1057
- self.colorbox('400px', "Congratulations! You have been upgraded to Premium Scanning.", "You have upgraded to a Premium API key. Once this page reloads, you can choose which premium scanning options you would like to enable and then click save. Click the button below to reload this page now.<br /><br /><center><input type='button' name='wfReload' value='Reload page and enable Premium options' onclick='window.location.reload();' /></center>");
1058
  return;
1059
  } else if(res['reload'] == 'reload' || WFAD.reloadConfigPage){
1060
- self.colorbox('400px', "Please reload this page", "You selected a config option that requires a page reload. Click the button below to reload this page to update the menu.<br /><br /><center><input type='button' name='wfReload' value='Reload page' onclick='window.location.reload();' /></center>");
1061
  return;
1062
  } else {
1063
  self.pulse('.wfSavedMsg');
@@ -1267,6 +1283,58 @@ window['wordfenceAdmin'] = {
1267
  self.pulse('.wfSaveMsg');
1268
  });
1269
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1270
  getQueryParam: function(name){
1271
  name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
1272
  var regexS = "[\\?&]" + name + "=([^&#]*)";
83
  startTicker = true;
84
  if(! this.tourClosed){
85
  var self = this;
86
+ this.tour('wfWelcomeContent4', 'wfHeading', 'top', 'left', "Learn about Cellphone Sign-in", function(){ self.tourRedir('WordfenceTwoFactor'); });
87
  }
88
+ } else if(jQuery('#wordfenceMode_twoFactor').length > 0){
89
+ this.mode = 'twoFactor';
90
+ startTicker = false;
91
+ if(! this.tourClosed){
92
+ var self = this;
93
+ this.tour('wfWelcomeTwoFactor', 'wfHeading', 'top', 'left', "Learn how to Block Countries", function(){ self.tourRedir('WordfenceCountryBlocking'); });
94
+ }
95
+ this.loadTwoFactor();
96
+
97
  } else if(jQuery('#wordfenceMode_countryBlocking').length > 0){
98
  this.mode = 'countryBlocking';
99
  startTicker = false;
153
  tourFinish: function(){
154
  this.ajax('wordfence_tourClosed', {}, function(res){});
155
  },
156
+ downgradeLicense: function(){
157
+ this.colorbox('400px', "Confirm Downgrade", "Are you sure you want to downgrade your Wordfence Premium License? This will disable all Premium features and return you to the free version of Wordfence. <a href=\"https://www.wordfence.com/manage-wordfence-api-keys/\" target=\"_blank\">Click here to renew your paid membership</a> or click the button below to confirm you want to downgrade.<br /><br /><input type=\"button\" value=\"Downgrade and disable Premium features\" onclick=\"WFAD.downgradeLicenseConfirm();\" /><br />");
158
+ },
159
+ downgradeLicenseConfirm: function(){
160
+ jQuery.colorbox.close();
161
+ this.ajax('wordfence_downgradeLicense', {}, function(res){ location.reload(true); });
162
+ },
163
  tour: function(contentID, elemID, edge, align, buttonLabel, buttonCallback){
164
  var self = this;
165
  if(this.currentPointer){
1070
  jQuery('.wfAjax24').hide();
1071
  if(res.ok){
1072
  if(res['paidKeyMsg']){
1073
+ self.colorbox('400px', "Congratulations! You have been upgraded to Premium Scanning.", "You have upgraded to a Premium API key. Once this page reloads, you can choose which premium scanning options you would like to enable and then click save. Click the button below to reload this page now.<br /><br /><center><input type='button' name='wfReload' value='Reload page and enable Premium options' onclick='window.location.reload(true);' /></center>");
1074
  return;
1075
  } else if(res['reload'] == 'reload' || WFAD.reloadConfigPage){
1076
+ self.colorbox('400px', "Please reload this page", "You selected a config option that requires a page reload. Click the button below to reload this page to update the menu.<br /><br /><center><input type='button' name='wfReload' value='Reload page' onclick='window.location.reload(true);' /></center>");
1077
  return;
1078
  } else {
1079
  self.pulse('.wfSavedMsg');
1283
  self.pulse('.wfSaveMsg');
1284
  });
1285
  },
1286
+ twoFacStatus: function(msg){
1287
+ jQuery('#wfTwoFacMsg').html(msg);
1288
+ jQuery('#wfTwoFacMsg').fadeIn(function(){
1289
+ setTimeout(function(){ jQuery('#wfTwoFacMsg').fadeOut(); }, 2000);
1290
+ });
1291
+ },
1292
+ addTwoFactor: function(username, phone){
1293
+ var self = this;
1294
+ this.ajax('wordfence_addTwoFactor', {
1295
+ username: username,
1296
+ phone: phone
1297
+ }, function(res){
1298
+ if(res.ok){
1299
+ self.twoFacStatus('User added! Check the user\'s phone to get the activation code.');
1300
+ jQuery('<div id="twoFacCont_' + res.userID + '">' + jQuery('#wfTwoFacUserTmpl').tmpl(res).html() + '</div>').prependTo(jQuery('#wfTwoFacUsers'));
1301
+ }
1302
+ });
1303
+ },
1304
+ twoFacActivate: function(userID, code){
1305
+ var self = this;
1306
+ this.ajax('wordfence_twoFacActivate', {
1307
+ userID: userID,
1308
+ code: code
1309
+ }, function(res){
1310
+ if(res.ok){
1311
+ jQuery('#twoFacCont_' + res.userID).html(
1312
+ jQuery('#wfTwoFacUserTmpl').tmpl(res)
1313
+ );
1314
+ self.twoFacStatus('Cellphone Sign-in activated for user.');
1315
+ }
1316
+ });
1317
+ },
1318
+ delTwoFac: function(userID){
1319
+ this.ajax('wordfence_twoFacDel', {
1320
+ userID: userID
1321
+ }, function(res){
1322
+ if(res.ok){
1323
+ jQuery('#twoFacCont_' + res.userID).fadeOut(function(){ jQuery(this).remove(); });
1324
+ }
1325
+ });
1326
+ },
1327
+ loadTwoFactor: function(){
1328
+ this.ajax('wordfence_loadTwoFactor', {}, function(res){
1329
+ if(res.users && res.users.length > 0){
1330
+ for(var i = 0; i < res.users.length; i++){
1331
+ jQuery('<div id="twoFacCont_' + res.users[i].userID + '">' +
1332
+ jQuery('#wfTwoFacUserTmpl').tmpl(res.users[i]).html() +
1333
+ + '</div>').appendTo(jQuery('#wfTwoFacUsers'));
1334
+ }
1335
+ }
1336
+ });
1337
+ },
1338
  getQueryParam: function(name){
1339
  name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
1340
  var regexS = "[\\?&]" + name + "=([^&#]*)";
lib/menu_options.php CHANGED
@@ -15,6 +15,22 @@ var WFSLevels = <?php echo json_encode(wfConfig::$securityLevels); ?>;
15
 
16
  <form id="wfConfigForm">
17
  <table class="wfConfigForm">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  <tr><td colspan="2"><h2>Basic Options</h2></td></tr>
19
  <tr><th class="wfConfigEnable">Enable firewall </th><td><input type="checkbox" id="firewallEnabled" class="wfConfigElem" name="firewallEnabled" value="1" <?php $w->cb('firewallEnabled'); ?> />&nbsp;<span style="color: #F00;">NOTE:</span> This checkbox enables ALL firewall functions including IP, country and advanced blocking and the "Firewall Rules" below.</td></tr>
20
  <tr><td colspan="2">&nbsp;</td></tr>
@@ -26,15 +42,7 @@ var WFSLevels = <?php echo json_encode(wfConfig::$securityLevels); ?>;
26
  <tr><td colspan="2">&nbsp;</td></tr>
27
 
28
  <tr><th>Where to email alerts:</th><td><input type="text" id="alertEmails" name="alertEmails" value="<?php $w->f('alertEmails'); ?>" size="50" />&nbsp;<span class="wfTipText">Separate multiple emails with commas</span></td></tr>
29
- <tr><th>Your Wordfence API Key:</th><td><input type="text" id="apiKey" name="apiKey" value="<?php $w->f('apiKey'); ?>" size="50" />
30
- </td></tr>
31
- <tr><th>Key type currently active:</th><td>
32
- <?php if(wfConfig::get('isPaid')){ ?>
33
- The currently active API Key is a Premium Key. <span style="font-weight: bold; color: #0A0;">Premium scanning enabled!</span>
34
- <?php } else {?>
35
- The currently active API Key is a <span style="color: #F00; font-weight: bold;">Free Key</a>. <a href="https://www.wordfence.com/choose-a-wordfence-membership-type/?s2-ssl=yes" target="_blank">Upgrade to Premium Scanning now.</a>
36
- <?php } ?>
37
- </td></tr>
38
  <tr><th>Security Level:</th><td>
39
  <select id="securityLevel" name="securityLevel" onchange="WFAD.changeSecurityLevel(); return true;">
40
  <option value="0"<?php $w->sel('securityLevel', '0'); ?>>Level 0: Disable all Wordfence security measures</option>
@@ -147,6 +155,12 @@ var WFSLevels = <?php echo json_encode(wfConfig::$securityLevels); ?>;
147
  <div class="wfMarker" id="wfMarkerLoginSecurity"></div>
148
  <h3 class="wfConfigHeading">Login Security Options</h3>
149
  </td></tr>
 
 
 
 
 
 
150
  <tr><th>Lock out after how many login failures</th><td>
151
  <select id="loginSec_maxFailures" class="wfConfigElem" name="loginSec_maxFailures">
152
  <option value="1"<?php $w->sel('loginSec_maxFailures', '1'); ?>>1</option>
15
 
16
  <form id="wfConfigForm">
17
  <table class="wfConfigForm">
18
+ <tr><td colspan="2"><h2>License</h2></td></tr>
19
+
20
+ <tr><th>Your Wordfence API Key:</th><td><input type="text" id="apiKey" name="apiKey" value="<?php $w->f('apiKey'); ?>" size="80" /></td></tr>
21
+ <tr><th>Key type currently active:</th><td>
22
+ <?php if(wfConfig::get('isPaid')){ ?>
23
+ The currently active API Key is a Premium Key. <span style="font-weight: bold; color: #0A0;">Premium scanning enabled!</span>
24
+ <?php } else {?>
25
+ The currently active API Key is a <span style="color: #F00; font-weight: bold;">Free Key</a>. <a href="https://www.wordfence.com/wordfence-signup/" target="_blank">Upgrade to Premium Scanning now.</a>
26
+ <?php } ?>
27
+ </td></tr>
28
+ <tr><td colspan="2">
29
+ <?php if(wfConfig::get('isPaid')){ ?>
30
+ <table border="0"><tr><td><a href="https://www.wordfence.com/manage-wordfence-api-keys/" target="_blank"><input type="button" value="Renew your premium license" /></a></td><td>&nbsp;</td><td><input type="button" value="Downgrade to a free license" onclick="WFAD.downgradeLicense();" /></td></tr></table>
31
+ <?php } ?>
32
+
33
+
34
  <tr><td colspan="2"><h2>Basic Options</h2></td></tr>
35
  <tr><th class="wfConfigEnable">Enable firewall </th><td><input type="checkbox" id="firewallEnabled" class="wfConfigElem" name="firewallEnabled" value="1" <?php $w->cb('firewallEnabled'); ?> />&nbsp;<span style="color: #F00;">NOTE:</span> This checkbox enables ALL firewall functions including IP, country and advanced blocking and the "Firewall Rules" below.</td></tr>
36
  <tr><td colspan="2">&nbsp;</td></tr>
42
  <tr><td colspan="2">&nbsp;</td></tr>
43
 
44
  <tr><th>Where to email alerts:</th><td><input type="text" id="alertEmails" name="alertEmails" value="<?php $w->f('alertEmails'); ?>" size="50" />&nbsp;<span class="wfTipText">Separate multiple emails with commas</span></td></tr>
45
+ <tr><th colspan="2">&nbsp;</th></tr>
 
 
 
 
 
 
 
 
46
  <tr><th>Security Level:</th><td>
47
  <select id="securityLevel" name="securityLevel" onchange="WFAD.changeSecurityLevel(); return true;">
48
  <option value="0"<?php $w->sel('securityLevel', '0'); ?>>Level 0: Disable all Wordfence security measures</option>
155
  <div class="wfMarker" id="wfMarkerLoginSecurity"></div>
156
  <h3 class="wfConfigHeading">Login Security Options</h3>
157
  </td></tr>
158
+ <tr><th>Enforce strong passwords?</th><td>
159
+ <select class="wfConfigElem" id="loginSec_strongPasswds" name="loginSec_strongPasswds">
160
+ <option value="">Do not force users to use strong passwords</option>
161
+ <option value="pubs"<?php $w->sel('loginSec_strongPasswds', 'pubs'); ?>>Force admins and publishers to use strong passwords (recommended)</option>
162
+ <option value="all"<?php $w->sel('loginSec_strongPasswds', 'all'); ?>>Force all members to use strong passwords</option>
163
+ </select>
164
  <tr><th>Lock out after how many login failures</th><td>
165
  <select id="loginSec_maxFailures" class="wfConfigElem" name="loginSec_maxFailures">
166
  <option value="1"<?php $w->sel('loginSec_maxFailures', '1'); ?>>1</option>
lib/menu_rangeBlocking.php CHANGED
@@ -15,7 +15,7 @@
15
  </ul>
16
  </div>
17
  <table class="wfConfigForm">
18
- <tr><th>Block anyone that has an IP address in this range:</th><td><input id="ipRange" type="text" size="30" maxlength="255" value="<?php if($_GET['wfBlockRange']){ echo $_GET['wfBlockRange']; } ?>" onkeyup="WFAD.calcRangeTotal();">&nbsp;<span id="wfShowRangeTotal"></span></td></tr>
19
  <tr><td></td><td style="padding-bottom: 15px;"><strong>Examples:</strong> 192.168.200.200 - 192.168.200.220</td></tr>
20
  <tr><th>...you can also enter a User-Agent (browser) that matches:</th><td><input id="uaRange" type="text" size="30" maxlength="255" >&nbsp;(Case insensitive)</td></tr>
21
  <tr><td></td><td style="padding-bottom: 15px;"><strong>Examples:</strong> *badRobot*, AnotherBadRobot*, *someKindOfSuffix</td></tr>
15
  </ul>
16
  </div>
17
  <table class="wfConfigForm">
18
+ <tr><th>Block anyone that has an IP address in this range:</th><td><input id="ipRange" type="text" size="30" maxlength="255" value="<?php if( isset( $_GET['wfBlockRange'] ) && $_GET['wfBlockRange']){ echo $_GET['wfBlockRange']; } ?>" onkeyup="WFAD.calcRangeTotal();">&nbsp;<span id="wfShowRangeTotal"></span></td></tr>
19
  <tr><td></td><td style="padding-bottom: 15px;"><strong>Examples:</strong> 192.168.200.200 - 192.168.200.220</td></tr>
20
  <tr><th>...you can also enter a User-Agent (browser) that matches:</th><td><input id="uaRange" type="text" size="30" maxlength="255" >&nbsp;(Case insensitive)</td></tr>
21
  <tr><td></td><td style="padding-bottom: 15px;"><strong>Examples:</strong> *badRobot*, AnotherBadRobot*, *someKindOfSuffix</td></tr>
lib/menu_twoFactor.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="wordfenceModeElem" id="wordfenceMode_twoFactor"></div>
2
+ <div class="wrap" id="paidWrap">
3
+ <div class="wordfence-lock-icon wordfence-icon32"><br /></div><h2 id="wfHeading">Cellphone Sign-in</h2>
4
+ <div class="wordfenceWrap" style="margin: 20px 20px 20px 30px;">
5
+ <p style="width: 500px;">
6
+ Wordfence's Cellphone Sign-in uses a technique called "Two Factor Authentication" which is used by banks, government agencies and military world-wide as one of the most secure forms of remote system authentication.
7
+ It's now available from Wordfence for your WordPress website. "Two Factor" relies on two things: Something you know (your password) and something you have (your cellphone).
8
+ To access your website, you need to know your password and have your cellphone with you.
9
+ <br /><br />
10
+ Cellphone sign-in is a two step sign-in process. When you enable this feature for a member, they first sign-in using their username and password.
11
+ Then they receive an SMS on their cellphone containing a code. Then they sign in again using their username, and they reenter their
12
+ password with a space and the code they received at the end of the password.
13
+ <br /><br />
14
+ Cellphone Sign-in eliminates all common forms of brute force hacking. For a hacker to access a user account with Cellphone Sign-in enabled, they would have to steal
15
+ a member's cellphone to access their account.
16
+ We recommend you enable Cellphone Sign-in for all Administrator level accounts.
17
+ </p>
18
+ <p>
19
+ To enable Cellphone Sign-in Authentication for a user account:
20
+ <ol>
21
+ <li>Enter the username.</li>
22
+ <li>Enter a phone number where the code will be sent when the member wants to sign in.</li>
23
+ <li>Hit the enable button.</li>
24
+ <li>An activation code is sent to the member's phone.</li>
25
+ <li>Get the activation code from the member and enter it next to the username in the list below.</li>
26
+ <li>Click the "Enable" button to enable Cellphone Sign-in for that member.</li>
27
+ <li>From now on the user will only be able to sign-in by using Cellphone Sign-in.</li>
28
+ </ol>
29
+ <br />
30
+ <table border="0">
31
+ <tr><td>Enter a username to enable Cellphone Sign-in:</td><td><input type="text" id="wfUsername" value="" size="20" /></td></tr>
32
+ <tr><td>Enter a phone number where the code will be sent:</td><td><input type="text" id="wfPhone" value="" size="20" />Format: +1-123-555-5034</td></tr>
33
+ <tr><td colspan="2"><input type="button" value="Enable Cellphone Sign-in" onclick="WFAD.addTwoFactor(jQuery('#wfUsername').val(), jQuery('#wfPhone').val());" /></td></tr>
34
+ </table>
35
+ </p>
36
+ <div style="height: 20px;">
37
+ <div id="wfTwoFacMsg" style="color: #F00;">
38
+ &nbsp;
39
+ </div>
40
+ </div>
41
+ <div id="wfTwoFacUsers">
42
+
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <script type="text/x-jquery-template" id="wfTwoFacUserTmpl">
48
+ <div>
49
+ <table border="0"><tr>
50
+ <td style="width: 100px;">${username}</td>
51
+ <td style="width: 150px;">${phone}</td>
52
+ <td>
53
+ {{if status == 'activated'}}
54
+ <span style="color: #0A0;">Cellphone Sign-in Enabled</span>
55
+ {{else}}
56
+ Enter activation code:<input type="text" id="wfActivate" size="4" /><input type="button" value="Activate" onclick="WFAD.twoFacActivate('${userID}', jQuery('#wfActivate').val());" />
57
+ {{/if}}
58
+ </td>
59
+ <td>&nbsp;&nbsp;&nbsp;<a href="#" onclick="WFAD.delTwoFac('${userID}'); return false;">[Delete]</a></td>
60
+ </tr>
61
+ </table>
62
+ </div>
63
+ </script>
64
+ <script type="text/javascript">
65
+ <?php
66
+ if( (! wfConfig::get('isPaid')) && (wfConfig::get('tourClosed', 0) == '1') ){
67
+ echo 'WFAD.paidUsersOnly("Wordfence Cellphone Sign-in uses a technique called \'Two Factor Authentication\'. Two Factor Authentication is used by banks, government agencies and military world-wide as one of the most secure forms of remote system authentication. It\'s now available for all paid Wordfence members to permanently stop all brute force hacks. <br /><br />When you enable Cellphone Sign-in on a member\'s account, they complete a two-step process to sign in. First they enter their username and password as per normal. If the username and password are correct, we send a code to their phone. Then they enter their username and their password again but this time they add a space and the code to the end of their password. This form of authentication provides the highest level of security. It is called Two Factor in the security industry because it relies on two factors: Something you know (your password) and something you have (your phone).");';
68
+ }
69
+ ?>
70
+ </script>
71
+ <script type="text/x-jquery-template" id="wfWelcomeTwoFactor">
72
+ <div>
73
+ <h3>Secure Sign-in using your Cellphone</h3>
74
+ <strong><p>Want to permanently block all brute-force hacks?</p></strong>
75
+ <p>
76
+ The premium version of Wordfence includes Cellphone Sign-in, also called Two Factor Authentication in the security industry.
77
+ When you enable Cellphone Sign-in on a member's account, they need to complete a
78
+ two step process to sign in. First they enter their username and password
79
+ as usual to sign-into your WordPress website. Then they're told
80
+ that a code was sent to their phone. Once they get the code, they sign
81
+ into your site again and this time they add a space and the code to the end of their password.
82
+ </p>
83
+ <p>
84
+ This technique is called Two Factor Authentication because it relies on two factors:
85
+ Something you know (your password) and something you have (your phone).
86
+ It is used by banks and military world-wide as a way to dramatically increase
87
+ security.
88
+ </p>
89
+ <p>
90
+ <?php
91
+ if(wfConfig::get('isPaid')){
92
+ ?>
93
+ You have upgraded to the premium version of Wordfence and have full access
94
+ to this feature along with our other premium features.
95
+ <?php
96
+ } else {
97
+ ?>
98
+ If you would like access to this premium feature, please
99
+ <a href="https://www.wordfence.com/choose-a-wordfence-membership-type/?s2-ssl=yes" target="_blank">upgrade to our premium version</a>.
100
+ <?php
101
+ }
102
+ ?>
103
+ </p>
104
+ </div>
105
+ </script>
lib/menu_whois.php CHANGED
@@ -24,7 +24,7 @@ if(! function_exists('fsockopen')){
24
  <input type="text" name="whois" id="wfwhois" value="" size="40" maxlength="255" onkeydown="if(event.keyCode == 13){ WFAD.whois(jQuery('#wfwhois').val()); }" />&nbsp;<input type="button" name="whoisbutton" id="whoisbutton" class="button-primary" value="Look up IP or Domain" onclick="WFAD.whois(jQuery('#wfwhois').val());" />
25
 
26
  </p>
27
- <?php if($_GET['wfnetworkblock']){ ?>
28
  <h2>How to block a network</h2>
29
  <p style="width: 600px;">
30
  You've chosen to block the network that <span style="color: #F00;"><?php echo $_GET['whoisval']; ?></span> is part of.
@@ -50,7 +50,7 @@ if(! function_exists('fsockopen')){
50
  </div>
51
  </script>
52
  <script type="text/javascript">
53
- var whoisval = "<?php echo $_GET['whoisval']; ?>";
54
  if(whoisval){
55
  jQuery(function(){
56
  jQuery('#wfwhois').val(whoisval);
24
  <input type="text" name="whois" id="wfwhois" value="" size="40" maxlength="255" onkeydown="if(event.keyCode == 13){ WFAD.whois(jQuery('#wfwhois').val()); }" />&nbsp;<input type="button" name="whoisbutton" id="whoisbutton" class="button-primary" value="Look up IP or Domain" onclick="WFAD.whois(jQuery('#wfwhois').val());" />
25
 
26
  </p>
27
+ <?php if( isset( $_GET['wfnetworkblock'] ) && $_GET['wfnetworkblock']){ ?>
28
  <h2>How to block a network</h2>
29
  <p style="width: 600px;">
30
  You've chosen to block the network that <span style="color: #F00;"><?php echo $_GET['whoisval']; ?></span> is part of.
50
  </div>
51
  </script>
52
  <script type="text/javascript">
53
+ var whoisval = "<?php if( isset( $_GET['whoisval'] ) ) { echo $_GET['whoisval']; } ?>";
54
  if(whoisval){
55
  jQuery(function(){
56
  jQuery('#wfwhois').val(whoisval);
lib/schedWeekEntry.php CHANGED
@@ -20,7 +20,7 @@
20
  }
21
  echo '</tr><tr><th></th><td></td>';
22
  for($hour = 0; $hour <= 23; $hour++){
23
- $checked = ($sched[$dayIndex][$hour] ? 'checked' : '');
24
  echo '<td><input class="wfSchedCheckbox" type="checkbox" id="wfSchedDay_' . $dayIndex . '_' . $hour . '" ' . $checked . ' /></td>';
25
  }
26
  ?>
20
  }
21
  echo '</tr><tr><th></th><td></td>';
22
  for($hour = 0; $hour <= 23; $hour++){
23
+ $checked = ( isset( $sched[$dayIndex] ) && $sched[$dayIndex][$hour] ? 'checked' : '');
24
  echo '<td><input class="wfSchedCheckbox" type="checkbox" id="wfSchedDay_' . $dayIndex . '_' . $hour . '" ' . $checked . ' /></td>';
25
  }
26
  ?>
lib/wfConfig.php CHANGED
@@ -54,6 +54,7 @@ class wfConfig {
54
  "neverBlockBG" => "neverBlockVerified",
55
  "loginSec_countFailMins" => "5",
56
  "loginSec_lockoutMins" => "5",
 
57
  'loginSec_maxFailures' => "500",
58
  'loginSec_maxForgotPasswd' => "500",
59
  'maxGlobalRequests' => "DISABLED",
@@ -119,6 +120,7 @@ class wfConfig {
119
  "neverBlockBG" => "neverBlockVerified",
120
  "loginSec_countFailMins" => "5",
121
  "loginSec_lockoutMins" => "5",
 
122
  'loginSec_maxFailures' => "50",
123
  'loginSec_maxForgotPasswd' => "50",
124
  'maxGlobalRequests' => "DISABLED",
@@ -184,6 +186,7 @@ class wfConfig {
184
  "neverBlockBG" => "neverBlockVerified",
185
  "loginSec_countFailMins" => "240",
186
  "loginSec_lockoutMins" => "240",
 
187
  'loginSec_maxFailures' => "20",
188
  'loginSec_maxForgotPasswd' => "20",
189
  'maxGlobalRequests' => "DISABLED",
@@ -249,6 +252,7 @@ class wfConfig {
249
  "neverBlockBG" => "neverBlockVerified",
250
  "loginSec_countFailMins" => "1440",
251
  "loginSec_lockoutMins" => "1440",
 
252
  'loginSec_maxFailures' => "10",
253
  'loginSec_maxForgotPasswd' => "10",
254
  'maxGlobalRequests' => "960",
@@ -314,6 +318,7 @@ class wfConfig {
314
  "neverBlockBG" => "neverBlockVerified",
315
  "loginSec_countFailMins" => "1440",
316
  "loginSec_lockoutMins" => "1440",
 
317
  'loginSec_maxFailures' => "5",
318
  'loginSec_maxForgotPasswd' => "5",
319
  'maxGlobalRequests' => "960",
54
  "neverBlockBG" => "neverBlockVerified",
55
  "loginSec_countFailMins" => "5",
56
  "loginSec_lockoutMins" => "5",
57
+ 'loginSec_strongPasswds' => '',
58
  'loginSec_maxFailures' => "500",
59
  'loginSec_maxForgotPasswd' => "500",
60
  'maxGlobalRequests' => "DISABLED",
120
  "neverBlockBG" => "neverBlockVerified",
121
  "loginSec_countFailMins" => "5",
122
  "loginSec_lockoutMins" => "5",
123
+ 'loginSec_strongPasswds' => 'pubs',
124
  'loginSec_maxFailures' => "50",
125
  'loginSec_maxForgotPasswd' => "50",
126
  'maxGlobalRequests' => "DISABLED",
186
  "neverBlockBG" => "neverBlockVerified",
187
  "loginSec_countFailMins" => "240",
188
  "loginSec_lockoutMins" => "240",
189
+ 'loginSec_strongPasswds' => 'pubs',
190
  'loginSec_maxFailures' => "20",
191
  'loginSec_maxForgotPasswd' => "20",
192
  'maxGlobalRequests' => "DISABLED",
252
  "neverBlockBG" => "neverBlockVerified",
253
  "loginSec_countFailMins" => "1440",
254
  "loginSec_lockoutMins" => "1440",
255
+ 'loginSec_strongPasswds' => 'all',
256
  'loginSec_maxFailures' => "10",
257
  'loginSec_maxForgotPasswd' => "10",
258
  'maxGlobalRequests' => "960",
318
  "neverBlockBG" => "neverBlockVerified",
319
  "loginSec_countFailMins" => "1440",
320
  "loginSec_lockoutMins" => "1440",
321
+ 'loginSec_strongPasswds' => 'all',
322
  'loginSec_maxFailures' => "5",
323
  'loginSec_maxForgotPasswd' => "5",
324
  'maxGlobalRequests' => "960",
lib/wfLog.php CHANGED
@@ -397,7 +397,7 @@ class wfLog {
397
  $res['blocked'] = $this->getDB()->querySingle("select blockedTime from " . $this->blocksTable . " where IP=%s and (permanent = 1 OR (blockedTime + %s > unix_timestamp()))", $res['IP'], wfConfig::get('blockedTime'));
398
  $res['IP'] = wfUtils::inet_ntoa($res['IP']);
399
  $res['extReferer'] = false;
400
- if($res['referer']){
401
  $refURL = parse_url($res['referer']);
402
  if(is_array($refURL) && $refURL['host']){
403
  $refHost = strtolower(preg_replace('/^www\./i', '', $refURL['host']));
@@ -414,15 +414,17 @@ class wfLog {
414
  }
415
  if($q){
416
  $queryVars = array();
417
- parse_str($refURL['query'], $queryVars);
418
- if(isset($queryVars[$q])){
419
- $res['searchTerms'] = $queryVars[$q];
 
 
420
  }
421
  }
422
  }
423
  }
424
  if($res['extReferer']){
425
- if ( stristr( $referringPage['host'], 'google.' ) )
426
  {
427
  parse_str( $referringPage['query'], $queryVars );
428
  echo $queryVars['q']; // This is the search term used
397
  $res['blocked'] = $this->getDB()->querySingle("select blockedTime from " . $this->blocksTable . " where IP=%s and (permanent = 1 OR (blockedTime + %s > unix_timestamp()))", $res['IP'], wfConfig::get('blockedTime'));
398
  $res['IP'] = wfUtils::inet_ntoa($res['IP']);
399
  $res['extReferer'] = false;
400
+ if( isset( $res['referer'] ) && $res['referer']){
401
  $refURL = parse_url($res['referer']);
402
  if(is_array($refURL) && $refURL['host']){
403
  $refHost = strtolower(preg_replace('/^www\./i', '', $refURL['host']));
414
  }
415
  if($q){
416
  $queryVars = array();
417
+ if( isset( $refURL['query'] ) ) {
418
+ parse_str($refURL['query'], $queryVars);
419
+ if(isset($queryVars[$q])){
420
+ $res['searchTerms'] = $queryVars[$q];
421
+ }
422
  }
423
  }
424
  }
425
  }
426
  if($res['extReferer']){
427
+ if ( isset( $referringPage ) && stristr( $referringPage['host'], 'google.' ) )
428
  {
429
  parse_str( $referringPage['query'], $queryVars );
430
  echo $queryVars['q']; // This is the search term used
lib/wfScan.php CHANGED
@@ -12,7 +12,7 @@ class wfScan {
12
  if(! wordfence::wfSchemaExists()){
13
  self::errorExit("Looks like the Wordfence database tables have been deleted. You can fix this by de-activating and re-activating the Wordfence plugin from your Plugins menu.");
14
  }
15
- if($_GET['test'] == '1'){
16
  echo "WFCRONTESTOK:" . wfConfig::get('cronTestID');
17
  self::status(4, 'info', "Cron test received and message printed");
18
  exit();
12
  if(! wordfence::wfSchemaExists()){
13
  self::errorExit("Looks like the Wordfence database tables have been deleted. You can fix this by de-activating and re-activating the Wordfence plugin from your Plugins menu.");
14
  }
15
+ if( isset( $_GET['test'] ) && $_GET['test'] == '1'){
16
  echo "WFCRONTESTOK:" . wfConfig::get('cronTestID');
17
  self::status(4, 'info', "Cron test received and message printed");
18
  exit();
lib/whois/whois.idna.php CHANGED
@@ -51,7 +51,7 @@
51
  * @version 0.5.1
52
  *
53
  */
54
- class idna_convert
55
  {
56
  /**
57
  * Holds all relevant mapping tables, loaded from a seperate file on construct
@@ -942,7 +942,7 @@ class idna_convert
942
  * Adapter class for aligning the API of idna_convert with that of Net_IDNA
943
  * @author Matthias Sommerfeld <mso@phlylabs.de>
944
  */
945
- class Net_IDNA_php4 extends idna_convert
946
  {
947
  /**
948
  * Sets a new option value. Available options and values:
@@ -966,4 +966,4 @@ class Net_IDNA_php4 extends idna_convert
966
  }
967
  }
968
 
969
- ?>
51
  * @version 0.5.1
52
  *
53
  */
54
+ class wordfence_idna_convert
55
  {
56
  /**
57
  * Holds all relevant mapping tables, loaded from a seperate file on construct
942
  * Adapter class for aligning the API of idna_convert with that of Net_IDNA
943
  * @author Matthias Sommerfeld <mso@phlylabs.de>
944
  */
945
+ class Net_IDNA_php4 extends wordfence_idna_convert
946
  {
947
  /**
948
  * Sets a new option value. Available options and values:
966
  }
967
  }
968
 
969
+ ?>
lib/whois/whois.main.php CHANGED
@@ -82,7 +82,7 @@ class Whois extends WhoisClient
82
 
83
  $query = trim($query);
84
 
85
- $IDN = new idna_convert();
86
 
87
  if ($is_utf)
88
  $query = $IDN->encode($query);
82
 
83
  $query = trim($query);
84
 
85
+ $IDN = new wordfence_idna_convert();
86
 
87
  if ($is_utf)
88
  $query = $IDN->encode($query);
lib/wordfenceClass.php CHANGED
@@ -14,6 +14,7 @@ require_once('wfSchema.php');
14
  class wordfence {
15
  public static $printStatus = false;
16
  public static $wordfence_wp_version = false;
 
17
  protected static $lastURLError = false;
18
  protected static $curlContent = "";
19
  protected static $curlDataWritten = 0;
@@ -259,12 +260,15 @@ class wordfence {
259
  add_action('init', 'wordfence::initAction');
260
  add_action('template_redirect', 'wordfence::templateRedir');
261
  add_action('shutdown', 'wordfence::shutdownAction');
262
- add_action('wp_authenticate','wordfence::authAction');
263
  add_action('login_init','wordfence::loginInitAction');
264
  add_action('wp_login','wordfence::loginAction');
265
  add_action('wp_logout','wordfence::logoutAction');
266
  add_action('profile_update', 'wordfence::profileUpdateAction', '99', 2);
267
  add_action('lostpassword_post', 'wordfence::lostPasswordPost', '1');
 
 
 
268
  //add_filter('cron_schedules', 'wordfence::moreCronReccurences');
269
  add_filter('pre_comment_approved', 'wordfence::preCommentApprovedFilter', '99', 2);
270
  add_filter('authenticate', 'wordfence::authenticateFilter', 99, 3);
@@ -338,6 +342,61 @@ class wordfence {
338
  }
339
  $returnArr['nonce'] = wp_create_nonce('wp-ajax');
340
  die(json_encode($returnArr));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  }
342
  public static function lostPasswordPost(){
343
  $IP = wfUtils::getIP();
@@ -496,10 +555,57 @@ class wordfence {
496
  }
497
  public static function authenticateFilter($authResult){
498
  $IP = wfUtils::getIP();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
  if(self::getLog()->isWhitelisted($IP)){
500
  return $authResult;
501
  }
502
- if(wfConfig::get('loginSecurityEnabled')){
503
  if(is_wp_error($authResult) && $authResult->get_error_code() == 'invalid_username' && wfConfig::get('loginSec_lockInvalidUsers')){
504
  self::lockOutIP($IP, "Used an invalid username '" . $_POST['log'] . "' to try to sign in.");
505
  require('wfLockedOut.php');
@@ -536,12 +642,19 @@ class wordfence {
536
  require('wfLockedOut.php');
537
  }
538
  }
539
- public static function authAction($username){
540
  if(self::isLockedOut(wfUtils::getIP())){
541
  require('wfLockedOut.php');
542
  }
543
  if(! $username){ return; }
544
  $userDat = get_user_by('login', $username);
 
 
 
 
 
 
 
545
  if($userDat){
546
  require_once( ABSPATH . 'wp-includes/class-phpass.php');
547
  $hasher = new PasswordHash(8, TRUE);
@@ -589,6 +702,122 @@ class wordfence {
589
  return array('errorMsg' => $e->getMessage());
590
  }
591
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
  public static function ajax_saveScanSchedule_callback(){
593
  if(! wfConfig::get('isPaid')){
594
  return array('errorMsg' => "Sorry but this feature is only available for paid customers.");
@@ -742,6 +971,21 @@ class wordfence {
742
  wfConfig::set('tourClosed', 0);
743
  return array('ok' => 1);
744
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
745
  public static function ajax_tourClosed_callback(){
746
  wfConfig::set('tourClosed', 1);
747
  return array('ok' => 1);
@@ -890,12 +1134,7 @@ class wordfence {
890
  return array('errorMsg' => "Your options have been saved. However we noticed you changed your API key and we tried to verify it with the Wordfence servers and received an error: " . $e->getMessage());
891
  }
892
  }
893
- //Clears next scan if scans are disabled. Schedules next scan if enabled.
894
- if($err){
895
- return array('errorMsg' => $err);
896
- } else {
897
- return array('ok' => 1, 'reload' => $reload, 'paidKeyMsg' => $paidKeyMsg );
898
- }
899
  }
900
  public static function ajax_clearAllBlocked_callback(){
901
  $op = $_POST['op'];
@@ -1353,7 +1592,7 @@ class wordfence {
1353
  }
1354
  public static function wfFunc_diff(){
1355
  $result = self::getWPFileContent($_GET['file'], $_GET['cType'], $_GET['cName'], $_GET['cVersion']);
1356
- if($result['errorMsg']){
1357
  echo $result['errorMsg'];
1358
  exit(0);
1359
  } else if(! $result['fileContent']){
@@ -1395,7 +1634,7 @@ class wordfence {
1395
  }
1396
  public static function admin_init(){
1397
  if(! wfUtils::isAdmin()){ return; }
1398
- foreach(array('activate', 'scan', 'sendActivityLog', 'restoreFile', 'deleteFile', 'removeExclusion', 'activityLogUpdate', 'ticker', 'loadIssues', 'updateIssueStatus', 'deleteIssue', 'updateAllIssues', 'reverseLookup', 'unlockOutIP', 'loadBlockRanges', 'unblockRange', 'blockIPUARange', 'whois', 'unblockIP', 'blockIP', 'permBlockIP', 'loadStaticPanel', 'saveConfig', 'clearAllBlocked', 'killScan', 'saveCountryBlocking', 'saveScanSchedule', 'tourClosed', 'startTourAgain') as $func){
1399
  add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
1400
  }
1401
 
@@ -1494,6 +1733,7 @@ class wordfence {
1494
  add_submenu_page("Wordfence", "Live Traffic", "Live Traffic", "activate_plugins", "WordfenceActivity", 'wordfence::menu_activity');
1495
  }
1496
  add_submenu_page('Wordfence', 'Blocked IPs', 'Blocked IPs', 'activate_plugins', 'WordfenceBlockedIPs', 'wordfence::menu_blockedIPs');
 
1497
  add_submenu_page("Wordfence", "Country Blocking", "Country Blocking", "activate_plugins", "WordfenceCountryBlocking", 'wordfence::menu_countryBlocking');
1498
  add_submenu_page("Wordfence", "Scan Schedule", "Scan Schedule", "activate_plugins", "WordfenceScanSchedule", 'wordfence::menu_scanSchedule');
1499
  add_submenu_page("Wordfence", "Whois Lookup", "Whois Lookup", "activate_plugins", "WordfenceWhois", 'wordfence::menu_whois');
@@ -1509,6 +1749,9 @@ class wordfence {
1509
  public static function menu_scanSchedule(){
1510
  require 'menu_scanSchedule.php';
1511
  }
 
 
 
1512
  public static function menu_countryBlocking(){
1513
  require 'menu_countryBlocking.php';
1514
  }
14
  class wordfence {
15
  public static $printStatus = false;
16
  public static $wordfence_wp_version = false;
17
+ private static $passwordCodePattern = '/\s+(wf[a-z0-9]+)$/i';
18
  protected static $lastURLError = false;
19
  protected static $curlContent = "";
20
  protected static $curlDataWritten = 0;
260
  add_action('init', 'wordfence::initAction');
261
  add_action('template_redirect', 'wordfence::templateRedir');
262
  add_action('shutdown', 'wordfence::shutdownAction');
263
+ add_action('wp_authenticate','wordfence::authAction', 1, 2);
264
  add_action('login_init','wordfence::loginInitAction');
265
  add_action('wp_login','wordfence::loginAction');
266
  add_action('wp_logout','wordfence::logoutAction');
267
  add_action('profile_update', 'wordfence::profileUpdateAction', '99', 2);
268
  add_action('lostpassword_post', 'wordfence::lostPasswordPost', '1');
269
+ add_action('user_profile_update_errors', 'wordfence::validateProfileUpdate', 0, 3 );
270
+ add_action('validate_password_reset', 'wordfence::validatePassword', 10, 2 );
271
+
272
  //add_filter('cron_schedules', 'wordfence::moreCronReccurences');
273
  add_filter('pre_comment_approved', 'wordfence::preCommentApprovedFilter', '99', 2);
274
  add_filter('authenticate', 'wordfence::authenticateFilter', 99, 3);
342
  }
343
  $returnArr['nonce'] = wp_create_nonce('wp-ajax');
344
  die(json_encode($returnArr));
345
+ exit;
346
+ }
347
+ public static function validateProfileUpdate($errors, $update, $userData){
348
+ wordfence::validatePassword($errors, $userData);
349
+ }
350
+ public static function validatePassword($errors, $userData){
351
+ $password = ( isset( $_POST[ 'pass1' ] ) && trim( $_POST[ 'pass1' ] ) ) ? $_POST[ 'pass1' ] : false;
352
+ $user_id = isset( $userData->ID ) ? $userData->ID : false;
353
+ $username = isset( $_POST["user_login"] ) ? $_POST["user_login"] : $userData->user_login;
354
+ if($password == false){ return $errors; }
355
+ if($errors->get_error_data("pass") ){ return $errors; }
356
+ $enforce = false;
357
+ if(wfConfig::get('loginSec_strongPasswds') == 'pubs'){
358
+ if(user_can($user_id, 'publish_posts')){
359
+ $enforce = true;
360
+ }
361
+ } else if(wfConfig::get('loginSec_strongPasswds') == 'all'){
362
+ $enforce = true;
363
+ }
364
+ if($enforce){
365
+ if(! wordfence::isStrongPasswd($password, $username)){
366
+ $errors->add('pass', "Please choose a stronger password. Try including numbers, symbols and a mix of upper and lower case letters and remove common words.");
367
+ return $errors;
368
+ }
369
+ }
370
+ $twoFactorUsers = wfConfig::get_ser('twoFactorUsers', array());
371
+ if(preg_match(self::$passwordCodePattern, $password) && isset($twoFactorUsers) && is_array($twoFactorUsers) && sizeof($twoFactorUsers) > 0){
372
+ $errors->add('pass', "Passwords containing a space followed by 'wf' without quotes are not allowed.");
373
+ return $errors;
374
+ }
375
+ return $errors;
376
+ }
377
+ function isStrongPasswd($passwd, $username ) {
378
+ $strength = 0;
379
+ if(strlen( $passwd ) < 5)
380
+ return false;
381
+ if(strtolower( $passwd ) == strtolower( $username ) )
382
+ return false;
383
+ if(preg_match('/(?:password|passwd|mypass|wordpress)/i', $passwd)){
384
+ return false;
385
+ }
386
+ if($num = preg_match_all( "/\d/", $passwd, $matches) ){
387
+ $strength += ((int)$num * 10);
388
+ }
389
+ if ( preg_match( "/[a-z]/", $passwd ) )
390
+ $strength += 26;
391
+ if ( preg_match( "/[A-Z]/", $passwd ) )
392
+ $strength += 26;
393
+ if ($num = preg_match_all( "/[^a-zA-Z0-9]/", $passwd, $matches)){
394
+ $strength += (31 * (int)$num);
395
+
396
+ }
397
+ if($strength > 60){
398
+ return true;
399
+ }
400
  }
401
  public static function lostPasswordPost(){
402
  $IP = wfUtils::getIP();
555
  }
556
  public static function authenticateFilter($authResult){
557
  $IP = wfUtils::getIP();
558
+ $secEnabled = wfConfig::get('loginSecurityEnabled');
559
+ if($secEnabled){
560
+ $twoFactorUsers = wfConfig::get_ser('twoFactorUsers', array());
561
+ if(isset($twoFactorUsers) && is_array($twoFactorUsers) && sizeof($twoFactorUsers) > 0){
562
+ $userDat = $_POST['wordfence_userDat'];
563
+ if(get_class($authResult) == 'WP_User'){ //Valid username and password either with or without the 'wf...' code. Users is now logged in at this point.
564
+ if(isset($_POST['wordfence_authFactor']) && $_POST['wordfence_authFactor']){ //user entered a valid user and password with ' wf....' appended
565
+ foreach($twoFactorUsers as &$t){
566
+ if($t[0] == $userDat->ID && $t[3] == 'activated'){
567
+ if($_POST['wordfence_authFactor'] == $t[2] && $t[4] > time()){
568
+ //Do nothing and allow user to sign in. Their passwd has already been modified to be the passwd without the code.
569
+ } else if($_POST['wordfence_authFactor'] == $t[2]){
570
+ $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
571
+ $codeResult = $api->call('twoFactor_verification', array(), array('phone' => $t[1]) );
572
+ if(isset($codeResult['ok']) && $codeResult['ok']){
573
+ $t[2] = $codeResult['code'];
574
+ $t[4] = time() + 1800; //30 minutes until code expires
575
+ wfConfig::set_ser('twoFactorUsers', $twoFactorUsers); //save the code the user needs to enter and return an error.
576
+ return new WP_Error( 'twofactor_required', __( '<strong>CODE EXPIRED. CHECK YOUR PHONE:</strong> The code you entered has expired. Codes are only valid for 30 minutes for security reasons. We have sent you a new code. Please sign in using your username and your password followed by a space and the new code we sent you.'));
577
+ } else {
578
+ break; //No new code was received. Let them sign in with the expired code.
579
+ }
580
+ } else { //Bad code, so cancel the login and return an error to user.
581
+ return new WP_Error( 'twofactor_required', __( '<strong>INVALID CODE</strong>: You need to enter your password followed by a space and the code we sent to your phone. The code should start with \'wf\' and should be four characters. e.g. wfAB12. In this case you would enter your password as: \'mypassword wfAB12\' without quotes.'));
582
+ }
583
+ } //No user matches and has TF activated so let user sign in.
584
+ }
585
+ } else { //valid login with no code entered
586
+ foreach($twoFactorUsers as &$t){
587
+ if($t[0] == $userDat->ID && $t[3] == 'activated'){ //Yup, enabled, so lets send the code
588
+ $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
589
+ $codeResult = $api->call('twoFactor_verification', array(), array('phone' => $t[1]) );
590
+ if(isset($codeResult['ok']) && $codeResult['ok']){
591
+ $t[2] = $codeResult['code'];
592
+ $t[4] = time() + 1800; //30 minutes until code expires
593
+ wfConfig::set_ser('twoFactorUsers', $twoFactorUsers); //save the code the user needs to enter and return an error.
594
+ return new WP_Error( 'twofactor_required', __( '<strong>CHECK YOUR PHONE</strong>: A code has been sent to your phone and will arrive within 30 seconds. Please sign in again and add a space and the code to the end of your password.' ) );
595
+ } else { //oops, our API returned an error.
596
+ break; //Let them sign in without two factor because the API is broken and we don't want to lock users out of their own systems.
597
+ }
598
+ } //User is not present in two factor list or is not activated. Sign in without twofactor.
599
+ } //Two facto users is empty. Sign in without two factor.
600
+ }
601
+ }
602
+ }
603
+ }
604
+
605
  if(self::getLog()->isWhitelisted($IP)){
606
  return $authResult;
607
  }
608
+ if($secEnabled){
609
  if(is_wp_error($authResult) && $authResult->get_error_code() == 'invalid_username' && wfConfig::get('loginSec_lockInvalidUsers')){
610
  self::lockOutIP($IP, "Used an invalid username '" . $_POST['log'] . "' to try to sign in.");
611
  require('wfLockedOut.php');
642
  require('wfLockedOut.php');
643
  }
644
  }
645
+ public static function authAction($username, $passwd){
646
  if(self::isLockedOut(wfUtils::getIP())){
647
  require('wfLockedOut.php');
648
  }
649
  if(! $username){ return; }
650
  $userDat = get_user_by('login', $username);
651
+ $_POST['wordfence_userDat'] = $userDat;
652
+ if(preg_match(self::$passwordCodePattern, $passwd, $matches)){
653
+ $_POST['wordfence_authFactor'] = $matches[1];
654
+ $passwd = preg_replace('/^(.+)\s+(wf[a-z0-9]+)$/i', '$1', $passwd);
655
+ $_POST['pwd'] = $passwd;
656
+ }
657
+
658
  if($userDat){
659
  require_once( ABSPATH . 'wp-includes/class-phpass.php');
660
  $hasher = new PasswordHash(8, TRUE);
702
  return array('errorMsg' => $e->getMessage());
703
  }
704
  }
705
+ public static function ajax_addTwoFactor_callback(){
706
+ $username = $_POST['username'];
707
+ $phone = $_POST['phone'];
708
+ $user = get_user_by('login', $username);
709
+ if(! $user){
710
+ return array('errorMsg' => "The username you specified does not exist.");
711
+ }
712
+ if(! preg_match('/^\+\d[\d\-]+$/', $phone)){
713
+ return array('errorMsg' => "The phone number you entered must start with a '+', then country code and then area code and number. It can only contain the starting plus sign and then numbers and dashes. It can not contain spaces. For example, a number in the United States with country code '1' would look like this: +1-123-555-1234");
714
+ }
715
+ $api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
716
+ try {
717
+ $codeResult = $api->call('twoFactor_verification', array(), array('phone' => $phone));
718
+ } catch(Exception $e){
719
+ return array('errorMsg' => "Could not contact Wordfence servers to generate a verification code: " . $e->getMessage());
720
+ }
721
+ if(isset($codeResult['ok']) && $codeResult['ok']){
722
+ $code = $codeResult['code'];
723
+ } else if(isset($codeResult['errorMsg']) && $codeResult['errorMsg']){
724
+ return array('errorMsg' => $codeResult['errorMsg']);
725
+ } else {
726
+ return array('errorMsg' => "We could not generate a verification code.");
727
+ }
728
+ self::twoFactorAdd($user->ID, $phone, $code);
729
+ return array(
730
+ 'ok' => 1,
731
+ 'userID' => $user->ID,
732
+ 'username' => $username,
733
+ 'phone' => $phone
734
+ );
735
+ }
736
+ public static function ajax_twoFacActivate_callback(){
737
+ $userID = $_POST['userID'];
738
+ $code = $_POST['code'];
739
+ $twoFactorUsers = wfConfig::get_ser('twoFactorUsers', array());
740
+ if(! is_array($twoFactorUsers)){
741
+ $twoFactorUsers = array();
742
+ }
743
+ $found = false;
744
+ $user = false;
745
+ for($i = 0; $i < sizeof($twoFactorUsers); $i++){
746
+ if($twoFactorUsers[$i][0] == $userID){
747
+ if($twoFactorUsers[$i][2] == $code){
748
+ $twoFactorUsers[$i][3] = 'activated';
749
+ $found = true;
750
+ $user = $twoFactorUsers[$i];
751
+ break;
752
+ } else {
753
+ return array('errorMsg' => "That is not the correct code. Please look for an SMS containing an activation code on the phone with number: " . $twoFactorUsers[$i][1]);
754
+ }
755
+ }
756
+ }
757
+ if(! $found){
758
+ return array('errorMsg' => "We could not find the user you are trying to activate. They may have been removed from the list of Cellphone Sign-in users. Please reload this page.");
759
+ }
760
+ wfConfig::set_ser('twoFactorUsers', $twoFactorUsers);
761
+ $WPuser = get_userdata($userID);
762
+ return array(
763
+ 'ok' => 1,
764
+ 'userID' => $userID,
765
+ 'username' => $WPuser->user_login,
766
+ 'phone' => $user[1],
767
+ 'status' => 'activated'
768
+ );
769
+ }
770
+ private static function twoFactorAdd($ID, $phone, $code){
771
+ $twoFactorUsers = wfConfig::get_ser('twoFactorUsers', array());
772
+ if(! is_array($twoFactorUsers)){
773
+ $twoFactorUsers = array();
774
+ }
775
+ for($i = 0; $i < sizeof($twoFactorUsers); $i++){
776
+ if($twoFactorUsers[$i][0] == $ID || (! $twoFactorUsers[$i][0]) ){
777
+ array_splice($twoFactorUsers, $i, 1);
778
+ $i--;
779
+ }
780
+ }
781
+ array_push($twoFactorUsers, array($ID, $phone, $code, 'notActivated', time() + (86400 * 100))); //expiry of code is 100 days in future
782
+ wfConfig::set_ser('twoFactorUsers', $twoFactorUsers);
783
+ }
784
+ public static function ajax_loadTwoFactor_callback(){
785
+ $users = wfConfig::get_ser('twoFactorUsers', array());
786
+ $ret = array();
787
+ foreach($users as $user){
788
+ $WPuser = get_userdata($user[0]);
789
+ if($user){
790
+ array_push($ret, array(
791
+ 'userID' => $user[0],
792
+ 'username' => $WPuser->user_login,
793
+ 'phone' => $user[1],
794
+ 'status' => $user[3]
795
+ ));
796
+ }
797
+ }
798
+ return array('ok' => 1, 'users' => $ret);
799
+ }
800
+ public static function ajax_twoFacDel_callback(){
801
+ $ID = $_POST['userID'];
802
+ $twoFactorUsers = wfConfig::get_ser('twoFactorUsers', array());
803
+ if(! is_array($twoFactorUsers)){
804
+ $twoFactorUsers = array();
805
+ }
806
+ $deleted = false;
807
+ for($i = 0; $i < sizeof($twoFactorUsers); $i++){
808
+ if($twoFactorUsers[$i][0] == $ID){
809
+ array_splice($twoFactorUsers, $i, 1);
810
+ $deleted = true;
811
+ $i--;
812
+ }
813
+ }
814
+ wfConfig::set_ser('twoFactorUsers', $twoFactorUsers);
815
+ if($deleted){
816
+ return array('ok' => 1, 'userID' => $ID);
817
+ } else {
818
+ return array('errorMsg' => "That user has already been removed from the list.");
819
+ }
820
+ }
821
  public static function ajax_saveScanSchedule_callback(){
822
  if(! wfConfig::get('isPaid')){
823
  return array('errorMsg' => "Sorry but this feature is only available for paid customers.");
971
  wfConfig::set('tourClosed', 0);
972
  return array('ok' => 1);
973
  }
974
+ public static function ajax_downgradeLicense_callback(){
975
+ $api = new wfAPI('', wfUtils::getWPVersion());
976
+ try {
977
+ $keyData = $api->call('get_anon_api_key');
978
+ if($keyData['ok'] && $keyData['apiKey']){
979
+ wfConfig::set('apiKey', $keyData['apiKey']);
980
+ wfConfig::set('isPaid', 0);
981
+ } else {
982
+ throw new Exception("Could not understand the response we received from the Wordfence servers when applying for a free API key.");
983
+ }
984
+ } catch(Exception $e){
985
+ return array('errorMsg' => "Could not fetch free API key from Wordfence: " . $e->getMessage());
986
+ }
987
+ return array('ok' => 1);
988
+ }
989
  public static function ajax_tourClosed_callback(){
990
  wfConfig::set('tourClosed', 1);
991
  return array('ok' => 1);
1134
  return array('errorMsg' => "Your options have been saved. However we noticed you changed your API key and we tried to verify it with the Wordfence servers and received an error: " . $e->getMessage());
1135
  }
1136
  }
1137
+ return array('ok' => 1, 'reload' => $reload, 'paidKeyMsg' => $paidKeyMsg );
 
 
 
 
 
1138
  }
1139
  public static function ajax_clearAllBlocked_callback(){
1140
  $op = $_POST['op'];
1592
  }
1593
  public static function wfFunc_diff(){
1594
  $result = self::getWPFileContent($_GET['file'], $_GET['cType'], $_GET['cName'], $_GET['cVersion']);
1595
+ if( isset( $result['errorMsg'] ) && $result['errorMsg']){
1596
  echo $result['errorMsg'];
1597
  exit(0);
1598
  } else if(! $result['fileContent']){
1634
  }
1635
  public static function admin_init(){
1636
  if(! wfUtils::isAdmin()){ return; }
1637
+ foreach(array('activate', 'scan', 'sendActivityLog', 'restoreFile', 'deleteFile', 'removeExclusion', 'activityLogUpdate', 'ticker', 'loadIssues', 'updateIssueStatus', 'deleteIssue', 'updateAllIssues', 'reverseLookup', 'unlockOutIP', 'loadBlockRanges', 'unblockRange', 'blockIPUARange', 'whois', 'unblockIP', 'blockIP', 'permBlockIP', 'loadStaticPanel', 'saveConfig', 'clearAllBlocked', 'killScan', 'saveCountryBlocking', 'saveScanSchedule', 'tourClosed', 'startTourAgain', 'downgradeLicense', 'addTwoFactor', 'twoFacActivate', 'twoFacDel', 'loadTwoFactor') as $func){
1638
  add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
1639
  }
1640
 
1733
  add_submenu_page("Wordfence", "Live Traffic", "Live Traffic", "activate_plugins", "WordfenceActivity", 'wordfence::menu_activity');
1734
  }
1735
  add_submenu_page('Wordfence', 'Blocked IPs', 'Blocked IPs', 'activate_plugins', 'WordfenceBlockedIPs', 'wordfence::menu_blockedIPs');
1736
+ add_submenu_page("Wordfence", "Cellphone Sign-in", "Cellphone Sign-in", "activate_plugins", "WordfenceTwoFactor", 'wordfence::menu_twoFactor');
1737
  add_submenu_page("Wordfence", "Country Blocking", "Country Blocking", "activate_plugins", "WordfenceCountryBlocking", 'wordfence::menu_countryBlocking');
1738
  add_submenu_page("Wordfence", "Scan Schedule", "Scan Schedule", "activate_plugins", "WordfenceScanSchedule", 'wordfence::menu_scanSchedule');
1739
  add_submenu_page("Wordfence", "Whois Lookup", "Whois Lookup", "activate_plugins", "WordfenceWhois", 'wordfence::menu_whois');
1749
  public static function menu_scanSchedule(){
1750
  require 'menu_scanSchedule.php';
1751
  }
1752
+ public static function menu_twoFactor(){
1753
+ require 'menu_twoFactor.php';
1754
+ }
1755
  public static function menu_countryBlocking(){
1756
  require 'menu_countryBlocking.php';
1757
  }
lib/wordfenceConstants.php CHANGED
@@ -1,5 +1,5 @@
1
  <?php
2
- define('WORDFENCE_API_VERSION', '2.6');
3
  define('WORDFENCE_API_URL_SEC', 'https://noc1.wordfence.com/');
4
  define('WORDFENCE_API_URL_NONSEC', 'http://noc1.wordfence.com/');
5
  define('WORDFENCE_MAX_SCAN_TIME', 86400); //Increased this from 10 mins to 1 day because very big scans run for a long time. Users can use kill.
1
  <?php
2
+ define('WORDFENCE_API_VERSION', '2.7');
3
  define('WORDFENCE_API_URL_SEC', 'https://noc1.wordfence.com/');
4
  define('WORDFENCE_API_URL_NONSEC', 'http://noc1.wordfence.com/');
5
  define('WORDFENCE_MAX_SCAN_TIME', 86400); //Increased this from 10 mins to 1 day because very big scans run for a long time. Users can use kill.
lib/wordfenceScanner.php CHANGED
@@ -36,7 +36,9 @@ class wordfenceScanner {
36
  }
37
  private function setupSigs(){
38
  $this->api = new wfAPI($this->apiKey, $this->wordpressVersion);
39
- $sigData = $this->api->call('get_patterns', array(), array());
 
 
40
  if(! (is_array($sigData) && isset($sigData['sigPattern'])) ){
41
  throw new Exception("Wordfence could not get the attack signature patterns from the scanning server.");
42
  }
@@ -50,17 +52,18 @@ class wordfenceScanner {
50
  $this->lastStatusTime = microtime(true);
51
  }
52
  $db = new wfDB();
53
- $keepGoing = true;
54
- $limitOffset = 0;
55
- $queryChunkSize = 1000;
56
- while($keepGoing){
57
- $keepGoing = false;
58
- $res1 = $db->querySelect("select filename, filenameMD5, hex(newMD5) as newMD5 from " . $db->prefix() . "wfFileMods where oldMD5 != newMD5 and knownFile=0 limit $limitOffset , $queryChunkSize");
59
- if(sizeof($res1) > 0){
60
- $keepGoing = true;
61
- $limitOffset += $queryChunkSize;
 
 
62
  }
63
-
64
  foreach($res1 as $rec1){
65
  $db->queryWrite("update " . $db->prefix() . "wfFileMods set oldMD5 = newMD5 where filenameMD5='%s'", $rec1['filenameMD5']); //A way to mark as scanned so that if we come back from a sleep we don't rescan this one.
66
  $file = $rec1['filename'];
@@ -134,8 +137,8 @@ class wordfenceScanner {
134
  'severity' => 1,
135
  'ignoreP' => $this->path . $file,
136
  'ignoreC' => $fileSum,
137
- 'shortMsg' => "This file appears to be an attack shell",
138
- 'longMsg' => "This file appears to be an executable shell that allows hackers entry to your site via a backdoor. If you know about this file you can choose to ignore it to exclude it from future scans. The text we found in this file that matches a known malicious file is: <strong style=\"color: #F00;\">\"" . $matches[1] . "\"</strong>.",
139
  'data' => array(
140
  'file' => $file,
141
  'canDiff' => false,
36
  }
37
  private function setupSigs(){
38
  $this->api = new wfAPI($this->apiKey, $this->wordpressVersion);
39
+ $sigData = $this->api->call('get_patterns', array(), array());
40
+ //For testing, comment out above two, include server sig file and get local sigs
41
+ //$sigData = wfSigs::getSigData();
42
  if(! (is_array($sigData) && isset($sigData['sigPattern'])) ){
43
  throw new Exception("Wordfence could not get the attack signature patterns from the scanning server.");
44
  }
52
  $this->lastStatusTime = microtime(true);
53
  }
54
  $db = new wfDB();
55
+ $lastCount = 'whatever';
56
+ while(true){
57
+ $thisCount = $db->querySingle("select count(*) from " . $db->prefix() . "wfFileMods where oldMD5 != newMD5 and knownFile=0");
58
+ if($thisCount == $lastCount){
59
+ //count should always be decreasing. If not, we're in an infinite loop so lets catch it early
60
+ break;
61
+ }
62
+ $lastCount = $thisCount;
63
+ $res1 = $db->querySelect("select filename, filenameMD5, hex(newMD5) as newMD5 from " . $db->prefix() . "wfFileMods where oldMD5 != newMD5 and knownFile=0 limit 500");
64
+ if(sizeof($res1) < 1){
65
+ break;
66
  }
 
67
  foreach($res1 as $rec1){
68
  $db->queryWrite("update " . $db->prefix() . "wfFileMods set oldMD5 = newMD5 where filenameMD5='%s'", $rec1['filenameMD5']); //A way to mark as scanned so that if we come back from a sleep we don't rescan this one.
69
  $file = $rec1['filename'];
137
  'severity' => 1,
138
  'ignoreP' => $this->path . $file,
139
  'ignoreC' => $fileSum,
140
+ 'shortMsg' => "This file appears to be malicious",
141
+ 'longMsg' => "This file appears to be installed by a hacker to perform malicious activity. If you know about this file you can choose to ignore it to exclude it from future scans. The text we found in this file that matches a known malicious file is: <strong style=\"color: #F00;\">\"" . $matches[1] . "\"</strong>.",
142
  'data' => array(
143
  'file' => $file,
144
  'canDiff' => false,
readme.txt CHANGED
@@ -1,24 +1,26 @@
1
  === Wordfence Security ===
2
  Contributors: mmaunder
3
- Tags: wordpress, security, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure
4
  Requires at least: 3.3.1
5
  Tested up to: 3.5.1
6
- Stable tag: 3.7.2
7
 
8
  Wordfence Security is a free enterprise class security plugin that includes a firewall, virus scanning, real-time traffic with geolocation and more.
9
 
10
  == Description ==
11
 
12
- Wordfence Security is a free enterprise class security plugin that includes a firewall, anti-virus scanning, malicious URL scanning and live traffic including crawlers. Wordfence is the only WordPress security plugin that can verify and repair your core, theme and plugin files, even if you don't have backups.
13
 
14
- Wordfence is now Multi-Site compatible.
15
 
16
  [Remember to visit our support forums if you have questions or comments.](http://wordfence.com/forums/)
17
 
18
- Wordfence is 100% free. We also offer a Premium API key that lets you block countries and schedule scans for specific times.
19
 
20
  Wordfence:
21
 
 
 
22
  * Scans core files, themes and plugins against WordPress.org repository versions to check their integrity.
23
  * Includes a firewall to block common security threats like fake Googlebots, malicious scans from hackers and botnets.
24
  * Includes advanced IP and Domain WHOIS to report malicious IP's or networks and block entire networks using the firewall.
@@ -70,7 +72,7 @@ To install Wordfence on WordPress Multi-Site installations (support is currently
70
 
71
  == Frequently Asked Questions ==
72
 
73
- [Remember to visit our support forums if you have questions or comments.](http://wordfence.com/forums/)
74
 
75
  = What does Wordfence Security do that other WordPress security plugins don't do? =
76
 
@@ -137,13 +139,6 @@ version of timthumb (which the creator of Wordfence wrote and donated to the tim
137
  caused the problem. However we do scan for old version of timthumb for good measure to make sure they don't
138
  cause a security hole on your site.
139
 
140
- = People keep telling me that WordPress itself has security problems. Is that true? =
141
-
142
- In general, no it's not. The WordPress team work very hard to keep the awesome software they have produced secure and in the
143
- rare cases when a security hole is found, they fix it very quickly. Most responsible plugin authors also fix security holes
144
- as soon as they are told about them. That's why Wordfence will warn you if you're running an old version of WordPress, a plugin
145
- or a theme, because often these have been updated to fix a security hole.
146
-
147
 
148
  == Screenshots ==
149
 
@@ -155,6 +150,14 @@ or a theme, because often these have been updated to fix a security hole.
155
 
156
  == Changelog ==
157
 
 
 
 
 
 
 
 
 
158
  = 3.7.2 =
159
  * Fixed issue that caused locked out IP's to not appear, or to appear with incorrect "locked out until" time.
160
 
1
  === Wordfence Security ===
2
  Contributors: mmaunder
3
+ Tags: wordpress, security, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure, two factor, cellphone sign-in, cellphone signin, cellphone, twofactor
4
  Requires at least: 3.3.1
5
  Tested up to: 3.5.1
6
+ Stable tag: 3.8.1
7
 
8
  Wordfence Security is a free enterprise class security plugin that includes a firewall, virus scanning, real-time traffic with geolocation and more.
9
 
10
  == Description ==
11
 
12
+ Wordfence Security is a free enterprise class security plugin that includes a firewall, anti-virus scanning, cellphone sign-in (two factor authentication), malicious URL scanning and live traffic including crawlers. Wordfence is the only WordPress security plugin that can verify and repair your core, theme and plugin files, even if you don't have backups.
13
 
14
+ Wordfence is now Multi-Site compatible and includes Cellphone Sign-in which permanently secures your website from brute force hacks.
15
 
16
  [Remember to visit our support forums if you have questions or comments.](http://wordfence.com/forums/)
17
 
18
+ Wordfence is 100% free. We also offer a Premium API key that gives you Cellphone Sign-in via SMS, lets you block countries and schedule scans for specific times.
19
 
20
  Wordfence:
21
 
22
+ * Sign-in using your password and your cellphone. This is called Two Factor Authentication and is used by banks, government agencies and military world-wide.
23
+ * Enforce strong passwords among your administrators, publishers and users.
24
  * Scans core files, themes and plugins against WordPress.org repository versions to check their integrity.
25
  * Includes a firewall to block common security threats like fake Googlebots, malicious scans from hackers and botnets.
26
  * Includes advanced IP and Domain WHOIS to report malicious IP's or networks and block entire networks using the firewall.
72
 
73
  == Frequently Asked Questions ==
74
 
75
+ [Visit our Website FAQ which is more comprehensive and updated frequently.](http://www.wordfence.com/docs/frequently-asked-questions/)
76
 
77
  = What does Wordfence Security do that other WordPress security plugins don't do? =
78
 
139
  caused the problem. However we do scan for old version of timthumb for good measure to make sure they don't
140
  cause a security hole on your site.
141
 
 
 
 
 
 
 
 
142
 
143
  == Screenshots ==
144
 
150
 
151
  == Changelog ==
152
 
153
+ = 3.8.1 =
154
+ * Added Cellphone Sign-in (Two Factor Authentication) for paid members. Stop brute-force attacks permanently! See new "Cellphone Sign-in" menu option.
155
+ * Added ability to enforce strong passwords when accounts are created or users change their password. See Wordfence 'options' page under 'Login Security Options'.
156
+ * Added new backdoor/malware signatures including detection for spamming scripts, youtube spam scripts and a new attack shell.
157
+ * Fixed issue: Under some conditions, files not part of core or a known theme or plugin would be excluded from a scan.
158
+ * Fixes from Juliette R. F. Remove warnings for unset variables. Fix options 'save' spinner spinning infinitely on some platforms. Removed redundant error handling code.
159
+ * Added ability to downgrade a paid license to free.
160
+
161
  = 3.7.2 =
162
  * Fixed issue that caused locked out IP's to not appear, or to appear with incorrect "locked out until" time.
163
 
wordfence.php CHANGED
@@ -4,10 +4,10 @@ Plugin Name: Wordfence Security
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
  Author: Mark Maunder
7
- Version: 3.7.2
8
  Author URI: http://www.wordfence.com/
9
  */
10
- define('WORDFENCE_VERSION', '3.7.2');
11
  if(get_option('wordfenceActivated') != 1){
12
  add_action('activated_plugin','wordfence_save_activation_error'); function wordfence_save_activation_error(){ update_option('wf_plugin_act_error', ob_get_contents()); }
13
  }
4
  Plugin URI: http://www.wordfence.com/
5
  Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
  Author: Mark Maunder
7
+ Version: 3.8.1
8
  Author URI: http://www.wordfence.com/
9
  */
10
+ define('WORDFENCE_VERSION', '3.8.1');
11
  if(get_option('wordfenceActivated') != 1){
12
  add_action('activated_plugin','wordfence_save_activation_error'); function wordfence_save_activation_error(){ update_option('wf_plugin_act_error', ob_get_contents()); }
13
  }