Knowledgebase
Understanding email header injection exploits
Posted by zz-James Moir on 22 June 2016 09:57 AM

Overview

Botnets are constantly scanning the web to locate PHP scripts which are vulnerable to a email header injection exploit. All PHP scripts which send email based on input data, such as when a user supplies a "From" or a "To" address, may be vulnerable. In addition, botnet attempts to inject headers into any other fields it finds on a form such as hidden fields. This article discusses this issue in relation to PHP, but is relevant to all scripting languages.

Discussion

A distributed network of machines are employed to scan PHP-based websites in search of scripts which might be vulnerable to an injection-style security exploit. The exploit, if successful, permits the botnet to send emails to arbitrary destinations. A common target of the botnet is the kind of web-based feedback form which submits an email to a user-provided designated address. The botnet script injects malicious email headers into the form's fields, which are then passed to the mail server. The mail server parses those headers and then, if the attack was successful, it sends an email to the address designated in the maliciously injected headers.

Are My Scripts Vulnerable?

If you have a script that relies on user input for generating an email, then your script is potentially vulnerable to this exploit.

You should review the code for any of your scripts that rely on user input for constructing an email. In addition, you should examine any other fields in your scripts that may NOT rely on user input but which are still used in the generation of an email. Note the two error log snippets below.

In Snippet No. 1, the botnet has tried to manipulate form variables, a hidden form variable ('_submit') and other variables such as the session ID.

 

array(53) {
["HOME"]=> string(1) "/"
["PATH"]=> string(29) "/sbin:/bin:/usr/sbin:/usr/bin"
["_submit"]=> string(29) "yrfockfsy@example.com"
["password"]=> string(29) "yrfockfsy@example.com"
["PHPSESSID"]=> string(445) "yrfockfsy@example.com
Content-Type: multipart/mixed; boundary=\"===============1678997057==\"
MIME-Version: 1.0
Subject: 8526bb87
To: yrfockfsy@example.com
bcc: Homeiragtime@aol.com
From: yrfockfsy@example.com

This is a multi-part message in MIME format.

--===============1678997057==
Content-Type: text/plain; charset=\"us-ascii\"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
fvf
--===============1678997057==--
"

 

 

In Snippet No. 2, the botnet has again tried to manipulate form variables, a hidden form variable ('_submit') and other variables such as the session ID but in this case, the submitted values have been changed.

 

array(53) {
["HOME"]=> string(1) "/"
["PATH"]=> string(29) "/sbin:/bin:/usr/sbin:/usr/bin"
["_submit"]=> string(25) "rfljy@example.com"
["password"]=> string(438) "rfljy@example.com
Content-Type: multipart/mixed; boundary=\"===============1104808547==\"
MIME-Version: 1.0
Subject: da79e5ec
To: rfljy@example.com
bcc: Homeiragtime@aol.com
From: rfljy@example.com

This is a multi-part message in MIME format.

--===============1104808547==
Content-Type: text/plain; charset=\"us-ascii\"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

twjgdcbd
--===============1104808547==--

"
["PHPSESSID"]=> string(25) "rfljy@example.com"

 

 

Solution

To prevent an email header injection attack, you need to filter all input data including any other fields on your forms (such as hidden fields) that may be used by your scripts in the creation of an email. The actual filtering implementation is a matter of programming style.

You can prevent the attack by performing a data validation procedure that will not allow a script to run until the unwanted strings have been removed. For example, the following pattern can be used with PHP's preg_match function. If the function returns false, you can prevent the user from proceeding.

Pattern for filtering email addresses:

 

"/^([a-z0-9._-](\+[a-z0-9])*)+@[a-z0-9.-]+\.[a-z]{2,6}$/i"

 

 

Using the pattern for email addresses, you might do the following:

 

$emailPattern = "/^([a-z0-9._-](\+[a-z0-9])*)+@[a-z0-9.-]+\.[a-z]{2,6}$/i";
if (!preg_match($emailPattern, $emailFieldToTest))
{
print 'Please review the email address you entered. There seems to be a problem';
}

 


Why does this Exploit Work? Is PHP Insecure?

This exploit has nothing to do with the behavior of PHP and, therefore, is not a shortcoming of PHP. The exploit "works" because of the MIME and SMTP standards.

A basic MIME message is broken into two parts; a header and a body. The header and body are separated by a blank line, i.e., \r\n\r\n (although most mail clients will accept UNIX and other line delimiters, like \n\n or even \n\r\n\r). MIME is also multi-part which means that messages can be embedded in other messages. This is done by having the top level header declare a boundary, which is then used to process further header/body pairs.

If someone is able to inject content into this top level header, they can now essentially control the structure of the entire message. This is done by creating their own boundary, thus defining what's a body and what's a header.

The other significant point of weakness that's taken advantage of is the fact that MIME (and subsequently SMTP) can deal with multiple headers of the same name. Meaning, if there is a To: followed by a To: they are interpreted "correctly" with no errors.

Putting it all together, this allows an attacker to define additional recipients for a message, not to mention adding Bcc: and Cc: headers using multiple headers of the same name. In addition the attacker can define body parts to be sent as the content of the message by defining a multi-part message. So even user input intended as content is vulnerable to the injection of a header instruction. That is why all input data must be filtered.

Conclusion

This article examined the specifics of a particular script exploit and is not meant to protect all PHP scripts from all exploits. As often happens, once one security hole has been closed, another one is found. The important lesson is the need to know exactly how your script will behave and, most importantly, exactly what data you expect to receive from the script.

A solid architecture would assure that:

  1. You know what you are sending out
    Keep track of forms you've put on your site and develop a policy for accepting form submittal (for instance time outs, multiple forms per user id, multiple submissions, not accepting forms you don't expect, etc). This can be implemented using tokens.
  2. You know what you should be getting back
    This is crucial. Just because a <select> field contains certain values, don't think you can't get back something totally different such as PHP code, SQL, etc.
    • Know the fields you need to have back to accept the form as valid.
    • Restrict exactly what values you'd accept as input.
    • Always minimize taking data from forms (or any external source) and using it directly in database queries, or other inner and intimate parts of your application.