Skip to content

Commit 37962c6

Browse files
cmb69smalyshev
authored andcommitted
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.
1 parent 1b88c85 commit 37962c6

File tree

3 files changed

+130
-0
lines changed

3 files changed

+130
-0
lines changed

ext/imap/php_imap.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3533,6 +3533,23 @@ PHP_FUNCTION(imap_fetch_overview)
35333533
}
35343534
/* }}} */
35353535

3536+
static zend_bool header_injection(zend_string *str, zend_bool adrlist)
3537+
{
3538+
char *p = ZSTR_VAL(str);
3539+
3540+
while ((p = strpbrk(p, "\r\n")) != NULL) {
3541+
if (!(p[0] == '\r' && p[1] == '\n')
3542+
/* adrlists do not support folding, but swallow trailing line breaks */
3543+
&& !((adrlist && p[1] == '\0')
3544+
/* other headers support folding */
3545+
|| !adrlist && (p[1] == ' ' || p[1] == '\t'))) {
3546+
return 1;
3547+
}
3548+
p++;
3549+
}
3550+
return 0;
3551+
}
3552+
35363553
/* {{{ proto string imap_mail_compose(array envelope, array body)
35373554
Create a MIME message based on given envelope and body sections */
35383555
PHP_FUNCTION(imap_mail_compose)
@@ -3553,6 +3570,13 @@ PHP_FUNCTION(imap_mail_compose)
35533570
return;
35543571
}
35553572

3573+
#define CHECK_HEADER_INJECTION(zstr, adrlist, header) \
3574+
if (header_injection(zstr, adrlist)) { \
3575+
php_error_docref(NULL, E_WARNING, "header injection attempt in " header); \
3576+
RETVAL_FALSE; \
3577+
goto done; \
3578+
}
3579+
35563580
#define PHP_RFC822_PARSE_ADRLIST(target, value) \
35573581
str_copy = estrndup(Z_STRVAL_P(value), Z_STRLEN_P(value)); \
35583582
rfc822_parse_adrlist(target, str_copy, "NO HOST"); \
@@ -3561,46 +3585,57 @@ PHP_FUNCTION(imap_mail_compose)
35613585
env = mail_newenvelope();
35623586
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "remail", sizeof("remail") - 1)) != NULL) {
35633587
convert_to_string_ex(pvalue);
3588+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "remail");
35643589
env->remail = cpystr(Z_STRVAL_P(pvalue));
35653590
}
35663591
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "return_path", sizeof("return_path") - 1)) != NULL) {
35673592
convert_to_string_ex(pvalue);
3593+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "return_path");
35683594
PHP_RFC822_PARSE_ADRLIST(&env->return_path, pvalue);
35693595
}
35703596
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "date", sizeof("date") - 1)) != NULL) {
35713597
convert_to_string_ex(pvalue);
3598+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "date");
35723599
env->date = (unsigned char*)cpystr(Z_STRVAL_P(pvalue));
35733600
}
35743601
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "from", sizeof("from") - 1)) != NULL) {
35753602
convert_to_string_ex(pvalue);
3603+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "from");
35763604
PHP_RFC822_PARSE_ADRLIST(&env->from, pvalue);
35773605
}
35783606
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "reply_to", sizeof("reply_to") - 1)) != NULL) {
35793607
convert_to_string_ex(pvalue);
3608+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "reply_to");
35803609
PHP_RFC822_PARSE_ADRLIST(&env->reply_to, pvalue);
35813610
}
35823611
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "in_reply_to", sizeof("in_reply_to") - 1)) != NULL) {
35833612
convert_to_string_ex(pvalue);
3613+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "in_reply_to");
35843614
env->in_reply_to = cpystr(Z_STRVAL_P(pvalue));
35853615
}
35863616
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "subject", sizeof("subject") - 1)) != NULL) {
35873617
convert_to_string_ex(pvalue);
3618+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "subject");
35883619
env->subject = cpystr(Z_STRVAL_P(pvalue));
35893620
}
35903621
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "to", sizeof("to") - 1)) != NULL) {
35913622
convert_to_string_ex(pvalue);
3623+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "to");
35923624
PHP_RFC822_PARSE_ADRLIST(&env->to, pvalue);
35933625
}
35943626
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "cc", sizeof("cc") - 1)) != NULL) {
35953627
convert_to_string_ex(pvalue);
3628+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "cc");
35963629
PHP_RFC822_PARSE_ADRLIST(&env->cc, pvalue);
35973630
}
35983631
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "bcc", sizeof("bcc") - 1)) != NULL) {
35993632
convert_to_string_ex(pvalue);
3633+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 1, "bcc");
36003634
PHP_RFC822_PARSE_ADRLIST(&env->bcc, pvalue);
36013635
}
36023636
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "message_id", sizeof("message_id") - 1)) != NULL) {
36033637
convert_to_string_ex(pvalue);
3638+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "message_id");
36043639
env->message_id=cpystr(Z_STRVAL_P(pvalue));
36053640
}
36063641

