|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2021-10-10 20:35 UTC] hanskrentel at yahoo dot de
Description:
------------
By crafting a specifically default_mimetype ini setting, it is possible
to circumvent header() injection protection and smuggle lines into the
response.
Code Example:
<?php
ini_set("default_mimetype",
"text/html;charset=UTF-8\r\nContent-Length: 31\r\n\r\n" .
"Lets smuggle a HTTP response.\r\n");
phpinfo();
?>
Response:
~~~
$ curl --no-progress-meter -i 'http://[::1]:8011'
HTTP/1.1 200 OK
Host: [::1]:8011
Date: Sun, 10 Oct 2021 17:01:05 GMT
Connection: close
X-Powered-By: PHP/8.1.0RC2
Content-type: text/html;charset=UTF-8
Content-Length: 31
Lets smuggle a HTTP response.
~~~
Setting the Content-Type header later again from php userland would
override it, which can be protected by sending headers with ob_flush()
or perhaps flush().
Similar injection is possible with default_charset, with differences in
the semantics:
<?php
ini_set('default_charset', 'UTF-8' . "\r\nHeader-Injection: Works!");
header('Content-Type: text/'); // trigger header injection
ob_flush(); // trigger sending headers
ini_set('default_charset', 'UTF-8'); // restore default_charset as otherwise
// htmlspecialchars() etc. give warning
?>
Ran over this while fiddling with the built-in developmenet server, can
imagine other SAPIs are affected as well and therefore considered to
report it. But I have not looked if it is SAPI specific.
To reproduce with the built-in webserver:
1. Start with a temporary router script that has the payload, e.g. in zsh:
~~~
$ php8.1 -S '[::1]:8011' =(<<<'<?php
ini_set("default_mimetyp".($_GET["a"]??""),
"text/html;charset=UTF-8\r\nContent-Length: 31\r\n\r\n" .
"Lets smuggle a HTTP response.\r\n");
phpinfo();')
[Sun Oct 10 18:56:30 2021] PHP 8.1.0RC2 Development Server (http://[::1]:8011) started
~~~
2. in a different terminal request (with triggering the payload):
~~~
$ curl --no-progress-meter -i 'http://[::1]:8011?a=e'
HTTP/1.1 200 OK
Host: [::1]:8011
Date: Sun, 10 Oct 2021 17:09:24 GMT
Connection: close
X-Powered-By: PHP/8.1.0RC2
Content-type: text/html;charset=UTF-8
Content-Length: 31
Lets smuggle a HTTP response.
~~~
Normally a header() call prevents such injections as no new-lines are
allowed:
<?php
header("Foo: Bar\r\nHeader-Injection: Works not.");
// Warning: Header may not contain more than a single header, new line detected
?>
Using the Content-Type header together with the ini-settings allows to
circumvent that.
It is also possible to inject new lines with a php.ini file:
~~~
$ php8.0 -S '[::1]:8011' -c =(<<<$'default_mimetype="text/plainer\r\n on the run"') =(<<<'')
[Sun Oct 10 20:35:15 2021] PHP 8.0.11 Development Server (http://[::1]:8011) started
...
~~~
~~~
$ curl --no-progress-meter -i 'http://[::1]:8011'
HTTP/1.1 200 OK
Host: [::1]:8011
Date: Sun, 10 Oct 2021 18:36:06 GMT
Connection: close
X-Powered-By: PHP/8.0.11
Content-type: text/plainer
on the run; charset=UTF-8
~~~
To prevent header injection, the ini settings default_mimetype and
default_charset must not contain new lines.
Similiar to how the header() function rejects new lines, these two
ini settings need too, as well, to prevent header injection, as their
values are put into the Content-Type header.
PatchesPull Requests
Pull requests:
HistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sat Oct 25 00:00:02 2025 UTC |
I can confirm this issue; for (F)CGI as well. However, it doesn't look like a security issue to me, since an exploit would require to set these INI settings to improperly validated/sanitized user input (or another already existing RCE vulnerability). Stas, what do you think? Anyhow, a quick-fix for the default_charset case could be something like: main/main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main/main.c b/main/main.c index 533417a569..5438d7757d 100644 --- a/main/main.c +++ b/main/main.c @@ -614,6 +614,12 @@ PHPAPI void (*php_internal_encoding_changed)(void) = NULL; */ static PHP_INI_MH(OnUpdateDefaultCharset) { + char *s = ZSTR_VAL(new_value), *e = s + ZSTR_LEN(new_value); + while (--e >= s) { + if (*e == '\r' || *e == '\n') { + return FAILURE; + } + } OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); if (php_internal_encoding_changed) { php_internal_encoding_changed();