Peter Strucks: Ist mein Login System sicher genug

Hallo.

Ich habe folgendes Loginsystem:
// Login-Formular:

<form method='post' action='loginskript.php'>
Benutzer/ID:<input name='user' type='text'/>
Passwort:<input name='pw' type='password'/>
<input value='Login' type='submit' name='Login'/>
</form>

// Loginskript:
session_start();
$host=xxx;
$user=xxx;
$pw=xxx;
$c=mysql_connect($host,$user,$pw);
mysql_select_db('name',$c);
if(!$_POST['user']||$_POST['user']=='') {
  header('Location: index.php?show=blank&action=lfailed&err=nonen');
}elseif(!$_POST['pw']||$_POST['pw']=='') {
      header('Location: index.php?show=blank&action=lfailed&err=nopen');
}

$nick=$_POST['user'];
$pw=$_POST['pw'];

$query="
SELECT user,pwd,xxxxxx,userid FROM xxx1 WHERE user='$nick'
";
$num=mysql_query($query,$c) or die (mysql_error());
$row = mysql_fetch_array($num);
$r_name = $row["user"];
$r_pw = $row["pwd"];
$r_branche = $row["xxxxxx"];
$r_uid = $row["userid"];
$_SESSION["user_id"] = $r_uid;
$_SESSION["user_nickname"] = $r_name;
$_SESSION["user_nachname"] = $r_xxxxxx;
$_SESSION['IP']=$_SERVER['REMOTE_ADDR'];

header("Location: online.php");

// online.php
session_start ();
if(!isset ($_SESSION["user_id"])){
 header ("Location: index.php?show=start");
 die();
}elseif($_SESSION['IP'] != $_SERVER['REMOTE_ADDR']) {
 header ("Location: index.php?show=start");
 die();
}

Beim Login Formular werden nur folgende Zeichen zugelassen:
0-9,a-z,A-Z,_,-

Das ganez Funktioniert. Aber ist es sicher genug? Was kann ich noch tun?

Schönen Tag noch

