I inherited a problem when I became webmaster at an established ISP. (More than just one, but I have this one in mind at the moment.) Like many ISP's we use Matt Wright's formmail.pl, making it available to customers. This script takes the contents of a web form and converts it to an email, sent to a recipient mentioned in a type=hidden tag.

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}

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') } }
(code from http://worldwidemart.com/scripts/cgi-bin/download.cgi?s=formmail&c=txt&f=FormMail.pl. Copyright 1995-2001 Matt Wright)

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?


In reply to Port 80 Mail Relay (Spammers Welcome?) by ginseng

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.