Okay, so just to clarify, am I actually breaking this news? No. Definitely not. But my automated log scanner picked up a jump in malicious activity yesterday on my network, so it’s worth taking a closer look. First, we see the jump in numbers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$ grep wp-login access.log-20140330 | awk '{ print $1 }' | sort | uniq -c | sort -n 2 X.X.X.197 5 X.X.X.122 5 X.X.X.219 5 X.X.X.98 5 X.X.X.114 5 X.X.X.37 8 X.X.X.159 10 X.X.X.226 10 X.X.X.36 10 X.X.X.160 11 X.X.X.141 15 X.X.X.49 15 X.X.X.12 15 X.X.X.15 20 X.X.X.127 35 X.X.X.101 $ grep wp-login access.log-20140329 | awk '{ print $1 }' | sort | uniq -c | sort -n 2 X.X.X.249 $ grep wp-login access.log-20140328 | awk '{ print $1 }' | sort | uniq -c | sort -n 1 X.X.X.196 |
Note that addresses have been lazily obfuscated but do not actually belong to the same class C, though some do share common ASNs (and some do indeed share a common class C). Wonderful, so let’s take a look at the log itself (a small sample is presented below for brevity):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
X.X.X.127 - - [29/Mar/2014:07:31:19 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.127 - - [29/Mar/2014:07:31:20 -0700] "POST /wp-login.php HTTP/1.1" 200 7269 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.127 - - [29/Mar/2014:07:31:21 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.127 - - [29/Mar/2014:07:31:21 -0700] "POST /wp-login.php HTTP/1.1" 200 7269 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.127 - - [29/Mar/2014:07:31:22 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.49 - - [29/Mar/2014:07:32:39 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.49 - - [29/Mar/2014:07:32:40 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.49 - - [29/Mar/2014:07:32:41 -0700] "POST /wp-login.php HTTP/1.1" 200 7269 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.49 - - [29/Mar/2014:07:32:42 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.49 - - [29/Mar/2014:07:32:43 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.101 - - [29/Mar/2014:07:33:51 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.101 - - [29/Mar/2014:07:33:52 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.101 - - [29/Mar/2014:07:33:53 -0700] "POST /wp-login.php HTTP/1.1" 200 7269 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.101 - - [29/Mar/2014:07:33:54 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.101 - - [29/Mar/2014:07:33:54 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" |
This pattern of 5 IPs making quick attacks and rotating out repeats throughout the set of the botnet, possibly as an attempt to avoid brute force mitigation. Also, notice the user agent string? Yeah, hooray for modern web infrastructure relying on arbitrary data on which non-repudiation cannot be established.
Looking at the timeline of the attacks, we see it lasted about an hour and a half:
1 2 3 |
$ grep wp-login access.log-20140330 | head -1 && grep wp-login access.log-20140330 | tail -1 X.X.X.141 - - [29/Mar/2014:06:09:09 -0700] "GET /wp-login.php HTTP/1.1" 200 7088 "cryptobells.com" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1" "-" X.X.X.101 - - [29/Mar/2014:07:36:09 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" |
Wait, that’s interesting. What’s that first hit doing? A GET is sent to wp-login.php with no referer. Let’s look at all of that IP’s traffic:
1 2 3 4 5 6 7 |
$ grep X.X.X.141 access.log-20140330 | head -6 X.X.X.141 - - [29/Mar/2014:06:09:07 -0700] "GET / HTTP/1.1" 301 5 "https://cryptobells.com" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1" "-" X.X.X.141 - - [29/Mar/2014:06:09:09 -0700] "GET / HTTP/1.1" 200 26355 "https://cryptobells.com" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1" "-" X.X.X.141 - - [29/Mar/2014:06:09:09 -0700] "GET /wp-login.php HTTP/1.1" 200 7088 "cryptobells.com" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1" "-" X.X.X.141 - - [29/Mar/2014:06:27:32 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.141 - - [29/Mar/2014:06:27:33 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "-" X.X.X.141 - - [29/Mar/2014:06:27:34 -0700] "POST /wp-login.php HTTP/1.1" 200 7263 "https://cryptobells.com/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1" "- |
This pattern indicates that X.X.X.141 functions as a crawler, searching for targets. Once the bot’s victim is identified, the labor of distributed brute force is assigned to other members of the attacking group, with each attacking host working in serial, again, likely as an attempt to avoid detection.
So what’s a sysadmin to do? Most people will resort to plugins for this sort of defense, but most brute force detection plugins come packaged as an all-in-one framework. I’m partial to a WAF approach with mod_security:
1 2 3 4 5 6 7 8 |
SecAction phase:1,nolog,pass,initcol:ip=%{REMOTE_ADDR},initcol:user=%{REMOTE_ADDR},id:99999 # Setup brute force detection. # React if block flag has been set. SecRule user:bf_block "@gt 0" "deny,status:503,log,id:5000135,msg:'ip address blocked for 5 minutes, more than 4 login attempts in 3 minutes.'" # Setup Tracking. On a successful login, a 302 redirect is performed, a 200 indicates login failed. SecRule RESPONSE_STATUS "^302" "phase:5,t:none,nolog,pass,setvar:ip.bf_counter=0,id:99998" SecRule RESPONSE_STATUS "^200" "phase:5,chain,t:none,nolog,pass,setvar:ip.bf_counter=+1,deprecatevar:ip.bf_counter=1/180,id:99997" SecRule ip:bf_counter "@gt 4" "t:none,setvar:user.bf_block=1,expirevar:user.bf_block=300,setvar:ip.bf_counter=0" |
O rry? This rook a very famiriar
Thanks, I forgot to cite that. The mod_security rules posted above are lifted from http://kb.liquidweb.com/wordpress-modsecurity-rules/. They’re intended to demonstrate one method of using a WAF to defend against a WordPress brute force login attack, not as a production-ready copypasta. In my specific situation, I use homebrew scripting and automation to add offending IPs to my server’s firewall, blocking traffic at the NIC, rather than having it processed by my HTTP daemon. Most people would argue that this is overkill, and I would tend to agree, but I’m root and IdowhatIwant.
So I’m curious. Is the botnet automatically stopping login attempts from individual IPs after five instances or is that your security software kicking in? What you’ve written and what the log shows suggests the former, but I haven’t played enough with this kind of thing to be sure.
Matt- this behavior actually appears to be a function of the botnet itself, not any configurations on my part. The scanner I mentioned above is a passive analysis tool.