Peter Strucks

  1. Hallo,

    Beim Login Formular werden nur folgende Zeichen zugelassen:
    0-9,a-z,A-Z,_,-

    wie geht das? vielleicht interessiert dich dieser thread!

    --
    Swiss Army Chainsaw
    Terrorific!
    VI VI VI - the editor of the beast!
    1. Hi.

      wie geht das? vielleicht interessiert dich dieser thread!

      ich habs so gemacht:

      $string = preg_replace("![^A-Za-z0-9-]!is","",$string);

      So long..

      Peter Strucks

      1. ich habs so gemacht:

        $string = preg_replace("![^A-Za-z0-9-]!is","",$string);

        Du solltest den Nutzer über seinen evtl. unabsichtlichen Fehler in Kenntnis setzen und einfach preg_match nutzen...

  2. echo $begrüßung;

    $c=mysql_connect($host,$user,$pw);
    mysql_select_db('name',$c);

    Connect-Anforderungen können auch dann misslingen, wenn die übergebenen Daten richtig sind. Zum Beispiel dann, wenn der Server grad nicht verfügbar ist. Diesen Fehler solltest du ebenso berücksichtigen wie den Rückgabewert von mysql_select_db().

    if(!$_POST['user']||$_POST['user']=='') {
      header('Location: index.php?show=blank&action=lfailed&err=nonen');
    }

    Vermutlich möchtest du, dass an dieser Stelle das Script nicht weiter verarbeitet wird. Du solltest es definiert beenden.

    elseif(!$_POST['pw']||$_POST['pw']=='') {

    Das else ist überflüssig, da du auf jeden Fall nur bei einer nicht erfolgreichen Prüfung hier vorbeikommst.

    header('Location: index.php?show=blank&action=lfailed&err=nopen');
    }

    Auch hier fehlt vermutlich wieder ein die() oder exit.

    $nick=$_POST['user'];
    $pw=$_POST['pw'];

    Man kann mit den Werten in $_POST auch direkt weiterarbeiten. Das Anlegen neuer Variablen ist nicht erforderlich.

    $query="
    SELECT user,pwd,xxxxxx,userid FROM xxx1 WHERE user='$nick'
    ";

    Was passiert, wenn ich ' OR 1 OR ' (inklusive der ' ) eingebe? Und wo wird das Passwort überprüft? Auch im weiteren Verlauf kann ich eine Prüfung nicht finden.

    $num=mysql_query($query,$c) or die (mysql_error());

    Was will der Anwender im Fehlerfall mit dem genauen Wortlaut der MySQL-Fehlermeldung? Was hat es für ihn für einen Nutzen, wenn das Script an dieser Stelle stirbt? Den Fehlerfall kann der Anwender auch absichtlich provozieren, indem er etwas eingibt, das zu einem Syntax-Fehler des SQL-Statements führt. Ich sehe keine Gegenmaßnahmen in deinem Script (Stichwort mysql_real_escape_string())

    $row = mysql_fetch_array($num);

    Was ist, wenn bei der Abfrage kein Ergebnisdatensatz ermittelt werden konnte?

    $r_name = $row["user"];

    $row ist dann false und kein Array mit den Elementen, auf die du hier zugreifen willst. Das gäbe schöne Notice-Meldungen, wenn du sie dir durch ein auf E_ALL gestelltes error_reporting anzeigen ließest. Du hast doch sicher mal dein Script mit falschen Usernamen und Passwörtern malträtiert?

    Beim Login Formular werden nur folgende Zeichen zugelassen:
    0-9,a-z,A-Z,_,-

    Solch eine Prüfung habe ich in deinem Code nicht gesehen. Da man sie auf mehrere Arten realisieren kann (Pseudo-Prüfung mit Javascript, Prüfung auf dem Server) kann ich so nicht sagen, wie hilfreich sie ist.

    Das ganez Funktioniert. Aber ist es sicher genug?

    Definiere "sicher genug". Sicher genug wogegen? Lege fest, was passieren darf und was nicht passieren darf. Wenn du dir nicht sicher bist, stell uns diese Liste vor und lass sie dir von uns auseinandernehmen.

    Was kann ich noch tun?

    Füttere dein Script mit allem möglichen sinnvollen und unsinnigen Zeichen und Werten und schau, ob es dann immer noch fehlerfrei funktioniert.

    echo "$verabschiedung $name";

    1. Hi.

      Connect-Anforderungen können auch dann misslingen, wenn die übergebenen Daten richtig sind. Zum Beispiel dann, wenn der Server grad nicht verfügbar ist. Diesen Fehler solltest du ebenso berücksichtigen wie den Rückgabewert von mysql_select_db().

      Hab ich Gefixt.

      Vermutlich möchtest du, dass an dieser Stelle das Script nicht weiter verarbeitet wird. Du solltest es definiert beenden.

      Hab ich Gefixt.

      Das else ist überflüssig, da du auf jeden Fall nur bei einer nicht erfolgreichen Prüfung hier vorbeikommst.

      Hab ich Gefixt.

      header('Location: index.php?show=blank&action=lfailed&err=nopen');
      }

      Auch hier fehlt vermutlich wieder ein die() oder exit.

      Hab ich Gefixt.

      $nick=$_POST['user'];
      $pw=$_POST['pw'];

      Man kann mit den Werten in $_POST auch direkt weiterarbeiten. Das Anlegen neuer Variablen ist nicht erforderlich.

      Ist mir irgendwie übersichtlicher. Oder ändert es viel an der Performance?

      Was passiert, wenn ich ' OR 1 OR ' (inklusive der ' ) eingebe? Und wo wird das Passwort überprüft? Auch im weiteren Verlauf kann ich eine Prüfung nicht finden.

      Hatte ich vergessen reinzuschreiben:
      $nick = preg_replace("![^A-Za-z0-9- ]!is","",$nick);
      $pw = preg_replace("![^A-Za-z0-9- ]!is","",$pw);

      Was will der Anwender im Fehlerfall mit dem genauen Wortlaut der MySQL-Fehlermeldung? Was hat es für ihn für einen Nutzen, wenn das Script an dieser Stelle stirbt? Den Fehlerfall kann der Anwender auch absichtlich provozieren, indem er etwas eingibt, das zu einem Syntax-Fehler des SQL-Statements führt. Ich sehe keine Gegenmaßnahmen in deinem Script (Stichwort mysql_real_escape_string())

      Hmm.. okay.. aber das mit dem mysql_real_escape_string() verstehe ich nicht ganz obwohl ich mir dazu das Manual durchgelesen habe.

      $row = mysql_fetch_array($num);

      Was ist, wenn bei der Abfrage kein Ergebnisdatensatz ermittelt werden konnte?

      Hab ich gefixt.

      Das ganez Funktioniert. Aber ist es sicher genug?

      Sicher gegen:

      • SQL-Injektions
      • Abfangen von Userdaten
      • Fälschung der Session (verdammt wichtig)
          das sich kein Fremder einloggen kann der nicht angemeldet ist.
      • Einschläusung von Code in die Datenbank
      • Einschläusung von Fehlern
      • Bruteforce Attacken

      echo "$verabschiedung $name";

      1. hi,

        Sicher gegen:

        • SQL-Injektions
        • Einschläusung von Code in die Datenbank

        Damit bist du wieder bei mysql_real_escape_string. Wenn du dessen Aufgabe noch nicht verstanden hast, solltest du noch mal nachlesen. Und zum Stichwort SQL Inje(c|k)tion wissen auch die Wikipedia und weitere, durch Suchen auffindbare Quellen im www etwas.

        gruß,
        wahsaga

        --
        /voodoo.css:
        #GeorgeWBush { position:absolute; bottom:-6ft; }
      2. echo $begrüßung;

        $nick=$_POST['user'];
        $pw=$_POST['pw'];
        Man kann mit den Werten in $_POST auch direkt weiterarbeiten. Das Anlegen neuer Variablen ist nicht erforderlich.
        Ist mir irgendwie übersichtlicher. Oder ändert es viel an der Performance?

        Das mit der Übersicht kann man so oder so sehen. Wenn du die Werte umkopierst, sieht man nicht mehr auf Anhieb, dass der Inhalt der Variablen, wenn sie später weiterverwendet wird, von außen kommt. Man muss sich merken, dass diese Variablen ungefilterte und ungeprüfte Werte enthält.

        Beim Anlegen einer Variable mit dem Inhalt einer anderen Variable legt PHP intern nur einen Zeiger auf den ursprünglichen Inhalt an. Erst wenn sich der Inhalt einer der beiden Variablen ändert, wird eine Kopie des Inhalts erzeugt. Von daher wird zunächst kein doppelter Speicherplatz für den Inhalt und auch keine Zeit für dessen Kopieren verwendet, aber das Anlegen der Variable ist natürlich nicht völlig umsonst zu haben.

        Hmm.. okay.. aber das mit dem mysql_real_escape_string() verstehe ich nicht ganz obwohl ich mir dazu das Manual durchgelesen habe.

        Ich hab da schon mal was vorbereitet: </archiv/2006/8/t134653/#m873635>

        Das ganez Funktioniert. Aber ist es sicher genug?
        Sicher gegen:

        [...]

        • Abfangen von Userdaten

        Durch wen und wobei?

        • Fälschung der Session (verdammt wichtig)
            das sich kein Fremder einloggen kann der nicht angemeldet ist.

        Im Allgmeinen sind Cookies besser gegen Weitergabe geschützt als eine URL mit angehängter Session-ID, aber manche User schalten die Keks aus, teilweise auch aus Unkenntnis über ihr Wesen.

        • Einschläusung von Code in die Datenbank

        Zum Einschleusen muss man zwar schlau sein, trotzdem stammt das Wort nicht davon ab. :-)
        Welcher Art die Daten sind, die in der DB zu liegen kommen ist nicht ganz so wichtig. Wichtiger ist, dass sie beim Eintragen, Auslesen und Weiterverarbeiten als Daten behandelt werden und nicht als ausführbaren Code. Deswegen ist es wichtig, die Daten gemäß den Regeln des Ausgabemediums zu kodieren (mysql_real_escape_string() für MySQL-Stements, htmlspecialchars() für HTML, usw. usf.)

        • Einschläusung von Fehlern

        Fehler schleust man nicht ein, sie sind entweder da (auch unerkannterweise) und damit ausnutzbar oder nicht.

        • Bruteforce Attacken

        Man kann verhindern, dass mehrfache Versuche uneingeschränkt gelingen, das sollte man aber wohlüberlegt angehen, da man sonst auch unschuldige Anwender aussperren kann.

        echo "$verabschiedung $name";

        1. hey.

          Das mit der Übersicht kann man so oder so sehen. Wenn du die Werte umkopierst, sieht man nicht mehr auf Anhieb, dass der Inhalt der Variablen, wenn sie später weiterverwendet wird, von außen kommt. Man muss sich merken, dass diese Variablen ungefilterte und ungeprüfte Werte enthält.

          Achso okay...

          Ich hab da schon mal was vorbereitet: </archiv/2006/8/t134653/#m873635>

          Ich werds mir durchlesen. Danke.

          Das ganez Funktioniert. Aber ist es sicher genug?
          Sicher gegen:

          • Abfangen von Userdaten

          Durch wen und wobei?

          Leute die unbefugten Zugriff haben wollen auf die Datei online.php.

          • Fälschung der Session (verdammt wichtig)
              das sich kein Fremder einloggen kann der nicht angemeldet ist.

          Im Allgmeinen sind Cookies besser gegen Weitergabe geschützt als eine URL mit angehängter Session-ID, aber manche User schalten die Keks aus, teilweise auch aus Unkenntnis über ihr Wesen.

          Genau darum gehts mir ich möchte das die SID per URL weitergegeben und das so sicher wie möglich. Wie mache ich das am besten?

          Ich würde User&PW aus der Datenbank auslesen, in sid per url übergeben, prüfen und je nachdem dann den Inhalt von online.php ausgeben oder auf die Startseite zurück leiten.

          Andere Methoden?

          Welcher Art die Daten sind, die in der DB zu liegen kommen ist nicht ganz so wichtig. Wichtiger ist, dass sie beim Eintragen, Auslesen und Weiterverarbeiten als Daten behandelt werden und nicht als ausführbaren Code. Deswegen ist es wichtig, die Daten gemäß den Regeln des Ausgabemediums zu kodieren (mysql_real_escape_string() für MySQL-Stements, htmlspecialchars() für HTML, usw. usf.)

          Okay as hab ich verstanden.

          Man kann verhindern, dass mehrfache Versuche uneingeschränkt gelingen, das sollte man aber wohlüberlegt angehen, da man sonst auch unschuldige Anwender aussperren kann.

          Ich denke 6 versuche sind fair oder? Wie realisiere ich das am besten?

          echo "$verabschiedung $name";

          1. echo $begrüßung;

            • Abfangen von Userdaten

            Durch wen und wobei?

            Leute die unbefugten Zugriff haben wollen auf die Datei online.php.

            Ah, das meinst du. Das wäre ja nur ein Verhindern unautorisierter Eindringversuche. Unter Abfangen verstehe ich ein Auflauern auf dem Weg (Man-in-the-Middle). Das ließe sich durch eine gesicherte Übertragung verhindern/erschweren.

            • Fälschung der Session (verdammt wichtig)
                das sich kein Fremder einloggen kann der nicht angemeldet ist.

            Im Allgmeinen sind Cookies besser gegen Weitergabe geschützt als eine URL mit angehängter Session-ID, aber manche User schalten die Keks aus, teilweise auch aus Unkenntnis über ihr Wesen.

            Genau darum gehts mir ich möchte das die SID per URL weitergegeben und das so sicher wie möglich. Wie mache ich das am besten?

            Wenn die Session-ID in die URL steht ist sie schnell mal beabsichtigt oder unbeabsichtigt weitergegeben. Szenarien:

            • "Schau mal, was für ein tolles Hastenichtgesehen ich grad gefunden habe. Ich geb dir mal den Link."
            • Der Anwender klickt einen Link zu einem externen Angebot und der Browser gibt die URL samt Session-ID als Referrer mit.
            • Einem Angreifer gelingt es, per XSS oder ähnlichem einen Link in deine Seite einzubauen ... weiter siehe eins drüber.

            Am besten ist, wie gesagt, immer noch ein Cookie. Mir fällt derzeit nur XSS ein, um Cookies unautorisiert auslesen zu können.

            Man kann verhindern, dass mehrfache Versuche uneingeschränkt gelingen, das sollte man aber wohlüberlegt angehen, da man sonst auch unschuldige Anwender aussperren kann.

            Ich denke 6 versuche sind fair oder? Wie realisiere ich das am besten?

            Eine Möglichkeit wäre, die IP zu überprüfen und bei mehrfachen Login-Versuchen mit der selben IP diese abzublocken. Doch die IP ist ein recht unsicheres Identifikationsmerkmal, um nicht zu sagen: eigentlich gar keins. Mehrere Nutzer können sich eine IP teilen (Proxy, NAT), ein Nutzer kann mehrere IPs haben (Proxy-Farm) und Angreifer können über mehrere IPs verfügen (Bot-Netz).

            Wenn es wirklich notwendig ist, Mehrfach-Loginversuche abzublocken, würde ich nur die Kennung für einen angemessenen Zeitraum sperren. Vielleicht auch gestaffelt, beispielsweise erst 15 Minuten, beim den nächsten Versuchen 1 Stunde usw. Vielleicht auch bei falscher Eingabe das Script einfach 5 Sekunden schlafen lassen. Das frisst außer der bestehenden Verbindung weiter kein Brot.
            Bei längerer Sperrzeit sollte das einhergehen mit einer Beschreibung der Sachlage für den Anwender, möglichst mit Lösungsvorschlägen. Z.B.: (nicht übermäßig viel Fachchinesisch verwenden)
            "Kennung derzeit/noch x Minuten gesperrt. Mögliche Gründe:

            • Sie hab zu oft ein falsches Passwort eingegeben.
            • Jemand versucht ihren Anmeldenamen zu missbrauchen.
            • ...
              Sollte dies öfter passieren, nehmen Sie bitte Kontakt zu uns auf, damit wir gemeinsam eine Lösung finden können. ..."
              Ein möglicher Ausweg, den man dann dem Anwender vorschlagen kann, wäre ein Wechsel des Anmeldenamens.

            echo "$verabschiedung $name";

          2. hi,

            Man kann verhindern, dass mehrfache Versuche uneingeschränkt gelingen, das sollte man aber wohlüberlegt angehen, da man sonst auch unschuldige Anwender aussperren kann.

            Ich denke 6 versuche sind fair oder?

            Das kommt auf den Fall an.

            eBay limitiert die Loginversuche m.W. gar nicht - klar, sonst könnte ich mir die Konkurrenz, die ebenfalls schon auf den von mir ins Auge gefassten Artikel geboten hat, und mich ggf. kurz vor Ende der Auktion noch mal überbieten könnte, "vom Hals schaffen", in dem ich kurz vorher mit ihren Nutzernamen x ungültige Loginversuche durchführe.

            Leider sorgt dies auch dafür, dass eBay-Accounts ziemlich unsicher sind - kann sich jeder mit Brute-Force dran versuchen, quasi ohne Limit.

            eBay täte besser daran vorzuschreiben, dass Login- und Screenname zwingend verschieden zu sein haben - dann kann ich aus dem Screennamen nämlich nicht mehr 1:1 auf den Loginnamen schließen, obiges wäre hinfällig, und sie könnten ihr System effektiver gegen Brute-Force-Attacken schützen.

            Aber eBay scheint wenig an der Sicherheit der Daten seiner Nutzer interessiert - was eBay interessiert, hört einzig und allein auf den Namen $.

            gruß,
            wahsaga

            --
            /voodoo.css:
            #GeorgeWBush { position:absolute; bottom:-6ft; }