By Dan Cornell
Andrew wrote up some notes for our internal blog about an experience he had on a recent Capture the Flag (CTF) event. I thought they were interesting so we talked and decided to republish them here.
I came across an interesting twist on exploiting a PHP local file inclusion vulnerability while participating in a CTF. LFIs are the little sister vulnerability to remote file inclusion (RFI), which is when an attacker can tell an application to execute arbitrary code from another file. The distinction with an LFI is it will only read and execute from files that are already on the server.
Developers usually create these vulns by allowing input from the client to decide the destination of an include() function call, which among other things is useful in templating out code. An example would be:
$page = $_GET['page'];
include("app/schedule/" . $page . ".php");
The developer’s intention is to pull in schedule code into another page, perhaps onto a sidebar. An attacker who notices this can specify their own page parameter to point to whatever file they want to be included. If an attacker can manage to upload code to the server, they can use this flaw to execute that code and probably compromise the machine. If not, they can still try to include files they’re not supposed to, such as /etc/passwd, but if the web server's account is locked down, it might not have read-access to access all the interesting files an attacker might want.
If the system is locked down then it seems like the risk here is somewhat mitigated, but PHP 5 has a fun little feature that can keep LFI in the game. PHP offers a protocol for accessing streams called ‘php://’. This protocol can do such things as read from the standard out stream with ‘php://stdout’. Interestingly, it can also apply filters to the stream, such as a filter which compresses the data as it passes through the stream.
We can take advantage of PHP’s base64-encode converter to tell PHP to base64-encode the php file before including it. The input would appear as such:
This would get parsed into this include function call as:
PHP does not parse base64-encoded text, so the entire base64 blob gets outputted onto the page as text. At this point an attacker is one base64 decode away from having the PHP source code to the application.
While the machine hasn’t been rooted yet, having the PHP source code offers all sorts of new attack options. From personal experience, I’ve found PHP developers rapidly sober up at the prospect of their source code being leaked. Features such as these is the reason the optimal fix for LFIs in PHP is to avoid it entirely in design by never using client-controlled input inside of an include.
Contact us to talk about ways you can build secure PHP applications and test the security of the PHP applications you have deployed.
dan _at_ denimgroup.com