Den besten Schutz bietet immer noch ein VPN-Tunnel zur eigenen lokalen Cloud wie Nextcloud oder Owncloud, aber nicht jeder kann oder möchte diesen Weg gehen. Aus diesem Grund habe ich eine kleine Firewall entwickelt, die grundsätzlich jeden Besucher blockiert, außer er gibt einen gültigen Schlüssel ein, um seine IP-Adresse dauerhaft freizuschalten. Hier ist der Quellcode der firewall.php [Download]:

<?php
# #####################################
# Script:      Website Firewall v0.3
# Description: This scripts adds a firewall to your .htaccess and contains a 
#              white listing mechanism for your visitor's IPs
# Author:      Marc Gutt
# 
# Changelog:
# 0.3
# - Firewall code is added to the top of the .htaccess file (executed first)
# - whitelist complete IP-ranges
# - added .ip file extension to the whitelist files
# - optional email notification
# 0.2
# - Visitor IP and IP type (REMOTE_ADDR or HTTP_X_FORWARDED_FOR) is displayed
# - added German translation
# 0.1
# - initial release
# ######### Settings ##################
$key = ""; // optional
$firewall_path = "firewall/";
$email = ""; // optional notification
# #####################################
# 
# ######### Script ####################
error_reporting(E_ALL);
$locale = locale_accept_from_http($_SERVER['HTTP_ACCEPT_LANGUAGE']);
$langs = array(
    "Error: The key has the wrong format!" => array(
        "de_DE" => "Fehler: Der Schlüssel hat das falsche Format!",
    ),
    "Error: The .htaccess file does not exist!" => array(
        "de_DE" => "Fehler: Die .htaccess Datei existiert nicht!",
    ),
    "Your key is <q>%s</q>" => array(
        "de_DE" => "Dein Schlüssel lautet <q>%s</q>",
    ),
    '<a href="%s">Add your IP</a> to the whitelist or <a href="%s&uninstall">uninstall the firewall</a>.' => array(
        "de_DE" => '<a href="%s">Deine IP-Adresse freischalten</a> oder <a href="%s&uninstall">die Firewall deinstallieren</a>.',
    ),
    "The server IP %s has been added to the whitelist." => array(
        "de_DE" => "Die Server IP-Adresse %s wurde der Whitelist hinzugefügt.",
    ),
    "Error: It was not possible to add the server IP to the whitelist!" => array(
        "de_DE" => "Fehler: Die Server IP-Adresse konnte nicht der Whitelist hinzugefügt werden.",
    ),
    "Error: It was not possible to create the key file %s.key" => array(
        "de_DE" => "Fehler: Die Schlüsseldatei %s konnte nicht erstellt werden!",
    ),
    "Error: It was not possible to create the firewall path %s" => array(
        "de_DE" => "Fehler: Das Firewall Verzeichnis %s konnte nicht erstellt werden!",
    ),
    "Key" => array(
        "de_DE" => "Schlüssel",
    ),
    "Uninstall" => array(
        "de_DE" => "Deinstallieren",
    ),
    "Unlock my IP!" => array(
        "de_DE" => "IP-Adresse freischalten!",
    ),
    "Error: Your IP is not part of the provided IP range!" => array(
        "de_DE" => "Fehler: Deine IP-Adresse ist nicht Teil des bereitgestellten IP-Adressbereichs!",
    ),
    "The firewall code has been removed from the .htaccess file." => array(
        "de_DE" => "Der Firewall-Code wurde aus der .htaccess Datei entfernt.",
    ),
    "Error: The firewall code in the .htaccess file is corrupt." => array(
        "de_DE" => "Der Firewall-Code in der .htaccess Datei is korrupt.",
    ),
    "All whitelisted IPs have been deleted!" => array(
        "de_DE" => "Alle IPs wurden aus der Whitelist entfernt.",
    ),
    "Firewall path has been deleted!" => array(
        "de_DE" => "Das Firewall-Verzeichnis wurde gelöscht!",
    ),
    'Delete the firewall.php file from your host or <a href="%s">re-install the firewall</a>.' => array(
        "de_DE" => 'Lösche die Datei firewall.php oder <a href="%s">installiere die Firewall neu</a>.',
    ),
    "Your IP %s has been added to the whitelist." => array(
        "de_DE" => "Deine IP-Adresse %s wurde der Whitelist hinzugefügt.",
    ),
    '<a href="%s">Here is the page</a> which you intended to open.' => array(
        "de_DE" => '<a href="%s">Hier geht es zur Seite</a>, die du eigentlich öffnen wolltest.',
    ),
    "The firewall code has been added to your .htaccess file." => array(
        "de_DE" => "Der Firewall-Code wurde der .htaccess Datei hinzugefügt.",
    ),
    "Error: It was not possible to add the firewall code to the .htaccess file!" => array(
        "de_DE" => "Fehler: Der Firewall-Code konnte nicht der .htaccess Datei hinzugefügt werden!",
    ),
    "Error: The firewall can not be installed as the firewall path already exists! Please change the firewall_path value in the settings." => array(
        "de_DE" => "Die Firewall konnte nicht installiert werden, da das Firewall Verzeichnis bereits existiert! Bitte ändere in den Einstellungen den Wert von firewall_path.",
    ),
    "The firewall detected your IP %s (%s)." => array(
        "de_DE" => "Die Firewall hat deine IP-Adresse %s (%s) erkannt.",
    ),
    "Error: The key is wrong!" => array(
        "de_DE" => "Fehler: Der Schlüssel ist falsch!",
    ),
);
function lang($text, string ...$strings) {
    global $locale, $langs;
    return isset($langs[$text][$locale]) ? sprintf($langs[$text][$locale], ...$strings) : sprintf($text, ...$strings);
}
?>
<!DOCTYPE html>
<html lang="<?= $locale[0] . $locale[1] ?>">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="robots" content="noindex">
    <title>Firewall</title>
  </head>
  <body>
    <h1>Firewall</h1>
    <div>
