A while ago, when this web site was really getting going, I discovered the need for a mail form to help people communicate back. At the time I was having problems with random senders being blocked and this seemed the easiest way to do it. I found what looked to be a good system in Jack’s Formmail.php v5.0!. I went through it carefully, pulled out parts which uploaded files to the server which I thought were dangerous, and used it. This has been running for a couple of years—until today.
Today I got a heap of bounced emails into an account that is not normally used much and looking at a few, it was clear that the originals had been generated by my modified script; I had signed them.
The content of a form generated email from my script is as follows, much the same as the original in fact:
1. To: [recipient] 2. Subject: [subject] 3. MIME-Version: 1.0 4. From: "[realname]" <[email]> 5. Reply-To: [email] 6. X-Mailer: DT Formmail5.0_RJP_2 7. Content-Type: multipart/mixed; 8. boundary="----=_OuterBoundary_000" 9. This is a multi-part message in MIME format. 10. 11. ------=_OuterBoundary_000 12. Content-Type: multipart/alternative; 13. boundary="----=_InnerBoundery_001" 14. 15. 16. ------=_InnerBoundery_001 17. Content-Type: text/plain; 18. charset="iso-8859-1" 19. Content-Transfer-Encoding: quoted-printable 20. 21. realname: [realname] 22. email: [email] 23. message: [message] 24. 25. Message sent by formail.php v5.0_RJP_2 from [HTTP_REFERER] 26. 27. 28. ------=_InnerBoundery_001-- 29. 30. ------=_OuterBoundary_000--
Lines 1 & 2 are generated by the PHP mail()
routine, lines 4 & 5 are generated by the script, lines 21-23 are obtained from the input form and the rest is pretty much fixed. I think my web host inserts Return-Path; [account email address]
after line 8 and adds
Received: (from [account]@localhost) by west-penwith.org.uk (8.12.11/8.12.11/Submit) id [id]; [datestamp] Date: [datestamp] Message-Id: [id]
to the front before the mail leaves home. [recipient]
is fixed to my email address and coded in the script so that it can’t be harvested, [subject]
is, I presume, sanitised by mail()
. [email]
, the sender’s email address, is checked using a regexp and malformed ones rejected. [message]
is not checked, but is protected by being inside a MIME type text/plain part.
Have you seen the flaw yet?
Looking at the bounced messages kindly provided by Yahoo! where they quoted the incoming message in full there were some strange additions. There was a big gap between lines 4 and 5 containing the spammy message. Also lines 21-23 were in a different order and there was a lot of additional text before line 25. This consisted of the same spammy message and a very long "bcc:"
list. What they had done was inject
[bogus email address at west-penwith.org.uk] Content-Transfer-Encoding: quoted-printable Content-Type: text/plain Subject: [spammy subject] bcc: [lots of email addresses] [Spammy content] ..
as the value for my variable [realname]
and I hadn’t validated it. The insertion into the content of the message was benign, but in the header, it was taken and interpreted as written which is why all those bcc addresses were sent rubbish. As a last minute alteration, I thought it would be nice to have the mail come from a real person rather than just an email address and didn’t think of the consequences.
Verdict: Guilty as charged.
What I am curious about is a) why the mail stream wasn’t terminated by the “..” before line 5 as it is supposed to be and b) how they discovered the flaw. Unfortunately I haven’t been receiving mail from this form for a few weeks (this may be related) so I would have missed any test runs. Do they have a bot which picks up the variables used by a form and try injecting rubbish into them to see what happens, or has a human cracker been on the job?
I should point out that the flaw that was exploited was not in the original script, it was caused by an alteration I had made. The [subject]
may also be vulnerable in a similar way, though it would depend on how mail()
interprets it, the manual is vague on this. There are a lot of other things which even the original script doesn’t check so perhaps I should look around for a better one.
All I can assume is that anyone else using the same script is going to be vulnerable to a similar attack.
I really am no expert, but if you work on the principle that what a spammer wants to inject into your email headers is essentially a long list of email addresses, if you set up a few small lines of code to remove “to:”, and “cc:” from your email body variable then i would have thought you make it a no win situation for them (removing “cc:” would leave only “b” from “bcc:” of course – thats not gonna send their message to a long list of email addresses!)
For example, if your email is built by adding attribute=value pairs to a variable called $subject, before you fire off the email to yourself at the end of the script, do something like
$subject = eregi_replace(“cc:”,””,$subject) //case insensitive replace to change “cc:” to nothing in the variable $subject
Maybe add the same line, but next time replacing “to:” with nothing – I may be wrong but I have a memory that you can have as many “to’s” in an email as you like. Well, even, you could replace the @ with something arbitrary like =A=, then for a genuine person sending you a single email you would know what to do to reply to them – whereas a spammers list is going to fall over if its sent to people=A=somehost.com
Like I said, I aren’t an expert but in my simplistic way of looking at it, whatever other rubbish they can inject into your headers, at the very least only you will be getting the email.
Hi Chris,
the good news is that, for this particular problem at least, it was self inflicted and other users of the package should not be affected, except as I noted that it may be exploitable through similar variables. Your solution in general is a good one and is roughly what I will be doing but working from a better base – that is when the tuits are available.