From Case-Insensitive to RCE

This blogpost is a contribution of Ramon Pinuaga [LinkedIn][Twitter].

Some time ago, I was doing a webapp penetration testing when I found something really interesting. The application was coded in PHP and it relied on some commercial components. Soon I found lots of XSS and SQLi vulnerable forms, but we won't focus on that in this post.

Suddenly, I realised that requesting the same page in uppercase or lowercase changed its behavior. The webserver was based on Apache and Linux (case sensitive) but the files seem to be hosted in a cases insensitive filesystem (maybe a NAS share?), so when I requested page.php it responded as expected, but when I requested page.PHP its source code was shown.

That vulnerability allowed to review the commercial application's source code, where I found several potentially dangerous functions such as "include()" and "include_once()" that could be vulnerable. No other dangerous functions such as "system()", "open()" or "file_get_contents()" were found.

A few minutes later I could confirm that at least one of those "include_once()" functions was vulnerable to LFI and exploitable. However, only files with .js extension could be loaded.

I tried several ways to bypass this protection, such as null byte, long paths, etc, but nothing seemed to work. So... we're tied to that. How we could upload or create a .js file in the local filesystem?

The upload forms didn't allow that kind of extensions and we weren't able to create files inside the server. Or... maybe we were? What about the following piece of code?

It was a feature that allowed to cache JavaScript files on disk! But... How could we inject custom content in those cached files?

An in-depth look showed that some JavaScripts were generated from a template and one of them included some user controled parameters. That sounds great!

Even better, the injectable parameter was the same that was vulnerable to LFI, so we could find a way to exploit both vulnerabilities at the same time.

JavaScript files were stored in a path like that: "/cache/".$offv.$theme.$lang.$type.$name.".js" , where $offv was the user provided input and the other parts could be easy guessed looking at the source code. So... the exploit string should be as follows:

However, I got a "500 - Internal Server Error". What the hell??? Something was terribly crashing. How we could fix it? I tried some other PHP functions such as "system()", "file_get_contents()" and "phpversion()" but most of them crashed in the same way.

Wait, what if the exploit works but it crashes at some point after that? what if we try to "exit()" the execution?

It works! Finally I had Remote Code Execution in the server, and everything began with a small source code leak caused by a case-insensitive filesystem. Small things sometimes cause big troubles.
Written on February 20, 2015