Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 53 additions & 34 deletions ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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) {
Expand Down
35 changes: 35 additions & 0 deletions ext/curl/tests/curl_setopt_error_headers.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--TEST--
curl_setopt() throws ValueError for newlines in headers
--EXTENSIONS--
curl
--FILE--
<?php

$ch = curl_init();

$bad_headers = [
"LF injection" => ["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
52 changes: 52 additions & 0 deletions ext/curl/tests/curl_setopt_error_nul_byte.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
--TEST--
curl_setopt() throws ValueError for NUL bytes in lists
--EXTENSIONS--
curl
--FILE--
<?php

$ch = curl_init();

$list_options = [
"CURLOPT_HTTP200ALIASES",
"CURLOPT_HTTPHEADER",
"CURLOPT_POSTQUOTE",
"CURLOPT_PREQUOTE",
"CURLOPT_QUOTE",
"CURLOPT_TELNETOPTIONS",
"CURLOPT_MAIL_RCPT",
"CURLOPT_RESOLVE",
"CURLOPT_PROXYHEADER",
"CURLOPT_CONNECT_TO",
];

foreach ($list_options as $option) {
try {
curl_setopt($ch, constant($option), ["Something: foo\0bar"]);
} catch (ValueError $exception) {
echo $option . ": " . $exception->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
Loading