Learn how simply using the email address entered in an online form as the return address leaves sites open to one of the most commonly exploited attacks. This video covers a golden oldie that deserves retelling time and time again.
- [Instructor] Hi, I'm David Powers and welcome to this week's edition of PHP tips, tricks and techniques designed to help you become a smarter, more productive PHP developer. Email header injection is one of the oldest malicious exploits of online forms. Unfortunately, it's also a trap that inexperienced PHP developers frequently fall into. This scrip, which you can download in the exercise files for this video, is typical of the poorly written code that I've come across time and time again.
It begins by assigning values submitted through the post array to simple variables. Then it builds the body of an email message and then uses the mail function to send the message. The problem lies in here. In the fourth argument and the way that it's being used. This argument allows you to set additional headers to be added to the email. Now there are several things that are wrong with this, but the most important is that email, that's unfiltered input that's come from the online form and it's being inserted directly into the email headers.
It's too easy for an attacker to side-step. Validation must be done on the server with PHP. Now checking email addresses with a regular expression is notoriously difficult. The pattern is either too crude or hopelessly complicated. But you can let PHP's filer input function do the hard work for you. So up here on line four, let's get rid of that post email. And instead we'll assign to the email variable, filter input, that's a built-in PHP function.
The first argument is the type of input and this is a PHP constant. So, we're using the post array. It's input_post. If you're using the get array, it becomes input_get. The second argument is a string containing the name of the field that you want to validate. So in this case it's email and then the third argument is how you want to filter it. Again we're going to use a PHP built-in constant.
And that is filter validate email. Now this won't actually check that the email address exists or that it's live, it simply checks that the submitted value is in a valid format for an email address and crucially, that it contains nothing else. If the value looks okay, the function returns the email address, otherwise it returns false and doing this alone protects the script from email header injection because email will be false if any attempt is made to add spurious headers through that field.
So, even though we've got the from header down here and we're putting email in it, nothing will happen because that will be false. But, if email is false, there's no point in even attempting to send the email. So we can wrap the rest of the script in a conditional statement. So just before building the message, conditional statement, so if email, if it contains a value, that will equate to true, otherwise it will equate to false. Then we have our curly braces to wrap the rest of the script.
So, if email is false, none of this script will be executed, but we could have an else clause down here to handle the situation where you have an email which is false. But if we've got something that looks like a valid email address, this will be executed. So far, so good, but there's a problem. It's a common misconception that it's a good idea to put the user's email address in a from header like this because it makes it easy to reply to user feedback. It works. But it can also result in the email from the form failing to be delivered.
That's because many mail servers check that the domain name in the from address is the same as the server originating the email. In other words, it needs to be a valid address on your server. If it isn't, there's a strong likelihood that the email will be bounced. There's a separate header for where replies should be directed. So let's create both headers. I'm going to create them separately here. I have a variable called headers. Then a double quoted string.
From and then we'll have, let's say, email@example.com. Obviously this needs to be a correct email address on your own domain. Then the escape sequences for carriage return and new line character. And then the reply to header and that's where we'll put the email that's been submitted through the form. So, the fourth argument to the mail function now becomes headers.
So with just a few lines of code the script now not only protects the form from email header injection, but it also uses the from and reply to headers correctly, improving the likelihood of the email from the form being delivered. Well there's a lot more that could be done to improve this script and I haven't delved into all the details of how the mail function works. The focus has been on how to prevent email header injection. You're vulnerable to this type of attack if you use unfiltered user input in the additional headers passed as the fourth argument to the mail function.
You can't rely on validation in the browser. It must be done on the server. A simple way to do this with PHP is to use the filter input function. The first and third arguments are PHP constants. The second argument is a string containing the name of the element in the post array that you want to check. If the submitted value looks like a single valid email address, it returns the value, otherwise it returns false. You can use the result to block the rest of the script if the return value is false.
We also looked at the correct use of additional headers. The from header should contain a valid email address on the website's domain. The reply to header can be used for the submitted email address as long as it's been validated. It's surprising how many beginners are unaware of this basic knowledge and security precautions. If you're a seasoned developer, please pass the message on. Well, that's all for this week's PHP tips, tricks and techniques. I hope you can join me next time.
Note: The exercise files are free to all members. The code is commented to enhance your learning, but you will need database connectivity for some files to run as intended.