1
0
mirror of https://github.com/php/php-src.git synced 2026-04-26 09:28:21 +02:00

Fix GHSA-pcmh-g36c-qc44: http headers without colon

The header line must contain colon otherwise it is invalid and it needs
to fail.

Reviewed-by: Tim Düsterhus <tim@tideways-gmbh.com>
This commit is contained in:
Jakub Zelenka
2025-01-19 17:49:53 +01:00
parent 9fe496696d
commit 61bb8ef240
5 changed files with 156 additions and 27 deletions
+38 -13
View File
@@ -146,6 +146,7 @@ static zend_result php_stream_handle_proxy_authorization_header(const char *s, s
typedef struct _php_stream_http_response_header_info {
php_stream_filter *transfer_encoding;
size_t file_size;
bool error;
bool follow_location;
char location[HTTP_HEADER_BLOCK_SIZE];
} php_stream_http_response_header_info;
@@ -155,6 +156,7 @@ static void php_stream_http_response_header_info_init(
{
header_info->transfer_encoding = NULL;
header_info->file_size = 0;
header_info->error = false;
header_info->follow_location = 1;
header_info->location[0] = '\0';
}
@@ -192,10 +194,11 @@ static bool php_stream_http_response_header_trim(char *http_header_line,
/* Process folding headers of the current line and if there are none, parse last full response
* header line. It returns NULL if the last header is finished, otherwise it returns updated
* last header line. */
static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
php_stream_context *context, int options, zend_string *last_header_line_str,
char *header_line, size_t *header_line_length, int response_code,
zval *response_header, php_stream_http_response_header_info *header_info)
static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *wrapper,
php_stream *stream, php_stream_context *context, int options,
zend_string *last_header_line_str, char *header_line, size_t *header_line_length,
int response_code, zval *response_header,
php_stream_http_response_header_info *header_info)
{
char *last_header_line = ZSTR_VAL(last_header_line_str);
size_t last_header_line_length = ZSTR_LEN(last_header_line_str);
@@ -237,6 +240,19 @@ static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
/* Find header separator position. */
char *last_header_value = memchr(last_header_line, ':', last_header_line_length);
if (last_header_value) {
/* Verify there is no space in header name */
char *last_header_name = last_header_line + 1;
while (last_header_name < last_header_value) {
if (*last_header_name == ' ' || *last_header_name == '\t') {
header_info->error = true;
php_stream_wrapper_log_error(wrapper, options,
"HTTP invalid response format (space in header name)!");
zend_string_efree(last_header_line_str);
return NULL;
}
++last_header_name;
}
last_header_value++; /* Skip ':'. */
/* Strip leading whitespace. */
@@ -245,9 +261,12 @@ static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
last_header_value++;
}
} else {
/* There is no colon. Set the value to the end of the header line, which is effectively
* an empty string. */
last_header_value = last_header_line_end;
/* There is no colon which means invalid response so error. */
header_info->error = true;
php_stream_wrapper_log_error(wrapper, options,
"HTTP invalid response format (no colon in header line)!");
zend_string_efree(last_header_line_str);
return NULL;
}
bool store_header = true;
@@ -944,10 +963,16 @@ finish:
if (last_header_line_str != NULL) {
/* Parse last header line. */
last_header_line_str = php_stream_http_response_headers_parse(stream, context,
options, last_header_line_str, http_header_line, &http_header_line_length,
response_code, response_header, &header_info);
if (last_header_line_str != NULL) {
last_header_line_str = php_stream_http_response_headers_parse(wrapper, stream,
context, options, last_header_line_str, http_header_line,
&http_header_line_length, response_code, response_header, &header_info);
if (EXPECTED(last_header_line_str == NULL)) {
if (UNEXPECTED(header_info.error)) {
php_stream_close(stream);
stream = NULL;
goto out;
}
} else {
/* Folding header present so continue. */
continue;
}
@@ -977,8 +1002,8 @@ finish:
/* If the stream was closed early, we still want to process the last line to keep BC. */
if (last_header_line_str != NULL) {
php_stream_http_response_headers_parse(stream, context, options, last_header_line_str,
NULL, NULL, response_code, response_header, &header_info);
php_stream_http_response_headers_parse(wrapper, stream, context, options,
last_header_line_str, NULL, NULL, response_code, response_header, &header_info);
}
if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
+15 -11
View File
@@ -70,23 +70,27 @@ do_test(1, true);
echo "\n";
?>
--EXPECT--
Type='text/plain'
Hello
Size=5
World
--EXPECTF--
Type='text/plain'
Hello
Size=5
World
Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
Type='text/plain'
Hello
Size=5
World
Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
Type='text/plain'
Hello
Size=5
World
Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
Type='text/plain'
Hello
Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
+1 -3
View File
@@ -26,11 +26,9 @@ http_server_kill($pid);
--EXPECT--
NULL
string(0) ""
array(2) {
array(1) {
[0]=>
string(15) "HTTP/1.0 200 Ok"
[1]=>
string(14) "Content-Length"
}
array(2) {
[0]=>
@@ -0,0 +1,51 @@
--TEST--
GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (colon)
--FILE--
<?php
$serverCode = <<<'CODE'
$ctxt = stream_context_create([
"socket" => [
"tcp_nodelay" => true
]
]);
$server = stream_socket_server(
"tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
phpt_notify_server_start($server);
$conn = stream_socket_accept($server);
phpt_notify(message:"server-accepted");
fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header\r\nGood-Header: test\r\n\r\nbody\r\n");
CODE;
$clientCode = <<<'CODE'
function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
switch($notification_code) {
case STREAM_NOTIFY_MIME_TYPE_IS:
echo "Found the mime-type: ", $message, PHP_EOL;
break;
}
}
$ctx = stream_context_create();
stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
var_dump($http_response_header);
CODE;
include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--EXPECTF--
Found the mime-type: text/html
Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
bool(false)
array(2) {
[0]=>
string(15) "HTTP/1.0 200 Ok"
[1]=>
string(23) "Content-Type: text/html"
}
@@ -0,0 +1,51 @@
--TEST--
GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (name)
--FILE--
<?php
$serverCode = <<<'CODE'
$ctxt = stream_context_create([
"socket" => [
"tcp_nodelay" => true
]
]);
$server = stream_socket_server(
"tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
phpt_notify_server_start($server);
$conn = stream_socket_accept($server);
phpt_notify(message:"server-accepted");
fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header : test\r\nGood-Header: test\r\n\r\nbody\r\n");
CODE;
$clientCode = <<<'CODE'
function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
switch($notification_code) {
case STREAM_NOTIFY_MIME_TYPE_IS:
echo "Found the mime-type: ", $message, PHP_EOL;
break;
}
}
$ctx = stream_context_create();
stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
var_dump($http_response_header);
CODE;
include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--EXPECTF--
Found the mime-type: text/html
Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (space in header name)! in %s
bool(false)
array(2) {
[0]=>
string(15) "HTTP/1.0 200 Ok"
[1]=>
string(23) "Content-Type: text/html"
}