<?php
list($script_path) = get_included_files();
if (!$key) {
    $key = substr(md5(filemtime($script_path)), 0, 8);
}
# only a-z and 0-9 are allowed
if (!ctype_alnum($key)) {
    echo "<p>" . lang("Error: The key has the wrong format!") . "</p>";
}
# check if path already exists
if (file_exists($firewall_path) && !glob($firewall_path . '*.key')) {
    echo "<p>" . lang("Error: The firewall can not be installed as the firewall path already exists! Please change the firewall_path value in the settings.") . "</p>";
# initial setup
} else if (!file_exists($firewall_path)) {
    $htaccess_file = ".htaccess";
    if (file_exists($htaccess_file)) {
        $htaccess_content = file($htaccess_file, FILE_IGNORE_NEW_LINES);
        if (!in_array('# ############ Firewall ###############', $htaccess_content)) {
            # QSD explained here: https://www.philipphoffmann.de/post/how-to-discard-the-query-string-in-a-rewriterule-apache-mod_rewrite/
            $firewall_code = '
# ############ Firewall ###############
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/firewall\.php$
RewriteCond %{REMOTE_ADDR} (\d+)\.(\d+)\.(\d+)\.(\d+)
RewriteCond ' . $firewall_path . '%1\.%2\.%3\.%4\.ip !-f
RewriteCond ' . $firewall_path . '%1\.%2\.%3\.ip !-f
RewriteCond ' . $firewall_path . '%1\.%2\.ip !-f
RewriteRule .* /firewall\.php [QSD,L,R=307]
</IfModule>
# ############ Firewall ###############
';
            if (file_put_contents($htaccess_file, $firewall_code . implode(PHP_EOL, $htaccess_content), LOCK_EX)) {
                echo "<p>" . lang("The firewall code has been added to your .htaccess file.") . "</p>";
            }
            else {
                echo "<p>" . lang("Error: It was not possible to add the firewall code to the .htaccess file!") . "</p>";
            }
        }
    } else {
        echo "<p>" . lang("Error: The .htaccess file does not exist!") . "</p>";
    }
    if (mkdir($firewall_path)) {
        if (touch($firewall_path . $key . '.key')) {
            $scheme = "http" . (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS'])? "s" : "" ) . "://";
            $port = ($_SERVER["SERVER_PORT"] != "80" && $_SERVER["SERVER_PORT"] != "443") ? ":" . intval($_SERVER["SERVER_PORT"]) : "";
            $url = $scheme . $_SERVER['SERVER_NAME'] . $port . $_SERVER['SCRIPT_NAME'] . "?key=" . $key;
            echo "<p>" . lang("Your key is <q>%s</q>", $key) . "</p>";
            echo "<p>" . lang('<a href="%s">Add your IP</a> to the whitelist or <a href="%s&uninstall">uninstall the firewall</a>.', $url, $url) . "</p>";
            $server_ip = filter_var($_SERVER['SERVER_ADDR'], FILTER_VALIDATE_IP) ? $_SERVER['SERVER_ADDR'] : "";
            if ($server_ip && touch($firewall_path . $server_ip . '.ip')) {
                echo "<p>" . lang("The server IP %s has been added to the whitelist.", $server_ip) . "</p>";
            } else {
                echo "<p>" . lang("Error: It was not possible to add the server IP to the whitelist!") . "</p>";
            }
        } else {
            echo "<p>" . lang("Error: It was not possible to create the key file %s.key", $firewall_path . $key) . "</p>";
        }
    } else {
        echo "<p>" . lang("Error: It was not possible to create the firewall path %s", $firewall_path) . "</p>";
    }
}
# Key check
$user_ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
$user_ip = filter_var($user_ip, FILTER_VALIDATE_IP) ? $user_ip : "";
$user_key = isset($_GET['key']) ? $_GET['key'] : "";
$ip_range = isset($_GET['ip']) ? $_GET['ip'] : "";
if (!$user_key) {
    $oct = explode(".", $user_ip);
    $user_ip_range3 = implode(".", array_slice($oct, 0, 3));
    $user_ip_range2 = implode(".", array_slice($oct, 0, 2));
    $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : "";
    $referer = filter_var($referer, FILTER_VALIDATE_URL);
    $referer = $referer ? base64_encode($referer) : "";
?>
    <form action="<?= htmlspecialchars($_SERVER['SCRIPT_NAME'], ENT_QUOTES) ?>" method="get">
        <input type="text" name="key" maxlength="50" placeholder="<?= lang("Key") ?>">
        <input type="radio" name="ip" value="<?= $user_ip ?>" checked><?= $user_ip ?> 
        <input type="radio" name="ip" value="<?= $user_ip_range3 ?>"><?= $user_ip_range3 ?>.x 
        <input type="radio" name="ip" value="<?= $user_ip_range2 ?>"><?= $user_ip_range2 ?>.x.x 
        <input type="hidden" name="referer" value="<?= $referer ?>">
        <button type="submit"><?= lang("Unlock my IP!") ?></button>
    </form>

<?php
} else {
    # only a-z and 0-9 are allowed
    if (!ctype_alnum($user_key)) {
        echo "<p>" . lang("Error: The key has the wrong format!") . "</p>";
    }
    else if ($user_key == $key || file_exists($firewall_path . $user_key . '.key')) {
        $key = $user_key;
        $uninstall = isset($_GET['uninstall']) ? 1 : 0;
        if ($uninstall) {
            $htaccess_file = ".htaccess";
            if (file_exists($htaccess_file)) {
                $htaccess_content = file($htaccess_file, FILE_IGNORE_NEW_LINES);
                $pos = array_keys($htaccess_content, "# ############ Firewall ###############");
                if (count($pos) == 2) {
                    $htaccess_content = array_slice($htaccess_content, 0, $pos[0]) + array_slice($htaccess_content, $pos[1] + 1);
                    file_put_contents($htaccess_file, implode(PHP_EOL, $htaccess_content), LOCK_EX);
                    echo "<p>" . lang("The firewall code has been removed from the .htaccess file.") . "</p>";
                }
                else {
                    echo "<p>" . lang("Error: The firewall code in the .htaccess file is corrupt.") . "</p>";
                }
            }
            // if the firewall directory contains a ".key" file, we assume it was generated by our script and are allowed to delete it
            if (glob($firewall_path . '*.key')) {
                array_map('unlink', glob("$firewall_path*"));
                echo "<p>" . lang("All whitelisted IPs have been deleted!") . "</p>";
                rmdir($firewall_path);
                echo "<p>" . lang("Firewall path has been deleted!") . "</p>";
                if ($email) {
                    mail($email, "Firewall", lang("Firewall path has been deleted!"));
                }
                $scheme = "http" . (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS'])? "s" : "" ) . "://";
                $port = ($_SERVER["SERVER_PORT"] != "80" && $_SERVER["SERVER_PORT"] != "443") ? ":" . intval($_SERVER["SERVER_PORT"]) : "";
                $url = $scheme . $_SERVER['SERVER_NAME'] . $port . $_SERVER['SCRIPT_NAME'];
                echo "<p>" . lang('Delete the firewall.php file from your host or <a href="%s">re-install the firewall</a>.', $url) . "</p>";
            }
        }
        else if ($ip_range) {
            # check if user_ip is part of ip_range
            if (strpos($user_ip, $ip_range) === false) {
                echo "<p>" . lang("Error: The IP is not part of the IP range!") . "</p>";
            }
            else {
                touch($firewall_path . $ip_range . '.ip');
                echo "<p>" . lang("Your IP %s has been added to the whitelist.", $ip_range) . "</p>";
                if ($email) {
                    mail($email, "Firewall", lang("Your IP %s has been added to the whitelist.", $ip_range));
                }
                $referer = isset($_GET['referer']) ? filter_var(base64_decode($_GET['referer'], FILTER_VALIDATE_URL)) : "";
                if (!$referer) {
                    $scheme = "http" . (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS'])? "s" : "" ) . "://";
                    $port = ($_SERVER["SERVER_PORT"] != "80" && $_SERVER["SERVER_PORT"] != "443") ? ":" . intval($_SERVER["SERVER_PORT"]) : "";
                    $referer = $scheme . $_SERVER['SERVER_NAME'] . $port;
                }
                echo "<p>" . lang('<a href="%s">Here is the page</a> which you intended to open.', $referer) . "</p>";
            }
        }
    }
    else {
        echo "<p>" . lang("Error: The key is wrong!") . "</p>";
    }
}
$user_ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
$ip_type = isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? "HTTP_X_FORWARDED_FOR" : "REMOTE_ADDR";
?>
    <p><small><?= lang("The firewall detected your IP %s (%s).", $user_ip, $ip_type) ?></small></p>
  </body>
</html>

Nach dem Hochladen der Datei in das Hauptverzeichnis der Website, einfach https://example.com/firewall.php aufrufen und der Schlüssel wird automatisch an Hand des Änderungsdatums der firewall.php Datei generiert. Wer einen eigenen Schlüssel vergeben möchte (Buchstaben und Zahlen werden unterstützt), kann das im Code unter „Settings“ über die Variable $key machen.

Den Schlüssel sollte man sich natürlich merken und nur mit zugriffsberechtigten Personen teilen. Falls er abhanden kommen sollte, findet man ihn im Verzeichnis „firewall/“ mit der Dateiendung „.key“:

Vorausgesetzt wird ein Apache 2.4 Webserver mit dem mod_rewrite Modul, da die Firewall darauf basiert. Der Code sollte auch mit Apache 2.2 funktionieren, dazu muss dann aber die „QSD“ Option im .htaccess Code entfernt werden.

Wer Nextcloud/Owncloud hinter einem Proxy verwendet, muss dafür sorgen, dass der Proxy über „HTTP_X_FORWARDED_FOR“ die IP des Besuchers übermittelt. Ansonsten schaltet man nur die IP des Proxy frei und alle Besucher kommen auf die Website. Wer wissen möchte welche IPs freigeschaltet wurden, kann im Verzeichnis „firewall/“ nachschauen.

Geplante Verbesserungen:

Weitere Vorschläge können gerne als Kommentar eingereicht werden.

Du willst meine Arbeit unterstützen:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.