I thought I’d write a short post about this issue as I’ve seen it come up a couple of times in PHP code audits. The incorrect assumption is that the Location header somehow forces a browser or forces execution to move elsewhere.

Take a look at the following code sample –

<?php
	$logged_in = 0;

	/* Do login verification routine here */

	if (!$logged_in)
	{
		/* User is not logged in and shouldn't be here */
		header("Location: /index.php");
	}
	/* User is logged in */
	echo "Secret Member Content";
?>

Now I’ve omitted the verification/cookie routine for the sake of simplicity, however in this case $logged_in is clearly set to 0, and so the user should never see the secret content. WRONG! If we visit this page in Firefox, sure enough, we get redirected to index.php as expected, but what’s the header() function actually doing? header() will just send an HTTP header to the browser, and in the case of the ‘Location:’ header, will also return a 302 response – it doesn’t some how ‘force’ a browser to redirect. As with any HTTP header, it is an optional directive that the browser can choose to ignore.

At this point, we can either use an intercepting proxy, to just ignore the header and not follow the redirect, or we can use ‘curl’. Specifying ‘-I’ to curl shows us the headers we received:

root@pwn:/var/www# curl http://localhost/members.php -I
HTTP/1.1 302 Found
Date: Fri, 13 Sep 2013 11:12:36 GMT
Server: Apache/2.2.14 (Ubuntu)
X-Powered-By: PHP/5.3.2-1ubuntu4.18
Location: /index.php
Vary: Accept-Encoding
Content-Type: text/html

As expected, however when we view the response body:

root@pwn:/var/www# curl http://localhost/members.php
Secret Member Content
root@pwn:/var/www#

The logged in member’s content is revealed. Although the header is sent, execution continues. This issue should be solved by ensuring that a die(); follows the header() statement, forcing execution to stop.