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