1: <?php
2:
3: 4: 5:
6: class Protector
7: {
8: public $mydirname;
9:
10: public $_conn;
11: public $_conf = array();
12: public $_conf_serialized = '';
13:
14: public $_bad_globals = array();
15:
16: public $message = '';
17: public $warning = false;
18: public $error = false;
19: public $_doubtful_requests = array();
20: public $_bigumbrella_doubtfuls = array();
21:
22: public $_dblayertrap_doubtfuls = array();
23: public $_dblayertrap_doubtful_needles = array(
24: 'information_schema',
25: 'select',
26: "'",
27: '"');
28:
29: public $_logged = false;
30:
31: public $_done_badext = false;
32: public $_done_intval = false;
33: public $_done_dotdot = false;
34: public $_done_nullbyte = false;
35: public $_done_contami = false;
36: public $_done_isocom = false;
37: public $_done_union = false;
38: public $_done_dos = false;
39:
40: public $_safe_badext = true;
41: public $_safe_contami = true;
42: public $_safe_isocom = true;
43: public $_safe_union = true;
44:
45: public $_spamcount_uri = 0;
46:
47: public $_should_be_banned_time0 = false;
48: public $_should_be_banned = false;
49:
50: public $_dos_stage;
51:
52: public $ip_matched_info;
53:
54: public $last_error_type = 'UNKNOWN';
55:
56: 57: 58:
59: protected function __construct()
60: {
61: $this->mydirname = 'protector';
62:
63:
64: $this->_conf_serialized = @file_get_contents($this->get_filepath4confighcache());
65: $this->_conf = @unserialize($this->_conf_serialized);
66: if (empty($this->_conf)) {
67: $this->_conf = array();
68: }
69:
70: if (!empty($this->_conf['global_disabled'])) {
71: return;
72: }
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85: $this->_bad_globals = array(
86: 'GLOBALS',
87: '_SESSION',
88: 'HTTP_SESSION_VARS',
89: '_GET',
90: 'HTTP_GET_VARS',
91: '_POST',
92: 'HTTP_POST_VARS',
93: '_COOKIE',
94: 'HTTP_COOKIE_VARS',
95: '_SERVER',
96: 'HTTP_SERVER_VARS',
97: '_REQUEST',
98: '_ENV',
99: '_FILES',
100: 'xoopsDB',
101: 'xoopsUser',
102: 'xoopsUserId',
103: 'xoopsUserGroups',
104: 'xoopsUserIsAdmin',
105: 'xoopsConfig',
106: 'xoopsOption',
107: 'xoopsModule',
108: 'xoopsModuleConfig');
109:
110: $this->_initial_recursive($_GET, 'G');
111: $this->_initial_recursive($_POST, 'P');
112: $this->_initial_recursive($_COOKIE, 'C');
113: }
114:
115: 116: 117: 118:
119: protected function _initial_recursive($val, $key)
120: {
121: if (is_array($val)) {
122: foreach ($val as $subkey => $subval) {
123:
124: if (in_array($subkey, $this->_bad_globals, true)) {
125: $this->message .= "Attempt to inject '$subkey' was found.\n";
126: $this->_safe_contami = false;
127: $this->last_error_type = 'CONTAMI';
128: }
129: $this->_initial_recursive($subval, $key . '_' . base64_encode($subkey));
130: }
131: } else {
132:
133: if (@$this->_conf['san_nullbyte'] && false !== strpos($val, chr(0))) {
134: $val = str_replace(chr(0), ' ', $val);
135: $this->replace_doubtful($key, $val);
136: $this->message .= "Injecting Null-byte '$val' found.\n";
137: $this->output_log('NullByte', 0, false, 32);
138:
139: }
140:
141:
142: if (preg_match('?[\s\'"`/]?', $val)) {
143: $this->_doubtful_requests["$key"] = $val;
144: }
145: }
146: }
147:
148: 149: 150:
151: public static function getInstance()
152: {
153: static $instance;
154: if (!isset($instance)) {
155: $instance = new Protector();
156: }
157:
158: return $instance;
159: }
160:
161: 162: 163:
164: public function updateConfFromDb()
165: {
166: $constpref = '_MI_' . strtoupper($this->mydirname);
167:
168: if (empty($this->_conn)) {
169: return false;
170: }
171:
172: $result = @mysqli_query($this->_conn, 'SELECT conf_name,conf_value FROM ' . XOOPS_DB_PREFIX . "_config WHERE conf_title like '" . $constpref . "%'");
173: if (!$result || mysqli_num_rows($result) < 5) {
174: return false;
175: }
176: $db_conf = array();
177: while (list($key, $val) = mysqli_fetch_row($result)) {
178: $db_conf[$key] = $val;
179: }
180: $db_conf_serialized = serialize($db_conf);
181:
182:
183: if ($db_conf_serialized != $this->_conf_serialized) {
184: $fp = fopen($this->get_filepath4confighcache(), 'w');
185: fwrite($fp, $db_conf_serialized);
186: fclose($fp);
187: $this->_conf = $db_conf;
188: }
189:
190: return true;
191: }
192:
193: 194: 195:
196: public function setConn($conn)
197: {
198: $this->_conn = $conn;
199: }
200:
201: 202: 203:
204: public function getConf()
205: {
206: return $this->_conf;
207: }
208:
209: 210: 211:
212: public function purge($redirect_to_top = false)
213: {
214: $this->purgeNoExit();
215:
216: if ($redirect_to_top) {
217: header('Location: ' . XOOPS_URL . '/');
218: exit;
219: } else {
220: $ret = $this->call_filter('prepurge_exit');
221: if ($ret == false) {
222: die('Protector detects attacking actions');
223: }
224: }
225: }
226:
227: public function purgeSession()
228: {
229:
230: if (isset($_SESSION)) {
231: foreach ($_SESSION as $key => $val) {
232: $_SESSION[$key] = '';
233: if (isset($GLOBALS[$key])) {
234: $GLOBALS[$key] = '';
235: }
236: }
237: }
238: }
239:
240: public function purgeCookies()
241: {
242: if (!headers_sent()) {
243:
244: setcookie('PHPSESSID', '', time() - 3600, '/', '', 0);
245: if (isset($_COOKIE[session_name()])) {
246: setcookie(session_name(), '', time() - 3600, '/', '', 0);
247: }
248:
249:
250: $xoops_cookie_path = defined('XOOPS_COOKIE_PATH') ? XOOPS_COOKIE_PATH : preg_replace('?http[s]{0,1}://[^/]+(/.*)$?', "$1", XOOPS_URL);
251: if ($xoops_cookie_path == XOOPS_URL) {
252: $xoops_cookie_path = '/';
253: }
254: setcookie($GLOBALS['xoopsConfig']['usercookie'], null, time() - 3600, '/', XOOPS_COOKIE_DOMAIN, 0);
255: setcookie($GLOBALS['xoopsConfig']['usercookie'], null, time() - 3600, '/');
256:
257: setcookie('autologin_uname', '', time() - 3600, $xoops_cookie_path, '', 0);
258: setcookie('autologin_pass', '', time() - 3600, $xoops_cookie_path, '', 0);
259: }
260: }
261:
262: public function purgeNoExit()
263: {
264: $this->purgeSession();
265: $this->purgeCookies();
266: }
267:
268: public function deactivateCurrentUser()
269: {
270:
271: global $xoopsUser;
272:
273: if (is_object($xoopsUser)) {
274:
275: $userHandler = xoops_getHandler('user');
276: $xoopsUser->setVar('level', 0);
277: $actkey = substr(md5(uniqid(mt_rand(), 1)), 0, 8);
278: $xoopsUser->setVar('actkey', $actkey);
279: $userHandler->insert($xoopsUser);
280: }
281: $this->purgeNoExit();
282: }
283:
284: 285: 286: 287: 288: 289: 290: 291:
292: public function output_log($type = 'UNKNOWN', $uid = 0, $unique_check = false, $level = 1)
293: {
294: if ($this->_logged) {
295: return true;
296: }
297:
298: if (!($this->_conf['log_level'] & $level)) {
299: return true;
300: }
301:
302: if (empty($this->_conn)) {
303: $this->_conn = new mysqli(XOOPS_DB_HOST, XOOPS_DB_USER, XOOPS_DB_PASS);
304: if (0 !== $this->_conn->connect_errno) {
305: die('db connection failed.');
306: }
307: if (!mysqli_select_db($this->_conn, XOOPS_DB_NAME)) {
308: die('db selection failed.');
309: }
310: }
311:
312: $ip = \Xmf\IPAddress::fromRequest()->asReadable();
313: $agent = @$_SERVER['HTTP_USER_AGENT'];
314:
315: if ($unique_check) {
316: $result = mysqli_query($this->_conn, 'SELECT ip,type FROM ' . XOOPS_DB_PREFIX . '_' . $this->mydirname . '_log ORDER BY timestamp DESC LIMIT 1');
317: list($last_ip, $last_type) = mysqli_fetch_row($result);
318: if ($last_ip == $ip && $last_type == $type) {
319: $this->_logged = true;
320:
321: return true;
322: }
323: }
324:
325: mysqli_query(
326: $this->_conn,
327: 'INSERT INTO ' . XOOPS_DB_PREFIX . '_' . $this->mydirname . "_log SET ip='"
328: . mysqli_real_escape_string($this->_conn, $ip) . "',agent='"
329: . mysqli_real_escape_string($this->_conn, $agent) . "',type='"
330: . mysqli_real_escape_string($this->_conn, $type) . "',description='"
331: . mysqli_real_escape_string($this->_conn, $this->message) . "',uid='"
332: . (int)$uid . "',timestamp=NOW()"
333: );
334: $this->_logged = true;
335:
336: return true;
337: }
338:
339: 340: 341: 342: 343:
344: public function write_file_bwlimit($expire)
345: {
346: $expire = min((int)$expire, time() + 300);
347:
348: $fp = @fopen($this->get_filepath4bwlimit(), 'w');
349: if ($fp) {
350: @flock($fp, LOCK_EX);
351: fwrite($fp, $expire . "\n");
352: @flock($fp, LOCK_UN);
353: fclose($fp);
354:
355: return true;
356: } else {
357: return false;
358: }
359: }
360:
361: 362: 363:
364: public function get_bwlimit()
365: {
366: list($expire) = @file(Protector::get_filepath4bwlimit());
367: $expire = min((int)$expire, time() + 300);
368:
369: return $expire;
370: }
371:
372: 373: 374:
375: public static function get_filepath4bwlimit()
376: {
377: return XOOPS_TRUST_PATH . '/modules/protector/configs/bwlimit' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
378: }
379:
380: 381: 382: 383: 384:
385: public function write_file_badips($bad_ips)
386: {
387: asort($bad_ips);
388:
389: $fp = @fopen($this->get_filepath4badips(), 'w');
390: if ($fp) {
391: @flock($fp, LOCK_EX);
392: fwrite($fp, serialize($bad_ips) . "\n");
393: @flock($fp, LOCK_UN);
394: fclose($fp);
395:
396: return true;
397: } else {
398: return false;
399: }
400: }
401:
402: 403: 404: 405: 406: 407:
408: public function register_bad_ips($jailed_time = 0, $ip = null)
409: {
410: if (empty($ip)) {
411: $ip = \Xmf\IPAddress::fromRequest()->asReadable();
412: }
413: if (empty($ip)) {
414: return false;
415: }
416:
417: $bad_ips = $this->get_bad_ips(true);
418: $bad_ips[$ip] = $jailed_time ?: 0x7fffffff;
419:
420: return $this->write_file_badips($bad_ips);
421: }
422:
423: 424: 425: 426: 427:
428: public function get_bad_ips($with_jailed_time = false)
429: {
430: list($bad_ips_serialized) = @file(Protector::get_filepath4badips());
431: $bad_ips = empty($bad_ips_serialized) ? array() : @unserialize($bad_ips_serialized);
432: if (!is_array($bad_ips) || isset($bad_ips[0])) {
433: $bad_ips = array();
434: }
435:
436:
437: $pos = 0;
438: foreach ($bad_ips as $bad_ip => $jailed_time) {
439: if ($jailed_time >= time()) {
440: break;
441: }
442: ++$pos;
443: }
444: $bad_ips = array_slice($bad_ips, $pos);
445:
446: if ($with_jailed_time) {
447: return $bad_ips;
448: } else {
449: return array_keys($bad_ips);
450: }
451: }
452:
453: 454: 455:
456: public static function get_filepath4badips()
457: {
458: return XOOPS_TRUST_PATH . '/modules/protector/configs/badips' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
459: }
460:
461: 462: 463: 464: 465:
466: public function get_group1_ips($with_info = false)
467: {
468: list($group1_ips_serialized) = @file(Protector::get_filepath4group1ips());
469: $group1_ips = empty($group1_ips_serialized) ? array() : @unserialize($group1_ips_serialized);
470: if (!is_array($group1_ips)) {
471: $group1_ips = array();
472: }
473:
474: if ($with_info) {
475: $group1_ips = array_flip($group1_ips);
476: }
477:
478: return $group1_ips;
479: }
480:
481: 482: 483:
484: public static function get_filepath4group1ips()
485: {
486: return XOOPS_TRUST_PATH . '/modules/protector/configs/group1ips' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
487: }
488:
489: 490: 491:
492: public function get_filepath4confighcache()
493: {
494: return XOOPS_TRUST_PATH . '/modules/protector/configs/configcache' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
495: }
496:
497: 498: 499: 500: 501:
502: public function ip_match($ips)
503: {
504: $requestIp = \Xmf\IPAddress::fromRequest()->asReadable();
505: if (false === $requestIp) {
506: $this->ip_matched_info = null;
507: return false;
508: }
509: foreach ($ips as $ip => $info) {
510: if ($ip) {
511: switch (strtolower(substr($ip, -1))) {
512: case '.' :
513: case ':' :
514:
515: if (substr($requestIp, 0, strlen($ip)) == $ip) {
516: $this->ip_matched_info = $info;
517: return true;
518: }
519: break;
520: case '0' :
521: case '1' :
522: case '2' :
523: case '3' :
524: case '4' :
525: case '5' :
526: case '6' :
527: case '7' :
528: case '8' :
529: case '9' :
530: case 'a' :
531: case 'b' :
532: case 'c' :
533: case 'd' :
534: case 'e' :
535: case 'f' :
536:
537: if ($requestIp == $ip) {
538: $this->ip_matched_info = $info;
539: return true;
540: }
541: break;
542: default :
543:
544: if (@preg_match($ip, $requestIp)) {
545: $this->ip_matched_info = $info;
546: return true;
547: }
548: break;
549: }
550: }
551: }
552: $this->ip_matched_info = null;
553: return false;
554: }
555:
556: 557: 558: 559: 560:
561: public function deny_by_htaccess($ip = null)
562: {
563: if (empty($ip)) {
564: $ip = \Xmf\IPAddress::fromRequest()->asReadable();
565: }
566: if (empty($ip)) {
567: return false;
568: }
569: if (!function_exists('file_get_contents')) {
570: return false;
571: }
572:
573: $target_htaccess = XOOPS_ROOT_PATH . '/.htaccess';
574: $backup_htaccess = XOOPS_ROOT_PATH . '/uploads/.htaccess.bak';
575:
576: $ht_body = file_get_contents($target_htaccess);
577:
578:
579: if ($ht_body && !file_exists($backup_htaccess)) {
580: $fw = fopen($backup_htaccess, 'w');
581: fwrite($fw, $ht_body);
582: fclose($fw);
583: }
584:
585:
586: if (!$ht_body && file_exists($backup_htaccess)) {
587: $ht_body = file_get_contents($backup_htaccess);
588: }
589:
590:
591: if ($ht_body === false) {
592: $ht_body = '';
593: }
594:
595: if (preg_match("/^(.*)#PROTECTOR#\s+(DENY FROM .*)\n#PROTECTOR#\n(.*)$/si", $ht_body, $regs)) {
596: if (substr($regs[2], -strlen($ip)) == $ip) {
597: return true;
598: }
599: $new_ht_body = $regs[1] . "#PROTECTOR#\n" . $regs[2] . " $ip\n#PROTECTOR#\n" . $regs[3];
600: } else {
601: $new_ht_body = "#PROTECTOR#\nDENY FROM $ip\n#PROTECTOR#\n" . $ht_body;
602: }
603:
604:
605:
606: $fw = fopen($target_htaccess, 'w');
607: @flock($fw, LOCK_EX);
608: fwrite($fw, $new_ht_body);
609: @flock($fw, LOCK_UN);
610: fclose($fw);
611:
612: return true;
613: }
614:
615: 616: 617:
618: public function getDblayertrapDoubtfuls()
619: {
620: return $this->_dblayertrap_doubtfuls;
621: }
622:
623: 624: 625: 626:
627: protected function _dblayertrap_check_recursive($val)
628: {
629: if (is_array($val)) {
630: foreach ($val as $subval) {
631: $this->_dblayertrap_check_recursive($subval);
632: }
633: } else {
634: if (strlen($val) < 6) {
635: return null;
636: }
637: $val = get_magic_quotes_gpc() ? stripslashes($val) : $val;
638: foreach ($this->_dblayertrap_doubtful_needles as $needle) {
639: if (false !== stripos($val, $needle)) {
640: $this->_dblayertrap_doubtfuls[] = $val;
641: }
642: }
643: }
644: }
645:
646: 647: 648: 649:
650: public function dblayertrap_init($force_override = false)
651: {
652: if (!empty($GLOBALS['xoopsOption']['nocommon']) || defined('_LEGACY_PREVENT_EXEC_COMMON_') || defined('_LEGACY_PREVENT_LOAD_CORE_')) {
653: return null;
654: }
655:
656: $this->_dblayertrap_doubtfuls = array();
657: $this->_dblayertrap_check_recursive($_GET);
658: $this->_dblayertrap_check_recursive($_POST);
659: $this->_dblayertrap_check_recursive($_COOKIE);
660: if (empty($this->_conf['dblayertrap_wo_server'])) {
661: $this->_dblayertrap_check_recursive($_SERVER);
662: }
663:
664: if (!empty($this->_dblayertrap_doubtfuls) || $force_override) {
665: @define('XOOPS_DB_ALTERNATIVE', 'ProtectorMysqlDatabase');
666: require_once dirname(__DIR__) . '/class/ProtectorMysqlDatabase.class.php';
667: }
668: }
669:
670: 671: 672:
673: protected function _bigumbrella_check_recursive($val)
674: {
675: if (is_array($val)) {
676: foreach ($val as $subval) {
677: $this->_bigumbrella_check_recursive($subval);
678: }
679: } else {
680: if (preg_match('/[<\'"].{15}/s', $val, $regs)) {
681: $this->_bigumbrella_doubtfuls[] = $regs[0];
682: }
683: }
684: }
685:
686: public function bigumbrella_init()
687: {
688: $this->_bigumbrella_doubtfuls = array();
689: $this->_bigumbrella_check_recursive($_GET);
690: $this->_bigumbrella_check_recursive(@$_SERVER['PHP_SELF']);
691:
692: if (!empty($this->_bigumbrella_doubtfuls)) {
693: ob_start(array($this, 'bigumbrella_outputcheck'));
694: }
695: }
696:
697: 698: 699: 700: 701:
702: public function bigumbrella_outputcheck($s)
703: {
704: if (defined('BIGUMBRELLA_DISABLED')) {
705: return $s;
706: }
707:
708: if (function_exists('headers_list')) {
709: foreach (headers_list() as $header) {
710: if (false !== stripos($header, 'Content-Type:') && false === stripos($header, 'text/html')) {
711: return $s;
712: }
713: }
714: }
715:
716: if (!is_array($this->_bigumbrella_doubtfuls)) {
717: return 'bigumbrella injection found.';
718: }
719:
720: foreach ($this->_bigumbrella_doubtfuls as $doubtful) {
721: if (false !== strpos($s, $doubtful)) {
722: return 'XSS found by Protector.';
723: }
724: }
725:
726: return $s;
727: }
728:
729: 730: 731:
732: public function intval_allrequestsendid()
733: {
734: global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS;
735:
736: if ($this->_done_intval) {
737: return true;
738: } else {
739: $this->_done_intval = true;
740: }
741:
742: foreach ($_GET as $key => $val) {
743: if (substr($key, -2) === 'id' && !is_array($_GET[$key])) {
744: $newval = preg_replace('/[^0-9a-zA-Z_-]/', '', $val);
745: $_GET[$key] = $HTTP_GET_VARS[$key] = $newval;
746: if ($_REQUEST[$key] == $_GET[$key]) {
747: $_REQUEST[$key] = $newval;
748: }
749: }
750: }
751: foreach ($_POST as $key => $val) {
752: if (substr($key, -2) === 'id' && !is_array($_POST[$key])) {
753: $newval = preg_replace('/[^0-9a-zA-Z_-]/', '', $val);
754: $_POST[$key] = $HTTP_POST_VARS[$key] = $newval;
755: if ($_REQUEST[$key] == $_POST[$key]) {
756: $_REQUEST[$key] = $newval;
757: }
758: }
759: }
760: foreach ($_COOKIE as $key => $val) {
761: if (substr($key, -2) === 'id' && !is_array($_COOKIE[$key])) {
762: $newval = preg_replace('/[^0-9a-zA-Z_-]/', '', $val);
763: $_COOKIE[$key] = $HTTP_COOKIE_VARS[$key] = $newval;
764: if ($_REQUEST[$key] == $_COOKIE[$key]) {
765: $_REQUEST[$key] = $newval;
766: }
767: }
768: }
769:
770: return true;
771: }
772:
773: 774: 775:
776: public function eliminate_dotdot()
777: {
778: global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS;
779:
780: if ($this->_done_dotdot) {
781: return true;
782: } else {
783: $this->_done_dotdot = true;
784: }
785:
786: foreach ($_GET as $key => $val) {
787: if (is_array($_GET[$key])) {
788: continue;
789: }
790: if (substr(trim($val), 0, 3) === '../' || false !== strpos($val, '/../')) {
791: $this->last_error_type = 'DirTraversal';
792: $this->message .= "Directory Traversal '$val' found.\n";
793: $this->output_log($this->last_error_type, 0, false, 64);
794: $sanitized_val = str_replace(chr(0), '', $val);
795: if (substr($sanitized_val, -2) !== ' .') {
796: $sanitized_val .= ' .';
797: }
798: $_GET[$key] = $HTTP_GET_VARS[$key] = $sanitized_val;
799: if ($_REQUEST[$key] == $_GET[$key]) {
800: $_REQUEST[$key] = $sanitized_val;
801: }
802: }
803: }
804:
805: 806: 807: 808: 809: 810: 811: 812: 813: 814: 815: 816: 817: 818: 819: 820: 821: 822: 823: 824: 825: 826: 827: 828: 829: 830: 831: 832:
833:
834: return true;
835: }
836:
837: 838: 839: 840: 841: 842:
843: public function &get_ref_from_base64index(&$current, $indexes)
844: {
845: foreach ($indexes as $index) {
846: $index = base64_decode($index);
847: if (!is_array($current)) {
848: return false;
849: }
850: $current =& $current[$index];
851: }
852:
853: return $current;
854: }
855:
856: 857: 858: 859:
860: public function replace_doubtful($key, $val)
861: {
862: global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS;
863:
864: $index_expression = '';
865: $indexes = explode('_', $key);
866: $base_array = array_shift($indexes);
867:
868: switch ($base_array) {
869: case 'G' :
870: $main_ref =& $this->get_ref_from_base64index($_GET, $indexes);
871: $legacy_ref =& $this->get_ref_from_base64index($HTTP_GET_VARS, $indexes);
872: break;
873: case 'P' :
874: $main_ref =& $this->get_ref_from_base64index($_POST, $indexes);
875: $legacy_ref =& $this->get_ref_from_base64index($HTTP_POST_VARS, $indexes);
876: break;
877: case 'C' :
878: $main_ref =& $this->get_ref_from_base64index($_COOKIE, $indexes);
879: $legacy_ref =& $this->get_ref_from_base64index($HTTP_COOKIE_VARS, $indexes);
880: break;
881: default :
882: exit;
883: }
884: if (!isset($main_ref)) {
885: exit;
886: }
887: $request_ref =& $this->get_ref_from_base64index($_REQUEST, $indexes);
888: if ($request_ref !== false && $main_ref == $request_ref) {
889: $request_ref = $val;
890: }
891: $main_ref = $val;
892: $legacy_ref = $val;
893: }
894:
895: 896: 897:
898: public function check_uploaded_files()
899: {
900: if ($this->_done_badext) {
901: return $this->_safe_badext;
902: } else {
903: $this->_done_badext = true;
904: }
905:
906:
907: $bad_extensions = array('php', 'phtml', 'phtm', 'php3', 'php4', 'cgi', 'pl', 'asp');
908:
909: $image_extensions = array(
910: 1 => 'gif',
911: 2 => 'jpg',
912: 3 => 'png',
913: 4 => 'swf',
914: 5 => 'psd',
915: 6 => 'bmp',
916: 7 => 'tif',
917: 8 => 'tif',
918: 9 => 'jpc',
919: 10 => 'jp2',
920: 11 => 'jpx',
921: 12 => 'jb2',
922: 13 => 'swc',
923: 14 => 'iff',
924: 15 => 'wbmp',
925: 16 => 'xbm');
926:
927: foreach ($_FILES as $_file) {
928: if (!empty($_file['error'])) {
929: continue;
930: }
931: if (!empty($_file['name']) && is_string($_file['name'])) {
932: $ext = strtolower(substr(strrchr($_file['name'], '.'), 1));
933: if ($ext === 'jpeg') {
934: $ext = 'jpg';
935: } elseif ($ext === 'tiff') {
936: $ext = 'tif';
937: }
938:
939:
940: if (count(explode('.', str_replace('.tar.gz', '.tgz', $_file['name']))) > 2) {
941: $this->message .= "Attempt to multiple dot file {$_file['name']}.\n";
942: $this->_safe_badext = false;
943: $this->last_error_type = 'UPLOAD';
944: }
945:
946:
947: if (in_array($ext, $bad_extensions)) {
948: $this->message .= "Attempt to upload {$_file['name']}.\n";
949: $this->_safe_badext = false;
950: $this->last_error_type = 'UPLOAD';
951: }
952:
953:
954: if (in_array($ext, $image_extensions)) {
955: $image_attributes = @getimagesize($_file['tmp_name']);
956: if ($image_attributes === false && is_uploaded_file($_file['tmp_name'])) {
957:
958: $temp_file = XOOPS_ROOT_PATH . '/uploads/protector_upload_temporary' . md5(time());
959: move_uploaded_file($_file['tmp_name'], $temp_file);
960: $image_attributes = @getimagesize($temp_file);
961: @unlink($temp_file);
962: }
963:
964: if ($image_attributes === false || $image_extensions[(int)$image_attributes[2]] != $ext) {
965: $this->message .= "Attempt to upload camouflaged image file {$_file['name']}.\n";
966: $this->_safe_badext = false;
967: $this->last_error_type = 'UPLOAD';
968: }
969: }
970: }
971: }
972:
973: return $this->_safe_badext;
974: }
975:
976: 977: 978:
979: public function check_contami_systemglobals()
980: {
981: 982:
983:
984: 985: 986: 987: 988: 989: 990:
991:
992: return $this->_safe_contami;
993: }
994:
995: 996: 997: 998: 999:
1000: public function check_sql_isolatedcommentin($sanitize = true)
1001: {
1002: if ($this->_done_isocom) {
1003: return $this->_safe_isocom;
1004: } else {
1005: $this->_done_isocom = true;
1006: }
1007:
1008: foreach ($this->_doubtful_requests as $key => $val) {
1009: $str = $val;
1010: while ($str = strstr($str, '/*')) {
1011: $str = strstr(substr($str, 2), '*/');
1012: if ($str === false) {
1013: $this->message .= "Isolated comment-in found. ($val)\n";
1014: if ($sanitize) {
1015: $this->replace_doubtful($key, $val . '*/');
1016: }
1017: $this->_safe_isocom = false;
1018: $this->last_error_type = 'ISOCOM';
1019: }
1020: }
1021: }
1022:
1023: return $this->_safe_isocom;
1024: }
1025:
1026: 1027: 1028: 1029: 1030:
1031: public function check_sql_union($sanitize = true)
1032: {
1033: if ($this->_done_union) {
1034: return $this->_safe_union;
1035: } else {
1036: $this->_done_union = true;
1037: }
1038:
1039: foreach ($this->_doubtful_requests as $key => $val) {
1040: $str = str_replace(array('/*', '*/'), '', preg_replace('?/\*.+\*/?sU', '', $val));
1041: if (preg_match('/\sUNION\s+(ALL|SELECT)/i', $str)) {
1042: $this->message .= "Pattern like SQL injection found. ($val)\n";
1043: if ($sanitize) {
1044:
1045: $this->replace_doubtful($key, str_ireplace('union', 'uni-on', $val));
1046: }
1047: $this->_safe_union = false;
1048: $this->last_error_type = 'UNION';
1049: }
1050: }
1051:
1052: return $this->_safe_union;
1053: }
1054:
1055: 1056: 1057: 1058: 1059:
1060: public function stopforumspam($uid)
1061: {
1062: if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
1063: return false;
1064: }
1065:
1066: $result = $this->stopForumSpamLookup(
1067: isset($_POST['email']) ? $_POST['email'] : null,
1068: $_SERVER['REMOTE_ADDR'],
1069: isset($_POST['uname']) ? $_POST['uname'] : null
1070: );
1071:
1072: if (false === $result) {
1073: return false;
1074: }
1075:
1076: $spammer = false;
1077: if (isset($result['email']) && isset($result['email']['lastseen'])) {
1078: $spammer = true;
1079: }
1080:
1081: if (isset($result['ip']) && isset($result['ip']['lastseen'])) {
1082: $last = strtotime($result['ip']['lastseen']);
1083: $oneMonth = 60 * 60 * 24 * 31;
1084: $oneMonthAgo = time() - $oneMonth;
1085: if ($last > $oneMonthAgo) {
1086: $spammer = true;
1087: }
1088: }
1089:
1090: if (!$spammer) {
1091: return false;
1092: }
1093:
1094: $this->last_error_type = 'SPAMMER POST';
1095:
1096: switch ($this->_conf['stopforumspam_action']) {
1097: default :
1098: case 'log' :
1099: break;
1100: case 'san' :
1101: $_POST = array();
1102: $this->message .= 'POST deleted for IP:' . $_SERVER['REMOTE_ADDR'];
1103: break;
1104: case 'biptime0' :
1105: $_POST = array();
1106: $this->message .= 'BAN and POST deleted for IP:' . $_SERVER['REMOTE_ADDR'];
1107: $this->_should_be_banned_time0 = true;
1108: break;
1109: case 'bip' :
1110: $_POST = array();
1111: $this->message .= 'Ban and POST deleted for IP:' . $_SERVER['REMOTE_ADDR'];
1112: $this->_should_be_banned = true;
1113: break;
1114: }
1115:
1116: $this->output_log($this->last_error_type, $uid, false, 16);
1117:
1118: return true;
1119: }
1120:
1121: public function stopForumSpamLookup($email, $ip, $username)
1122: {
1123: if (!function_exists('curl_init')) {
1124: return false;
1125: }
1126:
1127: $query = '';
1128: $query .= (empty($ip)) ? '' : '&ip=' . $ip;
1129: $query .= (empty($email)) ? '' : '&email=' . $email;
1130: $query .= (empty($username)) ? '' : '&username=' . $username;
1131:
1132: if (empty($query)) {
1133: return false;
1134: }
1135:
1136: $url = 'http://www.stopforumspam.com/api?f=json' . $query;
1137: $ch = curl_init();
1138: curl_setopt($ch, CURLOPT_URL, $url);
1139: curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1140: curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
1141: $result = json_decode(curl_exec($ch), true);
1142: curl_close($ch);
1143:
1144: return $result;
1145: }
1146:
1147: 1148: 1149: 1150: 1151: 1152:
1153: public function check_dos_attack($uid = 0, $can_ban = false)
1154: {
1155: global $xoopsDB;
1156:
1157: if ($this->_done_dos) {
1158: return true;
1159: }
1160:
1161: $ip = \Xmf\IPAddress::fromRequest();
1162: if (false === $ip->asReadable()) {
1163: return true;
1164: }
1165: $uri = @$_SERVER['REQUEST_URI'];
1166:
1167:
1168: $parts = parse_url($uri);
1169: $basename = empty($parts['path']) ? '' : basename($parts['path']);
1170: if (('ajaxfineupload.php' === $basename) && ('' !== \Xmf\Request::getHeader('Authorization', ''))) {
1171: return true;
1172: }
1173:
1174: $ip4sql = $xoopsDB->quote($ip->asReadable());
1175: $uri4sql = $xoopsDB->quote($uri);
1176:
1177:
1178: $result = $xoopsDB->queryF(
1179: 'DELETE FROM ' . $xoopsDB->prefix($this->mydirname . '_access')
1180: . ' WHERE expire < UNIX_TIMESTAMP()'
1181: );
1182:
1183:
1184: if ($result === false) {
1185: $this->_done_dos = true;
1186:
1187: return true;
1188: }
1189:
1190:
1191: $sql4insertlog = 'INSERT INTO ' . $xoopsDB->prefix($this->mydirname . '_access')
1192: . " SET ip={$ip4sql}, request_uri={$uri4sql},"
1193: . " expire=UNIX_TIMESTAMP()+'" . (int)$this->_conf['dos_expire'] . "'";
1194:
1195:
1196: if (@$this->_conf['bwlimit_count'] >= 10) {
1197: $result = $xoopsDB->query('SELECT COUNT(*) FROM ' . $xoopsDB->prefix($this->mydirname . '_access'));
1198: list($bw_count) = $xoopsDB->fetchRow($result);
1199: if ($bw_count > $this->_conf['bwlimit_count']) {
1200: $this->write_file_bwlimit(time() + $this->_conf['dos_expire']);
1201: }
1202: }
1203:
1204:
1205: $result = $xoopsDB->query(
1206: 'SELECT COUNT(*) FROM ' . $xoopsDB->prefix($this->mydirname . '_access')
1207: . " WHERE ip={$ip4sql} AND request_uri={$uri4sql}");
1208: list($f5_count) = $xoopsDB->fetchRow($result);
1209: if ($f5_count > $this->_conf['dos_f5count']) {
1210:
1211:
1212: $xoopsDB->queryF($sql4insertlog);
1213:
1214:
1215:
1216:
1217:
1218: $ret = $this->call_filter('f5attack_overrun');
1219:
1220:
1221: $this->_done_dos = true;
1222: $this->last_error_type = 'DoS';
1223: switch ($this->_conf['dos_f5action']) {
1224: default :
1225: case 'exit' :
1226: $this->output_log($this->last_error_type, $uid, true, 16);
1227: exit;
1228: case 'none' :
1229: $this->output_log($this->last_error_type, $uid, true, 16);
1230:
1231: return true;
1232: case 'biptime0' :
1233: if ($can_ban) {
1234: $this->register_bad_ips(time() + $this->_conf['banip_time0']);
1235: }
1236: break;
1237: case 'bip' :
1238: if ($can_ban) {
1239: $this->register_bad_ips();
1240: }
1241: break;
1242: case 'hta' :
1243: if ($can_ban) {
1244: $this->deny_by_htaccess();
1245: }
1246: break;
1247: case 'sleep' :
1248: sleep(5);
1249: break;
1250: }
1251:
1252: return false;
1253: }
1254:
1255:
1256: if (trim($this->_conf['dos_crsafe']) != '' && preg_match($this->_conf['dos_crsafe'], @$_SERVER['HTTP_USER_AGENT'])) {
1257:
1258: $this->_done_dos = true;
1259:
1260: return true;
1261: }
1262:
1263:
1264: $result = $xoopsDB->query(
1265: 'SELECT COUNT(*) FROM ' . $xoopsDB->prefix($this->mydirname . '_access') . " WHERE ip={$ip4sql}"
1266: );
1267: list($crawler_count) = $xoopsDB->fetchRow($result);
1268:
1269:
1270: $xoopsDB->queryF($sql4insertlog);
1271:
1272: if ($crawler_count > $this->_conf['dos_crcount']) {
1273:
1274:
1275: $ret = $this->call_filter('crawler_overrun');
1276:
1277:
1278: $this->_done_dos = true;
1279: $this->last_error_type = 'CRAWLER';
1280: switch ($this->_conf['dos_craction']) {
1281: default :
1282: case 'exit' :
1283: $this->output_log($this->last_error_type, $uid, true, 16);
1284: exit;
1285: case 'none' :
1286: $this->output_log($this->last_error_type, $uid, true, 16);
1287:
1288: return true;
1289: case 'biptime0' :
1290: if ($can_ban) {
1291: $this->register_bad_ips(time() + $this->_conf['banip_time0']);
1292: }
1293: break;
1294: case 'bip' :
1295: if ($can_ban) {
1296: $this->register_bad_ips();
1297: }
1298: break;
1299: case 'hta' :
1300: if ($can_ban) {
1301: $this->deny_by_htaccess();
1302: }
1303: break;
1304: case 'sleep' :
1305: sleep(5);
1306: break;
1307: }
1308:
1309: return false;
1310: }
1311:
1312: return true;
1313: }
1314:
1315:
1316: 1317: 1318:
1319: public function check_brute_force()
1320: {
1321: global $xoopsDB;
1322:
1323: $ip = \Xmf\IPAddress::fromRequest();
1324: if (false === $ip->asReadable()) {
1325: return true;
1326: }
1327: $uri = @$_SERVER['REQUEST_URI'];
1328: $ip4sql = $xoopsDB->quote($ip->asReadable());
1329: $uri4sql = $xoopsDB->quote($uri);
1330:
1331: $victim_uname = empty($_COOKIE['autologin_uname']) ? $_POST['uname'] : $_COOKIE['autologin_uname'];
1332:
1333: if ($victim_uname === 'deleted') {
1334: return null;
1335: }
1336: $mal4sql = $xoopsDB->quote("BRUTE FORCE: $victim_uname");
1337:
1338:
1339: $result = $xoopsDB->queryF(
1340: 'DELETE FROM ' . $xoopsDB->prefix($this->mydirname . '_access') . ' WHERE expire < UNIX_TIMESTAMP()'
1341: );
1342:
1343:
1344: $sql4insertlog = 'INSERT INTO ' . $xoopsDB->prefix($this->mydirname . '_access')
1345: . " SET ip={$ip4sql}, request_uri={$uri4sql}, malicious_actions={$mal4sql}, expire=UNIX_TIMESTAMP()+600";
1346:
1347:
1348: $result = $xoopsDB->query(
1349: 'SELECT COUNT(*) FROM ' . $xoopsDB->prefix($this->mydirname . '_access')
1350: . " WHERE ip={$ip4sql} AND malicious_actions like 'BRUTE FORCE:%'"
1351: );
1352: list($bf_count) = $xoopsDB->fetchRow($result);
1353: if ($bf_count > $this->_conf['bf_count']) {
1354: $this->register_bad_ips(time() + $this->_conf['banip_time0']);
1355: $this->last_error_type = 'BruteForce';
1356: $this->message .= "Trying to login as '" . addslashes($victim_uname) . "' found.\n";
1357: $this->output_log('BRUTE FORCE', 0, true, 1);
1358: $ret = $this->call_filter('bruteforce_overrun');
1359: if ($ret == false) {
1360: exit;
1361: }
1362: }
1363:
1364: $xoopsDB->queryF($sql4insertlog);
1365: return null;
1366: }
1367:
1368: 1369: 1370:
1371: protected function _spam_check_point_recursive($val)
1372: {
1373: if (is_array($val)) {
1374: foreach ($val as $subval) {
1375: $this->_spam_check_point_recursive($subval);
1376: }
1377: } else {
1378:
1379: $path_array = parse_url(XOOPS_URL);
1380: $http_host = empty($path_array['host']) ? 'www.xoops.org' : $path_array['host'];
1381:
1382:
1383: $count = -1;
1384: foreach (preg_split('#https?\:\/\/#i', $val) as $fragment) {
1385: if (strncmp($fragment, $http_host, strlen($http_host)) !== 0) {
1386: ++$count;
1387: }
1388: }
1389: if ($count > 0) {
1390: $this->_spamcount_uri += $count;
1391: }
1392:
1393:
1394: $this->_spamcount_uri += count(preg_split('/\[url=(?!http|\\"http|\\\'http|' . $http_host . ')/i', $val)) - 1;
1395: }
1396: }
1397:
1398: 1399: 1400: 1401:
1402: public function spam_check($points4deny, $uid)
1403: {
1404: $this->_spamcount_uri = 0;
1405: $this->_spam_check_point_recursive($_POST);
1406:
1407: if ($this->_spamcount_uri >= $points4deny) {
1408: $this->message .= @$_SERVER['REQUEST_URI'] . " SPAM POINT: $this->_spamcount_uri\n";
1409: $this->output_log('URI SPAM', $uid, false, 128);
1410: $ret = $this->call_filter('spamcheck_overrun');
1411: if ($ret == false) {
1412: exit;
1413: }
1414: }
1415: }
1416:
1417: public function disable_features()
1418: {
1419: global $HTTP_POST_VARS, $HTTP_GET_VARS, $HTTP_COOKIE_VARS;
1420:
1421:
1422: $error_reporting_level = error_reporting(0);
1423:
1424:
1425:
1426:
1427: if ($this->_conf['disable_features'] & 1) {
1428:
1429:
1430: if (
1431: substr(@$_SERVER['SCRIPT_NAME'], -10) === 'xmlrpc.php'
1432: ) {
1433: $this->output_log('xmlrpc', 0, true, 1);
1434: exit;
1435: }
1436:
1437:
1438: if ($_POST['uname'] === '0' || $_COOKIE['autologin_pass'] === '0') {
1439: $this->output_log('CRITERIA');
1440: exit;
1441: }
1442: }
1443:
1444:
1445:
1446:
1447: if ($this->_conf['disable_features'] & 1024) {
1448:
1449:
1450: if (false === stripos(@$_SERVER['SCRIPT_NAME'], 'modules')) {
1451:
1452: if (substr(@$_SERVER['SCRIPT_NAME'], -8) === 'misc.php' && ($_GET['type'] === 'debug' || $_POST['type'] === 'debug') && !preg_match('/^dummy_\d+\.html$/', $_GET['file'])) {
1453: $this->output_log('misc debug');
1454: exit;
1455: }
1456:
1457:
1458: if (substr(@$_SERVER['SCRIPT_NAME'], -8) === 'misc.php' && ($_GET['type'] === 'smilies' || $_POST['type'] === 'smilies') && !preg_match('/^[0-9a-z_]*$/i', $_GET['target'])) {
1459: $this->output_log('misc smilies');
1460: exit;
1461: }
1462:
1463:
1464: if (substr(@$_SERVER['SCRIPT_NAME'], -12) === 'edituser.php' && $_POST['op'] === 'avatarchoose' && false !== strpos($_POST['user_avatar'], '..')) {
1465: $this->output_log('edituser avatarchoose');
1466: exit;
1467: }
1468: }
1469:
1470:
1471: if (substr(@$_SERVER['SCRIPT_NAME'], -24) === 'modules/system/admin.php' && ($_GET['fct'] === 'findusers' || $_POST['fct'] === 'findusers')) {
1472: foreach ($_POST as $key => $val) {
1473: if (false !== strpos($key, "'") || false !== strpos($val, "'")) {
1474: $this->output_log('findusers');
1475: exit;
1476: }
1477: }
1478: }
1479:
1480:
1481:
1482: if (substr(@$_SERVER['SCRIPT_NAME'], -23) === 'modules/news/submit.php' && isset($_POST['preview']) && strpos(@$_SERVER['HTTP_REFERER'], XOOPS_URL . '/modules/news/submit.php') !== 0) {
1483: $HTTP_POST_VARS['nohtml'] = $_POST['nohtml'] = 1;
1484: }
1485:
1486: if (substr(@$_SERVER['SCRIPT_NAME'], -28) === 'modules/news/admin/index.php' && ($_POST['op'] === 'preview' || $_GET['op'] === 'preview') && strpos(@$_SERVER['HTTP_REFERER'], XOOPS_URL . '/modules/news/admin/index.php') !== 0) {
1487: $HTTP_POST_VARS['nohtml'] = $_POST['nohtml'] = 1;
1488: }
1489:
1490: if (isset($_POST['com_dopreview']) && false === strpos(substr(@$_SERVER['HTTP_REFERER'], -16), 'comment_post.php')) {
1491: $HTTP_POST_VARS['dohtml'] = $_POST['dohtml'] = 0;
1492: }
1493:
1494: if (substr(@$_SERVER['SCRIPT_NAME'], -24) === 'modules/system/admin.php' && ($_GET['fct'] === 'blocksadmin' || $_POST['fct'] === 'blocksadmin') && isset($_POST['previewblock']) ) {
1495: die("Danger! don't use this preview. Use 'altsys module' instead.(by Protector)");
1496: }
1497:
1498: if (substr(@$_SERVER['SCRIPT_NAME'], -24) === 'modules/system/admin.php' && ($_GET['fct'] === 'tplsets' || $_POST['fct'] === 'tplsets')) {
1499: if ($_POST['op'] === 'previewpopup' || $_GET['op'] === 'previewpopup' || isset($_POST['previewtpl'])) {
1500: die("Danger! don't use this preview.(by Protector)");
1501: }
1502: }
1503: }
1504:
1505:
1506: error_reporting($error_reporting_level);
1507: }
1508:
1509: 1510: 1511: 1512: 1513: 1514:
1515: public function call_filter($type, $dying_message = '')
1516: {
1517: require_once __DIR__ . '/ProtectorFilter.php';
1518: $filter_handler = ProtectorFilterHandler::getInstance();
1519: $ret = $filter_handler->execute($type);
1520: if ($ret == false && $dying_message) {
1521: die($dying_message);
1522: }
1523:
1524: return $ret;
1525: }
1526: }
1527: