1 : <?php
2 :
3 : /**
4 : * PHPIDS
5 : *
6 : * Requirements: PHP5, SimpleXML
7 : *
8 : * Copyright (c) 2007 PHPIDS group (http://php-ids.org)
9 : *
10 : * This program is free software; you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License as published by
12 : * the Free Software Foundation; version 2 of the license.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * PHP version 5.1.6+
20 : *
21 : * @category Security
22 : * @package PHPIDS
23 : * @author Mario Heiderich <mario.heiderich@gmail.com>
24 : * @author Christian Matthies <ch0012@gmail.com>
25 : * @author Lars Strojny <lars@strojny.net>
26 : * @license http://www.gnu.org/licenses/lgpl.html LGPL
27 : * @link http://php-ids.org/
28 : */
29 :
30 : /**
31 : * PHPIDS specific utility class to convert charsets manually
32 : *
33 : * Note that if you make use of IDS_Converter::runAll(), existing class
34 : * methods will be executed in the same order as they are implemented in the
35 : * class tree!
36 : *
37 : * @category Security
38 : * @package PHPIDS
39 : * @author Christian Matthies <ch0012@gmail.com>
40 : * @author Mario Heiderich <mario.heiderich@gmail.com>
41 : * @author Lars Strojny <lars@strojny.net>
42 : * @copyright 2007 The PHPIDS Group
43 : * @license http://www.gnu.org/licenses/lgpl.html LGPL
44 : * @version Release: $Id:Converter.php 517 2007-09-15 15:04:13Z mario $
45 : * @link http://php-ids.org/
46 : */
47 : class IDS_Converter
48 1 : {
49 : /**
50 : * Runs all converter functions
51 : *
52 : * Note that if you make use of IDS_Converter::runAll(), existing class
53 : * methods will be executed in the same order as they are implemented in the
54 : * class tree!
55 : *
56 : * @param string $value the value to convert
57 : *
58 : * @static
59 : * @return string
60 : */
61 : public static function runAll($value)
62 : {
63 34 : foreach (get_class_methods(__CLASS__) as $method) {
64 :
65 34 : if (strpos($method, 'run') === 0) {
66 34 : continue;
67 0 : }
68 34 : $value = self::$method($value);
69 34 : }
70 :
71 34 : return $value;
72 : }
73 :
74 : /**
75 : * Check for comments and erases them if available
76 : *
77 : * @param string $value the value to convert
78 : *
79 : * @static
80 : * @return string
81 : */
82 : public static function convertFromCommented($value)
83 : {
84 : // check for existing comments
85 34 : if (preg_match('/(?:\<!-|-->|\/\*|\*\/|\/\/\W*\w+\s*$)|' .
86 34 : '(?:--[^-]*-)/ms', $value)) {
87 :
88 : $pattern = array(
89 7 : '/(?:(?:<!)(?:(?:--(?:[^-]*(?:-[^-]+)*)--\s*)*)(?:>))/ms',
90 7 : '/(?:(?:\/\*\/*[^\/\*]*)+\*\/)/ms',
91 : '/(?:--[^-]*-)/ms'
92 7 : );
93 :
94 7 : $converted = preg_replace($pattern, ';', $value);
95 7 : $value .= "\n" . $converted;
96 7 : }
97 : //make sure inline comments are detected and converted correctly
98 34 : $value = preg_replace('/(<\w+)\/+(\w+=?)/m', '$1/$2', $value);
99 34 : $value = preg_replace('/[^\\\:]\/\/(.*)$/m', '/**/$1', $value);
100 :
101 34 : return $value;
102 : }
103 :
104 : /**
105 : * Strip newlines
106 : *
107 : * @param string $value the value to convert
108 : *
109 : * @static
110 : * @return string
111 : */
112 : public static function convertFromNewLines($value)
113 : {
114 : //check for inline linebreaks
115 34 : $search = array('\r', '\n', '\f', '\t', '\v');
116 34 : $value = str_replace($search, ';', $value);
117 :
118 : //convert real linebreaks
119 34 : return preg_replace('/(?:\n|\r|\v)/m', ' ', $value);
120 : }
121 :
122 : /**
123 : * Checks for common charcode pattern and decodes them
124 : *
125 : * @param string $value the value to convert
126 : *
127 : * @static
128 : * @return string
129 : */
130 : public static function convertFromJSCharcode($value)
131 : {
132 34 : $matches = array();
133 :
134 : // check if value matches typical charCode pattern
135 34 : if (preg_match_all('/(?:[\d+-=\/\* ]+(?:\s?,\s?[\d+-=\/\* ]+)+){4,}/ms',
136 34 : $value, $matches)) {
137 :
138 1 : $converted = '';
139 1 : $string = implode(',', $matches[0]);
140 1 : $string = preg_replace('/\s/', '', $string);
141 1 : $string = preg_replace('/\w+=/', '', $string);
142 1 : $charcode = explode(',', $string);
143 :
144 1 : foreach ($charcode as $char) {
145 1 : $char = preg_replace('/\W0/s', '', $char);
146 :
147 1 : if (preg_match_all('/\d*[+-\/\* ]\d+/', $char, $matches)) {
148 1 : $match = preg_split('/(\W?\d+)/',
149 1 : (implode('', $matches[0])),
150 1 : null,
151 1 : PREG_SPLIT_DELIM_CAPTURE);
152 :
153 1 : if (array_sum($match) >= 20 && array_sum($match) <= 127) {
154 1 : $converted .= chr(array_sum($match));
155 1 : }
156 :
157 1 : } elseif (!empty($char) && $char >= 20 && $char <= 127) {
158 1 : $converted .= chr($char);
159 1 : }
160 1 : }
161 :
162 1 : $value .= "\n" . $converted;
163 1 : }
164 :
165 : // check for octal charcode pattern
166 34 : if (preg_match_all('/(?:(?:[\\\]+\d+[ \t]*){8,})/ims', $value, $matches)) {
167 :
168 1 : $converted = '';
169 1 : $charcode = explode('\\', preg_replace('/\s/', '', implode(',',
170 1 : $matches[0])));
171 :
172 1 : foreach ($charcode as $char) {
173 1 : if (!empty($char)) {
174 1 : if (octdec($char) >= 20 && octdec($char) <= 127) {
175 1 : $converted .= chr(octdec($char));
176 1 : }
177 1 : }
178 1 : }
179 1 : $value .= "\n" . $converted;
180 1 : }
181 :
182 : // check for hexadecimal charcode pattern
183 34 : if (preg_match_all('/(?:(?:[\\\]+\w+\s*){8,})/ims', $value, $matches)) {
184 :
185 2 : $converted = '';
186 2 : $charcode = explode('\\', preg_replace('/[ux]/', '', implode(',',
187 2 : $matches[0])));
188 :
189 2 : foreach ($charcode as $char) {
190 2 : if (!empty($char)) {
191 2 : if (hexdec($char) >= 20 && hexdec($char) <= 127) {
192 2 : $converted .= chr(hexdec($char));
193 2 : }
194 2 : }
195 2 : }
196 2 : $value .= "\n" . $converted;
197 2 : }
198 :
199 34 : return $value;
200 : }
201 :
202 : /**
203 : * Eliminate JS regex modifiers
204 : *
205 : * @param string $value the value to convert
206 : *
207 : * @static
208 : * @return string
209 : */
210 : public static function convertJSRegexModifiers($value)
211 : {
212 34 : $value = preg_replace('/\/[gim]/', '/', $value);
213 :
214 34 : return $value;
215 : }
216 :
217 : /**
218 : * Converts from hex/dec entities
219 : *
220 : * @param string $value the value to convert
221 : *
222 : * @static
223 : * @return string
224 : */
225 : public static function convertEntities($value)
226 : {
227 34 : $converted = null;
228 34 : if (preg_match('/&#x?[\w]+/ms', $value)) {
229 6 : $converted = preg_replace('/(&#x?[\w]{2}\d?);?/ms', '$1;', $value);
230 6 : $converted = html_entity_decode($converted, ENT_QUOTES, 'UTF-8');
231 6 : $value .= "\n" . str_replace(';;', ';', $converted);
232 6 : }
233 :
234 34 : return $value;
235 : }
236 :
237 : /**
238 : * Normalize quotes
239 : *
240 : * @param string $value the value to convert
241 : *
242 : * @static
243 : * @return string
244 : */
245 : public static function convertQuotes($value)
246 : {
247 : // normalize different quotes to "
248 34 : $pattern = array('\'', '`', '´', '’', '‘');
249 34 : $value = str_replace($pattern, '"', $value);
250 :
251 34 : return $value;
252 : }
253 :
254 : /**
255 : * Converts SQLHEX to plain text
256 : *
257 : * @param string $value the value to convert
258 : *
259 : * @static
260 : * @return string
261 : */
262 : public static function convertFromSQLHex($value)
263 : {
264 34 : $matches = array();
265 34 : if(preg_match_all('/(?:0x[a-f\d]{2,}[a-f\d\s]*)+/im', $value, $matches)) {
266 3 : foreach($matches[0] as $match) {
267 3 : $converted = '';
268 3 : foreach(str_split($match, 2) as $hex_index) {
269 3 : if(preg_match('/[a-f\d]{2,3}/i', $hex_index)) {
270 3 : $converted .= chr(hexdec($hex_index));
271 3 : }
272 3 : }
273 3 : $value = str_replace($match, $converted, $value);
274 3 : }
275 3 : }
276 34 : return $value;
277 : }
278 :
279 : /**
280 : * Converts basic SQL keywords and obfuscations
281 : *
282 : * @param string $value the value to convert
283 : *
284 : * @static
285 : * @return string
286 : */
287 : public static function convertFromSQLKeywords($value)
288 : {
289 : $pattern = array('/(?:IS\s+null)|(LIKE\s+null)|' .
290 34 : '(?:(?:^|\W)IN[+\s]*\([^()]+\))/ims');
291 34 : $value = preg_replace($pattern, '"=0', $value);
292 34 : $value = preg_replace('/null,/ims', ',0', $value);
293 34 : $value = preg_replace('/,null/ims', ',0', $value);
294 : $pattern = array('/[^\w,]NULL|\\\N|TRUE|FALSE|UTC_TIME|' .
295 34 : 'LOCALTIME(?:STAMP)?|CURRENT_\w+|BINARY|' .
296 34 : '(?:(?:ASCII|SOUNDEX|' .
297 34 : 'MD5|R?LIKE)[+\s]*\([^()]+\))|(?:-+\d)/ims');
298 34 : $value = preg_replace($pattern, 0, $value);
299 : $pattern = array('/(?:NOT\s+BETWEEN)|(?:IS\s+NOT)|(?:NOT\s+IN)|' .
300 34 : '(?:XOR|\WDIV\W|\WNOT\W|<>|RLIKE(?:\s+BINARY)?)|' .
301 34 : '(?:REGEXP\s+BINARY)|' .
302 34 : '(?:SOUNDS\s+LIKE)/ims');
303 34 : $value = preg_replace($pattern, '!', $value);
304 34 : $value = preg_replace('/"\s+\d/', '"', $value);
305 34 : $value = str_replace('~', ' ', $value);
306 :
307 34 : return $value;
308 : }
309 :
310 : /**
311 : * Detects nullbytes and controls chars via ord()
312 : *
313 : * @param string $value the value to convert
314 : *
315 : * @static
316 : * @return string
317 : */
318 : public static function convertFromControlChars($value)
319 : {
320 : // critical ctrl values
321 34 : $search = array(chr(0), chr(1), chr(2),
322 34 : chr(3), chr(4), chr(5),
323 34 : chr(6), chr(7), chr(8),
324 34 : chr(11), chr(12), chr(14),
325 34 : chr(15), chr(16), chr(17),
326 34 : chr(18), chr(19));
327 34 : $value = str_replace($search, '%00', $value);
328 34 : $urlencoded = urlencode($value);
329 :
330 : //take care for malicious unicode characters
331 34 : $value = urldecode(preg_replace('/(?:%E(?:2|3)%8(?:0|1)%(?:A|8|9)' .
332 34 : '\w|%EF%BB%BF|%EF%BF%BD)|(?:&#(?:65|8)\d{3};?)/i', null,
333 34 : $urlencoded));
334 :
335 34 : $value = preg_replace('/(?:&[#x]*(200|820|[jlmnrwz]+)\w?;?)/i', null,
336 34 : $value);
337 :
338 34 : $value = preg_replace('/(?:&#(?:65|8)\d{3};?)|' .
339 34 : '(?:&#(?:56|7)3\d{2};?)|' .
340 34 : '(?:&#x(?:fe|20)\w{2};?)|' .
341 34 : '(?:&#x(?:d[c-f])\w{2};?)/i', null,
342 34 : $value);
343 :
344 34 : return $value;
345 : }
346 :
347 : /**
348 : * This method matches and translates base64 strings and fragments
349 : * used in data URIs
350 : *
351 : * @param string $value the value to convert
352 : *
353 : * @static
354 : * @return string
355 : */
356 : public static function convertFromNestedBase64($value)
357 : {
358 34 : $matches = array();
359 34 : preg_match_all('/(?:^|[,&?])\s*([a-z0-9]{30,}=*)(?:\W|$)/im',
360 34 : $value,
361 34 : $matches);
362 :
363 34 : foreach ($matches[1] as $item) {
364 2 : if (isset($item) && !preg_match('/[a-f0-9]{32}/i', $item)) {
365 2 : $value = str_replace($item, base64_decode($item), $value);
366 2 : }
367 2 : }
368 :
369 34 : return $value;
370 : }
371 :
372 : /**
373 : * Detects nullbytes and controls chars via ord()
374 : *
375 : * @param string $value the value to convert
376 : *
377 : * @static
378 : * @return string
379 : */
380 : public static function convertFromOutOfRangeChars($value)
381 : {
382 34 : $values = str_split($value);
383 34 : foreach ($values as $item) {
384 34 : if (ord($item) >= 127) {
385 7 : $value = str_replace($item, 'U', $value);
386 7 : }
387 34 : }
388 :
389 34 : return $value;
390 : }
391 :
392 : /**
393 : * Strip XML patterns
394 : *
395 : * @param string $value the value to convert
396 : *
397 : * @static
398 : * @return string
399 : */
400 : public static function convertFromXML($value)
401 : {
402 34 : $converted = strip_tags($value);
403 :
404 34 : if ($converted != $value) {
405 24 : return $value . "\n" . $converted;
406 : }
407 28 : return $value;
408 : }
409 :
410 : /**
411 : * This method converts JS unicode code points to
412 : * regular characters
413 : *
414 : * @param string $value the value to convert
415 : *
416 : * @static
417 : * @return string
418 : */
419 : public static function convertFromJSUnicode($value)
420 : {
421 34 : $matches = array();
422 :
423 34 : preg_match_all('/\\\u[0-9a-f]{4}/ims', $value, $matches);
424 :
425 34 : if (!empty($matches[0])) {
426 0 : foreach ($matches[0] as $match) {
427 0 : $value = str_replace($match,
428 0 : chr(hexdec(substr($match, 2, 4))),
429 0 : $value);
430 0 : }
431 0 : $value .= "\n\u0001";
432 0 : }
433 :
434 34 : return $value;
435 : }
436 :
437 :
438 : /**
439 : * Converts relevant UTF-7 tags to UTF-8
440 : *
441 : * @param string $value the value to convert
442 : *
443 : * @static
444 : * @return string
445 : */
446 : public static function convertFromUTF7($value)
447 : {
448 34 : if (function_exists('mb_convert_encoding')
449 34 : && preg_match('/\+A\w+-/m', $value)) {
450 1 : $value .= "\n" . mb_convert_encoding($value, 'UTF-8', 'UTF-7');
451 1 : } else {
452 : //list of all critical UTF7 codepoints
453 : $schemes = array(
454 34 : '+ACI-' => '"',
455 34 : '+ADw-' => '<',
456 34 : '+AD4-' => '>',
457 34 : '+AFs-' => '[',
458 34 : '+AF0-' => ']',
459 34 : '+AHs-' => '{',
460 34 : '+AH0-' => '}',
461 34 : '+AFw-' => '\\',
462 34 : '+ADs-' => ';',
463 34 : '+ACM-' => '#',
464 34 : '+ACY-' => '&',
465 34 : '+ACU-' => '%',
466 34 : '+ACQ-' => '$',
467 34 : '+AD0-' => '=',
468 34 : '+AGA-' => '`',
469 34 : '+ALQ-' => '"',
470 34 : '+IBg-' => '"',
471 34 : '+IBk-' => '"',
472 34 : '+AHw-' => '|',
473 34 : '+ACo-' => '*',
474 34 : '+AF4-' => '^',
475 34 : '+ACIAPg-' => '">',
476 : '+ACIAPgA8-' => '">'
477 34 : );
478 :
479 34 : $value = str_ireplace(array_keys($schemes),
480 34 : array_values($schemes), $value);
481 : }
482 34 : return $value;
483 : }
484 :
485 : /**
486 : * Converts basic concatenations
487 : *
488 : * @param string $value the value to convert
489 : *
490 : * @static
491 : * @return string
492 : */
493 : public static function convertConcatenations($value)
494 : {
495 : //normalize remaining backslashes
496 34 : if ($value != preg_replace('/(\w)\\\/', "$1", $value)) {
497 3 : $value .= preg_replace('/(\w)\\\/', "$1", $value);
498 3 : }
499 :
500 34 : $compare = stripslashes($value);
501 :
502 34 : $pattern = array('/(?:<\/\w+>\+<\w+>)/s',
503 34 : '/(?:":\d+[^"[]+")/s',
504 34 : '/(?:"?"\+\w+\+")/s',
505 34 : '/(?:"\s*;[^"]+")|(?:";[^"]+:\s*")/s',
506 34 : '/(?:"\s*(?:;|\+).{8,18}:\s*")/s',
507 34 : '/(?:";\w+=)|(?:!""&&")|(?:~)/s',
508 34 : '/(?:"?"\+""?\+?"?)|(?:;\w+=")|(?:"[|&]{2,})/s',
509 34 : '/(?:"\s*\W+")/s',
510 34 : '/(?:";\w\s*\+=\s*\w?\s*")/s',
511 34 : '/(?:"[|&;]+\s*[^|&\n]*[|&]+\s*"?)/s',
512 34 : '/(?:";\s*\w+\W+\w*\s*[|&]*")/s',
513 34 : '/(?:"\s*"\s*\.)/s',
514 34 : '/(?:\s*new\s+\w+\s*[+"])/',
515 34 : '/(?:(?:^|\s+)(?:do|else)\s+)/',
516 34 : '/(?:\{\s*new\s+\w+\s*\})/');
517 :
518 : // strip out concatenations
519 34 : $converted = preg_replace($pattern, null, $compare);
520 :
521 : //strip object traversal
522 34 : $converted = preg_replace('/\w(\.\w\()/', "$1", $converted);
523 :
524 : //convert JS special numbers
525 34 : $converted = preg_replace('/(?:\(*[.\d]e[+-]*[^a-z\W]+\)*)' .
526 34 : '|(?:NaN|Infinity)\W/ms', 1, $converted);
527 :
528 34 : if ($compare != $converted) {
529 15 : $value .= "\n" . $converted;
530 15 : }
531 :
532 34 : return $value;
533 : }
534 :
535 : /**
536 : * This method collects and decodes proprietary encoding types
537 : *
538 : * @param string $value the value to convert
539 : * @param IDS_Monitor $monitor the monitor object
540 : *
541 : * @static
542 : * @return string
543 : */
544 : public static function convertFromProprietaryEncodings($value) {
545 :
546 : //Xajax error reportings
547 34 : $value = preg_replace('/<!\[CDATA\[(\W+)\]\]>/im', '$1', $value);
548 :
549 : //strip quotes within typical search patterns
550 34 : $value = preg_replace('/^"([^"=\\!>]+)"$/', '$1', $value);
551 :
552 : //convert Content to null to avoid false alerts
553 34 : $value = preg_replace('/Content/', null, $value);
554 :
555 : //strip emoticons
556 34 : $value = preg_replace(
557 34 : '/(?:[:;]-[()\/PD]+)|(?:\s;[()PD]+)|(?::[()PD]+)|-\.-|\^\^/m',
558 34 : null,
559 : $value
560 34 : );
561 :
562 34 : return $value;
563 : }
564 :
565 : /**
566 : * This method is the centrifuge prototype
567 : *
568 : * @param string $value the value to convert
569 : * @param IDS_Monitor $monitor the monitor object
570 : *
571 : * @static
572 : * @return string
573 : */
574 : public static function runCentrifuge($value, IDS_Monitor $monitor = null)
575 : {
576 34 : $threshold = 3.49;
577 :
578 : try {
579 34 : $unserialized = @unserialize($value);
580 34 : } catch (Exception $exception) {
581 : $unserialized = false;
582 : }
583 :
584 34 : if (strlen($value) > 25 && !$unserialized) {
585 : // Check for the attack char ratio
586 32 : $tmp_value = $value;
587 32 : $tmp_value = preg_replace('/([*.!?+-])\1{1,}/m', '$1', $tmp_value);
588 32 : $tmp_value = preg_replace('/"[\p{L}\d\s]+"/m', null, $tmp_value);
589 :
590 32 : $stripped_length = strlen(preg_replace('/[\d\s\p{L}.:,%\/><-]+/m',
591 32 : null, $tmp_value));
592 32 : $overall_length = strlen(preg_replace('/([\d\s\p{L}]{4,})+/m', 'aaa',
593 32 : preg_replace('/\s{2,}/m', null, $tmp_value)));
594 :
595 : if ($stripped_length != 0
596 32 : && $overall_length/$stripped_length <= $threshold) {
597 :
598 15 : $monitor->centrifuge['ratio'] =
599 15 : $overall_length/$stripped_length;
600 15 : $monitor->centrifuge['threshold'] =
601 : $threshold;
602 :
603 15 : $value .= "\n$[!!!]";
604 15 : }
605 32 : }
606 :
607 34 : if (strlen($value) > 40) {
608 : // Replace all non-special chars
609 31 : $converted = preg_replace('/[\w\s\p{L},.]/', null, $value);
610 :
611 : // Split string into an array, unify and sort
612 31 : $array = str_split($converted);
613 31 : $array = array_unique($array);
614 31 : asort($array);
615 :
616 : // Normalize certain tokens
617 : $schemes = array(
618 31 : '~' => '+',
619 31 : '^' => '+',
620 31 : '|' => '+',
621 31 : '*' => '+',
622 31 : '%' => '+',
623 31 : '&' => '+',
624 : '/' => '+'
625 31 : );
626 :
627 31 : $converted = implode($array);
628 31 : $converted = str_replace(array_keys($schemes),
629 31 : array_values($schemes), $converted);
630 31 : $converted = preg_replace('/[+-]\s*\d+/', '+', $converted);
631 31 : $converted = preg_replace('/[()[\]{}]/', '(', $converted);
632 31 : $converted = preg_replace('/[!?:=]/', ':', $converted);
633 31 : $converted = preg_replace('/[^:(+]/', null, stripslashes($converted));
634 :
635 : // Sort again and implode
636 31 : $array = str_split($converted);
637 31 : asort($array);
638 :
639 31 : $converted = implode($array);
640 :
641 31 : if (preg_match('/(?:\({2,}\+{2,}:{2,})|(?:\({2,}\+{2,}:+)|' .
642 31 : '(?:\({3,}\++:{2,})/', $converted)) {
643 :
644 15 : $monitor->centrifuge['converted'] = $converted;
645 :
646 15 : return $value . "\n" . $converted;
647 : }
648 30 : }
649 :
650 34 : return $value;
651 : }
652 : }
653 :
654 : /*
655 : * Local variables:
656 : * tab-width: 4
657 : * c-basic-offset: 4
658 : * End:
659 : */
|