Skip to content

Commit

Permalink
validate: php: choose regex delimiters carefully
Browse files Browse the repository at this point in the history
The regex '/abc/' can't be wrapped in '/.../' delimiters.
Try all supported delimiters according to the PHP docs.
If none will work, report it is unsupported.
  • Loading branch information
davisjam committed Jan 21, 2019
1 parent 7c197f0 commit e34b2bc
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 30 deletions.
97 changes: 67 additions & 30 deletions src/validate/src/php/query-php.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
#!/usr/bin/env php
<?php
// Author: Jamie Davis <davisjam@vt.edu>
// 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;
Expand All @@ -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");
Expand Down
1 change: 1 addition & 0 deletions src/validate/src/test/contains-all-specials.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"pattern": "/#`\\{\\]\\(<", "input": "/#`{](<"}
1 change: 1 addition & 0 deletions src/validate/src/test/contains-many-specials.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"pattern": "/#`\\{\\]\\(", "input": "/#`{]("}
1 change: 1 addition & 0 deletions src/validate/src/test/contains-slash-1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"pattern": "/[^A-Za-z0-9._/-]/", "input": "/ /"}
1 change: 1 addition & 0 deletions src/validate/src/test/contains-slash-2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"pattern": "/\\*.*scope.*\\*/", "input": "/*scope*/"}
1 change: 1 addition & 0 deletions src/validate/src/test/valid-capture-witness.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"pattern": "(?:(a)|(b)|(c))+", "input": "b?:abbbcbcbbbbccbbc?:ac?:abbb?:abc"}
1 change: 1 addition & 0 deletions src/validate/src/test/valid-capture-witness2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"pattern": "(?:(a)|(b)|(c))+", "input": "?:ac?:abcb?:acbbccccbb?:abccccc?:ab"}

0 comments on commit e34b2bc

Please sign in to comment.