@@ -3611,6 +3646,7 @@ PHP_FUNCTION(imap_mail_compose)
36113646
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pvalue), env_data) {
36123647
custom_headers_param = mail_newbody_parameter();
36133648
convert_to_string_ex(env_data);
3649+
CHECK_HEADER_INJECTION(Z_STR_P(env_data), 0, "custom_headers");
36143650
custom_headers_param->value = (char *) fs_get(Z_STRLEN_P(env_data) + 1);
36153651
custom_headers_param->attribute = NULL;
36163652
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)
36493685
}
36503686
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "charset", sizeof("charset") - 1)) != NULL) {
36513687
convert_to_string_ex(pvalue);
3688+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body charset");
36523689
tmp_param = mail_newbody_parameter();
36533690
tmp_param->value = cpystr(Z_STRVAL_P(pvalue));
36543691
tmp_param->attribute = cpystr("CHARSET");
@@ -3661,9 +3698,11 @@ PHP_FUNCTION(imap_mail_compose)
36613698
SEPARATE_ARRAY(pvalue);
36623699
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) {
36633700
if (key == NULL) continue;
3701+
CHECK_HEADER_INJECTION(key, 0, "body disposition key");
36643702
disp_param = mail_newbody_parameter();
36653703
disp_param->attribute = cpystr(ZSTR_VAL(key));
36663704
convert_to_string_ex(disp_data);
3705+
CHECK_HEADER_INJECTION(Z_STR_P(disp_data), 0, "body disposition value");
36673706
disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1);
36683707
memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1);
36693708
disp_param->next = tmp_param;
@@ -3674,18 +3713,22 @@ PHP_FUNCTION(imap_mail_compose)
36743713
}
36753714
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "subtype", sizeof("subtype") - 1)) != NULL) {
36763715
convert_to_string_ex(pvalue);
3716+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body subtype");
36773717
bod->subtype = cpystr(Z_STRVAL_P(pvalue));
36783718
}
36793719
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "id", sizeof("id") - 1)) != NULL) {
36803720
convert_to_string_ex(pvalue);
3721+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body id");
36813722
bod->id = cpystr(Z_STRVAL_P(pvalue));
36823723
}
36833724
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "description", sizeof("description") - 1)) != NULL) {
36843725
convert_to_string_ex(pvalue);
3726+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body description");
36853727
bod->description = cpystr(Z_STRVAL_P(pvalue));
36863728
}
36873729
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition.type", sizeof("disposition.type") - 1)) != NULL) {
36883730
convert_to_string_ex(pvalue);
3731+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body disposition.type");
36893732
bod->disposition.type = (char *) fs_get(Z_STRLEN_P(pvalue) + 1);
36903733
memcpy(bod->disposition.type, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue)+1);
36913734
}
@@ -3695,9 +3738,11 @@ PHP_FUNCTION(imap_mail_compose)
36953738
SEPARATE_ARRAY(pvalue);
36963739
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) {
36973740
if (key == NULL) continue;
3741+
CHECK_HEADER_INJECTION(key, 0, "body type.parameters key");
36983742
disp_param = mail_newbody_parameter();
36993743
disp_param->attribute = cpystr(ZSTR_VAL(key));
37003744
convert_to_string_ex(disp_data);
3745+
CHECK_HEADER_INJECTION(Z_STR_P(disp_data), 0, "body type.parameters value");
37013746
disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1);
37023747
memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1);
37033748
disp_param->next = tmp_param;
@@ -3728,6 +3773,7 @@ PHP_FUNCTION(imap_mail_compose)
37283773
}
37293774
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "md5", sizeof("md5") - 1)) != NULL) {
37303775
convert_to_string_ex(pvalue);
3776+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body md5");
37313777
bod->md5 = cpystr(Z_STRVAL_P(pvalue));
37323778
}
37333779
} else if (Z_TYPE_P(data) == IS_ARRAY && topbod->type == TYPEMULTIPART) {
@@ -3760,6 +3806,7 @@ PHP_FUNCTION(imap_mail_compose)
37603806
}
37613807
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "charset", sizeof("charset") - 1)) != NULL) {
37623808
convert_to_string_ex(pvalue);
3809+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body charset");
37633810
tmp_param = mail_newbody_parameter();
37643811
tmp_param->value = (char *) fs_get(Z_STRLEN_P(pvalue) + 1);
37653812
memcpy(tmp_param->value, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue) + 1);
@@ -3773,9 +3820,11 @@ PHP_FUNCTION(imap_mail_compose)
37733820
SEPARATE_ARRAY(pvalue);
37743821
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) {
37753822
if (key == NULL) continue;
3823+
CHECK_HEADER_INJECTION(key, 0, "body type.parameters key");
37763824
disp_param = mail_newbody_parameter();
37773825
disp_param->attribute = cpystr(ZSTR_VAL(key));
37783826
convert_to_string_ex(disp_data);
3827+
CHECK_HEADER_INJECTION(Z_STR_P(disp_data), 0, "body type.parameters value");
37793828
disp_param->value = (char *)fs_get(Z_STRLEN_P(disp_data) + 1);
37803829
memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1);
37813830
disp_param->next = tmp_param;
@@ -3786,18 +3835,22 @@ PHP_FUNCTION(imap_mail_compose)
37863835
}
37873836
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "subtype", sizeof("subtype") - 1)) != NULL) {
37883837
convert_to_string_ex(pvalue);
3838+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body subtype");
37893839
bod->subtype = cpystr(Z_STRVAL_P(pvalue));
37903840
}
37913841
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "id", sizeof("id") - 1)) != NULL) {
37923842
convert_to_string_ex(pvalue);
3843+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body id");
37933844
bod->id = cpystr(Z_STRVAL_P(pvalue));
37943845
}
37953846
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "description", sizeof("description") - 1)) != NULL) {
37963847
convert_to_string_ex(pvalue);
3848+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body description");
37973849
bod->description = cpystr(Z_STRVAL_P(pvalue));
37983850
}
37993851
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition.type", sizeof("disposition.type") - 1)) != NULL) {
38003852
convert_to_string_ex(pvalue);
3853+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body disposition.type");
38013854
bod->disposition.type = (char *) fs_get(Z_STRLEN_P(pvalue) + 1);
38023855
memcpy(bod->disposition.type, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue)+1);
38033856
}
@@ -3807,9 +3860,11 @@ PHP_FUNCTION(imap_mail_compose)
38073860
SEPARATE_ARRAY(pvalue);
38083861
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) {
38093862
if (key == NULL) continue;
3863+
CHECK_HEADER_INJECTION(key, 0, "body disposition key");
38103864
disp_param = mail_newbody_parameter();
38113865
disp_param->attribute = cpystr(ZSTR_VAL(key));
38123866
convert_to_string_ex(disp_data);
3867+
CHECK_HEADER_INJECTION(Z_STR_P(disp_data), 0, "body disposition value");
38133868
disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1);
38143869
memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1);
38153870
disp_param->next = tmp_param;
@@ -3840,6 +3895,7 @@ PHP_FUNCTION(imap_mail_compose)
38403895
}
38413896
if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "md5", sizeof("md5") - 1)) != NULL) {
38423897
convert_to_string_ex(pvalue);
3898+
CHECK_HEADER_INJECTION(Z_STR_P(pvalue), 0, "body md5");
38433899
bod->md5 = cpystr(Z_STRVAL_P(pvalue));
38443900
}
38453901
}

