Andreas Korthaus: "echte" Login-Session mit HTTP?

Hallo!

Ich hatte da eine Idee wir ich evtl. mit PHP und HTTP-Auth eine möglichst sichere und einfache Login-Session erstellen kann. Ich hatte dazu zwar unten schonmal was geschrieben([pref:t=32932&m=180132]), nur fand ich es wichtig genug es einmal in den Mittelpunkt zu rücken, außerdem war der Beispiel-Code schlecht.
Und zwar stelle ich mir das so vor, das der User sich über HTTP-AUTH einloggen muß, was in PHP mit dem 401-Header gemacht wird, dann prüfe ich in PHP die AUTH-Daten in den Variablen PHP_AUTH_USER und PHP_AUTH_PW wenn die korrekt sind dann erstelle ich entweder eine Session mit dem usernamen als Wert für die session_ID, oder wenn eine Session mit dieser ID(also für diesen User) bereits läuft, dann starte ich eben diese.

Ein Beispiel-Script mit dem das ganze (zumindest oberflächlich und zumindest bei mir)hervorragend funktioniert:

<?php

$userarray = array('haus' => 'haus9',
                     'andreas' => 'test',
                     'baum' => 'baum9');

if (!isset($_SERVER['PHP_AUTH_USER']) ||
      $userarray[$_SERVER['PHP_AUTH_USER']] != $_SERVER['PHP_AUTH_PW'] ||
   !in_array($_SERVER['PHP_AUTH_USER'],array_keys($userarray))) {

header('WWW-Authenticate: Basic realm="My Realm"');
        header('HTTP/1.0 401 Unauthorized');
        echo 'Text to send if user hits Cancel button';
        exit;

} else {

session_id($_SERVER['PHP_AUTH_USER']);
        session_start();
        if (!isset($_SESSION['zaehler'])) {
            $_SESSION['zaehler'] = 0;
        } else {
            $_SESSION['zaehler']++;

}
  }
?>

Das ganze bietet IMHO einige Vorteile gegenüber allen anderen Lösungen die ich kenne:

  • Die "SessionID" wird über die HTTP-AUTH HEader übertragen, welche IMHO schwerer zu manipulieren sind als Cookies und URLs,
  • man erreicht nichst ohne das Passwort, d.h. man kann ohne das Passwort keine Session übernehmen
  • die HTTP-AUTH Daten sind am wenigsten anfällig für Störungen(vor/zurück im Browser...)
  • Ich kann über die Session einen Login-Timeout festlegen
  • Der Angreifer weiß nicht dass es überhaupt eine Session gibt(!!!)
  • Die "sessionID" wird _nirgendwo_ aus Versehen geloggt, das Passwort schon gar nicht
  • Das ganze ist sehr performant da ich nicht bei jedem Zugriff auf eine Datenbank zugreifen muß, zumindest nicht extra um eine Session_ID herauszufinden.
  • IMHO kommt das einem "echten" Login wie bei SSH oder FTP schon sehr nahe
  • Session läßt sich auf einem anderen PC übernehmen

Ich habe das jetzt mal so wie ich es oben gepostet habe ein wenig ausprobiert, und das ganze klappt prima! Ich wüßte nicht wo es da Probleme geben könnte. Was sagt Ihr dazu? Ist das evtl. doch unsicher, oder würdet Ihr das aus einem anderen Grund nicht so machen?

Viele Grüße
Andreas

