diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 1dff14b7c8c2..3506316cf741 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -2030,42 +2030,42 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue HashTable *ph; zend_string *val, *tmp_val; struct curl_slist *slist = NULL; + const char *name = NULL; - if (Z_TYPE_P(zvalue) != IS_ARRAY) { - const char *name = NULL; - switch (option) { - case CURLOPT_HTTPHEADER: - name = "CURLOPT_HTTPHEADER"; - break; - case CURLOPT_QUOTE: - name = "CURLOPT_QUOTE"; - break; - case CURLOPT_HTTP200ALIASES: - name = "CURLOPT_HTTP200ALIASES"; - break; - case CURLOPT_POSTQUOTE: - name = "CURLOPT_POSTQUOTE"; - break; - case CURLOPT_PREQUOTE: - name = "CURLOPT_PREQUOTE"; - break; - case CURLOPT_TELNETOPTIONS: - name = "CURLOPT_TELNETOPTIONS"; - break; - case CURLOPT_MAIL_RCPT: - name = "CURLOPT_MAIL_RCPT"; - break; - case CURLOPT_RESOLVE: - name = "CURLOPT_RESOLVE"; - break; - case CURLOPT_PROXYHEADER: - name = "CURLOPT_PROXYHEADER"; - break; - case CURLOPT_CONNECT_TO: - name = "CURLOPT_CONNECT_TO"; - break; - } + switch (option) { + case CURLOPT_HTTPHEADER: + name = "CURLOPT_HTTPHEADER"; + break; + case CURLOPT_QUOTE: + name = "CURLOPT_QUOTE"; + break; + case CURLOPT_HTTP200ALIASES: + name = "CURLOPT_HTTP200ALIASES"; + break; + case CURLOPT_POSTQUOTE: + name = "CURLOPT_POSTQUOTE"; + break; + case CURLOPT_PREQUOTE: + name = "CURLOPT_PREQUOTE"; + break; + case CURLOPT_TELNETOPTIONS: + name = "CURLOPT_TELNETOPTIONS"; + break; + case CURLOPT_MAIL_RCPT: + name = "CURLOPT_MAIL_RCPT"; + break; + case CURLOPT_RESOLVE: + name = "CURLOPT_RESOLVE"; + break; + case CURLOPT_PROXYHEADER: + name = "CURLOPT_PROXYHEADER"; + break; + case CURLOPT_CONNECT_TO: + name = "CURLOPT_CONNECT_TO"; + break; + } + if (Z_TYPE_P(zvalue) != IS_ARRAY) { zend_type_error("%s(): The %s option must have an array value", get_active_function_name(), name); return FAILURE; } @@ -2074,6 +2074,25 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue ZEND_HASH_FOREACH_VAL(ph, current) { ZVAL_DEREF(current); val = zval_get_tmp_string(current, &tmp_val); + + if (zend_str_has_nul_byte(val)) { + curl_slist_free_all(slist); + zend_tmp_string_release(tmp_val); + zend_value_error("%s(): cURL option %s must not contain any null bytes", get_active_function_name(), name); + return FAILURE; + } + + switch (option) { + case CURLOPT_HTTPHEADER: + case CURLOPT_PROXYHEADER: + if (strpbrk(ZSTR_VAL(val), "\r\n")) { + curl_slist_free_all(slist); + zend_tmp_string_release(tmp_val); + zend_value_error("%s(): Header entries for the %s option may not contain more than a single header, new line detected", get_active_function_name(), name); + return FAILURE; + } + } + struct curl_slist *new_slist = curl_slist_append(slist, ZSTR_VAL(val)); zend_tmp_string_release(tmp_val); if (!new_slist) { diff --git a/ext/curl/tests/curl_setopt_error_headers.phpt b/ext/curl/tests/curl_setopt_error_headers.phpt new file mode 100644 index 000000000000..1008c571ee74 --- /dev/null +++ b/ext/curl/tests/curl_setopt_error_headers.phpt @@ -0,0 +1,35 @@ +--TEST-- +curl_setopt() throws ValueError for newlines in headers +--EXTENSIONS-- +curl +--FILE-- + ["Hello: world\nFoo: bar"], + "CR injection" => ["Hello: world\rFoo: bar"], + "CRLF injection" => ["Hello: world\r\nFoo: bar"], +]; + +foreach ($bad_headers as $type => $headers) { + echo "Testing $type:\n"; + try { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } catch (ValueError $exception) { + echo $exception->getMessage() . "\n\n"; + } +} + +$ch = null; +?> +--EXPECT-- +Testing LF injection: +curl_setopt(): Header entries for the CURLOPT_HTTPHEADER option may not contain more than a single header, new line detected + +Testing CR injection: +curl_setopt(): Header entries for the CURLOPT_HTTPHEADER option may not contain more than a single header, new line detected + +Testing CRLF injection: +curl_setopt(): Header entries for the CURLOPT_HTTPHEADER option may not contain more than a single header, new line detected diff --git a/ext/curl/tests/curl_setopt_error_nul_byte.phpt b/ext/curl/tests/curl_setopt_error_nul_byte.phpt new file mode 100644 index 000000000000..23be973b82c2 --- /dev/null +++ b/ext/curl/tests/curl_setopt_error_nul_byte.phpt @@ -0,0 +1,52 @@ +--TEST-- +curl_setopt() throws ValueError for NUL bytes in lists +--EXTENSIONS-- +curl +--FILE-- +getMessage() . "\n\n"; + } +} + +$ch = null; +?> +--EXPECT-- +CURLOPT_HTTP200ALIASES: curl_setopt(): cURL option CURLOPT_HTTP200ALIASES must not contain any null bytes + +CURLOPT_HTTPHEADER: curl_setopt(): cURL option CURLOPT_HTTPHEADER must not contain any null bytes + +CURLOPT_POSTQUOTE: curl_setopt(): cURL option CURLOPT_POSTQUOTE must not contain any null bytes + +CURLOPT_PREQUOTE: curl_setopt(): cURL option CURLOPT_PREQUOTE must not contain any null bytes + +CURLOPT_QUOTE: curl_setopt(): cURL option CURLOPT_QUOTE must not contain any null bytes + +CURLOPT_TELNETOPTIONS: curl_setopt(): cURL option CURLOPT_TELNETOPTIONS must not contain any null bytes + +CURLOPT_MAIL_RCPT: curl_setopt(): cURL option CURLOPT_MAIL_RCPT must not contain any null bytes + +CURLOPT_RESOLVE: curl_setopt(): cURL option CURLOPT_RESOLVE must not contain any null bytes + +CURLOPT_PROXYHEADER: curl_setopt(): cURL option CURLOPT_PROXYHEADER must not contain any null bytes + +CURLOPT_CONNECT_TO: curl_setopt(): cURL option CURLOPT_CONNECT_TO must not contain any null bytes