diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index 22c18259d689..b49acd947b39 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -25,23 +25,24 @@ static zend_string *get_http_headers(php_stream *socketd); #define smart_str_append_const(str, const) \ smart_str_appendl(str,const,sizeof(const)-1) -static void soap_smart_str_append_header_value(smart_str *dest, const zend_string *value, const char *header_name) +static void soap_smart_str_append_header_value_ex(smart_str *dest, const char *src, size_t len, const char *header_name) { - const char *src = ZSTR_VAL(value); - size_t len = ZSTR_LEN(value); size_t i = 0; while (i < len && src[i] != '\r' && src[i] != '\n') { i++; } + smart_str_appendl(dest, src, i); if (i < len) { - smart_str_appendl(dest, src, i); php_error_docref(NULL, E_WARNING, "Header %s value contains newline characters and has been truncated", header_name); - } else { - smart_str_append(dest, value); } } +static void soap_smart_str_append_header_value(smart_str *dest, const zend_string *value, const char *header_name) +{ + soap_smart_str_append_header_value_ex(dest, ZSTR_VAL(value), ZSTR_LEN(value), header_name); +} + /* Proxy HTTP Authentication */ bool proxy_authentication(zval* this_ptr, smart_str* soap_headers) { @@ -656,13 +657,13 @@ bool make_http_soap_request( Z_STRLEN_P(tmp) > 0 ) { smart_str_append_const(&soap_headers, "Content-Type: "); - smart_str_append(&soap_headers, Z_STR_P(tmp)); + soap_smart_str_append_header_value(&soap_headers, Z_STR_P(tmp), "Content-Type"); } else { smart_str_append_const(&soap_headers, "Content-Type: application/soap+xml; charset=utf-8"); } if (soapaction) { smart_str_append_const(&soap_headers,"; action=\""); - smart_str_appends(&soap_headers, soapaction); + soap_smart_str_append_header_value_ex(&soap_headers, soapaction, strlen(soapaction), "SOAPAction"); smart_str_append_const(&soap_headers,"\""); } smart_str_append_const(&soap_headers,"\r\n"); @@ -673,14 +674,14 @@ bool make_http_soap_request( Z_STRLEN_P(tmp) > 0 ) { smart_str_append_const(&soap_headers, "Content-Type: "); - smart_str_append(&soap_headers, Z_STR_P(tmp)); + soap_smart_str_append_header_value(&soap_headers, Z_STR_P(tmp), "Content-Type"); smart_str_append_const(&soap_headers, "\r\n"); } else { smart_str_append_const(&soap_headers, "Content-Type: text/xml; charset=utf-8\r\n"); } if (soapaction) { smart_str_append_const(&soap_headers, "SOAPAction: \""); - smart_str_appends(&soap_headers, soapaction); + soap_smart_str_append_header_value_ex(&soap_headers, soapaction, strlen(soapaction), "SOAPAction"); smart_str_append_const(&soap_headers, "\"\r\n"); } } @@ -892,9 +893,9 @@ bool make_http_soap_request( smart_str_appends(&soap_headers, "; "); } first_cookie = false; - smart_str_append(&soap_headers, key); + soap_smart_str_append_header_value(&soap_headers, key, "Cookie"); smart_str_appendc(&soap_headers, '='); - smart_str_append(&soap_headers, Z_STR_P(value)); + soap_smart_str_append_header_value(&soap_headers, Z_STR_P(value), "Cookie"); } } } diff --git a/ext/soap/tests/content_type_header_injection.phpt b/ext/soap/tests/content_type_header_injection.phpt new file mode 100644 index 000000000000..d04cf337164e --- /dev/null +++ b/ext/soap/tests/content_type_header_injection.phpt @@ -0,0 +1,62 @@ +--TEST-- +SoapClient must truncate a content_type context option that contains newline characters +--EXTENSIONS-- +soap +--SKIPIF-- + +--FILE-- + ['content_type' => "text/xml\r\nX-Injected: yes"], +]); + +foreach ([SOAP_1_1, SOAP_1_2] as $version) { + $client = new SoapClient(NULL, [ + 'location' => 'http://' . PHP_CLI_SERVER_ADDRESS, + 'uri' => 'misc-uri', + 'trace' => true, + 'soap_version' => $version, + 'stream_context' => $context, + ]); + + $client->__soapCall("foo", []); + echo $client->__getLastRequestHeaders(); +} + +?> +--EXPECTF-- +Warning: SoapClient::__doRequest(): Header Content-Type value contains newline characters and has been truncated in %s on line %d +POST / HTTP/1.1 +Host: localhost:%d +Connection: Keep-Alive +User-Agent: PHP-SOAP/%s +Content-Type: text/xml +SOAPAction: "misc-uri#foo" +Content-Length: %d + + +Warning: SoapClient::__doRequest(): Header Content-Type value contains newline characters and has been truncated in %s on line %d +POST / HTTP/1.1 +Host: localhost:%d +Connection: Keep-Alive +User-Agent: PHP-SOAP/%s +Content-Type: text/xml; action="misc-uri#foo" +Content-Length: %d diff --git a/ext/soap/tests/cookie_header_injection.phpt b/ext/soap/tests/cookie_header_injection.phpt new file mode 100644 index 000000000000..880050e84d03 --- /dev/null +++ b/ext/soap/tests/cookie_header_injection.phpt @@ -0,0 +1,52 @@ +--TEST-- +SoapClient must truncate a cookie name or value that contains newline characters +--EXTENSIONS-- +soap +--SKIPIF-- + +--FILE-- + 'http://' . PHP_CLI_SERVER_ADDRESS, + 'uri' => 'misc-uri', + 'trace' => true, +]); + +$client->__setCookie("evil-value", "a\r\nX-Injected: yes"); +$client->__setCookie("evil-name\r\nX-Injected: yes", "b"); +$client->__setCookie("harmless", "c"); + +$client->__soapCall("foo", []); +echo $client->__getLastRequestHeaders(); + +?> +--EXPECTF-- +Warning: SoapClient::__doRequest(): Header Cookie value contains newline characters and has been truncated in %s on line %d + +Warning: SoapClient::__doRequest(): Header Cookie value contains newline characters and has been truncated in %s on line %d +POST / HTTP/1.1 +Host: localhost:%d +Connection: Keep-Alive +User-Agent: PHP-SOAP/%s +Content-Type: text/xml; charset=utf-8 +SOAPAction: "misc-uri#foo" +Content-Length: %d +Cookie: evil-value=a; evil-name=b; harmless=c diff --git a/ext/soap/tests/soapaction_header_injection.phpt b/ext/soap/tests/soapaction_header_injection.phpt new file mode 100644 index 000000000000..996c193624f1 --- /dev/null +++ b/ext/soap/tests/soapaction_header_injection.phpt @@ -0,0 +1,57 @@ +--TEST-- +SoapClient must truncate a soapaction that contains newline characters +--EXTENSIONS-- +soap +--SKIPIF-- + +--FILE-- + 'http://' . PHP_CLI_SERVER_ADDRESS, + 'uri' => 'misc-uri', + 'trace' => true, + 'soap_version' => $version, + ]); + + $client->__soapCall("foo", [], ['soapaction' => "an-action\r\nX-Injected: yes"]); + echo $client->__getLastRequestHeaders(); +} + +?> +--EXPECTF-- +Warning: SoapClient::__doRequest(): Header SOAPAction value contains newline characters and has been truncated in %s on line %d +POST / HTTP/1.1 +Host: localhost:%d +Connection: Keep-Alive +User-Agent: PHP-SOAP/%s +Content-Type: text/xml; charset=utf-8 +SOAPAction: "an-action" +Content-Length: %d + + +Warning: SoapClient::__doRequest(): Header SOAPAction value contains newline characters and has been truncated in %s on line %d +POST / HTTP/1.1 +Host: localhost:%d +Connection: Keep-Alive +User-Agent: PHP-SOAP/%s +Content-Type: application/soap+xml; charset=utf-8; action="an-action" +Content-Length: %d