Skip to content

Commit eaecc75

Browse files
committed
ext/soap: truncate a user_agent containing newline characters
GH-17976 sanitized user_agent where the HTTP wrapper emits headers and left the SOAP client alone, so a value carrying CRLF still injects request headers through all three of its sources: the SoapClient user_agent option, the http.user_agent stream context option, and the ini setting. Resolve the three to one zend_string and emit it through the same truncate-and-warn check the HTTP wrapper applies.
1 parent 524ae41 commit eaecc75

2 files changed

Lines changed: 75 additions & 11 deletions

File tree

ext/soap/php_http.c

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,23 @@ static zend_string *get_http_headers(php_stream *socketd);
2525
#define smart_str_append_const(str, const) \
2626
smart_str_appendl(str,const,sizeof(const)-1)
2727

28+
static void soap_smart_str_append_header_value(smart_str *dest, const zend_string *value, const char *header_name)
29+
{
30+
const char *src = ZSTR_VAL(value);
31+
size_t len = ZSTR_LEN(value);
32+
size_t i = 0;
33+
while (i < len && src[i] != '\r' && src[i] != '\n') {
34+
i++;
35+
}
36+
if (i < len) {
37+
smart_str_appendl(dest, src, i);
38+
php_error_docref(NULL, E_WARNING,
39+
"Header %s value contains newline characters and has been truncated", header_name);
40+
} else {
41+
smart_str_append(dest, value);
42+
}
43+
}
44+
2845
/* Proxy HTTP Authentication */
2946
bool proxy_authentication(zval* this_ptr, smart_str* soap_headers)
3047
{
@@ -607,25 +624,25 @@ bool make_http_soap_request(
607624
smart_str_append_const(&soap_headers, "\r\n"
608625
"Connection: Keep-Alive\r\n");
609626
}
627+
zend_string *ua_str = NULL;
628+
610629
tmp = Z_CLIENT_USER_AGENT_P(this_ptr);
611630
if (Z_TYPE_P(tmp) == IS_STRING) {
612-
if (Z_STRLEN_P(tmp) > 0) {
613-
smart_str_append_const(&soap_headers, "User-Agent: ");
614-
smart_str_append(&soap_headers, Z_STR_P(tmp));
615-
smart_str_append_const(&soap_headers, "\r\n");
616-
}
631+
ua_str = Z_STR_P(tmp);
617632
} else if (context &&
618633
(tmp = php_stream_context_get_option(context, "http", "user_agent")) != NULL &&
619634
Z_TYPE_P(tmp) == IS_STRING) {
620-
if (Z_STRLEN_P(tmp) > 0) {
635+
ua_str = Z_STR_P(tmp);
636+
} else if (FG(user_agent)) {
637+
ua_str = FG(user_agent);
638+
}
639+
640+
if (ua_str) {
641+
if (ZSTR_LEN(ua_str) > 0) {
621642
smart_str_append_const(&soap_headers, "User-Agent: ");
622-
smart_str_append(&soap_headers, Z_STR_P(tmp));
643+
soap_smart_str_append_header_value(&soap_headers, ua_str, "User-Agent");
623644
smart_str_append_const(&soap_headers, "\r\n");
624645
}
625-
} else if (FG(user_agent)) {
626-
smart_str_append_const(&soap_headers, "User-Agent: ");
627-
smart_str_append(&soap_headers, FG(user_agent));
628-
smart_str_append_const(&soap_headers, "\r\n");
629646
} else {
630647
smart_str_append_const(&soap_headers, "User-Agent: PHP-SOAP/"PHP_VERSION"\r\n");
631648
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
SoapClient must truncate a user_agent that contains newline characters
3+
--EXTENSIONS--
4+
soap
5+
--SKIPIF--
6+
<?php
7+
if (!file_exists(__DIR__ . "/../../../sapi/cli/tests/php_cli_server.inc")) {
8+
echo "skip sapi/cli/tests/php_cli_server.inc required but not found";
9+
}
10+
?>
11+
--FILE--
12+
<?php
13+
14+
include __DIR__ . "/../../../sapi/cli/tests/php_cli_server.inc";
15+
16+
$args = ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=" . (substr(PHP_OS, 0, 3) == "WIN" ? "php_" : "") . "soap." . PHP_SHLIB_SUFFIX];
17+
if (php_ini_loaded_file()) {
18+
$args[] = "-c";
19+
$args[] = php_ini_loaded_file();
20+
}
21+
$code = <<<'PHP'
22+
$content = trim(file_get_contents("php://input")) . PHP_EOL;
23+
PHP;
24+
25+
php_cli_server_start($code, null, $args);
26+
27+
ini_set('user_agent', "evil\r\nX-Injected: yes");
28+
29+
$client = new SoapClient(NULL, [
30+
'location' => 'http://' . PHP_CLI_SERVER_ADDRESS,
31+
'uri' => 'misc-uri',
32+
'trace' => true,
33+
]);
34+
35+
$client->__soapCall("foo", []);
36+
echo $client->__getLastRequestHeaders();
37+
38+
?>
39+
--EXPECTF--
40+
Warning: SoapClient::__doRequest(): Header User-Agent value contains newline characters and has been truncated in %s on line %d
41+
POST / HTTP/1.1
42+
Host: localhost:%d
43+
Connection: Keep-Alive
44+
User-Agent: evil
45+
Content-Type: text/xml; charset=utf-8
46+
SOAPAction: "misc-uri#foo"
47+
Content-Length: %d

0 commit comments

Comments
 (0)