PluginProbe ʕ •ᴥ•ʔ
Wordfence Security – Firewall, Malware Scan, and Login Security / 8.0.5
Wordfence Security – Firewall, Malware Scan, and Login Security v8.0.5
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 / wfLog.php
wordfence / lib Last commit date
Diff 1 year ago audit-log 1 year ago dashboard 1 year ago rest-api 1 year ago .htaccess 7 years ago Diff.php 6 years ago GeoLite2-Country.mmdb 1 year ago IPTraf.php 1 year ago IPTrafList.php 1 year ago WFLSPHP52Compatability.php 6 years ago compat.php 8 years ago diffResult.php 1 year ago email_genericAlert.php 5 years ago email_newIssues.php 3 years ago email_unlockRequest.php 5 years ago email_unsubscribeRequest.php 4 years ago flags.php 7 years ago live_activity.php 4 years ago menu_dashboard.php 3 years ago menu_dashboard_options.php 3 years ago menu_firewall.php 3 years ago menu_firewall_blocking.php 4 years ago menu_firewall_blocking_options.php 3 years ago menu_firewall_waf.php 4 years ago menu_firewall_waf_options.php 3 years ago menu_install.php 3 years ago menu_options.php 1 year ago menu_scanner.php 2 years ago menu_scanner_credentials.php 1 year ago menu_scanner_options.php 3 years ago menu_support.php 1 year ago menu_tools.php 1 year ago menu_tools_auditlog.php 1 year ago menu_tools_diagnostic.php 1 year ago menu_tools_importExport.php 4 years ago menu_tools_livetraffic.php 2 years ago menu_tools_twoFactor.php 4 years ago menu_tools_whois.php 3 years ago menu_wordfence_central.php 3 years ago noc1.key 7 years ago sodium_compat_fast.php 2 years ago sysinfo.php 1 year ago viewFullActivityLog.php 4 years ago wf503.php 2 years ago wfAPI.php 1 year ago wfActivityReport.php 1 year ago wfAdminNoticeQueue.php 4 years ago wfAlerts.php 5 years ago wfArray.php 3 years ago wfAuditLog.php 1 year ago wfBrowscap.php 3 years ago wfBrowscapCache.php 7 years ago wfBulkCountries.php 2 years ago wfCache.php 3 years ago wfCentralAPI.php 1 year ago wfConfig.php 1 year ago wfCrawl.php 1 year ago wfCredentialsController.php 1 year ago wfCrypt.php 6 years ago wfCurlInterceptor.php 3 years ago wfDB.php 1 year ago wfDashboard.php 1 year ago wfDateLocalization.php 2 years ago wfDeactivationOption.php 3 years ago wfDiagnostic.php 1 year ago wfDict.php 8 years ago wfDirectoryIterator.php 7 years ago wfFileUtils.php 2 years ago wfHelperBin.php 11 years ago wfHelperString.php 1 year ago wfIPWhitelist.php 5 years ago wfImportExportController.php 5 years ago wfInaccessibleDirectoryException.php 2 years ago wfInvalidPathException.php 3 years ago wfIpLocation.php 3 years ago wfIpLocator.php 3 years ago wfIssues.php 1 year ago wfJWT.php 7 years ago wfLicense.php 3 years ago wfLockedOut.php 2 years ago wfLog.php 1 year ago wfMD5BloomFilter.php 8 years ago wfModuleController.php 7 years ago wfNotification.php 8 years ago wfOnboardingController.php 1 year ago wfPersistenceController.php 1 year ago wfRESTAPI.php 7 years ago wfScan.php 2 years ago wfScanEngine.php 1 year ago wfScanEntrypoint.php 3 years ago wfScanFile.php 1 year ago wfScanFileLink.php 3 years ago wfScanFileListItem.php 1 year ago wfScanFileProperties.php 1 year ago wfScanMonitor.php 2 years ago wfScanPath.php 3 years ago wfSchema.php 1 year ago wfStyle.php 1 year ago wfSupportController.php 1 year ago wfUnlockMsg.php 5 years ago wfUpdateCheck.php 1 year ago wfUtils.php 1 year ago wfVersionCheckController.php 3 years ago wfVersionSupport.php 1 year ago wfView.php 5 years ago wfViewResult.php 1 year ago wfWebsite.php 3 years ago wordfenceClass.php 1 year ago wordfenceConstants.php 1 year ago wordfenceHash.php 1 year ago wordfenceScanner.php 1 year ago wordfenceURLHoover.php 2 years ago
wfLog.php
2130 lines
1 <?php
2 require_once(dirname(__FILE__) . '/wfDB.php');
3 require_once(dirname(__FILE__) . '/wfUtils.php');
4 require_once(dirname(__FILE__) . '/wfBrowscap.php');
5 class wfLog {
6 public $canLogHit = true;
7 private $effectiveUserID = 0;
8 private $hitsTable = '';
9 private $apiKey = '';
10 private $wp_version = '';
11 private $db = false;
12 private $googlePattern = '/\.(?:googlebot\.com|google\.[a-z]{2,3}|google\.[a-z]{2}\.[a-z]{2}|1e100\.net)$/i';
13 private $loginsTable, $statusTable;
14 private static $gbSafeCache = array();
15
16 /**
17 * @var wfRequestModel
18 */
19 private $currentRequest;
20
21 public static function shared() {
22 static $_shared = null;
23 if ($_shared === null) {
24 $_shared = new wfLog(wfConfig::get('apiKey'), wfUtils::getWPVersion());
25 }
26 return $_shared;
27 }
28
29 /**
30 * Returns whether or not we have a cached record identifying the visitor as human. This is used both by certain
31 * rate limiting features and by Live Traffic.
32 *
33 * @param bool|string $IP
34 * @param bool|string $UA
35 * @return bool
36 */
37 public static function isHumanRequest($IP = false, $UA = false) {
38 global $wpdb;
39
40 if ($IP === false) {
41 $IP = wfUtils::getIP();
42 }
43
44 if ($UA === false || $UA === null) {
45 $UA = (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
46 }
47
48 $ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($IP));
49 $table = wfDB::networkTable('wfLiveTrafficHuman');
50 if ($wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE IP = {$ipHex} AND identifier = %s AND expiration >= UNIX_TIMESTAMP()", hash('sha256', $UA, true)))) {
51 return true;
52 }
53 return false;
54 }
55
56 /**
57 * Creates a cache record for the requester to tag it as human.
58 *
59 * @param bool|string $IP
60 * @param bool|string $UA
61 * @return bool
62 */
63 public static function cacheHumanRequester($IP = false, $UA = false) {
64 global $wpdb;
65
66 if ($IP === false) {
67 $IP = wfUtils::getIP();
68 }
69
70 if ($UA === false) {
71 $UA = (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
72 }
73
74 $ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($IP));
75 $table = wfDB::networkTable('wfLiveTrafficHuman');
76 if ($wpdb->get_var($wpdb->prepare("INSERT IGNORE INTO {$table} (IP, identifier, expiration) VALUES ({$ipHex}, %s, UNIX_TIMESTAMP() + 86400)", hash('sha256', $UA, true)))) {
77 return true;
78 }
79 }
80
81 /**
82 * Prunes any expired records from the human cache.
83 */
84 public static function trimHumanCache() {
85 global $wpdb;
86 $table = wfDB::networkTable('wfLiveTrafficHuman');
87 $wpdb->query("DELETE FROM {$table} WHERE `expiration` < UNIX_TIMESTAMP()");
88 }
89
90 public function __construct($apiKey, $wp_version){
91 $this->apiKey = $apiKey;
92 $this->wp_version = $wp_version;
93 $this->hitsTable = wfDB::networkTable('wfHits');
94 $this->loginsTable = wfDB::networkTable('wfLogins');
95 $this->statusTable = wfDB::networkTable('wfStatus');
96
97 add_filter('determine_current_user', array($this, '_userIDDetermined'), 99, 1);
98 }
99
100 public function _userIDDetermined($userID) {
101 //Needed because the REST API will clear the authenticated user if it fails a nonce check on the request
102 $this->effectiveUserID = (int) $userID;
103 return $userID;
104 }
105
106 public function initLogRequest() {
107 if ($this->currentRequest === null) {
108 $this->currentRequest = new wfRequestModel();
109
110 $this->currentRequest->ctime = sprintf('%.6f', microtime(true));
111 $this->currentRequest->statusCode = 200;
112 $this->currentRequest->isGoogle = (wfCrawl::isGoogleCrawler() ? 1 : 0);
113 $this->currentRequest->IP = wfUtils::inet_pton(wfUtils::getIP());
114 $this->currentRequest->userID = $this->getCurrentUserID();
115 $this->currentRequest->URL = wfUtils::getRequestedURL();
116 $this->currentRequest->referer = (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '');
117 $this->currentRequest->UA = (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
118 $this->currentRequest->jsRun = 0;
119
120 add_action('wp_loaded', array($this, 'actionSetRequestJSEnabled'));
121 add_action('init', array($this, 'actionSetRequestOnInit'), 9999);
122
123 if (function_exists('register_shutdown_function')) {
124 register_shutdown_function(array($this, 'logHit'));
125 }
126 }
127 }
128
129 public function actionSetRequestJSEnabled() {
130 if (get_current_user_id() > 0) {
131 $this->currentRequest->jsRun = true;
132 return;
133 }
134
135 $IP = wfUtils::getIP();
136 $UA = $this->currentRequest->UA;
137 $this->currentRequest->jsRun = wfLog::isHumanRequest($IP, $UA);
138 }
139
140 /**
141 * CloudFlare's plugin changes $_SERVER['REMOTE_ADDR'] on init.
142 */
143 public function actionSetRequestOnInit() {
144 $this->currentRequest->IP = wfUtils::inet_pton(wfUtils::getIP());
145 $this->currentRequest->userID = $this->getCurrentUserID();
146 }
147
148 /**
149 * @return wfRequestModel
150 */
151 public function getCurrentRequest() {
152 return $this->currentRequest;
153 }
154
155 public function logLogin($action, $fail, $username){
156 if(! $username){
157 return;
158 }
159 $user = get_user_by('login', $username);
160 $userID = 0;
161 if($user){
162 $userID = $user->ID;
163 if(! $userID){
164 return;
165 }
166 }
167 else {
168 $user = get_user_by('email', $username);
169 if ($user) {
170 $userID = $user->ID;
171 if (!$userID) {
172 return;
173 }
174 }
175 }
176 // change the action flag here if the user does not exist.
177 if ($action == 'loginFailValidUsername' && $userID == 0) {
178 $action = 'loginFailInvalidUsername';
179 }
180
181 $hitID = 0;
182 if ($this->currentRequest !== null) {
183 $this->currentRequest->userID = $userID;
184 $this->currentRequest->action = $action;
185 $this->currentRequest->save();
186 $hitID = $this->currentRequest->getPrimaryKey();
187 }
188
189 //Else userID stays 0 but we do log this even though the user doesn't exist.
190 $ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton(wfUtils::getIP()));
191 $this->getDB()->queryWrite("insert into " . $this->loginsTable . " (hitID, ctime, fail, action, username, userID, IP, UA) values (%d, %f, %d, '%s', '%s', %s, {$ipHex}, '%s')",
192 $hitID,
193 sprintf('%.6f', microtime(true)),
194 $fail,
195 $action,
196 $username,
197 $userID,
198 (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '')
199 );
200 }
201 private function getCurrentUserID(){
202 if (!function_exists('get_current_user_id') || !defined('AUTH_COOKIE')) { //If pluggable.php is loaded early by some other plugin on a multisite installation, it leads to an error because AUTH_COOKIE is undefined and WP doesn't check for it first
203 return 0;
204 }
205 $id = get_current_user_id();
206 return $id ? $id : 0;
207 }
208 public function logLeechAndBlock($type) { //404 or hit
209 if (!wfRateLimit::mightRateLimit($type)) {
210 return;
211 }
212
213 wfRateLimit::countHit($type, wfUtils::getIP());
214
215 if (wfRateLimit::globalRateLimit()->shouldEnforce($type)) {
216 $this->takeBlockingAction('maxGlobalRequests', __("Exceeded the maximum global requests per minute for crawlers or humans.", 'wordfence'));
217 }
218 else if (wfRateLimit::crawlerViewsRateLimit()->shouldEnforce($type)) {
219 $this->takeBlockingAction('maxRequestsCrawlers', __("Exceeded the maximum number of requests per minute for crawlers.", 'wordfence')); //may not exit
220 }
221 else if (wfRateLimit::crawler404sRateLimit()->shouldEnforce($type)) {
222 $this->takeBlockingAction('max404Crawlers', __("Exceeded the maximum number of page not found errors per minute for a crawler.", 'wordfence'));
223 }
224 else if (wfRateLimit::humanViewsRateLimit()->shouldEnforce($type)) {
225 $this->takeBlockingAction('maxRequestsHumans', __("Exceeded the maximum number of page requests per minute for humans.", 'wordfence'));
226 }
227 else if (wfRateLimit::human404sRateLimit()->shouldEnforce($type)) {
228 $this->takeBlockingAction('max404Humans', __("Exceeded the maximum number of page not found errors per minute for humans.", 'wordfence'));
229 }
230 }
231
232 public function tagRequestForBlock($reason, $wfsn = false) {
233 if ($this->currentRequest !== null) {
234 $this->currentRequest->statusCode = 403;
235 $this->currentRequest->action = 'blocked:' . ($wfsn ? 'wfsn' : 'wordfence');
236 $this->currentRequest->actionDescription = $reason;
237 }
238 }
239
240 public function tagRequestForLockout($reason) {
241 if ($this->currentRequest !== null) {
242 $this->currentRequest->statusCode = 503;
243 $this->currentRequest->action = 'lockedOut';
244 $this->currentRequest->actionDescription = $reason;
245 }
246 }
247
248 /**
249 * @return bool|int
250 */
251 public function logHit() {
252 $liveTrafficEnabled = wfConfig::liveTrafficEnabled();
253 $action = $this->currentRequest->action;
254 $logHitOK = $this->logHitOK();
255 if (!$logHitOK) {
256 return false;
257 }
258 if (!$liveTrafficEnabled && !$action) {
259 return false;
260 }
261 if ($this->currentRequest !== null) {
262 if ($this->currentRequest->save()) {
263 return $this->currentRequest->getPrimaryKey();
264 }
265 }
266 return false;
267 }
268
269 public function getHits($hitType /* 'hits' or 'logins' */, $type, $afterTime, $limit = 50, $IP = false){
270 global $wpdb;
271 $IPSQL = "";
272 if($IP){
273 $ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($IP));
274 $IPSQL = " and IP={$ipHex} ";
275 $sqlArgs = array($afterTime, $limit);
276 } else {
277 $sqlArgs = array($afterTime, $limit);
278 }
279 if($hitType == 'hits'){
280 $securityOnly = !wfConfig::liveTrafficEnabled();
281 $delayedHumanBotFiltering = false;
282
283 if($type == 'hit'){
284 $typeSQL = " ";
285 } else if($type == 'crawler'){
286 if ($securityOnly) {
287 $typeSQL = " ";
288 $delayedHumanBotFiltering = true;
289 }
290 else {
291 $now = time();
292 $typeSQL = " and jsRun = 0 and {$now} - ctime > 30 ";
293 }
294 } else if($type == 'gCrawler'){
295 $typeSQL = " and isGoogle = 1 ";
296 } else if($type == '404'){
297 $typeSQL = " and statusCode = 404 ";
298 } else if($type == 'human'){
299 if ($securityOnly) {
300 $typeSQL = " ";
301 $delayedHumanBotFiltering = true;
302 }
303 else {
304 $typeSQL = " and jsRun = 1 ";
305 }
306 } else if($type == 'ruser'){
307 $typeSQL = " and userID > 0 ";
308 } else {
309 wordfence::status(1, 'error', sprintf(/* translators: Error message. */ __("Invalid log type to wfLog: %s", 'wordfence'), $type));
310 return false;
311 }
312 array_unshift($sqlArgs, "select h.*, u.display_name from {$this->hitsTable} h
313 LEFT JOIN {$wpdb->users} u on h.userID = u.ID
314 where ctime > %f $IPSQL $typeSQL order by ctime desc limit %d");
315 $results = call_user_func_array(array($this->getDB(), 'querySelect'), $sqlArgs);
316
317 if ($delayedHumanBotFiltering) {
318 $browscap = wfBrowscap::shared();
319 foreach ($results as $index => $res) {
320 if ($res['UA']) {
321 $b = $browscap->getBrowser($res['UA']);
322 if ($b && $b['Parent'] != 'DefaultProperties') {
323 $jsRun = wfUtils::truthyToBoolean($res['jsRun']);
324 if (!wfConfig::liveTrafficEnabled() && !$jsRun) {
325 $jsRun = !(isset($b['Crawler']) && $b['Crawler']);
326 }
327
328 if ($type == 'crawler' && $jsRun || $type == 'human' && !$jsRun) {
329 unset($results[$index]);
330 }
331 }
332 }
333 }
334 }
335
336 } else if($hitType == 'logins'){
337 array_unshift($sqlArgs, "select l.*, u.display_name from {$this->loginsTable} l
338 LEFT JOIN {$wpdb->users} u on l.userID = u.ID
339 where ctime > %f $IPSQL order by ctime desc limit %d");
340 $results = call_user_func_array(array($this->getDB(), 'querySelect'), $sqlArgs );
341
342 } else {
343 wordfence::status(1, 'error', sprintf(/* translators: Error message. */ __("getHits got invalid hitType: %s", 'wordfence'), $hitType));
344 return false;
345 }
346 $this->processGetHitsResults($type, $results);
347 return $results;
348 }
349
350 private function processActionDescription($description) {
351 switch ($description) {
352 case wfWAFIPBlocksController::WFWAF_BLOCK_UAREFIPRANGE:
353 return __('UA/Hostname/Referrer/IP Range not allowed', 'wordfence');
354 default:
355 return $description;
356 }
357 }
358
359 /**
360 * @param string $type
361 * @param array $results
362 * @throws Exception
363 */
364 public function processGetHitsResults($type, &$results) {
365 $serverTime = $this->getDB()->querySingle("select unix_timestamp()");
366
367 $this->resolveIPs($results);
368 $ourURL = parse_url(site_url());
369 $ourHost = strtolower($ourURL['host']);
370 $ourHost = preg_replace('/^www\./i', '', $ourHost);
371 $browscap = wfBrowscap::shared();
372
373 $patternBlocks = wfBlock::patternBlocks(true);
374
375 foreach($results as &$res){
376 $res['type'] = $type;
377 $res['IP'] = wfUtils::inet_ntop($res['IP']);
378 $res['timeAgo'] = wfUtils::makeTimeAgo($serverTime - $res['ctime']);
379 $res['blocked'] = false;
380 $res['rangeBlocked'] = false;
381 $res['ipRangeID'] = -1;
382 if (array_key_exists('actionDescription', $res))
383 $res['actionDescription'] = $this->processActionDescription($res['actionDescription']);
384
385 $ipBlock = wfBlock::findIPBlock($res['IP']);
386 if ($ipBlock !== false) {
387 $res['blocked'] = true;
388 $res['blockID'] = $ipBlock->id;
389 }
390
391 foreach ($patternBlocks as $b) {
392 if (empty($b->ipRange)) { continue; }
393 $range = new wfUserIPRange($b->ipRange);
394 if ($range->isIPInRange($res['IP'])) {
395 $res['rangeBlocked'] = true;
396 $res['ipRangeID'] = $b->id;
397 break;
398 }
399 }
400
401 $res['extReferer'] = false;
402 if(isset( $res['referer'] ) && $res['referer']){
403 if(wfUtils::hasXSS($res['referer'] )){ //filtering out XSS
404 $res['referer'] = '';
405 }
406 }
407 if( isset( $res['referer'] ) && $res['referer']){
408 $refURL = parse_url($res['referer']);
409 if(is_array($refURL) && isset($refURL['host']) && $refURL['host']){
410 $refHost = strtolower(preg_replace('/^www\./i', '', $refURL['host']));
411 if($refHost != $ourHost){
412 $res['extReferer'] = true;
413 //now extract search terms
414 $q = false;
415 if(preg_match('/(?:google|bing|alltheweb|aol|ask)\./i', $refURL['host'])){
416 $q = 'q';
417 } else if(stristr($refURL['host'], 'yahoo.')){
418 $q = 'p';
419 } else if(stristr($refURL['host'], 'baidu.')){
420 $q = 'wd';
421 }
422 if($q){
423 $queryVars = array();
424 if( isset( $refURL['query'] ) ) {
425 parse_str($refURL['query'], $queryVars);
426 if(isset($queryVars[$q])){
427 $res['searchTerms'] = urlencode($queryVars[$q]);
428 }
429 }
430 }
431 }
432 }
433 if($res['extReferer']){
434 if ( isset( $referringPage ) && stristr( $referringPage['host'], 'google.' ) )
435 {
436 parse_str( $referringPage['query'], $queryVars );
437 // echo $queryVars['q']; // This is the search term used
438 }
439 }
440 }
441 $res['browser'] = false;
442 if($res['UA']){
443 $b = $browscap->getBrowser($res['UA']);
444 if($b && $b['Parent'] != 'DefaultProperties'){
445 $res['browser'] = array(
446 'browser' => !empty($b['Browser']) ? $b['Browser'] : "",
447 'version' => !empty($b['Version']) ? $b['Version'] : "",
448 'platform' => !empty($b['Platform']) ? $b['Platform'] : "",
449 'isMobile' => !empty($b['isMobileDevice']) ? $b['isMobileDevice'] : "",
450 'isCrawler' => !empty($b['Crawler']) ? $b['Crawler'] : "",
451 );
452
453 if (isset($res['jsRun']) && !wfConfig::liveTrafficEnabled() && !wfUtils::truthyToBoolean($res['jsRun'])) {
454 $res['jsRun'] = !(isset($b['Crawler']) && $b['Crawler']) ? '1' : '0';
455 }
456 }
457 else {
458 $IP = wfUtils::getIP();
459 $res['browser'] = array(
460 'isCrawler' => !wfLog::isHumanRequest($IP, $res['UA']) ? 'true' : ''
461 );
462 }
463 }
464
465
466 if($res['userID']){
467 $ud = get_userdata($res['userID']);
468 if($ud){
469 $res['user'] = array(
470 'editLink' => wfUtils::editUserLink($res['userID']),
471 'display_name' => $res['display_name'],
472 'ID' => $res['userID']
473 );
474 }
475 } else {
476 $res['user'] = false;
477 }
478 }
479 }
480
481 public function resolveIPs(&$results){
482 if(sizeof($results) < 1){ return; }
483 $IPs = array();
484 foreach($results as &$res){
485 if($res['IP']){ //Can also be zero in case of non IP events
486 $IPs[] = $res['IP'];
487 }
488 }
489 $IPLocs = wfUtils::getIPsGeo($IPs); //Creates an array with IP as key and data as value
490
491 foreach($results as &$res){
492 $ip_printable = wfUtils::inet_ntop($res['IP']);
493 if(isset($IPLocs[$ip_printable])){
494 $res['loc'] = $IPLocs[$ip_printable];
495 } else {
496 $res['loc'] = false;
497 }
498 }
499 }
500 public function logHitOK(){
501 if (!$this->canLogHit) {
502 return false;
503 }
504 if (is_admin()) { return false; } //Don't log admin pageviews
505 if (isset($_SERVER['HTTP_USER_AGENT'])) {
506 if (preg_match('/WordPress\/' . $this->wp_version . '/i', $_SERVER['HTTP_USER_AGENT'])) { return false; } //Ignore regular requests generated by WP UA.
507 }
508 $userID = get_current_user_id();
509 if (!$userID) {
510 $userID = $this->effectiveUserID;
511 }
512 if ($userID) {
513 $user = new WP_User($userID);
514 if ($user && $user->exists()) {
515 if (wfConfig::get('liveTraf_ignorePublishers') && ($user->has_cap('publish_posts') || $user->has_cap('publish_pages'))) {
516 return false;
517 }
518
519 if (wfConfig::get('liveTraf_ignoreUsers')) {
520 $ignored = explode(',', wfConfig::get('liveTraf_ignoreUsers'));
521 foreach ($ignored as $entry) {
522 if($user->user_login == $entry){
523 return false;
524 }
525 }
526 }
527 }
528 }
529 if(wfConfig::get('liveTraf_ignoreIPs')){
530 $IPs = explode(',', wfConfig::get('liveTraf_ignoreIPs'));
531 $IP = wfUtils::getIP();
532 foreach($IPs as $ignoreIP){
533 if($ignoreIP == $IP){
534 return false;
535 }
536 }
537 }
538 if( isset($_SERVER['HTTP_USER_AGENT']) && wfConfig::get('liveTraf_ignoreUA') ){
539 if($_SERVER['HTTP_USER_AGENT'] == wfConfig::get('liveTraf_ignoreUA')){
540 return false;
541 }
542 }
543
544 return true;
545 }
546 private function getDB(){
547 if(! $this->db){
548 $this->db = new wfDB();
549 }
550 return $this->db;
551 }
552 public function firewallBadIPs() {
553 $IP = wfUtils::getIP();
554 if (wfBlock::isWhitelisted($IP)) {
555 return;
556 }
557
558 //Range and UA pattern blocking
559 $patternBlocks = wfBlock::patternBlocks(true);
560 $userAgent = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
561 $referrer = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
562 foreach ($patternBlocks as $b) {
563 if ($b->matchRequest($IP, $userAgent, $referrer) !== wfBlock::MATCH_NONE) {
564 $b->recordBlock();
565 wfActivityReport::logBlockedIP($IP, null, 'advanced');
566 $this->currentRequest->actionDescription = @__('UA/Referrer/IP Range not allowed', 'wordfence');
567 $this->do503(3600, @__("Advanced blocking in effect.", 'wordfence')); //exits
568 }
569 }
570
571 // Country blocking
572 $countryBlocks = wfBlock::countryBlocks(true);
573 foreach ($countryBlocks as $b) {
574 $match = $b->matchRequest($IP, false, false);
575 if ($match === wfBlock::MATCH_COUNTRY_REDIR_BYPASS) {
576 $bypassRedirDest = wfConfig::get('cbl_bypassRedirDest', '');
577
578 $this->initLogRequest();
579 $this->getCurrentRequest()->actionDescription = @__('redirected to bypass URL', 'wordfence');
580 $this->getCurrentRequest()->statusCode = 302;
581 $this->currentRequest->action = 'cbl:redirect';
582 $this->logHit();
583
584 wfUtils::doNotCache();
585 wp_redirect($bypassRedirDest, 302);
586 exit();
587 }
588 else if ($match === wfBlock::MATCH_COUNTRY_REDIR) {
589 $b->recordBlock();
590 wfConfig::inc('totalCountryBlocked');
591
592 $this->initLogRequest();
593 $this->getCurrentRequest()->actionDescription = sprintf(/* translators: URL */ @__('blocked access via country blocking and redirected to URL (%s)', 'wordfence'), wfConfig::get('cbl_redirURL'));
594 $this->getCurrentRequest()->statusCode = 503;
595 if (!$this->getCurrentRequest()->action) {
596 $this->currentRequest->action = 'blocked:wordfence';
597 }
598 $this->logHit();
599
600 wfActivityReport::logBlockedIP($IP, null, 'country');
601
602 wfUtils::doNotCache();
603 wp_redirect(wfConfig::get('cbl_redirURL'), 302);
604 exit();
605 }
606 else if ($match !== wfBlock::MATCH_NONE) {
607 $b->recordBlock();
608 $this->currentRequest->actionDescription = @__('blocked access via country blocking', 'wordfence');
609 wfConfig::inc('totalCountryBlocked');
610 wfActivityReport::logBlockedIP($IP, null, 'country');
611 $this->do503(3600, @__('Access from your area has been temporarily limited for security reasons', 'wordfence'));
612 }
613 }
614
615 //Specific IP blocks
616 $ipBlock = wfBlock::findIPBlock($IP);
617 if ($ipBlock !== false) {
618 $ipBlock->recordBlock();
619 $secsToGo = max(0, $ipBlock->expiration - time());
620 if (wfConfig::get('other_WFNet') && self::isAuthRequest()) { //It's an auth request and this IP has been blocked
621 $this->getCurrentRequest()->action = 'blocked:wfsnrepeat';
622 wordfence::wfsnReportBlockedAttempt($IP, 'login');
623 }
624 $reason = $ipBlock->reason;
625 if ($ipBlock->type == wfBlock::TYPE_IP_MANUAL || $ipBlock->type == wfBlock::TYPE_IP_AUTOMATIC_PERMANENT) {
626 $reason = @__('Manual block by administrator', 'wordfence');
627 }
628 $this->do503($secsToGo, $reason); //exits
629 }
630 }
631
632 private function takeBlockingAction($configVar, $reason) {
633 if ($this->googleSafetyCheckOK()) {
634 $action = wfConfig::get($configVar . '_action');
635 if (!$action) {
636 return;
637 }
638
639 $IP = wfUtils::getIP();
640 $secsToGo = 0;
641 if ($action == 'block') { //Rate limited - block temporarily
642 $secsToGo = wfBlock::blockDuration();
643 wfBlock::createRateBlock($reason, $IP, $secsToGo);
644 wfActivityReport::logBlockedIP($IP, null, 'throttle');
645 $this->tagRequestForBlock($reason);
646
647 $alertCallback = array(new wfBlockAlert($IP, $reason, $secsToGo), 'send');
648
649 do_action('wordfence_security_event', 'block', array(
650 'ip' => $IP,
651 'reason' => $reason,
652 'duration' => $secsToGo,
653 ), $alertCallback);
654 wordfence::status(2, 'info', sprintf(/* translators: 1. IP address. 2. Description of firewall action. */ __('Blocking IP %1$s. %2$s', 'wordfence'), $IP, $reason));
655 }
656 else if ($action == 'throttle') { //Rate limited - throttle
657 $secsToGo = wfBlock::rateLimitThrottleDuration();
658 wfBlock::createRateThrottle($reason, $IP, $secsToGo);
659 wfActivityReport::logBlockedIP($IP, null, 'throttle');
660
661 do_action('wordfence_security_event', 'throttle', array(
662 'ip' => $IP,
663 'reason' => $reason,
664 'duration' => $secsToGo,
665 ));
666 wordfence::status(2, 'info', sprintf(/* translators: 1. IP address. 2. Description of firewall action. */ __('Throttling IP %1$s. %2$s', 'wordfence'), $IP, $reason));
667 wfConfig::inc('totalIPsThrottled');
668 }
669 $this->do503($secsToGo, $reason, false);
670 }
671
672 return;
673 }
674
675 /**
676 * Test if the current request is for wp-login.php or xmlrpc.php
677 *
678 * @return boolean
679 */
680 private static function isAuthRequest() {
681 if ((strpos($_SERVER['REQUEST_URI'], '/wp-login.php') !== false)) {
682 return true;
683 }
684 return false;
685 }
686
687 public function do503($secsToGo, $reason, $sendEventToCentral = true){
688 $this->initLogRequest();
689
690 if ($sendEventToCentral) {
691 do_action('wordfence_security_event', 'block', array(
692 'ip' => wfUtils::inet_ntop($this->currentRequest->IP),
693 'reason' => $this->currentRequest->actionDescription ? $this->currentRequest->actionDescription : $reason,
694 'duration' => $secsToGo,
695 ));
696 }
697
698 $this->currentRequest->statusCode = 503;
699 if (!$this->currentRequest->action) {
700 $this->currentRequest->action = 'blocked:wordfence';
701 }
702 if (!$this->currentRequest->actionDescription) {
703 $this->currentRequest->actionDescription = "blocked: " . $reason;
704 }
705
706 $this->logHit();
707
708 wfConfig::inc('total503s');
709 wfUtils::doNotCache();
710 header('HTTP/1.1 503 Service Temporarily Unavailable');
711 header('Status: 503 Service Temporarily Unavailable');
712 if($secsToGo){
713 header('Retry-After: ' . $secsToGo);
714 }
715 $customText = wpautop(wp_strip_all_tags(wfConfig::get('blockCustomText', '')));
716 require_once(dirname(__FILE__) . '/wf503.php');
717 exit();
718 }
719 private function redirect($URL){
720 wfUtils::doNotCache();
721 wp_redirect($URL, 302);
722 exit();
723 }
724 private function googleSafetyCheckOK(){ //returns true if OK to block. Returns false if we must not block.
725 $cacheKey = md5( (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '') . ' ' . wfUtils::getIP());
726 //Cache so we can call this multiple times in one request
727 if(! isset(self::$gbSafeCache[$cacheKey])){
728 $nb = wfConfig::get('neverBlockBG');
729 if($nb == 'treatAsOtherCrawlers'){
730 self::$gbSafeCache[$cacheKey] = true; //OK to block because we're treating google like everyone else
731 } else if($nb == 'neverBlockUA' || $nb == 'neverBlockVerified'){
732 if(wfCrawl::isGoogleCrawler()){ //Check the UA using regex
733 if($nb == 'neverBlockVerified'){
734 if(wfCrawl::isVerifiedGoogleCrawler(wfUtils::getIP())){ //UA check passed, now verify using PTR if configured to
735 self::$gbSafeCache[$cacheKey] = false; //This is a verified Google crawler, so no we can't block it
736 } else {
737 self::$gbSafeCache[$cacheKey] = true; //This is a crawler claiming to be Google but it did not verify
738 }
739 } else { //neverBlockUA
740 self::$gbSafeCache[$cacheKey] = false; //User configured us to only do a UA check and this claims to be google so don't block
741 }
742 } else {
743 self::$gbSafeCache[$cacheKey] = true; //This isn't a Google UA, so it's OK to block
744 }
745 } else {
746 //error_log("Wordfence error: neverBlockBG option is not set.");
747 self::$gbSafeCache[$cacheKey] = false; //Oops the config option is not set. This should never happen because it's set on install. So we return false to indicate it's not OK to block just for safety.
748 }
749 }
750 if(! isset(self::$gbSafeCache[$cacheKey])){
751 //error_log("Wordfence assertion fail in googleSafetyCheckOK: cached value is not set.");
752 return false; //for safety
753 }
754 return self::$gbSafeCache[$cacheKey]; //return cached value
755 }
756 public function addStatus($level, $type, $msg){
757 //$msg = '[' . sprintf('%.2f', memory_get_usage(true) / (1024 * 1024)) . '] ' . $msg;
758 $this->getDB()->queryWrite("insert into " . $this->statusTable . " (ctime, level, type, msg) values (%s, %d, '%s', '%s')", sprintf('%.6f', microtime(true)), $level, $type, $msg);
759 }
760 public function getStatusEvents($lastCtime){
761 if($lastCtime < 1){
762 $lastCtime = $this->getDB()->querySingle("select ctime from " . $this->statusTable . " order by ctime desc limit 1000,1");
763 if(! $lastCtime){
764 $lastCtime = 0;
765 }
766 }
767 $results = $this->getDB()->querySelect("select ctime, level, type, msg from " . $this->statusTable . " where ctime > %f order by ctime asc", $lastCtime);
768 $timeOffset = 3600 * get_option('gmt_offset');
769 foreach($results as &$rec){
770 //$rec['timeAgo'] = wfUtils::makeTimeAgo(time() - $rec['ctime']);
771 $rec['date'] = date('M d H:i:s', (int) $rec['ctime'] + $timeOffset);
772 $rec['msg'] = wp_kses_data( (string) $rec['msg']);
773 }
774 return $results;
775 }
776 public function getSummaryEvents(){
777 $results = $this->getDB()->querySelect("select ctime, level, type, msg from " . $this->statusTable . " where level = 10 order by ctime desc limit 100");
778 $timeOffset = 3600 * get_option('gmt_offset');
779 foreach($results as &$rec){
780 $rec['date'] = date('M d H:i:s', (int) $rec['ctime'] + $timeOffset);
781 if(strpos($rec['msg'], 'SUM_PREP:') === 0){
782 break;
783 }
784 }
785 return array_reverse($results);
786 }
787
788 /**
789 * @return string
790 */
791 public function getGooglePattern() {
792 return $this->googlePattern;
793 }
794
795 }
796
797 /**
798 *
799 */
800 class wfUserIPRange {
801
802 /**
803 * @var string|null
804 */
805 private $ip_string;
806
807 /**
808 * @param string|null $ip_string
809 */
810 public function __construct($ip_string = null) {
811 $this->setIPString($ip_string);
812 }
813
814 /**
815 * Check if the supplied IP address is within the user supplied range.
816 *
817 * @param string $ip
818 * @return bool
819 */
820 public function isIPInRange($ip) {
821 $ip_string = $this->getIPString();
822
823 if (strpos($ip_string, '/') !== false) { //CIDR range -- 127.0.0.1/24
824 return wfUtils::subnetContainsIP($ip_string, $ip);
825 }
826 else if (strpos($ip_string, '[') !== false) //Bracketed range -- 127.0.0.[1-100]
827 {
828 // IPv4 range
829 if (strpos($ip_string, '.') !== false && strpos($ip, '.') !== false) {
830 // IPv4-mapped-IPv6
831 if (preg_match('/:ffff:([^:]+)$/i', $ip_string, $matches)) {
832 $ip_string = $matches[1];
833 }
834 if (preg_match('/:ffff:([^:]+)$/i', $ip, $matches)) {
835 $ip = $matches[1];
836 }
837
838 // Range check
839 if (preg_match('/\[\d+\-\d+\]/', $ip_string)) {
840 $IPparts = explode('.', $ip);
841 $whiteParts = explode('.', $ip_string);
842 $mismatch = false;
843 if (count($whiteParts) != 4 || count($IPparts) != 4) {
844 return false;
845 }
846
847 for ($i = 0; $i <= 3; $i++) {
848 if (preg_match('/^\[(\d+)\-(\d+)\]$/', $whiteParts[$i], $m)) {
849 if ($IPparts[$i] < $m[1] || $IPparts[$i] > $m[2]) {
850 $mismatch = true;
851 }
852 }
853 else if ($whiteParts[$i] != $IPparts[$i]) {
854 $mismatch = true;
855 }
856 }
857 if ($mismatch === false) {
858 return true; // Is whitelisted because we did not get a mismatch
859 }
860 }
861 else if ($ip_string == $ip) {
862 return true;
863 }
864
865 // IPv6 range
866 }
867 else if (strpos($ip_string, ':') !== false && strpos($ip, ':') !== false) {
868 $ip = strtolower(wfUtils::expandIPv6Address($ip));
869 $ip_string = strtolower(self::expandIPv6Range($ip_string));
870 if (preg_match('/\[[a-f0-9]+\-[a-f0-9]+\]/i', $ip_string)) {
871 $IPparts = explode(':', $ip);
872 $whiteParts = explode(':', $ip_string);
873 $mismatch = false;
874 if (count($whiteParts) != 8 || count($IPparts) != 8) {
875 return false;
876 }
877
878 for ($i = 0; $i <= 7; $i++) {
879 if (preg_match('/^\[([a-f0-9]+)\-([a-f0-9]+)\]$/i', $whiteParts[$i], $m)) {
880 $ip_group = hexdec($IPparts[$i]);
881 $range_group_from = hexdec($m[1]);
882 $range_group_to = hexdec($m[2]);
883 if ($ip_group < $range_group_from || $ip_group > $range_group_to) {
884 $mismatch = true;
885 break;
886 }
887 }
888 else if ($whiteParts[$i] != $IPparts[$i]) {
889 $mismatch = true;
890 break;
891 }
892 }
893 if ($mismatch === false) {
894 return true; // Is whitelisted because we did not get a mismatch
895 }
896 }
897 else if ($ip_string == $ip) {
898 return true;
899 }
900 }
901 }
902 else if (strpos($ip_string, '-') !== false) { //Linear range -- 127.0.0.1 - 127.0.1.100
903 list($ip1, $ip2) = explode('-', $ip_string);
904 $ip1N = wfUtils::inet_pton($ip1);
905 $ip2N = wfUtils::inet_pton($ip2);
906 $ipN = wfUtils::inet_pton($ip);
907 return (strcmp($ip1N, $ipN) <= 0 && strcmp($ip2N, $ipN) >= 0);
908 }
909 else { //Treat as a literal IP
910 $ip1 = wfUtils::inet_pton($ip_string);
911 $ip2 = wfUtils::inet_pton($ip);
912 if ($ip1 !== false && $ip1 == $ip2) {
913 return true;
914 }
915 }
916
917 return false;
918 }
919
920 private static function repeatString($string, $count) {
921 if ($count <= 0)
922 return '';
923 return str_repeat($string, $count);
924 }
925
926 /**
927 * Expand a compressed printable range representation of an IPv6 address.
928 *
929 * @todo Hook up exceptions for better error handling.
930 * @todo Allow IPv4 mapped IPv6 addresses (::ffff:192.168.1.1).
931 * @param string $ip_range
932 * @return string
933 */
934 public static function expandIPv6Range($ip_range) {
935 $colon_count = substr_count($ip_range, ':');
936 $dbl_colon_count = substr_count($ip_range, '::');
937 if ($dbl_colon_count > 1) {
938 return false;
939 }
940 $dbl_colon_pos = strpos($ip_range, '::');
941 if ($dbl_colon_pos !== false) {
942 $ip_range = str_replace('::', self::repeatString(':0000',
943 (($dbl_colon_pos === 0 || $dbl_colon_pos === strlen($ip_range) - 2) ? 9 : 8) - $colon_count) . ':', $ip_range);
944 $ip_range = trim($ip_range, ':');
945 }
946 $colon_count = substr_count($ip_range, ':');
947 if ($colon_count != 7) {
948 return false;
949 }
950
951 $groups = explode(':', $ip_range);
952 $expanded = '';
953 foreach ($groups as $group) {
954 if (preg_match('/\[([a-f0-9]{1,4})\-([a-f0-9]{1,4})\]/i', $group, $matches)) {
955 $expanded .= sprintf('[%s-%s]', str_pad(strtolower($matches[1]), 4, '0', STR_PAD_LEFT), str_pad(strtolower($matches[2]), 4, '0', STR_PAD_LEFT)) . ':';
956 } else if (preg_match('/[a-f0-9]{1,4}/i', $group)) {
957 $expanded .= str_pad(strtolower($group), 4, '0', STR_PAD_LEFT) . ':';
958 } else {
959 return false;
960 }
961 }
962 return trim($expanded, ':');
963 }
964
965 /**
966 * @return bool
967 */
968 public function isValidRange() {
969 return $this->isValidCIDRRange() || $this->isValidBracketedRange() || $this->isValidLinearRange() || wfUtils::isValidIP($this->getIPString());
970 }
971
972 public function isValidCIDRRange() { //e.g., 192.0.2.1/24
973 $ip_string = $this->getIPString();
974 if (preg_match('/[^0-9a-f:\/\.]/i', $ip_string)) { return false; }
975 return wfUtils::isValidCIDRRange($ip_string);
976 }
977
978 public function isValidBracketedRange() { //e.g., 192.0.2.[1-10]
979 $ip_string = $this->getIPString();
980 if (preg_match('/[^0-9a-f:\.\[\]\-]/i', $ip_string)) { return false; }
981 if (strpos($ip_string, '.') !== false) { //IPv4
982 if (preg_match_all('/(\d+)/', $ip_string, $matches) > 0) {
983 foreach ($matches[1] as $match) {
984 $group = (int) $match;
985 if ($group > 255 || $group < 0) {
986 return false;
987 }
988 }
989 }
990
991 $group_regex = '([0-9]{1,3}|\[[0-9]{1,3}\-[0-9]{1,3}\])';
992 return preg_match('/^' . str_repeat("{$group_regex}\\.", 3) . $group_regex . '$/i', $ip_string) > 0;
993 }
994
995 //IPv6
996 if (strpos($ip_string, '::') !== false) {
997 $ip_string = self::expandIPv6Range($ip_string);
998 }
999 if (!$ip_string) {
1000 return false;
1001 }
1002 $group_regex = '([a-f0-9]{1,4}|\[[a-f0-9]{1,4}\-[a-f0-9]{1,4}\])';
1003 return preg_match('/^' . str_repeat("$group_regex:", 7) . $group_regex . '$/i', $ip_string) > 0;
1004 }
1005
1006 public function isValidLinearRange() { //e.g., 192.0.2.1-192.0.2.100
1007 $ip_string = $this->getIPString();
1008 if (preg_match('/[^0-9a-f:\.\-]/i', $ip_string)) { return false; }
1009 list($ip1, $ip2) = explode("-", $ip_string);
1010
1011 if (!wfUtils::isValidIP($ip1) || !wfUtils::isValidIP($ip2)) {
1012 return false;
1013 }
1014
1015 $ip1N = wfUtils::inet_pton($ip1);
1016 $ip2N = wfUtils::inet_pton($ip2);
1017
1018 if ($ip1N === false || $ip2N === false) {
1019 return false;
1020 }
1021
1022 return strcmp($ip1N, $ip2N) <= 0;
1023 }
1024
1025 public function isMixedRange() { //e.g., 192.0.2.1-2001:db8::ffff
1026 $ip_string = $this->getIPString();
1027 if (preg_match('/[^0-9a-f:\.\-]/i', $ip_string)) { return false; }
1028 list($ip1, $ip2) = explode("-", $ip_string);
1029
1030 $ipv4Count = 0;
1031 $ipv4Count += filter_var($ip1, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false ? 1 : 0;
1032 $ipv4Count += filter_var($ip2, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false ? 1 : 0;
1033
1034 $ipv6Count = 0;
1035 $ipv6Count += filter_var($ip1, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false ? 1 : 0;
1036 $ipv6Count += filter_var($ip2, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false ? 1 : 0;
1037
1038 if ($ipv4Count != 2 && $ipv6Count != 2) {
1039 return true;
1040 }
1041
1042 return false;
1043 }
1044
1045 protected function _sanitizeIPRange($ip_string) {
1046 if (!is_string($ip_string))
1047 return null;
1048 $ip_string = preg_replace('/\s/', '', $ip_string); //Strip whitespace
1049 $ip_string = preg_replace('/[\\x{2013}-\\x{2015}]/u', '-', $ip_string); //Non-hyphen dashes to hyphen
1050 $ip_string = strtolower($ip_string);
1051
1052 if (preg_match('/^\d+-\d+$/', $ip_string)) { //v5 32 bit int style format
1053 list($start, $end) = explode('-', $ip_string);
1054 $start = long2ip($start);
1055 $end = long2ip($end);
1056 $ip_string = "{$start}-{$end}";
1057 }
1058
1059 return $ip_string;
1060 }
1061
1062 /**
1063 * @return string|null
1064 */
1065 public function getIPString() {
1066 return $this->ip_string;
1067 }
1068
1069 /**
1070 * @param string|null $ip_string
1071 */
1072 public function setIPString($ip_string) {
1073 $this->ip_string = $this->_sanitizeIPRange($ip_string);
1074 }
1075 }
1076
1077 /**
1078 * The function of this class is to detect admin users created via direct access to the database (in other words, not
1079 * through WordPress).
1080 */
1081 class wfAdminUserMonitor {
1082
1083 protected $currentAdminList = array();
1084
1085 public function isEnabled() {
1086 $options = wfScanner::shared()->scanOptions();
1087 $enabled = $options['scansEnabled_suspiciousAdminUsers'];
1088 if ($enabled && is_multisite()) {
1089 if (!function_exists('wp_is_large_network')) {
1090 require_once(ABSPATH . WPINC . '/ms-functions.php');
1091 }
1092 $enabled = !wp_is_large_network('sites') && !wp_is_large_network('users');
1093 }
1094 return $enabled;
1095 }
1096
1097 /**
1098 *
1099 */
1100 public function createInitialList() {
1101 $admins = $this->getCurrentAdmins();
1102 $adminUserList = array();
1103 foreach ($admins as $id => $user) {
1104 $adminUserList[$id] = 1;
1105 }
1106 wfConfig::set_ser('adminUserList', $adminUserList);
1107 }
1108
1109 /**
1110 * @param int $userID
1111 */
1112 public function grantSuperAdmin($userID = null) {
1113 if ($userID) {
1114 $this->addAdmin($userID);
1115 }
1116 }
1117
1118 /**
1119 * @param int $userID
1120 */
1121 public function revokeSuperAdmin($userID = null) {
1122 if ($userID) {
1123 $this->removeAdmin($userID);
1124 }
1125 }
1126
1127 /**
1128 * @param int $ID
1129 * @param mixed $role
1130 * @param mixed $old_roles
1131 */
1132 public function updateToUserRole($ID = null, $role = null, $old_roles = null) {
1133 $admins = $this->getLoggedAdmins();
1134 if ($role !== 'administrator' && array_key_exists($ID, $admins)) {
1135 $this->removeAdmin($ID);
1136 } else if ($role === 'administrator') {
1137 $this->addAdmin($ID);
1138 }
1139 }
1140
1141 /**
1142 * @return array|bool
1143 */
1144 public function checkNewAdmins() {
1145 $loggedAdmins = $this->getLoggedAdmins();
1146 $admins = $this->getCurrentAdmins();
1147 $suspiciousAdmins = array();
1148 foreach ($admins as $adminID => $v) {
1149 if (!array_key_exists($adminID, $loggedAdmins)) {
1150 $suspiciousAdmins[] = $adminID;
1151 }
1152 }
1153 return $suspiciousAdmins ? $suspiciousAdmins : false;
1154 }
1155
1156 /**
1157 * Checks if the supplied user ID is suspicious.
1158 *
1159 * @param int $userID
1160 * @return bool
1161 */
1162 public function isAdminUserLogged($userID) {
1163 $loggedAdmins = $this->getLoggedAdmins();
1164 return array_key_exists($userID, $loggedAdmins);
1165 }
1166
1167 /**
1168 * @param bool $forceReload
1169 * @return array
1170 */
1171 public function getCurrentAdmins($forceReload = false) {
1172 if (empty($this->currentAdminList) || $forceReload) {
1173 require_once(ABSPATH . WPINC . '/user.php');
1174 if (is_multisite()) {
1175 if (function_exists("get_sites")) {
1176 $sites = get_sites(array(
1177 'network_id' => null,
1178 ));
1179 }
1180 else {
1181 $sites = wp_get_sites(array(
1182 'network_id' => null,
1183 ));
1184 }
1185 } else {
1186 $sites = array(array(
1187 'blog_id' => get_current_blog_id(),
1188 ));
1189 }
1190
1191 // not very efficient, but the WordPress API doesn't provide a good way to do this.
1192 $this->currentAdminList = array();
1193 foreach ($sites as $siteRow) {
1194 $siteRowArray = (array) $siteRow;
1195 $user_query = new WP_User_Query(array(
1196 'blog_id' => $siteRowArray['blog_id'],
1197 'role' => 'administrator',
1198 ));
1199 $users = $user_query->get_results();
1200 if (is_array($users)) {
1201 /** @var WP_User $user */
1202 foreach ($users as $user) {
1203 $this->currentAdminList[$user->ID] = $user;
1204 }
1205 }
1206 }
1207
1208 // Add any super admins that aren't also admins on a network
1209 $superAdmins = get_super_admins();
1210 foreach ($superAdmins as $userLogin) {
1211 $user = get_user_by('login', $userLogin);
1212 if ($user) {
1213 $this->currentAdminList[$user->ID] = $user;
1214 }
1215 }
1216 }
1217
1218 return $this->currentAdminList;
1219 }
1220
1221 public function getLoggedAdmins() {
1222 $loggedAdmins = wfConfig::get_ser('adminUserList', false);
1223 if (!is_array($loggedAdmins)) {
1224 $this->createInitialList();
1225 $loggedAdmins = wfConfig::get_ser('adminUserList', false);
1226 }
1227 if (!is_array($loggedAdmins)) {
1228 $loggedAdmins = array();
1229 }
1230 return $loggedAdmins;
1231 }
1232
1233 /**
1234 * @param int $userID
1235 */
1236 public function addAdmin($userID) {
1237 $loggedAdmins = $this->getLoggedAdmins();
1238 if (!array_key_exists($userID, $loggedAdmins)) {
1239 $loggedAdmins[$userID] = 1;
1240 wfConfig::set_ser('adminUserList', $loggedAdmins);
1241 }
1242 }
1243
1244 /**
1245 * @param int $userID
1246 */
1247 public function removeAdmin($userID) {
1248 $loggedAdmins = $this->getLoggedAdmins();
1249 if (array_key_exists($userID, $loggedAdmins) && !array_key_exists($userID, $this->getCurrentAdmins())) {
1250 unset($loggedAdmins[$userID]);
1251 wfConfig::set_ser('adminUserList', $loggedAdmins);
1252 }
1253 }
1254 }
1255
1256 /**
1257 * Represents a request record
1258 *
1259 * @property int $id
1260 * @property float $attackLogTime
1261 * @property float $ctime
1262 * @property string $IP
1263 * @property bool $jsRun
1264 * @property int $statusCode
1265 * @property bool $isGoogle
1266 * @property int $userID
1267 * @property string $URL
1268 * @property string $referer
1269 * @property string $UA
1270 * @property string $action
1271 * @property string $actionDescription
1272 * @property string $actionData
1273 */
1274 class wfRequestModel extends wfModel {
1275
1276 private static $actionDataEncodedParams = array(
1277 'paramKey',
1278 'paramValue',
1279 'path',
1280 );
1281
1282 /**
1283 * @param $actionData
1284 * @return mixed|string|void
1285 */
1286 public static function serializeActionData($actionData, $optionalKeys = array(), $maxLength = 65535) {
1287 if (is_array($actionData)) {
1288 foreach (self::$actionDataEncodedParams as $key) {
1289 if (array_key_exists($key, $actionData)) {
1290 $actionData[$key] = base64_encode($actionData[$key]);
1291 }
1292 }
1293 }
1294 do {
1295 $serialized = json_encode($actionData, JSON_UNESCAPED_SLASHES);
1296 $length = strlen($serialized);
1297 if ($length <= $maxLength)
1298 return $serialized;
1299 $excess = $length - $maxLength;
1300 $truncated = false;
1301 foreach ($optionalKeys as $key) {
1302 if (array_key_exists($key, $actionData)) {
1303 $fieldValue = $actionData[$key];
1304 $fieldLength = strlen($fieldValue);
1305 $truncatedLength = min($fieldLength, $excess);
1306 $truncated = true;
1307 if ($truncatedLength > 0) {
1308 $actionData[$key] = substr($fieldValue, 0, -$truncatedLength);
1309 $excess -= $truncatedLength;
1310 }
1311 else {
1312 unset($actionData[$key]);
1313 break;
1314 }
1315 }
1316 }
1317 } while ($truncated);
1318 return null;
1319 }
1320
1321 /**
1322 * @param $actionDataJSON
1323 * @return mixed|string|void
1324 */
1325 public static function unserializeActionData($actionDataJSON) {
1326 $actionData = json_decode($actionDataJSON, true);
1327 if (is_array($actionData)) {
1328 foreach (self::$actionDataEncodedParams as $key) {
1329 if (array_key_exists($key, $actionData)) {
1330 $actionData[$key] = base64_decode($actionData[$key]);
1331 }
1332 }
1333 }
1334 else {
1335 $actionData = array();
1336 }
1337 return $actionData;
1338 }
1339
1340 private $columns = array(
1341 'id',
1342 'attackLogTime',
1343 'ctime',
1344 'IP',
1345 'jsRun',
1346 'statusCode',
1347 'isGoogle',
1348 'userID',
1349 'URL',
1350 'referer',
1351 'UA',
1352 'action',
1353 'actionDescription',
1354 'actionData',
1355 );
1356
1357 public function getIDColumn() {
1358 return 'id';
1359 }
1360
1361 public function getTable() {
1362 return wfDB::networkTable('wfHits');
1363 }
1364
1365 public function hasColumn($column) {
1366 return in_array($column, $this->columns);
1367 }
1368
1369 public function save() {
1370 $sapi = @php_sapi_name();
1371 if ($sapi == "cli") {
1372 return false;
1373 }
1374
1375 return parent::save();
1376 }
1377 }
1378
1379
1380 class wfLiveTrafficQuery {
1381
1382 protected $validParams = array(
1383 'id' => 'h.id',
1384 'ctime' => 'h.ctime',
1385 'ip' => 'h.ip',
1386 'jsrun' => 'h.jsrun',
1387 'statuscode' => 'h.statuscode',
1388 'isgoogle' => 'h.isgoogle',
1389 'userid' => 'h.userid',
1390 'url' => 'h.url',
1391 'referer' => 'h.referer',
1392 'ua' => 'h.ua',
1393 'action' => 'h.action',
1394 'actiondescription' => 'h.actiondescription',
1395 'actiondata' => 'h.actiondata',
1396
1397 // wfLogins
1398 'user_login' => 'u.user_login',
1399 'username' => 'l.username',
1400 );
1401
1402 /** @var wfLiveTrafficQueryFilterCollection */
1403 private $filters = array();
1404
1405 /** @var wfLiveTrafficQueryGroupBy */
1406 private $groupBy;
1407 /**
1408 * @var float|null
1409 */
1410 private $startDate;
1411 /**
1412 * @var float|null
1413 */
1414 private $endDate;
1415 /**
1416 * @var int
1417 */
1418 private $limit;
1419 /**
1420 * @var int
1421 */
1422 private $offset;
1423
1424 private $tableName;
1425
1426 /** @var wfLog */
1427 private $wfLog;
1428
1429 /**
1430 * wfLiveTrafficQuery constructor.
1431 *
1432 * @param wfLog $wfLog
1433 * @param wfLiveTrafficQueryFilterCollection $filters
1434 * @param wfLiveTrafficQueryGroupBy $groupBy
1435 * @param float $startDate
1436 * @param float $endDate
1437 * @param int $limit
1438 * @param int $offset
1439 */
1440 public function __construct($wfLog, $filters = null, $groupBy = null, $startDate = null, $endDate = null, $limit = 20, $offset = 0) {
1441 $this->wfLog = $wfLog;
1442 $this->filters = $filters;
1443 $this->groupBy = $groupBy;
1444 $this->startDate = $startDate;
1445 $this->endDate = $endDate;
1446 $this->limit = $limit;
1447 $this->offset = $offset;
1448 }
1449
1450 /**
1451 * @return array|null|object
1452 */
1453 public function execute() {
1454 global $wpdb;
1455 $delayedHumanBotFiltering = false;
1456 $humanOnly = false;
1457 $sql = $this->buildQuery($delayedHumanBotFiltering, $humanOnly);
1458 $results = $wpdb->get_results($sql, ARRAY_A);
1459
1460 if ($delayedHumanBotFiltering) {
1461 $browscap = wfBrowscap::shared();
1462 foreach ($results as $index => $res) {
1463 if ($res['UA']) {
1464 $b = $browscap->getBrowser($res['UA']);
1465 $jsRun = wfUtils::truthyToBoolean($res['jsRun']);
1466 if ($b && $b['Parent'] != 'DefaultProperties') {
1467 $jsRun = wfUtils::truthyToBoolean($res['jsRun']);
1468 if (!wfConfig::liveTrafficEnabled() && !$jsRun) {
1469 $jsRun = !(isset($b['Crawler']) && $b['Crawler']);
1470 }
1471 }
1472
1473 if (!$humanOnly && $jsRun || $humanOnly && !$jsRun) {
1474 unset($results[$index]);
1475 }
1476 }
1477 }
1478 }
1479
1480 $this->getWFLog()->processGetHitsResults('', $results);
1481
1482 $verifyCrawlers = false;
1483 if ($this->filters !== null && count($this->filters->getFilters()) > 0) {
1484 $filters = $this->filters->getFilters();
1485 foreach ($filters as $f) {
1486 if (strtolower($f->getParam()) == "isgoogle") {
1487 $verifyCrawlers = true;
1488 break;
1489 }
1490 }
1491 }
1492
1493 foreach ($results as $key => &$row) {
1494 if ($row['isGoogle'] && $verifyCrawlers) {
1495 if (!wfCrawl::isVerifiedGoogleCrawler($row['IP'], $row['UA'])) {
1496 unset($results[$key]); //foreach copies $results and iterates on the copy, so it is safe to mutate $results within the loop
1497 continue;
1498 }
1499 }
1500
1501 $row['actionData'] = $row['actionData'] === null ? array() : (array) json_decode($row['actionData'], true);
1502 }
1503 return array_values($results);
1504 }
1505
1506 /**
1507 * @param mixed $delayedHumanBotFiltering Whether or not human/bot filtering should be applied in PHP rather than SQL.
1508 * @param mixed $humanOnly When using delayed filtering, whether to show only humans or only bots.
1509 *
1510 * @return string
1511 * @throws wfLiveTrafficQueryException
1512 */
1513 public function buildQuery(&$delayedHumanBotFiltering, &$humanOnly) {
1514 global $wpdb;
1515 $filters = $this->getFilters();
1516 $groupBy = $this->getGroupBy();
1517 $startDate = $this->getStartDate();
1518 $endDate = $this->getEndDate();
1519 $limit = absint($this->getLimit());
1520 $offset = absint($this->getOffset());
1521
1522 $wheres = array("h.action != 'logged:waf'", "h.action != 'scan:detectproxy'");
1523 if ($startDate) {
1524 $wheres[] = $wpdb->prepare('h.ctime > %f', $startDate);
1525 }
1526 if ($endDate) {
1527 $wheres[] = $wpdb->prepare('h.ctime < %f', $endDate);
1528 }
1529
1530 if ($filters instanceof wfLiveTrafficQueryFilterCollection) {
1531 if (!wfConfig::liveTrafficEnabled()) {
1532 $individualFilters = $filters->getFilters();
1533 foreach ($individualFilters as $index => $f) {
1534 if ($f->getParam() == 'jsRun' && $delayedHumanBotFiltering !== null && $humanOnly !== null) {
1535 $humanOnly = wfUtils::truthyToBoolean($f->getValue());
1536 if ($f->getOperator() == '!=') {
1537 $humanOnly = !$humanOnly;
1538 }
1539 $delayedHumanBotFiltering = true;
1540 unset($individualFilters[$index]);
1541 }
1542 }
1543 $filters->setFilters($individualFilters);
1544 }
1545
1546 $filtersSQL = $filters->toSQL();
1547 if ($filtersSQL) {
1548 $wheres[] = $filtersSQL;
1549 }
1550 }
1551
1552 $orderBy = 'ORDER BY h.ctime DESC';
1553 $select = ', l.username';
1554 $groupBySQL = '';
1555 if ($groupBy && $groupBy->validate()) {
1556 $groupBySQL = "GROUP BY {$groupBy->getParam()}";
1557 $orderBy = 'ORDER BY hitCount DESC';
1558 $select .= ', COUNT(h.id) as hitCount, MAX(h.ctime) AS lastHit, u.user_login AS username';
1559
1560 if ($groupBy->getParam() == 'user_login') {
1561 $wheres[] = 'user_login IS NOT NULL';
1562 }
1563 else if ($groupBy->getParam() == 'action') {
1564 $wheres[] = '(statusCode = 403 OR statusCode = 503)';
1565 }
1566 }
1567
1568 $where = join(' AND ', $wheres);
1569 if ($where) {
1570 $where = 'WHERE ' . $where;
1571 }
1572 if (!$limit || $limit > 1000) {
1573 $limit = 20;
1574 }
1575 $limitSQL = $wpdb->prepare('LIMIT %d, %d', $offset, $limit);
1576
1577 $table_wfLogins = wfDB::networkTable('wfLogins');
1578 $sql = <<<SQL
1579 SELECT h.*, u.display_name{$select} FROM {$this->getTableName()} h
1580 LEFT JOIN {$wpdb->users} u on h.userID = u.ID
1581 LEFT JOIN {$table_wfLogins} l on h.id = l.hitID
1582 $where
1583 $groupBySQL
1584 $orderBy
1585 $limitSQL
1586 SQL;
1587
1588 return $sql;
1589 }
1590
1591 /**
1592 * @param $param
1593 * @return bool
1594 */
1595 public function isValidParam($param) {
1596 return array_key_exists(strtolower($param), $this->validParams);
1597 }
1598
1599 /**
1600 * @param $getParam
1601 * @return bool|string
1602 */
1603 public function getColumnFromParam($getParam) {
1604 $getParam = strtolower($getParam);
1605 if (array_key_exists($getParam, $this->validParams)) {
1606 return $this->validParams[$getParam];
1607 }
1608 return false;
1609 }
1610
1611 /**
1612 * @return wfLiveTrafficQueryFilterCollection
1613 */
1614 public function getFilters() {
1615 return $this->filters;
1616 }
1617
1618 /**
1619 * @param wfLiveTrafficQueryFilterCollection $filters
1620 */
1621 public function setFilters($filters) {
1622 $this->filters = $filters;
1623 }
1624
1625 /**
1626 * @return float|null
1627 */
1628 public function getStartDate() {
1629 return $this->startDate;
1630 }
1631
1632 /**
1633 * @param float|null $startDate
1634 */
1635 public function setStartDate($startDate) {
1636 $this->startDate = $startDate;
1637 }
1638
1639 /**
1640 * @return float|null
1641 */
1642 public function getEndDate() {
1643 return $this->endDate;
1644 }
1645
1646 /**
1647 * @param float|null $endDate
1648 */
1649 public function setEndDate($endDate) {
1650 $this->endDate = $endDate;
1651 }
1652
1653 /**
1654 * @return wfLiveTrafficQueryGroupBy
1655 */
1656 public function getGroupBy() {
1657 return $this->groupBy;
1658 }
1659
1660 /**
1661 * @param wfLiveTrafficQueryGroupBy $groupBy
1662 */
1663 public function setGroupBy($groupBy) {
1664 $this->groupBy = $groupBy;
1665 }
1666
1667 /**
1668 * @return int
1669 */
1670 public function getLimit() {
1671 return $this->limit;
1672 }
1673
1674 /**
1675 * @param int $limit
1676 */
1677 public function setLimit($limit) {
1678 $this->limit = $limit;
1679 }
1680
1681 /**
1682 * @return int
1683 */
1684 public function getOffset() {
1685 return $this->offset;
1686 }
1687
1688 /**
1689 * @param int $offset
1690 */
1691 public function setOffset($offset) {
1692 $this->offset = $offset;
1693 }
1694
1695 /**
1696 * @return string
1697 */
1698 public function getTableName() {
1699 if ($this->tableName === null) {
1700 $this->tableName = wfDB::networkTable('wfHits');
1701 }
1702 return $this->tableName;
1703 }
1704
1705 /**
1706 * @param string $tableName
1707 */
1708 public function setTableName($tableName) {
1709 $this->tableName = $tableName;
1710 }
1711
1712 /**
1713 * @return wfLog
1714 */
1715 public function getWFLog() {
1716 return $this->wfLog;
1717 }
1718
1719 /**
1720 * @param wfLog $wfLog
1721 */
1722 public function setWFLog($wfLog) {
1723 $this->wfLog = $wfLog;
1724 }
1725 }
1726
1727 class wfLiveTrafficQueryFilterCollection {
1728
1729 private $filters = array();
1730
1731 /**
1732 * wfLiveTrafficQueryFilterCollection constructor.
1733 *
1734 * @param array $filters
1735 */
1736 public function __construct($filters = array()) {
1737 $this->filters = $filters;
1738 }
1739
1740 public function toSQL() {
1741 $params = array();
1742 $sql = '';
1743 $filters = $this->getFilters();
1744 if ($filters) {
1745 /** @var wfLiveTrafficQueryFilter $filter */
1746 foreach ($filters as $filter) {
1747 $params[$filter->getParam()][] = $filter;
1748 }
1749 }
1750
1751 foreach ($params as $param => $filters) {
1752 // $sql .= '(';
1753 $filtersSQL = '';
1754 foreach ($filters as $filter) {
1755 $filterSQL = $filter->toSQL();
1756 if ($filterSQL) {
1757 $filtersSQL .= $filterSQL . ' OR ';
1758 }
1759 }
1760 if ($filtersSQL) {
1761 $sql .= '(' . substr($filtersSQL, 0, -4) . ') AND ';
1762 }
1763 }
1764 if ($sql) {
1765 $sql = substr($sql, 0, -5);
1766 }
1767 return $sql;
1768 }
1769
1770 public function addFilter($filter) {
1771 $this->filters[] = $filter;
1772 }
1773
1774 /**
1775 * @return array
1776 */
1777 public function getFilters() {
1778 return $this->filters;
1779 }
1780
1781 /**
1782 * @param array $filters
1783 */
1784 public function setFilters($filters) {
1785 $this->filters = $filters;
1786 }
1787 }
1788
1789 class wfLiveTrafficQueryFilter {
1790
1791 private $param;
1792 private $operator;
1793 private $value;
1794
1795 protected $validOperators = array(
1796 '=',
1797 '!=',
1798 'contains',
1799 'match',
1800 'hregexp',
1801 'hnotregexp',
1802 );
1803
1804 /**
1805 * @var wfLiveTrafficQuery
1806 */
1807 private $query;
1808
1809 /**
1810 * wfLiveTrafficQueryFilter constructor.
1811 *
1812 * @param wfLiveTrafficQuery $query
1813 * @param string $param
1814 * @param string $operator
1815 * @param string $value
1816 */
1817 public function __construct($query, $param, $operator, $value) {
1818 $this->query = $query;
1819 $this->param = $param;
1820 $this->operator = $operator;
1821 $this->value = $value;
1822 }
1823
1824 /**
1825 * @return string|void
1826 */
1827 public function toSQL() {
1828 $sql = '';
1829 if ($this->validate()) {
1830 /** @var wpdb $wpdb */
1831 global $wpdb;
1832 $operator = $this->getOperator();
1833 $param = $this->getQuery()->getColumnFromParam($this->getParam());
1834 if (!$param) {
1835 return $sql;
1836 }
1837 $value = $this->getValue();
1838 switch ($operator) {
1839 case 'contains':
1840 $like = addcslashes($value, '_%\\');
1841 $sql = $wpdb->prepare("$param LIKE %s", "%$like%");
1842 break;
1843
1844 case 'match':
1845 $sql = $wpdb->prepare("$param LIKE %s", $value);
1846 break;
1847
1848 case 'hregexp':
1849 $sql = $wpdb->prepare("HEX($param) REGEXP %s", $value);
1850 break;
1851
1852 case 'hnotregexp':
1853 $sql = $wpdb->prepare("HEX($param) NOT REGEXP %s", $value);
1854 break;
1855
1856 default:
1857 $sql = $wpdb->prepare("$param $operator %s", $value);
1858 break;
1859 }
1860 }
1861 return $sql;
1862 }
1863
1864 /**
1865 * @return bool
1866 */
1867 public function validate() {
1868 $valid = $this->isValidParam($this->getParam()) && $this->isValidOperator($this->getOperator());
1869 if (defined('WP_DEBUG') && WP_DEBUG) {
1870 if (!$valid) {
1871 throw new wfLiveTrafficQueryException("Invalid param/operator [{$this->getParam()}]/[{$this->getOperator()}] passed to " . get_class($this));
1872 }
1873 return true;
1874 }
1875 return $valid;
1876 }
1877
1878 /**
1879 * @param string $param
1880 * @return bool
1881 */
1882 public function isValidParam($param) {
1883 return $this->getQuery() && $this->getQuery()->isValidParam($param);
1884 }
1885
1886 /**
1887 * @param string $operator
1888 * @return bool
1889 */
1890 public function isValidOperator($operator) {
1891 return in_array($operator, $this->validOperators);
1892 }
1893
1894 /**
1895 * @return mixed
1896 */
1897 public function getParam() {
1898 return $this->param;
1899 }
1900
1901 /**
1902 * @param mixed $param
1903 */
1904 public function setParam($param) {
1905 $this->param = $param;
1906 }
1907
1908 /**
1909 * @return mixed
1910 */
1911 public function getOperator() {
1912 return $this->operator;
1913 }
1914
1915 /**
1916 * @param mixed $operator
1917 */
1918 public function setOperator($operator) {
1919 $this->operator = $operator;
1920 }
1921
1922 /**
1923 * @return mixed
1924 */
1925 public function getValue() {
1926 return $this->value;
1927 }
1928
1929 /**
1930 * @param mixed $value
1931 */
1932 public function setValue($value) {
1933 $this->value = $value;
1934 }
1935
1936 /**
1937 * @return wfLiveTrafficQuery
1938 */
1939 public function getQuery() {
1940 return $this->query;
1941 }
1942
1943 /**
1944 * @param wfLiveTrafficQuery $query
1945 */
1946 public function setQuery($query) {
1947 $this->query = $query;
1948 }
1949 }
1950
1951 class wfLiveTrafficQueryGroupBy {
1952
1953 private $param;
1954
1955 /**
1956 * @var wfLiveTrafficQuery
1957 */
1958 private $query;
1959
1960 /**
1961 * wfLiveTrafficQueryGroupBy constructor.
1962 *
1963 * @param wfLiveTrafficQuery $query
1964 * @param string $param
1965 */
1966 public function __construct($query, $param) {
1967 $this->query = $query;
1968 $this->param = $param;
1969 }
1970
1971 /**
1972 * @return bool
1973 * @throws wfLiveTrafficQueryException
1974 */
1975 public function validate() {
1976 $valid = $this->isValidParam($this->getParam());
1977 if (defined('WP_DEBUG') && WP_DEBUG) {
1978 if (!$valid) {
1979 throw new wfLiveTrafficQueryException("Invalid param [{$this->getParam()}] passed to " . get_class($this));
1980 }
1981 return true;
1982 }
1983 return $valid;
1984 }
1985
1986 /**
1987 * @param string $param
1988 * @return bool
1989 */
1990 public function isValidParam($param) {
1991 return $this->getQuery() && $this->getQuery()->isValidParam($param);
1992 }
1993
1994 /**
1995 * @return wfLiveTrafficQuery
1996 */
1997 public function getQuery() {
1998 return $this->query;
1999 }
2000
2001 /**
2002 * @param wfLiveTrafficQuery $query
2003 */
2004 public function setQuery($query) {
2005 $this->query = $query;
2006 }
2007
2008 /**
2009 * @return mixed
2010 */
2011 public function getParam() {
2012 return $this->param;
2013 }
2014
2015 /**
2016 * @param mixed $param
2017 */
2018 public function setParam($param) {
2019 $this->param = $param;
2020 }
2021
2022 }
2023
2024
2025 class wfLiveTrafficQueryException extends Exception {
2026
2027 }
2028
2029 class wfErrorLogHandler {
2030 public static function getErrorLogs($deepSearch = false) {
2031 static $errorLogs = null;
2032
2033 if ($errorLogs === null) {
2034 $searchPaths = array(ABSPATH, ABSPATH . 'wp-admin', ABSPATH . 'wp-content');
2035
2036 $homePath = wfUtils::getHomePath();
2037 if (!in_array($homePath, $searchPaths)) {
2038 $searchPaths[] = $homePath;
2039 }
2040
2041 $errorLogPath = ini_get('error_log');
2042 if (!empty($errorLogPath) && !in_array($errorLogPath, $searchPaths)) {
2043 $searchPaths[] = $errorLogPath;
2044 }
2045
2046 $errorLogs = array();
2047 foreach ($searchPaths as $s) {
2048 $errorLogs = array_merge($errorLogs, self::_scanForLogs($s, $deepSearch));
2049 }
2050 }
2051 return $errorLogs;
2052 }
2053
2054 private static function _scanForLogs($path, $deepSearch = false) {
2055 static $processedFolders = array(); //Protection for endless loops caused by symlinks
2056 if (is_file($path)) {
2057 $file = basename($path);
2058 if (preg_match('#(?:^php_errorlog$|error_log(\-\d+)?$|\.log$)#i', $file)) {
2059 return array($path => is_readable($path));
2060 }
2061 return array();
2062 }
2063
2064 $path = untrailingslashit($path);
2065 if (empty($path)) {
2066 return array();
2067 }
2068
2069 $contents = @scandir($path);
2070 if (!is_array($contents)) {
2071 return array();
2072 }
2073
2074 $processedFolders[$path] = true;
2075 $errorLogs = array();
2076 foreach ($contents as $name) {
2077 if ($name == '.' || $name == '..') { continue; }
2078 $testPath = $path . DIRECTORY_SEPARATOR . $name;
2079 if (!array_key_exists($testPath, $processedFolders)) {
2080 if ((is_dir($testPath) && $deepSearch) || !is_dir($testPath)) {
2081 $errorLogs = array_merge($errorLogs, self::_scanForLogs($testPath, $deepSearch));
2082 }
2083 }
2084 }
2085 return $errorLogs;
2086 }
2087
2088 public static function outputErrorLog($path) {
2089 $errorLogs = self::getErrorLogs();
2090 if (!isset($errorLogs[$path])) { //Only allow error logs we've identified
2091 global $wp_query;
2092 $wp_query->set_404();
2093 status_header(404);
2094 nocache_headers();
2095
2096 $template = get_404_template();
2097 if ($template && file_exists($template)) {
2098 include($template);
2099 }
2100 exit;
2101 }
2102
2103 $fh = @fopen($path, 'r');
2104 if (!$fh) {
2105 status_header(503);
2106 nocache_headers();
2107 echo "503 Service Unavailable";
2108 exit;
2109 }
2110
2111 $headersOutputted = false;
2112 while (!feof($fh)) {
2113 $data = fread($fh, 1 * 1024 * 1024); //read 1 megs max per chunk
2114 if ($data === false) { //Handle the error where the file was reported readable but we can't actually read it
2115 status_header(503);
2116 nocache_headers();
2117 echo "503 Service Unavailable";
2118 exit;
2119 }
2120
2121 if (!$headersOutputted) {
2122 header('Content-Type: text/plain');
2123 header('Content-Disposition: attachment; filename="' . basename($path));
2124 $headersOutputted = true;
2125 }
2126 echo $data;
2127 }
2128 exit;
2129 }
2130 }