Arcturus Labs

Background

As how all the most… inspired storytelling begins, I was at work. I was working on a pretty routine website test for a client; a fascinatingly average WordPress setup.

During my perusal of the site I noticed that wpscan can bruteforce WordPress logins using the XMLRPC API. Trying it, I piped the requests through Burp (which I definitely always remember to do for the audit trail haha 👀) and found out that it performs just one auth attempt per API call. After some light digging through the XMLRPC documentation, I discovered system.multicall.

Oho! Anything starting with “system” sounds naughty. I had a eureka! moment when I figured I could multicall the method wpscan uses to brute-force logins, which would let me bruteforce much faster..!

After spending a good hour developing a working proof-of-concept I then discovered that said multicall login bruteforce issue had been discovered and patched a long time ago. Great minds and all that. Anyway, this system.multicall method… the potential to run any API callback thousands of times, from a single request? Who came up with this? It’s BEGGING for abuse.

I moved on to investigating other available API callbacks. One such contender, ripe for some multicall abuse, is our friend pingback.ping. An awful “feature” (opinionated? me?) that allows blogs to tell each other when… uh… users post on them? I don’t even know. who cares about pingbacks Who cares? Point is that you can call this API unauthenticated and that’s what matters.

Calling this pingback API causes the server to send an HTTP request to a server of your choice. So, what happens if we use system.multicall to batch 4000 pingbacks into a single request, then send it about 200 times..?

bad things Bad things.

Root Cause

This is the BIG BRAIN section. If you just wanna go and DoS some scrubs, the code is here.

So. Root cause. What’s happening is that on receiving a request containing a load of pingback requests, WordPress actually issues EVERY SINGLE ONE, the ABSOLUTE madlad. As you can imagine, this takes a rather long time. Suppose (generously) that a server can complete a full request in 1 second, issuing 2000 pingbacks will use up half an hour of server resources.

Now isn’t this what PHP’s max_execution_time directive is for, you ask? Well, yes! A configuration directive with the sole purpose of preventing long-running scripts consuming too many resources! Or so you’d think:

time spent on activity that happens outside the execution of the script [..] is not included when determining the maximum time that the script has been running.

On *nix, PHP doesn’t take into account time spent doing non-PHP things… such as cURL requests. bruh. why even have the directive in the first place lmao.

Each request WordPress issues has a 10 second timeout and we can queue a BUNCH of them into a single API call. After we issue about 200 such batched calls the server will be too busy repeatedly using up socket descriptors to do anything else.

OH GOD HOW DO I FIX IT?

WordPress have a troubling history of Not Giving a Shit™ about Denial-of-Service issues, so right on cue they rapidly WONTFIX’d this after we raised it with them: dont care Loss of availability isn’t a “severe issue”, after all.

I also tried to contact the PHP developers to argue that this is a language fault, but I got completely stonewalled.

But worry not, dear netizen! Arcturus are here to help. The hands-down best solution is to uninstall WordPress remove xmlrpc.php. Failing that, go through every single post on your site and disable pingbacks. Another option is to disable the system.multicall method with this handy patch. Y’all welcome ♥

Pre-publish Update

Begrudgingly, I realised that as a PrOfeSSioNaL, I should try at least once more to report this responsibly to the community. I turned to the WordPress Bug Tracker (what a horrorshow) and started regurgitating this article. Before submitting I, on a whim, made a search for ‘pingback multicall’. Turns out a similarly BIG BRAINed hacker found this exact issue! 4 months ago. Lo and behold, THEY CLOSED IT AS NON-APPLICABLE AHHAHAHAHAHAH.

Despite this having been reported, this is still technically a zero-day because there’s no patch. A 4-month old zero-day..! So yeah, the official line would appear to be “screw you, you’re on your own”. Apply the patch above and get busy moving the hell away from the platform.

On that happy note, proof of concept code is linked below. DON’T DO AN ILLEGAL, iF YoU dO iT’S NOt mY fAuLt. Merry Christmas!

PoC: code here, no foolin
Hours slept: 2
Reported: 19th November
Caffeine consumed: 1250mg
Fixed: ‘DoS reports are out of the program scope.’ ¯\_(ツ)_/¯