1: <?php
2:
3: if (file_exists(XOOPS_ROOT_PATH . '/class/database/drivers/' . XOOPS_DB_TYPE . '/database.php')) {
4: require_once XOOPS_ROOT_PATH . '/class/database/drivers/' . XOOPS_DB_TYPE . '/database.php';
5: } else {
6: require_once XOOPS_ROOT_PATH . '/class/database/' . XOOPS_DB_TYPE . 'database.php';
7: }
8:
9: require_once XOOPS_ROOT_PATH . '/class/database/database.php';
10:
11: /**
12: * Class ProtectorMySQLDatabase
13: */
14: class ProtectorMySQLDatabase extends XoopsMySQLDatabaseProxy
15: {
16: public $doubtful_requests = array();
17: public $doubtful_needles = array(
18: // 'order by' ,
19: 'concat',
20: 'information_schema',
21: 'select',
22: 'union',
23: '/*', /**/
24: '--',
25: '#');
26:
27: /**
28: * ProtectorMySQLDatabase constructor.
29: */
30: public function __construct()
31: {
32: $protector = Protector::getInstance();
33: $this->doubtful_requests = $protector->getDblayertrapDoubtfuls();
34: $this->doubtful_needles = array_merge($this->doubtful_needles, $this->doubtful_requests);
35: }
36:
37: /**
38: * @param $sql
39: */
40: public function injectionFound($sql)
41: {
42: $protector = Protector::getInstance();
43:
44: $protector->last_error_type = 'SQL Injection';
45: $protector->message .= $sql;
46: $protector->output_log($protector->last_error_type);
47: die('SQL Injection found');
48: }
49:
50: /**
51: * @param $sql
52: *
53: * @return array
54: */
55: public function separateStringsInSQL($sql)
56: {
57: $sql = trim($sql);
58: $sql_len = strlen($sql);
59: $char = '';
60: $string_start = '';
61: $in_string = false;
62: $sql_wo_string = '';
63: $strings = array();
64: $current_string = '';
65:
66: for ($i = 0; $i < $sql_len; ++$i) {
67: $char = $sql[$i];
68: if ($in_string) {
69: while (1) {
70: $new_i = strpos($sql, $string_start, $i);
71: $current_string .= substr($sql, $i, $new_i - $i + 1);
72: $i = $new_i;
73: if ($i === false) {
74: break 2;
75: } elseif (/* $string_start == '`' || */
76: $sql[$i - 1] !== '\\'
77: ) {
78: $string_start = '';
79: $in_string = false;
80: $strings[] = $current_string;
81: break;
82: } else {
83: $j = 2;
84: $escaped_backslash = false;
85: while ($i - $j > 0 && $sql[$i - $j] === '\\') {
86: $escaped_backslash = !$escaped_backslash;
87: ++$j;
88: }
89: if ($escaped_backslash) {
90: $string_start = '';
91: $in_string = false;
92: $strings[] = $current_string;
93: break;
94: } else {
95: ++$i;
96: }
97: }
98: }
99: } elseif ($char === '"' || $char === "'") { // dare to ignore ``
100: $in_string = true;
101: $string_start = $char;
102: $current_string = $char;
103: } else {
104: $sql_wo_string .= $char;
105: }
106: // dare to ignore comment
107: // because unescaped ' or " have been already checked in stage1
108: }
109:
110: return array($sql_wo_string, $strings);
111: }
112:
113: /**
114: * @param $sql
115: */
116: public function checkSql($sql)
117: {
118: list($sql_wo_strings, $strings) = $this->separateStringsInSQL($sql);
119:
120: // stage1: addslashes() processed or not
121: foreach ($this->doubtful_requests as $request) {
122: if (addslashes($request) != $request) {
123: if (false !== stripos($sql, trim($request))) {
124: // check the request stayed inside of strings as whole
125: $ok_flag = false;
126: foreach ($strings as $string) {
127: if (false !== strpos($string, $request)) {
128: $ok_flag = true;
129: break;
130: }
131: }
132: if (!$ok_flag) {
133: $this->injectionFound($sql);
134: }
135: }
136: }
137: }
138:
139: // stage2: doubtful requests exists and outside of quotations ('or")
140: // $_GET['d'] = '1 UNION SELECT ...'
141: // NG: select a from b where c=$d
142: // OK: select a from b where c='$d_escaped'
143: // $_GET['d'] = '(select ... FROM)'
144: // NG: select a from b where c=(select ... from)
145: foreach ($this->doubtful_requests as $request) {
146: if (false !== strpos($sql_wo_strings, trim($request))) {
147: $this->injectionFound($sql);
148: }
149: }
150:
151: // stage3: comment exists or not without quoted strings (too sensitive?)
152: if (preg_match('/(\/\*|\-\-|\#)/', $sql_wo_strings, $regs)) {
153: foreach ($this->doubtful_requests as $request) {
154: if (false !== strpos($request, $regs[1])) {
155: $this->injectionFound($sql);
156: }
157: }
158: }
159: }
160:
161: /**
162: * @param string $sql
163: * @param int $limit
164: * @param int $start
165: *
166: * @return mysqli_result|bool query result or FALSE if successful
167: * or TRUE if successful and no result
168: */
169: public function query($sql, $limit = 0, $start = 0)
170: {
171: $sql4check = substr($sql, 7);
172: foreach ($this->doubtful_needles as $needle) {
173: if (false !== stripos($sql4check, $needle)) {
174: $this->checkSql($sql);
175: break;
176: }
177: }
178:
179: if (!defined('XOOPS_DB_PROXY')) {
180: $ret = parent::queryF($sql, $limit, $start);
181: } else {
182: $ret = parent::query($sql, $limit, $start);
183: }
184:
185: return $ret;
186: }
187: }
188: