I have recently implemented a simple php contact form for a client who had suffered header injection spam with the previous form. My solution is holding up well for now but I would like opinions from others experiences.
Rather than search for \r\n or to: cc: bcc: or content-type or . . . I decided to validate the single line entries and test the message contents to ensure that injection type data was just sent as a message and didn't create any additional messages.
The form has only four fields: name,email,subject,comments. What I did was to use regex to validate the first three:
Name ^([A-Z '-]+)$
Contains alphas, dashes, spaces and apostrophe for O'Malley. Names longer that 50 characters are truncated and have ... added
Subject ^([A-Z0-9 '-:\!\?]+)$
Similar to name with the addition of some punctuation. Long subjects are truncated in the same way as long names.
Note that neither of these fields can contain @ signs, CRLF characters or the % that the hex equivalents would contain.
This only allows one email address that I use as a reply-to but not as a from. No surprises created by replying to multiple email addresses. This will not allow a valid email address with a .museum domain but the client doesn't expect these. They would be allowed if I used (2,6) instead. At present, I am also truncating email addresses longer than 50 characters - I find it hard to imagine that a regular human user typing an email address into a box on a form could have an email address longer than 20 - 30 characters at the most.
For the actual message, I want to allow anything and preserve the crlf for formatting. I found that I could create an extra header if I a \r\nbcc:firstname.lastname@example.org at the very end of the message. Then, when I added my \r\n to send the message, an email was sent to the injected address. To prevent this from happening, I just added a string at the end of the message. In my case, I added the IP address originating the form message. But pretty well any "end of message" string would work, it seems to me.
Magic_quotes are on and I strip_slashes before sending the data - also strip_tags, keeping \n (but not \r) for only the message and then replacing the \n with <br>\r\n to keep paragraph formatting. This seems to sometimes add extra paragraph spacing so if anyone has a comment about this I'd appreciate it.
I created test data using the types of header injection data described at
but using my own email addresses. I checked the headers of the email that arrived at the recipient and also checked to see whether any email arrived at my additional email addresses.
I know that they can be spoofed but I also check for valid referrers using $_SERVER['HTTP_REFERER'] and an actual (i.e. not blank) browser using $_SERVER['HTTP_USER_AGENT'] - should I bother or do most spammers spoof these?
I check that $_SERVER['REQUEST_METHOD'] is 'post' because I saw it suggested somewhere but I don't really understand the reasoning behind it so if anyone can enlighten me I would appreciate it.
To hopefully prevent flooding, the system uses a session variable to prevent another message being sent after the first one. If the user closes the browser and re-opens it, they can send a second message.
I calculated that most normal users would take about 30 seconds at the very least to type an entry in every field of the form and a couple of lines in the message so I added a time delay. I record a timestamp at the start of the session and calulate the elapsed time just before sending the message. If the elapsed time is less than 30 sec, I just sleep until the 30 sec is up. Actually, I had to sleep for 2 sec, output a blank and then sleep for 2 sec in a while loop or the browser created a time out.
If anyone wants to or is willing to test my form, I will give you a link to my test version of it if you PM me.
Is there anything different that people think I should be doing? Or? This is working right now but I am a relative novice at php and I am hoping that others who know more will have some comments on what I have come up with.
Thanks in advance for any/all comments.