Menü

 

 

 

de-de

Geschützte Seiten

Dieses Kapitel beschreibt Wege und Möglichkeiten, Teile von Webauftritten nur für privilegierte User zugänglich zu machen. Typische schützenswerte Seiten sind z.B. Administrationsseiten von Gästebüchern, Downloadbereiche usw. Man sollte sich aber darüber klar sein, dass kriminelle Elemente immer wieder Wege finden, auch vermeintliche sichere Seiten zu knacken.

Wenn also etwas so geheim ist, dass die Mehrheit der Benutzer es nicht sehen darf, so gibt es eine sehr einfache und wirkungsvolle Möglichkeit: man stellt es nicht ins Internet!

Verbreitete Irrtümer

Inhalte schützen

Wie oft habe ich schon gelesen: "Ich möchte, dass man die Bilder auf meiner Seite nur betrachten, aber nicht kopieren kann" oder "Ich möchte meinen Quellcode schützen". Es leuchtet ein, dass das nicht funktionieren kann, wenn man sich einmal klar macht, dass die Webseite mit den Bildern sich bereits im Computer des Benutzers befindet, wenn er sie ansieht. Maximal kann man es dem Benutzer erschweren, sich die Bilder zu kopieren. So gibt es beispielsweise Leute, die unbedingt die Funktion der rechten Maustaste verändern wollen, um damit das Kopieren zu verhindern. Oder es gibt Leute, die legen ein durchsichtiges Bild über das eigentliche Bild, um dem Betrachter das falsche Bild zum Kopieren anzubieten. Das Einzige, das man mit solchen Verhinderungsmethoden erreicht, ist eine Verärgerung der Benutzer.

Allen diesen Leuten sei hier gesagt, dass man die Kopien sehr leicht findet, indem man dem Ordner "Temporary Internet Files" leer macht und dann die Seite noch einmal lädt. Jetzt stehen ausschließlich die gesendeten Inhalte der Seite in diesem Verzeichnis.

Webseiten kopieren

Webseiten lassen sich, was reine HTML-Inhalte sind, recht einfach kopieren. Es gibt sogar Programme, die so etwas machen. Es dürfte, nachdem Sie das Kapitel Grundlagen verstanden haben, klar sein, dass solche Programme serverseitigen Code, wie er z.B. in PHP-Scriptdateien vorliegt, nicht kopieren kann, da nur die Ergebnisse solcher Scripts an den Client übertragen werden. Also Vorsicht beim Kopieren ganzer Webs!

Weiterführendes ...

Wikipedia: Authentifizierung

Selfhtml: .htaccess
Authentifizeirung und Zugangsbeschränkung mit .htaccess

Authentifizierung

Definition

Die Authentifizierung (auch Authentifikation, engl. authentication) bezeichnet den Vorgang, die Identität einer Person oder eines Programms an Hand eines bestimmten Merkmals zu überprüfen. Dies kann zum Beispiel mit einem Fingerabdruck, einem Passwort oder einem beliebigen anderen Berechtigungsnachweis geschehen. Weitere Informationen dazu findet man bei Wikipedia.

Authentifizierung und Zugansbeschränkung per .htaccess

Diese Art der Zugangsbeschränkung und -kontrolle ist ausführlich bei Selfhtml beschrieben, so dass ich hier darauf verzichte.

Ein Konzept zur Authentifizierung

Ich möchte hier ein Konzept vorstellen, bei dem die privilegierten Benutzer in einer Datenbank gespeichert sind und bei dem jeder Benutzer unterschiedliche Zugriffsrechte haben kann. Das Konzept nutzt Sessions und kann mit Cookies für Erinnerungsfunktionen ausgestattet werden. Im Einzelnen soll es so funktionieren:

Das Konzept erfüllt nicht die schärfsten Sicherheitsbedingungen und ist daher nicht für z.B. finanzielle Anwendungen geeignet. Es kann aber sicher durch Nutzung der .htaccess-Rechte und durch zusätzliche Sicherungsmaßnahmen weitgehenst sicher gemacht werden. Ich wage es nicht, 100% sicher zu sagen, da in der Vergangenheit immer wieder "sichere" Seiten gehacktwurden.

