Yesterday I was warned by Nagios that something is going on with my server. The number of processes has been gone beyond the limit. The reason for this were a bunch of Exim processes trying to deliver a lot of mails. Apparently a spammer made it happen to send spams via my server. How could this happen?
I was seeing a lot of those log entries:
2010-06-28 16:29:25 1OTFKt-0004cZ-1R <= vsvc@euhu.com H=(qknlvj.com) [58.212.194.230] P=esmtpa A=cram_md5:inna S=1455 id=0ec5ff00b06c43369e2582fc4269839e@e12c17c51ec546bf9e7a23e6f9875941
2010-06-28 16:29:27 1OTFKw-0004cZ-KZ <= ebms@ylmdbm.com H=(qknlvj.com) [58.212.194.230] P=esmtpa A=cram_md5:inna S=1520 id=58913c34bde64e908310db7e1280eff5@2dc8ed5998b64e8485c472cae5ef98c3
2010-06-28 16:29:28 1OTFKy-0004cZ-1p <= wiyscy@hegyogz.com H=(qknlvj.com) [58.212.194.230] P=esmtpa A=cram_md5:inna S=1436 id=814a7bdcbe85447a9283d4a0a074830b@0ada3cde4a4c41c38c65bc912639afb6
Apparently the spammer has successfully found a way to bypass the authenticators in Exim. By default only authenticated users can send mails via my mailserver, of course. But this spammer found a way around that problem. A user "inna" is not known, so the spammer shouldn’t be able to send mails at all. But he can. Something is broken apparently.
The spammer sent an empty password (”) and the database didn’t find an user by this name and returned an empty resultset (”). Both sides of the comparison matches and the mail is allowed in. Let’s have a look at my Exim config:
cram_md5:
server_debug_print = "running smtp auth $1 $2"
driver = cram_md5
public_name = CRAM-MD5
server_secret = ${lookup pgsql{select clear from passwd where passwd.usr=’$1′ limit 1}{$value}}
server_set_id = $1
On #exim@freenode there was just another user with the very same problem with exactly the same spammer. Apparently the lookup is true for empty passwords or users. The solution is to add a "fail" in the lookup statement:
server_secret = ${lookup pgsql{select clear from passwd where passwd.usr=’$1′ limit 1}{$value}fail}
That’s pretty all to do to prevent the spamming. But how could this happen at all? Back then when I was configuring my mailserver (around 2003 or so) I can remember that I tried a "fail" statement in the server_secret as it is described in the Exim documentation, but it didn’t work as expected. So I ended up with no "fail". This worked fairly well over the years and I remember that I tested this lookup by trying to send with user/password combinations of all different sorts. So it went into "production".
Apparently it doesn’t work anymore today. The other guy on the #exim channel said that he found no database lookup example with "fail" at all. Even the example in one of his books was without "fail". So I believe that something changed either in Exim or in PostgreSQL to make submitting an empty password string a positive match for bypassing authentication nowadays.
For the records: the spammer send approx. >10.000 mails. Another bunch of 8000 mails were rejected, but this is just slightly over the daily average.
ouh…
oh hey, I just stumbled across this blog entry and the exact same thing happened to me a month ago (also with that very same ‘inna’ user and no password + CRAM-MD5), except I’m using a MySQL backend. I extended icinga afterwards to keep an eye on my mailqueue 😉
Yeah, I believe it’s more
Yeah, I believe it’s more like an Exim problem or some sort of bug/changed behaviour in Exim than a database problem. I’m really confident that this was not possible when I configured the mailserver years ago. Since then there where many, many updates of Exim and somewhen the behaviour of the lookup changed.
I wrote the blogpost with the idea in mind to help others find and fix the issue of this kind of spamming. Hopefully it will help…
I’m not an Exim admin, but I
I’m not an Exim admin, but I spotted this on planet.debian.org. Should you not be using quote_pgsql? Otherwise it might be possible to bypass it using SQL injection tricks?
Yes, indeed… I’m already
Yes, indeed… I’m already using quote_pgsql in my real config, but I left this out of this example to not make it more complex.
Possible Solution
Hi, I’ve not read all the comments; someone may have already mentioned this, but I’ll post anyway…it looks like the SQL lookup in this event will return ‘fail’ if a user doesn’t exist, which, if the spammers use ‘fail’ as a password, will allow them in. the solution is to remove the return value {fail} altogether, so it either returns the password, or nothing at all.