Version Description
- Fixed bug that caused IP range blocking to not block.
- Fixed bug that caused unblocking a permanently blocked IP to work, but not refresh the list.
- Added usernames to the email you receive when a user is locked out.
- Added a few more status messages for URL malware scanning.
- Removed the sockets function call from connection testing because some hosts don't allow calls to socket_create()
- Added detection in the Whois page to check if the server has the fsockopen() function available with helpful message if it's disabled.
- Whitelisted IP's now override country blocking and range blocking.
- Removed Bluehost affiliate links for free customers
- Fixed issue that caused scans to crash when checking URLs for malware.
- Fixed issue that caused scans with large numbers of posts that contain the same URL to crash.
- Updated the GeoIP database for country blocking to newest version.
Download this release
Release Info
Developer | mmaunder |
Plugin | Wordfence Security – Firewall & Malware Scan |
Version | 3.6.8 |
Comparing to | |
See all releases |
Code changes from version 3.6.7 to 3.6.8
- css/main.css +0 -14
- js/admin.js +8 -0
- lib/GeoIP.dat +0 -0
- lib/conntest.php +0 -25
- lib/menu_blockedIPs.php +1 -1
- lib/menu_whois.php +19 -0
- lib/pageTitle.php +0 -17
- lib/wfAPI.php +4 -1
- lib/wfLog.php +9 -5
- lib/wordfenceClass.php +3 -3
- lib/wordfenceScanner.php +139 -128
- lib/wordfenceURLHoover.php +11 -7
- readme.txt +14 -1
- wordfence.php +2 -2
css/main.css
CHANGED
@@ -43,20 +43,6 @@ div.wordfenceLive p {
|
|
43 |
#wfHeading {
|
44 |
white-space: nowrap;
|
45 |
}
|
46 |
-
a.bluehostBanner {
|
47 |
-
width: 120px;
|
48 |
-
height: 60px;
|
49 |
-
padding: 0;
|
50 |
-
margin: 0px 0 0px 0px;
|
51 |
-
display: block;
|
52 |
-
border-width: 0;
|
53 |
-
}
|
54 |
-
a.bluehostBanner1 { background-image: url(../images/bh_120x60_01.gif); }
|
55 |
-
a.bluehostBanner2 { background-image: url(../images/bh_120x60_02.gif); }
|
56 |
-
a.bluehostBanner3 { background-image: url(../images/bh_120x60_03.gif); }
|
57 |
-
a.bluehostBanner4 { background-image: url(../images/bh_120x60_04.gif); }
|
58 |
-
a.bluehostBanner5 { background-image: url(../images/bh_120x60_05.gif); }
|
59 |
-
|
60 |
div.wordfence-lock-icon {
|
61 |
background-image: url(../images/wordfence-logo-32x32.png);
|
62 |
}
|
43 |
#wfHeading {
|
44 |
white-space: nowrap;
|
45 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
div.wordfence-lock-icon {
|
47 |
background-image: url(../images/wordfence-logo-32x32.png);
|
48 |
}
|
js/admin.js
CHANGED
@@ -1021,6 +1021,14 @@ window['wordfenceAdmin'] = {
|
|
1021 |
self.reloadActivities();
|
1022 |
});
|
1023 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1024 |
permBlockIP: function(IP){
|
1025 |
var self = this;
|
1026 |
this.ajax('wordfence_permBlockIP', {
|
1021 |
self.reloadActivities();
|
1022 |
});
|
1023 |
},
|
1024 |
+
unblockIPTwo: function(IP){
|
1025 |
+
var self = this;
|
1026 |
+
this.ajax('wordfence_unblockIP', {
|
1027 |
+
IP: IP
|
1028 |
+
}, function(res){
|
1029 |
+
self.staticTabChanged();
|
1030 |
+
});
|
1031 |
+
},
|
1032 |
permBlockIP: function(IP){
|
1033 |
var self = this;
|
1034 |
this.ajax('wordfence_permBlockIP', {
|
lib/GeoIP.dat
CHANGED
Binary file
|
lib/conntest.php
CHANGED
@@ -8,31 +8,6 @@
|
|
8 |
<br /><br />
|
9 |
DNS lookup for noc1.wordfence.com returns: <?php echo gethostbyname('noc1.wordfence.com'); ?><br /><br />
|
10 |
<?php
|
11 |
-
function testSocket($service_port){
|
12 |
-
echo "<b>STARTING SOCKET TEST TO PORT $service_port</b><br />\n";
|
13 |
-
error_reporting(E_ALL);
|
14 |
-
//$service_port = getservbyname('www', 'tcp');
|
15 |
-
$address = gethostbyname('noc1.wordfence.com');
|
16 |
-
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
17 |
-
if($socket === false){
|
18 |
-
echo "Could not create socket: " . socket_strerror(socket_last_error()) . "<br />\n";
|
19 |
-
} else {
|
20 |
-
echo "Socket created OK<br />\n";
|
21 |
-
}
|
22 |
-
echo "Attempting to connect to '$address' on port '$service_port'...";
|
23 |
-
$result = socket_connect($socket, $address, $service_port);
|
24 |
-
if($result === false){
|
25 |
-
echo "socket_connect() failed.\nReason: ($result) " . socket_strerror(socket_last_error($socket)) . "<br /><br />\n";
|
26 |
-
} else {
|
27 |
-
echo "Socket connected OK to port $service_port<br /><br />\n";
|
28 |
-
}
|
29 |
-
socket_close($socket);
|
30 |
-
}
|
31 |
-
testSocket(80);
|
32 |
-
testSocket(443);
|
33 |
-
|
34 |
-
?>
|
35 |
-
<?php
|
36 |
$curlContent = "";
|
37 |
function curlWrite($h, $d){
|
38 |
global $curlContent;
|
8 |
<br /><br />
|
9 |
DNS lookup for noc1.wordfence.com returns: <?php echo gethostbyname('noc1.wordfence.com'); ?><br /><br />
|
10 |
<?php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
$curlContent = "";
|
12 |
function curlWrite($h, $d){
|
13 |
global $curlContent;
|
lib/menu_blockedIPs.php
CHANGED
@@ -118,7 +118,7 @@
|
|
118 |
{{/if}}
|
119 |
</div>
|
120 |
<div>
|
121 |
-
<strong>IP:</strong> <a href="${WFAD.makeIPTrafLink(IP)}" target="_blank">${IP}</a> [<a href="#" onclick="WFAD.
|
122 |
{{if permanent == '1'}}
|
123 |
[<span style="color: #F00;">permanently blocked</span>]
|
124 |
{{else}} [<a href="#" onclick="WFAD.permBlockIP('${IP}'); return false;">make permanent</a>]{{/if}}
|
118 |
{{/if}}
|
119 |
</div>
|
120 |
<div>
|
121 |
+
<strong>IP:</strong> <a href="${WFAD.makeIPTrafLink(IP)}" target="_blank">${IP}</a> [<a href="#" onclick="WFAD.unblockIPTwo('${IP}'); return false;">unblock</a>]
|
122 |
{{if permanent == '1'}}
|
123 |
[<span style="color: #F00;">permanently blocked</span>]
|
124 |
{{else}} [<a href="#" onclick="WFAD.permBlockIP('${IP}'); return false;">make permanent</a>]{{/if}}
|
lib/menu_whois.php
CHANGED
@@ -5,6 +5,21 @@
|
|
5 |
<p style="width: 600px;">
|
6 |
Wordfence WHOIS queries the WHOIS servers on the Internet and gets information about domain name or IP address owners. This helps you determine who is hacking your site and helps you report them to the relevant authorities. If you see a malicious IP address, do a WHOIS lookup, find out who is responsible for that IP address and send an email reporting them to the 'abuse' email address provided.<br /><br />
|
7 |
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
<p>
|
9 |
<input type="text" name="whois" id="wfwhois" value="" size="40" maxlength="255" onkeydown="if(event.keyCode == 13){ WFAD.whois(jQuery('#wfwhois').val()); }" /> <input type="button" name="whoisbutton" id="whoisbutton" class="button-primary" value="Look up IP or Domain" onclick="WFAD.whois(jQuery('#wfwhois').val());" />
|
10 |
|
@@ -64,3 +79,7 @@ if(whoisval){
|
|
64 |
</p>
|
65 |
</div>
|
66 |
</script>
|
|
|
|
|
|
|
|
5 |
<p style="width: 600px;">
|
6 |
Wordfence WHOIS queries the WHOIS servers on the Internet and gets information about domain name or IP address owners. This helps you determine who is hacking your site and helps you report them to the relevant authorities. If you see a malicious IP address, do a WHOIS lookup, find out who is responsible for that IP address and send an email reporting them to the 'abuse' email address provided.<br /><br />
|
7 |
</p>
|
8 |
+
<?php
|
9 |
+
if(! function_exists('fsockopen')){
|
10 |
+
?>
|
11 |
+
<p style="color: #F00; width: 600px;">
|
12 |
+
Sorry, but your web hosting provider has disabled the 'fsockopen' function on your WordPress server. That means you can't
|
13 |
+
use WHOIS. Please log a support call with them asking them to enable this function. Explain that you need it to be able to
|
14 |
+
perform Whois lookups on IP addresses which will allow you to determine who owns the IP's that are attacking your website.
|
15 |
+
<br /><br />
|
16 |
+
If you are hosting your own site, edit your php.ini config file and make sure 'fsockopen' does not appear
|
17 |
+
next to disable_functions in php.ini. You may have to restart your web server for the changes to take effect.
|
18 |
+
</p>
|
19 |
+
<?php
|
20 |
+
} else {
|
21 |
+
?>
|
22 |
+
</p>
|
23 |
<p>
|
24 |
<input type="text" name="whois" id="wfwhois" value="" size="40" maxlength="255" onkeydown="if(event.keyCode == 13){ WFAD.whois(jQuery('#wfwhois').val()); }" /> <input type="button" name="whoisbutton" id="whoisbutton" class="button-primary" value="Look up IP or Domain" onclick="WFAD.whois(jQuery('#wfwhois').val());" />
|
25 |
|
79 |
</p>
|
80 |
</div>
|
81 |
</script>
|
82 |
+
<?php
|
83 |
+
} //closing paren for function_exists('fsockopen')
|
84 |
+
?>
|
85 |
+
|
lib/pageTitle.php
CHANGED
@@ -1,18 +1 @@
|
|
1 |
-
<?php if(! wfConfig::get('isPaid')){
|
2 |
-
$affURL = 'http://www.bluehost.com/track/wordfence/wfplghd2?page=/wordpress';
|
3 |
-
?>
|
4 |
-
<table border="0">
|
5 |
-
<tr>
|
6 |
-
<td style="padding-right: 50px;">
|
7 |
-
<div class="wordfence-lock-icon wordfence-icon32"><br /></div><h2 id="wfHeading"><?php echo $pageTitle; ?></h2>
|
8 |
-
</td><td style="width: 450px; padding-top: 10px;">
|
9 |
-
You're using the Free version of Wordfence which you can support by visiting <a href="<?php echo $affURL; ?>" target="_blank">Bluehost.com</a>. We recommend <a href="<?php echo $affURL; ?>" target="_blank">Bluehost for WordPress hosting</a> and use them for our own WordPress websites. <a href="<?php echo $affURL; ?>" target="_blank">»Visit Bluehost now»</a>
|
10 |
-
</td>
|
11 |
-
<td style="width: 120px; padding-top: 10px;">
|
12 |
-
<a href="<?php echo $affURL; ?>" target="_blank" class="bluehostBanner bluehostBanner<?php echo rand(1,5); ?>"></a>
|
13 |
-
</td>
|
14 |
-
</tr>
|
15 |
-
</table>
|
16 |
-
<?php } else { ?>
|
17 |
<div class="wordfence-lock-icon wordfence-icon32"><br /></div><h2 id="wfHeading"><?php echo $pageTitle; ?></h2>
|
18 |
-
<?php } ?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
<div class="wordfence-lock-icon wordfence-icon32"><br /></div><h2 id="wfHeading"><?php echo $pageTitle; ?></h2>
|
|
lib/wfAPI.php
CHANGED
@@ -52,8 +52,9 @@ class wfAPI {
|
|
52 |
curl_setopt ($curl, CURLOPT_WRITEFUNCTION, array($this, 'curlWrite'));
|
53 |
curl_setopt($curl, CURLOPT_POST, true);
|
54 |
curl_setopt($curl, CURLOPT_POSTFIELDS, $postParams);
|
55 |
-
|
56 |
$curlResult = curl_exec($curl);
|
|
|
57 |
$httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
58 |
$this->lastCurlErrorNo = curl_errno($curl);
|
59 |
if($httpStatus == 200){
|
@@ -65,6 +66,7 @@ class wfAPI {
|
|
65 |
throw new Exception("We received an error response when trying to contact the Wordfence scanning servers. The HTTP status code was [$httpStatus] and the curl error number was [" . $this->lastCurlErrorNo . "] " . ($cerror ? (' and the error from CURL was: ' . $cerror) : ''));
|
66 |
}
|
67 |
} else {
|
|
|
68 |
$data = $this->fileGet($url, $postParams);
|
69 |
if($data === false){
|
70 |
$err = error_get_last();
|
@@ -117,6 +119,7 @@ class wfAPI {
|
|
117 |
curl_setopt($curl, CURLOPT_POSTFIELDS, array());
|
118 |
}
|
119 |
$data = curl_exec($curl);
|
|
|
120 |
$httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
121 |
if($httpStatus != 200){
|
122 |
$cError = curl_error($curl);
|
52 |
curl_setopt ($curl, CURLOPT_WRITEFUNCTION, array($this, 'curlWrite'));
|
53 |
curl_setopt($curl, CURLOPT_POST, true);
|
54 |
curl_setopt($curl, CURLOPT_POSTFIELDS, $postParams);
|
55 |
+
wordfence::status(4, 'info', "CURL fetching URL: " . $url);
|
56 |
$curlResult = curl_exec($curl);
|
57 |
+
|
58 |
$httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
59 |
$this->lastCurlErrorNo = curl_errno($curl);
|
60 |
if($httpStatus == 200){
|
66 |
throw new Exception("We received an error response when trying to contact the Wordfence scanning servers. The HTTP status code was [$httpStatus] and the curl error number was [" . $this->lastCurlErrorNo . "] " . ($cerror ? (' and the error from CURL was: ' . $cerror) : ''));
|
67 |
}
|
68 |
} else {
|
69 |
+
wordfence::status(4, 'info', "Fetching URL with file_get: " . $url);
|
70 |
$data = $this->fileGet($url, $postParams);
|
71 |
if($data === false){
|
72 |
$err = error_get_last();
|
119 |
curl_setopt($curl, CURLOPT_POSTFIELDS, array());
|
120 |
}
|
121 |
$data = curl_exec($curl);
|
122 |
+
|
123 |
$httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
124 |
if($httpStatus != 200){
|
125 |
$cError = curl_error($curl);
|
lib/wfLog.php
CHANGED
@@ -233,7 +233,7 @@ class wfLog {
|
|
233 |
}
|
234 |
public function lockOutIP($IP, $reason){
|
235 |
if($this->isWhitelisted($IP)){ return false; }
|
236 |
-
$this->getDB()->queryWrite("insert into " . $this->lockOutTable . " (IP, blockedTime, reason) values(%s, unix_timestamp(), '%s') ON DUPLICATE KEY update blockedTime=unix_timestamp(), reason='%s'",
|
237 |
wfUtils::inet_aton($IP),
|
238 |
$reason,
|
239 |
$reason
|
@@ -518,6 +518,11 @@ class wfLog {
|
|
518 |
return $this->db;
|
519 |
}
|
520 |
public function firewallBadIPs(){
|
|
|
|
|
|
|
|
|
|
|
521 |
|
522 |
//New range and UA pattern blocking:
|
523 |
$r1 = $this->getDB()->querySelect("select id, blockType, blockString from " . $this->ipRangesTable);
|
@@ -587,7 +592,7 @@ class wfLog {
|
|
587 |
} else if(strpos($_SERVER['REQUEST_URI'], '/wp-login.php') !== false && (! wfConfig::get('cbl_loginFormBlocked', false)) ){ //It's the login form and we're allowing that
|
588 |
//Do nothing
|
589 |
} else {
|
590 |
-
if($country = wfUtils::IP2Country(
|
591 |
foreach(explode(',', $blockedCountries) as $blocked){
|
592 |
if(strtoupper($blocked) == strtoupper($country)){ //At this point we know the user has been blocked
|
593 |
if(wfConfig::get('cbl_action') == 'redir'){
|
@@ -613,9 +618,8 @@ class wfLog {
|
|
613 |
}
|
614 |
}
|
615 |
|
616 |
-
$IP =
|
617 |
-
|
618 |
-
$this->getDB()->queryWrite("update " . $this->blocksTable . " set lastAttempt=unix_timestamp(), blockedHits = blockedHits + 1 where IP=%s", $IP);
|
619 |
$now = $this->getDB()->querySingle("select unix_timestamp()");
|
620 |
$secsToGo = ($rec['blockedTime'] + wfConfig::get('blockedTime')) - $now;
|
621 |
$this->do503($secsToGo, $rec['reason']);
|
233 |
}
|
234 |
public function lockOutIP($IP, $reason){
|
235 |
if($this->isWhitelisted($IP)){ return false; }
|
236 |
+
$this->getDB()->queryWrite("insert into " . $this->lockOutTable . " (IP, blockedTime, reason) values (%s, unix_timestamp(), '%s') ON DUPLICATE KEY update blockedTime=unix_timestamp(), reason='%s'",
|
237 |
wfUtils::inet_aton($IP),
|
238 |
$reason,
|
239 |
$reason
|
518 |
return $this->db;
|
519 |
}
|
520 |
public function firewallBadIPs(){
|
521 |
+
$IP = wfUtils::getIP();
|
522 |
+
if($this->isWhitelisted($IP)){
|
523 |
+
return;
|
524 |
+
}
|
525 |
+
$IPnum = wfUtils::inet_aton($IP);
|
526 |
|
527 |
//New range and UA pattern blocking:
|
528 |
$r1 = $this->getDB()->querySelect("select id, blockType, blockString from " . $this->ipRangesTable);
|
592 |
} else if(strpos($_SERVER['REQUEST_URI'], '/wp-login.php') !== false && (! wfConfig::get('cbl_loginFormBlocked', false)) ){ //It's the login form and we're allowing that
|
593 |
//Do nothing
|
594 |
} else {
|
595 |
+
if($country = wfUtils::IP2Country($IP) ){
|
596 |
foreach(explode(',', $blockedCountries) as $blocked){
|
597 |
if(strtoupper($blocked) == strtoupper($country)){ //At this point we know the user has been blocked
|
598 |
if(wfConfig::get('cbl_action') == 'redir'){
|
618 |
}
|
619 |
}
|
620 |
|
621 |
+
if($rec = $this->getDB()->querySingleRec("select blockedTime, reason from " . $this->blocksTable . " where IP=%s and (permanent=1 OR (blockedTime + %s > unix_timestamp()))", $IPnum, wfConfig::get('blockedTime'))){
|
622 |
+
$this->getDB()->queryWrite("update " . $this->blocksTable . " set lastAttempt=unix_timestamp(), blockedHits = blockedHits + 1 where IP=%s", $IPnum);
|
|
|
623 |
$now = $this->getDB()->querySingle("select unix_timestamp()");
|
624 |
$secsToGo = ($rec['blockedTime'] + wfConfig::get('blockedTime')) - $now;
|
625 |
$this->do503($secsToGo, $rec['reason']);
|
lib/wordfenceClass.php
CHANGED
@@ -363,7 +363,7 @@ class wordfence {
|
|
363 |
$forgotAttempts = 1;
|
364 |
}
|
365 |
if($forgotAttempts >= wfConfig::get('loginSec_maxForgotPasswd')){
|
366 |
-
self::lockOutIP($IP, "Exceeded the maximum number of tries to recover their password which is set at: " . wfConfig::get('loginSec_maxForgotPasswd'));
|
367 |
require('wfLockedOut.php');
|
368 |
}
|
369 |
set_transient($tKey, $forgotAttempts, wfConfig::get('loginSec_countFailMins') * 60);
|
@@ -494,7 +494,7 @@ class wordfence {
|
|
494 |
}
|
495 |
if(wfConfig::get('loginSecurityEnabled')){
|
496 |
if(is_wp_error($authResult) && $authResult->get_error_code() == 'invalid_username' && wfConfig::get('loginSec_lockInvalidUsers')){
|
497 |
-
self::lockOutIP($IP, "Used an invalid username to try to sign in.");
|
498 |
require('wfLockedOut.php');
|
499 |
}
|
500 |
$tKey = 'wflginfl_' . wfUtils::inet_aton($IP);
|
@@ -506,7 +506,7 @@ class wordfence {
|
|
506 |
$tries = 1;
|
507 |
}
|
508 |
if($tries >= wfConfig::get('loginSec_maxFailures')){
|
509 |
-
self::lockOutIP($IP, "Exceeded the maximum number of login failures which is: " . wfConfig::get('loginSec_maxFailures'));
|
510 |
require('wfLockedOut.php');
|
511 |
}
|
512 |
set_transient($tKey, $tries, wfConfig::get('loginSec_countFailMins') * 60);
|
363 |
$forgotAttempts = 1;
|
364 |
}
|
365 |
if($forgotAttempts >= wfConfig::get('loginSec_maxForgotPasswd')){
|
366 |
+
self::lockOutIP($IP, "Exceeded the maximum number of tries to recover their password which is set at: " . wfConfig::get('loginSec_maxForgotPasswd') . ". The last username or email they entered before getting locked out was: '" . $_POST['user_login'] . "'");
|
367 |
require('wfLockedOut.php');
|
368 |
}
|
369 |
set_transient($tKey, $forgotAttempts, wfConfig::get('loginSec_countFailMins') * 60);
|
494 |
}
|
495 |
if(wfConfig::get('loginSecurityEnabled')){
|
496 |
if(is_wp_error($authResult) && $authResult->get_error_code() == 'invalid_username' && wfConfig::get('loginSec_lockInvalidUsers')){
|
497 |
+
self::lockOutIP($IP, "Used an invalid username '" . $_POST['log'] . "' to try to sign in.");
|
498 |
require('wfLockedOut.php');
|
499 |
}
|
500 |
$tKey = 'wflginfl_' . wfUtils::inet_aton($IP);
|
506 |
$tries = 1;
|
507 |
}
|
508 |
if($tries >= wfConfig::get('loginSec_maxFailures')){
|
509 |
+
self::lockOutIP($IP, "Exceeded the maximum number of login failures which is: " . wfConfig::get('loginSec_maxFailures') . ". The last username they tried to sign in with was: '" . $_POST['log'] . "'");
|
510 |
require('wfLockedOut.php');
|
511 |
}
|
512 |
set_transient($tKey, $tries, wfConfig::get('loginSec_countFailMins') * 60);
|
lib/wordfenceScanner.php
CHANGED
@@ -50,146 +50,157 @@ class wordfenceScanner {
|
|
50 |
$this->lastStatusTime = microtime(true);
|
51 |
}
|
52 |
$db = new wfDB();
|
53 |
-
$
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
$
|
58 |
-
|
59 |
-
if(
|
60 |
-
|
61 |
-
|
62 |
-
$fileExt = '';
|
63 |
-
if(preg_match('/\.([a-zA-Z\d\-]{1,7})$/', $file, $matches)){
|
64 |
-
$fileExt = strtolower($matches[1]);
|
65 |
-
}
|
66 |
-
$isPHP = false;
|
67 |
-
if(preg_match('/^(?:php|phtml|php\d+)$/', $fileExt)){
|
68 |
-
$isPHP = true;
|
69 |
}
|
70 |
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
//We should not need this check because files > 2 gigs are not hashed and therefore won't be received back as unknowns from the API server
|
76 |
-
//But we do it anyway to be safe.
|
77 |
-
wordfence::status(2, 'error', "Encountered file that is too large: $file - Skipping.");
|
78 |
-
continue;
|
79 |
-
}
|
80 |
-
$fsize = filesize($this->path . $file); //Checked if too big above
|
81 |
-
if($fsize > 1000000){
|
82 |
-
$fsize = sprintf('%.2f', ($fsize / 1000000)) . "M";
|
83 |
-
} else {
|
84 |
-
$fsize = $fsize . "B";
|
85 |
-
}
|
86 |
-
if(function_exists('memory_get_usage')){
|
87 |
-
wordfence::status(4, 'info', "Scanning contents: $file (Size:$fsize Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
|
88 |
-
} else {
|
89 |
-
wordfence::status(4, 'info', "Scanning contents: $file (Size: $fsize)");
|
90 |
-
}
|
91 |
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
$
|
100 |
-
|
101 |
-
|
102 |
-
break;
|
103 |
}
|
104 |
-
if($isPHP){
|
105 |
-
if(strpos($data, '$allowed'.'Sites') !== false && strpos($data, "define ('VER"."SION', '1.") !== false && strpos($data, "TimThum"."b script created by") !== false){
|
106 |
-
$this->addResult(array(
|
107 |
-
'type' => 'file',
|
108 |
-
'severity' => 1,
|
109 |
-
'ignoreP' => $this->path . $file,
|
110 |
-
'ignoreC' => $fileSum,
|
111 |
-
'shortMsg' => "File is an old version of TimThumb which is vulnerable.",
|
112 |
-
'longMsg' => "This file appears to be an old version of the TimThumb script which makes your system vulnerable to attackers. Please upgrade the theme or plugin that uses this or remove it.",
|
113 |
-
'data' => array(
|
114 |
-
'file' => $file,
|
115 |
-
'canDiff' => false,
|
116 |
-
'canFix' => false,
|
117 |
-
'canDelete' => true
|
118 |
-
)
|
119 |
-
));
|
120 |
-
break;
|
121 |
-
} else if(strpos($file, 'lib/wordfenceScanner.php') === false && preg_match($this->patterns['sigPattern'], $data, $matches)){
|
122 |
-
$this->addResult(array(
|
123 |
-
'type' => 'file',
|
124 |
-
'severity' => 1,
|
125 |
-
'ignoreP' => $this->path . $file,
|
126 |
-
'ignoreC' => $fileSum,
|
127 |
-
'shortMsg' => "This file appears to be an attack shell",
|
128 |
-
'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>.",
|
129 |
-
'data' => array(
|
130 |
-
'file' => $file,
|
131 |
-
'canDiff' => false,
|
132 |
-
'canFix' => false,
|
133 |
-
'canDelete' => true
|
134 |
-
)
|
135 |
-
));
|
136 |
-
break;
|
137 |
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
break;
|
157 |
}
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
break;
|
175 |
}
|
176 |
-
$this->urlHoover->hoover($file, $data);
|
177 |
-
} else {
|
178 |
-
$this->urlHoover->hoover($file, $data);
|
179 |
}
|
180 |
-
|
181 |
-
|
182 |
-
|
|
|
|
|
|
|
183 |
}
|
|
|
184 |
}
|
185 |
-
fclose($fh);
|
186 |
-
$mtime = sprintf("%.5f", microtime(true) - $stime);
|
187 |
-
$this->totalFilesScanned++;
|
188 |
-
if(microtime(true) - $this->lastStatusTime > 1){
|
189 |
-
$this->lastStatusTime = microtime(true);
|
190 |
-
$this->writeScanningStatus();
|
191 |
-
}
|
192 |
-
$forkObj->forkIfNeeded();
|
193 |
}
|
194 |
$this->writeScanningStatus();
|
195 |
wordfence::status(2, 'info', "Asking Wordfence to check URL's against malware list.");
|
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'];
|
67 |
+
$fileSum = $rec1['newMD5'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
+
if(! file_exists($this->path . $file)){
|
70 |
+
continue;
|
71 |
+
}
|
72 |
+
$fileExt = '';
|
73 |
+
if(preg_match('/\.([a-zA-Z\d\-]{1,7})$/', $file, $matches)){
|
74 |
+
$fileExt = strtolower($matches[1]);
|
75 |
+
}
|
76 |
+
$isPHP = false;
|
77 |
+
if(preg_match('/^(?:php|phtml|php\d+)$/', $fileExt)){
|
78 |
+
$isPHP = true;
|
|
|
79 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
|
81 |
+
if(preg_match('/^(?:jpg|jpeg|mp3|avi|m4v|gif|png)$/', $fileExt)){
|
82 |
+
continue;
|
83 |
+
}
|
84 |
+
if(wfUtils::fileTooBig($this->path . $file)){ //We can't use filesize on 32 bit systems for files > 2 gigs
|
85 |
+
//We should not need this check because files > 2 gigs are not hashed and therefore won't be received back as unknowns from the API server
|
86 |
+
//But we do it anyway to be safe.
|
87 |
+
wordfence::status(2, 'error', "Encountered file that is too large: $file - Skipping.");
|
88 |
+
continue;
|
89 |
+
}
|
90 |
+
$fsize = filesize($this->path . $file); //Checked if too big above
|
91 |
+
if($fsize > 1000000){
|
92 |
+
$fsize = sprintf('%.2f', ($fsize / 1000000)) . "M";
|
93 |
+
} else {
|
94 |
+
$fsize = $fsize . "B";
|
95 |
+
}
|
96 |
+
if(function_exists('memory_get_usage')){
|
97 |
+
wordfence::status(4, 'info', "Scanning contents: $file (Size:$fsize Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
|
98 |
+
} else {
|
99 |
+
wordfence::status(4, 'info', "Scanning contents: $file (Size: $fsize)");
|
100 |
+
}
|
101 |
+
|
102 |
+
$stime = microtime(true);
|
103 |
+
$fh = @fopen($this->path . $file, 'r');
|
104 |
+
if(! $fh){
|
105 |
+
continue;
|
106 |
+
}
|
107 |
+
$totalRead = 0;
|
108 |
+
while(! feof($fh)){
|
109 |
+
$data = fread($fh, 1 * 1024 * 1024); //read 1 megs max per chunk
|
110 |
+
$totalRead += strlen($data);
|
111 |
+
if($totalRead < 1){
|
112 |
break;
|
113 |
}
|
114 |
+
if($isPHP){
|
115 |
+
if(strpos($data, '$allowed'.'Sites') !== false && strpos($data, "define ('VER"."SION', '1.") !== false && strpos($data, "TimThum"."b script created by") !== false){
|
116 |
+
$this->addResult(array(
|
117 |
+
'type' => 'file',
|
118 |
+
'severity' => 1,
|
119 |
+
'ignoreP' => $this->path . $file,
|
120 |
+
'ignoreC' => $fileSum,
|
121 |
+
'shortMsg' => "File is an old version of TimThumb which is vulnerable.",
|
122 |
+
'longMsg' => "This file appears to be an old version of the TimThumb script which makes your system vulnerable to attackers. Please upgrade the theme or plugin that uses this or remove it.",
|
123 |
+
'data' => array(
|
124 |
+
'file' => $file,
|
125 |
+
'canDiff' => false,
|
126 |
+
'canFix' => false,
|
127 |
+
'canDelete' => true
|
128 |
+
)
|
129 |
+
));
|
130 |
+
break;
|
131 |
+
} else if(strpos($file, 'lib/wordfenceScanner.php') === false && preg_match($this->patterns['sigPattern'], $data, $matches)){
|
132 |
+
$this->addResult(array(
|
133 |
+
'type' => 'file',
|
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,
|
142 |
+
'canFix' => false,
|
143 |
+
'canDelete' => true
|
144 |
+
)
|
145 |
+
));
|
146 |
+
break;
|
147 |
+
|
148 |
+
}
|
149 |
+
/*
|
150 |
+
$longestNospace = wfUtils::longestNospace($data);
|
151 |
+
if($longestNospace > 1000 && (strpos($data, $this->patterns['pat1']) !== false || preg_match('/preg_replace\([^\(]+\/[a-z]*e/', $data)) ){
|
152 |
+
$this->addResult(array(
|
153 |
+
'type' => 'file',
|
154 |
+
'severity' => 1,
|
155 |
+
'ignoreP' => $this->path . $file,
|
156 |
+
'ignoreC' => $fileSum,
|
157 |
+
'shortMsg' => "This file may contain malicious executable code",
|
158 |
+
'longMsg' => "This file is a PHP executable file and contains a line $longestNospace characters long without spaces that may be encoded data along with functions that may be used to execute that code. If you know about this file you can choose to ignore it to exclude it from future scans.",
|
159 |
+
'data' => array(
|
160 |
+
'file' => $file,
|
161 |
+
'canDiff' => false,
|
162 |
+
'canFix' => false,
|
163 |
+
'canDelete' => true
|
164 |
+
)
|
165 |
+
));
|
166 |
+
break;
|
167 |
+
}
|
168 |
+
*/
|
169 |
+
if(preg_match($this->patterns['pat2'], $data)){
|
170 |
+
$this->addResult(array(
|
171 |
+
'type' => 'file',
|
172 |
+
'severity' => 1,
|
173 |
+
'ignoreP' => $this->path . $file,
|
174 |
+
'ignoreC' => $fileSum,
|
175 |
+
'shortMsg' => "This file may contain malicious executable code",
|
176 |
+
'longMsg' => "This file is a PHP executable file and contains an " . $this->patterns['word1'] . " function and " . $this->patterns['word2'] . " decoding function on the same line. This is a common technique used by hackers to hide and execute code. If you know about this file you can choose to ignore it to exclude it from future scans.",
|
177 |
+
'data' => array(
|
178 |
+
'file' => $file,
|
179 |
+
'canDiff' => false,
|
180 |
+
'canFix' => false,
|
181 |
+
'canDelete' => true
|
182 |
+
)
|
183 |
+
));
|
184 |
+
break;
|
185 |
+
}
|
186 |
+
$this->urlHoover->hoover($file, $data);
|
187 |
+
} else {
|
188 |
+
$this->urlHoover->hoover($file, $data);
|
189 |
+
}
|
190 |
+
|
191 |
+
if($totalRead > 2 * 1024 * 1024){
|
192 |
break;
|
193 |
}
|
|
|
|
|
|
|
194 |
}
|
195 |
+
fclose($fh);
|
196 |
+
$mtime = sprintf("%.5f", microtime(true) - $stime);
|
197 |
+
$this->totalFilesScanned++;
|
198 |
+
if(microtime(true) - $this->lastStatusTime > 1){
|
199 |
+
$this->lastStatusTime = microtime(true);
|
200 |
+
$this->writeScanningStatus();
|
201 |
}
|
202 |
+
$forkObj->forkIfNeeded();
|
203 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
204 |
}
|
205 |
$this->writeScanningStatus();
|
206 |
wordfence::status(2, 'info', "Asking Wordfence to check URL's against malware list.");
|
lib/wordfenceURLHoover.php
CHANGED
@@ -158,7 +158,9 @@ class wordfenceURLHoover {
|
|
158 |
$this->dbg("Checking hostkey: " . bin2hex($key));
|
159 |
}
|
160 |
}
|
|
|
161 |
$resp = $this->api->binCall('check_host_keys', implode('', $allHostKeys));
|
|
|
162 |
$this->dbg("Done hostkey check");
|
163 |
|
164 |
$badHostKeys = array();
|
@@ -188,11 +190,15 @@ class wordfenceURLHoover {
|
|
188 |
}
|
189 |
if(sizeof($badHostKeys) > 0){
|
190 |
$urlsToCheck = array();
|
|
|
191 |
//need to figure out which id's have bad hostkeys
|
192 |
//need to feed in all URL's from those id's where the hostkey matches a URL
|
193 |
foreach($badHostKeys as $badHostKey){
|
194 |
if($this->useDB){
|
195 |
-
|
|
|
|
|
|
|
196 |
foreach($q1 as $rec){
|
197 |
$url = 'http://' . $rec['host'] . $rec['path'];
|
198 |
if(! isset($urlsToCheck[$rec['owner']])){
|
@@ -200,6 +206,7 @@ class wordfenceURLHoover {
|
|
200 |
}
|
201 |
if(! in_array($url, $urlsToCheck[$rec['owner']])){
|
202 |
$urlsToCheck[$rec['owner']][] = $url;
|
|
|
203 |
}
|
204 |
}
|
205 |
} else {
|
@@ -211,6 +218,7 @@ class wordfenceURLHoover {
|
|
211 |
}
|
212 |
if(! in_array($url, $urlsToCheck[$rec['owner']])){
|
213 |
$urlsToCheck[$rec['owner']][] = $url;
|
|
|
214 |
}
|
215 |
}
|
216 |
}
|
@@ -218,13 +226,9 @@ class wordfenceURLHoover {
|
|
218 |
}
|
219 |
|
220 |
if(sizeof($urlsToCheck) > 0){
|
221 |
-
|
222 |
-
if($this->debug){
|
223 |
-
foreach($urlsToCheck as $url){
|
224 |
-
$this->dbg("Checking URL: " . var_export($url, true));
|
225 |
-
}
|
226 |
-
}
|
227 |
$badURLs = $this->api->call('check_bad_urls', array(), array( 'toCheck' => json_encode($urlsToCheck)) );
|
|
|
228 |
$this->dbg("Done URL check");
|
229 |
if(is_array($badURLs) && sizeof($badURLs) > 0){
|
230 |
$finalResults = array();
|
158 |
$this->dbg("Checking hostkey: " . bin2hex($key));
|
159 |
}
|
160 |
}
|
161 |
+
wordfence::status(2, 'info', "Checking " . sizeof($allHostKeys) . " host keys against Wordfence scanning servers.");
|
162 |
$resp = $this->api->binCall('check_host_keys', implode('', $allHostKeys));
|
163 |
+
wordfence::status(2, 'info', "Done host key check.");
|
164 |
$this->dbg("Done hostkey check");
|
165 |
|
166 |
$badHostKeys = array();
|
190 |
}
|
191 |
if(sizeof($badHostKeys) > 0){
|
192 |
$urlsToCheck = array();
|
193 |
+
$totalURLs = 0;
|
194 |
//need to figure out which id's have bad hostkeys
|
195 |
//need to feed in all URL's from those id's where the hostkey matches a URL
|
196 |
foreach($badHostKeys as $badHostKey){
|
197 |
if($this->useDB){
|
198 |
+
//Putting a 10000 limit in here for sites that have a huge number of items with the same URL that repeats.
|
199 |
+
// This is an edge case. But if the URLs are malicious then presumably the admin will fix the malicious URLs
|
200 |
+
// and on subsequent scans the items (owners) that are above the 10000 limit will appear.
|
201 |
+
$q1 = $this->db->querySelect("select owner, host, path from $this->table where hostKey='%s' limit 10000", $badHostKey);
|
202 |
foreach($q1 as $rec){
|
203 |
$url = 'http://' . $rec['host'] . $rec['path'];
|
204 |
if(! isset($urlsToCheck[$rec['owner']])){
|
206 |
}
|
207 |
if(! in_array($url, $urlsToCheck[$rec['owner']])){
|
208 |
$urlsToCheck[$rec['owner']][] = $url;
|
209 |
+
$totalURLs++;
|
210 |
}
|
211 |
}
|
212 |
} else {
|
218 |
}
|
219 |
if(! in_array($url, $urlsToCheck[$rec['owner']])){
|
220 |
$urlsToCheck[$rec['owner']][] = $url;
|
221 |
+
$totalURLs++;
|
222 |
}
|
223 |
}
|
224 |
}
|
226 |
}
|
227 |
|
228 |
if(sizeof($urlsToCheck) > 0){
|
229 |
+
wordfence::status(2, 'info', "Checking " . $totalURLs . " URLs from " . sizeof($urlsToCheck) . " sources.");
|
|
|
|
|
|
|
|
|
|
|
230 |
$badURLs = $this->api->call('check_bad_urls', array(), array( 'toCheck' => json_encode($urlsToCheck)) );
|
231 |
+
wordfence::status(2, 'info', "Done URL check.");
|
232 |
$this->dbg("Done URL check");
|
233 |
if(is_array($badURLs) && sizeof($badURLs) > 0){
|
234 |
$finalResults = array();
|
readme.txt
CHANGED
@@ -3,7 +3,7 @@ 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.6.
|
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 |
|
@@ -155,6 +155,19 @@ or a theme, because often these have been updated to fix a security hole.
|
|
155 |
|
156 |
== Changelog ==
|
157 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
= 3.6.7 =
|
159 |
* Improved security for Cloudflare customers to prevent spoofing attacks and protect when a hacker bypasses Cloudflare proxies.
|
160 |
* Added clear explanation of what increasing AJAX polling time does on options page.
|
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.6.8
|
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 |
|
155 |
|
156 |
== Changelog ==
|
157 |
|
158 |
+
= 3.6.8 =
|
159 |
+
* Fixed bug that caused IP range blocking to not block.
|
160 |
+
* Fixed bug that caused unblocking a permanently blocked IP to work, but not refresh the list.
|
161 |
+
* Added usernames to the email you receive when a user is locked out.
|
162 |
+
* Added a few more status messages for URL malware scanning.
|
163 |
+
* Removed the sockets function call from connection testing because some hosts don't allow calls to socket_create()
|
164 |
+
* Added detection in the Whois page to check if the server has the fsockopen() function available with helpful message if it's disabled.
|
165 |
+
* Whitelisted IP's now override country blocking and range blocking.
|
166 |
+
* Removed Bluehost affiliate links for free customers
|
167 |
+
* Fixed issue that caused scans to crash when checking URLs for malware.
|
168 |
+
* Fixed issue that caused scans with large numbers of posts that contain the same URL to crash.
|
169 |
+
* Updated the GeoIP database for country blocking to newest version.
|
170 |
+
|
171 |
= 3.6.7 =
|
172 |
* Improved security for Cloudflare customers to prevent spoofing attacks and protect when a hacker bypasses Cloudflare proxies.
|
173 |
* Added clear explanation of what increasing AJAX polling time does on options page.
|
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.6.
|
8 |
Author URI: http://www.wordfence.com/
|
9 |
*/
|
10 |
-
define('WORDFENCE_VERSION', '3.6.
|
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.6.8
|
8 |
Author URI: http://www.wordfence.com/
|
9 |
*/
|
10 |
+
define('WORDFENCE_VERSION', '3.6.8');
|
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 |
}
|