PS: Ich würde noch den Session_save_path in ein eigenes Verzeichnis legen.

  1. Holladiewaldfee,

    Ein Beispiel-Script mit dem das ganze (zumindest oberflächlich und zumindest bei mir)hervorragend funktioniert:

    Solange nicht zwei User gleichzeitig mit dem gleichen Benutzernamen angemeldet sind ... Spätestens dann bekommst Du nämlich Probleme, da sich dann zwei Benutzer die gleiche Session "teilen" müssen.

    • Ich kann über die Session einen Login-Timeout festlegen

    Ja, kannst Du ... Bloß was ist, wenn der Timeout abläuft und der User nochwas vergessen hat? Einloggen kann er sich ja nicht mehr, weil er jedesmal wieder die abgelaufene Sessino zugewiesen bekommt.

    • Der Angreifer weiß nicht dass es überhaupt eine Session gibt(!!!)

    ? Wie kommst Du da drauf? So weit ich weiß schickt session_start() wenn möglich gleich ein Cookie an den Browser.

    • Die "sessionID" wird _nirgendwo_ aus Versehen geloggt, das Passwort schon gar nicht

    Sicher? Soweit ich weiß hat PHP eine Funktion (magic_irgendwas), die im Fall der Fälle (wenn das Cookie nicht angekommen ist) die SessionID an jeden Link anhängt. Dann kann die ID wie woanders auch ohne Probleme in den LogFiles hängen bleiben. Nur, daß sie in Deinem Fall ohne Passwort natürlich nicht mehr sooo nützlich ist (dafür steht aber ein mit Sicherheit gültiger Benutzername drin!)

    • Session läßt sich auf einem anderen PC übernehmen

    Das ist ein Vor- und ein Nachteil ... wie schon gesagt.

    Ist das evtl. doch unsicher, oder würdet Ihr das aus einem anderen Grund nicht so machen?

    Naja ... ich sehe keinen echten Vorteil darin, außer daß man einen gültigen Usernamen preisgibt. So eine 32-stellige (oder wieviel auch immer) ID dürfte nicht zu erraten sein. Wenn, dann nur über LogFiles oder durch (wie auch immer geartetes) Abhören der Verbindung ...

    Ciao,

    Harry

    --
      (There are only 10 types of people in this world: Those who understand binary and those who don't)
    1. Hallo Harry!

      Solange nicht zwei User gleichzeitig mit dem gleichen Benutzernamen angemeldet sind ... Spätestens dann bekommst Du nämlich Probleme, da sich dann zwei Benutzer die gleiche Session "teilen" müssen.

      Das ist in meinem Fall egal, denn in der Session werden nur allgemeine Daten gespeichert, die sonst bei jedem Aufruf einer Seite neu aus TXT-Files und DB geholt werden müßten. Ich speichere da so Dinge wie Rechte des Useres, individuelle Menüstruktur als multidimensionalen Array, seine UserID, seinen Namen..., den Root-path, die Root-url..., das ganze gilt für alle Leute die sich mit diesem Usernamen einloggen könnten. Es kommt bisher leider auch einmal vor, dass ich die Session auch anderweitig brauche, da kann muß ich dann mal gucken wie ich das mache, entweder schreibe ich mir dafür was eigenes, oder ich uintegriere das irgendwie in meine Session, oder ich arbeite mit 2 echten Sessions - wenn das geht, und sonst muß ich das doch mit PHPSESSID machen. Aber das will ich nicht, denn die müßte ich wieder per Cookie oder URL übertragen.

      Ja, kannst Du ... Bloß was ist, wenn der Timeout abläuft und der User nochwas vergessen hat? Einloggen kann er sich ja nicht mehr, weil er jedesmal wieder die abgelaufene Sessino zugewiesen bekommt.

      Du hast das doch überall, web.de, gmx.de, die haben alle einen Timeout. Wenn der Useer was vergesen hat, dann muß er sich auch nochmal neu anmelden. Die Daten die der USer bearbeitet stehen ja nicht in der Session sondern in der Datenbank, in de Session stehen nur die oben genannten Werte, die dann halt noch einmal neu ermittelt werden müßten, aber besser so als auf jeder Seite neu!

      ? Wie kommst Du da drauf? So weit ich weiß schickt session_start() wenn möglich gleich ein Cookie an den Browser.

      Das ist ja mein Trick! Cockies _und_ Trans-SID sind auf 0! Der Server schickt nichts und niemandem was! Wenn Du dir den Code nochmal anguckst wird die SessionID mit dem Wert von PHP_AUTH_USER belegt, und der wird über den HTTP_AUTH HEADER geschickt, das hat aber nichts mit Sessions zu tun, sondern halt mit http-authentifizierung!

      • Die "sessionID" wird _nirgendwo_ aus Versehen geloggt, das Passwort schon gar nicht

      Sicher? Soweit ich weiß hat PHP eine Funktion (magic_irgendwas), die im Fall der Fälle (wenn das Cookie nicht angekommen ist) die SessionID an jeden Link anhängt. Dann kann die ID wie woanders auch ohne Probleme in den LogFiles hängen bleiben.

      Wie gesagt habe ich alles augeschaltet, der Server sendet _nichts_! ich setze manuell die SessionID in jedem Script, und zwar nach Prüfung auf PHP_AUTH_USER!

      Nur, daß sie in Deinem Fall ohne Passwort natürlich nicht mehr sooo nützlich ist (dafür steht aber ein mit Sicherheit gültiger Benutzername drin!)

      Der steht nirgends. Den zu ermittlen geht nur über das belauschen des Traffics, aber wenn das möglich ist kannst Du dich über http nicht schützen, daher verwende ich zusätzlich https ;-)

      Naja ... ich sehe keinen echten Vorteil darin, außer daß man einen gültigen Usernamen preisgibt. So eine 32-stellige (oder wieviel auch immer) ID dürfte nicht zu erraten sein. Wenn, dann nur über LogFiles oder durch (wie auch immer geartetes) Abhören der Verbindung ...

      Wi egesagt, es geht _nur_ über das abhören, aber dann ist jeder Login-Schutz offen, egal ob über htaccess, oder eigenes Login-Formular... da bekommt er genau so alle Daten serviert, mit https denke ich sollte das hinreichend sicher sein.

      Grüße
      Andreas

      1. Holladiewaldfee,

        Ja, kannst Du ... Bloß was ist, wenn der Timeout abläuft und der User nochwas vergessen hat? Einloggen kann er sich ja nicht mehr, weil er jedesmal wieder die abgelaufene Sessino zugewiesen bekommt.

        Du hast das doch überall, web.de, gmx.de, die haben alle einen Timeout. Wenn der Useer was vergesen hat, dann muß er sich auch nochmal neu anmelden. Die Daten die der USer bearbeitet stehen ja nicht in der Session sondern in der Datenbank, in de Session stehen nur die oben genannten Werte, die dann halt noch einmal neu ermittelt werden müßten, aber besser so als auf jeder Seite neu!

        Jein. Wenn Du eine Session-Variable hast, $_SESSION["timeout"]=1984944797 (oder so), und Du frägst diese Variable dann ab ... dann kann sich der User nicht(!) neu anmelden weil er immer wieder dieselbe Session bekommen würde.

        Naja ... ich sehe keinen echten Vorteil darin, außer daß man einen gültigen Usernamen preisgibt. So eine 32-stellige (oder wieviel auch immer) ID dürfte nicht zu erraten sein. Wenn, dann nur über LogFiles oder durch (wie auch immer geartetes) Abhören der Verbindung ...

        Also, wenn Du's so machst wie im Rest beschrieben ... dann sind einige Vorteile wirklich nicht von der Hand zu weisen :-)
        Aber wenn Du eh HTTPS verwendest ... warum soll das Cookie dann böse sein (wenn die Session sowieso einen Timeout besitzt?)

        Ciao,

        Harry
         (PS: Ich fahr jetzt in Urlaub ... deswegen werd ich wohl keine Antwort mehr schreiben können)

        --
          (There are only 10 types of people in this world: Those who understand binary and those who don't)
        1. Hallo!

          Jein. Wenn Du eine Session-Variable hast, $_SESSION["timeout"]=1984944797 (oder so), und Du frägst diese Variable dann ab ... dann kann sich der User nicht(!) neu anmelden weil er immer wieder dieselbe Session bekommen würde.

          Ich "fräge" überhaupt nix ;-)
          Ich habe session-cache.timeout gemeint, da muß ich nichtmal in die session eingreifen! Aber trotzem wäre es besser Deine Variante mit einem Timestamp in der Session, nur verstehe ich da das Problem nicht! Der Timeout soll doch nur dazu führen dass der User sich neu anmelden muß. Wenn die Zeitdifferenz zwischen time() und $last_request_timestamp halt größer als 60x10, dann wird ein 401-Header gesendet und der Gute muß sich neu anmelden. Gleichzeitig muß ich natürlich diesen alten Timestamp aus der Session löschen, denn sonst kommt er nicht mehr rein. Aber das ist ja kein Problem, man kann sich ja hier einen sehr schönen Mechanismus bauen.

          ich da aber noch eine Frage zur Umsetzung:

          • Ich will das wie bei der normalen HTTP-AUTH durch den Apache nach dem 3. Fehlversuch kein 401 sondern eine Fehlerseite gesendet wird. Wie mache ich das? Vermutlich macht der Apache das über die IP, d.h. er loggt die IPs mit Zeit und Anzahl an Versuchen oder? Kommt mir so umständlich vor! Da muß ich auf ale Fälle was mchen gegen Brute-Force.

          Also, wenn Du's so machst wie im Rest beschrieben ... dann sind einige Vorteile wirklich nicht von der Hand zu weisen :-)

          Ich muß mal ein wenig weiter nachdenken - ob es mir überhaupt einen Vorteil bietet - mal schaun!

          Aber wenn Du eh HTTPS verwendest ... warum soll das Cookie dann böse sein (wenn die Session sowieso einen Timeout besitzt?)

          Nicht jeder akzeptiert cockies.

          (PS: Ich fahr jetzt in Urlaub ... deswegen werd ich wohl keine Antwort mehr schreiben können)

          Dann mal viel Spaß und einen guten Rutsch!

          Grüße
          Andreas

          1. Hallo Andreas,

            • Ich will das wie bei der normalen HTTP-AUTH durch den Apache nach dem 3. Fehlversuch kein 401 sondern eine Fehlerseite gesendet wird. Wie mache ich das? Vermutlich macht der Apache das über die IP, d.h. er loggt die IPs mit Zeit und Anzahl an Versuchen oder? Kommt mir so umständlich vor! Da muß ich auf ale Fälle was mchen gegen Brute-Force.

            Der Apache sendet _immer_ einen 401er _inklusive_ der Fehlerseite - der Browser zeigt sie nur beim dritten Mal erst wieder an. Gegen einen Angreifer, der es per Brute-Force und Perl-Script o.ä. probiert, kommst der Apache so nicht an.

            Du kannst aber die IP jedes gescheiterten Versuchs mitloggen und bei drei Fehlerversuchen innerhalb einer Stunde von dieser IP dann einen 403er senden. (und Du brauchst einen Cleanup-Code, dass er alle mitgeloggten IPs, die älter als eine Stunde sind, wieder löscht)

            Grüße,

            Christian

            --
            Ich wünsche allen frohe Weihnachten!
            Ich bitte darum, dass ein Themenbereich (BARRIEREFREIHEIT) eingerichtet wird.
            1. Hallo Christian!

              Du kannst aber die IP jedes gescheiterten Versuchs mitloggen und bei drei Fehlerversuchen innerhalb einer Stunde von dieser IP dann einen 403er senden. (und Du brauchst einen Cleanup-Code, dass er alle mitgeloggten IPs, die älter als eine Stunde sind, wieder löscht)

              Wäre es nicht sicherer die Fehl-Versuche pro Username zu loggen, also unabhängig von der IP? Denn mit der IP denke ich könnte man mit Proxies auch in einem Brute-Force Script was machen, aber Benutzernamen müssen auch für Bruteforce nunmal gleich sein.

              Ich speichere username|timestamp

              Das speicher ich bei jedem Fehleversuch, und lösche jedesmal Einträge älter als 1 Stunde, und wenn ein Username mehr als 3 mal vorkommt wird dieser gesperrt, d.h. diese File müßte ich bei _jedem_ Request abfragen, oder? Kommt mir etwas umständlich vor!
              Ich könnte gesperrte Usernamen auch auf eine Art Blacklist schreiben, halt nur alle Gesperrten User auslesen, dann kann ich die übrigen Prüfungen bei sowieso gescheiterten Login-Versuchen machen, da ist das ja egal. Dann müßte ich nur Einträge von der Blacklist wieder löschen. Vielleicht mache ich es auch besser über Dateien, ich erstele ein Verzeichnis blacklist/, da kommen dann alle gesperrten Usernamen als leere Datei rein mit Namen gesperrter_user-timestamp.
              Das würde ich dann mit den Verzeichnisfunktionen parsen... aber vermutlich mache ich mir mal wieder zu viele Gedanken um performance, und ich weiß noch nichtmal ob das so schneller ist. Ich muß mal ein bisschen testen, 4 Varianten:
              1. Datenbank-Tabelle
              2. Datei parsen
              3. Verzeichnis auslesen
              4. Array includen

              Grüße
              Andreas