How to deal with joe-jobs and massive bounce storms

As I’ve noted before, we still have a major problem with sites generating bounce/backscatter storms in response to forged mail — whether deliberately targeted, as a “Joe-Job”, or as a side-effect of attempts to evade over-simplistic sender address verification as seen in spam, viruses, and so on.

Sites sending these bounces have a broken mail configuration, but there are thousands remaining out there — it’s very hard to fix an old mail setup to avoid this issue. As a result, even if your mail server is set up correctly and can handle the incoming spam load just fine, a single spam run sent to other people can amplify the volume of response bounces in a Smurf-attack-style volume multiplication, acting as a denial of service. I’ve regularly had serious load problems and backlogs on my MX, due solely to these bounces.

However, I think I’ve now solved it, with only a little loss of functionality. Here’s how I did it, using Postfix and SpamAssassin.

(UPDATE: if you use the algorithm described below, you’ll block mail from people using Sender Address Verification! Use this updated version instead.)

Firstly, note that if you adopt this, you will lose functionality. Third party sites will not be able to generate bounces which are sent back to senders via your MX — except during the SMTP transaction.

However, if a message delivery attempt is run from your MX, and it is bounced by the host during that SMTP transaction, this bounce message will still be preserved. This is good, since this is basically the only bounce scenario that can be recommended, or expected to work, in modern SMTP.

Also, a small subset of third-party bounce messages will still get past, and be delivered — the ones that are not in the RFC-3464 bounce format generated by modern MTAs, but that include your outbound relays in the quoted header. The idea here is that “good bounces”, such as messages from mailing lists warning that your mails were moderated, will still be safe.

OK, the details:

In Postfix

Ideally, we could do this entirely outside Postfix — but in my experience, the volume (amplified by the Smurf attack effects) is such that these need to be rejected as soon as possible, during the SMTP transaction.

Update: I’ve now changed this technique: see this blog post for the current details, and skip this section entirely!

(If you’re curious, though, here’s what I used to recommend:)

In my Postfix configuration, on the machine that acts as MX for my domains — edit ‘/etc/postfix/header_checks’, and add these lines:
/^Return-Path: <>/                              REJECT no third-party DSNs
/^From:.*MAILER-DAEMON/                         REJECT no third-party DSNs
Edit ‘/etc/postfix/null_sender’, and add:
<>              550 no third-party DSNs
Edit ‘/etc/postfix/’, and ensure it contains these lines:
header_checks = regexp:/etc/postfix/header_checks
smtpd_sender_restrictions = check_sender_access hash:/etc/postfix/null_sender
(If you already have an ‘smtpd_sender_restrictions’ line, just add ‘check_sender_access hash:/etc/postfix/null_sender’ to the end.) Finally, run:
sudo postmap /etc/postfix/null_sender
sudo /etc/init.d/postfix restart
This catches most of the bounces — RFC-3464-format Delivery-Status-Notification messages from other mail servers.

In SpamAssassin

Install the Virus-bounce ruleset. This will catch challenge-response mails, “out of office” noise, “virus scanner detected blah” crap, and bounce mails generated by really broken groupware MTAs — the stuff that gets past the Postfix front-line.

Once you’ve done these two things, that deals with almost all the forged-bounce load, at what I think is a reasonable cost. Comments welcome…

