Magento Poisoning the Cache
For those of you that don’t know, the Magento routeing system is largely based on classes provided by Zend Framework 1. Whilst browsing through the code I came across something interesting in the method Zend_Controller_Request_Http::setRequestUri. What was interesting to me, was that it prioritises two headers (HTTP_X_ORIGINAL_URL and HTTP_X_REWRITE_URL) over REQUEST_URI. Why is that interesting? Because, by default, the rest of your stack is probably not doing the same. I’m assuming this was done in order to maintain compatibility with certain software stacks, but they are certainly not common configurations at this point in time.
So Magento prioritises HTTP_X_ORIGINAL_URL over REQUEST_URI and the rest of your stack doesn’t. What’s the impact of this? Well, there were two things that sprang to mind immediately.
The content the request shows and the actions it performs don’t match the normal content/actions for the URL that appears in your standard access logs. Unless all request headers are logged for the request (which isn’t standard in Apache), this may allow for malicious actions to be performed or private information to be viewed without any detectable audit trail.
The REQUEST_URI header is often used as (or part of) the cache key in a caching mechanism. If the returned response isn’t the conventional content, this can lead to that cache entry being ‘poisoned’ with incorrect content. Depending on your caching mechanism, this content could be anything from your 404 page to your admin login screen or a random product page. If some obscure content page on your site suffers from an XSS attack vector, it may be possible for somebody to poison the cache of a more frequently visited page, such as your homepage, in order to exploit more targets.
With this sound theoretical exploitation in mind, I set off to see if this might be exploitable in the real world. The first place I decided to look was the FPC implementation that comes with Magento EE and upon investigation, sure enough, the REQUEST_URI is used as the cache key. This took me a step further and gave me a caching mechanism vulnerable to my theory, but what are the chances of the rest of the stack playing ball and allowing those headers through to Magento?
As you may be aware, most web servers such as Apache or NGINX will pass received headers through to PHP in the $_SERVER array with an HTTP_ prefix. If you make a request such as
curl --header "FOO: foo" http://example.com/test.php, then
var_dump(isset($_SERVER['HTTP_FOO'])); has a good chance of being true. There are however restrictions in place that mean certain headers or headers containing certain characters aren’t passed through. After performing some initial tests I came to the sad conclusion that this couldn’t be widely exploited because seemingly only older versions of Apache (2.2) would pass through X_ORIGINAL_URL as HTTP_X_ORIGINAL_URL. Whilst this isn’t widely used I decided I should report the issue to Magento as the theory proved sound and it could be exploited given the right circumstance.
I’m sure some readers are ahead of me on this one, but it was only after I reported it that I made an interesting discovery. Whilst adding
--header "X_ORIGINAL_URL: /foo" produced very little success. It turns out that if you do
--header "X-ORIGINAL-URL: /foo" the success rate increases significantly. Turns out (given default configuration) both Apache and NGINX will convert this header into HTTP_X_ORIGINAL_URL and thus expose the flaw!
Magento fairly recently released the security patch SUPEE-8788 which includes a fix for the issue in the EE FPC, dubbed APPSEC-1338. The fix is to simply check the headers in the same order as the routeing system. You have of course diligently applied this patch or upgraded, and/or you don’t use EE, so you can safely move on after reading this – extremely interesting 😉 – article, right? Not so fast.
If you are using a 3rd party FPC implementation, there’s every chance it suffers from the same issue. Nearly every Varnish VCL I’ve seen supplied with an FPC extension has been vulnerable. Even if you don’t have an FPC, the logging inaccuracies alone lead me to the recommendation that you ensure these headers never reach your Magento installations. You can do this at any tier in your stack, just avoid letting the headers reach all the way to Magento. I’m no system administrator, but if you’re using apache you can get away with something like the folllowing (depending on your configuration, some of these may be redundant):
RequestHeader unset X-REWRITE-URL
RequestHeader unset X-REWRITE_URL
RequestHeader unset X_REWRITE-URL
RequestHeader unset X_REWRITE_URL
RequestHeader unset X-ORIGINAL-URL
RequestHeader unset X-ORIGINAL_URL
RequestHeader unset X_ORIGINAL-URL
RequestHeader unset X_ORIGINAL_URL