ext/imap/tests/bug80710_1.phpt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Bug #80710 (imap_mail_compose() header injection) - MIME Splitting Attack
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded("imap")) die("skip imap extension not available");
6+
?>
7+
--FILE--
8+
<?php
9+
$envelope["from"]= "joe@example.com\n From : X-INJECTED";
10+
$envelope["to"] = "foo@example.com\nFrom: X-INJECTED";
11+
$envelope["cc"] = "bar@example.com\nFrom: X-INJECTED";
12+
$envelope["subject"] = "bar@example.com\n\n From : X-INJECTED";
13+
$envelope["x-remail"] = "bar@example.com\nFrom: X-INJECTED";
14+
$envelope["something"] = "bar@example.com\nFrom: X-INJECTED";
15+
16+
$part1["type"] = TYPEMULTIPART;
17+
$part1["subtype"] = "mixed";
18+
19+
$part2["type"] = TYPEAPPLICATION;
20+
$part2["encoding"] = ENCBINARY;
21+
$part2["subtype"] = "octet-stream\nContent-Type: X-INJECTED";
22+
$part2["description"] = "some file\nContent-Type: X-INJECTED";
23+
$part2["contents.data"] = "ABC\nContent-Type: X-INJECTED";
24+
25+
$part3["type"] = TYPETEXT;
26+
$part3["subtype"] = "plain";
27+
$part3["description"] = "description3";
28+
$part3["contents.data"] = "contents.data3\n\n\n\t";
29+
30+
$body[1] = $part1;
31+
$body[2] = $part2;
32+
$body[3] = $part3;
33+
34+
echo imap_mail_compose($envelope, $body);
35+
?>
36+
--EXPECTF--
37+
Warning: imap_mail_compose(): header injection attempt in from in %s on line %d

