Fighting spam on roundcube with modsecurity
Every couple of months, one of my users falls prey to phishing attacks, and send their login/password data to an unknown somebody who poses as… Well, as me, their always-friendly and always-helpful systems administrator.
What follows is, of course, me spending a week trying to get our systems out of all of the RBLs/DNSBLs. But, no matter how fast I act, there’s always distruption and lost mails (bounced or classified as spam) for my users.
Most of my users use the Webmail I have configured on our institute’s servers, Roundcube, for which I have the highest appreciation. Only that… Of course, when a user yields their username and password to an attacker, it is very successful at… Sending huge amounts of unrequested mail, leading to my server losing its reputation ☹
This week, I set two bits of mitigation strategies. The first one,
most straightforward, was to ask Roundcube to disallow sending mails
with over ten recipients. In a Debian install, this is as easy as
setting up the following variable in /etc/roundcube/config.inc.php
:
$config['max_recipients'] = 10
However, a dilligent spammer can still clog the server by sending many, many, many, many requests — maybe each of them with ten recipients only; last weekend, I got a new mail every three seconds or so.
Adding rate limit to a specific Roundcube action is not easy,
however, or at least it took me quite a bit of headbanging to get it
right ☹. Roundcube is a very AJAX-y system where all (most, at least)
actions are received by /index.php
and there is quite a bit of
parsing to do to understand the actions done. When sending a mail, of
course, it is done using the POST
HTTP verb, and the URI-specified
variables include _task=mail&_unlock=loading<message_id>
(of course,
with changing message IDs).
After some poking here and there, I faced to SpiderLabs’ ModSecurity… Only that I am not yet well versed in writing rules for it. But after quite a bit of reading, poking, breaking… I was able to come up with the following rules:
# How often does the limit counter expire ⇒ ratelimit_client=60,
# every 60 seconds
SecRule REQUEST_LINE "@rx POST.*_task=mail&_unlock" id:10,phase:2,nolog,pass,setuid:%{tx.ua_hash},setvar:user.ratelimit_client=+1,expirevar:user.ratelimit_client=60
# How many requests do we allow in the specified time period? ⇒
# @gt 3, 3 requests
SecRule user:ratelimit_client "@gt 2" chain,id:100009,phase:2,deny,status:429,setenv:RATELIMITED,log,msg:RATE-LIMITED
SecRule REQUEST_LINE "@rx POST.*_task=mail&_unlock"
The first line specifies the rule will match request lines specifying
the POST verb aind including the _task=mail&_unlock
fragment in the
URL. It increments tht ratelimit_client
user variable, but expires
it after 60 seconds.
The first line verifies whether the above specified variable (do note
that it’s user:
instead of user.
) is greater than 2. If so, it
sets the deny
action, HTTP return status of 429 (Too Many
Requests
), and logs the reason why this request was denied
(rate-limited).
And… Given the way Roundcube works, this even works transparently! If a user hits the limit, the mail sending component will just wait and, after a while, time out. Then, the user can click Send again. If legitimate users are too productive and try to send over three mails in a minute, they won’t lose any of it; spammers will (hopefully!) find it unbearably slow and give up.
Logging is quite informative; I will probably later restrict it to show fewer parts (even if just for privacy sake, as it logs the full request!) For a complex permissions framework such as mod_security, having information such as the following is most welcome in order to find a possibly misbehaving rule:
--76659f4b-H--
Message: Access denied with code 429 (phase 2). Pattern match "POST.*_task=mail&_unlock" at REQUEST_LINE. [file "/etc/modsecurity/rate_limit_sender.conf"] [line "20"] [id "100009"] [msg "RATELIMITED BOT"]
Apache-Error: [file "apache2_util.c"] [line 273] [level 3] [client 192.168.1.48] ModSecurity: Access denied with code 429 (phase 2). Pattern match "POST.*_task=mail&_unlock" at REQUEST_LINE. [file "/etc/modsecurity/rate_limit_sender.conf"] [line "20"] [id "100009"] [msg "RATELIMITED BOT"] [hostname "my.server.mx"] [uri "/roundcube/"] [unique_id "YMzJLR9jVDMGsG@18kB1qAAAAAY"]
Action: Intercepted (phase 2)
Stopwatch: 1624033581838813 1204 (- - -)
Stopwatch2: 1624033581838813 1204; combined=352, p1=29, p2=140, p3=0, p4=0, p5=94, sr=81, sw=89, l=0, gc=0
Response-Body-Transformed: Dechunked
Producer: ModSecurity for Apache/2.9.3 (http://www.modsecurity.org/).
Server: Apache
WebApp-Info: "default" "-" ""
Engine-Mode: "ENABLED"
I truly, truly hope this is the last time my server falls in the black pits of DNSBL/RBL lists ☹