Die Datenbank

Die wichtigen Benutzerdaten in der Datenbank sind der Benutzername "username" und das Passwort "pwd". Das Passwort wird in der Datenbank md5-verschlüsselt abgespeichert. Ich habe immer, um Eindeutigkeit sicherzustellen, eine "id" mitgeführt. Alle anderen Benutzerdaten, wie z.B. Email-Adresse, Adresse, Telefon usw. sind optional und für die Authentifizierung nicht notwendig.

Aufbau der Tabelle "users":

Zuerst wird eine Datenbank angelegt:

CREATE DATABASE `beispiel_auth` ;

Danach wird die Tabelle "users" erzeugt:

CREATE TABLE `users` (
    `id` int(5) unsigned NOT NULL auto_increment,
    `username` varchar(32) default NULL,
    `pwd` varchar(32) default NULL,
    PRIMARY KEY (`id`)
    ) TYPE=MyISAM AUTO_INCREMENT=1 ;

Anmerkungen zur Registrierung eines Benutzers

In irgendeiner Form muss der (privilegierte) Benutzer in die Datenbanktabelle eingetragen werden. Dazu wird ein Registrierungs-Script diskutiert, das entweder durch den Benutzer selbst, oder aber durch einen Administrator ausgeführt wird. Dazu wird ein Formular erzeugt, in dem der Benutzer (der Administrator) bestimmte Benutzerkenndaten eingeben muss.Zu diesen Benutzerkenndaten gehören Benutzername, Passwort, Email und sonstige.

Benutzername

Man muss aber berücksichtigen, dass der Benutzer jeden beliebigen Namen als Benutzernamen angeben kann, also auch solche, die bereits existieren oder auch solche mit Sonderzeichen. Es ist also gut, Benutzernamen auf sinnvolle Zeichen zu beschränken und zu prüfen, ob ein Benutzername bereits existiert.

Passwort

Das Passwort wird zwar md5-verschlüsselt, sollte aber trotzdem nicht ein gebräuchliches Wort (wie z.B. Name) sein, da Brute-Force-Methoden solche Passworter sehr leicht knacken können." xH1pw+s5Dk" ist ein gutes Passwort, "Schatzi" oder "a1" sind schlechte Passwörter. Im Script kann man z.B. Passwörter zufällig erzeugen und diese dem Benutzer anbieten, oder man kann die Mindestlänge eines Passwortes vorgeben. Manche Scripte legen sogar fest, wieviele Großbuchstaben, Kleinbuchstaben und Ziffern das Passwort mindestens haben muss. Die Mindestanforderung an das Script ist, den Benutzer darauf hinzuweisen. Für das Passwort muss es nach der Registrierung die Möglichkeit geben, dass der Benutzer es ändern kann.

Email-Adresse

Eine Email-Adresse wird für eine Authentifizierung zwar nicht unbedingt benötigt, aber es gibt durchaus Vorteile damit. Ein Benutzername kann prinzipiell beliebig sein, eine Email-Adresse nicht. Ich glaube sogar, dass jede Email-Adresse nur ein einziges Mal weltweit vorkommt; mich würde mal interessieren, ob das jemand bestätigen oder widerlegen könnte. Mit der Email-Adresse hat man die Möglichkeit, anonyme virtuelle Benutzer nicht zuzulassen. Man fragt Benutzername und Email-Adresse ab und schickt ein zufällig generiertes Passwort per Email an die Email-Adresse. Wenn die Email-Adresse nicht gültig war, kann sich der Benutzer auch nicht einloggen.

Die Wahl der Methode

Es gibt also verschiedene Möglichkeiten, ein Registrierungsscript zu gestalten:

Ich beschreibe hier die zweite Methode, da sie die größte Komplexität darstellt.

Das Registrierungsscript

Der Passwortgenerator

Da das Passwort automatisch erzeugt werden soll, wird diese Aufgabe durch einen Passwortgenerator in Form einer Funktion realisiert.

downloadrunCodebeispiel 004:

<?php
// erzeugt Passwort der Länge $laenge
function pw_generator($laenge) {

// Wahrscheinlichkeit, mit der ein Zeichen erzeugt werden soll
$p_ziffer 15// bitte ändern falls gewünscht
$p_grossbuchstabe 25// bitte ändern falls gewünscht

// starte Zufallsgenerator
    
mt_srand((double)microtime()*1000000);

    
$pwd '';
    for (
$i=0;$i<$laenge;$i++) {
        
$ziffer chr(intval(mt_rand(4857))); // Zufallsziffer
        
$gross chr(intval(mt_rand(6590))); // Zufallsgroßbuchstabe
        
$klein chr(intval(mt_rand(97122))); // Zufallskleinbuchstabe

        
$select mt_rand(1100); //Zufallszahl
        
if ($select<$p_ziffer) { // $p_ziffer aller Zeichen im Passwort sind Ziffern
            
$pwd .= $ziffer;
        } elseif (
$select>100-$p_grossbuchstabe) { // $p_grossbuchstabe aller Zeichen im Passwort sind Großbuchstaben
            
$pwd .= $gross;
        } else { 
// der Rest aller Zeichen sind Kleinbuchstaben
            
$pwd .= $klein;
        }
    }
    return 
$pwd;
}

?>
<!DOCTYPE HTML>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Passwortgenerator</title>
</head>

<body>
<?php echo pw_generator(10); ?>
</body>
</html>

Als Parameter wird die Länge des Passworts übergeben; die Funktion liefert ein Passwort der entsprechenden Länge. Ich habe den Generator so aufgebaut, dass er mit 15%iger Wahrscheinlichkeit Ziffern, 25%iger Großbuchstaben und 60%iger Kleinbuchstaben. Als Test wird ein 10stelliges Passwort erzeugt. Man geht davon aus, dass die notwendige Komplexität eines Passworts zwischen 8 und 12 Stellen verlangt. Man könnte noch die Wahrscheinlichkeiten als Parameter mitgeben; dies bleibt dem Leser als Übung. Zudem sind noch Mechanismen denkbar, die sicherstellen, dass sie Summe der Wahrscheinlichkeiten 100% nicht überschreitet.

Das Formular

Da ein Formular mit Email-Gültigkeitsprüfung bereits bei den Grundlagen beschrieben wurde, kann hier auf Details verzichtet werden. Ich gehe im Folgenden davon aus, dass der Benutzername als $_POST['username'] und die Email-Adresse als $_POST['email'] zur Verfügung stehen.

Prüfung, ob Benutzername bereits vorhanden

Diese Prüfung durchsucht die Datenbank nach dem Benutzernamen $_POST['username'].

Codebeispiel 005:

<?php
$sql 
'SELECT username FROM users WHERE username = %s';
$sql sprintf($sql'\''.$_POST['username'].'\'');
$result mysql_query($sql$dblink);
if (
mysql_num_rows($result)>0) die('Fehler: Benutzername '.$_POST['username'].' ist bereits vorhanden.');
?>

Wenn in obigem Beispiel die Anzahl der gefundenen Datensätze größer 0 ist, ist der Benutzername bereits vorhanden. Natürlich wird man das Script nicht abbrechen (wie im Beispiel mit die('...'); ), aber man wird mit einer Fehlermeldung ins Formular zurück gehen.

Benutzerdaten in Datenbank speichern

