0

I have a family history website, call it "my_family.com". The primary file, index.php, has some introductory remarks of explanation and an html form into which one puts the website's password (there's a single password used by all family members). If one runs "my_family.com" and inserts the correct password and clicks on the "Submit" button, the php code in the file takes you to the first of several html files -- call it "first.html," which gives one links to further html files. All of these files contain family trees, copies of letters, photos, reminiscences, obituaries, etc., and none of them should be available to non-family-members.

The problem is that if an intruder happens to learn the filename "first.html" and puts "my_family.com/first.html" into his/her browser, the intruder is immediately taken to first.html, skipping the password process altogether. From there, the intruder has access to the entire website.

How can I prevent this from happening?

A technician from GoDaddy, my web host, showed me how to insert a password protector in an .htaccess file. With this change, the website's first action is, in a sterile dialog box, to request a username and password. But I don't want to employ a username at all and I want to use my custom form for the password.

Any help would be greatly appreciated.

1
  • What you're asking for is a password for "anonymous" users. This is easily done in Microsoft, but I don't know about Apache. I'm not an Apache person, but take a look at httpd.apache.org/docs/2.4/mod/… and see if this has any value. It appears that you can configure Apache to allow "anonymous" users access to your site. I don't know what the password prompt will look like though. – Trebor Sep 19 '20 at 14:05
1

I know it's not exactly what you want, but I think your GoDaddy technician is onto the best, easiest solution. What I'd do is put all your secure files into a separate directory and link to that directory from your unsecured index.php file. You can include a note telling your users what username to use if you only want them to have to remember a password. Essentially then you'd have a site like this:

For example, in https://my_family.com/index.php:

<p>
<a href="https://my_family.com/secure/first.html">Login Here with the username "Guest"</a>
</p>

Then in https://my_family.com/secure/ you'd have your .htaccess file as described by your GoDaddy tech which would be something like:

Authtype Basic
AuthName "WHATEVER_YOU_WANT_HERE"
AuthUserFile /path/to/password/file
Require valid-user

Unauthenticated users would thus be able to view and visit your index.php page, but they'd then need the correct password to access your sensitive content. Granted, they'd have the ugly login form you're talking about, but it would be quick, easy and secure.

That said, if you really want to have a custom form, it's going to add a whole lot of complexity. But there are a number of approaches you could take. Here's one:

  1. Secure your sensitive files by putting them into a separate directory as above -- or better yet, take them out of the DocumentRoot completely.
  2. Create a script that allows for authentication and that reads and outputs the secure files to authenticated users upon request.

You could do something like this:

<?php

class MySecurePage
{
    const PASSWORD = 'YOUR_PASSWORD_HERE';
    const SECURE_PATH = '/path/to/secure/folder';

    /**
     * Run the page
     */
    public static function run()
    {
        session_start();
        if (isset($_GET['directory'])) {
            $directory = $_GET['directory'];
        } else {
            $directory = '/';
        }

        if (isset($_GET['file'])) {
            // Regularize forward slashes and remove any periods in the path which could otherwise be a security risk
            $file = $_GET['file'];
        } else {
            $file = '';
        }

        if (isset($_GET['log_out'])) {
            $_SESSION['logged_in'] = false;
            echo "Logged Out";
        } elseif (!self::checkLogin() && !self::checkPassword()) {
            self::showLoginForm();
        } elseif ($file) {
            self::readFile($directory, $file);
        } else {
            self::showFiles($directory);
        }
        return true;
    }

    /**
     * Returns TRUE if the user is logged in.
     *
     * @return bool
     * 1     */
    public static function checkLogin()
    {
        if (isset($_SESSION['logged_in'])) {
            return $_SESSION['logged_in'];
        } else {
            return false;
        }
    }

    /**
     * If the user submitted the correct password, sets the logged_in SESSION variable and refreshes the page to complete
     * the log in process.
     *
     * @return bool
     */
    public static function checkPassword()
    {
        if (isset($_POST['password']) && $_POST['password'] === self::PASSWORD) {
            $_SESSION['logged_in'] = true;
            header('Location: ' . self::getBaseUrl());
            exit;
        } else {
            return false;
        }
    }