ext/imap/tests/bug80710_2.phpt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Bug #80710 (imap_mail_compose() header injection) - Remail
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded("imap")) die("skip imap extension not available");
6+
?>
7+
--FILE--
8+
<?php
9+
$envelope["from"]= "joe@example.com\n From : X-INJECTED";
10+
$envelope["to"] = "foo@example.com\nFrom: X-INJECTED";
11+
$envelope["cc"] = "bar@example.com\nFrom: X-INJECTED";
12+
$envelope["subject"] = "bar@example.com\n\n From : X-INJECTED";
13+
$envelope["remail"] = "X-INJECTED-REMAIL: X-INJECTED\nFrom: X-INJECTED-REMAIL-FROM"; //<--- Injected as first hdr
14+
$envelope["something"] = "bar@example.com\nFrom: X-INJECTED";
15+
16+
$part1["type"] = TYPEMULTIPART;
17+
$part1["subtype"] = "mixed";
18+
19+
$part2["type"] = TYPEAPPLICATION;
20+
$part2["encoding"] = ENCBINARY;
21+
$part2["subtype"] = "octet-stream\nContent-Type: X-INJECTED";
22+
$part2["description"] = "some file\nContent-Type: X-INJECTED";
23+
$part2["contents.data"] = "ABC\nContent-Type: X-INJECTED";
24+
25+
$part3["type"] = TYPETEXT;
26+
$part3["subtype"] = "plain";
27+
$part3["description"] = "description3";
28+
$part3["contents.data"] = "contents.data3\n\n\n\t";
29+
30+
$body[1] = $part1;
31+
$body[2] = $part2;
32+
$body[3] = $part3;
33+
34+
echo imap_mail_compose($envelope, $body);
35+
?>
36+
--EXPECTF--
37+
Warning: imap_mail_compose(): header injection attempt in remail in %s on line %d

0 commit comments

Comments
 (0)