diff --git a/src/validate/src/php/query-php.php b/src/validate/src/php/query-php.php index d70ff38f..8b8fb854 100755 --- a/src/validate/src/php/query-php.php +++ b/src/validate/src/php/query-php.php @@ -1,12 +1,36 @@ #!/usr/bin/env php -// Description: Try REDOS attack on PHP +// Description: Evaluate a regex in PHP function my_log($msg) { fwrite(STDERR, $msg . "\n"); } +// Return a string that can be used +// Returns NULL if nothing could be found +function patternAsPHPRegex($pat) { + //http://php.net/manual/en/regexp.reference.delimiters.php + $pairedDelimiters = [ + ['/', '/'], + ['#', '#'], + ['`', '`'], + ['(', ')'], + ['{', '}'], + ['[', ']'], + ['<', '>'], + ]; + foreach($pairedDelimiters as $delim) { + $first = $delim[0]; + $last = $delim[1]; + if (strpos($pat, $first) === FALSE && strpos($pat, $last) === FALSE) { + return $first . $pat . $last; + } + } + + return NULL; +} + function main() { // Assume args are correct, this is a horrible language. global $argc, $argv; @@ -18,43 +42,56 @@ function main() { my_log('obj'); // Query regexp. - my_log('matching: Pattern /' . $obj->{'pattern'} . '/, input: len ' . strlen($obj->{'input'})); + $phpPattern = patternAsPHPRegex($obj->{'pattern'}); + if (!is_null($phpPattern)) { + my_log('matching: pattern ' . $obj->{'pattern'} . ' --> phpPattern ' . $phpPattern); + my_log('matching: Pattern ' . $phpPattern . ', input: len ' . strlen($obj->{'input'})); - $matched = @preg_match('/' . $obj->{'pattern'} . '/', $obj->{'input'}, $matches); // Partial match - //var_dump($matches); - // NB: (a?)abc|(d) on "abc" --> (a?) is empty, but (d) is just dropped + $matched = @preg_match($phpPattern, $obj->{'input'}, $matches); // Partial match + //var_dump($matches); + // NB: (a?)abc|(d) on "abc" --> (a?) is empty, but trailing unused groups like (d) are just dropped - // capture exception, if any. - // will return OK even if there's compilation problems. - // PHP 7.4-dev emits a warning unless we @ to ignore it. - $except = @array_flip(get_defined_constants(true)['pcre'])[preg_last_error()]; + // capture exception, if any. + // will return OK even if there's compilation problems. + // PHP 7.4-dev emits a warning unless we @ to ignore it. + $except = @array_flip(get_defined_constants(true)['pcre'])[preg_last_error()]; - // check for compilation - $compilation_failed_message = 'preg_match(): Compilation failed:'; - $last_error = error_get_last(); - if(strpos($last_error['message'], $compilation_failed_message) !== false) { - my_log("caught the invalid input"); - $except = "INVALID_INPUT"; - $obj->{'validPattern'} = 0; - } else { - $obj->{'validPattern'} = 1; - } + // check for compilation + $compilation_failed_message = 'preg_match(): Compilation failed:'; + $last_error = error_get_last(); + if(strpos($last_error['message'], $compilation_failed_message) !== false) { + my_log("caught the invalid input"); + $except = "INVALID_INPUT"; // Override compilation failed + $obj->{'validPattern'} = 0; + } else { + $obj->{'validPattern'} = 1; + } - // Compose output. - $obj->{'matched'} = $matched; - if ($matched) { - $obj->{'matchContents'} = new stdClass(); - $obj->{'matchContents'}->{'matchedString'} = $matches[0]; + // Compose output. + $obj->{'matched'} = $matched; + if ($matched) { + $obj->{'matchContents'} = new stdClass(); + $obj->{'matchContents'}->{'matchedString'} = $matches[0]; - // Unset any capture groups keyed by name instead of number for consistency with other testers - foreach ($matches as $key => $value) { - if (!is_int($key)) { - unset($matches[$key]); + // Unset any capture groups keyed by name instead of number for consistency with other testers + foreach ($matches as $key => $value) { + if (!is_int($key)) { + unset($matches[$key]); + } } - } - $obj->{'matchContents'}->{'captureGroups'} = array_slice($matches, 1); + $obj->{'matchContents'}->{'captureGroups'} = array_slice($matches, 1); + } + } else { + $except = "INVALID_INPUT"; // Override compilation failed + $obj->{'validPattern'} = 0; + // Dummy values + $obj->{'matched'} = 0; + $obj->{'matchContents'} = new stdClass(); + $obj->{'matchContents'}->{'matchedString'} = ""; + $obj->{'matchContents'}->{'captureGroups'} = []; } + $obj->{'inputLength'} = strlen($obj->{'input'}); $obj->{'exceptionString'} = $except; fwrite(STDOUT, json_encode($obj) . "\n"); diff --git a/src/validate/src/test/contains-all-specials.json b/src/validate/src/test/contains-all-specials.json new file mode 100644 index 00000000..95fff36a --- /dev/null +++ b/src/validate/src/test/contains-all-specials.json @@ -0,0 +1 @@ +{"pattern": "/#`\\{\\]\\(<", "input": "/#`{](<"} diff --git a/src/validate/src/test/contains-many-specials.json b/src/validate/src/test/contains-many-specials.json new file mode 100644 index 00000000..e3cc2c45 --- /dev/null +++ b/src/validate/src/test/contains-many-specials.json @@ -0,0 +1 @@ +{"pattern": "/#`\\{\\]\\(", "input": "/#`{]("} diff --git a/src/validate/src/test/contains-slash-1.json b/src/validate/src/test/contains-slash-1.json new file mode 100644 index 00000000..f176459d --- /dev/null +++ b/src/validate/src/test/contains-slash-1.json @@ -0,0 +1 @@ +{"pattern": "/[^A-Za-z0-9._/-]/", "input": "/ /"} diff --git a/src/validate/src/test/contains-slash-2.json b/src/validate/src/test/contains-slash-2.json new file mode 100644 index 00000000..7196aec9 --- /dev/null +++ b/src/validate/src/test/contains-slash-2.json @@ -0,0 +1 @@ +{"pattern": "/\\*.*scope.*\\*/", "input": "/*scope*/"} diff --git a/src/validate/src/test/valid-capture-witness.json b/src/validate/src/test/valid-capture-witness.json new file mode 100644 index 00000000..c00ab319 --- /dev/null +++ b/src/validate/src/test/valid-capture-witness.json @@ -0,0 +1 @@ +{"pattern": "(?:(a)|(b)|(c))+", "input": "b?:abbbcbcbbbbccbbc?:ac?:abbb?:abc"} diff --git a/src/validate/src/test/valid-capture-witness2.json b/src/validate/src/test/valid-capture-witness2.json new file mode 100644 index 00000000..0925cc0b --- /dev/null +++ b/src/validate/src/test/valid-capture-witness2.json @@ -0,0 +1 @@ +{"pattern": "(?:(a)|(b)|(c))+", "input": "?:ac?:abcb?:acbbccccbb?:abccccc?:ab"}