    /**
     * Show the login form
     *
     * @param bool $hasError
     */
    public static function showLoginForm($hasError = false)
    {
        if (isset($_POST['password'])) {
            echo "<div>Invalid password</div>";
        }
        ?>
        <form action="" method="post">
            <label for="password">Password:</label>
            <input type="password" name="password" id="password">
            <input type="submit" name="submit" value="Submit">
        </form>
        <?php
    }

    /**
     * Shows a list of the files/directories in a given directory
     *
     * @param $directory
     * @throws Exception
     */
    public static function showFiles($directory)
    {
        // Filter out hidden files. You may also prefer to only show certain types of files (e.g, HTML files)
        $secureDirectory = self::getSecurePath($directory);
        $files = preg_grep('/^([^.])/', scandir($secureDirectory));

        echo "<div>Current Directory: $directory</div>";


        if ($files) {
            echo '<ul>';
            foreach ($files as $file) {
                if (is_dir($secureDirectory . '/' . $file)) {
                    printf('<li><a href="%s">%s</a></li>', self::getBaseUrl() . '?directory=' . urlencode($directory . $file), htmlentities($file));
                } else {
                    printf('<li><a href="%s?file=%s&directory=%s">%s</a></li>', self::getBaseUrl(), urlencode($file), urlencode($directory), htmlentities($file));
                }
            }
            echo '</ul>';
        } else {
            echo 'No files';
        }
    }

    /**
     * Outputs the contents of a file
     *
     * @param $directory
     * @param $file
     */
    public static function readFile($directory, $file)
    {
        // As above, regularize forward slashes and remove any periods in the path which could otherwise be a security risk
        $fullPath = preg_replace('#[/]+#', '/', '/' . self::SECURE_PATH . '/' . $directory . '/') . str_replace('/', '', $file);
        if (is_file($fullPath)) {
            readfile($fullPath);
        } else {
            echo "Not a file ";
            echo $fullPath;
        }
    }

    /**
     * Gets the base URL for the current web page
     *
     * @return string
     */
    public static function getBaseUrl()
    {
        return 'https://' . $_SERVER[HTTP_HOST] . strtok($_SERVER["REQUEST_URI"], '?');
    }

    /**
     * Builds a secure path for the given directory and file. Ensures that the directory or file is located within the
     * secure path so that malicious users cannot access files they should have access to
     *
     * @param $directory
     * @param null $file
     * @return bool|string
     * @throws Exception
     */
    public static function getSecurePath($directory, $file = null)
    {
        // First let's regularize the forward slashes in the directory
        $directory = preg_replace('#/+#', '/', '/' . self::SECURE_PATH . '/' . $directory . '/');
        // Next get rid of all forward slashes in the file
        $file = str_replace('/', '', $file);

        $result = realpath($directory . $file);

        // Make sure that the directory/file falls within the secure directory
        if (strpos($result, realpath(self::SECURE_PATH)) !== 0) {
            throw new Exception("Invalid path");
        } else {
            return $result;
        }
    }
}

MySecurePage::run();

Please note that the script above is only meant to be an example. It's not very usable; it hasn't been thoroughly tested; and it may well introduce security vulnerabilities!

Despite the above example, I'd seriously suggest you consider the first option even if it's not exactly what you want. It's not only much easier to implement, but it's also likely to be more secure. For instance, in the script I included, I essentially hard-coded a plaintext password into the file, which is a big no-no from a security practice. (I suppose you could do something like hash the password to make it a little more secure, but even this is far less than ideal!) As another example, it would also be relatively easy to accidentally allow authenticated users to access files you don't even want them to be able to access if you're not careful.

I guess the question you need to decide is how much time and effort you're willing to spend just to have a prettier login form.

2
  • This approach will not work on first.html (or any .html file) - the OP will have to switch to .php files – Steve Sep 20 '20 at 6:47
  • @Steve, the script I included would indeed read and output any file (HTML or otherwise) in the secure directory. But it has a whole slew of other problems -- horrible navigation, <img> or other tags referencing files in the secure directory not working, incorrect ContentType for non-HTML pages, etc -- none of which are unsolvable, but all of which seem to add up to more work than is worth it. In theory he could switch to PHP pages -- adding a login check to each of them -- but that wouldn't secure things like images, which he seemed to want to be able to do. – Aaron Sep 20 '20 at 14:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy