Skip to content

Commit 8020788

Browse files
committed
Disallow newlines in CURLOPT_HTTPHEADER
It was possible to have multiple lines in a HTTP headers in a cURL request, which then get interpreted as multiple headers. This commit changes that to throw a ValueError when using newlines or carriage returns in a string passed as header.
1 parent 9dc29aa commit 8020788

2 files changed

Lines changed: 77 additions & 34 deletions

File tree

ext/curl/interface.c

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2030,42 +2030,42 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
20302030
HashTable *ph;
20312031
zend_string *val, *tmp_val;
20322032
struct curl_slist *slist = NULL;
2033+
const char *name = NULL;
20332034

2034-
if (Z_TYPE_P(zvalue) != IS_ARRAY) {
2035-
const char *name = NULL;
2036-
switch (option) {
2037-
case CURLOPT_HTTPHEADER:
2038-
name = "CURLOPT_HTTPHEADER";
2039-
break;
2040-
case CURLOPT_QUOTE:
2041-
name = "CURLOPT_QUOTE";
2042-
break;
2043-
case CURLOPT_HTTP200ALIASES:
2044-
name = "CURLOPT_HTTP200ALIASES";
2045-
break;
2046-
case CURLOPT_POSTQUOTE:
2047-
name = "CURLOPT_POSTQUOTE";
2048-
break;
2049-
case CURLOPT_PREQUOTE:
2050-
name = "CURLOPT_PREQUOTE";
2051-
break;
2052-
case CURLOPT_TELNETOPTIONS:
2053-
name = "CURLOPT_TELNETOPTIONS";
2054-
break;
2055-
case CURLOPT_MAIL_RCPT:
2056-
name = "CURLOPT_MAIL_RCPT";
2057-
break;
2058-
case CURLOPT_RESOLVE:
2059-
name = "CURLOPT_RESOLVE";
2060-
break;
2061-
case CURLOPT_PROXYHEADER:
2062-
name = "CURLOPT_PROXYHEADER";
2063-
break;
2064-
case CURLOPT_CONNECT_TO:
2065-
name = "CURLOPT_CONNECT_TO";
2066-
break;
2067-
}
2035+
switch (option) {
2036+
case CURLOPT_HTTPHEADER:
2037+
name = "CURLOPT_HTTPHEADER";
2038+
break;
2039+
case CURLOPT_QUOTE:
2040+
name = "CURLOPT_QUOTE";
2041+
break;
2042+
case CURLOPT_HTTP200ALIASES:
2043+
name = "CURLOPT_HTTP200ALIASES";
2044+
break;
2045+
case CURLOPT_POSTQUOTE:
2046+
name = "CURLOPT_POSTQUOTE";
2047+
break;
2048+
case CURLOPT_PREQUOTE:
2049+
name = "CURLOPT_PREQUOTE";
2050+
break;
2051+
case CURLOPT_TELNETOPTIONS:
2052+
name = "CURLOPT_TELNETOPTIONS";
2053+
break;
2054+
case CURLOPT_MAIL_RCPT:
2055+
name = "CURLOPT_MAIL_RCPT";
2056+
break;
2057+
case CURLOPT_RESOLVE:
2058+
name = "CURLOPT_RESOLVE";
2059+
break;
2060+
case CURLOPT_PROXYHEADER:
2061+
name = "CURLOPT_PROXYHEADER";
2062+
break;
2063+
case CURLOPT_CONNECT_TO:
2064+
name = "CURLOPT_CONNECT_TO";
2065+
break;
2066+
}
20682067

2068+
if (Z_TYPE_P(zvalue) != IS_ARRAY) {
20692069
zend_type_error("%s(): The %s option must have an array value", get_active_function_name(), name);
20702070
return FAILURE;
20712071
}
@@ -2074,6 +2074,14 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
20742074
ZEND_HASH_FOREACH_VAL(ph, current) {
20752075
ZVAL_DEREF(current);
20762076
val = zval_get_tmp_string(current, &tmp_val);
2077+
2078+
if ((option == CURLOPT_HTTPHEADER || option == CURLOPT_PROXYHEADER) && strpbrk(ZSTR_VAL(val), "\r\n")) {
2079+
curl_slist_free_all(slist);
2080+
zend_tmp_string_release(tmp_val);
2081+
zend_value_error("%s(): The %s option array cannot contain strings with newline characters", get_active_function_name(), name);
2082+
return FAILURE;
2083+
}
2084+
20772085
struct curl_slist *new_slist = curl_slist_append(slist, ZSTR_VAL(val));
20782086
zend_tmp_string_release(tmp_val);
20792087
if (!new_slist) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
curl_setopt() throws ValueError for newlines in headers
3+
--EXTENSIONS--
4+
curl
5+
--FILE--
6+
<?php
7+
8+
$ch = curl_init();
9+
10+
$bad_headers = [
11+
"LF injection" => ["Hello: world\nFoo: bar"],
12+
"CR injection" => ["Hello: world\rFoo: bar"],
13+
"CRLF injection" => ["Hello: world\r\nFoo: bar"]
14+
];
15+
16+
foreach ($bad_headers as $type => $headers) {
17+
echo "Testing $type:\n";
18+
try {
19+
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
20+
} catch (ValueError $exception) {
21+
echo $exception->getMessage() . "\n\n";
22+
}
23+
}
24+
25+
$ch = null;
26+
?>
27+
--EXPECT--
28+
Testing LF injection:
29+
curl_setopt(): The CURLOPT_HTTPHEADER option array cannot contain strings with newline characters
30+
31+
Testing CR injection:
32+
curl_setopt(): The CURLOPT_HTTPHEADER option array cannot contain strings with newline characters
33+
34+
Testing CRLF injection:
35+
curl_setopt(): The CURLOPT_HTTPHEADER option array cannot contain strings with newline characters

0 commit comments

Comments
 (0)