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