30 if ( !isset($debug[0]['file']) ) |
30 if ( !isset($debug[0]['file']) ) |
31 return false; |
31 return false; |
32 $debug = $debug[0]['file'] . ', line ' . $debug[0]['line']; |
32 $debug = $debug[0]['file'] . ', line ' . $debug[0]['line']; |
33 echo "<b>$errtype:</b> $errstr<br />Error source:<pre>$debug</pre>"; |
33 echo "<b>$errtype:</b> $errstr<br />Error source:<pre>$debug</pre>"; |
34 } |
34 } |
35 |
35 |
|
36 global $db_sql_parse_time; |
|
37 $db_sql_parse_time = 0; |
|
38 |
36 class mysql { |
39 class mysql { |
37 var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug; |
40 var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug; |
38 var $row = array(); |
41 var $row = array(); |
39 var $rowset = array(); |
42 var $rowset = array(); |
40 var $errhandler; |
43 var $errhandler; |
307 $this->disable_errorhandler(); |
310 $this->disable_errorhandler(); |
308 return $r; |
311 return $r; |
309 } |
312 } |
310 |
313 |
311 /** |
314 /** |
312 * Checks a SQL query for possible signs of injection attempts |
315 * Performs heuristic analysis on a SQL query to check for known attack patterns. |
313 * @param string $q the query to check |
316 * @param string $q the query to check |
314 * @return bool true if query passed check, otherwise false |
317 * @return bool true if query passed check, otherwise false |
315 */ |
318 */ |
316 |
319 |
317 function check_query($q, $debug = false) |
320 function check_query($q, $debug = false) |
318 { |
321 { |
319 if($debug) echo "\$db->check_query(): checking query: ".htmlspecialchars($q).'<br />'."\n"; |
322 global $db_sql_parse_time; |
320 $sz = strlen($q); |
323 $ts = microtime_float(); |
321 $quotechar = false; |
324 |
322 $quotepos = 0; |
325 // remove properly escaped quotes |
323 $prev_is_quote = false; |
326 $q = str_replace(array("\\\"", "\\'"), '', $q); |
324 $just_started = false; |
327 |
325 for ( $i = 0; $i < strlen($q); $i++, $c = substr($q, $i, 1) ) |
328 // make sure quotes match |
326 { |
329 foreach ( array('"', "'") as $quote ) |
327 $next = substr($q, $i+1, 1); |
330 { |
328 $next2 = substr($q, $i+2, 1); |
331 if ( get_char_count($q, $quote) % 2 == 1 ) |
329 $prev = substr($q, $i-1, 1); |
332 { |
330 $prev2 = substr($q, $i-2, 1); |
333 // mismatched quotes |
331 if(isset($c) && in_array($c, Array('"', "'", '`'))) |
|
332 { |
|
333 if($quotechar) |
|
334 { |
|
335 if ( |
|
336 ( $quotechar == $c && $quotechar != $next && ( $quotechar != $prev || $just_started ) && $prev != '\\') || |
|
337 ( $prev2 == '\\' && $prev == $quotechar && $quotechar == $c ) |
|
338 ) |
|
339 { |
|
340 $quotechar = false; |
|
341 if($debug) echo('$db->check_query(): just finishing a quote section, quoted string: '.htmlspecialchars(substr($q, $quotepos, $i - $quotepos + 1)) . '<br />'); |
|
342 $q = substr($q, 0, $quotepos) . 'SAFE_QUOTE' . substr($q, $i + 1, strlen($q)); |
|
343 if($debug) echo('$db->check_query(): Filtered query: '.$q.'<br />'); |
|
344 $i = $quotepos; |
|
345 } |
|
346 } |
|
347 else |
|
348 { |
|
349 $quotechar = $c; |
|
350 $quotepos = $i; |
|
351 $just_started = true; |
|
352 } |
|
353 if($debug) echo '$db->check_query(): found quote char as pos: '.$i.'<br />'; |
|
354 continue; |
|
355 } |
|
356 $just_started = false; |
|
357 } |
|
358 if(substr(trim($q), strlen(trim($q))-1, 1) == ';') $q = substr(trim($q), 0, strlen(trim($q))-1); |
|
359 for($i=0;$i<strlen($q);$i++,$c=substr($q, $i, 1)) |
|
360 { |
|
361 if ( |
|
362 ( ( $c == ';' && $i != $sz-1 ) || $c . substr($q, $i+1, 1) == '--' ) |
|
363 || ( in_array($c, Array('"', "'", '`')) ) |
|
364 ) // Don't permit semicolons in mid-query, and never allow comments |
|
365 { |
|
366 // Injection attempt! |
|
367 if($debug) |
|
368 { |
|
369 $e = ''; |
|
370 for($j=$i-5;$j<$i+5;$j++) |
|
371 { |
|
372 if($j == $i) $e .= '<span style="color: red; text-decoration: underline;">' . $c . '</span>'; |
|
373 else $e .= $c; |
|
374 } |
|
375 echo 'Injection attempt caught at pos: '.$i.'<br />'; |
|
376 } |
|
377 return false; |
334 return false; |
378 } |
335 } |
379 } |
336 // this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token |
|
337 $q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q); |
|
338 } |
|
339 $q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q); |
|
340 |
|
341 // quotes are now matched out. does this string have a comment marker in it? |
|
342 if ( strstr($q, '--') ) |
|
343 { |
|
344 return false; |
|
345 } |
|
346 |
380 if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) ) |
347 if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) ) |
381 { |
348 { |
382 if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>'; |
349 if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>'; |
383 return false; |
350 return false; |
384 } |
351 } |
|
352 |
|
353 $ts = microtime_float() - $ts; |
|
354 $db_sql_parse_time += $ts; |
385 return true; |
355 return true; |
386 } |
356 } |
387 |
357 |
388 /** |
358 /** |
389 * Set the internal result pointer to X |
359 * Set the internal result pointer to X |
1064 * @return bool true if query passed check, otherwise false |
1034 * @return bool true if query passed check, otherwise false |
1065 */ |
1035 */ |
1066 |
1036 |
1067 function check_query($q, $debug = false) |
1037 function check_query($q, $debug = false) |
1068 { |
1038 { |
1069 if($debug) echo "\$db->check_query(): checking query: ".htmlspecialchars($q).'<br />'."\n"; |
1039 global $db_sql_parse_time; |
1070 $sz = strlen($q); |
1040 $ts = microtime_float(); |
1071 $quotechar = false; |
1041 |
1072 $quotepos = 0; |
1042 // remove properly escaped quotes |
1073 $prev_is_quote = false; |
1043 $q = str_replace(array("\\\"", "\\'"), '', $q); |
1074 $just_started = false; |
1044 |
1075 for ( $i = 0; $i < strlen($q); $i++, $c = substr($q, $i, 1) ) |
1045 // make sure quotes match |
1076 { |
1046 foreach ( array('"', "'") as $quote ) |
1077 $next = substr($q, $i+1, 1); |
1047 { |
1078 $next2 = substr($q, $i+2, 1); |
1048 if ( get_char_count($q, $quote) % 2 == 1 ) |
1079 $prev = substr($q, $i-1, 1); |
1049 { |
1080 $prev2 = substr($q, $i-2, 1); |
1050 // mismatched quotes |
1081 if(isset($c) && in_array($c, Array('"', "'", '`'))) |
|
1082 { |
|
1083 if($quotechar) |
|
1084 { |
|
1085 if ( |
|
1086 ( $quotechar == $c && $quotechar != $next && ( $quotechar != $prev || $just_started ) && $prev != '\\') || |
|
1087 ( $prev2 == '\\' && $prev == $quotechar && $quotechar == $c ) |
|
1088 ) |
|
1089 { |
|
1090 $quotechar = false; |
|
1091 if($debug) echo('$db->check_query(): just finishing a quote section, quoted string: '.htmlspecialchars(substr($q, $quotepos, $i - $quotepos + 1)) . '<br />'); |
|
1092 $q = substr($q, 0, $quotepos) . 'SAFE_QUOTE' . substr($q, $i + 1, strlen($q)); |
|
1093 if($debug) echo('$db->check_query(): Filtered query: '.$q.'<br />'); |
|
1094 $i = $quotepos; |
|
1095 } |
|
1096 } |
|
1097 else |
|
1098 { |
|
1099 $quotechar = $c; |
|
1100 $quotepos = $i; |
|
1101 $just_started = true; |
|
1102 } |
|
1103 if($debug) echo '$db->check_query(): found quote char as pos: '.$i.'<br />'; |
|
1104 continue; |
|
1105 } |
|
1106 $just_started = false; |
|
1107 } |
|
1108 if(substr(trim($q), strlen(trim($q))-1, 1) == ';') $q = substr(trim($q), 0, strlen(trim($q))-1); |
|
1109 for($i=0;$i<strlen($q);$i++,$c=substr($q, $i, 1)) |
|
1110 { |
|
1111 if ( |
|
1112 ( ( $c == ';' && $i != $sz-1 ) || $c . substr($q, $i+1, 1) == '--' ) |
|
1113 || ( in_array($c, Array('"', "'", '`')) ) |
|
1114 ) // Don't permit semicolons in mid-query, and never allow comments |
|
1115 { |
|
1116 // Injection attempt! |
|
1117 if($debug) |
|
1118 { |
|
1119 $e = ''; |
|
1120 for($j=$i-5;$j<$i+5;$j++) |
|
1121 { |
|
1122 if($j == $i) $e .= '<span style="color: red; text-decoration: underline;">' . $c . '</span>'; |
|
1123 else $e .= $c; |
|
1124 } |
|
1125 echo 'Injection attempt caught at pos: '.$i.'<br />'; |
|
1126 } |
|
1127 return false; |
1051 return false; |
1128 } |
1052 } |
1129 } |
1053 // this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token |
|
1054 $q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q); |
|
1055 } |
|
1056 $q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q); |
|
1057 |
|
1058 // quotes are now matched out. does this string have a comment marker in it? |
|
1059 if ( strstr($q, '--') ) |
|
1060 { |
|
1061 return false; |
|
1062 } |
|
1063 |
1130 if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) ) |
1064 if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) ) |
1131 { |
1065 { |
1132 if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>'; |
1066 if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>'; |
1133 return false; |
1067 return false; |
1134 } |
1068 } |
|
1069 |
|
1070 $ts = microtime_float() - $ts; |
|
1071 $db_sql_parse_time += $ts; |
1135 return true; |
1072 return true; |
1136 } |
1073 } |
1137 |
1074 |
1138 /** |
1075 /** |
1139 * Set the internal result pointer to X |
1076 * Set the internal result pointer to X |