up arrow How To Stop A WordPress Dictionary Attack

You guys! Lest we forget, Your Joe Dog was under attack!

Apparently there’s a widespread dictionary attack that uses tens of thousands of malwared computers to attack WordPress sites. Your JoeDog uses WordPress as a CMS. Your JoeDog was attacked!

The extent of the attack was not initially clear. I was alerted by sluggish performance. I noticed a lot of POSTs to wp-login.php. Those POSTs appeared in the access log like this:

92.47.65.37 - - [17/Jun/2013:09:06:42 -0400] "POST /wp-login.php HTTP/1.0" 
200 3444 "-" "Mozilla/5.0 (Windows NT 6.1; rv:19.0) Gecko/2010 Firefox/19.0"

I have a script that allows me to quickly block IP addresses with iptables. So I started harvesting addresses and blocking them. Done and done.

Except the attacker seemed to have an endless supply of IP addresses. The attack persisted no matter how many addresses I blocked.

Take a look at the log entry above. The referer field is empty. A JoeDog Fellow suggested I block all POSTs that don’t include a referer. Afterall, you don’t POST out of the blue – you submit a form in your browser. I blocked those types of requests with a simple mod_rewrite rule:

 RewriteCond %{REQUEST_METHOD} =POST
 RewriteCond %{HTTP_REFERER} ^-?$
 RewriteRule ^/(wp-login.php|wp-admin) - [F,NS,L]

Done and done. Amiright? Sadly, no….

Soon after I added that rule, I noticed someone was crafting wp-login.php requests in a browser. It didn’t take long for them to determine the rule I used to “thwart” them. Entries started to appear in the logs that looked similar to this one:

2.134.35.69 - - [17/Jun/2013:09:03:41 -0400] "POST /wp-login.php HTTP/1.0"
200 3444 "www.joedog.org/wp-login.php" "Mozilla/5.0 (Windows NT 6.1; rv:19.0)
Gecko/20100101 Firefox/19.0"

See what he did? He hard-coded a referer into his script. If a browser would have submitted that request, it would have included an http:// scheme in the referer field.

I had to stop this guy. When he was able to send requests that generated database transactions, it brought the site to its knees. As a stop-gap measure, I password protected the login page:

<FilesMatch "wp-login.php">
  AuthType Basic
  AuthName "Kiss my fscking ass"
  AuthUserFile /path/to/my/file
  Require user franklindelanoroosevelt
</FilesMatch>

Apache doesn’t require many resources to say “401, gimme a password!” The site was no longer on its knees but now I had two different logins in order to gain access. PITA!

I considered allowing access by IP address, but mine changes frequently so I’d be as likely to block myself as the hacker. I wanted something that was convenient to me and provided enough obstacle for him to move along in another direction.

I discovered an interesting hack on the apache website. You can allow access to requests based on environment variables. If the variable is set, then let them in. Way cool!

In the example on the apache site, they set an env variable based on a User-agent. It’s easy to find a browser add-on that let’s you set a custom agent. Unfortunately, developers often deliver custom content based on User-agents. By using this approach, I could alter the look and feel of the web.

Here was the solution I finally went with: Custom headers.

I added a Chrome module called Extra Headers. It allows you to send a custom header to a website based on its domain. The module appears as a ghost icon after you install it. Click the ghost, then click options. On that page you’ll find a form that allows you to add a header:

new_header

Now we need to give it a domain to which it will send our X-MamaMia header:

domains

So when our domain is joedog.org, we send a X-MamaMia header. Now all we have to do is give it a value.  To do that, click on the ghost again. You should see your custom header and an empty field. Put a value in the empty field. In this example, I’m going to add “iszesty”.  Now every time I visit joedog.org, I’ll send the following header:

X-MamaMia: iszesty

Now we’re ready to add our access controls to the web server. You want to add something like this to your apache config:

 SetEnvIf X-MamaMia iszesty thats_a_meatball
 <Directory "/path/to/wp-admin">
   Order deny,allow
   Deny from all
   Allow from env=thats_a_meatball
 </Directory>
 <FilesMatch "wp-login.php">
   Order deny,allow
   Deny from all
   Allow from env=thats_a_meatball
 </FilesMatch>

Now, we’re finally done and done. In order to request wp-login.php or anything inside wp-admin, you must send a custom header which matches the one in our apache config. You have to clear that hurdle, just to try guessing our actual username and password in the login form.

One more thing: If you’re using WordPress, don’t use an ‘admin’ account. Set up another account with admin privileges then delete the admin account (When you delete admin, WordPress will let you transfer ownership to the new users).

UPDATE: I inadvertently blocked Mrs. JoeDog. She used the login page while I was harvesting IP addresses to block with iptables. I found the address and removed it. Now she’s back again….

Posted in Security, Wordpress | 4 Comments

4 Responses to “How To Stop A WordPress Dictionary Attack”

  1. Gus says:

    hi Jeff,

    Thank you for publishing this. I must say I am a novice when it comes down to configuring apache’s conf file. Where exactly does one add the environmental variable portion? After I added it to httpd.conf and restarted apache, all the bot attack lines in my access log turned from a 200 status to a 302, and browser visits would become an endless loop that’d eventually end in Chrome. What am I doing wrong? Much appreciated, Gus.

    • Jeff Fulmer says:

      If you put them in httpd.conf, that should work. I have mine in a VirtualHost. You should also make sure you have mod_setenvif loaded:

      LoadModule setenvif_module modules/mod_setenvif.so

      If the config didn’t barf at startup, then you probably do.

      What’s curious is that it turned from 200 to 302. There’s nothing in the config I posted here that would force a redirect. Without seeing more, that’s not much I can recommend.

  2. Gus says:

    Thanks for the update.

    I ended up adding the code to the VirtualHost as you suggested. I no longer see any bot requests, so unless the attack has stopped, the trick works. I received 150k requests in two days, the password protection of the wp-admin directory made things a little less messy on the VPS. Still, system load averages remained above 3 for the day. Right now, it’s at 0.50 or so.

    For the record, I am using this extension for Firefox to modify the headers: https://addons.mozilla.org/en-us/firefox/addon/header-tool/

    I’ll test it some more tomorrow and will report back :-)

  3. Gus says:

    So here’s an update. Bot traffic dropped tremendously today and 99% of the requests are legit. It seems that the extra header method works, either by directly blocking access or by sending the request into a loop (that 302 error results in an endless loop that is ended by the browser). Thanks again for providing a great solution to those pests!

Leave a Reply




Recent Comments

  • Tim: For those who enjoy playing at home and are extra OCD … they’ll spot something wrong with this....
  • roshni: Hi jeff, I need your help regarding running urls in a file containing post directives. Could you please send...
  • Alle: In seige, what does the pink result mean?
  • Windows User: Nice collection of Perl modules. Thanks for sharing.
  • Jeff Fulmer: No idea. What do you see in the webserver’s logs?