Skip to content

Commit 1db70e6

Browse files
Add cgi.security_limit_extensions
1 parent 3b8e988 commit 1db70e6

15 files changed

Lines changed: 152 additions & 27 deletions

‎ext/dl_test/tests/frameless_temporary.phpt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ $cmd = 'env -u SCRIPT_FILENAME -u PATH_TRANSLATED -u REDIRECT_STATUS -u REQUEST_
2929
. ' setarch ' . escapeshellarg(php_uname('m')) . ' -R '
3030
. escapeshellarg(getenv('TEST_PHP_CGI_EXECUTABLE'))
3131
. ' -n -q -T 2 -d enable_dl=1 -d extension_dir=' . escapeshellarg(ini_get('extension_dir'))
32+
. ' -d cgi.security_limit_extensions=.inc'
3233
. ' ' . escapeshellarg(__DIR__ . '/frameless_temporary_cgi.inc');
3334
$proc = proc_open($cmd, [
3435
0 => ['pipe', 'r'],

‎sapi/cgi/cgi_main.c‎

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ static const opt_struct OPTIONS[] = {
164164
typedef struct _php_cgi_globals_struct {
165165
HashTable user_config_cache;
166166
char *redirect_status_env;
167+
char *security_limit_extensions;
167168
bool rfc2616_headers;
168169
bool nph;
169170
bool check_shebang_line;
@@ -1505,6 +1506,7 @@ PHP_INI_BEGIN()
15051506
STD_PHP_INI_BOOLEAN("cgi.check_shebang_line", "1", PHP_INI_SYSTEM, OnUpdateBool, check_shebang_line, php_cgi_globals_struct, php_cgi_globals)
15061507
STD_PHP_INI_BOOLEAN("cgi.force_redirect", "1", PHP_INI_SYSTEM, OnUpdateBool, force_redirect, php_cgi_globals_struct, php_cgi_globals)
15071508
STD_PHP_INI_ENTRY("cgi.redirect_status_env", NULL, PHP_INI_SYSTEM, OnUpdateString, redirect_status_env, php_cgi_globals_struct, php_cgi_globals)
1509+
STD_PHP_INI_ENTRY("cgi.security_limit_extensions", ".php .phar", PHP_INI_PERDIR, OnUpdateString, security_limit_extensions, php_cgi_globals_struct, php_cgi_globals)
15081510
STD_PHP_INI_BOOLEAN("cgi.fix_pathinfo", "1", PHP_INI_SYSTEM, OnUpdateBool, fix_pathinfo, php_cgi_globals_struct, php_cgi_globals)
15091511
STD_PHP_INI_BOOLEAN("cgi.discard_path", "0", PHP_INI_SYSTEM, OnUpdateBool, discard_path, php_cgi_globals_struct, php_cgi_globals)
15101512
STD_PHP_INI_BOOLEAN("fastcgi.logging", "1", PHP_INI_SYSTEM, OnUpdateBool, fcgi_logging, php_cgi_globals_struct, php_cgi_globals)
@@ -1524,6 +1526,7 @@ static void php_cgi_globals_ctor(php_cgi_globals_struct *php_cgi_globals_ptr)
15241526
php_cgi_globals_ptr->check_shebang_line = 1;
15251527
php_cgi_globals_ptr->force_redirect = 1;
15261528
php_cgi_globals_ptr->redirect_status_env = NULL;
1529+
php_cgi_globals_ptr->security_limit_extensions = NULL;
15271530
php_cgi_globals_ptr->fix_pathinfo = 1;
15281531
php_cgi_globals_ptr->discard_path = 0;
15291532
php_cgi_globals_ptr->fcgi_logging = 1;
@@ -1684,6 +1687,43 @@ static void add_response_header(sapi_header_struct *h, zval *return_value) /* {{
16841687
}
16851688
/* }}} */
16861689

1690+
bool cgi_extension_is_allowed(char *path) {
1691+
const char *allowed_extensions = CGIG(security_limit_extensions);
1692+
if (!path || !allowed_extensions || strlen(allowed_extensions) == 0) {
1693+
// No path, or no filter configured
1694+
return true;
1695+
}
1696+
1697+
const char *dot = strrchr(path, '.');
1698+
if (!dot) {
1699+
// No file extension
1700+
return false;
1701+
}
1702+
1703+
/* We want to be able to allow things like `.test.php` (but not all of
1704+
* `.php` or `.test`. For each extension in the list, check if the path
1705+
* given ends with exactly that. Allowing `.php` allows `.test.php`,
1706+
* `.foo.php`, and everything else because dots are valid file name
1707+
* characters. */
1708+
const char *next = allowed_extensions;
1709+
const size_t path_len = strlen(path);
1710+
while (next && *next) {
1711+
size_t extension_len = strcspn(next, " \t");
1712+
1713+
if (extension_len <= path_len) {
1714+
if (memcmp(path + path_len - extension_len, next, extension_len) == 0) {
1715+
return true;
1716+
}
1717+
}
1718+
next += extension_len;
1719+
if (*next) {
1720+
next += strspn(next, " \t");
1721+
}
1722+
}
1723+
1724+
return false;
1725+
}
1726+
16871727
PHP_FUNCTION(apache_response_headers) /* {{{ */
16881728
{
16891729
ZEND_PARSE_PARAMETERS_NONE();
@@ -2475,9 +2515,10 @@ consult the installation file that came with this distribution, or visit \n\
24752515
2. we are running as cgi or fastcgi
24762516
*/
24772517
if (cgi || fastcgi || SG(request_info).path_translated) {
2478-
if (php_fopen_primary_script(&file_handle) == FAILURE) {
2518+
bool limited_extension = !cgi_extension_is_allowed(SG(request_info).path_translated);
2519+
if (limited_extension || php_fopen_primary_script(&file_handle) == FAILURE) {
24792520
zend_try {
2480-
if (errno == EACCES) {
2521+
if (limited_extension || errno == EACCES) {
24812522
SG(sapi_headers).http_response_code = 403;
24822523
PUTS("Access denied.\n");
24832524
} else {

‎sapi/cgi/tests/003.phpt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ class test { /* {{{ */
3838
file_put_contents($filename, $code);
3939

4040
var_dump(shell_exec(<<<SHELL
41-
$php -n -w "$filename"
41+
$php -n -d cgi.security_limit_extensions=".test.php" -w "$filename"
4242
SHELL));
4343
var_dump(shell_exec(<<<SHELL
44-
$php -n -w "wrong"
44+
$php -n -d cgi.security_limit_extensions="" -w "wrong"
4545
SHELL));
4646
var_dump(shell_exec(<<<SHELL
4747
echo "<?php /* comment */ class test {\n // comment \n function foo() {} } ?>" | $php -n -w

‎sapi/cgi/tests/004.phpt‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ file_put_contents($filename, $code);
2828

2929
if (defined("PHP_WINDOWS_VERSION_MAJOR")) {
3030
var_dump(shell_exec(<<<SHELL
31-
$php -n -f "$filename"
31+
$php -n -d cgi.security_limit_extensions='.test.php' -f "$filename"
3232
SHELL));
3333
} else {
3434
var_dump(shell_exec(<<<SHELL
35-
$php -n -f "$filename" 2>/dev/null
35+
$php -n -d cgi.security_limit_extensions='.test.php' -f "$filename" 2>/dev/null
3636
SHELL));
3737
}
3838
var_dump(shell_exec(<<<SHELL
39-
$php -n -f "wrong"
39+
$php -n -d cgi.security_limit_extensions='' -f "wrong"
4040
SHELL));
4141

4242
@unlink($filename);

‎sapi/cgi/tests/005.phpt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ $php = get_cgi_path();
1111
reset_env_vars();
1212

1313
var_dump(shell_exec(<<<SHELL
14-
$php -n -a -f "wrong"
14+
$php -n -d cgi.security_limit_extensions="" -a -f "wrong"
1515
SHELL));
1616
var_dump(shell_exec(<<<SHELL
17-
$php -n -f "wrong" -a
17+
$php -n -d cgi.security_limit_extensions="" -f "wrong" -a
1818
SHELL));
1919

2020
echo "Done\n";

‎sapi/cgi/tests/006.phpt‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ echo test::$var;
3030
file_put_contents($filename, $code);
3131

3232
var_dump(shell_exec(<<<SHELL
33-
"$php" -n -l "$filename"
33+
"$php" -n -d cgi.security_limit_extensions=".test.php" -l "$filename"
3434
SHELL));
3535
var_dump(shell_exec(<<<SHELL
36-
"$php" -n -l some.unknown
36+
"$php" -n -d cgi.security_limit_extensions='.unknown' -l some.unknown
3737
SHELL));
3838

3939
$code = '
@@ -50,11 +50,11 @@ file_put_contents($filename, $code);
5050

5151
if (defined("PHP_WINDOWS_VERSION_MAJOR")) {
5252
var_dump(shell_exec(<<<SHELL
53-
"$php" -n -l "$filename"
53+
"$php" -n -d cgi.security_allowed_extensions=".test.php" -l "$filename"
5454
SHELL));
5555
} else {
5656
var_dump(shell_exec(<<<SHELL
57-
"$php" -n -l "$filename" 2>/dev/null
57+
"$php" -n -d cgi.security_allowed_extensions=".test.php" -l "$filename" 2>/dev/null
5858
SHELL));
5959
}
6060

‎sapi/cgi/tests/008.phpt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ $o = new test;
3131
file_put_contents($filename, $code);
3232

3333
var_dump(shell_exec(<<<SHELL
34-
"$php" -n -s "$filename"
34+
"$php" -n -d cgi.security_limit_extensions='.test.php' -s "$filename"
3535
SHELL));
3636
var_dump(shell_exec(<<<SHELL
37-
"$php" -n -s "unknown"
37+
"$php" -n -d cgi.security_limit_extensions='' -s "unknown"
3838
SHELL));
3939

4040
@unlink($filename);

‎sapi/cgi/tests/009.phpt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ putenv("TRANSLATED_PATH=".$f."/x");
1616
putenv("SCRIPT_FILENAME=".$f."/x");
1717
file_put_contents($f, '<?php var_dump($_SERVER["TRANSLATED_PATH"]); ?>');
1818

19-
echo shell_exec("$php -n $f");
19+
echo shell_exec("$php -n -d cgi.security_limit_extensions='' $f");
2020

2121
echo "Done\n";
2222

‎sapi/cgi/tests/010.phpt‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,19 @@ header("HTTP/1.1 403 Forbidden");
1919
header("Status: 403 Also Forbidden");
2020
?>');
2121

22-
echo shell_exec("$php -n $f");
22+
echo shell_exec("$php -n -d cgi.security_limit_extensions='' $f");
2323

2424
file_put_contents($f, '<?php
2525
header("HTTP/1.1 403 Forbidden");
2626
?>');
2727

28-
echo shell_exec("$php -n $f");
28+
echo shell_exec("$php -n -d cgi.security_limit_extensions='' $f");
2929

3030
file_put_contents($f, '<?php
3131
header("Status: 403 Also Forbidden");
3232
?>');
3333

34-
echo shell_exec("$php -n $f");
34+
echo shell_exec("$php -n -d cgi.security_limit_extensions='' $f");
3535

3636
echo "Done\n";
3737

‎sapi/cgi/tests/011.phpt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ $f = tempnam(sys_get_temp_dir(), 'cgitest');
1515
function test($script) {
1616
file_put_contents($GLOBALS['f'], $script);
1717
$cmd = escapeshellcmd($GLOBALS['php']);
18-
$cmd .= ' -n -dreport_zend_debug=0 -dhtml_errors=0 ' . escapeshellarg($GLOBALS['f']);
18+
$cmd .= ' -n -dreport_zend_debug=0 -dhtml_errors=0 -dcgi.security_limit_extensions="" ' . escapeshellarg($GLOBALS['f']);
1919
echo "----------\n";
2020
echo rtrim($script) . "\n";
2121
echo "----------\n";

0 commit comments

Comments
 (0)