|
|
The Dark Side of CGIA Hands-on How-toSMfrom Brass Cannon ConsultingA little vague handwaving can often save hours of tedious explanation. |
It goes against everything I believe in to discourage anyone from trying to write useful code... but on the other hand, you need to be aware of the dangers involved in making your code (or anyone else's) available to the entire world.
Case in point: A chap (who's already been flamed by better men than me) tried to do a nice thing for the world by providing a whole set of Perl CGI scripts. One of them was called FormMail.pl, and it is, to put it simply, a bad script [PDF doc]. In its original form, it will happily allow anyone who has access to your webserver to use it to send mail... to anyone. Their message, but your server, your bandwidth, and your reputation. Spammers are actively searching the web for exploitable FormMail scripts.
This problem isn't going away. Just the other day I went looking for a "shopping cart" script to put on a client's site, and was horrified to find one that uses FormMail.pl to send orders to the site owner. That's not the worst of it: The author remarked that the "new" FormMail 1.9, which at least tries to fix some of the known security bugs, wasn't compatible with his code. What did he do? He provided a link to an OLD copy of formmail -- the one that has NO security fixes at all -- rather than fix his own code!
First, don't run formmail. That's easy. There are far better replacements -- cgiemail, originally from MIT, has a friendly challenge: it bets you can set it up on a working HTTP server in five minutes or less. It's inherently more secure than formmail because you can easily install it in such a way that the person who is filling out your web form cannot create ANY header lines.
It works like this: There is a "template" file which is just plain text. You can specify fields that will be plugged in by cgiemail when it runs. They are identified with square brackets: [name] will be replaced by the HTML form field called "name" and so forth. Create an HTML text box called "message" and put a variable called [message] in your template, and so forth. The install instructions for cgiemail give you the details, but that's enough information to let us explain and fix the problem.
You can set up cgiemail insecurely -- and there's a jerk out there who is actively trying to take advantage of that. The problem arises if you ask someone for a field and use it to create a header in your mail rather than limiting it to the body of the message. For instance, it seems harmless to ask the visitor for the Subject of the email, but a malicious visitor can inject a line break into the Subject, which your email utility may interpret as starting a new header line.
Now imagine that new header line is a "CC:" or "BCC:" header with a few thousand email addresses in it. All that spam goes out from YOUR server.
Ewwww.
The good news is That shouldn't work -- if you've set up your cgiemail template correctly.
| Right | Wrong |
| Subject: Mail from contact form | Subject: Message from [visitor] |
Rather that engaging in an "arms race" trying to strip out exploitable strings, I simply limit the use of form variables to the body of the message. Here's a sample contactform.txt for use with cgiemail:
To: me@example.com Subject: Message from contact form Your contact information: Name: [required-name] Your email: [required-mail] Your Message: [required-body] -- Sent using [$HTTP_USER_AGENT], from or through [$REMOTE_ADDR]
And here's the form that drives it:
<h2>Contact Form</h2> <form METHOD="POST" ACTION="http://example.com/cgi-bin/cgiemail/contactform.txt"> Your Name: <input type=text size=64 name="required-name"> <br> Your E-mail address: <input type=text size=64 name="required-mail"> <br> Your message: <textarea rows=6 cols=48 name="required-body"></textarea> <br> <input type="hidden" name="success" value="http://example.com/thanx.html"> <input type="submit" value="Send e-mail"> </form>
Yes, it's inconvenient that all the mail from your contact page has the same Subject -- but it's much more inconvenient to be abused as a spam amplifier. The first blank line in contactform.txt -- the one after "Subject" -- separates the email headers from the email body, and there is nothing variable in the headers for the bad guy to exploit. I repeat: do not use any variable from the form in the HEADERS of your template. Even the Apache variables, such as the captured IP address of the sender, go in the signature of the message.
There is also a patch to one of the modules that is supposed to fix the problem, even if you don't follow my tips. I applied it, just in case, but I don't count on it. Google for help with the patch command if it's not obvious how to make these changes.
Free bonus tip: By default cgiemail will show people a copy of the email that was sent. Sometimes you would prefer to send them to a nice bland "Your message has been sent" page instead. That is also supported (see the install docs!), but if you put it in as a relative link, it gets a tummyache and throws an error such as "403 no variable substitutions in template" followed by the name of your "success" page. Use a full "http://yada.example.com/yeah-it-worked.html" link instead to solve the problem.
If you want features that FormMail.pl provides and cgiemail doesn't, get NMS FormMail from SourceForge.com. The NMS project was started by some Perl gurus who took it upon themselves to restore Perl's reputation after formmail's insecurity dragged it through the mud.
Second, since the world is full of broken formmail scripts, here's a procmail recipe that is fairly good at detecting mail that originated at one of them:
:0 B
* 1^0 .*the result of your feedback form\. *It was submitted by
* 1^0 ^Folgendes wurde am
* 1^0 ^Folgende Mitteilung wurde gesendet von
* 1^0 ^Folgende Formulardaten wurden
* 1^0 ^Abaixo segue conteudo do formulario enviado por
* 1^0 ^Sono stati ricevuti i seguenti dati. Sono stati inviati da
* 1^0 ^Oto zamowienie zlozone przez formularz WWW
* 1^0 ^Wys.ano przy pomocy formularza przez:
{
LOG="formmail abuser "
:0
/dev/null
}
Third, as there are people soulless and spiteful enough to abuse those open formmail scripts, it's only common sense for the rest of us to protect ourselves against them to the best of our abilities. If someone comes to this server looking for a formmail.cgi, I give them one -- and here's what it looks like:
#!/usr/bin/perl print "Content-type: text/html\n\n"; print "<html><head><title>BUSTED!</title></head><body>"; print "Below are the results of your feedback form:"; print "Your IP address has been blacklisted as a formmail abuser." print "</body></html>";
Anything you put into your cgi-bin directory is executed, not merely served as static content. For that reason I had to write this as a CGI script, not a static page... but you'll notice that a CGI doesn't have to accept any parameters as input. This one simply generates a static page each time it is run.
You are invited to discuss this article with its author on the Brass Cannon Webboard.