Wordfence Security – Firewall & Malware Scan - Version 2.0.1

Version Description

  • Improved scanning for specific attacks being used in the PHP-CGI vulnerability ( CVE-2012-1823)
  • API keys no longer required. WF fetches a temporary anonymous API key for you on activation.
  • Added real-time activity log on scan page.
  • Added real-time summary updates on scan page.
  • Fixed ability to view files that have symlinks in path.
  • Added message to configure alert email address for multi-site and single site installs on activation.
  • Disabled firewall rules by default because most sites don't need them.
  • Disabled blocking of fake googlebots except for high security levels to prevent users who like to pretend they're googlebot from blocking themselves.
  • Geshi the syntax highlighter now asks for more memory before running.
  • Fixed bug that caused scan to hang on very large files.
  • Added an index to wfStatus to make it faster for summary statuses
  • Removed multisite pre-activation check to make activation more reliable on multisite installs.
  • Better problem reporting if you trashed your Wordfence schema but the plugin is still installed.
Download this release

Release Info

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

Code changes from version 1.5.6 to 2.0.1

css/main.css CHANGED
@@ -1,5 +1,5 @@
1
  .wordfenceWrap {
2
- margin: 0 0 0 20px;
3
  }
4
  div.wordfenceLive {
5
  height: 29px;
@@ -89,7 +89,7 @@ div.wordfenceScanButton input.button-wf-grey {
89
  .wfTabsContainer {
90
  overflow: hidden;
91
  border: 1px solid #CCC;
92
- max-width: 970px;
93
  padding: 15px;
94
  min-height: 200px;
95
  -webkit-font-smoothing: antialiased;
@@ -187,24 +187,18 @@ table th.wfConfigEnable { font-weight: bold; }
187
  table th.wfSubheading { font-weight: bold; padding-top: 10px; }
188
  .wfALogTime { color: #999; }
189
  .wfALogEntry { }
190
- .wfALogMailLink, .wfALogReloadLink {
191
  display: block;
192
  position: absolute;
193
- width: 16px;
194
- height: 16px;
195
- padding: 0;
196
  margin: 0;
197
- }
198
- .wfALogMailLink {
199
- right: 20px;
200
- top: 2px;
201
  background-image: url(../images/icons/email_go.png);
 
 
202
  }
203
- .wfALogReloadLink {
204
- right: 42px;
205
- top: 2px;
206
- background-image: url(../images/icons/arrow_refresh.png);
207
- }
208
  #wfActivity { position: relative; }
209
  h3.wfConfigHeading {
210
  font-size: 22px;
@@ -218,3 +212,53 @@ h3.wfConfigHeading {
218
  font-family: Georgia;
219
  font-style: italic;
220
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  .wordfenceWrap {
2
+ margin: 20px 0 0 20px;
3
  }
4
  div.wordfenceLive {
5
  height: 29px;
89
  .wfTabsContainer {
90
  overflow: hidden;
91
  border: 1px solid #CCC;
92
+ max-width: 870px;
93
  padding: 15px;
94
  min-height: 200px;
95
  -webkit-font-smoothing: antialiased;
187
  table th.wfSubheading { font-weight: bold; padding-top: 10px; }
188
  .wfALogTime { color: #999; }
189
  .wfALogEntry { }
190
+ .wfALogMailLink {
191
  display: block;
192
  position: absolute;
193
+ padding: 0 0 0 18px;
 
 
194
  margin: 0;
195
+ right: 10px;
196
+ top: 0px;
 
 
197
  background-image: url(../images/icons/email_go.png);
198
+ background-repeat: no-repeat;
199
+ font-weight: normal;
200
  }
201
+
 
 
 
 
202
  #wfActivity { position: relative; }
203
  h3.wfConfigHeading {
204
  font-size: 22px;
212
  font-family: Georgia;
213
  font-style: italic;
214
  }
215
+
216
+ .consoleHead {
217
+ position: relative;
218
+ padding: 0 0 0 3px;
219
+ font-weight: bold;
220
+ width: 800px;
221
+ }
222
+ .consoleHeadText {
223
+ font-size: 18px;
224
+ font-family: Georgia, serif;
225
+ font-style: italic;
226
+ color: #555;
227
+ font-weight: bold;
228
+ -webkit-font-smoothing: antialiased;
229
+
230
+ }
231
+ .consoleOuter { width: 800px; margin-bottom: 20px; }
232
+ .consoleInner { height: 116px; overflow: auto; z-index: 1; }
233
+ .bevelDiv1 { border: 1px solid #EFEFEF; }
234
+ .bevelDiv2 { border: 1px solid #AAA; }
235
+ .bevelDiv3 { border: 1px solid #555;
236
+ background-color: #FFFFE0; /* #FFFFF0; /* #FFEBCD; #FFFACD; */
237
+ color: #000; padding: 5px; font-family: Arial; -webkit-font-smoothing: none; }
238
+
239
+ .wfBlackCursor{ color: #FFF; }
240
+ .wfSecure { color: #0A0; font-weight: bold; }
241
+ .wfActivityLine {
242
+ }
243
+ .wfSummaryDate { float: left; margin-left: 3px; }
244
+ .wfSummaryMsg { float: left; margin-left: 3px; }
245
+ .wfSummaryResult { float: right; text-align: left; width: 280px; }
246
+ .wfSummaryLoading { width: 16px; height: 11px; background-image: url('../images/icons/ajaxScan.gif'); }
247
+ .wfSummaryBad, .wfSummaryErr { color: #A00; }
248
+ .wfSummaryOK { color: #0A0; }
249
+ .wfClear {
250
+ content: ".";
251
+ display: block;
252
+ height: 0;
253
+ width: 0;
254
+ line-height: 0;
255
+ clear: both;
256
+ visibility: hidden;
257
+ }
258
+ .wfSummaryFinal {
259
+ -webkit-font-smoothing: antialiased;
260
+ font-weight: bold;
261
+ color: #555;
262
+ }
263
+ input.wfStartScanButton { width: 160px; text-align: left; padding-left: 20px; }
264
+
images/icons/ajaxScan.gif ADDED
Binary file
js/admin.js CHANGED
@@ -1,6 +1,7 @@
1
  if(! window['wordfenceAdmin']){
2
  window['wordfenceAdmin'] = {
3
  loading16: '<div class="wfLoading16"></div>',
 
4
  dbCheckTables: [],
5
  dbCheckCount_ok: 0,
6
  dbCheckCount_skipped: 0,
@@ -11,7 +12,6 @@ window['wordfenceAdmin'] = {
11
  scanIDLoaded: 0,
12
  colorboxQueue: [],
13
  colorboxOpen: false,
14
- scanPending: false,
15
  mode: '',
16
  visibleIssuesPanel: 'new',
17
  preFirstScanMsgsLoaded: false,
@@ -19,33 +19,179 @@ window['wordfenceAdmin'] = {
19
  elementGeneratorIter: 1,
20
  reloadConfigPage: false,
21
  nonce: false,
 
 
 
 
 
 
 
22
  init: function(){
23
  this.nonce = WordfenceAdminVars.firstNonce;
 
24
  if(jQuery('#wordfenceMode_scan').length > 0){
25
  this.mode = 'scan';
 
 
26
  this.noScanHTML = jQuery('#wfNoScanYetTmpl').tmpl().html();
 
 
27
  } else if(jQuery('#wordfenceMode_activity').length > 0){
28
  this.mode = 'activity';
29
  this.activityMode = 'hit';
30
- this.updateTicker(true);
31
  } else if(jQuery('#wordfenceMode_options').length > 0){
32
  this.mode = 'options';
33
  jQuery('.wfConfigElem').change(function(){ jQuery('#securityLevel').val('CUSTOM'); });
34
  this.updateTicker(true);
 
35
  } else if(jQuery('#wordfenceMode_blockedIPs').length > 0){
36
  this.mode = 'blocked';
37
  this.staticTabChanged();
38
  this.updateTicker(true);
 
39
  } else {
40
  this.mode = false;
41
  }
42
  if(this.mode){ //We are in a Wordfence page
43
  var self = this;
44
- this.liveInt = setInterval(function(){ self.updateTicker(); }, 2000);
 
 
45
  jQuery(document).bind('cbox_closed', function(){ self.colorboxIsOpen = false; self.colorboxServiceQueue(); });
46
  }
47
 
48
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  updateTicker: function(forceUpdate){
50
  if( (! forceUpdate) && this.tickerUpdatePending){
51
  return;
@@ -80,27 +226,7 @@ window['wordfenceAdmin'] = {
80
  jQuery('#wfLiveStatus').hide().html(newMsg).fadeIn(200);
81
  }
82
 
83
- if(this.mode == 'scan'){
84
- if(res.running){
85
- jQuery('.wfStartScanButton').addClass('button-wf-grey').val("A scan is in progress...").unbind('click').click(function(){ wordfenceAdmin.scanRunningMsg(); }).show();
86
- } else {
87
- if(! this.scanPending){
88
- jQuery('.wfStartScanButton').removeClass('button-wf-grey').val("Start a Wordfence Scan").unbind('click').click(function(){ wordfenceAdmin.startScan(); }).show();
89
- }
90
- }
91
- if(res.currentScanID && res.currentScanID != this.scanIDLoaded){
92
- this.scanIDLoaded = res.currentScanID;
93
- this.loadIssues();
94
- } else if( (! res.currentScanID) && (! this.scanIDLoaded)){
95
- //We haven't done our first scan yet.
96
- if(! this.preFirstScanMsgsLoaded){
97
- this.preFirstScanMsgsLoaded = true;
98
- jQuery('#wfSummaryTables').html(this.noScanHTML);
99
- this.switchIssuesTab(jQuery('#wfNewIssuesTab'), 'new');
100
- jQuery('#wfActivity').html('<p>No events to report yet. Please complete your first scan.</p>');
101
- }
102
- }
103
- } else if(this.mode == 'activity'){
104
  if(res.alsoGet != 'logList_' + this.activityMode){ return; } //user switched panels since ajax request started
105
  if(/^(?:topScanners|topLeechers)$/.test(this.activityMode)){
106
  if(statusMsgChanged){ this.updateTicker(true); } return;
@@ -202,12 +328,20 @@ window['wordfenceAdmin'] = {
202
  });
203
  },
204
  startScan: function(){
205
- var self = this;
206
- jQuery('.wfStartScanButton').addClass('button-wf-grey').val("A scan is in progress...").unbind('click').click(function(){ wordfenceAdmin.scanRunningMsg(); }).show();
207
- //scanPending prevents the button from switching to grey when clicked and then quickly to blue and grey again as the ticker us updated.
208
- this.scanPending = true;
209
- var self = this;
210
- setTimeout(function(){ self.scanPending = false; }, 10000);
 
 
 
 
 
 
 
 
211
  this.ajax('wordfence_scan', {}, function(res){ } );
212
  },
213
  loadIssues: function(callback){
@@ -231,7 +365,6 @@ window['wordfenceAdmin'] = {
231
  displayIssues: function(res, callback){
232
  var self = this;
233
  res.summary['lastScanCompleted'] = res['lastScanCompleted'];
234
- jQuery('#wfSummaryTables').html( jQuery('#wfScanSummaryTmpl').tmpl(res.summary).html() );
235
  jQuery('.wfIssuesContainer').hide();
236
  for(issueStatus in res.issuesLists){
237
  var containerID = 'wfIssues_dataTable_' + issueStatus;
1
  if(! window['wordfenceAdmin']){
2
  window['wordfenceAdmin'] = {
3
  loading16: '<div class="wfLoading16"></div>',
4
+ actUpdateInterval: 2000,
5
  dbCheckTables: [],
6
  dbCheckCount_ok: 0,
7
  dbCheckCount_skipped: 0,
12
  scanIDLoaded: 0,
13
  colorboxQueue: [],
14
  colorboxOpen: false,
 
15
  mode: '',
16
  visibleIssuesPanel: 'new',
17
  preFirstScanMsgsLoaded: false,
19
  elementGeneratorIter: 1,
20
  reloadConfigPage: false,
21
  nonce: false,
22
+ tickerUpdatePending: false,
23
+ activityLogUpdatePending: false,
24
+ lastALogCtime: 0,
25
+ activityQueue: [],
26
+ totalActAdded: 0,
27
+ maxActivityLogItems: 1000,
28
+ scanReqAnimation: false,
29
  init: function(){
30
  this.nonce = WordfenceAdminVars.firstNonce;
31
+ var startTicker = false;
32
  if(jQuery('#wordfenceMode_scan').length > 0){
33
  this.mode = 'scan';
34
+ jQuery('#consoleActivity').scrollTop(jQuery('#consoleActivity').prop('scrollHeight'));
35
+ jQuery('#consoleScan').scrollTop(jQuery('#consoleScan').prop('scrollHeight'));
36
  this.noScanHTML = jQuery('#wfNoScanYetTmpl').tmpl().html();
37
+ this.loadIssues();
38
+ this.startActivityLogUpdates();
39
  } else if(jQuery('#wordfenceMode_activity').length > 0){
40
  this.mode = 'activity';
41
  this.activityMode = 'hit';
42
+ startTicker = true;
43
  } else if(jQuery('#wordfenceMode_options').length > 0){
44
  this.mode = 'options';
45
  jQuery('.wfConfigElem').change(function(){ jQuery('#securityLevel').val('CUSTOM'); });
46
  this.updateTicker(true);
47
+ startTicker = true;
48
  } else if(jQuery('#wordfenceMode_blockedIPs').length > 0){
49
  this.mode = 'blocked';
50
  this.staticTabChanged();
51
  this.updateTicker(true);
52
+ startTicker = true;
53
  } else {
54
  this.mode = false;
55
  }
56
  if(this.mode){ //We are in a Wordfence page
57
  var self = this;
58
+ if(startTicker){
59
+ this.liveInt = setInterval(function(){ self.updateTicker(); }, 2000);
60
+ }
61
  jQuery(document).bind('cbox_closed', function(){ self.colorboxIsOpen = false; self.colorboxServiceQueue(); });
62
  }
63
 
64
  },
65
+ startActivityLogUpdates: function(){
66
+ var self = this;
67
+ setInterval(function(){
68
+ self.updateActivityLog();
69
+ }, this.actUpdateInterval);
70
+ },
71
+ updateActivityLog: function(){
72
+ if(this.activityLogUpdatePending){
73
+ return;
74
+ }
75
+ this.activityLogUpdatePending = true;
76
+ var self = this;
77
+ this.ajax('wordfence_activityLogUpdate', {
78
+ lastctime: this.lastALogCtime
79
+ }, function(res){ self.doneUpdateActivityLog(res); }, function(){ self.activityLogUpdatePending = false; });
80
+
81
+ },
82
+ doneUpdateActivityLog: function(res){
83
+ this.actNextUpdateAt = (new Date()).getTime() + this.actUpdateInterval;
84
+ if(res.ok){
85
+ if(res.items.length > 0){
86
+ this.activityQueue.push.apply(this.activityQueue, res.items);
87
+ this.lastALogCtime = res.items[res.items.length - 1].ctime;
88
+ this.processActQueue(res.currentScanID);
89
+ }
90
+ }
91
+ this.activityLogUpdatePending = false;
92
+ },
93
+ processActQueue: function(currentScanID){
94
+ if(this.activityQueue.length > 0){
95
+
96
+ this.addActItem(this.activityQueue.shift());
97
+ this.totalActAdded++;
98
+ if(this.totalActAdded > this.maxActivityLogItems){
99
+ jQuery('#consoleActivity div:first').remove();
100
+ this.totalActAdded--;
101
+ }
102
+
103
+ var timeTillNextUpdate = this.actNextUpdateAt - (new Date()).getTime();
104
+ var maxRate = 50 / 1000; //Rate per millisecond
105
+ var bulkTotal = 0;
106
+ while(this.activityQueue.length > 0 && this.activityQueue.length / timeTillNextUpdate > maxRate ){
107
+ var item = this.activityQueue.shift();
108
+ if(item){
109
+ bulkTotal++;
110
+ this.addActItem(item);
111
+ }
112
+ }
113
+ this.totalActAdded += bulkTotal;
114
+ if(this.totalActAdded > this.maxActivityLogItems){
115
+ jQuery('#consoleActivity div:lt(' + bulkTotal + ')').remove();
116
+ this.totalActAdded -= bulkTotal;
117
+ }
118
+ var minDelay = 100;
119
+ var delay = minDelay;
120
+ if(timeTillNextUpdate < 1){
121
+ delay = minDelay;
122
+ } else {
123
+ delay = Math.round(timeTillNextUpdate / this.activityQueue.length);
124
+ if(delay < minDelay){ delay = minDelay; }
125
+ }
126
+ var self = this;
127
+ setTimeout(function(){ self.processActQueue(); }, delay);
128
+ }
129
+ jQuery('#consoleActivity').scrollTop(jQuery('#consoleActivity').prop('scrollHeight'));
130
+ },
131
+ processActArray: function(arr){
132
+ for(var i = 0; i < arr.length; i++){
133
+ this.addActItem(arr[i]);
134
+ }
135
+ },
136
+ addActItem: function(item){
137
+ if(item.msg.indexOf('SUM_') == 0){
138
+ this.processSummaryLine(item);
139
+ jQuery('#consoleSummary').scrollTop(jQuery('#consoleSummary').prop('scrollHeight'));
140
+ jQuery('#wfStartingScan').addClass('wfSummaryOK').html('Done.');
141
+ } else {
142
+ jQuery('#consoleActivity').append('<div class="wfActivityLine wf' + item.type + '">[' + item.date + ']&nbsp;' + item.msg + '</div>');
143
+ if(/Scan complete\./i.test(item.msg)){
144
+ this.loadIssues();
145
+ }
146
+ }
147
+ },
148
+ processSummaryLine: function(item){
149
+ if(item.msg.indexOf('SUM_START:') != -1){
150
+ var msg = item.msg.replace('SUM_START:', '');
151
+ jQuery('#consoleSummary').append('<div class="wfSummaryLine"><div class="wfSummaryDate">[' + item.date + ']</div><div class="wfSummaryMsg">' + msg + '</div><div class="wfSummaryResult"><div class="wfSummaryLoading"></div></div><div class="wfClear"></div>');
152
+ summaryUpdated = true;
153
+ } else if(item.msg.indexOf('SUM_ENDBAD') != -1){
154
+ var msg = item.msg.replace('SUM_ENDBAD:', '');
155
+ jQuery('div.wfSummaryMsg:contains("' + msg + '")').next().addClass('wfSummaryBad').html('Problems found.');
156
+ summaryUpdated = true;
157
+ } else if(item.msg.indexOf('SUM_ENDOK') != -1){
158
+ var msg = item.msg.replace('SUM_ENDOK:', '');
159
+ jQuery('div.wfSummaryMsg:contains("' + msg + '")').next().addClass('wfSummaryOK').html('Secure.');
160
+ summaryUpdated = true;
161
+ } else if(item.msg.indexOf('SUM_ENDERR') != -1){
162
+ var msg = item.msg.replace('SUM_ENDERR:', '');
163
+ jQuery('div.wfSummaryMsg:contains("' + msg + '")').next().addClass('wfSummaryErr').html('An error occured.');
164
+ summaryUpdated = true;
165
+ } else if(item.msg.indexOf('SUM_DISABLED:') != -1){
166
+ var msg = item.msg.replace('SUM_DISABLED:', '');
167
+ jQuery('#consoleSummary').append('<div class="wfSummaryLine"><div class="wfSummaryDate">[' + item.date + ']</div><div class="wfSummaryMsg">' + msg + '</div><div class="wfSummaryResult">Disabled</div><div class="wfClear"></div>');
168
+ summaryUpdated = true;
169
+ } else if(item.msg.indexOf('SUM_PAIDONLY:') != -1){
170
+ var msg = item.msg.replace('SUM_PAIDONLY:', '');
171
+ jQuery('#consoleSummary').append('<div class="wfSummaryLine"><div class="wfSummaryDate">[' + item.date + ']</div><div class="wfSummaryMsg">' + msg + '</div><div class="wfSummaryResult"><a href="http://www.wordfence.com/" target="_blank">Paid Members Only</a></div><div class="wfClear"></div>');
172
+ summaryUpdated = true;
173
+ } else if(item.msg.indexOf('SUM_FINAL:') != -1){
174
+ var msg = item.msg.replace('SUM_FINAL:', '');
175
+ jQuery('#consoleSummary').append('<div class="wfSummaryLine"><div class="wfSummaryDate">[' + item.date + ']</div><div class="wfSummaryMsg wfSummaryFinal">' + msg + '</div><div class="wfSummaryResult wfSummaryOK">Scan Complete.</div><div class="wfClear"></div>');
176
+ } else if(item.msg.indexOf('SUM_PREP:') != -1){
177
+ var msg = item.msg.replace('SUM_PREP:', '');
178
+ jQuery('#consoleSummary').empty().html('<div class="wfSummaryLine"><div class="wfSummaryDate">[' + item.date + ']</div><div class="wfSummaryMsg">' + msg + '</div><div class="wfSummaryResult" id="wfStartingScan"><div class="wfSummaryLoading"></div></div><div class="wfClear"></div>');
179
+ }
180
+ },
181
+ processActQueueItem: function(){
182
+ var item = this.activityQueue.shift();
183
+ if(item){
184
+ jQuery('#consoleActivity').append('<div class="wfActivityLine wf' + item.type + '">[' + item.date + ']&nbsp;' + item.msg + '</div>');
185
+ this.totalActAdded++;
186
+ if(this.totalActAdded > this.maxActivityLogItems){
187
+ jQuery('#consoleActivity div:first').remove();
188
+ this.totalActAdded--;
189
+ }
190
+ if(item.msg == 'Scan complete.'){
191
+ this.loadIssues();
192
+ }
193
+ }
194
+ },
195
  updateTicker: function(forceUpdate){
196
  if( (! forceUpdate) && this.tickerUpdatePending){
197
  return;
226
  jQuery('#wfLiveStatus').hide().html(newMsg).fadeIn(200);
227
  }
228
 
229
+ if(this.mode == 'activity'){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  if(res.alsoGet != 'logList_' + this.activityMode){ return; } //user switched panels since ajax request started
231
  if(/^(?:topScanners|topLeechers)$/.test(this.activityMode)){
232
  if(statusMsgChanged){ this.updateTicker(true); } return;
328
  });
329
  },
330
  startScan: function(){
331
+ var scanReqAnimation = setInterval(function(){
332
+ var str = jQuery('#wfStartScanButton1').prop('value');
333
+ ch = str.charAt(str.length - 1);
334
+ if(ch == '/'){ ch = '-'; }
335
+ else if(ch == '-'){ ch = '\\'; }
336
+ else if(ch == '\\'){ ch = '|'; }
337
+ else if(ch == '|'){ ch = '/'; }
338
+ else {ch = '/'; }
339
+ jQuery('#wfStartScanButton1,#wfStartScanButton2').prop('value', "Requesting a New Scan " + ch);
340
+ }, 100);
341
+ setTimeout(function(res){
342
+ clearInterval(scanReqAnimation);
343
+ jQuery('#wfStartScanButton1,#wfStartScanButton2').prop('value', "Start a Wordfence Scan");
344
+ }, 2000);
345
  this.ajax('wordfence_scan', {}, function(res){ } );
346
  },
347
  loadIssues: function(callback){
365
  displayIssues: function(res, callback){
366
  var self = this;
367
  res.summary['lastScanCompleted'] = res['lastScanCompleted'];
 
368
  jQuery('.wfIssuesContainer').hide();
369
  for(issueStatus in res.issuesLists){
370
  var containerID = 'wfIssues_dataTable_' + issueStatus;
lib/menu_scan.php CHANGED
@@ -1,27 +1,49 @@
1
  <div class="wordfenceModeElem" id="wordfenceMode_scan"></div>
2
  <div class="wrap wordfence">
3
  <div class="wordfence-lock-icon wordfence-icon32"><br /></div><h2>Wordfence Scan</h2>
4
- <div class="wordfenceLive">
5
- <table border="0" cellpadding="0" cellspacing="0">
6
- <tr><td><h2>Wordfence Activity Log:</h2></td><td id="wfLiveStatus"></td></tr>
7
- </table>
8
- </div>
9
  <div class="wordfenceWrap">
 
 
 
10
  <div>
11
- <div id="wfTabs">
12
- <a href="#" class="wfTab1 wfTabSwitch selected" onclick="wordfenceAdmin.switchToSummaryTab(this); return false;">Summary</a>
13
- <a href="#" class="wfTab1 wfTabSwitch" onclick="wordfenceAdmin.switchToLiveTab(this); return false;">Activity Log</a>
14
  </div>
15
- <div class="wfTabsContainer">
16
- <div id="wfSummaryTables" class="wfDataPanel">
17
- <div class="wfLoadingWhite32"></div>
18
- &nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />
19
- </div>
20
- <div id="wfActivity" class="wfDataPanel" style="display: none; overflow: scroll; height: 400px; border: 1px solid #CCC; padding: 2px;">
21
- <div class="wfLoadingWhite32"></div>
22
- &nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />
23
  </div>
 
 
 
 
 
24
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  </div>
26
  <div style="margin-top: 20px;">
27
  <div id="wfTabs">
@@ -36,7 +58,8 @@
36
  If you have fixed all the issues below, you can <a href="#" onclick="WFAD.updateAllIssues('deleteNew'); return false;">click here to mark all new issues as fixed</a>.
37
  You can also <a href="#" onclick="WFAD.updateAllIssues('ignoreAllNew'); return false;">ignore all new issues</a> which will exclude all issues listed below from future scans.
38
  </p>
39
- <div id="wfIssues_dataTable_new"></div>
 
40
  </div>
41
  <div id="wfIssues_ignored" class="wfIssuesContainer">
42
  <h2>Ignored Issues</h2>
@@ -395,7 +418,7 @@
395
  where you can post your comments or questions. We would love to hear from you.
396
  </td></tr>
397
  <tr><td>
398
- <div class="wordfenceScanButton"><input type="button" value="Start a Wordfence Scan" class="wfStartScanButton button-primary" /></div>
399
  </td></tr>
400
  </table>
401
  </td>
@@ -403,74 +426,4 @@
403
  </div>
404
  </script>
405
 
406
- <script type="text/x-jquery-template" id="wfScanSummaryTmpl">
407
- <div>
408
- <table class="wfSummaryParent" cellpadding="0" cellspacing="0">
409
- <tr><th class="wfHead">Activity Summary:</th><th class="wfHead" colspan="3">Wordfence is Protecting:</th></tr>
410
- <tr><td>
411
- <table class="wfSC1" cellpadding="0" cellspacing="0">
412
- <tr><td>
413
- The most recent scan completed ${scanTimeAgo} ago.
414
- </td></tr>
415
- <tr><td>
416
- {{if scanRunning == '1'}}
417
- There is currently a scan running
418
- {{else}}
419
- A scan is not running at this time
420
- {{/if}}
421
- {{if scheduledScansEnabled}}
422
- and the next scan is scheduled to run approximately ${nextRun}.
423
- {{else}}
424
- and scheduled scans are disabled.
425
- {{/if}}
426
- </td></tr>
427
- <tr><td>
428
- {{if totalCritical > 0 || totalWarning > 0}}
429
- There are currently
430
- {{if totalCritical > 0 && totalWarning > 0}}
431
- ${totalCritical} critical issues and ${totalWarning} warning issues
432
- {{else totalCritical > 0}}
433
- ${totalCritical} critical issues
434
- {{else totalWarning > 0}}
435
- ${totalWarning} warning issues
436
- {{/if}}
437
- you need to investigate. See below for full details.
438
- {{else lastScanCompleted == 'ok'}}
439
- Congratulations, you have no security issues that need investigating.
440
- {{else lastScanCompleted}}
441
- <span style="color: #A00;">Latest scan failed: ${lastScanCompleted}</span>
442
- {{/if}}
443
- </td></tr>
444
- <tr><td>
445
- <div class="wordfenceScanButton"><input type="button" value="Start a Wordfence Scan" class="wfStartScanButton button-primary" onclick="wordfenceAdmin.startScan();" /></div>
446
- <a href="http://www.wordfence.com/forums/" target="_blank">Visit the Wordfence forums for help.</a>
447
- </td></tr>
448
- </table>
449
- </td>
450
- <td>
451
- <table class="wfSummaryChild wfSC2" cellpadding="0" cellspacing="0">
452
- <tr><th>${wordfenceAdmin.commify(totalFiles)}</th><td>Files</td></tr>
453
- <tr><th>${wordfenceAdmin.commify(totalDirs)}</th><td>Directories</td></tr>
454
- <tr><th>${wordfenceAdmin.commify(totalUsers)}</th><td>Users</td></tr>
455
- <tr><th>${wordfenceAdmin.commify(totalPlugins)}</th><td>Plugins</td></tr>
456
- <tr><th>${wordfenceAdmin.commify(totalThemes)}</th><td>Themes</td></tr>
457
- <tr><th>${wordfenceAdmin.commify(totalPages)}</th><td>Pages</td></tr>
458
- <tr><th>${wordfenceAdmin.commify(totalPosts)}</th><td>Posts</td></tr>
459
- </table>
460
- </td>
461
- <td>&nbsp;&nbsp;</td>
462
- <td>
463
- <table class="wfSummaryChild wfSC3" cellpadding="0" cellspacing="0">
464
- <tr><th>${wordfenceAdmin.commify(totalComments)}</th><td>Comments</td></tr>
465
- <tr><th>${wordfenceAdmin.commify(totalCategories)}</th><td>Categories</td></tr>
466
- <tr><th>${wordfenceAdmin.commify(linesOfPHP)}</th><td>Lines of PHP code</td></tr>
467
- <tr><th>${wordfenceAdmin.commify(linesOfJCH)}</th><td>Lines of JS, HTML and CSS code</td></tr>
468
- <tr><th>${wordfenceAdmin.commify(totalData)}</th><td>of data in ${wordfenceAdmin.commify(totalFiles)} files</td></tr>
469
- <tr><th>${wordfenceAdmin.commify(totalTables)}</th><td>Database Tables</td><tr>
470
- <tr><th>${wordfenceAdmin.commify(totalRows)}</th><td>Database Rows</td></tr>
471
- </table>
472
- </td>
473
- </tr></table>
474
- </div>
475
- </script>
476
 
1
  <div class="wordfenceModeElem" id="wordfenceMode_scan"></div>
2
  <div class="wrap wordfence">
3
  <div class="wordfence-lock-icon wordfence-icon32"><br /></div><h2>Wordfence Scan</h2>
 
 
 
 
 
4
  <div class="wordfenceWrap">
5
+ <div class="wordfenceScanButton"><input type="button" value="Start a Wordfence Scan" id="wfStartScanButton1" class="wfStartScanButton button-primary" onclick="wordfenceAdmin.startScan();" />
6
+ <a target="_blank" href="http://www.wordfence.com/forums/">You can always get help on our support forum.</a>
7
+ </div>
8
  <div>
9
+ <div class="consoleHead">
10
+ <span class="consoleHeadText">Scan Summary</span>
 
11
  </div>
12
+ <?php
13
+ $events = wordfence::getLog()->getStatusEvents(0);
14
+ ?>
15
+ <div class="bevelDiv1 consoleOuter"><div class="bevelDiv2"><div class="bevelDiv3 consoleInner" id="consoleSummary">
16
+ <?php if(sizeof($events) < 1){ ?>
17
+ <div style="width: 500px;">
18
+ Welcome to Wordfence!<br /><br />
19
+ To get started, simply click the blue button at the top of this page to start your first scan.
20
  </div>
21
+ <?php } ?>
22
+ </div></div></div>
23
+ <div class="consoleHead">
24
+ <span class="consoleHeadText">Scan Detailed Activity</span>
25
+ <a href="#" class="wfALogMailLink" onclick="WFAD.emailActivityLog(); return false;">Email Activity Log</a>
26
  </div>
27
+ <div class="bevelDiv1 consoleOuter"><div class="bevelDiv2"><div class="bevelDiv3 consoleInner" id="consoleActivity">
28
+ <?php
29
+ if(sizeof($events) > 0){
30
+ $newestItem = 0;
31
+ $sumEvents = array();
32
+ foreach($events as $e){
33
+ if(strpos($e['msg'], 'SUM_') !== 0){
34
+ echo '<div class="wfActivityLine wf' . $e['type'] . '">[' . date('M d H:i:s', $e['ctime']) . ']&nbsp;' . $e['msg'] . '</div>';
35
+ }
36
+ $newestItem = $e['ctime'];
37
+ }
38
+
39
+ echo '<script type="text/javascript">WFAD.lastALogCtime = ' . $newestItem . '; WFAD.processActArray(' . json_encode(wordfence::getLog()->getSummaryEvents()) . ');</script>';
40
+ } else { ?>
41
+ A live stream of what Wordfence is busy with right now will appear in this box.
42
+
43
+ <?php
44
+ }
45
+ ?>
46
+ </div></div></div>
47
  </div>
48
  <div style="margin-top: 20px;">
49
  <div id="wfTabs">
58
  If you have fixed all the issues below, you can <a href="#" onclick="WFAD.updateAllIssues('deleteNew'); return false;">click here to mark all new issues as fixed</a>.
59
  You can also <a href="#" onclick="WFAD.updateAllIssues('ignoreAllNew'); return false;">ignore all new issues</a> which will exclude all issues listed below from future scans.
60
  </p>
61
+ <div id="wfIssues_dataTable_new">
62
+ </div>
63
  </div>
64
  <div id="wfIssues_ignored" class="wfIssuesContainer">
65
  <h2>Ignored Issues</h2>
418
  where you can post your comments or questions. We would love to hear from you.
419
  </td></tr>
420
  <tr><td>
421
+ <div class="wordfenceScanButton"><input type="button" value="Start a Wordfence Scan" id="wfStartScanButton2" class="wfStartScanButton button-primary" /></div>
422
  </td></tr>
423
  </table>
424
  </td>
426
  </div>
427
  </script>
428
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
 
lib/wfAPI.php CHANGED
@@ -15,7 +15,6 @@ class wfAPI {
15
  $this->wordpressVersion = $wordpressVersion;
16
  }
17
  public function call($action, $getParams = array(), $postParams = array()){
18
- wordfence::status(3, 'info', "Starting API call: $action");
19
  $this->errorMsg = false;
20
  $json = $this->getURL(WORDFENCE_API_URL . '/v' . WORDFENCE_VERSION . '/?' . $this->makeAPIQueryString() . '&' . http_build_query(
21
  array_merge(
@@ -42,7 +41,7 @@ class wfAPI {
42
  if($this->errorMsg){
43
  wordfence::status(3, 'error', "API Error: " . $this->errorMsg);
44
  } else {
45
- wordfence::status(3, 'info', "Completed API call: $action");
46
  }
47
  return $dat;
48
  }
@@ -94,7 +93,6 @@ class wfAPI {
94
 
95
  }
96
  public function binCall($func, $postData){
97
- wordfence::status(3, 'info', "Starting binary API call: $func");
98
  $this->errorMsg = false;
99
  $url = WORDFENCE_API_URL . '/v' . WORDFENCE_VERSION . '/?' . $this->makeAPIQueryString() . '&action=' . $func;
100
  $curl = curl_init($url);
@@ -120,7 +118,6 @@ class wfAPI {
120
  return false;
121
  }
122
  }
123
- wordfence::status(3, 'info', "Completed binary API call $func with code: $httpStatus");
124
  return array('code' => $httpStatus, 'data' => $data);
125
  }
126
  public function makeAPIQueryString(){
15
  $this->wordpressVersion = $wordpressVersion;
16
  }
17
  public function call($action, $getParams = array(), $postParams = array()){
 
18
  $this->errorMsg = false;
19
  $json = $this->getURL(WORDFENCE_API_URL . '/v' . WORDFENCE_VERSION . '/?' . $this->makeAPIQueryString() . '&' . http_build_query(
20
  array_merge(
41
  if($this->errorMsg){
42
  wordfence::status(3, 'error', "API Error: " . $this->errorMsg);
43
  } else {
44
+ //wordfence::status(3, 'info', "Completed API call: $action");
45
  }
46
  return $dat;
47
  }
93
 
94
  }
95
  public function binCall($func, $postData){
 
96
  $this->errorMsg = false;
97
  $url = WORDFENCE_API_URL . '/v' . WORDFENCE_VERSION . '/?' . $this->makeAPIQueryString() . '&action=' . $func;
98
  $curl = curl_init($url);
118
  return false;
119
  }
120
  }
 
121
  return array('code' => $httpStatus, 'data' => $data);
122
  }
123
  public function makeAPIQueryString(){
lib/wfConfig.php CHANGED
@@ -89,7 +89,7 @@ class wfConfig {
89
  "scansEnabled_diskSpace" => true,
90
  "scansEnabled_dns" => true,
91
  "scansEnabled_oldVersions" => true,
92
- "firewallEnabled" => true,
93
  "blockFakeBots" => false,
94
  "autoBlockScanners" => true,
95
  "loginSecurityEnabled" => true,
@@ -150,8 +150,8 @@ class wfConfig {
150
  "scansEnabled_diskSpace" => true,
151
  "scansEnabled_dns" => true,
152
  "scansEnabled_oldVersions" => true,
153
- "firewallEnabled" => true,
154
- "blockFakeBots" => true,
155
  "autoBlockScanners" => true,
156
  "loginSecurityEnabled" => true,
157
  "loginSec_lockInvalidUsers" => false,
@@ -212,7 +212,7 @@ class wfConfig {
212
  "scansEnabled_dns" => true,
213
  "scansEnabled_oldVersions" => true,
214
  "firewallEnabled" => true,
215
- "blockFakeBots" => true,
216
  "autoBlockScanners" => true,
217
  "loginSecurityEnabled" => true,
218
  "loginSec_lockInvalidUsers" => false,
89
  "scansEnabled_diskSpace" => true,
90
  "scansEnabled_dns" => true,
91
  "scansEnabled_oldVersions" => true,
92
+ "firewallEnabled" => false,
93
  "blockFakeBots" => false,
94
  "autoBlockScanners" => true,
95
  "loginSecurityEnabled" => true,
150
  "scansEnabled_diskSpace" => true,
151
  "scansEnabled_dns" => true,
152
  "scansEnabled_oldVersions" => true,
153
+ "firewallEnabled" => false,
154
+ "blockFakeBots" => false,
155
  "autoBlockScanners" => true,
156
  "loginSecurityEnabled" => true,
157
  "loginSec_lockInvalidUsers" => false,
212
  "scansEnabled_dns" => true,
213
  "scansEnabled_oldVersions" => true,
214
  "firewallEnabled" => true,
215
+ "blockFakeBots" => false,
216
  "autoBlockScanners" => true,
217
  "loginSecurityEnabled" => true,
218
  "loginSec_lockInvalidUsers" => false,
lib/wfDB.php CHANGED
@@ -117,6 +117,25 @@ class wfDB {
117
  error_log($msg);
118
  exit(1);
119
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  }
121
 
122
  ?>
117
  error_log($msg);
118
  exit(1);
119
  }
120
+ public function createKeyIfNotExists($table, $col, $keyName){
121
+ global $wpdb; $prefix = $wpdb->prefix;
122
+ $table = $prefix . $table;
123
+ $exists = $this->querySingle("show tables like '$table'");
124
+ $keyFound = false;
125
+ if($exists){
126
+ $q = $this->query("show keys from $table");
127
+ if($q){
128
+ while($row = mysql_fetch_assoc($q)){
129
+ if($row['Key_name'] == $keyName){
130
+ $keyFound = true;
131
+ }
132
+ }
133
+ }
134
+ }
135
+ if(! $keyFound){
136
+ $this->query("alter table $table add KEY $keyName($col)");
137
+ }
138
+ }
139
  }
140
 
141
  ?>
lib/wfIssues.php CHANGED
@@ -25,9 +25,9 @@ class wfIssues {
25
  $ignoreC = md5($ignoreC);
26
  $rec = $this->getDB()->querySingleRec("select status, ignoreP, ignoreC from " . $this->issuesTable . " where (ignoreP='%s' OR ignoreC='%s')", $ignoreP, $ignoreC);
27
  if($rec){
28
- if($rec['status'] == 'new' && ($rec['ignoreC'] == $ignoreC || $rec['ignoreP'] == $ignoreP)){ return; }
29
- if($rec['status'] == 'ignoreC' && $rec['ignoreC'] == $ignoreC){ return; }
30
- if($rec['status'] == 'ignoreP' && $rec['ignoreP'] == $ignoreP){ return; }
31
  }
32
 
33
  $this->totalIssues++;
@@ -56,6 +56,7 @@ class wfIssues {
56
  $longMsg,
57
  serialize($templateData)
58
  );
 
59
  }
60
  public function deleteIgnored(){
61
  $this->getDB()->query("delete from " . $this->issuesTable . " where status='ignoreP' or status='ignoreC'");
@@ -190,7 +191,7 @@ class wfIssues {
190
  $this->updateSummaryItems();
191
  }
192
  $arr = wfConfig::get_ser('wf_summaryItems', array());
193
- $arr['scanTimeAgo'] = wfUtils::makeTimeAgo(sprintf('%.0f', time() - $arr['scanTime']));
194
  $arr['scanRunning'] = wfConfig::get('wf_scanRunning') ? '1' : '0';
195
  $arr['scheduledScansEnabled'] = wfConfig::get('scheduledScansEnabled');
196
  $secsToGo = wp_next_scheduled('wordfence_scheduled_scan') - time();
25
  $ignoreC = md5($ignoreC);
26
  $rec = $this->getDB()->querySingleRec("select status, ignoreP, ignoreC from " . $this->issuesTable . " where (ignoreP='%s' OR ignoreC='%s')", $ignoreP, $ignoreC);
27
  if($rec){
28
+ if($rec['status'] == 'new' && ($rec['ignoreC'] == $ignoreC || $rec['ignoreP'] == $ignoreP)){ return false; }
29
+ if($rec['status'] == 'ignoreC' && $rec['ignoreC'] == $ignoreC){ return false; }
30
+ if($rec['status'] == 'ignoreP' && $rec['ignoreP'] == $ignoreP){ return false; }
31
  }
32
 
33
  $this->totalIssues++;
56
  $longMsg,
57
  serialize($templateData)
58
  );
59
+ return true;
60
  }
61
  public function deleteIgnored(){
62
  $this->getDB()->query("delete from " . $this->issuesTable . " where status='ignoreP' or status='ignoreC'");
191
  $this->updateSummaryItems();
192
  }
193
  $arr = wfConfig::get_ser('wf_summaryItems', array());
194
+ //$arr['scanTimeAgo'] = wfUtils::makeTimeAgo(sprintf('%.0f', time() - $arr['scanTime']));
195
  $arr['scanRunning'] = wfConfig::get('wf_scanRunning') ? '1' : '0';
196
  $arr['scheduledScansEnabled'] = wfConfig::get('scheduledScansEnabled');
197
  $secsToGo = wp_next_scheduled('wordfence_scheduled_scan') - time();
lib/wfLog.php CHANGED
@@ -575,16 +575,37 @@ class wfLog {
575
  //$msg = '[' . sprintf('%.2f', memory_get_usage(true) / (1024 * 1024)) . '] ' . $msg;
576
  $this->getDB()->query("insert into " . $this->statusTable . " (ctime, level, type, msg) values (%s, %d, '%s', '%s')", microtime(true), $level, $type, $msg);
577
  }
578
- public function getStatusEvents(){
579
- $res = $this->getDB()->query("select ctime, level, type, msg from " . $this->statusTable . " order by ctime desc limit 2000");
 
 
 
 
 
 
580
  $results = array();
581
  $lastTime = false;
582
  while($rec = mysql_fetch_assoc($res)){
583
- $rec['timeAgo'] = wfUtils::makeTimeAgo(time() - $rec['ctime']);
 
584
  array_push($results, $rec);
585
  }
586
  return $results;
587
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
  }
589
 
590
  ?>
575
  //$msg = '[' . sprintf('%.2f', memory_get_usage(true) / (1024 * 1024)) . '] ' . $msg;
576
  $this->getDB()->query("insert into " . $this->statusTable . " (ctime, level, type, msg) values (%s, %d, '%s', '%s')", microtime(true), $level, $type, $msg);
577
  }
578
+ public function getStatusEvents($lastCtime){
579
+ if($lastCtime < 1){
580
+ $lastCtime = $this->getDB()->querySingle("select ctime from " . $this->statusTable . " order by ctime desc limit 1000,1");
581
+ if(! $lastCtime){
582
+ $lastCtime = 0;
583
+ }
584
+ }
585
+ $res = $this->getDB()->query("select ctime, level, type, msg from " . $this->statusTable . " where ctime > %f order by ctime asc", $lastCtime);
586
  $results = array();
587
  $lastTime = false;
588
  while($rec = mysql_fetch_assoc($res)){
589
+ //$rec['timeAgo'] = wfUtils::makeTimeAgo(time() - $rec['ctime']);
590
+ $rec['date'] = date('M d H:i:s', $rec['ctime']);
591
  array_push($results, $rec);
592
  }
593
  return $results;
594
  }
595
+ public function getSummaryEvents(){
596
+ $res = $this->getDB()->query("select ctime, level, type, msg from " . $this->statusTable . " where level = 10 order by ctime desc limit 100", $lastCtime);
597
+ $results = array();
598
+ $lastTime = false;
599
+ while($rec = mysql_fetch_assoc($res)){
600
+ $rec['date'] = date('M d H:i:s', $rec['ctime']);
601
+ array_push($results, $rec);
602
+ if(strpos($rec['msg'], 'SUM_PREP:') === 0){
603
+ break;
604
+ }
605
+ }
606
+ return array_reverse($results);
607
+ }
608
+
609
  }
610
 
611
  ?>
lib/wfScanEngine.php CHANGED
@@ -14,7 +14,9 @@ class wfScanEngine {
14
  private $apiKey = false;
15
  private $errorStopped = false;
16
  private $dictWords = array();
 
17
  public function __construct(){
 
18
  $this->i = new wfIssues();
19
  $this->wp_version = wfUtils::getWPVersion();
20
  $this->apiKey = wfConfig::get('apiKey');
@@ -86,40 +88,58 @@ class wfScanEngine {
86
  $this->scanOldVersions();
87
  if($this->errorStopped){ return; }
88
  }
89
- $this->status(1, 'info', "Scan complete.");
 
 
 
 
 
 
90
  return;
91
  }
92
  private function scanKnownFiles(){
93
  $malwareScanEnabled = $coreScanEnabled = $pluginScanEnabled = $themeScanEnabled = false;
 
 
 
 
 
 
94
  if(wfConfig::get('scansEnabled_core')){
95
  $coreScanEnabled = true;
 
96
  } else {
97
- $this->status(2, 'info', "Skipping core scan because it's disabled.");
98
  }
99
  if(wfConfig::get('scansEnabled_plugins')){
100
  $pluginScanEnabled = true;
 
101
  } else {
 
102
  $this->status(2, 'info', "Skipping plugin scan because it's disabled.");
103
  }
104
  if(wfConfig::get('scansEnabled_themes')){
105
  $themeScanEnabled = true;
 
106
  } else {
 
107
  $this->status(2, 'info', "Skipping themes scan because it's disabled.");
108
  }
109
 
110
  if(wfConfig::get('scansEnabled_malware')){
 
111
  $malwareScanEnabled = true;
112
  } else {
 
113
  $this->status(2, 'info', "Skipping malware scan because it's disabled.");
114
  }
115
  $summaryUpdateRequired = $this->i->summaryUpdateRequired();
116
  if((! $summaryUpdateRequired) && (! ($coreScanEnabled || $pluginScanEnabled || $themeScanEnabled || $malwareScanEnabled))){
117
- $this->status(2, 'info', "Finishign this stage because we don't have to do a summary update and we don't need to do a core, plugin, theme or malware scan.");
118
  return array();
119
  }
120
 
121
  //CORE SCAN
122
- $this->status(2, 'info', "Examining files in WordPress base directory.");
123
  $hasher = new wordfenceHash(strlen(ABSPATH));
124
  $baseWPStuff = array( '.htaccess', 'index.php', 'license.txt', 'readme.html', 'wp-activate.php', 'wp-admin', 'wp-app.php', 'wp-blog-header.php', 'wp-comments-post.php', 'wp-config-sample.php', 'wp-content', 'wp-cron.php', 'wp-includes', 'wp-links-opml.php', 'wp-load.php', 'wp-login.php', 'wp-mail.php', 'wp-pass.php', 'wp-register.php', 'wp-settings.php', 'wp-signup.php', 'wp-trackback.php', 'xmlrpc.php');
125
  $baseContents = scandir(ABSPATH);
@@ -186,28 +206,44 @@ class wfScanEngine {
186
  $result1 = $this->api->call('main_scan', array(), array(
187
  'data' => json_encode($scanData)
188
  ));
189
- $this->status(2, 'info', "Done API call to Wordfence servers and got a result.");
190
  if($this->api->errorMsg){
191
  $this->errorStop($this->api->errorMsg);
 
192
  return;
193
  }
194
  if(empty($result1['errorMsg']) === false){
195
  $this->errorStop($result['errorMsg']);
 
196
  return;
197
  }
198
  if(! $result1){
199
  $this->errorStop("We received an empty response from the Wordfence server when scanning core, plugin and theme files.");
 
200
  return;
201
  }
202
  $this->status(2, 'info', "Processing scan results");
 
 
 
 
 
 
203
  foreach($result1['results'] as $issue){
204
  $this->status(2, 'info', "Adding issue: " . $issue['shortMsg']);
205
- $this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data']);
 
 
 
 
 
 
 
206
  }
207
  return $result1['unknownFiles'];
208
  }
209
  private function scanFileContents($unknownFiles){
210
- $this->status(1, 'info', "Scanning file contents.");
 
211
  if(! is_array($unknownFiles)){
212
  $unknownFiles = array();
213
  }
@@ -219,13 +255,23 @@ class wfScanEngine {
219
  if($scanner->errorMsg){
220
  $this->errorStop($scanner->errorMsg);
221
  }
 
 
222
  foreach($result2 as $issue){
223
  $this->status(2, 'info', "Adding issue: " . $issue['shortMsg']);
224
- $this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data']);
 
 
 
 
 
 
225
  }
 
 
226
  }
227
  private function scanPosts(){
228
- $this->status(1, 'info', "Starting posts scan");
229
  global $wpdb;
230
  $wfdb = new wfDB();
231
  //NOTE: There must be no other DB activity by wfDB between here and free_result below because we're doing an unbuffered query. THAT INCLUDES calls to status() which updates the DB
@@ -248,8 +294,11 @@ class wfScanEngine {
248
  $this->status(2, 'info', "Done examining URls");
249
  if($h->errorMsg){
250
  $this->errorStop($h->errorMsg);
 
251
  return;
 
252
  }
 
253
  foreach($hooverResults as $id => $hresults){
254
  $uctype = ucfirst($postDat[$id]['type']);
255
  $type = $postDat[$id]['type'];
@@ -265,7 +314,7 @@ class wfScanEngine {
265
  continue;
266
  }
267
  $this->status(2, 'info', "Adding issue: $shortMsg");
268
- $this->addIssue('postBadURL', 1, $id, $id . $postDat[$id]['contentMD5'], $shortMsg, $longMsg, array(
269
  'postID' => $id,
270
  'badURL' => $result['URL'],
271
  'postTitle' => $postDat[$id]['title'],
@@ -274,9 +323,12 @@ class wfScanEngine {
274
  'permalink' => get_permalink($id),
275
  'editPostLink' => get_edit_post_link($id),
276
  'postDate' => $postDat[$id]['postDate']
277
- ));
 
 
278
  }
279
  }
 
280
  }
281
  public function isBadComment($author, $email, $url, $IP, $content){
282
  $content = $author . ' ' . $email . ' ' . $url . ' ' . $IP . ' ' . $content;
@@ -315,11 +367,13 @@ class wfScanEngine {
315
  return false;
316
  }
317
  private function scanComments(){
 
318
  global $wpdb;
319
  $wfdb = new wfDB();
320
  //NOTE: There must be no other DB activity by wfDB between here and free_result below because we're doing an unbuffered query. THAT INCLUDES calls to status() which updates the DB
321
  $q1 = $wfdb->uQuery("select comment_ID, comment_date, comment_type, comment_author, comment_author_url, comment_content from $wpdb->comments where comment_approved=1");
322
  if( ! $q1){
 
323
  return;
324
  }
325
  $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
@@ -336,12 +390,17 @@ class wfScanEngine {
336
  );
337
  }
338
  mysql_free_result($q1);
339
- if(! $gotRow){ return; }
 
 
 
340
  $hooverResults = $h->getBaddies();
341
  if($h->errorMsg){
342
  $this->errorStop($h->errorMsg);
 
343
  return;
344
  }
 
345
  foreach($hooverResults as $id => $hresults){
346
  $uctype = ucfirst($commentDat[$id]['type']);
347
  $type = $commentDat[$id]['type'];
@@ -356,7 +415,7 @@ class wfScanEngine {
356
  //A list type that may be new and the plugin has not been upgraded yet.
357
  continue;
358
  }
359
- $this->addIssue('commentBadURL', 1, $id, $id . $commentDat[$id]['contentMD5'], $shortMsg, $longMsg, array(
360
  'commentID' => $id,
361
  'badURL' => $result['URL'],
362
  'author' => $commentDat[$id]['author'],
@@ -364,9 +423,12 @@ class wfScanEngine {
364
  'uctype' => $uctype,
365
  'editCommentLink' => get_edit_comment_link($id),
366
  'commentDate' => $commentDat[$id]['date']
367
- ));
 
 
368
  }
369
  }
 
370
  }
371
  private function highestCap($caps){
372
  foreach(array('administrator', 'editor', 'author', 'contributor', 'subscriber') as $cap){
@@ -385,12 +447,16 @@ class wfScanEngine {
385
  return false;
386
  }
387
  private function scanAllPasswords(){
 
388
  global $wpdb;
389
  $ws = $wpdb->get_results("SELECT ID, user_login FROM $wpdb->users");
 
390
  foreach($ws as $user){
391
  $this->status(2, 'info', "Checking password strength for: " . $user->user_login);
392
- $this->scanUserPassword($user->ID);
 
393
  }
 
394
  }
395
  public function scanUserPassword($userID){
396
  require_once( ABSPATH . 'wp-includes/class-phpass.php');
@@ -412,28 +478,33 @@ class wfScanEngine {
412
  $level = 2;
413
  $words = array($userDat->user_login);
414
  }
 
415
  for($i = 0; $i < sizeof($words); $i++){
416
  if($hasher->CheckPassword($words[$i], $userDat->user_pass)){
417
  $this->status(2, 'info', "Adding issue " . $shortMsg);
418
- $this->addIssue('easyPassword', $level, $userDat->ID, $userDat->ID . '-' . $userDat->user_pass, $shortMsg, $longMsg, array(
419
  'ID' => $userDat->ID,
420
  'user_login' => $userDat->user_login,
421
  'user_email' => $userDat->user_email,
422
  'first_name' => $userDat->first_name,
423
  'last_name' => $userDat->last_name,
424
  'editUserLink' => wfUtils::editUserLink($userDat->ID)
425
- ));
 
 
426
  break;
427
  }
428
  }
429
  $this->status(2, 'info', "Completed checking password strength of user '" . $userDat->user_login . "'");
 
430
  }
431
  private function scanDiskSpace(){
432
- $this->status(2, 'info', "Starting disk space check.");
433
  $total = disk_total_space('.');
434
  $free = disk_free_space('.');
435
  $this->status(2, 'info', "Total space: $total Free space: $free");
436
  if( (! $total) || (! $free )){ //If we get zeros it's probably not reading right. If free is zero then we're out of space and already in trouble.
 
437
  return;
438
  }
439
  $level = false;
@@ -444,20 +515,23 @@ class wfScanEngine {
444
  } else if($spaceLeft < 5){
445
  $level = 2;
446
  } else {
 
447
  return;
448
  }
449
- $this->status(2, 'info', "Adding issue due to low disk space.");
450
- $this->addIssue('diskSpace', $level, 'diskSpace' . $level, 'diskSpace' . $level, "You have $spaceLeft" . "% disk space remaining", "You only have $spaceLeft" . "% of your disk space remaining. Please free up disk space or your website may stop serving requests.", array(
451
- 'spaceLeft' => $spaceLeft ));
452
-
453
-
 
454
  }
455
  private function scanDNSChanges(){
456
  if(! function_exists('dns_get_record')){
457
  $this->status(1, 'info', "Skipping DNS scan because this system does not support dns_get_record()");
458
  return;
459
  }
460
- $this->status(2, 'info', "Starting DNS checks");
 
461
  $home = get_home_url();
462
  if(preg_match('/https?:\/\/([^\/]+)/i', $home, $matches)){
463
  $host = strtolower($matches[1]);
@@ -480,12 +554,14 @@ class wfScanEngine {
480
  $loggedCNAME = wfConfig::get('wf_dnsCNAME');
481
  $dnsLogged = wfConfig::get('wf_dnsLogged', false);
482
  if($dnsLogged && $loggedCNAME != $currentCNAME){
483
- $this->addIssue('dnsChange', 2, 'dnsChanges', 'dnsChanges', "Your DNS records have changed", "We have detected a change in the CNAME records of your DNS configuration for the domain $host. A CNAME record is an alias that is used to point a domain name to another domain name. For example foo.example.com can point to bar.example.com which then points to an IP address of 10.1.1.1. $msg", array(
484
  'type' => 'CNAME',
485
  'host' => $host,
486
  'oldDNS' => $loggedCNAME,
487
  'newDNS' => $currentCNAME
488
- ));
 
 
489
  }
490
  wfConfig::set('wf_dnsCNAME', $currentCNAME);
491
 
@@ -505,12 +581,14 @@ class wfScanEngine {
505
  $dnsLogged = wfConfig::get('wf_dnsLogged', false);
506
  $msg = "A change in your DNS records may indicate that a hacker has hacked into your DNS administration system and has pointed your email or website to their own server for malicious purposes. It could also indicate that your domain has expired. If you made this change yourself you can mark it 'resolved' and safely ignore it.";
507
  if($dnsLogged && $loggedA != $currentA){
508
- $this->addIssue('dnsChange', 2, 'dnsChanges', 'dnsChanges', "Your DNS records have changed", "We have detected a change in the A records of your DNS configuration that may affect the domain $host. An A record is a record in DNS that points a domain name to an IP address. $msg", array(
509
  'type' => 'A',
510
  'host' => $host,
511
  'oldDNS' => $loggedA,
512
  'newDNS' => $currentA
513
- ));
 
 
514
  }
515
  wfConfig::set('wf_dnsA', $currentA);
516
 
@@ -530,29 +608,36 @@ class wfScanEngine {
530
  $currentMX = implode(', ', $mxArr);
531
  $loggedMX = wfConfig::get('wf_dnsMX');
532
  if($dnsLogged && $loggedMX != $currentMX){
533
- $this->addIssue('dnsChange', 2, 'dnsChanges', 'dnsChanges', "Your DNS records have changed", "We have detected a change in the email server (MX) records of your DNS configuration for the domain $host. $msg", array(
534
  'type' => 'MX',
535
  'host' => $host,
536
  'oldDNS' => $loggedMX,
537
  'newDNS' => $currentMX
538
- ));
 
 
539
 
540
  }
541
  wfConfig::set('wf_dnsMX', $currentMX);
542
 
543
  wfConfig::set('wf_dnsLogged', 1);
544
  }
 
545
  }
546
  private function scanOldVersions(){
 
547
  if(! function_exists( 'get_preferred_from_update_core')){
548
  require_once(ABSPATH . 'wp-admin/includes/update.php');
549
  }
550
  $cur = get_preferred_from_update_core();
 
551
  if(isset( $cur->response ) && $cur->response == 'upgrade'){
552
- $this->addIssue('wfUpgrade', 1, 'wfUpgrade' . $cur->current, 'wfUpgrade' . $cur->current, "Your WordPress version is out of date", "WordPress version " . $cur->current . " is now available. Please upgrade immediately to get the latest security updates from WordPress.", array(
553
  'currentVersion' => $this->wp_version,
554
  'newVersion' => $cur->current
555
- ));
 
 
556
  }
557
  $update_plugins = get_site_transient( 'update_plugins' );
558
  if(isset($update_plugins) && (! empty($update_plugins->response))){
@@ -565,7 +650,9 @@ class wfScanEngine {
565
  $data = get_plugin_data($pluginFile);
566
  $data['newVersion'] = $vals->new_version;
567
  $key = 'wfPluginUpgrade' . ' ' . $plugin . ' ' . $data['newVersion'] . ' ' . $data['Version'];
568
- $this->addIssue('wfPluginUpgrade', 1, $key, $key, "The Plugin \"" . $data['Name'] . "\" needs an upgrade.", "You need to upgrade \"" . $data['Name'] . "\" to the newest version to ensure you have any security fixes the developer has released.", $data);
 
 
569
  }
570
  }
571
  }
@@ -586,13 +673,15 @@ class wfScanEngine {
586
  'version' => $themeData['Version']
587
  );
588
  $key = 'wfThemeUpgrade' . ' ' . $theme . ' ' . $tData['version'] . ' ' . $tData['newVersion'];
589
- $this->addIssue('wfThemeUpgrade', 1, $key, $key, "The Theme \"" . $themeData['Name'] . "\" needs an upgrade.", "You need to upgrade \"" . $themeData['Name'] . "\" to the newest version to ensure you have any security fixes the developer has released.", $tData);
 
 
590
  }
591
  }
592
 
593
  }
594
  }
595
-
596
  }
597
  private function errorStop($msg){
598
  $this->errorStopped = true;
@@ -603,7 +692,7 @@ class wfScanEngine {
603
  wordfence::status($level, $type, $msg);
604
  }
605
  private function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData){
606
- $this->i->addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
607
  }
608
  }
609
 
14
  private $apiKey = false;
15
  private $errorStopped = false;
16
  private $dictWords = array();
17
+ private $startTime = 0;
18
  public function __construct(){
19
+ $this->startTime = time();
20
  $this->i = new wfIssues();
21
  $this->wp_version = wfUtils::getWPVersion();
22
  $this->apiKey = wfConfig::get('apiKey');
88
  $this->scanOldVersions();
89
  if($this->errorStopped){ return; }
90
  }
91
+ $summary = $this->i->getSummaryItems();
92
+ $this->status(1, 'info', "Scan Complete. Scanned " . $summary['totalFiles'] . " files, " . $summary['totalPlugins'] . " plugins, " . $summary['totalThemes'] . " themes, " . ($summary['totalPages'] + $summary['totalPosts']) . " pages, " . $summary['totalComments'] . " comments and " . $summary['totalRows'] . " records in " . (time() - $this->startTime) . " seconds.");
93
+ if($this->i->totalIssues > 0){
94
+ $this->status(10, 'info', "SUM_FINAL:Scan complete. You have " . $this->i->totalIssues . " new issues to fix. See below for details.");
95
+ } else {
96
+ $this->status(10, 'info', "SUM_FINAL:Scan complete. Congratulations, there were no problems found.");
97
+ }
98
  return;
99
  }
100
  private function scanKnownFiles(){
101
  $malwareScanEnabled = $coreScanEnabled = $pluginScanEnabled = $themeScanEnabled = false;
102
+ $statusIDX = array(
103
+ 'core' => false,
104
+ 'plugin' => false,
105
+ 'theme' => false,
106
+ 'unknown' => false
107
+ );
108
  if(wfConfig::get('scansEnabled_core')){
109
  $coreScanEnabled = true;
110
+ $statusIDX['core'] = wordfence::statusStart("Comparing core WordPress files against originals in repository");
111
  } else {
112
+ wordfence::statusDisabled("Skipping core scan");
113
  }
114
  if(wfConfig::get('scansEnabled_plugins')){
115
  $pluginScanEnabled = true;
116
+ $statusIDX['plugin'] = wordfence::statusStart("Comparing plugin files against originals in repository");
117
  } else {
118
+ wordfence::statusDisabled("Skipping plugin scan");
119
  $this->status(2, 'info', "Skipping plugin scan because it's disabled.");
120
  }
121
  if(wfConfig::get('scansEnabled_themes')){
122
  $themeScanEnabled = true;
123
+ $statusIDX['theme'] = wordfence::statusStart("Comparing theme files against originals in repository");
124
  } else {
125
+ wordfence::statusDisabled("Skipping theme scan");
126
  $this->status(2, 'info', "Skipping themes scan because it's disabled.");
127
  }
128
 
129
  if(wfConfig::get('scansEnabled_malware')){
130
+ $statusIDX['unknown'] = wordfence::statusStart("Scanning for known malware files");
131
  $malwareScanEnabled = true;
132
  } else {
133
+ wordfence::statusDisabled("Skipping malware scan");
134
  $this->status(2, 'info', "Skipping malware scan because it's disabled.");
135
  }
136
  $summaryUpdateRequired = $this->i->summaryUpdateRequired();
137
  if((! $summaryUpdateRequired) && (! ($coreScanEnabled || $pluginScanEnabled || $themeScanEnabled || $malwareScanEnabled))){
138
+ $this->status(2, 'info', "Finishing this stage because we don't have to do a summary update and we don't need to do a core, plugin, theme or malware scan.");
139
  return array();
140
  }
141
 
142
  //CORE SCAN
 
143
  $hasher = new wordfenceHash(strlen(ABSPATH));
144
  $baseWPStuff = array( '.htaccess', 'index.php', 'license.txt', 'readme.html', 'wp-activate.php', 'wp-admin', 'wp-app.php', 'wp-blog-header.php', 'wp-comments-post.php', 'wp-config-sample.php', 'wp-content', 'wp-cron.php', 'wp-includes', 'wp-links-opml.php', 'wp-load.php', 'wp-login.php', 'wp-mail.php', 'wp-pass.php', 'wp-register.php', 'wp-settings.php', 'wp-signup.php', 'wp-trackback.php', 'xmlrpc.php');
145
  $baseContents = scandir(ABSPATH);
206
  $result1 = $this->api->call('main_scan', array(), array(
207
  'data' => json_encode($scanData)
208
  ));
 
209
  if($this->api->errorMsg){
210
  $this->errorStop($this->api->errorMsg);
211
+ wordfence::statusEndErr();
212
  return;
213
  }
214
  if(empty($result1['errorMsg']) === false){
215
  $this->errorStop($result['errorMsg']);
216
+ wordfence::statusEndErr();
217
  return;
218
  }
219
  if(! $result1){
220
  $this->errorStop("We received an empty response from the Wordfence server when scanning core, plugin and theme files.");
221
+ wordfence::statusEndErr();
222
  return;
223
  }
224
  $this->status(2, 'info', "Processing scan results");
225
+ $haveIssues = array(
226
+ 'core' => false,
227
+ 'plugin' => false,
228
+ 'theme' => false,
229
+ 'unknown' => false
230
+ );
231
  foreach($result1['results'] as $issue){
232
  $this->status(2, 'info', "Adding issue: " . $issue['shortMsg']);
233
+ if($this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data'])){
234
+ $haveIssues[$issue['data']['cType']] = true;
235
+ }
236
+ }
237
+ foreach($haveIssues as $type => $have){
238
+ if($statusIDX[$type] !== false){
239
+ wordfence::statusEnd($statusIDX[$type], $have);
240
+ }
241
  }
242
  return $result1['unknownFiles'];
243
  }
244
  private function scanFileContents($unknownFiles){
245
+ $statusIDX = wordfence::statusStart('Scanning file contents for infections and vulnerabilities');
246
+ $statusIDX2 = wordfence::statusStart('Scanning files for URLs in Google\'s Safe Browsing List');
247
  if(! is_array($unknownFiles)){
248
  $unknownFiles = array();
249
  }
255
  if($scanner->errorMsg){
256
  $this->errorStop($scanner->errorMsg);
257
  }
258
+ $haveIssues = false;
259
+ $haveIssuesGSB = false;
260
  foreach($result2 as $issue){
261
  $this->status(2, 'info', "Adding issue: " . $issue['shortMsg']);
262
+ if($this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data'])){
263
+ if(empty($issue['data']['gsb']) === false){
264
+ $haveIssuesGSB = true;
265
+ } else {
266
+ $haveIssues = true;
267
+ }
268
+ }
269
  }
270
+ wordfence::statusEnd($statusIDX, $haveIssues);
271
+ wordfence::statusEnd($statusIDX2, $haveIssuesGSB);
272
  }
273
  private function scanPosts(){
274
+ $statusIDX = wordfence::statusStart('Scanning posts for URL\'s in Google\'s Safe Browsing List');
275
  global $wpdb;
276
  $wfdb = new wfDB();
277
  //NOTE: There must be no other DB activity by wfDB between here and free_result below because we're doing an unbuffered query. THAT INCLUDES calls to status() which updates the DB
294
  $this->status(2, 'info', "Done examining URls");
295
  if($h->errorMsg){
296
  $this->errorStop($h->errorMsg);
297
+ wordfence::statusEndErr();
298
  return;
299
+
300
  }
301
+ $haveIssues = false;
302
  foreach($hooverResults as $id => $hresults){
303
  $uctype = ucfirst($postDat[$id]['type']);
304
  $type = $postDat[$id]['type'];
314
  continue;
315
  }
316
  $this->status(2, 'info', "Adding issue: $shortMsg");
317
+ if($this->addIssue('postBadURL', 1, $id, $id . $postDat[$id]['contentMD5'], $shortMsg, $longMsg, array(
318
  'postID' => $id,
319
  'badURL' => $result['URL'],
320
  'postTitle' => $postDat[$id]['title'],
323
  'permalink' => get_permalink($id),
324
  'editPostLink' => get_edit_post_link($id),
325
  'postDate' => $postDat[$id]['postDate']
326
+ ))){
327
+ $haveIssues = true;
328
+ }
329
  }
330
  }
331
+ wordfence::statusEnd($statusIDX, $haveIssues);
332
  }
333
  public function isBadComment($author, $email, $url, $IP, $content){
334
  $content = $author . ' ' . $email . ' ' . $url . ' ' . $IP . ' ' . $content;
367
  return false;
368
  }
369
  private function scanComments(){
370
+ $statusIDX = wordfence::statusStart('Scanning comments for URL\'s in Google\'s Safe Browsing List');
371
  global $wpdb;
372
  $wfdb = new wfDB();
373
  //NOTE: There must be no other DB activity by wfDB between here and free_result below because we're doing an unbuffered query. THAT INCLUDES calls to status() which updates the DB
374
  $q1 = $wfdb->uQuery("select comment_ID, comment_date, comment_type, comment_author, comment_author_url, comment_content from $wpdb->comments where comment_approved=1");
375
  if( ! $q1){
376
+ wordfence::statusEndErr();
377
  return;
378
  }
379
  $h = new wordfenceURLHoover($this->apiKey, $this->wp_version);
390
  );
391
  }
392
  mysql_free_result($q1);
393
+ if(! $gotRow){
394
+ wordfence::statusEnd($statusIDX, false);
395
+ return;
396
+ }
397
  $hooverResults = $h->getBaddies();
398
  if($h->errorMsg){
399
  $this->errorStop($h->errorMsg);
400
+ wordfence::statusEndErr();
401
  return;
402
  }
403
+ $haveIssues = false;
404
  foreach($hooverResults as $id => $hresults){
405
  $uctype = ucfirst($commentDat[$id]['type']);
406
  $type = $commentDat[$id]['type'];
415
  //A list type that may be new and the plugin has not been upgraded yet.
416
  continue;
417
  }
418
+ if($this->addIssue('commentBadURL', 1, $id, $id . $commentDat[$id]['contentMD5'], $shortMsg, $longMsg, array(
419
  'commentID' => $id,
420
  'badURL' => $result['URL'],
421
  'author' => $commentDat[$id]['author'],
423
  'uctype' => $uctype,
424
  'editCommentLink' => get_edit_comment_link($id),
425
  'commentDate' => $commentDat[$id]['date']
426
+ ))){
427
+ $haveIssues = true;
428
+ }
429
  }
430
  }
431
+ wordfence::statusEnd($statusIDX, $haveIssues);
432
  }
433
  private function highestCap($caps){
434
  foreach(array('administrator', 'editor', 'author', 'contributor', 'subscriber') as $cap){
447
  return false;
448
  }
449
  private function scanAllPasswords(){
450
+ $statusIDX = wordfence::statusStart('Scanning for weak passwords');
451
  global $wpdb;
452
  $ws = $wpdb->get_results("SELECT ID, user_login FROM $wpdb->users");
453
+ $haveIssues = false;
454
  foreach($ws as $user){
455
  $this->status(2, 'info', "Checking password strength for: " . $user->user_login);
456
+ $isWeak = $this->scanUserPassword($user->ID);
457
+ if($isWeak){ $haveIssues = true; }
458
  }
459
+ wordfence::statusEnd($statusIDX, $haveIssues);
460
  }
461
  public function scanUserPassword($userID){
462
  require_once( ABSPATH . 'wp-includes/class-phpass.php');
478
  $level = 2;
479
  $words = array($userDat->user_login);
480
  }
481
+ $haveIssue = false;
482
  for($i = 0; $i < sizeof($words); $i++){
483
  if($hasher->CheckPassword($words[$i], $userDat->user_pass)){
484
  $this->status(2, 'info', "Adding issue " . $shortMsg);
485
+ if($this->addIssue('easyPassword', $level, $userDat->ID, $userDat->ID . '-' . $userDat->user_pass, $shortMsg, $longMsg, array(
486
  'ID' => $userDat->ID,
487
  'user_login' => $userDat->user_login,
488
  'user_email' => $userDat->user_email,
489
  'first_name' => $userDat->first_name,
490
  'last_name' => $userDat->last_name,
491
  'editUserLink' => wfUtils::editUserLink($userDat->ID)
492
+ ))){
493
+ $haveIssue = true;
494
+ }
495
  break;
496
  }
497
  }
498
  $this->status(2, 'info', "Completed checking password strength of user '" . $userDat->user_login . "'");
499
+ return $haveIssue;
500
  }
501
  private function scanDiskSpace(){
502
+ $statusIDX = wordfence::statusStart("Scanning to check available disk space");
503
  $total = disk_total_space('.');
504
  $free = disk_free_space('.');
505
  $this->status(2, 'info', "Total space: $total Free space: $free");
506
  if( (! $total) || (! $free )){ //If we get zeros it's probably not reading right. If free is zero then we're out of space and already in trouble.
507
+ wordfence::statusEnd($statusIDX, false);
508
  return;
509
  }
510
  $level = false;
515
  } else if($spaceLeft < 5){
516
  $level = 2;
517
  } else {
518
+ wordfence::statusEnd($statusIDX, false);
519
  return;
520
  }
521
+ if($this->addIssue('diskSpace', $level, 'diskSpace' . $level, 'diskSpace' . $level, "You have $spaceLeft" . "% disk space remaining", "You only have $spaceLeft" . "% of your disk space remaining. Please free up disk space or your website may stop serving requests.", array(
522
+ 'spaceLeft' => $spaceLeft ))){
523
+ wordfence::statusEnd($statusIDX, true);
524
+ } else {
525
+ wordfence::statusEnd($statusIDX, true);
526
+ }
527
  }
528
  private function scanDNSChanges(){
529
  if(! function_exists('dns_get_record')){
530
  $this->status(1, 'info', "Skipping DNS scan because this system does not support dns_get_record()");
531
  return;
532
  }
533
+ $statusIDX = wordfence::statusStart("Scanning DNS for unauthorized changes");
534
+ $haveIssues = false;
535
  $home = get_home_url();
536
  if(preg_match('/https?:\/\/([^\/]+)/i', $home, $matches)){
537
  $host = strtolower($matches[1]);
554
  $loggedCNAME = wfConfig::get('wf_dnsCNAME');
555
  $dnsLogged = wfConfig::get('wf_dnsLogged', false);
556
  if($dnsLogged && $loggedCNAME != $currentCNAME){
557
+ if($this->addIssue('dnsChange', 2, 'dnsChanges', 'dnsChanges', "Your DNS records have changed", "We have detected a change in the CNAME records of your DNS configuration for the domain $host. A CNAME record is an alias that is used to point a domain name to another domain name. For example foo.example.com can point to bar.example.com which then points to an IP address of 10.1.1.1. $msg", array(
558
  'type' => 'CNAME',
559
  'host' => $host,
560
  'oldDNS' => $loggedCNAME,
561
  'newDNS' => $currentCNAME
562
+ ))){
563
+ $haveIssues = true;
564
+ }
565
  }
566
  wfConfig::set('wf_dnsCNAME', $currentCNAME);
567
 
581
  $dnsLogged = wfConfig::get('wf_dnsLogged', false);
582
  $msg = "A change in your DNS records may indicate that a hacker has hacked into your DNS administration system and has pointed your email or website to their own server for malicious purposes. It could also indicate that your domain has expired. If you made this change yourself you can mark it 'resolved' and safely ignore it.";
583
  if($dnsLogged && $loggedA != $currentA){
584
+ if($this->addIssue('dnsChange', 2, 'dnsChanges', 'dnsChanges', "Your DNS records have changed", "We have detected a change in the A records of your DNS configuration that may affect the domain $host. An A record is a record in DNS that points a domain name to an IP address. $msg", array(
585
  'type' => 'A',
586
  'host' => $host,
587
  'oldDNS' => $loggedA,
588
  'newDNS' => $currentA
589
+ ))){
590
+ $haveIssues = true;
591
+ }
592
  }
593
  wfConfig::set('wf_dnsA', $currentA);
594
 
608
  $currentMX = implode(', ', $mxArr);
609
  $loggedMX = wfConfig::get('wf_dnsMX');
610
  if($dnsLogged && $loggedMX != $currentMX){
611
+ if($this->addIssue('dnsChange', 2, 'dnsChanges', 'dnsChanges', "Your DNS records have changed", "We have detected a change in the email server (MX) records of your DNS configuration for the domain $host. $msg", array(
612
  'type' => 'MX',
613
  'host' => $host,
614
  'oldDNS' => $loggedMX,
615
  'newDNS' => $currentMX
616
+ ))){
617
+ $haveIssues = true;
618
+ }
619
 
620
  }
621
  wfConfig::set('wf_dnsMX', $currentMX);
622
 
623
  wfConfig::set('wf_dnsLogged', 1);
624
  }
625
+ wordfence::statusEnd($statusIDX, $haveIssues);
626
  }
627
  private function scanOldVersions(){
628
+ $statusIDX = wordfence::statusStart("Scanning for old themes, plugins and core files");
629
  if(! function_exists( 'get_preferred_from_update_core')){
630
  require_once(ABSPATH . 'wp-admin/includes/update.php');
631
  }
632
  $cur = get_preferred_from_update_core();
633
+ $haveIssues = false;
634
  if(isset( $cur->response ) && $cur->response == 'upgrade'){
635
+ if($this->addIssue('wfUpgrade', 1, 'wfUpgrade' . $cur->current, 'wfUpgrade' . $cur->current, "Your WordPress version is out of date", "WordPress version " . $cur->current . " is now available. Please upgrade immediately to get the latest security updates from WordPress.", array(
636
  'currentVersion' => $this->wp_version,
637
  'newVersion' => $cur->current
638
+ ))){
639
+ $haveIssues = true;
640
+ }
641
  }
642
  $update_plugins = get_site_transient( 'update_plugins' );
643
  if(isset($update_plugins) && (! empty($update_plugins->response))){
650
  $data = get_plugin_data($pluginFile);
651
  $data['newVersion'] = $vals->new_version;
652
  $key = 'wfPluginUpgrade' . ' ' . $plugin . ' ' . $data['newVersion'] . ' ' . $data['Version'];
653
+ if($this->addIssue('wfPluginUpgrade', 1, $key, $key, "The Plugin \"" . $data['Name'] . "\" needs an upgrade.", "You need to upgrade \"" . $data['Name'] . "\" to the newest version to ensure you have any security fixes the developer has released.", $data)){
654
+ $haveIssues = true;
655
+ }
656
  }
657
  }
658
  }
673
  'version' => $themeData['Version']
674
  );
675
  $key = 'wfThemeUpgrade' . ' ' . $theme . ' ' . $tData['version'] . ' ' . $tData['newVersion'];
676
+ if($this->addIssue('wfThemeUpgrade', 1, $key, $key, "The Theme \"" . $themeData['Name'] . "\" needs an upgrade.", "You need to upgrade \"" . $themeData['Name'] . "\" to the newest version to ensure you have any security fixes the developer has released.", $tData)){
677
+ $haveIssues = true;
678
+ }
679
  }
680
  }
681
 
682
  }
683
  }
684
+ wordfence::statusEnd($statusIDX, $haveIssues);
685
  }
686
  private function errorStop($msg){
687
  $this->errorStopped = true;
692
  wordfence::status($level, $type, $msg);
693
  }
694
  private function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData){
695
+ return $this->i->addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
696
  }
697
  }
698
 
lib/wfSchema.php CHANGED
@@ -126,7 +126,8 @@ class wfSchema {
126
  level tinyint UNSIGNED NOT NULL,
127
  type char(5) NOT NULL,
128
  msg varchar(255) NOT NULL,
129
- KEY k1(ctime)
 
130
  ) default charset=utf8",
131
  'wfNet404s' => "(
132
  sig binary(16) NOT NULL PRIMARY KEY,
126
  level tinyint UNSIGNED NOT NULL,
127
  type char(5) NOT NULL,
128
  msg varchar(255) NOT NULL,
129
+ KEY k1(ctime),
130
+ KEY k2(type)
131
  ) default charset=utf8",
132
  'wfNet404s' => "(
133
  sig binary(16) NOT NULL PRIMARY KEY,
lib/wfUtils.php CHANGED
@@ -165,6 +165,38 @@ class wfUtils {
165
  }
166
  return 'unknown';
167
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  }
169
 
170
 
165
  }
166
  return 'unknown';
167
  }
168
+ public static function longestLine($data){
169
+ $lines = preg_split('/[\r\n]+/', $data);
170
+ $max = 0;
171
+ foreach($lines as $line){
172
+ $len = strlen($line);
173
+ if($len > $max){
174
+ $max = $len;
175
+ }
176
+ }
177
+ return $max;
178
+ }
179
+ public static function longestNospace($data){
180
+ $lines = preg_split('/[\r\n\s\t]+/', $data);
181
+ $max = 0;
182
+ foreach($lines as $line){
183
+ $len = strlen($line);
184
+ if($len > $max){
185
+ $max = $len;
186
+ }
187
+ }
188
+ return $max;
189
+ }
190
+ public static function requestMaxMemory(){
191
+ if(wfConfig::get('maxMem', false) && (int) wfConfig::get('maxMem') > 0){
192
+ $maxMem = (int) wfConfig::get('maxMem');
193
+ } else {
194
+ $maxMem = 256;
195
+ }
196
+ if( function_exists('memory_get_usage') && ( (int) @ini_get('memory_limit') < $maxMem ) ){
197
+ @ini_set('memory_limit', $maxMem . 'M');
198
+ }
199
+ }
200
  }
201
 
202
 
lib/wfViewResult.php CHANGED
@@ -15,6 +15,7 @@
15
  if($isEmpty){
16
  echo "File is empty.";
17
  } else {
 
18
  echo $geshi->parse_code();
19
  }
20
  ?>
15
  if($isEmpty){
16
  echo "File is empty.";
17
  } else {
18
+ wfUtils::requestMaxMemory();
19
  echo $geshi->parse_code();
20
  }
21
  ?>
lib/wordfenceClass.php CHANGED
@@ -22,13 +22,24 @@ class wordfence {
22
  public static $newVisit = false;
23
  private static $wfLog = false;
24
  private static $hitID = 0;
 
25
  public static function installPlugin(){
26
- if(is_multisite() && @$_GET['networkwide'] != 1){
27
- die("Sorry but you can't activate Wordfence on an individual site when WordPress MultiSite is enabled. Only the Network Admin can enable Wordfence and only they have access to administer Wordfence.");
28
- }
29
  $schema = new wfSchema();
30
  $schema->createAll(); //if not exists
31
  wfConfig::setDefaults(); //If not set
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  if( !wp_next_scheduled( 'wordfence_daily_cron' )){
33
  wp_schedule_event(time(), 'daily', 'wordfence_daily_cron');
34
  }
@@ -36,6 +47,10 @@ class wordfence {
36
  wp_schedule_event(time(), 'hourly', 'wordfence_daily_cron');
37
  }
38
  update_option('wordfenceActivated', 1);
 
 
 
 
39
  }
40
  public static function uninstallPlugin(){
41
  update_option('wordfenceActivated', 0);
@@ -136,8 +151,12 @@ class wordfence {
136
  $wfdb->query("delete from $p"."wfThrottleLog order by endTime asc limit %d", ($count3 - $maxRows));
137
  }
138
  $count4 = $wfdb->querySingle("select count(*) as cnt from $p"."wfStatus");
139
- if($count4 > 10000){ //max status events we keep. This determines how much gets emailed to us when users sends us a debug report.
140
- $wfdb->query("delete from $p"."wfStatus order by ctime asc limit %d", ($count4 - 10000));
 
 
 
 
141
  }
142
 
143
  }
@@ -651,8 +670,7 @@ class wordfence {
651
  $issues = new wfIssues();
652
  $jsonData = array(
653
  'serverTime' => $serverTime,
654
- 'msg' => $wfdb->querySingle("select msg from $p"."wfStatus where level < 3 order by ctime desc limit 1"),
655
- 'currentScanID' => $issues->getScanTime()
656
  );
657
  $events = array();
658
  $alsoGet = $_POST['alsoGet'];
@@ -667,16 +685,14 @@ class wordfence {
667
  }
668
  $jsonData['events'] = $events;
669
  $jsonData['alsoGet'] = $alsoGet; //send it back so we don't load data if panel has changed
670
- if(wfConfig::get('wf_scanRunning') && time() - wfConfig::get('wf_scanRunning') < WORDFENCE_MAX_SCAN_TIME){
671
- $jsonData['running'] = '1';
672
- } else {
673
- $jsonData['running'] = '';
674
- }
675
  return $jsonData;
676
  }
677
- public static function ajax_loadActivityLog_callback(){
 
678
  return array(
679
- 'events' => self::getLog()->getStatusEvents()
 
 
680
  );
681
  }
682
  public static function ajax_deleteFile_callback(){
@@ -943,10 +959,10 @@ class wordfence {
943
  return ($a['ctime'] < $b['ctime']) ? -1 : 1;
944
  }
945
  public static function wfFunc_view(){
946
- $localFile = realpath(ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $_GET['file']));
947
- if(strpos($localFile, ABSPATH) !== 0){
948
- echo "An invalid file was requested for viewing.";
949
- exit(0);
950
  }
951
  $lang = false;
952
  $cont = @file_get_contents($localFile);
@@ -1048,7 +1064,7 @@ class wordfence {
1048
  public static function admin_init(){
1049
  if(! self::isAdmin()){ return; }
1050
 
1051
- foreach(array('activate', 'scan', 'sendActivityLog', 'restoreFile', 'deleteFile', 'removeExclusion', 'loadActivityLog', 'ticker', 'loadIssues', 'updateIssueStatus', 'deleteIssue', 'updateAllIssues', 'reverseLookup', 'unlockOutIP', 'unblockIP', 'blockIP', 'loadStaticPanel', 'saveConfig', 'clearAllBlocked') as $func){
1052
  add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
1053
  }
1054
  wp_enqueue_style('wordfence-main-style', WP_PLUGIN_URL . '/wordfence/css/main.css');
@@ -1069,23 +1085,26 @@ class wordfence {
1069
 
1070
  }
1071
  public static function configure_warning(){
1072
- echo '<div id="wordfenceConfigWarning" class="updated fade"><p><strong>Wordfence is almost ready.</strong> You must <a href="admin.php?page=Wordfence">configure Wordfence to activate it</a>.</p></div>';
 
 
1073
  }
1074
  public static function admin_menus(){
1075
  if(! self::isAdmin()){ return; }
1076
- if(wfConfig::get('apiKey')){
1077
- add_submenu_page("Wordfence", "Scan", "Scan", "activate_plugins", "Wordfence", 'wordfence::menu_scan');
1078
- add_menu_page('Wordfence', 'Wordfence', 'activate_plugins', 'Wordfence', 'wordfence::menu_scan', WP_PLUGIN_URL . '/wordfence/images/wordfence-logo-16x16.png', 'div');
1079
- if(wfConfig::get('liveTrafficEnabled')){
1080
- add_submenu_page("Wordfence", "Live Traffic", "Live Traffic", "activate_plugins", "WordfenceActivity", 'wordfence::menu_activity');
1081
  }
1082
- add_submenu_page('Wordfence', 'Blocked IPs', 'Blocked IPs', 'activate_plugins', 'WordfenceBlockedIPs', 'wordfence::menu_blockedIPs');
1083
- add_submenu_page("Wordfence", "Options", "Options", "activate_plugins", "WordfenceSecOpt", 'wordfence::menu_options');
1084
- } else {
1085
- add_action('admin_notices', 'wordfence::configure_warning');
1086
- add_submenu_page("Wordfence", "Configure", "Configure", "activate_plugins", "Wordfence", 'wordfence::menu_config');
1087
- add_menu_page('Wordfence', 'Wordfence', 'activate_plugins', 'Wordfence', 'wordfence::menu_config', WP_PLUGIN_URL . '/wordfence/images/wordfence-logo-16x16.png', 'div');
1088
- }
 
1089
  }
1090
  public static function menu_options(){
1091
  require 'menu_options.php';
@@ -1201,5 +1220,39 @@ class wordfence {
1201
  }
1202
  return self::$wfLog;
1203
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1204
  }
1205
  ?>
22
  public static $newVisit = false;
23
  private static $wfLog = false;
24
  private static $hitID = 0;
25
+ private static $statusStartMsgs = array();
26
  public static function installPlugin(){
 
 
 
27
  $schema = new wfSchema();
28
  $schema->createAll(); //if not exists
29
  wfConfig::setDefaults(); //If not set
30
+
31
+ $api = new wfAPI('', wfUtils::getWPVersion());
32
+ $keyData = $api->call('get_anon_api_key');
33
+ if($api->errorMsg){
34
+ die("Error fetching free API key from Wordfence: " . $api->errorMsg);
35
+ }
36
+ if($keyData['ok'] && $keyData['apiKey']){
37
+ wfConfig::set('apiKey', $keyData['apiKey']);
38
+ } else {
39
+ die("Could not understand the response we received from the Wordfence servers when applying for a free API key.");
40
+ }
41
+
42
+
43
  if( !wp_next_scheduled( 'wordfence_daily_cron' )){
44
  wp_schedule_event(time(), 'daily', 'wordfence_daily_cron');
45
  }
47
  wp_schedule_event(time(), 'hourly', 'wordfence_daily_cron');
48
  }
49
  update_option('wordfenceActivated', 1);
50
+ $db = new wfDB();
51
+
52
+ //Upgrading from 1.5.6 or earlier needs:
53
+ $db->createKeyIfNotExists($prefix . 'wfStatus', 'level', 'k2');
54
  }
55
  public static function uninstallPlugin(){
56
  update_option('wordfenceActivated', 0);
151
  $wfdb->query("delete from $p"."wfThrottleLog order by endTime asc limit %d", ($count3 - $maxRows));
152
  }
153
  $count4 = $wfdb->querySingle("select count(*) as cnt from $p"."wfStatus");
154
+ if($count4 > 100000){ //max status events we keep. This determines how much gets emailed to us when users sends us a debug report.
155
+ $wfdb->query("delete from $p"."wfStatus where level != 10 order by ctime asc limit %d", ($count4 - 100000));
156
+ $count5 = $wfdb->querySingle("select count(*) as cnt from $p"."wfStatus where level=10");
157
+ if($count5 > 100){
158
+ $wfdb->query("delete from $p"."wfStatus where level = 10 order by ctime asc limit %d", ($count5 - 100) );
159
+ }
160
  }
161
 
162
  }
670
  $issues = new wfIssues();
671
  $jsonData = array(
672
  'serverTime' => $serverTime,
673
+ 'msg' => $wfdb->querySingle("select msg from $p"."wfStatus where level < 3 order by ctime desc limit 1")
 
674
  );
675
  $events = array();
676
  $alsoGet = $_POST['alsoGet'];
685
  }
686
  $jsonData['events'] = $events;
687
  $jsonData['alsoGet'] = $alsoGet; //send it back so we don't load data if panel has changed
 
 
 
 
 
688
  return $jsonData;
689
  }
690
+ public static function ajax_activityLogUpdate_callback(){
691
+ $issues = new wfIssues();
692
  return array(
693
+ 'ok' => 1,
694
+ 'items' => self::getLog()->getStatusEvents($_POST['lastctime']),
695
+ 'currentScanID' => $issues->getScanTime()
696
  );
697
  }
698
  public static function ajax_deleteFile_callback(){
959
  return ($a['ctime'] < $b['ctime']) ? -1 : 1;
960
  }
961
  public static function wfFunc_view(){
962
+ $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $_GET['file']);
963
+ if(strpos($localFile, '..') !== false){
964
+ echo "Invalid file requested. (Relative paths not allowed)";
965
+ exit();
966
  }
967
  $lang = false;
968
  $cont = @file_get_contents($localFile);
1064
  public static function admin_init(){
1065
  if(! self::isAdmin()){ return; }
1066
 
1067
+ foreach(array('activate', 'scan', 'sendActivityLog', 'restoreFile', 'deleteFile', 'removeExclusion', 'activityLogUpdate', 'ticker', 'loadIssues', 'updateIssueStatus', 'deleteIssue', 'updateAllIssues', 'reverseLookup', 'unlockOutIP', 'unblockIP', 'blockIP', 'loadStaticPanel', 'saveConfig', 'clearAllBlocked') as $func){
1068
  add_action('wp_ajax_wordfence_' . $func, 'wordfence::ajaxReceiver');
1069
  }
1070
  wp_enqueue_style('wordfence-main-style', WP_PLUGIN_URL . '/wordfence/css/main.css');
1085
 
1086
  }
1087
  public static function configure_warning(){
1088
+ if(! preg_match('/WordfenceSecOpt/', $_SERVER['REQUEST_URI'])){
1089
+ echo '<div id="wordfenceConfigWarning" class="updated fade"><p><strong>Please set up an email address to receive Wordfence security alerts. </strong> You can do this on the <a href="admin.php?page=WordfenceSecOpt">Wordfence Options Page</a>.</p></div>';
1090
+ }
1091
  }
1092
  public static function admin_menus(){
1093
  if(! self::isAdmin()){ return; }
1094
+ if(! wfConfig::get('alertEmails')){
1095
+ if(wfUtils::isAdminPageMU()){
1096
+ add_action('network_admin_notices', 'wordfence::configure_warning');
1097
+ } else {
1098
+ add_action('admin_notices', 'wordfence::configure_warning');
1099
  }
1100
+ }
1101
+ add_submenu_page("Wordfence", "Scan", "Scan", "activate_plugins", "Wordfence", 'wordfence::menu_scan');
1102
+ add_menu_page('Wordfence', 'Wordfence', 'activate_plugins', 'Wordfence', 'wordfence::menu_scan', WP_PLUGIN_URL . '/wordfence/images/wordfence-logo-16x16.png', 'div');
1103
+ if(wfConfig::get('liveTrafficEnabled')){
1104
+ add_submenu_page("Wordfence", "Live Traffic", "Live Traffic", "activate_plugins", "WordfenceActivity", 'wordfence::menu_activity');
1105
+ }
1106
+ add_submenu_page('Wordfence', 'Blocked IPs', 'Blocked IPs', 'activate_plugins', 'WordfenceBlockedIPs', 'wordfence::menu_blockedIPs');
1107
+ add_submenu_page("Wordfence", "Options", "Options", "activate_plugins", "WordfenceSecOpt", 'wordfence::menu_options');
1108
  }
1109
  public static function menu_options(){
1110
  require 'menu_options.php';
1220
  }
1221
  return self::$wfLog;
1222
  }
1223
+ public static function statusStart($msg){
1224
+ self::$statusStartMsgs[] = $msg;
1225
+ self::status(10, 'info', 'SUM_START:' . $msg);
1226
+ return sizeof(self::$statusStartMsgs) - 1;
1227
+ }
1228
+ public static function statusEnd($idx, $haveIssues){
1229
+ if($haveIssues){
1230
+ self::status(10, 'info', 'SUM_ENDBAD:' . self::$statusStartMsgs[$idx]);
1231
+ } else {
1232
+ self::status(10, 'info', 'SUM_ENDOK:' . self::$statusStartMsgs[$idx]);
1233
+ }
1234
+ self::$statusStartMsgs[$idx] = '';
1235
+ }
1236
+ public static function statusEndErr(){
1237
+ for($i = 0; $i < sizeof(self::$statusStartMsgs); $i++){
1238
+ if(empty(self::$statusStartMsgs[$i]) === false){
1239
+ self::status(10, 'info', 'SUM_ENDERR:' . self::$statusStartMsgs[$i]);
1240
+ self::$statusStartMsgs[$i] = '';
1241
+ }
1242
+ }
1243
+ }
1244
+ public static function statusDisabled($msg){
1245
+ if(wfConfig::get('isPaid') == 'free'){
1246
+ self::status(10, 'info', "SUM_PAIDONLY:" . $msg);
1247
+ } else {
1248
+ self::status(10, 'info', "SUM_DISABLED:" . $msg);
1249
+ }
1250
+ }
1251
+ public static function wfSchemaExists(){
1252
+ $db = new wfDB();
1253
+ global $wpdb; $prefix = $wpdb->prefix;
1254
+ $exists = $db->querySingle("show tables like '$prefix"."wfConfig'");
1255
+ return $exists ? true : false;
1256
+ }
1257
  }
1258
  ?>
lib/wordfenceConstants.php CHANGED
@@ -1,9 +1,10 @@
1
  <?php
2
- define('WORDFENCE_VERSION', 1.2);
3
  define('WORDFENCE_API_URL', 'https://noc1.wordfence.com/');
4
  define('WORDFENCE_MAX_SCAN_TIME', 600);
5
  define('WORDFENCE_TRANSIENTS_TIMEOUT', 3600); //how long are items cached in seconds e.g. files downloaded for diffing
6
  define('WORDFENCE_MAX_IPLOC_AGE', 604800); //1 week
7
  define('WORDFENCE_CRAWLER_VERIFY_CACHE_TIME', 604800);
8
  define('WORDFENCE_REVERSE_LOOKUP_CACHE_TIME', 86400);
 
9
  ?>
1
  <?php
2
+ define('WORDFENCE_VERSION', 1.3);
3
  define('WORDFENCE_API_URL', 'https://noc1.wordfence.com/');
4
  define('WORDFENCE_MAX_SCAN_TIME', 600);
5
  define('WORDFENCE_TRANSIENTS_TIMEOUT', 3600); //how long are items cached in seconds e.g. files downloaded for diffing
6
  define('WORDFENCE_MAX_IPLOC_AGE', 604800); //1 week
7
  define('WORDFENCE_CRAWLER_VERIFY_CACHE_TIME', 604800);
8
  define('WORDFENCE_REVERSE_LOOKUP_CACHE_TIME', 86400);
9
+ define('WORDFENCE_MAX_FILE_SIZE_TO_PROCESS', 52428800); //50 megs
10
  ?>
lib/wordfenceHash.php CHANGED
@@ -58,13 +58,17 @@ class wordfenceHash {
58
  }
59
  }
60
  private function processFile($file){
 
 
 
 
 
 
 
 
 
61
  $wfHash = $this->wfHash($file, true);
62
  if($wfHash){
63
- if(function_exists('memory_get_usage')){
64
- wordfence::status(2, 'info', "Examined file: $file (Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
65
- } else {
66
- wordfence::status(2, 'info', "Examined file: $file");
67
- }
68
  $this->hashes[substr($file, $this->striplen)] = $wfHash;
69
  //Now that we know we can open the file, lets update stats
70
  if(preg_match('/\.(?:js|html|htm|css)$/i', $file)){
58
  }
59
  }
60
  private function processFile($file){
61
+ if(@filesize($file) > WORDFENCE_MAX_FILE_SIZE_TO_PROCESS){
62
+ wordfence::status(2, 'info', "Skipping file larger than 50 megs: $file");
63
+ return;
64
+ }
65
+ if(function_exists('memory_get_usage')){
66
+ wordfence::status(2, 'info', "Scanning: $file (Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
67
+ } else {
68
+ wordfence::status(2, 'info', "Scanning: $file");
69
+ }
70
  $wfHash = $this->wfHash($file, true);
71
  if($wfHash){
 
 
 
 
 
72
  $this->hashes[substr($file, $this->striplen)] = $wfHash;
73
  //Now that we know we can open the file, lets update stats
74
  if(preg_match('/\.(?:js|html|htm|css)$/i', $file)){
lib/wordfenceScanner.php CHANGED
@@ -49,9 +49,9 @@ class wordfenceScanner {
49
  $fsize = $fsize . "B";
50
  }
51
  if(function_exists('memory_get_usage')){
52
- wordfence::status(2, 'info', "Currently scanning: $file (Size:$fsize Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
53
  } else {
54
- wordfence::status(2, 'info', "Currently scanning: $file (Size: $fsize)");
55
  }
56
  $stime = microtime(true);
57
  $fileSum = @md5_file($this->path . $file);
@@ -88,14 +88,15 @@ class wordfenceScanner {
88
  ));
89
  break;
90
  }
91
- if(strpos($data, 'eval') !== false && strpos($data, 'base64_decode') !== false && preg_match('/[a-zA-Z0-9\+\/\=]{200,}/', $data) ){
 
92
  $this->addResult(array(
93
  'type' => 'file',
94
  'severity' => 1,
95
  'ignoreP' => $this->path . $file,
96
  'ignoreC' => $fileSum,
97
- 'shortMsg' => "This file may contain hidden executable code",
98
- 'longMsg' => "This file is a PHP executable file and contains the 'eval' and 'base64_decode' functions which are very common in backdoor programs and other malicious files. If you know about this file you can choose to ignore it to exclude it from future scans.",
99
  'data' => array(
100
  'file' => $file,
101
  'canDiff' => false,
@@ -143,7 +144,8 @@ class wordfenceScanner {
143
  'file' => $file,
144
  'canDiff' => false,
145
  'canFix' => false,
146
- 'canDelete' => true
 
147
  )
148
  ));
149
  } else if($result['badList'] == 'googpub-phish-shavar'){
@@ -158,7 +160,8 @@ class wordfenceScanner {
158
  'file' => $file,
159
  'canDiff' => false,
160
  'canFix' => false,
161
- 'canDelete' => true
 
162
  )
163
  ));
164
  }
49
  $fsize = $fsize . "B";
50
  }
51
  if(function_exists('memory_get_usage')){
52
+ wordfence::status(2, 'info', "Scanning contents: $file (Size:$fsize Mem:" . sprintf('%.1f', memory_get_usage(true) / (1024 * 1024)) . "M)");
53
  } else {
54
+ wordfence::status(2, 'info', "Scanning contents: $file (Size: $fsize)");
55
  }
56
  $stime = microtime(true);
57
  $fileSum = @md5_file($this->path . $file);
88
  ));
89
  break;
90
  }
91
+ $longestNospace = wfUtils::longestNospace($data);
92
+ if($longestNospace > 1000 && (strpos($data, 'eval') !== false || preg_match('/preg_replace\([^\(]+\/[a-z]*e/', $data)) ){
93
  $this->addResult(array(
94
  'type' => 'file',
95
  'severity' => 1,
96
  'ignoreP' => $this->path . $file,
97
  'ignoreC' => $fileSum,
98
+ 'shortMsg' => "This file may contain malicious executable code",
99
+ '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.",
100
  'data' => array(
101
  'file' => $file,
102
  'canDiff' => false,
144
  'file' => $file,
145
  'canDiff' => false,
146
  'canFix' => false,
147
+ 'canDelete' => true,
148
+ 'gsb' => 'goog-malware-shavar'
149
  )
150
  ));
151
  } else if($result['badList'] == 'googpub-phish-shavar'){
160
  'file' => $file,
161
  'canDiff' => false,
162
  'canFix' => false,
163
+ 'canDelete' => true,
164
+ 'gsb' => 'googpub-phish-shavar'
165
  )
166
  ));
167
  }
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.3.2
6
- Stable tag: 1.5.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
 
@@ -152,6 +152,21 @@ or a theme, because often these have been updated to fix a security hole.
152
  5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
 
154
  == Changelog ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  = 1.5.6 =
156
  * Removed use of nonces and purely using 30 minute key for unlocking emails.
157
  * Fixed bug that caused admin emails to not get emailed when requesting unlocking email.
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.3.2
6
+ Stable tag: 2.0.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
 
152
  5. If you're technically minded, this is the under-the-hood view of Wordfence options where you can fine-tune your security settings.
153
 
154
  == Changelog ==
155
+ = 2.0.1 =
156
+ * Improved scanning for specific attacks being used in the PHP-CGI vulnerability ( CVE-2012-1823)
157
+ * API keys no longer required. WF fetches a temporary anonymous API key for you on activation.
158
+ * Added real-time activity log on scan page.
159
+ * Added real-time summary updates on scan page.
160
+ * Fixed ability to view files that have symlinks in path.
161
+ * Added message to configure alert email address for multi-site and single site installs on activation.
162
+ * Disabled firewall rules by default because most sites don't need them.
163
+ * Disabled blocking of fake googlebots except for high security levels to prevent users who like to pretend they're googlebot from blocking themselves.
164
+ * Geshi the syntax highlighter now asks for more memory before running.
165
+ * Fixed bug that caused scan to hang on very large files.
166
+ * Added an index to wfStatus to make it faster for summary statuses
167
+ * Removed multisite pre-activation check to make activation more reliable on multisite installs.
168
+ * Better problem reporting if you trashed your Wordfence schema but the plugin is still installed.
169
+
170
  = 1.5.6 =
171
  * Removed use of nonces and purely using 30 minute key for unlocking emails.
172
  * Fixed bug that caused admin emails to not get emailed when requesting unlocking email.
wfscan.php CHANGED
@@ -20,6 +20,9 @@ require_once('lib/wfScanEngine.php');
20
 
21
  class wfScan {
22
  public static function wfScanMain(){
 
 
 
23
  if(! $_SERVER['HTTP_X_WORDFENCE_CRONKEY']){
24
  self::errorExit("The Wordfence scanner did not receive the x_wordfence_cronkey secure header.");
25
  }
@@ -43,15 +46,8 @@ class wfScan {
43
  if($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_TIME){
44
  self::errorExit("There is already a scan running.");
45
  }
46
- if(wfConfig::get('maxMem', false) && (int) wfConfig::get('maxMem') > 0){
47
- $maxMem = (int) wfConfig::get('maxMem');
48
- } else {
49
- $maxMem = 256;
50
- }
51
- if( function_exists('memory_get_usage') && ( (int) @ini_get('memory_limit') < $maxMem ) ){
52
- wordfence::status(1, 'info', "Requesting a maximum memory limit of $maxMem megabytes from PHP.");
53
- @ini_set('memory_limit', $maxMem . 'M');
54
- }
55
 
56
  set_error_handler('wfScan::error_handler', E_ALL);
57
  register_shutdown_function('wfScan::shutdown');
@@ -62,6 +58,7 @@ class wfScan {
62
  wfConfig::set('wf_scanRunning', time());
63
  $scan = new wfScanEngine();
64
  $scan->go();
 
65
  wfConfig::set('wf_scanRunning', '');
66
  }
67
  public static function obHandler($buf){
20
 
21
  class wfScan {
22
  public static function wfScanMain(){
23
+ if(! wordfence::wfSchemaExists()){
24
+ 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.");
25
+ }
26
  if(! $_SERVER['HTTP_X_WORDFENCE_CRONKEY']){
27
  self::errorExit("The Wordfence scanner did not receive the x_wordfence_cronkey secure header.");
28
  }
46
  if($scanRunning && time() - $scanRunning < WORDFENCE_MAX_SCAN_TIME){
47
  self::errorExit("There is already a scan running.");
48
  }
49
+ wordfence::status(10, 'info', "SUM_PREP:Preparing a new scan.");
50
+ wfUtils::requestMaxMemory();
 
 
 
 
 
 
 
51
 
52
  set_error_handler('wfScan::error_handler', E_ALL);
53
  register_shutdown_function('wfScan::shutdown');
58
  wfConfig::set('wf_scanRunning', time());
59
  $scan = new wfScanEngine();
60
  $scan->go();
61
+
62
  wfConfig::set('wf_scanRunning', '');
63
  }
64
  public static function obHandler($buf){
wordfence.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: Wordfence Security
4
  Plugin URI: http://wordfence.com/
5
  Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
  Author: Mark Maunder
7
- Version: 1.5.6
8
  Author URI: http://wordfence.com/
9
  */
10
  require_once('lib/wordfenceConstants.php');
4
  Plugin URI: http://wordfence.com/
5
  Description: Wordfence Security - Anti-virus and Firewall security plugin for WordPress
6
  Author: Mark Maunder
7
+ Version: 2.0.1
8
  Author URI: http://wordfence.com/
9
  */
10
  require_once('lib/wordfenceConstants.php');