Proper prevention of mail injection in PHP
To filter valid emails for use in the recipient email field, take a look at filter_var()
:
$email = filter_var($_POST['recipient_email'], FILTER_VALIDATE_EMAIL);
if ($email === FALSE) {
echo 'Invalid email';
exit(1);
}
This will make sure your users only supply singular, valid emails, which you can then pass to the mail()
function. As far as I know, there's no way to inject headers through the message body using the PHP mail()
function, so that data shouldn't need any special processing.
Update:
According to the documentation for mail()
, when it's talking directly to an SMTP server, you will need to prevent full stops in the message body:
$body = str_replace("\n.", "\n..", $body);
Update #2:
Apparently, it's also possible to inject via the subject, as well, but since there is no FILTER_VALIDATE_EMAIL_SUBJECT
, you'll need to do the filtering yourself:
$subject = str_ireplace(array("\r", "\n", '%0A', '%0D'), '', $_POST['subject']);
Is this mail() function safe from header injection?
Header injection relies on being able to insert additional newlines into header variables, which makes the string look like a new header.
For example, allowing a subject value of Testing\nCc: spamrecipient@example.com\n\nSome body text
would result in a message header containing:
Subject: Testing
Cc: spamrecipient@example.com
Some body text
i.e. the abuser has not only added additional recipients, but they've managed to supply their own body text too.
However in your case the $toaddress
is constant, and even if $toaddress
had been user-supplied it should be correctly sanitised by the mail()
function.
Your subject header is similarly constant
The $message
variable is safe because by definition that's the body text and only sent after the real headers.
That only leaves $fromaddress
, and you're already using FILTER_VALIDATE_EMAIL
on that which should also reject anything with a newline in it.
However you should strictly be checking the result of that test, and aborting the whole thing if the result is FALSE
. As it is if the validation fails then mail()
will complain about being given a blank From:
address, but there's no header injection opportunity there.
As far as I can tell, then, this code is actually secure.
Also, IMHO, you shouldn't send the emails from the user-supplied email address. That would fall foul of anti-spam mechanisms such as SPF.
You should use a constant From:
value belonging to your own domain. If you like you could then use a correctly sanitised value in the Reply-To
header to make it easier to have the subsequent reply go to the desired address.
Is there any injection vulnerability in the body of an email?
There's a possible injection in the body text if you're speaking native SMTP to the mail server.
A single .
on its own terminates the current body in SMTP, so in theory you could have user supplied input like this:
some body text
.
MAIL FROM: <...>
RCPT TO: <...>
DATA
Subject: here's some spam
here's a new body
and the SMTP server might allow the second message through.
Some SMTP servers can be configured to prevent this by not allowing SMTP commands to be pipelined (i.e. requiring the client to read the response before permitting the next command).
What protection is needed for PHP mail scripts?
If you're not using a database then no, you don't need to protect against attacks that exploit database queries. Emails have a whole set of exploits of their own and I recommend using a library such as phpmailer or swiftmailer which will help with this. Either way, you should always verify that the data submitted from the form is in the format you expect it to be.
Preventing mail injection in php intercepting some characters \r, \n, %0A, %0D
I did a test. The test result is a success! I tested the regex trying directly in localhost
with different methods:
<?php
$test = "the dog \n was \r sleeping on the floor";
if (preg_match_all('/(%0A|%0D|\\n+|\\r+|;|mime-version:|content-type:|content-transfer-encoding:|subject:|to:|cc:|bcc:)/i', $test, $tmp)) {
echo "I found this character: '";
print_r($tmp[1]);
echo "'";
} else {
echo "I cannot find any string searched";
}
?>
Result:
I found this character: 'Array ( [0] => [1] => ) '
Looking at source I can see the \n and the \r
I found this character: 'Array
(
[0] =>
[1] =>
)
'
So I think that the regex is well build.
Also other test I did with strpos()
:
if !(strpos($_POST['subject'],'\n') === false)) {
fails with single quotes while finds the \n with double quotes...
if !(strpos($_POST['subject'],"\n") === false)) {
Conclusions: regex is well formed and strpos()
needs "" to match \n or \r.
Are to, subject and body safe against injection using mail()?
Yes, that's true, but it's also incomplete. In the engine source code, the function php_mail_build_headers
ensures headers comply with RFC 2822 § 3.6 requirements for maximum number of values. Particularly, the following headers are checked for single value:
orig-date
from
sender
reply-to
to
bcc
message-id
in-reply-to
subject
Yes, the message parameter is safe from header injection by definition: the message part is inserted after the separating new line between headers and body, so any header-like text inserted as part of the message will appear as literal text within the message body.
How to prevent code injection attacks in PHP?
mysql_real_escape_string
used when insert into databasehtmlentities()
used when outputting data into webpagehtmlspecialchars()
used when?strip_tags()
used when?addslashes()
used when?
htmlspecialchars() used when?
htmlspecialchars
is roughly the same as htmlentities
. The difference: character encodings.
Both encode control characters like <
, >
, &
and so on used for opening tags etc. htmlentities
also encode chars from other languages like umlauts, euro-symbols and such. If your websites are UTF, use htmlspecialchars()
, otherwise use htmlentities()
.
strip_tags() used when?
htmlspecialchars
/ entities
encode the special chars, so they're displayed but not interpreted. strip_tags
REMOVES them.
In practice, it depends on what you need to do.
An example: you've coded a forum, and give users a text field so they can post stuff. Malicious ones just try:
pictures of <a href="javascript:void(window.setInterval(function () {window.open('http://evil.example');}, 1000));">kittens</a> here
If you don't do anything, the link will be displayed and a victim that clicks on the link gets lots of pop-ups.
If you htmlentity/htmlspecialchar your output, the text will be there as-is. If you strip_tag it, it simply removes the tags and displays it:
pictures of kittens here
Sometimes you may want a mixture, leave some tags in there, like <b>
(strip_tags
can leave certain tags in there). This is unsafe too, so better use some full blown library against XSS.
addslashes
To quote an old version of the PHP manual:
Returns a string with backslashes before characters that need to be quoted in database queries etc. These characters are single quote ('), double quote ("), backslash () and NUL (the NULL byte).
An example use of addslashes() is when you're entering data into a database. For example, to insert the name O'reilly into a database, you will need to escape it. It's highly recommeneded to use DBMS specific escape function (e.g. mysqli_real_escape_string() for MySQL or pg_escape_string() for PostgreSQL), but if the DBMS you're using does't have an escape function and the DBMS uses \ to escape special chars, you can use this function.
The current version is worded differently.
Related Topics
PHP Datetime() Class, Change First Day of the Week to Monday
Woocommerce: Set Country by Default in Checkout Page
Get Repeated Matches with Preg_Match_All()
How to Get the Request Parameters in Symfony 2
Relative Path in Require_Once Doesn't Work
Calculate Total Seconds in PHP Dateinterval
Easiest Way to Alternate Row Colors in PHP/Html
How to Pass Objects by Reference in PHP 5
Error: MySQLnd Cannot Connect to MySQL 4.1+ Using the Old Insecure Authentication
Protection Against Xss Exploits
Int Variable with Leading Zero
How to Password-Protect PHP Page
Adding Three Months to a Date in PHP
Non-Breaking Utf-8 0Xc2A0 Space and Preg_Replace Strange Behaviour