Safely handling redirects with die() and exit() in PHP

It is frequently the case within web applications that redirects are used to direct the user to a different portion of the application. A typical example would be that of an application redirecting a user to the login page when accessing a page intended for an authenticated user if they are not currently logged in.

A redirect is nothing more than an HTTP response (typically with a status code starting with 3xx) and a Location HTTP header. The two most commonly used status codes you’ll see when dealing with redirects are 301 (permanent redirect) and 302 (temporary redirect). A 301 status code is usually used when a resource (such as a file, page or image) has been permanently relocated in a new location. On the contrary, a 302 status code is usually used when the redirect is only in place for a variable amount of time, for example until a user logs back in.

The following is what an HTTP response with a 302 status looks like.

HTTP/1.1 302 Found
Content-Type: text/html
Location: login.php

The Location header is crucial for the browser to actually follow the redirect. Without the Location header, the browser will not know where it is meant to navigate to as a result of the redirect. In the above example, upon receiving the HTTP response, the browser will navigate to login.php.

This brings us to the subject of what this code looks like on the server-side. A common occurrence is to simply set the header if a condition is met, as is demonstrated in the following PHP code.

<?php
$user_authenticated = false
if (!$user_authenticated){
// If the user is not authenticated, send
// a Location header to redirect the browser to login.php
header("Location: /login.php");
}
// User should be logged in to see anything below this point
echo "Keep this secret";

The code sample above however, has a major flaw. Although a browser will obey the Location header and correctly redirect the user to /login.php, tools that do not follow the Location header automatically will still be able to see the remainder of the HTTP response. This is easily demonstrable by using a tool such as cURL with the --include switch (includes HTTP response headers in the output) below.

$ curl --include http://example.com/redirect.php
HTTP/1.1 302 Found
Host: example.com
Connection: close
Location: /login.php
Content-type: text/html; charset=UTF-8

Keep this secret

The most effective method of fixing this vulnerability is to add die() or exit() after the Location header for PHP to stop processing the rest of the page. The only difference between die() and exit() is that die() closes the HTTP connection (see the Connection HTTP header below), while exit() does not (from a security perspective they are equivalent)

<?php
$user_authenticated = false
if (!$user_authenticated){
// If the user is not authenticated, send
// a Location header to redirect the browser to login.php
header("Location: /login.php");
// Stop rendering the rest of the page
die();
}
// User should be logged in to see anything below this point
echo "Keep this secret";

This time, if we were to send an HTTP request using cURL, we will not see any additional data that should have not been processed by PHP.

$ curl --include http://example.com/redirect.php
HTTP/1.1 302 Found
Host: example.com
Connection: close
Location: /login.php
Content-type: text/html; charset=UTF-8

While this vulnerability is simple to fix, it’s worth keeping in mind, since an attacker could leverage such a vulnerability as a means of obtaining information that leaks as a result of seeing what lies beyond the Location header.

Share this post

Leave a Reply

Your email address will not be published.