PluginProbe ʕ •ᴥ•ʔ
Wordfence Security – Firewall, Malware Scan, and Login Security / 7.4.4
Wordfence Security – Firewall, Malware Scan, and Login Security v7.4.4
8.2.2 8.2.1 8.2.0 3.7.1 3.7.2 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.8.8 3.8.9 3.9.1 4.0.1 4.0.2 4.0.3 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.0.9 5.1.1 5.1.2 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.2.1 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.1 5.3.10 5.3.11 5.3.12 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.3.8 5.3.9 6.0.1 6.0.10 6.0.11 6.0.12 6.0.14 6.0.15 6.0.16 6.0.17 6.0.18 6.0.19 6.0.2 6.0.20 6.0.21 6.0.22 6.0.23 6.0.24 6.0.25 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.0.8 6.0.9 6.1.1 6.1.10 6.1.11 6.1.12 6.1.14 6.1.15 6.1.16 6.1.17 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.1.9 6.2.0 6.2.1 6.2.10 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.2.8 6.2.9 6.3.0 6.3.1 6.3.10 6.3.11 6.3.12 6.3.14 6.3.15 6.3.16 6.3.17 6.3.18 6.3.19 6.3.2 6.3.20 6.3.21 6.3.22 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.3.8 6.3.9 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1.0 7.1.1 7.1.10 7.1.11 7.1.12 7.1.14 7.1.15 7.1.16 7.1.17 7.1.18 7.1.19 7.1.2 7.1.20 7.1.3 7.1.4 7.1.5 7.1.6 7.1.7 7.1.8 7.1.9 7.10.0 7.10.1 7.10.2 7.10.3 7.10.4 7.10.5 7.10.6 7.10.7 7.11.0 7.11.1 7.11.2 7.11.3 7.11.4 7.11.5 7.11.6 7.11.7 7.2.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3.1 7.3.2 7.3.3 7.3.4 7.3.5 7.3.6 7.4.0 7.4.1 7.4.10 7.4.11 7.4.12 7.4.14 7.4.2 7.4.3 trunk 7.4.4 1.1 7.4.5 1.2 7.4.6 1.3 7.4.7 1.3.1 7.4.8 1.3.2 7.4.9 1.3.3 7.5.0 1.4.2 7.5.1 1.4.3 7.5.10 1.4.4 7.5.11 1.4.5 7.5.2 1.4.6 7.5.3 1.4.7 7.5.4 1.4.8 7.5.5 1.5.1 7.5.6 1.5.2 7.5.7 1.5.3 7.5.8 1.5.4 7.5.9 1.5.5 7.6.0 1.5.6 7.6.1 2.0.1 7.6.2 2.0.2 7.7.0 2.0.3 7.7.1 2.0.5 7.8.0 2.0.6 7.8.1 2.0.7 7.8.2 2.1.0 7.9.0 2.1.1 7.9.1 2.1.2 7.9.2 2.1.3 7.9.3 2.1.4 8.0.0 2.1.5 8.0.1 3.0.2 8.0.2 3.0.3 8.0.3 3.0.4 8.0.4 3.0.5 8.0.5 3.0.6 8.1.0 3.0.7 8.1.1 3.0.8 8.1.2 3.0.9 8.1.3 3.1.0 8.1.4 3.1.1 v1.4.1 3.1.2 3.1.4 3.1.6 3.2.1 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.1 3.4.4 3.4.5 3.5.1 3.5.2 3.6.1 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9
wordfence / lib / wfIssues.php
wordfence / lib Last commit date
Diff 6 years ago dashboard 7 years ago rest-api 6 years ago .htaccess 7 years ago Diff.php 6 years ago GeoLite2-Country.mmdb 6 years ago IPTraf.php 6 years ago IPTrafList.php 7 years ago WFLSPHP52Compatability.php 6 years ago compat.php 8 years ago conntest.php 7 years ago cronview.php 8 years ago dbview.php 8 years ago diffResult.php 8 years ago email_genericAlert.php 7 years ago email_newIssues.php 6 years ago email_unlockRequest.php 8 years ago email_unsubscribeRequest.php 7 years ago flags.php 7 years ago live_activity.php 8 years ago menu_dashboard.php 7 years ago menu_dashboard_options.php 7 years ago menu_firewall.php 6 years ago menu_firewall_blocking.php 7 years ago menu_firewall_blocking_options.php 8 years ago menu_firewall_waf.php 7 years ago menu_firewall_waf_options.php 7 years ago menu_options.php 6 years ago menu_scanner.php 6 years ago menu_scanner_credentials.php 8 years ago menu_scanner_options.php 6 years ago menu_support.php 7 years ago menu_tools.php 7 years ago menu_tools_diagnostic.php 6 years ago menu_tools_importExport.php 7 years ago menu_tools_livetraffic.php 6 years ago menu_tools_twoFactor.php 7 years ago menu_tools_whois.php 8 years ago menu_wordfence_central.php 7 years ago noc1.key 7 years ago sysinfo.php 8 years ago unknownFiles.php 8 years ago viewFullActivityLog.php 8 years ago wf503.php 7 years ago wfAPI.php 6 years ago wfActivityReport.php 6 years ago wfAdminNoticeQueue.php 8 years ago wfAlerts.php 6 years ago wfArray.php 7 years ago wfBrowscap.php 6 years ago wfBrowscapCache.php 7 years ago wfBulkCountries.php 7 years ago wfCache.php 6 years ago wfCentralAPI.php 6 years ago wfConfig.php 6 years ago wfCrawl.php 6 years ago wfCredentialsController.php 7 years ago wfCrypt.php 6 years ago wfDB.php 7 years ago wfDashboard.php 7 years ago wfDateLocalization.php 8 years ago wfDiagnostic.php 6 years ago wfDict.php 8 years ago wfDirectoryIterator.php 7 years ago wfHelperBin.php 11 years ago wfHelperString.php 11 years ago wfIPWhitelist.php 7 years ago wfImportExportController.php 7 years ago wfIssues.php 6 years ago wfJWT.php 7 years ago wfLockedOut.php 7 years ago wfLog.php 6 years ago wfMD5BloomFilter.php 8 years ago wfModuleController.php 7 years ago wfNotification.php 8 years ago wfOnboardingController.php 7 years ago wfPersistenceController.php 8 years ago wfRESTAPI.php 7 years ago wfScan.php 6 years ago wfScanEngine.php 6 years ago wfSchema.php 6 years ago wfStyle.php 7 years ago wfSupportController.php 6 years ago wfUnlockMsg.php 6 years ago wfUpdateCheck.php 6 years ago wfUtils.php 6 years ago wfVersionCheckController.php 8 years ago wfView.php 10 years ago wfViewResult.php 8 years ago wordfenceClass.php 6 years ago wordfenceConstants.php 6 years ago wordfenceHash.php 6 years ago wordfenceScanner.php 6 years ago wordfenceURLHoover.php 6 years ago
wfIssues.php
720 lines
1 <?php
2 require_once(dirname(__FILE__) . '/wfUtils.php');
3 class wfIssues {
4 //Possible responses from `addIssue`
5 const ISSUE_ADDED = 'a';
6 const ISSUE_UPDATED = 'u';
7 const ISSUE_DUPLICATE = 'd';
8 const ISSUE_IGNOREP = 'ip';
9 const ISSUE_IGNOREC = 'ic';
10
11 //Possible status message states
12 const STATUS_NONE = 'n'; //Default state before running
13
14 const STATUS_SKIPPED = 's'; //The scan job was skipped because it didn't need to run
15 const STATUS_IGNORED = 'i'; //The scan job found an issue, but it matched an entry in the ignore list
16
17 const STATUS_PROBLEM = 'p'; //The scan job found an issue
18 const STATUS_SECURE = 'r'; //The scan job found no issues
19
20 const STATUS_FAILED = 'f'; //The scan job failed
21 const STATUS_SUCCESS = 'c'; //The scan job succeeded
22
23 const STATUS_PAIDONLY = 'x';
24
25 //Possible scan failure types
26 const SCAN_FAILED_GENERAL = 'general';
27 const SCAN_FAILED_TIMEOUT = 'timeout';
28 const SCAN_FAILED_DURATION_REACHED = 'duration';
29 const SCAN_FAILED_VERSION_CHANGE = 'versionchange';
30 const SCAN_FAILED_FORK_FAILED = 'forkfailed';
31 const SCAN_FAILED_CALLBACK_TEST_FAILED = 'callbackfailed';
32 const SCAN_FAILED_START_TIMEOUT = 'starttimeout';
33
34 const SCAN_FAILED_API_SSL_UNAVAILABLE = 'sslunavailable';
35 const SCAN_FAILED_API_CALL_FAILED = 'apifailed';
36 const SCAN_FAILED_API_INVALID_RESPONSE = 'apiinvalid';
37 const SCAN_FAILED_API_ERROR_RESPONSE = 'apierror';
38
39 const SEVERITY_NONE = 0;
40 const SEVERITY_LOW = 25;
41 const SEVERITY_MEDIUM = 50;
42 const SEVERITY_HIGH = 75;
43 const SEVERITY_CRITICAL = 100;
44
45 private $db = false;
46
47 //Properties that are serialized on sleep:
48 private $updateCalled = false;
49 private $issuesTable = '';
50 private $pendingIssuesTable = '';
51 private $maxIssues = 0;
52 private $newIssues = array();
53 public $totalIssues = 0;
54 public $totalIgnoredIssues = 0;
55 private $totalIssuesBySeverity = array();
56
57 public static $issueSeverities = array(
58 'checkGSB' => wfIssues::SEVERITY_CRITICAL,
59 'checkSpamIP' => wfIssues::SEVERITY_HIGH,
60 'spamvertizeCheck' => wfIssues::SEVERITY_CRITICAL,
61 'commentBadURL' => wfIssues::SEVERITY_LOW,
62 'postBadTitle' => wfIssues::SEVERITY_HIGH,
63 'postBadURL' => wfIssues::SEVERITY_HIGH,
64 'file' => wfIssues::SEVERITY_CRITICAL,
65 'timelimit' => wfIssues::SEVERITY_HIGH,
66 'checkHowGetIPs' => wfIssues::SEVERITY_HIGH,
67 'diskSpace' => wfIssues::SEVERITY_HIGH,
68 'wafStatus' => wfIssues::SEVERITY_CRITICAL,
69 'configReadable' => wfIssues::SEVERITY_CRITICAL,
70 'wfPluginVulnerable' => wfIssues::SEVERITY_HIGH,
71 'coreUnknown' => wfIssues::SEVERITY_HIGH,
72 'easyPasswordWeak' => wfIssues::SEVERITY_HIGH,
73 'knownfile' => wfIssues::SEVERITY_HIGH,
74 'optionBadURL' => wfIssues::SEVERITY_HIGH,
75 'publiclyAccessible' => wfIssues::SEVERITY_HIGH,
76 'suspiciousAdminUsers' => wfIssues::SEVERITY_HIGH,
77 'wfPluginAbandoned' => wfIssues::SEVERITY_MEDIUM,
78 'wfPluginRemoved' => wfIssues::SEVERITY_CRITICAL,
79 'wfPluginUpgrade' => wfIssues::SEVERITY_MEDIUM,
80 'wfThemeUpgrade' => wfIssues::SEVERITY_MEDIUM,
81 'wfUpgrade' => wfIssues::SEVERITY_HIGH,
82 'wpscan_directoryList' => wfIssues::SEVERITY_HIGH,
83 'wpscan_fullPathDiscl' => wfIssues::SEVERITY_HIGH,
84 );
85
86 public static function validIssueTypes() {
87 return array('checkHowGetIPs', 'checkSpamIP', 'commentBadURL', 'configReadable', 'coreUnknown', 'database', 'diskSpace', 'wafStatus', 'easyPassword', 'file', 'geoipSupport', 'knownfile', 'optionBadURL', 'postBadTitle', 'postBadURL', 'publiclyAccessible', 'spamvertizeCheck', 'suspiciousAdminUsers', 'timelimit', 'wfPluginAbandoned', 'wfPluginRemoved', 'wfPluginUpgrade', 'wfPluginVulnerable', 'wfThemeUpgrade', 'wfUpgrade', 'wpscan_directoryList', 'wpscan_fullPathDiscl');
88 }
89
90 public static function statusPrep(){
91 wfConfig::set_ser('wfStatusStartMsgs', array());
92 wordfence::status(10, 'info', "SUM_PREP:Preparing a new scan.");
93 wfIssues::updateScanStillRunning();
94 }
95
96 public static function statusStart($message) {
97 $statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
98 $statusStartMsgs[] = $message;
99 wfConfig::set_ser('wfStatusStartMsgs', $statusStartMsgs);
100 wordfence::status(10, 'info', 'SUM_START:' . $message);
101 wfIssues::updateScanStillRunning();
102 return count($statusStartMsgs) - 1;
103 }
104
105 public static function statusEnd($index, $state) {
106 $statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
107 if ($state == self::STATUS_SKIPPED) {
108 wordfence::status(10, 'info', 'SUM_ENDSKIPPED:' . $statusStartMsgs[$index]);
109 }
110 else if ($state == self::STATUS_IGNORED) {
111 wordfence::status(10, 'info', 'SUM_ENDIGNORED:' . $statusStartMsgs[$index]);
112 }
113 else if ($state == self::STATUS_PROBLEM) {
114 wordfence::status(10, 'info', 'SUM_ENDBAD:' . $statusStartMsgs[$index]);
115 }
116 else if ($state == self::STATUS_SECURE) {
117 wordfence::status(10, 'info', 'SUM_ENDOK:' . $statusStartMsgs[$index]);
118 }
119 else if ($state == self::STATUS_FAILED) {
120 wordfence::status(10, 'info', 'SUM_ENDFAILED:' . $statusStartMsgs[$index]);
121 }
122 else if ($state == self::STATUS_SUCCESS) {
123 wordfence::status(10, 'info', 'SUM_ENDSUCCESS:' . $statusStartMsgs[$index]);
124 }
125 wfIssues::updateScanStillRunning();
126 $statusStartMsgs[$index] = '';
127 wfConfig::set_ser('wfStatusStartMsgs', $statusStartMsgs);
128 }
129
130 public static function statusEndErr() {
131 $statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
132 for ($i = 0; $i < count($statusStartMsgs); $i++) {
133 if (empty($statusStartMsgs[$i]) === false) {
134 wordfence::status(10, 'info', 'SUM_ENDERR:' . $statusStartMsgs[$i]);
135 $statusStartMsgs[$i] = '';
136 }
137 }
138 wfIssues::updateScanStillRunning();
139 }
140
141 public static function statusPaidOnly($message) {
142 wordfence::status(10, 'info', "SUM_PAIDONLY:" . $message);
143 wfIssues::updateScanStillRunning();
144 }
145
146 public static function statusDisabled($message) {
147 wordfence::status(10, 'info', "SUM_DISABLED:" . $message);
148 wfIssues::updateScanStillRunning();
149 }
150
151 public static function updateScanStillRunning($running = true) {
152 $timestamp = time();
153 if (!$running) {
154 $timestamp = 0;
155 }
156 wfConfig::set('wf_scanLastStatusTime', $timestamp);
157 }
158
159 /**
160 * Returns false if the scan has not been detected as failed. If it has, returns a constant corresponding to the reason.
161 *
162 * @return bool|string
163 */
164 public static function hasScanFailed() {
165 $lastStatusUpdate = self::lastScanStatusUpdate();
166 if ($lastStatusUpdate !== false && wfScanner::shared()->isRunning()) {
167 $threshold = WORDFENCE_SCAN_FAILURE_THRESHOLD;
168 if (time() - $lastStatusUpdate > $threshold) {
169 return self::SCAN_FAILED_TIMEOUT;
170 }
171 }
172
173 $scanStartAttempt = wfConfig::get('scanStartAttempt', 0);
174 if ($scanStartAttempt && time() - $scanStartAttempt > WORDFENCE_SCAN_START_FAILURE_THRESHOLD) {
175 return self::SCAN_FAILED_START_TIMEOUT;
176 }
177
178 $recordedFailure = wfConfig::get('lastScanFailureType');
179 switch ($recordedFailure) {
180 case self::SCAN_FAILED_GENERAL:
181 case self::SCAN_FAILED_DURATION_REACHED:
182 case self::SCAN_FAILED_VERSION_CHANGE:
183 case self::SCAN_FAILED_FORK_FAILED:
184 case self::SCAN_FAILED_CALLBACK_TEST_FAILED:
185 case self::SCAN_FAILED_API_SSL_UNAVAILABLE:
186 case self::SCAN_FAILED_API_CALL_FAILED:
187 case self::SCAN_FAILED_API_INVALID_RESPONSE:
188 case self::SCAN_FAILED_API_ERROR_RESPONSE:
189 return $recordedFailure;
190 }
191
192 return false;
193 }
194
195 /**
196 * Returns false if the scan has not been detected as timed out. If it has, it returns the timestamp of the last status update.
197 *
198 * @return bool|int
199 */
200 public static function lastScanStatusUpdate() {
201 if (wfConfig::get('wf_scanLastStatusTime', 0) === 0) {
202 return false;
203 }
204
205 $threshold = WORDFENCE_SCAN_FAILURE_THRESHOLD;
206 return (time() > wfConfig::get('wf_scanLastStatusTime', 0) + $threshold) ? wfConfig::get('wf_scanLastStatusTime', 0) : false;
207 }
208
209 /**
210 * Returns the singleton wfIssues.
211 *
212 * @return wfIssues
213 */
214 public static function shared() {
215 static $_issues = null;
216 if ($_issues === null) {
217 $_issues = new wfIssues();
218 }
219 return $_issues;
220 }
221
222 public function __sleep(){ //Same order here as vars above
223 return array('updateCalled', 'issuesTable', 'pendingIssuesTable', 'maxIssues', 'newIssues', 'totalIssues', 'totalIgnoredIssues', 'totalIssuesBySeverity');
224 }
225 public function __construct(){
226 $this->issuesTable = wfDB::networkTable('wfIssues');
227 $this->pendingIssuesTable = wfDB::networkTable('wfPendingIssues');
228 $this->maxIssues = wfConfig::get('scan_maxIssues', 0);
229 }
230 public function __wakeup(){
231 $this->db = new wfDB();
232 }
233
234 public function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed = false) {
235 return $this->_addIssue('issue', $type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed);
236 }
237 public function addPendingIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData) {
238 return $this->_addIssue('pending', $type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
239 }
240
241 /**
242 * Create a new issue
243 *
244 * @param string $group The issue type (e.g., issue or pending
245 * @param string $type
246 * @param int $severity
247 * @param string $ignoreP string to compare against for permanent ignores
248 * @param string $ignoreC string to compare against for ignoring until something changes
249 * @param string $shortMsg
250 * @param string $longMsg
251 * @param array $templateData
252 * @param bool $alreadyHashed If true, don't re-hash $ignoreP and $ignoreC
253 * @return string One of the ISSUE_ constants
254 */
255 private function _addIssue($group, $type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed = false) {
256 if ($group == 'pending') {
257 $table = $this->pendingIssuesTable;
258 }
259 else {
260 $table = $this->issuesTable;
261 }
262
263 if (!$alreadyHashed) {
264 $ignoreP = md5($ignoreP);
265 $ignoreC = md5($ignoreC);
266 }
267
268 $results = $this->getDB()->querySelect("SELECT id, status, ignoreP, ignoreC FROM {$table} WHERE (ignoreP = '%s' OR ignoreC = '%s')", $ignoreP, $ignoreC);
269 foreach ($results as $row) {
270 if ($row['status'] == 'new' && ($row['ignoreC'] == $ignoreC || $row['ignoreP'] == $ignoreP)) {
271 if ($type != 'file' && $type != 'database') { //Filter out duplicate new issues except for infected files because we want to see all infections even if file contents are identical
272 return self::ISSUE_DUPLICATE;
273 }
274 }
275
276 if ($row['status'] == 'ignoreP' && $row['ignoreP'] == $ignoreP) { $this->totalIgnoredIssues++; return self::ISSUE_IGNOREP; } //Always ignore
277 else if ($row['status'] == 'ignoreC' && $row['ignoreC'] == $ignoreC) { $this->totalIgnoredIssues++; return self::ISSUE_IGNOREC; } //Unchanged, ignore
278 else if ($row['status'] == 'ignoreC') {
279 $updateID = $row['id']; //Re-use the existing issue row
280 break;
281 }
282 }
283
284 if ($group != 'pending') {
285 if (!array_key_exists($severity, $this->totalIssuesBySeverity)) {
286 $this->totalIssuesBySeverity[$severity] = 0;
287 }
288 $this->totalIssuesBySeverity[$severity]++;
289 $this->totalIssues++;
290 if (empty($this->maxIssues) || $this->totalIssues <= $this->maxIssues)
291 {
292 $this->newIssues[] = array(
293 'type' => $type,
294 'severity' => $severity,
295 'ignoreP' => $ignoreP,
296 'ignoreC' => $ignoreC,
297 'shortMsg' => $shortMsg,
298 'longMsg' => $longMsg,
299 'tmplData' => $templateData
300 );
301 }
302 }
303
304 if (isset($updateID)) {
305 if ($group !== 'pending' && wfCentral::isConnected()) {
306 wfCentral::sendIssue(array(
307 'id' => $updateID,
308 'lastUpdated' => time(),
309 'type' => $type,
310 'severity' => $severity,
311 'ignoreP' => $ignoreP,
312 'ignoreC' => $ignoreC,
313 'shortMsg' => $shortMsg,
314 'longMsg' => $longMsg,
315 'data' => $templateData,
316 ));
317 }
318
319 $this->getDB()->queryWrite(
320 "UPDATE {$table} SET lastUpdated = UNIX_TIMESTAMP(), status = '%s', type = '%s', severity = %d, ignoreP = '%s', ignoreC = '%s', shortMsg = '%s', longMsg = '%s', data = '%s' WHERE id = %d",
321 'new',
322 $type,
323 $severity,
324 $ignoreP,
325 $ignoreC,
326 $shortMsg,
327 $longMsg,
328 serialize($templateData),
329 $updateID);
330
331
332 return self::ISSUE_UPDATED;
333 }
334
335 $this->getDB()->queryWrite("INSERT INTO {$table} (time, lastUpdated, status, type, severity, ignoreP, ignoreC, shortMsg, longMsg, data) VALUES (UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s')",
336 'new',
337 $type,
338 $severity,
339 $ignoreP,
340 $ignoreC,
341 $shortMsg,
342 $longMsg,
343 serialize($templateData));
344
345 if ($group !== 'pending' && wfCentral::isConnected()) {
346 global $wpdb;
347 wfCentral::sendIssue(array(
348 'id' => $wpdb->insert_id,
349 'status' => 'new',
350 'time' => time(),
351 'lastUpdated' => time(),
352 'type' => $type,
353 'severity' => $severity,
354 'ignoreP' => $ignoreP,
355 'ignoreC' => $ignoreC,
356 'shortMsg' => $shortMsg,
357 'longMsg' => $longMsg,
358 'data' => $templateData,
359 ));
360 }
361
362 return self::ISSUE_ADDED;
363 }
364 public function deleteIgnored(){
365 if (wfCentral::isConnected()) {
366 $result = $this->getDB()->querySelect("SELECT id from " . $this->issuesTable . " where status='ignoreP' or status='ignoreC'");
367 $issues = array();
368 foreach ($result as $row) {
369 $issues[] = $row['id'];
370 }
371 wfCentral::deleteIssues($issues);
372 }
373
374 $this->getDB()->queryWrite("delete from " . $this->issuesTable . " where status='ignoreP' or status='ignoreC'");
375 }
376 public function deleteNew($types = null) {
377 if (!is_array($types)) {
378 if (wfCentral::isConnected()) {
379 wfCentral::deleteNewIssues();
380 }
381
382 $this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new'");
383 }
384 else {
385 if (wfCentral::isConnected()) {
386 wfCentral::deleteIssueTypes($types, 'new');
387 }
388
389 $query = "DELETE FROM {$this->issuesTable} WHERE status = 'new' AND type IN (" . implode(',', array_fill(0, count($types), "'%s'")) . ")";
390 array_unshift($types, $query);
391 call_user_func_array(array($this->getDB(), 'queryWrite'), $types);
392 }
393 }
394 public function ignoreAllNew(){
395 if (wfCentral::isConnected()) {
396 $issues = $this->getDB()->querySelect('SELECT * FROM ' . $this->issuesTable . ' WHERE status=\'new\'');
397 if ($issues) {
398 wfCentral::sendIssues($issues);
399 }
400 }
401
402 $this->getDB()->queryWrite("update " . $this->issuesTable . " set status='ignoreC' where status='new'");
403 }
404 public function emailNewIssues($timeLimitReached = false, $scanController = false){
405 $level = wfConfig::getAlertLevel();
406 $emails = wfConfig::getAlertEmails();
407 if (!count($emails)) {
408 return;
409 }
410
411 $shortSiteURL = preg_replace('/^https?:\/\//i', '', site_url());
412 $subject = "[Wordfence Alert] Problems found on $shortSiteURL";
413
414 if(sizeof($emails) < 1){ return; }
415 if($level < 1){ return; }
416 $needsToAlert = false;
417 foreach ($this->totalIssuesBySeverity as $issueSeverity => $totalIssuesBySeverity) {
418 if ($issueSeverity >= $level && $totalIssuesBySeverity > 0) {
419 $needsToAlert = true;
420 break;
421 }
422 }
423 if (!$needsToAlert) {
424 return;
425 }
426 $emailedIssues = wfConfig::get_ser('emailedIssuesList', array());
427 if(! is_array($emailedIssues)){
428 $emailedIssues = array();
429 }
430 $overflowCount = $this->totalIssues - count($this->newIssues);
431 $finalIssues = array();
432 foreach($this->newIssues as $newIssue){
433 $alreadyEmailed = false;
434 foreach($emailedIssues as $emailedIssue){
435 if($newIssue['ignoreP'] == $emailedIssue['ignoreP'] || $newIssue['ignoreC'] == $emailedIssue['ignoreC']){
436 $alreadyEmailed = true;
437 break;
438 }
439 }
440 if(! $alreadyEmailed){
441 $finalIssues[] = $newIssue;
442 }
443 else {
444 $overflowCount--;
445 }
446 }
447 if(sizeof($finalIssues) < 1){ return; }
448
449 $this->newIssues = array();
450 $this->totalIssues = 0;
451
452 $totals = array();
453 foreach($finalIssues as $i){
454 $emailedIssues[] = array( 'ignoreC' => $i['ignoreC'], 'ignoreP' => $i['ignoreP'] );
455 if (!array_key_exists($i['severity'], $totals)) {
456 $totals[$i['severity']] = 0;
457 }
458 $totals[$i['severity']]++;
459 }
460 wfConfig::set_ser('emailedIssuesList', $emailedIssues);
461 $needsToAlert = false;
462 foreach ($totals as $issueSeverity => $totalIssuesBySeverity) {
463 if ($issueSeverity >= $level && $totalIssuesBySeverity > 0) {
464 $needsToAlert = true;
465 break;
466 }
467 }
468 if (!$needsToAlert) {
469 return;
470 }
471
472 $content = wfUtils::tmpl('email_newIssues.php', array(
473 'isPaid' => wfConfig::get('isPaid'),
474 'issues' => $finalIssues,
475 'totals' => $totals,
476 'level' => $level,
477 'issuesNotShown' => $overflowCount,
478 'adminURL' => get_admin_url(),
479 'timeLimitReached' => $timeLimitReached,
480 'scanController' => ($scanController ? $scanController : wfScanner::shared()),
481 ));
482
483 foreach ($emails as $email) {
484 $uniqueContent = str_replace('<!-- ##UNSUBSCRIBE## -->', sprintf(__('No longer an administrator for this site? <a href="%s" target="_blank">Click here</a> to stop receiving security alerts.', 'wordfence'), wfUtils::getSiteBaseURL() . '?_wfsf=removeAlertEmail&jwt=' . wfUtils::generateJWT(array('email' => $email))), $content);
485 wp_mail($email, $subject, $uniqueContent, 'Content-type: text/html');
486 }
487 }
488 public function deleteIssue($id){
489 $this->getDB()->queryWrite("delete from " . $this->issuesTable . " where id=%d", $id);
490 if (wfCentral::isConnected()) {
491 wfCentral::deleteIssue($id);
492 }
493 }
494
495 public function deleteUpdateIssues($type)
496 {
497 $this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND type = '%s'", $type);
498
499 if (wfCentral::isConnected()) {
500 wfCentral::deleteIssueTypes(array($type));
501 }
502 }
503
504 public function deleteAllUpdateIssues()
505 {
506 $this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
507
508 if (wfCentral::isConnected()) {
509 wfCentral::deleteIssueTypes(array('wfUpgrade', 'wfPluginUpgrade', 'wfThemeUpgrade'));
510 }
511 }
512
513 public function updateIssue($id, $status){ //ignoreC, ignoreP, delete or new
514 if($status == 'delete'){
515 if (wfCentral::isConnected()) {
516 wfCentral::deleteIssue($id);
517 }
518 $this->getDB()->queryWrite("delete from " . $this->issuesTable . " where id=%d", $id);
519 } else if($status == 'ignoreC' || $status == 'ignoreP' || $status == 'new'){
520 $this->getDB()->queryWrite("update " . $this->issuesTable . " set status='%s' where id=%d", $status, $id);
521
522 if (wfCentral::isConnected()) {
523 $issue = $this->getDB()->querySelect('SELECT * FROM ' . $this->issuesTable . ' where id=%d', $id);
524 if ($issue) {
525 wfCentral::sendIssues($issue);
526 }
527 }
528 }
529 }
530 public function getIssueByID($id){
531 $rec = $this->getDB()->querySingleRec("select * from " . $this->issuesTable . " where id=%d", $id);
532 $rec['data'] = unserialize($rec['data']);
533 return $rec;
534 }
535 public function getIssueCounts() {
536 global $wpdb;
537 $counts = $wpdb->get_results('SELECT COUNT(*) AS c, status FROM ' . $this->issuesTable . ' WHERE status = "new" OR status = "ignoreP" OR status = "ignoreC" GROUP BY status', ARRAY_A);
538 $result = array();
539 foreach ($counts as $row) {
540 $result[$row['status']] = $row['c'];
541 }
542 return $result;
543 }
544 public function getIssues($offset = 0, $limit = 100, $ignoredOffset = 0, $ignoredLimit = 100) {
545 /** @var wpdb $wpdb */
546 global $wpdb;
547
548 $siteCleaningTypes = array('file', 'checkGSB', 'checkSpamIP', 'commentBadURL', 'knownfile', 'optionBadURL', 'postBadTitle', 'postBadURL', 'spamvertizeCheck', 'suspiciousAdminUsers');
549 $sortTagging = 'CASE';
550 foreach ($siteCleaningTypes as $index => $t) {
551 $sortTagging .= ' WHEN type = \'' . esc_sql($t) . '\' THEN ' . ((int) $index);
552 }
553 $sortTagging .= ' ELSE 999 END';
554
555 $ret = array(
556 'new' => array(),
557 'ignored' => array()
558 );
559 $userIni = ini_get('user_ini.filename');
560 $q1 = $this->getDB()->querySelect("SELECT *, {$sortTagging} AS sortTag FROM " . $this->issuesTable . " WHERE status = 'new' ORDER BY severity DESC, sortTag ASC, type ASC, time DESC LIMIT %d,%d", $offset, $limit);
561 $q2 = $this->getDB()->querySelect("SELECT *, {$sortTagging} AS sortTag FROM " . $this->issuesTable . " WHERE status = 'ignoreP' OR status = 'ignoreC' ORDER BY severity DESC, sortTag ASC, type ASC, time DESC LIMIT %d,%d", $ignoredOffset, $ignoredLimit);
562 $q = array_merge($q1, $q2);
563 foreach($q as $i){
564 $i['data'] = unserialize($i['data']);
565 $i['timeAgo'] = wfUtils::makeTimeAgo(time() - $i['time']);
566 $i['displayTime'] = wfUtils::formatLocalTime(get_option('date_format') . ' ' . get_option('time_format'), $i['time']);
567 $i['longMsg'] = wp_kses($i['longMsg'], 'post');
568 if($i['status'] == 'new'){
569 $ret['new'][] = $i;
570 } else if($i['status'] == 'ignoreP' || $i['status'] == 'ignoreC'){
571 $ret['ignored'][] = $i;
572 } else {
573 error_log("Issue has bad status: " . $i['status']);
574 continue;
575 }
576 }
577 foreach($ret as $status => &$issueList){
578 for($i = 0; $i < sizeof($issueList); $i++){
579 if ($issueList[$i]['type'] == 'file' || $issueList[$i]['type'] == 'knownfile') {
580 $localFile = $issueList[$i]['data']['file'];
581 if ($localFile != '.htaccess' && $localFile != $userIni) {
582 $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $localFile);
583 }
584 else {
585 $localFile = ABSPATH . '/' . $localFile;
586 }
587
588 if(file_exists($localFile)){
589 $issueList[$i]['data']['fileExists'] = true;
590 } else {
591 $issueList[$i]['data']['fileExists'] = '';
592 }
593 }
594 if ($issueList[$i]['type'] == 'database') {
595 $issueList[$i]['data']['optionExists'] = false;
596 if (!empty($issueList[$i]['data']['site_id'])) {
597 $table_options = wfDB::blogTable('options', $issueList[$i]['data']['site_id']);
598 $issueList[$i]['data']['optionExists'] = $wpdb->get_var($wpdb->prepare("SELECT count(*) FROM {$table_options} WHERE option_name = %s", $issueList[$i]['data']['option_name'])) > 0;
599 }
600 }
601 $issueList[$i]['issueIDX'] = $i;
602 if (isset($issueList[$i]['data']['cType'])) {
603 $issueList[$i]['data']['ucType'] = ucwords($issueList[$i]['data']['cType']);
604 }
605 }
606 }
607 return $ret; //array of lists of issues by status
608 }
609 public function getPendingIssues($offset = 0, $limit = 100){
610 /** @var wpdb $wpdb */
611 global $wpdb;
612 $issues = $this->getDB()->querySelect("SELECT * FROM {$this->pendingIssuesTable} ORDER BY id ASC LIMIT %d,%d", $offset, $limit);
613 foreach($issues as &$i){
614 $i['data'] = unserialize($i['data']);
615 }
616 return $issues;
617 }
618 public function getFixableIssueCount() {
619 global $wpdb;
620 $issues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE data LIKE '%s:6:\"canFix\";b:1;%'");
621 $count = 0;
622 foreach ($issues as $i) {
623 $i['data'] = unserialize($i['data']);
624 if (isset($i['data']['canFix']) && $i['data']['canFix']) {
625 $count++;
626 }
627 }
628 return $count;
629 }
630 public function getDeleteableIssueCount() {
631 global $wpdb;
632 $issues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE data LIKE '%s:9:\"canDelete\";b:1;%'");
633 $count = 0;
634 foreach ($issues as $i) {
635 $i['data'] = unserialize($i['data']);
636 if (isset($i['data']['canDelete']) && $i['data']['canDelete']) {
637 $count++;
638 }
639 }
640 return $count;
641 }
642 public function getIssueCount() {
643 return (int) $this->getDB()->querySingle("select COUNT(*) from " . $this->issuesTable . " WHERE status = 'new'");
644 }
645 public function getPendingIssueCount() {
646 return (int) $this->getDB()->querySingle("select COUNT(*) from " . $this->pendingIssuesTable . " WHERE status = 'new'");
647 }
648 public function getLastIssueUpdateTimestamp() {
649 return (int) $this->getDB()->querySingle("select MAX(lastUpdated) from " . $this->issuesTable);
650 }
651 public function reconcileUpgradeIssues($report = null, $useCachedValued = false) {
652 if ($report === null) {
653 $report = new wfActivityReport();
654 }
655
656 $updatesNeeded = $report->getUpdatesNeeded($useCachedValued);
657 if ($updatesNeeded) {
658 if (!$updatesNeeded['core']) {
659 $this->deleteUpdateIssues('wfUpgrade');
660 }
661
662 if ($updatesNeeded['plugins']) {
663 $upgradeNames = array();
664 foreach ($updatesNeeded['plugins'] as $p) {
665 $name = $p['Name'];
666 $upgradeNames[$name] = 1;
667 }
668 $upgradeIssues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE status = 'new' AND type = 'wfPluginUpgrade'");
669 foreach ($upgradeIssues as $issue) {
670 $data = unserialize($issue['data']);
671 $name = $data['Name'];
672 if (!isset($upgradeNames[$name])) { //Some plugins don't have a slug associated with them, so we anchor on the name
673 $this->deleteIssue($issue['id']);
674 }
675 }
676 }
677 else {
678 $this->deleteUpdateIssues('wfPluginUpgrade');
679 }
680
681 if ($updatesNeeded['themes']) {
682 $upgradeNames = array();
683 foreach ($updatesNeeded['themes'] as $t) {
684 $name = $t['Name'];
685 $upgradeNames[$name] = 1;
686 }
687 $upgradeIssues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE status = 'new' AND type = 'wfThemeUpgrade'");
688 foreach ($upgradeIssues as $issue) {
689 $data = unserialize($issue['data']);
690 $name = $data['Name'];
691 if (!isset($upgradeNames[$name])) { //Some themes don't have a slug associated with them, so we anchor on the name
692 $this->deleteIssue($issue['id']);
693 }
694 }
695 }
696 else {
697 $this->deleteUpdateIssues('wfThemeUpgrade');
698 }
699 }
700 else {
701 $this->deleteAllUpdateIssues();
702 }
703
704 wfScanEngine::refreshScanNotification($this);
705 }
706 private function getDB(){
707 if(! $this->db){
708 $this->db = new wfDB();
709 }
710 return $this->db;
711 }
712
713 /**
714 * @return string
715 */
716 public function getIssuesTable() {
717 return $this->issuesTable;
718 }
719 }
720