ginseng has asked for the wisdom of the Perl Monks concerning the following question:
Lately, with the increased difficulty of finding open relays, spammers and worse have resorted to locating servers with formmail.pl installed. They structure a GET request that specifies the content of an email, and its recipient. The web server brings in the request and gladly sends it out, provided that the most basic requirements are met.
This has happened to me, or at least to one of the servers for which I'm responsible. As it turns out, I didn't have a spammer relaying through my web server; I had a credit card fraud. The jerk has a worn-out scheme sending emails to AOL subscribers, telling them their credit card number was lost, etc, and please go to the new billing center and re-enter their data. I would love to think that no one is naive enough to fall for this, but if it were me, I'd guess that AOL is a good place to look for people naive enough to fall for this.
Of course, in the last week since this particular jerk started relaying through my web server, 56 others have probed for this hole.
My solution to the problem involved looking at how the hole has been plugged in the newest release, and I'm afraid I'm not impressed. (Go figure.) Basically, Matt has added an array of "allowable recipients"; if the specified recipient isn't in the list, it isn't sent.
This is fine if you have one web site hosted on a server, but when you have hundreds, many with contract designers and frequent changes, it gets just a bit unwieldy trying to keep such a list up-to-date, or trusting that each domain's local copy of the script is correctly configured. (I have it symlinked to prevent the need to maintain a hundred or more copies.)
There was a previous effort to control it based on the referrer, as shown here:
{snip}
(code from http://worldwidemart.com/scripts/cgi-bin/download.cgi?s=formmail&c=txt&f=FormMail.pl. Copyright 1995-2001 Matt Wright)sub check_url { # Localize the check_referer flag which determines if user is vali +d. # local($check_referer) = 0; # If a referring URL was specified, for each valid referer, make s +ure # # that a valid referring URL was passed to FormMail. + # if ($ENV{'HTTP_REFERER'}) { foreach $referer (@referers) { if ($ENV{'HTTP_REFERER'} =~ m|https?://([^/]*)$referer|i) +{ $check_referer = 1; last; } } } else { $check_referer = 1; } # If the HTTP_REFERER was invalid, send back an error. + # if ($check_referer != 1) { &error('bad_referer') } }
What I don't get is that the referer is ignored if not provided. Also, it still requires @referers to contain a list of all domains using the script, another unwieldy maintenance job.
My solution was a kludgy hack:
local($check_referer) = 0; my $refhost = $ENV{'HTTP_REFERER'}; $refhost =~ s/^.*\/\///g; $refhost =~ s/\/.*$//g; my $refip = `dnsip $refhost`; chomp $refip; $refip =~ s/\s+$//g; print STDERR "[" . localtime() . "] [formmail referer check] \$ENV +{'REMOTE_ADDR'} = $ENV{'REMOTE_ADDR'} [$ENV{'REMOTE_HOST'}] \$ENV{'HT +TP_REFERER'} = $ENV{'HTTP_REFERER'} \$refhost = $refhost \$refip = $r +efip.\n"; foreach $referer (@referers) { ($refip eq $referer) and $check_referer = 1; } # If the HTTP_REFERER was invalid, send back an error. + # if ($check_referer != 1) { &error('bad_referer') }
This does a dns lookup (I use Daniel Bernstein's programs whenever possible) of the referrer and compares that to the list of acceptable IP addresses in @referer. (Side note: I've never understood why the HTTP standard misspelled referrer. Anyone know?) Notice that it also fails if a referrer is not provided. (When is the referrer not available? I'd rather support the fewest calls possible; this strikes me as the method.) Also, it logs all formmail requests in the error log, with the client's address and the referring domain.
However, this method has a fatal flaw: it is trivial to forge the referrer. Matt Wright's solution does solve this, by restricting the recipient list. I thought hard about doing the same thing; but (a) I don't want the headache of taking care of this script forever and ever with customers, and (b) many of the customers have the form sent "off-site", to a domain hosted elsewhere, and need control over the recipient list.
So my question, wise ones, is how to fix this? (Whoa, broad question. I'll try to narrow things down a bit.)
First, are there secure alternatives to this script, preferably that don't involve telling my customers they need to rewrite all their forms?
Second, what would you do? Throw it away and start from scratch? (I'm considering it...) Patch and hack, keeping a step ahead of the spammers and frauds? Limit recipients and deal with the administrative tasks?
Third, if you were saddled with this legacy, maybe even considering a complete (but mostly compatible) rewrite, how would you prevent creating a port 80 relay?
|
|---|