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