Die Benutzerdaten (Benutzername, Email-Adresse und automatisch generiertes Passwort werden jetzt in die Datenbanktabelle eingetragen. dabei ist wichtig, dass das Passwort verschlüsselt in die Datenbank eingetragen wird. Da die md5-Verschlüsselung eine asymmetrische Verschlüsselung ist, gibt es keine Möglichkeit, ein vergessenes Passwort wieder herzustellen. In einem solchen Fall muss das Passwort neu generiert werden.

Codebeispiel 006:

<?php
// in Datenbank eintragen
$sql 'INSERT INTO users VALUES(\'\', %s, %s, %s)';
$sql sprintf($sql'\''.$_POST['username'].'\'''\''.md5($pwd).'\'''\''.$_POST['email'].'\'');
$result mysql_query($sql$link);
?>

Benutzer benachrichtigen

Jetzt muss noch dem Benutzer mitgeteilt werden, wie sein Passwort lautet. Dies kann mittels einer HTML-Mail oder einer normalen Mail geschehen. Zum Thema Emails mit PHP versenden gibt es ausreichend Literatur (z.B. php-faq). Da viele User HTML-Mails ausgeschaltet haben, verzichte ich darauf und stelle die Mail als normale Email dar.

Codebeispiel 007:

<?php
// Email an den Benutzer
$mail_subject "MeineWebsite: Ihre Registrierung";
$mail_message "Sie wurden als Benutzer ".$_POST['username']." erfolgreich registriert.\r\n";
$mail_message "Ihr Passwort ist ".$pwd." . Bitte ändern sie es, wenn Sie sich zum ersten Mal eingeloggt haben.\r\n\r\n";
$mail_message .= "Automatisch generierte Email; bitte nicht antworten.\r\n";
$mail_header "Von: MeineWebsite";
$mail_to $_POST['email'];
mail($mail_to,$mail_subject,$mail_message,$mail_header);
?>

Es ist übrigens wichtig, dass der Text der Email mit den doppelten Anführungszeichen (") eingeschlossen wird, da sonst die Zeilenvorschubzeichen \r (Wagenrücklauf) und \n (neue Zeile) nicht funktionieren.

Übung

Bitte erstellen Sie eine Registrierungsseite für Ihre Website.

downloadCodebeispiel 008: Musterlösung

Die Musterlösung stellt eine Möglichkeit unter vielen dar, wie eine Registrierungsseite aufgebaut sein kann. Sie nutzt PHP und MySQL. Auf Formatierungen (mit CSS) und Gültigkeitsprüfungen sowie sonstigen Text wurde verzichtet.

Das Login-Script

Das Login-Konzept

Nachdem ein Benutzer registriert ist und Benutzername sowie Passwort hat, soll er sich in eine geschützte Seite einloggen können. Da dies für viele Seiten funktionieren soll, muss das Login-Script bei allen diesen Seiten integriert sein. Wenn der Benutzer einmal eingeloggt ist, soll er nach Verlassen der Seite ohne weiteres Login die Seite wieder besuchen können. Was soll aber passieren, wenn die Login-Information falsch ist? Ich denke, der Benutzer sollte automatisch wieder zum Login-Script kommen. Dies sollte aber nicht unbegrenzt funktionieren; so sollte beispielsweise nach 3 missglückten Login-Versuchen auf eine andere Seite gesprungen werden, um Brute-Force-Attacken zu erschweren. Wir müssen also folgende Funktionen realisieren:

Umleitung zum Login-Script

Codebeispiel 009:

<?php
session_start
();
if (!isset(
$_SESSION['user_id'])) Header('Location: http://www.MeinWeb.de/login/login.php?quelle='.$_SERVER['PHP_SELF'].'&referer='.$_SERVER['HTTP_REFERER']);
?>

Die Zeile session_start() muss als erste Zeile im Script jeder geschützten Seite stehen. Unmittelbar danach sollte die Weiterleitung stehen, so dass weiterer Code des Scripts der Zielseite nicht ausgeführt wird. Die Zeile sagt aus, dass, wenn die Session_Variable user_id nicht existiert, zu der Seite http://www.MeinWeb.de/login/login.php umgeleitet wird. Dabei wird dem Login-Script mitgeteilt, von wo die Anforderung gekommen ist (quelle) und welche Seite diese Seite aufgerufen hat (referer).

Der Code des Login-Scripts

downloadCodebeispiel 010:

<?php
session_start
();

// Datenbankidentifizierung
include ('codebsp_008_dbsetup.php');

// spezielle Variablen
$errortext '&nbsp;';
$zugang true;
$errorcount 0;

// Quelle merken
if (isset($_GET['quelle'])) {
    
$quelle $_GET['quelle'];
} elseif (isset(
$_POST['quelle'])) {
    
$quelle $_POST['quelle'];
} else {
    
Header('Location: http://'.$_SERVER['HTTP_HOST']);
}

// Referer merken
if (isset($_GET['referer'])) {
    
$referer $_GET['referer'];
} elseif (isset(
$_POST['referer'])) {
    
$referer $_POST['referer'];
} else {
    
Header('Location: http://'.$_SERVER['HTTP_HOST']);
}

// Fehlversuche merken und erhöhen
if (isset($_POST['errorcount'])) {
    
$errorcount $_POST['errorcount'] + 1;
}

// nach 3 Fehlversuchen zur Referer-Seite umleiten
if ($errorcount>3Header('Location: '.$referer);

// Gültigkeitsprüfung
if (isset($_POST['username'])) {
    
$username trim($_POST['username']); // ggf. Leerzeichen am Anfang und Ende löschen
    
if (empty($username)) $zugang false;
}
if (isset(
$_POST['pwd'])) {
    
$pwd trim($_POST['pwd']); // ggf. Leerzeichen am Anfang und Ende löschen
    
if (empty($pwd)) $zugang false;
}

if (isset(
$_POST['senden']) && $zugang) {

// Datenbank oeffnen
    
$dblink mysql_connect($dbserver$dbuser$dbpass) or die ('Konnte Verbindung mit '.$dbserver.' nicht herstellen'.mysql_error());
    
mysql_select_db($dbname$dblink);

// Prüfen, ob Benutzer und Passwort ok
    
$sql 'SELECT id FROM users WHERE username = %s AND pwd = %s';
    
$sql sprintf($sql'\''.$username.'\'''\''.md5($pwd).'\'');
    
$result mysql_query($sql$dblink);
    if (
mysql_num_rows($result)!=1$zugang false;
    if (
$zugang$userdata mysql_fetch_array($result);

// Datenbank schließen
    
mysql_close($dblink);

// berechtigt => Session-Variable erzeugen und zu Quelle umleiten
    
if ($zugang) {
        
$_SESSION['user_id'] = $userdata['id'];
        
Header('Location: '.$quelle);
    } else {
        if (isset(
$_POST['senden'])) $errortext 'Zugang verweigert';
    }
}

?>
<!DOCTYPE HTML>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Login</title>
</head>

<body>
<h1>Login</h1>
<form action="<?php echo $_SERVER['PHP_SELF'];?>" method="post" name="login">

<input type="hidden" name="quelle" value="<?php if (isset($quelle)) echo $quelle?>" />
<input type="hidden" name="referer" value="<?php if (isset($referer)) echo $referer?>" />
<input type="hidden" name="errorcount" value="<?php if (isset($errorcount)) echo $errorcount?>" />

<p>Benutzername: <input name="username" type="text" value="<?php if (isset($username)) echo $username?>" /></p>
<p>Passwort: <input name="pwd" type="password" value="<?php if (isset($pwd)) echo $pwd?>" /></p>

<p><?php echo $errortext?></p>

<p><input name="senden" type="submit" value="Login" /></p>
<p><input name="zurueck" type="button" value="Zurück" onclick="location.href('<?php if (isset($referer)) echo $referer?>')" /></p>

</form>

</body>
</html>

Nach session_start() wird zuerst die Datenbank bekannt gemacht. Dazu kann das Setup aus Codebsp_008 genommen werden. Ich habe es hier mit einem include() dazugenommen.

Danach merke ich mir Quelle und Referer. Diese werden von der aufrufenden Seite per GET übertragen. Damit sie auch bei Login-Fehlern nicht verloren gehen, werden sie weiter unten im Formular als hidden-Felder per POST übertragen. Falls das Login-Script direkt aufgerufen wird, also nicht von einer anfordernden Seite kommt, wird auf die Homepage umgelenkt.

Das Zählen der Fehlversuche wird realisiert, indem man $errorcount per POST übergibt und dann bei jedem Aufruf um 1 erhöht. Nach dem dritten Mal wird auf die Referer-Seite umgeleitet. Hier kann man auch auf jede beliebige andere Seite umleiten, z.B. auf eine spezielle Fehlerseite.

Jetzt kommen die Gültigkeitsprüfungen, die ja aus den Grundlagen bereits bekannt sind. Die steuernde Variable für den Zugang zur geschützten Seite ist $zugang. Wenn eine Prüfung fehlschlägt, wird $zugang auf false gesetzt.

Im nächsten Schritt wird geprüft, ob es einen Datenbankeintrag mit username und pwd gibt. Wenn nicht, wird wieder $zugang auf false gesetzt.

Schließlich wird die Session-Variable mit der id des Benutzers aus der Datenbank gesetzt und dann zur aufrufenden Seite umgelenkt. Damit ist der Benutzer eingeloggt. Wenn er jetzt die aufrufende Seite ein zweites Mal besucht, ist er immer noch eingeloggt, da die Session nicht gelöscht wurde.

Logout

downloadCodebeispiel 011:

<?php
session_start
();
if (!isset(
$_SESSION['user_id'])) Header('Location: http://'.$_SERVER['HTTP_HOST']);

// lösche Session
session_unset();
session_destroy();

if (isset(
$_GET['ziel'])) {
    
Header('Location: '.$_GET['ziel']);
} else {
    
Header('Location: http://'.$_SERVER['HTTP_HOST']);
}
?>

Das Ausloggen ist ein recht einfaches Script. Zuerst wird die übliche Abfrage zur Existenz der Session durchgeführt. Danach wird die Session zerstört.

Wurde das Logout-Script mit einem Ziel aufgerufen, so wird dan auf dieses Ziel umgeleitet. Andernfalls wird auf die Homepage umgelenkt. Das Ziel muss in dem aufrufenden Script als GET-Feld definiert sein, z.B. <a href="codebsp_011.php?ziel=http://www.MeinWeb.de/irgendwas.php">logout</a>

Erinnerungsfunktion mit Cookies

Die Realisierung einer solchen Funktion ist eigentlich recht einfach. Sie benötigt im Login-Script eine zusätzliche Zeile, in der die Session-Variable im Cookie gespeichert wird. Sie benötigt weiterhin in der aufrufenden Seite eine zusätzliche Zeile, in der die Session wieder aus dem Cookie heraus erzeugt wird.

Codebeispiel 012:

<?php
/*********** hier der Zusatz im Login-Script ************/
    
if ($zugang) { //siehe Codebsp_010
        
$_SESSION['user_id'] = $userdata['id']; //siehe Codebsp_010
        
setcookie('MeinWebCookie'$_SESSION['user_id'], time()+60*60*24*100'/'); // diese Zeile erzeugt das Cookie mit der Gültigkeitsdauer von 100 Tagen
        
Header('Location: '.$quelle); //siehe Codebsp_010
    
} else { //siehe Codebsp_010
        
if (isset($_POST['senden'])) $errortext 'Zugang verweigert'//siehe Codebsp_010
    
//siehe Codebsp_010
?>

<?php
/*********** hier der Zusatz in der aufrufenden Seite ************/
session_start(); // siehe Codebsp_009

if (isset($_COOKIE['MeinWebCookie'])) $_SESSION['user_id'] = $_COOKIE['MeinWebCookie']; // diese Zeile erzeugt die Session, falls ein Cookie vorhanden ist

if (!isset($_SESSION['user_id'])) Header('Location: http://www.MeinWeb.de/login/login.php?quelle='.$_SERVER['PHP_SELF'].'&referer='.$_SERVER['HTTP_REFERER']); // siehe Codebsp_009
?>

Übung

Erzeugen Sie geschützte Seiten. Schreiben Sie dazu ein Login-Script, in dem der Benutzer wählen kann, ob er Cookies zur Erinnerungsfunktion nutzen will. Schreiben Sie auch das passende Logout-Script.

downloadrunCodebeispiel 013:

Das Beispiel zeigt ein vollständiges Web mit folgenden Features:

  • Eine Indexseite und eine öffentliche Seite
  • Drei geschützte Seiten und eine geschützte Admin-Seite
  • Individuelle Rechtevergabe
  • Cookies zum Speichern des Login-Rechts
  • Registrierung neuer Benutzer mit Email-Benachrichtigung (in Demo abgeschaltet)

Kommentar eintragen

Kommentare zu dieser Seite

08.09.2014 08:39

Soledad Bassetti

</br><a href="http://www.finalsurgery.com
">Soledad Bassetti</a></br>[url=http://www.finalsurgery.com
]Soledad Bassetti[/url]