Skip to content

Commit e34b2bc

Browse files
committed
validate: php: choose regex delimiters carefully
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.
1 parent 7c197f0 commit e34b2bc

7 files changed

+73
-30
lines changed

src/validate/src/php/query-php.php

+67-30
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,36 @@
11
#!/usr/bin/env php
22
<?php
33
// Author: Jamie Davis <[email protected]>
4-
// Description: Try REDOS attack on PHP
4+
// Description: Evaluate a regex in PHP
55

66
function my_log($msg) {
77
fwrite(STDERR, $msg . "\n");
88
}
99

10+
// Return a string that can be used
11+
// Returns NULL if nothing could be found
12+
function patternAsPHPRegex($pat) {
13+
//http://php.net/manual/en/regexp.reference.delimiters.php
14+
$pairedDelimiters = [
15+
['/', '/'],
16+
['#', '#'],
17+
['`', '`'],
18+
['(', ')'],
19+
['{', '}'],
20+
['[', ']'],
21+
['<', '>'],
22+
];
23+
foreach($pairedDelimiters as $delim) {
24+
$first = $delim[0];
25+
$last = $delim[1];
26+
if (strpos($pat, $first) === FALSE && strpos($pat, $last) === FALSE) {
27+
return $first . $pat . $last;
28+
}
29+
}
30+
31+
return NULL;
32+
}
33+
1034
function main() {
1135
// Assume args are correct, this is a horrible language.
1236
global $argc, $argv;
@@ -18,43 +42,56 @@ function main() {
1842
my_log('obj');
1943

2044
// Query regexp.
21-
my_log('matching: Pattern /' . $obj->{'pattern'} . '/, input: len ' . strlen($obj->{'input'}));
45+
$phpPattern = patternAsPHPRegex($obj->{'pattern'});
46+
if (!is_null($phpPattern)) {
47+
my_log('matching: pattern ' . $obj->{'pattern'} . ' --> phpPattern ' . $phpPattern);
48+
my_log('matching: Pattern ' . $phpPattern . ', input: len ' . strlen($obj->{'input'}));
2249

23-
$matched = @preg_match('/' . $obj->{'pattern'} . '/', $obj->{'input'}, $matches); // Partial match
24-
//var_dump($matches);
25-
// NB: (a?)abc|(d) on "abc" --> (a?) is empty, but (d) is just dropped
50+
$matched = @preg_match($phpPattern, $obj->{'input'}, $matches); // Partial match
51+
//var_dump($matches);
52+
// NB: (a?)abc|(d) on "abc" --> (a?) is empty, but trailing unused groups like (d) are just dropped
2653

27-
// capture exception, if any.
28-
// will return OK even if there's compilation problems.
29-
// PHP 7.4-dev emits a warning unless we @ to ignore it.
30-
$except = @array_flip(get_defined_constants(true)['pcre'])[preg_last_error()];
54+
// capture exception, if any.
55+
// will return OK even if there's compilation problems.
56+
// PHP 7.4-dev emits a warning unless we @ to ignore it.
57+
$except = @array_flip(get_defined_constants(true)['pcre'])[preg_last_error()];
3158

32-
// check for compilation
33-
$compilation_failed_message = 'preg_match(): Compilation failed:';
34-
$last_error = error_get_last();
35-
if(strpos($last_error['message'], $compilation_failed_message) !== false) {
36-
my_log("caught the invalid input");
37-
$except = "INVALID_INPUT";
38-
$obj->{'validPattern'} = 0;
39-
} else {
40-
$obj->{'validPattern'} = 1;
41-
}
59+
// check for compilation
60+
$compilation_failed_message = 'preg_match(): Compilation failed:';
61+
$last_error = error_get_last();
62+
if(strpos($last_error['message'], $compilation_failed_message) !== false) {
63+
my_log("caught the invalid input");
64+
$except = "INVALID_INPUT"; // Override compilation failed
65+
$obj->{'validPattern'} = 0;
66+
} else {
67+
$obj->{'validPattern'} = 1;
68+
}
4269

43-
// Compose output.
44-
$obj->{'matched'} = $matched;
45-
if ($matched) {
46-
$obj->{'matchContents'} = new stdClass();
47-
$obj->{'matchContents'}->{'matchedString'} = $matches[0];
70+
// Compose output.
71+
$obj->{'matched'} = $matched;
72+
if ($matched) {
73+
$obj->{'matchContents'} = new stdClass();
74+
$obj->{'matchContents'}->{'matchedString'} = $matches[0];
4875

49-
// Unset any capture groups keyed by name instead of number for consistency with other testers
50-
foreach ($matches as $key => $value) {
51-
if (!is_int($key)) {
52-
unset($matches[$key]);
76+
// Unset any capture groups keyed by name instead of number for consistency with other testers
77+
foreach ($matches as $key => $value) {
78+
if (!is_int($key)) {
79+
unset($matches[$key]);
80+
}
5381
}
54-
}
5582

56-
$obj->{'matchContents'}->{'captureGroups'} = array_slice($matches, 1);
83+
$obj->{'matchContents'}->{'captureGroups'} = array_slice($matches, 1);
84+
}
85+
} else {
86+
$except = "INVALID_INPUT"; // Override compilation failed
87+
$obj->{'validPattern'} = 0;
88+
// Dummy values
89+
$obj->{'matched'} = 0;
90+
$obj->{'matchContents'} = new stdClass();
91+
$obj->{'matchContents'}->{'matchedString'} = "";
92+
$obj->{'matchContents'}->{'captureGroups'} = [];
5793
}
94+
5895
$obj->{'inputLength'} = strlen($obj->{'input'});
5996
$obj->{'exceptionString'} = $except;
6097
fwrite(STDOUT, json_encode($obj) . "\n");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"pattern": "/#`\\{\\]\\(<", "input": "/#`{](<"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"pattern": "/#`\\{\\]\\(", "input": "/#`{]("}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"pattern": "/[^A-Za-z0-9._/-]/", "input": "/ /"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"pattern": "/\\*.*scope.*\\*/", "input": "/*scope*/"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"pattern": "(?:(a)|(b)|(c))+", "input": "b?:abbbcbcbbbbccbbc?:ac?:abbb?:abc"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"pattern": "(?:(a)|(b)|(c))+", "input": "?:ac?:abcb?:acbbccccbb?:abccccc?:ab"}

0 commit comments

Comments
 (0)