Search this site:

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['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:

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] 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 ""] [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 (
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 ☹