This entry was posted in Uncategorized and tagged , , , , , , , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.


  1. Posted January 10, 2007 at 16:57 | Permalink

    My technique for dealing with blow-back from third-party hosts is to check that the To: header of the bounce matches one of the envelope return paths I use when sending email. This isn’t a particularly scalable check (by which I mean I couldn’t apply it to all of my users’ email) but it works well for competent individuals.

  2. Devdas Bhagat
    Posted January 11, 2007 at 00:16 | Permalink

    Why not just use a check_sender_access

    check_sender_access hash:/etc/postfix/null_sender

    where /etc/postfix/null_sender contains

    <> 550 Joejob victim

    Header checks are far more expensive than sender checks in terms of resources used.

  3. Posted January 11, 2007 at 00:47 | Permalink

    uh, yeah, probably — I’m not much of a Postfix wiz ;) let me try that out…

  4. Posted January 11, 2007 at 02:06 | Permalink
  5. Posted January 11, 2007 at 14:12 | Permalink actually scared me off — it starts off with some good basic advice, but quickly gets very complex! The latter half actually recreates a fair bit of the VBounce ruleset, but in Postfix (where it’s a good deal riskier and hairier.) Best to avoid it, I think.

    Devdas — your tip looks good; rejecting most bounces at RCPT now. for what it’s worth, here’s the cut-and-paste log — I’ll probably move this into the main post soonish:

    sudo vi /etc/postfix/null_sender
    <>              550 no third-party DSNs
    sudo vi /etc/postfix/header_checks
    /^Return-Path: <>/                              REJECT no third-party DSNs
    /^From:.*MAILER-DAEMON/                         REJECT no third-party DSNs
    sudo postmap /etc/postfix/null_sender
    sudo /etc/init.d/postfix restart
  6. Posted January 11, 2007 at 19:10 | Permalink

    Devdas: thanks for the tip, seems to be working nicely. I’ve updated the main post to reflect that.

  7. Devdas Bhagat
    Posted January 11, 2007 at 20:05 | Permalink

    Hmmm, I recommend putting all your restrictions in smtpd_recipient_restrictions. The default is to delay evaluation till the RCPT stage, and life is made much simpler by keeping all your restrictions in one place. is probably one of the best places to start with (slightly dated, but still valid).

    The Return-Path header is added by the local delivery agent (local(8), virtual(8) or pipe(8)), so looking for that in incoming SMTP traffic isn’t going to be useful.

  8. Posted January 11, 2007 at 21:04 | Permalink

    Two things:

    1.) “Sites sending these bounces have a broken mail configuration,”

    No, sites sending these bounces are complying with RFC2821, section 3.7, which states:

    ” If an SMTP server has accepted the task of relaying the mail and later finds that the destination is incorrect or that the mail cannot be delivered for some other reason, then it MUST construct an “undeliverable mail” notification message and send it to the originator of the undeliverable mail (as indicated by the reverse- path). Formats specified for non-delivery reports by other standards (see, for example, [24, 25]) SHOULD be used if possible.”

    The relaying server is required by the RFC to deliver a bounce to the address in the reverse-path. If you feel this requirement is in error, feel free to propose a replacement for the SMTP specification.

    2.) I’m not a postfix guy, but it looks like you are disabling your ability to receive bounces of any kind, which is very rude to systems your users may have legitimately e-mailed and generated bounce messages at. It’s your system and you’re welcome to do that, but that’s a first-class ticket into, as RFC2821 and RFC1123 both point out the requirement of sites to accept bounce messages, and not reject messages simply because they have a null-envelope.

  9. Devdas Bhagat
    Posted January 11, 2007 at 22:09 | Permalink

    Derek, a very large percentage of blowback/backscatter is generated by hosts which do not do proper recipient validation in the SMTP transaction. Yes, your users might not get a legitimate bounce, but the cost of processing all the other bounces is just too high.

    And rejecting bounces while you are being joe-jobbed is a perfectly valid response. I believe that not even rfc-i will list in case a valid reason for rejection is provided.

  10. Albert
    Posted February 23, 2007 at 02:31 | Permalink

    “Whoever fights monsters should see to it that in the process he does not become a monster.” Friedrich Nietzsche, from Beyond Good and Evil

    I fully agree with Derek.

  11. Nicos
    Posted June 29, 2007 at 14:34 | Permalink

    Just found this, so please bear with me posting a late comment.

    Nietzsche was a nice guy, but not everything he said applies everywhere. Face it, SMTP is broken. It’s lacking accountability, and it’s really good at it. An SMTP server rarely ever knows who it’s talking to, and then there is no guarantee at all that the information is valid. This and a ton of other design decisions turned out very bad by now, so breaking the standard is the only way to keep using it.

    A server can only be (moderately) assured that it’s not being conned into something as long as the original incoming TCP connection lasts, so that is the only window of opportunity for proper error handling, and it’s the only valid path as well. Assuming that incoming data is benign is a fallacy, because in 80-99%, it’s not! A lot of the RFCs the protocol is based on assume a safe internet. Kiss that illusion goodbye.

  12. mouss
    Posted July 31, 2007 at 22:24 | Permalink

    smtp is not broken. It is a tool. If it doesn’t do the job, use another tool.

  13. Perry E. Metzger
    Posted October 4, 2007 at 23:17 | Permalink

    The problem with your “trick” here is that it interferes with your ability to send mail to anyone who (like me) is using postfix Sender Verify. A friend of mine mysteriously stopped being able to send me mail — it turned out his ISP had adopted your hack.

  14. Posted October 5, 2007 at 11:24 | Permalink

    hi Perry —

    I know. I revisited this back in May to fix that bug. I guess that ISP didn’t read that bit :(

    I’ll update this story to point to the May post, too.

  15. Cris
    Posted December 24, 2007 at 04:42 | Permalink

    Tony — about your “sanity check” for backscatter… Some spammers are trying to defeat some checks that are similar. Take a look at what I discovered here: