Preventing Mailman Subscription Spam

Mailman is an opensource software for managing mailing lists. As with everything on the internet, it’s subject to abuse. One of the mailman services we maintain experienced some wild success with 18K new subscriptions in 24 hours… or did it.

root@mailman:/var/log/mailman# cat subscribe|wc -l
18099

Let’s see who is responsible for these subscriptions.

root@mailman:/var/log/mailman# cat subscribe |awk '{print $NF;}'|sort|uniq -c|sort -nr|head -5
   3942 128.199.129.xxx
   1209 138.199.50.xxx
    514 49.64.213.xxx
    266 220.165.247.xxx
    261 114.218.207.xxx

And what domains the subscriptions were for.

root@mailman:/var/log/mailman# cat subscribe |awk '{print $8;}'|cut -d @ -f2|sort|uniq -c|sort -rn |head -10
   4103 yahoo.com
   3910 gmail.com
   2527 qq.com
    895 htl-salzburg.ac.at
    746 tmomail.net
    587 aol.com
    485 comcast.net
    289 163.com
    276 itp.ac.cn
    273 afejendomsservice.dk

Halt the celebrations… it’s time to implement some controls.

The mailman subscription form has CSRF functionality. You can implement it trivially by generating an random base64 string:

openssl rand -base64 18

Then append that string to the mm_cfg.py config as follows:

SUBSCRIBE_FORM_SECRET = "SvfUwoH2JuQGzP/7VKgL2vVO"

Restart mailman and you will now see the following on the form element:

<input type="hidden" name="sub_form_token" value="1636287646:49cca8aac23bef337e71049e63c1417f84e201cb">

Some attackers are a little more creative and still manage to subscribe. We need some limits around the number of subscriptions accepted from a single ip. Let’s create some fail2ban rules.

If you don’t have fail2ban installed, all distributions known to me have it packaged:

  • Debian derivative: apt install fail2ban
  • RH/Centos: yum install fail2ban
  • Fedora: dnf install fail2ban

In /etc/fail2ban/filter.d/mailman.conf, add the following:

# Fail2Ban filter to match abusive subscription ips
#

[Definition]
failregex = .*: pending .* <HOST>$
ignoreregex = 

In /etc/fail2ban/jail.d/mailman.conf, add the following and tweak:

  • findtime: the period of which ‘retries’ are scanned
  • bantime: ban duration
  • maxretry: how many successful subscriptions from a single ip are allowed in ‘findtime’
[mailman]
enabled = true
findtime = 12h
banaction = route
bantime = 86400
maxretry = 5
filter = mailman
logpath = /var/log/mailman/subscribe

Next restart fail2ban and look in /var/log/fail2ban.log for jail activation:

systemctl restart fail2ban
2021-11-09 23:37:15,127 fail2ban.jail           [24389]: INFO    Creating new jail 'mailman'
2021-11-09 23:37:15,127 fail2ban.jail           [24389]: INFO    Jail 'mailman' uses pyinotify {}
2021-11-09 23:37:15,131 fail2ban.jail           [24389]: INFO    Initiated 'pyinotify' backend
2021-11-09 23:37:15,136 fail2ban.filter         [24389]: INFO    Added logfile: '/var/log/mailman/subscribe' (pos = 812009, hash = 1585a07c06f0bfbd0d8219c2691dcadceaecd225)
2021-11-09 23:37:15,138 fail2ban.filter         [24389]: INFO      encoding: ANSI_X3.4-1968
2021-11-09 23:37:15,139 fail2ban.filter         [24389]: INFO      maxRetry: 5
2021-11-09 23:37:15,139 fail2ban.filter         [24389]: INFO      findtime: 43200
2021-11-09 23:37:15,139 fail2ban.actions        [24389]: INFO      banTime: 86400
2021-11-09 23:37:15,144 fail2ban.jail           [24389]: INFO    Jail 'mailman' started

Illustration of Vince

Vince Hillier is the President and Founder of Revenni Inc. He is an opensource advocate specializing in system engineering and infrastructure. Outside of building solid infrastructure that doesn't break the bank, he's interested in information security, privacy, and performance.