[php-src] PHP-8.0: Fix #80710: imap_mail_compose() header injection
| From: | Christoph M. Becker via Stanislav Malyshev | Date: | Tue, 27 Apr 2021 06:04:31 +0000 |
| Subject: | [php-src] PHP-8.0: Fix #80710: imap_mail_compose() header injection | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-115855@lists.php.net to get a copy of this message | ||
Author: Christoph M. Becker (cmb69)
Committer: Stanislav Malyshev (smalyshev)
Date: 2021-04-26T21:43:03-07:00
Commit: https://github.com/php/php-src/commit/37962c61d29794645ec45d45d78123382d82c2e5
Raw diff: https://github.com/php/php-src/commit/37962c61d29794645ec45d45d78123382d82c2e5.diff
Fix #80710: imap_mail_compose() header injection
Like
mail() and mb_send_mail(), imap_mail_compose() must
prevent
header injection. For maximum backward compatibility, we still allow
header folding for general headers, and still accept trailing line
breaks for address lists.
Changed paths:
A ext/imap/tests/bug80710_1.phpt
A ext/imap/tests/bug80710_2.phpt
M ext/imap/php_imap.c
Diff:
diff --git a/ext/imap/php_imap.c b/ext/imap/php_imap.c
index 7725f36f0eb1..06203835d6e9 100644
--- a/ext/imap/php_imap.c
+++ b/ext/imap/php_imap.c
@@ -3533,6 +3533,23 @@ PHP_FUNCTION(imap_fetch_overview)
}
/* }}} */
+static zend_bool header_injection(zend_string *str, zend_bool adrlist)
+{
+ char *p = ZSTR_VAL(str);
+
+ while ((p = strpbrk(p, "\r\n")) != NULL) {
+ if (!(p[0] == '\r' && p[1] == '\n')
+ /* adrlists do not support folding, but swallow trailing line breaks */
+ && !((adrlist && p[1] == '\0')
+ /* other headers support folding */
+ || !adrlist && (p[1] == ' ' || p[1] == '\t'))) {
+ return 1;
+ }
+ p++;
+ }
+ return 0;
+}
+
/* {{{ proto string imap_mail_compose(array envelope, array body)
Create a MIME message based on given envelope and body sections */
PHP_FUNCTION(imap_mail_compose)
@@ -3553,6 +3570,13 @@ PHP_FUNCTION(imap_mail_compose)
return;
}
+#define CHECK_HEADER_INJECTION(zstr, adrlist, header) \
+ if (header_injection(zstr, adrlist)) { \
+ php_error_docref(NULL, E_WARNING, "header injection attempt in " header); \
+ RETVAL_FALSE; \
+ goto done; \
+ }
+
#define PHP_RFC822_PARSE_ADRLIST(target, value) \
str_copy = estrndup(Z_STRVAL_P(value), Z_STRLEN_P(value)); \
rfc822_parse_adrlist(target, str_copy, "NO HOST"); \
@@ -3561,46 +3585,57 @@ PHP_FUNCTION(imap_mail_compose)
env = mail_newenvelope();
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "remail",
sizeof("remail") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "remail");
env->remail = cpystr(Z_STRVAL_P(pvalue));
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "return_path",
sizeof("return_path") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "return_path");
PHP_RFC822_PARSE_ADRLIST(&env->return_path, pvalue);
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "date", sizeof("date")
- 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "date");
env->date = (unsigned char*)cpystr(Z_STRVAL_P(pvalue));
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "from", sizeof("from")
- 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "from");
PHP_RFC822_PARSE_ADRLIST(&env->from, pvalue);
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "reply_to",
sizeof("reply_to") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "reply_to");
PHP_RFC822_PARSE_ADRLIST(&env->reply_to, pvalue);
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "in_reply_to",
sizeof("in_reply_to") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "in_reply_to");
env->in_reply_to = cpystr(Z_STRVAL_P(pvalue));
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "subject",
sizeof("subject") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "subject");
env->subject = cpystr(Z_STRVAL_P(pvalue));
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "to", sizeof("to") -
1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "to");
PHP_RFC822_PARSE_ADRLIST(&env->to, pvalue);
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "cc", sizeof("cc") -
1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "cc");
PHP_RFC822_PARSE_ADRLIST(&env->cc, pvalue);
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "bcc", sizeof("bcc") -
1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "bcc");
PHP_RFC822_PARSE_ADRLIST(&env->bcc, pvalue);
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "message_id",
sizeof("message_id") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "message_id");
env->message_id=cpystr(Z_STRVAL_P(pvalue));
}
@@ -3611,6 +3646,7 @@ PHP_FUNCTION(imap_mail_compose)
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pvalue), env_data) {
custom_headers_param = mail_newbody_parameter();
convert_to_string_ex(env_data);
+ CHECK_HEADER_INJECTION(Z_STR_P(env_data), 0, "custom_headers");
custom_headers_param->value = (char *) fs_get(Z_STRLEN_P(env_data) + 1);
custom_headers_param->attribute = NULL;
memcpy(custom_headers_param->value, Z_STRVAL_P(env_data), Z_STRLEN_P(env_data) + 1);
@@ -3649,6 +3685,7 @@ PHP_FUNCTION(imap_mail_compose)
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "charset",
sizeof("charset") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body charset");
tmp_param = mail_newbody_parameter();
tmp_param->value = cpystr(Z_STRVAL_P(pvalue));
tmp_param->attribute = cpystr("CHARSET");
@@ -3661,9 +3698,11 @@ PHP_FUNCTION(imap_mail_compose)
SEPARATE_ARRAY(pvalue);
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) {
if (key == NULL) continue;
+ CHECK_HEADER_INJECTION(key, 0, "body disposition key");
disp_param = mail_newbody_parameter();
disp_param->attribute = cpystr(ZSTR_VAL(key));
convert_to_string_ex(disp_data);
+ CHECK_HEADER_INJECTION(Z_STR_P(disp_data), 0, "body disposition value");
disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1);
memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1);
disp_param->next = tmp_param;
@@ -3674,18 +3713,22 @@ PHP_FUNCTION(imap_mail_compose)
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "subtype",
sizeof("subtype") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body subtype");
bod->subtype = cpystr(Z_STRVAL_P(pvalue));
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "id", sizeof("id") - 1))
!= NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body id");
bod->id = cpystr(Z_STRVAL_P(pvalue));
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "description",
sizeof("description") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body description");
bod->description = cpystr(Z_STRVAL_P(pvalue));
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition.type",
sizeof("disposition.type") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body disposition.type");
bod->disposition.type = (char *) fs_get(Z_STRLEN_P(pvalue) + 1);
memcpy(bod->disposition.type, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue)+1);
}
@@ -3695,9 +3738,11 @@ PHP_FUNCTION(imap_mail_compose)
SEPARATE_ARRAY(pvalue);
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) {
if (key == NULL) continue;
+ CHECK_HEADER_INJECTION(key, 0, "body type.parameters key");
disp_param = mail_newbody_parameter();
disp_param->attribute = cpystr(ZSTR_VAL(key));
convert_to_string_ex(disp_data);
+ CHECK_HEADER_INJECTION(Z_STR_P(disp_data), 0, "body type.parameters value");
disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1);
memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1);
disp_param->next = tmp_param;
@@ -3728,6 +3773,7 @@ PHP_FUNCTION(imap_mail_compose)
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "md5", sizeof("md5") -
1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body md5");
bod->md5 = cpystr(Z_STRVAL_P(pvalue));
}
} else if (Z_TYPE_P(data) == IS_ARRAY && topbod->type == TYPEMULTIPART) {
@@ -3760,6 +3806,7 @@ PHP_FUNCTION(imap_mail_compose)
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "charset",
sizeof("charset") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body charset");
tmp_param = mail_newbody_parameter();
tmp_param->value = (char *) fs_get(Z_STRLEN_P(pvalue) + 1);
memcpy(tmp_param->value, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue) + 1);
@@ -3773,9 +3820,11 @@ PHP_FUNCTION(imap_mail_compose)
SEPARATE_ARRAY(pvalue);
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) {
if (key == NULL) continue;
+ CHECK_HEADER_INJECTION(key, 0, "body type.parameters key");
disp_param = mail_newbody_parameter();
disp_param->attribute = cpystr(ZSTR_VAL(key));
convert_to_string_ex(disp_data);
+ CHECK_HEADER_INJECTION(Z_STR_P(disp_data), 0, "body type.parameters value");
disp_param->value = (char *)fs_get(Z_STRLEN_P(disp_data) + 1);
memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1);
disp_param->next = tmp_param;
@@ -3786,18 +3835,22 @@ PHP_FUNCTION(imap_mail_compose)
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "subtype",
sizeof("subtype") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body subtype");
bod->subtype = cpystr(Z_STRVAL_P(pvalue));
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "id", sizeof("id") - 1))
!= NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body id");
bod->id = cpystr(Z_STRVAL_P(pvalue));
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "description",
sizeof("description") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body description");
bod->description = cpystr(Z_STRVAL_P(pvalue));
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition.type",
sizeof("disposition.type") - 1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body disposition.type");
bod->disposition.type = (char *) fs_get(Z_STRLEN_P(pvalue) + 1);
memcpy(bod->disposition.type, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue)+1);
}
@@ -3807,9 +3860,11 @@ PHP_FUNCTION(imap_mail_compose)
SEPARATE_ARRAY(pvalue);
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) {
if (key == NULL) continue;
+ CHECK_HEADER_INJECTION(key, 0, "body disposition key");
disp_param = mail_newbody_parameter();
disp_param->attribute = cpystr(ZSTR_VAL(key));
convert_to_string_ex(disp_data);
+ CHECK_HEADER_INJECTION(Z_STR_P(disp_data), 0, "body disposition value");
disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1);
memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1);
disp_param->next = tmp_param;
@@ -3840,6 +3895,7 @@ PHP_FUNCTION(imap_mail_compose)
}
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "md5", sizeof("md5") -
1)) != NULL) {
convert_to_string_ex(pvalue);
+ CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body md5");
bod->md5 = cpystr(Z_STRVAL_P(pvalue));
}
}
diff --git a/ext/imap/tests/bug80710_1.phpt b/ext/imap/tests/bug80710_1.phpt
new file mode 100644
index 000000000000..5cdee0340175
--- /dev/null
+++ b/ext/imap/tests/bug80710_1.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Bug #80710 (imap_mail_compose() header injection) - MIME Splitting Attack
+--SKIPIF--
+<?php
+if (!extension_loaded("imap")) die("skip imap extension not available");
+?>
+--FILE--
+<?php
+$envelope["from"]= "joe@example.com\n From : X-INJECTED";
+$envelope["to"] = "foo@example.com\nFrom: X-INJECTED";
+$envelope["cc"] = "bar@example.com\nFrom: X-INJECTED";
+$envelope["subject"] = "bar@example.com\n\n From : X-INJECTED";
+$envelope["x-remail"] = "bar@example.com\nFrom: X-INJECTED";
+$envelope["something"] = "bar@example.com\nFrom: X-INJECTED";
+
+$part1["type"] = TYPEMULTIPART;
+$part1["subtype"] = "mixed";
+
+$part2["type"] = TYPEAPPLICATION;
+$part2["encoding"] = ENCBINARY;
+$part2["subtype"] = "octet-stream\nContent-Type: X-INJECTED";
+$part2["description"] = "some file\nContent-Type: X-INJECTED";
+$part2["contents.data"] = "ABC\nContent-Type: X-INJECTED";
+
+$part3["type"] = TYPETEXT;
+$part3["subtype"] = "plain";
+$part3["description"] = "description3";
+$part3["contents.data"] = "contents.data3\n\n\n\t";
+
+$body[1] = $part1;
+$body[2] = $part2;
+$body[3] = $part3;
+
+echo imap_mail_compose($envelope, $body);
+?>
+--EXPECTF--
+Warning: imap_mail_compose(): header injection attempt in from in %s on line %d
diff --git a/ext/imap/tests/bug80710_2.phpt b/ext/imap/tests/bug80710_2.phpt
new file mode 100644
index 000000000000..b9f2fa8544eb
--- /dev/null
+++ b/ext/imap/tests/bug80710_2.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Bug #80710 (imap_mail_compose() header injection) - Remail
+--SKIPIF--
+<?php
+if (!extension_loaded("imap")) die("skip imap extension not available");
+?>
+--FILE--
+<?php
+$envelope["from"]= "joe@example.com\n From : X-INJECTED";
+$envelope["to"] = "foo@example.com\nFrom: X-INJECTED";
+$envelope["cc"] = "bar@example.com\nFrom: X-INJECTED";
+$envelope["subject"] = "bar@example.com\n\n From : X-INJECTED";
+$envelope["remail"] = "X-INJECTED-REMAIL: X-INJECTED\nFrom:
X-INJECTED-REMAIL-FROM"; //<--- Injected as first hdr
+$envelope["something"] = "bar@example.com\nFrom: X-INJECTED";
+
+$part1["type"] = TYPEMULTIPART;
+$part1["subtype"] = "mixed";
+
+$part2["type"] = TYPEAPPLICATION;
+$part2["encoding"] = ENCBINARY;
+$part2["subtype"] = "octet-stream\nContent-Type: X-INJECTED";
+$part2["description"] = "some file\nContent-Type: X-INJECTED";
+$part2["contents.data"] = "ABC\nContent-Type: X-INJECTED";
+
+$part3["type"] = TYPETEXT;
+$part3["subtype"] = "plain";
+$part3["description"] = "description3";
+$part3["contents.data"] = "contents.data3\n\n\n\t";
+
+$body[1] = $part1;
+$body[2] = $part2;
+$body[3] = $part3;
+
+echo imap_mail_compose($envelope, $body);
+?>
+--EXPECTF--
+Warning: imap_mail_compose(): header injection attempt in remail in %s on line %d