PluginProbe ʕ •ᴥ•ʔ
Wordfence Security – Firewall, Malware Scan, and Login Security / 7.2.4
Wordfence Security – Firewall, Malware Scan, and Login Security v7.2.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 8 years ago dashboard 7 years ago rest-api 7 years ago .htaccess 7 years ago Diff.php 14 years ago GeoLite2-Country.mmdb 7 years ago IPTraf.php 8 years ago IPTrafList.php 7 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 7 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 7 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 7 years ago menu_scanner.php 7 years ago menu_scanner_credentials.php 8 years ago menu_scanner_options.php 8 years ago menu_support.php 7 years ago menu_tools.php 7 years ago menu_tools_diagnostic.php 7 years ago menu_tools_importExport.php 7 years ago menu_tools_livetraffic.php 7 years ago menu_tools_twoFactor.php 8 years ago menu_tools_whois.php 8 years ago menu_wordfence_central.php 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 7 years ago wfActivityReport.php 7 years ago wfAdminNoticeQueue.php 8 years ago wfArray.php 7 years ago wfBrowscap.php 8 years ago wfBrowscapCache.php 7 years ago wfBulkCountries.php 7 years ago wfCache.php 9 years ago wfCentralAPI.php 7 years ago wfConfig.php 7 years ago wfCrawl.php 8 years ago wfCredentialsController.php 7 years ago wfCrypt.php 8 years ago wfDB.php 7 years ago wfDashboard.php 7 years ago wfDateLocalization.php 8 years ago wfDiagnostic.php 7 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 7 years ago wfJWT.php 7 years ago wfLockedOut.php 7 years ago wfLog.php 7 years ago wfMD5BloomFilter.php 8 years ago wfNotification.php 8 years ago wfOnboardingController.php 7 years ago wfPersistenceController.php 8 years ago wfRESTAPI.php 7 years ago wfScan.php 7 years ago wfScanEngine.php 7 years ago wfSchema.php 7 years ago wfStyle.php 8 years ago wfSupportController.php 7 years ago wfUnlockMsg.php 7 years ago wfUpdateCheck.php 8 years ago wfUtils.php 7 years ago wfVersionCheckController.php 8 years ago wfView.php 10 years ago wfViewResult.php 8 years ago wordfenceClass.php 7 years ago wordfenceConstants.php 7 years ago wordfenceHash.php 7 years ago wordfenceScanner.php 7 years ago wordfenceURLHoover.php 7 years ago
wfIssues.php
733 lines
1 <?php
2 require_once('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 'dnsChangeDNS' => wfIssues::SEVERITY_HIGH,
73 'easyPasswordWeak' => wfIssues::SEVERITY_HIGH,
74 'knownfile' => wfIssues::SEVERITY_HIGH,
75 'optionBadURL' => wfIssues::SEVERITY_HIGH,
76 'publiclyAccessible' => wfIssues::SEVERITY_HIGH,
77 'suspiciousAdminUsers' => wfIssues::SEVERITY_HIGH,
78 'wfPluginAbandoned' => wfIssues::SEVERITY_MEDIUM,
79 'wfPluginRemoved' => wfIssues::SEVERITY_CRITICAL,
80 'wfPluginUpgrade' => wfIssues::SEVERITY_MEDIUM,
81 'wfThemeUpgrade' => wfIssues::SEVERITY_MEDIUM,
82 'wfUpgrade' => wfIssues::SEVERITY_HIGH,
83 'wpscan_directoryList' => wfIssues::SEVERITY_HIGH,
84 'wpscan_fullPathDiscl' => wfIssues::SEVERITY_HIGH,
85 );
86
87 public static function validIssueTypes() {
88 return array('checkHowGetIPs', 'checkSpamIP', 'commentBadURL', 'configReadable', 'coreUnknown', 'database', 'diskSpace', 'wafStatus', 'dnsChange', 'easyPassword', 'file', 'geoipSupport', 'knownfile', 'optionBadURL', 'postBadTitle', 'postBadURL', 'publiclyAccessible', 'spamvertizeCheck', 'suspiciousAdminUsers', 'timelimit', 'wfPluginAbandoned', 'wfPluginRemoved', 'wfPluginUpgrade', 'wfPluginVulnerable', 'wfThemeUpgrade', 'wfUpgrade', 'wpscan_directoryList', 'wpscan_fullPathDiscl');
89 }
90
91 public static function statusPrep(){
92 wfConfig::set_ser('wfStatusStartMsgs', array());
93 wordfence::status(10, 'info', "SUM_PREP:Preparing a new scan.");
94 wfIssues::updateScanStillRunning();
95 }
96
97 public static function statusStart($message) {
98 $statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
99 $statusStartMsgs[] = $message;
100 wfConfig::set_ser('wfStatusStartMsgs', $statusStartMsgs);
101 wordfence::status(10, 'info', 'SUM_START:' . $message);
102 wfIssues::updateScanStillRunning();
103 return count($statusStartMsgs) - 1;
104 }
105
106 public static function statusEnd($index, $state) {
107 $statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
108 if ($state == self::STATUS_SKIPPED) {
109 wordfence::status(10, 'info', 'SUM_ENDSKIPPED:' . $statusStartMsgs[$index]);
110 }
111 else if ($state == self::STATUS_IGNORED) {
112 wordfence::status(10, 'info', 'SUM_ENDIGNORED:' . $statusStartMsgs[$index]);
113 }
114 else if ($state == self::STATUS_PROBLEM) {
115 wordfence::status(10, 'info', 'SUM_ENDBAD:' . $statusStartMsgs[$index]);
116 }
117 else if ($state == self::STATUS_SECURE) {
118 wordfence::status(10, 'info', 'SUM_ENDOK:' . $statusStartMsgs[$index]);
119 }
120 else if ($state == self::STATUS_FAILED) {
121 wordfence::status(10, 'info', 'SUM_ENDFAILED:' . $statusStartMsgs[$index]);
122 }
123 else if ($state == self::STATUS_SUCCESS) {
124 wordfence::status(10, 'info', 'SUM_ENDSUCCESS:' . $statusStartMsgs[$index]);
125 }
126 wfIssues::updateScanStillRunning();
127 $statusStartMsgs[$index] = '';
128 wfConfig::set_ser('wfStatusStartMsgs', $statusStartMsgs);
129 }
130
131 public static function statusEndErr() {
132 $statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
133 for ($i = 0; $i < count($statusStartMsgs); $i++) {
134 if (empty($statusStartMsgs[$i]) === false) {
135 wordfence::status(10, 'info', 'SUM_ENDERR:' . $statusStartMsgs[$i]);
136 $statusStartMsgs[$i] = '';
137 }
138 }
139 wfIssues::updateScanStillRunning();
140 }
141
142 public static function statusPaidOnly($message) {
143 wordfence::status(10, 'info', "SUM_PAIDONLY:" . $message);
144 wfIssues::updateScanStillRunning();
145 }
146
147 public static function statusDisabled($message) {
148 wordfence::status(10, 'info', "SUM_DISABLED:" . $message);
149 wfIssues::updateScanStillRunning();
150 }
151
152 public static function updateScanStillRunning($running = true) {
153 $timestamp = time();
154 if (!$running) {
155 $timestamp = 0;
156 }
157 wfConfig::set('wf_scanLastStatusTime', $timestamp);
158 }
159
160 /**
161 * Returns false if the scan has not been detected as failed. If it has, returns a constant corresponding to the reason.
162 *
163 * @return bool|string
164 */
165 public static function hasScanFailed() {
166 $lastStatusUpdate = self::lastScanStatusUpdate();
167 if ($lastStatusUpdate !== false && wfScanner::shared()->isRunning()) {
168 $threshold = WORDFENCE_SCAN_FAILURE_THRESHOLD;
169 if (time() - $lastStatusUpdate > $threshold) {
170 return self::SCAN_FAILED_TIMEOUT;
171 }
172 }
173
174 $scanStartAttempt = wfConfig::get('scanStartAttempt', 0);
175 if ($scanStartAttempt && time() - $scanStartAttempt > WORDFENCE_SCAN_START_FAILURE_THRESHOLD) {
176 return self::SCAN_FAILED_START_TIMEOUT;
177 }
178
179 $recordedFailure = wfConfig::get('lastScanFailureType');
180 switch ($recordedFailure) {
181 case self::SCAN_FAILED_GENERAL:
182 case self::SCAN_FAILED_DURATION_REACHED:
183 case self::SCAN_FAILED_VERSION_CHANGE:
184 case self::SCAN_FAILED_FORK_FAILED:
185 case self::SCAN_FAILED_CALLBACK_TEST_FAILED:
186 case self::SCAN_FAILED_API_SSL_UNAVAILABLE:
187 case self::SCAN_FAILED_API_CALL_FAILED:
188 case self::SCAN_FAILED_API_INVALID_RESPONSE:
189 case self::SCAN_FAILED_API_ERROR_RESPONSE:
190 return $recordedFailure;
191 }
192
193 return false;
194 }
195
196 /**
197 * Returns false if the scan has not been detected as timed out. If it has, it returns the timestamp of the last status update.
198 *
199 * @return bool|int
200 */
201 public static function lastScanStatusUpdate() {
202 if (wfConfig::get('wf_scanLastStatusTime', 0) === 0) {
203 return false;
204 }
205
206 $threshold = WORDFENCE_SCAN_FAILURE_THRESHOLD;
207 return (time() > wfConfig::get('wf_scanLastStatusTime', 0) + $threshold) ? wfConfig::get('wf_scanLastStatusTime', 0) : false;
208 }
209
210 /**
211 * Returns the singleton wfIssues.
212 *
213 * @return wfIssues
214 */
215 public static function shared() {
216 static $_issues = null;
217 if ($_issues === null) {
218 $_issues = new wfIssues();
219 }
220 return $_issues;
221 }
222
223 public function __sleep(){ //Same order here as vars above
224 return array('updateCalled', 'issuesTable', 'pendingIssuesTable', 'maxIssues', 'newIssues', 'totalIssues', 'totalIgnoredIssues', 'totalIssuesBySeverity');
225 }
226 public function __construct(){
227 $this->issuesTable = wfDB::networkTable('wfIssues');
228 $this->pendingIssuesTable = wfDB::networkTable('wfPendingIssues');
229 $this->maxIssues = wfConfig::get('scan_maxIssues', 0);
230 }
231 public function __wakeup(){
232 $this->db = new wfDB();
233 }
234
235 public function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed = false) {
236 return $this->_addIssue('issue', $type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed);
237 }
238 public function addPendingIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData) {
239 return $this->_addIssue('pending', $type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
240 }
241
242 /**
243 * Create a new issue
244 *
245 * @param string $group The issue type (e.g., issue or pending
246 * @param string $type
247 * @param int $severity
248 * @param string $ignoreP string to compare against for permanent ignores
249 * @param string $ignoreC string to compare against for ignoring until something changes
250 * @param string $shortMsg
251 * @param string $longMsg
252 * @param array $templateData
253 * @param bool $alreadyHashed If true, don't re-hash $ignoreP and $ignoreC
254 * @return string One of the ISSUE_ constants
255 */
256 private function _addIssue($group, $type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed = false) {
257 if ($group == 'pending') {
258 $table = $this->pendingIssuesTable;
259 }
260 else {
261 $table = $this->issuesTable;
262 }
263
264 if (!$alreadyHashed) {
265 $ignoreP = md5($ignoreP);
266 $ignoreC = md5($ignoreC);
267 }
268
269 $results = $this->getDB()->querySelect("SELECT id, status, ignoreP, ignoreC FROM {$table} WHERE (ignoreP = '%s' OR ignoreC = '%s')", $ignoreP, $ignoreC);
270 foreach ($results as $row) {
271 if ($row['status'] == 'new' && ($row['ignoreC'] == $ignoreC || $row['ignoreP'] == $ignoreP)) {
272 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
273 return self::ISSUE_DUPLICATE;
274 }
275 }
276
277 if ($row['status'] == 'ignoreP' && $row['ignoreP'] == $ignoreP) { $this->totalIgnoredIssues++; return self::ISSUE_IGNOREP; } //Always ignore
278 else if ($row['status'] == 'ignoreC' && $row['ignoreC'] == $ignoreC) { $this->totalIgnoredIssues++; return self::ISSUE_IGNOREC; } //Unchanged, ignore
279 else if ($row['status'] == 'ignoreC') {
280 $updateID = $row['id']; //Re-use the existing issue row
281 break;
282 }
283 }
284
285 if ($group != 'pending') {
286 if (!array_key_exists($severity, $this->totalIssuesBySeverity)) {
287 $this->totalIssuesBySeverity[$severity] = 0;
288 }
289 $this->totalIssuesBySeverity[$severity]++;
290 $this->totalIssues++;
291 if (empty($this->maxIssues) || $this->totalIssues <= $this->maxIssues)
292 {
293 $this->newIssues[] = array(
294 'type' => $type,
295 'severity' => $severity,
296 'ignoreP' => $ignoreP,
297 'ignoreC' => $ignoreC,
298 'shortMsg' => $shortMsg,
299 'longMsg' => $longMsg,
300 'tmplData' => $templateData
301 );
302 }
303 }
304
305 if (isset($updateID)) {
306 if ($group !== 'pending' && wfCentral::isConnected()) {
307 wfCentral::sendIssue(array(
308 'id' => $updateID,
309 'lastUpdated' => time(),
310 'type' => $type,
311 'severity' => $severity,
312 'ignoreP' => $ignoreP,
313 'ignoreC' => $ignoreC,
314 'shortMsg' => $shortMsg,
315 'longMsg' => $longMsg,
316 'data' => $templateData,
317 ));
318 }
319
320 $this->getDB()->queryWrite(
321 "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",
322 'new',
323 $type,
324 $severity,
325 $ignoreP,
326 $ignoreC,
327 $shortMsg,
328 $longMsg,
329 serialize($templateData),
330 $updateID);
331
332
333 return self::ISSUE_UPDATED;
334 }
335
336 $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')",
337 'new',
338 $type,
339 $severity,
340 $ignoreP,
341 $ignoreC,
342 $shortMsg,
343 $longMsg,
344 serialize($templateData));
345
346 if ($group !== 'pending' && wfCentral::isConnected()) {
347 global $wpdb;
348 wfCentral::sendIssue(array(
349 'id' => $wpdb->insert_id,
350 'status' => 'new',
351 'time' => time(),
352 'lastUpdated' => time(),
353 'type' => $type,
354 'severity' => $severity,
355 'ignoreP' => $ignoreP,
356 'ignoreC' => $ignoreC,
357 'shortMsg' => $shortMsg,
358 'longMsg' => $longMsg,
359 'data' => $templateData,
360 ));
361 }
362
363 return self::ISSUE_ADDED;
364 }
365 public function deleteIgnored(){
366 if (wfCentral::isConnected()) {
367 $result = $this->getDB()->querySelect("SELECT id from " . $this->issuesTable . " where status='ignoreP' or status='ignoreC'");
368 $issues = array();
369 foreach ($result as $row) {
370 $issues[] = $row['id'];
371 }
372 wfCentral::deleteIssues($issues);
373 }
374
375 $this->getDB()->queryWrite("delete from " . $this->issuesTable . " where status='ignoreP' or status='ignoreC'");
376 }
377 public function deleteNew($types = null) {
378 if (!is_array($types)) {
379 if (wfCentral::isConnected()) {
380 $result = $this->getDB()->querySelect("SELECT id FROM {$this->issuesTable} WHERE status = 'new'");
381 $issues = array();
382 foreach ($result as $row) {
383 $issues[] = $row['id'];
384 }
385 wfCentral::deleteIssues($issues);
386 }
387
388 $this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new'");
389 }
390 else {
391 if (wfCentral::isConnected()) {
392 $query = "SELECT id FROM {$this->issuesTable} WHERE status = 'new' AND type IN (" . implode(',', array_fill(0, count($types), "'%s'")) . ")";
393 array_unshift($types, $query);
394 $result = call_user_func_array(array($this->getDB(), 'querySelect'), $types);
395 $issues = array();
396 foreach ($result as $row) {
397 $issues[] = $row['id'];
398 }
399 wfCentral::deleteIssues($issues);
400 }
401
402 $query = "DELETE FROM {$this->issuesTable} WHERE status = 'new' AND type IN (" . implode(',', array_fill(0, count($types), "'%s'")) . ")";
403 array_unshift($types, $query);
404 call_user_func_array(array($this->getDB(), 'queryWrite'), $types);
405 }
406 }
407 public function ignoreAllNew(){
408 if (wfCentral::isConnected()) {
409 $issues = $this->getDB()->querySelect('SELECT * FROM ' . $this->issuesTable . ' WHERE status=\'new\'');
410 if ($issues) {
411 wfCentral::sendIssues($issues);
412 }
413 }
414
415 $this->getDB()->queryWrite("update " . $this->issuesTable . " set status='ignoreC' where status='new'");
416 }
417 public function emailNewIssues($timeLimitReached = false, $scanController = false){
418 $level = wfConfig::getAlertLevel();
419 $emails = wfConfig::getAlertEmails();
420 if (!count($emails)) {
421 return;
422 }
423
424 $shortSiteURL = preg_replace('/^https?:\/\//i', '', site_url());
425 $subject = "[Wordfence Alert] Problems found on $shortSiteURL";
426
427 if(sizeof($emails) < 1){ return; }
428 if($level < 1){ return; }
429 $needsToAlert = false;
430 foreach ($this->totalIssuesBySeverity as $issueSeverity => $totalIssuesBySeverity) {
431 if ($issueSeverity >= $level && $totalIssuesBySeverity > 0) {
432 $needsToAlert = true;
433 break;
434 }
435 }
436 if (!$needsToAlert) {
437 return;
438 }
439 $emailedIssues = wfConfig::get_ser('emailedIssuesList', array());
440 if(! is_array($emailedIssues)){
441 $emailedIssues = array();
442 }
443 $overflowCount = $this->totalIssues - count($this->newIssues);
444 $finalIssues = array();
445 foreach($this->newIssues as $newIssue){
446 $alreadyEmailed = false;
447 foreach($emailedIssues as $emailedIssue){
448 if($newIssue['ignoreP'] == $emailedIssue['ignoreP'] || $newIssue['ignoreC'] == $emailedIssue['ignoreC']){
449 $alreadyEmailed = true;
450 break;
451 }
452 }
453 if(! $alreadyEmailed){
454 $finalIssues[] = $newIssue;
455 }
456 else {
457 $overflowCount--;
458 }
459 }
460 if(sizeof($finalIssues) < 1){ return; }
461
462 $this->newIssues = array();
463 $this->totalIssues = 0;
464
465 $totals = array();
466 foreach($finalIssues as $i){
467 $emailedIssues[] = array( 'ignoreC' => $i['ignoreC'], 'ignoreP' => $i['ignoreP'] );
468 if (!array_key_exists($i['severity'], $totals)) {
469 $totals[$i['severity']] = 0;
470 }
471 $totals[$i['severity']]++;
472 }
473 wfConfig::set_ser('emailedIssuesList', $emailedIssues);
474 $needsToAlert = false;
475 foreach ($totals as $issueSeverity => $totalIssuesBySeverity) {
476 if ($issueSeverity >= $level && $totalIssuesBySeverity > 0) {
477 $needsToAlert = true;
478 break;
479 }
480 }
481 if (!$needsToAlert) {
482 return;
483 }
484
485 $content = wfUtils::tmpl('email_newIssues.php', array(
486 'isPaid' => wfConfig::get('isPaid'),
487 'issues' => $finalIssues,
488 'totals' => $totals,
489 'level' => $level,
490 'issuesNotShown' => $overflowCount,
491 'adminURL' => get_admin_url(),
492 'timeLimitReached' => $timeLimitReached,
493 'scanController' => ($scanController ? $scanController : wfScanner::shared()),
494 ));
495
496 foreach ($emails as $email) {
497 $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);
498 wp_mail($email, $subject, $uniqueContent, 'Content-type: text/html');
499 }
500 }
501 public function deleteIssue($id){
502 $this->getDB()->queryWrite("delete from " . $this->issuesTable . " where id=%d", $id);
503 if (wfCentral::isConnected()) {
504 wfCentral::deleteIssue($id);
505 }
506 }
507
508 public function deleteUpdateIssues($type)
509 {
510 $this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND type = '%s'", $type);
511
512 if (wfCentral::isConnected()) {
513 wfCentral::deleteIssueTypes(array($type));
514 }
515 }
516
517 public function deleteAllUpdateIssues()
518 {
519 $this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
520
521 if (wfCentral::isConnected()) {
522 wfCentral::deleteIssueTypes(array('wfUpgrade', 'wfPluginUpgrade', 'wfThemeUpgrade'));
523 }
524 }
525
526 public function updateIssue($id, $status){ //ignoreC, ignoreP, delete or new
527 if($status == 'delete'){
528 if (wfCentral::isConnected()) {
529 wfCentral::deleteIssue($id);
530 }
531 $this->getDB()->queryWrite("delete from " . $this->issuesTable . " where id=%d", $id);
532 } else if($status == 'ignoreC' || $status == 'ignoreP' || $status == 'new'){
533 $this->getDB()->queryWrite("update " . $this->issuesTable . " set status='%s' where id=%d", $status, $id);
534
535 if (wfCentral::isConnected()) {
536 $issue = $this->getDB()->querySelect('SELECT * FROM ' . $this->issuesTable . ' where id=%d', $id);
537 if ($issue) {
538 wfCentral::sendIssues($issue);
539 }
540 }
541 }
542 }
543 public function getIssueByID($id){
544 $rec = $this->getDB()->querySingleRec("select * from " . $this->issuesTable . " where id=%d", $id);
545 $rec['data'] = unserialize($rec['data']);
546 return $rec;
547 }
548 public function getIssueCounts() {
549 global $wpdb;
550 $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);
551 $result = array();
552 foreach ($counts as $row) {
553 $result[$row['status']] = $row['c'];
554 }
555 return $result;
556 }
557 public function getIssues($offset = 0, $limit = 100, $ignoredOffset = 0, $ignoredLimit = 100) {
558 /** @var wpdb $wpdb */
559 global $wpdb;
560
561 $siteCleaningTypes = array('file', 'checkGSB', 'checkSpamIP', 'commentBadURL', 'dnsChange', 'knownfile', 'optionBadURL', 'postBadTitle', 'postBadURL', 'spamvertizeCheck', 'suspiciousAdminUsers');
562 $sortTagging = 'CASE';
563 foreach ($siteCleaningTypes as $index => $t) {
564 $sortTagging .= ' WHEN type = \'' . esc_sql($t) . '\' THEN ' . ((int) $index);
565 }
566 $sortTagging .= ' ELSE 999 END';
567
568 $ret = array(
569 'new' => array(),
570 'ignored' => array()
571 );
572 $userIni = ini_get('user_ini.filename');
573 $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);
574 $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);
575 $q = array_merge($q1, $q2);
576 foreach($q as $i){
577 $i['data'] = unserialize($i['data']);
578 $i['timeAgo'] = wfUtils::makeTimeAgo(time() - $i['time']);
579 $i['displayTime'] = wfUtils::formatLocalTime(get_option('date_format') . ' ' . get_option('time_format'), $i['time']);
580 $i['longMsg'] = wp_kses($i['longMsg'], 'post');
581 if($i['status'] == 'new'){
582 $ret['new'][] = $i;
583 } else if($i['status'] == 'ignoreP' || $i['status'] == 'ignoreC'){
584 $ret['ignored'][] = $i;
585 } else {
586 error_log("Issue has bad status: " . $i['status']);
587 continue;
588 }
589 }
590 foreach($ret as $status => &$issueList){
591 for($i = 0; $i < sizeof($issueList); $i++){
592 if ($issueList[$i]['type'] == 'file' || $issueList[$i]['type'] == 'knownfile') {
593 $localFile = $issueList[$i]['data']['file'];
594 if ($localFile != '.htaccess' && $localFile != $userIni) {
595 $localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $localFile);
596 }
597 else {
598 $localFile = ABSPATH . '/' . $localFile;
599 }
600
601 if(file_exists($localFile)){
602 $issueList[$i]['data']['fileExists'] = true;
603 } else {
604 $issueList[$i]['data']['fileExists'] = '';
605 }
606 }
607 if ($issueList[$i]['type'] == 'database') {
608 $issueList[$i]['data']['optionExists'] = false;
609 if (!empty($issueList[$i]['data']['site_id'])) {
610 $table_options = wfDB::blogTable('options', $issueList[$i]['data']['site_id']);
611 $issueList[$i]['data']['optionExists'] = $wpdb->get_var($wpdb->prepare("SELECT count(*) FROM {$table_options} WHERE option_name = %s", $issueList[$i]['data']['option_name'])) > 0;
612 }
613 }
614 $issueList[$i]['issueIDX'] = $i;
615 if (isset($issueList[$i]['data']['cType'])) {
616 $issueList[$i]['data']['ucType'] = ucwords($issueList[$i]['data']['cType']);
617 }
618 }
619 }
620 return $ret; //array of lists of issues by status
621 }
622 public function getPendingIssues($offset = 0, $limit = 100){
623 /** @var wpdb $wpdb */
624 global $wpdb;
625 $issues = $this->getDB()->querySelect("SELECT * FROM {$this->pendingIssuesTable} ORDER BY id ASC LIMIT %d,%d", $offset, $limit);
626 foreach($issues as &$i){
627 $i['data'] = unserialize($i['data']);
628 }
629 return $issues;
630 }
631 public function getFixableIssueCount() {
632 global $wpdb;
633 $issues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE data LIKE '%s:6:\"canFix\";b:1;%'");
634 $count = 0;
635 foreach ($issues as $i) {
636 $i['data'] = unserialize($i['data']);
637 if (isset($i['data']['canFix']) && $i['data']['canFix']) {
638 $count++;
639 }
640 }
641 return $count;
642 }
643 public function getDeleteableIssueCount() {
644 global $wpdb;
645 $issues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE data LIKE '%s:9:\"canDelete\";b:1;%'");
646 $count = 0;
647 foreach ($issues as $i) {
648 $i['data'] = unserialize($i['data']);
649 if (isset($i['data']['canDelete']) && $i['data']['canDelete']) {
650 $count++;
651 }
652 }
653 return $count;
654 }
655 public function getIssueCount() {
656 return (int) $this->getDB()->querySingle("select COUNT(*) from " . $this->issuesTable . " WHERE status = 'new'");
657 }
658 public function getPendingIssueCount() {
659 return (int) $this->getDB()->querySingle("select COUNT(*) from " . $this->pendingIssuesTable . " WHERE status = 'new'");
660 }
661 public function getLastIssueUpdateTimestamp() {
662 return (int) $this->getDB()->querySingle("select MAX(lastUpdated) from " . $this->issuesTable);
663 }
664 public function reconcileUpgradeIssues($report = null, $useCachedValued = false) {
665 if ($report === null) {
666 $report = new wfActivityReport();
667 }
668
669 $updatesNeeded = $report->getUpdatesNeeded($useCachedValued);
670 if ($updatesNeeded) {
671 if (!$updatesNeeded['core']) {
672 $this->deleteUpdateIssues('wfUpgrade');
673 }
674
675 if ($updatesNeeded['plugins']) {
676 $upgradeNames = array();
677 foreach ($updatesNeeded['plugins'] as $p) {
678 $name = $p['Name'];
679 $upgradeNames[$name] = 1;
680 }
681 $upgradeIssues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE status = 'new' AND type = 'wfPluginUpgrade'");
682 foreach ($upgradeIssues as $issue) {
683 $data = unserialize($issue['data']);
684 $name = $data['Name'];
685 if (!isset($upgradeNames[$name])) { //Some plugins don't have a slug associated with them, so we anchor on the name
686 $this->deleteIssue($issue['id']);
687 }
688 }
689 }
690 else {
691 $this->deleteUpdateIssues('wfPluginUpgrade');
692 }
693
694 if ($updatesNeeded['themes']) {
695 $upgradeNames = array();
696 foreach ($updatesNeeded['themes'] as $t) {
697 $name = $t['Name'];
698 $upgradeNames[$name] = 1;
699 }
700 $upgradeIssues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE status = 'new' AND type = 'wfThemeUpgrade'");
701 foreach ($upgradeIssues as $issue) {
702 $data = unserialize($issue['data']);
703 $name = $data['Name'];
704 if (!isset($upgradeNames[$name])) { //Some themes don't have a slug associated with them, so we anchor on the name
705 $this->deleteIssue($issue['id']);
706 }
707 }
708 }
709 else {
710 $this->deleteUpdateIssues('wfThemeUpgrade');
711 }
712 }
713 else {
714 $this->deleteAllUpdateIssues();
715 }
716
717 wfScanEngine::refreshScanNotification($this);
718 }
719 private function getDB(){
720 if(! $this->db){
721 $this->db = new wfDB();
722 }
723 return $this->db;
724 }
725
726 /**
727 * @return string
728 */
729 public function getIssuesTable() {
730 return $this->issuesTable;
731 }
732 }
733