Password Protecting Pages


Contents

Intro


In this document I'll be explaining how to password protect pages and files using PHP... Yes get all the laughter out of the way, we're using an awful and dead language but it's actually a very effective, minimal and easy way to create a simple content locker. Follow along with me and I teach you about the cool interactions between Apache file permissions and PHP file inclusions!

How Does This Work


Ok, let's start simple: how do we stop users being able to view a specific file? In Apache we have an optional configuration file you can put in any exposed folder and it's called .htaccess. In this configuration file we can set all sorts of permissions and options for the current directory we're in and it's contents (this includes child folders and thus it's common to see just one .htaccess in the root folder setting the options and permissions for all the content on the site). You can view the documentation on all of the features you can enable in the .htaccess here however I'll focus on the permission Require all denied. Setting this permission on a file basically means nobody requesting this page can access it... The key word here being "request", see PHP has a function called include() which allows you to embed content onto the page and doing so does not make a network request as it fetches a local file. Using this interesting interaction we can write a very simple PHP password screen where if the user enters the correct password, we embed a normally network locked HTML file onto the page!

Guide


Ok so we need to create a .php page that will take a page as a URI parameter and after submitting a form, will include the page referenced in the URI. This is how I do it:

<?php
    if (isset($_GET['page'])) {
        $string = preg_replace('/[^\p{L}\p{N}\s]/u', '', $_GET['page']);
        if( $_POST["key"] == "SetThisToYourPassword") {
            include($string . ".html");
            exit();
        } 
    }
?>

<html>
    <body>
        <p>Enter Password</p>
        <form action = "<?php $_PHP_SELF ?>" method = "POST">
            <input type="password" name="key" placeholder="Enter Password" autofocus/>
        </form>
    </body>
</html>

Let's ignore the PHP section at the top for a second and look at the HTML. We can see a form with a single input for a password which when submitted triggers a form action that then runs our PHP at the top.

Now let's go through this line by line. Line one of the PHP looks for if the parameter called "page" is set:

if (isset($_GET['page'])) {

If this parameter is set we get the value it holds, run preg_replace over it and then move the new replaced value into a new variable called $string (preg_replace is mentioned later but TLDR it prevents path traversal vulnerabilities in this instance):

$string = preg_replace('/[^\p{L}\p{N}\s]/u', '', $_GET['page']);

Finally we check if the password matches our set password. If it does, we will include() $string + .html which will display the page fully on this page replacing this password screen:

if( $_POST["key"] == "SetThisToYourPassword") {
    include($string . ".html");
    exit();
} 

So to actually use this page locker we want to apply it to links within our website. For example let's say I want to lock a page on my site called secret.html. To link this page normally we would do this:

<a href="secret.html">secret page</a>

To lock it we will replace this with:

<a href="locker.php?page=secret">secret page</a>

Ok cool so now it's locked... But how do I prevent people just manually navigating to the page without going through the locker? For this, create a file in your main web server directory called .htaccess. Then you can add the following to it:

<Files secret.html>
	Require all denied
</Files>

There are lots of options and attributes you can set in .htaccess files. For a more in depth read of all the functionality please read this great blog post from Linode. In this case we apply Require all denied which means no one can request this page... However an include() isn't a request so this doesn't apply to our locker!

Security Considerations


Yeah I know, you read the source and immediately thought "local file inclusion"... This is defiantly a concern as PHP's include() is mega dangerous however with some proper configuration we can attempt to mitigate most of this risk.

The first measure we should take it to properly configure the Apache user. The user that the Apache process is running as is independent from normal users on the machine and can be assigned very restrictive permissions. Does the Apache user ever need to interact with files outside of our web server folder? No. I won't drag out this post with sysadmin tutorials but this is a great way of preventing sensitive system files from being leaked

The next measures are already implemented in my code above: We use a preg_replace to remove all special characters and symbols (this does mean your password protected files cannot contain special characters) and we hard code the extension.