encoder: Passwortschutz für bestimmte Unterseite

Hallo ihr Lieben

Ich suche den einfachsten Weg um eine einzelne Unterseite eines Webauftritts mit einem Passwort zu schützen. Dieses wird vom Inhaber des Auftritts an diejenigen Personen verteilt die diese Seite sehen dürfen. Einen Benutzernamen gibt es nicht.

Ich dachte an einen AJAX Request. Dieser nimmt das Passwort in einem Eingabefeld entgegen und schickt es mit POST an das Serverscript, das schaut ob es das richtige ist. Die Rückantwort wird mit JS in die Seite eingebaut.

Ein "sollen die Daten erneut gesendet werden" möchte ich nicht. Dass beim Reload der Seite wieder die Abfrage erscheint ist in Ordnung.

Sollte doch eine sinnvolle Lösung sein oder gibts da was speziell für diesen Zweck gedachtes?

  1. Lieber encoder,

    Ich dachte an einen AJAX Request.

    ich dachte an eine Lösung, die rein serverseitig funktioniert - also ohne JavaScript:

    <!doctype html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>geschützte Seite</title>
      </head>
      <body>
        <h1>Überschrift</h1><?php
    // Passwort übertragen?
    if (array_key_exists('pw', $_POST)
      // hier wird das Passwort geprüft
      && $_POST['pw'] == 'geheim!'
    ) { ?>
        <p>Hier nun die geheimen Inhalte:</p>
        ...
    <?php
    } else {
      // hier wird zur Eingabe des Passwortes aufgefordert
    ?>
        <p>Diese Inhalte werden erst nach Eingabe eines Passwortes sichtbar.</p>
        <form method="post">
          <p>
            <label>
              Passwort:
              <input name="pw" type="password">
            </label>
            <button>freischalten</button>
          </p>
        </form>
    <?php
    } ?>
      </body>
    </html>
    

    Obigen Code in eine Datei mit der Endung .php speichern und von einem Webserver ausliefern lassen. Sollte funktionieren (bei mir tut's).

    Liebe Grüße,

    Felix Riesterer.

    1. Diese Lösung hätte eine Nachfrage beim Reload zur Folge und das Passwort könnte im Browser gespeichert werden, was ich eigentlich nicht will.

      Wäre interessant zu wissen warum die Antwort ein Minus bekommen hat.

      1. Lieber encoder,

        Diese Lösung hätte eine Nachfrage beim Reload zur Folge und das Passwort könnte im Browser gespeichert werden, was ich eigentlich nicht will.

        und das willst Du durch die Nutzung von JavaScript als "Umweg" vermeiden? Verstehe...

        Wäre interessant zu wissen warum die Antwort ein Minus bekommen hat.

        Weil sie im wörtlichsten aller Sinne "quick and dirty" ist.

        Liebe Grüße,

        Felix Riesterer.

      2. Diese Lösung hätte eine Nachfrage beim Reload zur Folge

        Da man nicht auf GET ausweichen kann kann das nur behoben werden, wenn man für die Passworteingabe zu einer Seite mit eigener URL weiterleitet:

        header ( 'location: "HTTPS://' . $_SERVER[SERVER_NAME] . '/login.php' );
        

        Die login.php muss natürlich im Erfolgsfall ihrerseits wieder umleiten. Das HTTPS habe ich nicht grundlos fest eingesetzt.

        und das Passwort könnte im Browser gespeichert werden.

        Mit Einschränkungen hilft hier:

        <input type="password" autocomplete="off">
        

        Die Einschränkung ist, dass ein auf einem nicht selbst beherrschten System jemand anderes einen Browser kompiliert haben kann, in dessen Quelltext die vom Betreiber vorgesehene Reaktion auf das autocomplete="off" nicht Deinen Vorstellungen entspricht. Das Passwort kann also immer im Browser oder in einer Datenbank gespeichert werden.

        Man vermeide also von der NSA, dem IS, der AKP oder der Mafia betriebene Interetcafes. Man erkennt diese ganz einfach am Firmenschild: Steht da "Internetcafe" sollte man darin, außer sehr vorsichtig und mit eigener Hardware, nichts tun, außer den Wetterbericht zu lesen.

        Allerdings führt das autocpmplete="off" durchaus auch wieder zu Problemen: Nämlich dem, dass die Benutzer es irgendwo notieren und dann schlampig damit umgehen.

        In dem Sinne kann es durchaus problematisch sein, wenn es ein Passwort für alle gibt. Man kann dann keinen einzelnen Benutzer sperren und eine Änderung führt zu nervigen Anfragen.

        Klar ist, dass auch solche Passwörter nicht einfach mal im Klartext irgend wo rumstehen sollten. Zudem zählt das gehashte Passwort zu den Daten. Also das Passwort hashen und einer Datei außerhalb von document_root ablegen:

        <?php
        define ('PASSWORD_FILE', '/nicht_oeffentlich/passwordfile.asc');
        $password = 'geheim';
        file_put_contents (
            PASSWORD_FILE,
            password_hash( $password, PASSWORD_DEFAULT )
        );
        

        Damit kann man das Passwort also (z.B. in einer SSH-Sitzung) manuell ablegen und so gegentesten:

        define ( 'PASSWORD_FILE', '/nicht_oeffentlich/passwordfile.asc' );
        $pw_hash = file_get_contents( PASSWORD_FILE );
        if (
          array_key_exists( 'pw', $_POST )
          // hier wird das Passwort geprüft
          && password_verify(
              $_POST['pw'],
              $pw_hash
             )
          ) {
            ### Stilles Rehashing bei verbesserter Hash-Methode:
            if (password_needs_rehash ( $pw_hash ) ) {
              file_put_contents (
                PASSWORD_FILE,
                password_hash( $_POST['pw'], PASSWORD_DEFAULT )
              );
            }
            # Weiter...
        

        Statt jetzt die Seite auszuliefern, wird eine Session gestartet:

              define ( 'RESSOURCE_ID' , 'ghh334jh' );
              session_start();
              $_SESSION[RESSOUCE_ID] = array();
              $_SESSION[RESSOUCE_ID]['ALLOWED'] = true);
        

        und weitergeleitet:

             header ( 'location: "HTTPS://' . $_SERVER[SERVER_NAME] . '/geheimnis.php' );
        

        Im Falle des Fehlschlages halt die Passworteingabe mittels Formular anfordern.

        Die geheime Seite:

        <?php
        define ( 'RESSOURCE_ID' ,'ghh334jh' );
        session_start();
        if ( ! true ===  $_SESSION[RESSOUCE_ID]['ALLOWED'] ) {
          header ( 'location: "HTTPS://' . $_SERVER[SERVER_NAME] . '/login.php' );
          exit; # deklaratorisch
        } else {
         
          ####  Inhalte ausliefern ###
        }
        

        Das kann man gewiss noch verfeinern - aber ich schreibe gerade mit Blick gegen die Sonne. Die RESSOUCE_ID habe ich eingeführt, falls Du morgen zwei Seiten mit unterschiedlichen Nutzerkreisen hast. Was ich zeige ist relativ einfach auf ganze Verzeichnisse unstellbar und auch darauf, dass die Benutzer vielleicht mal eigene Passwörter haben.

      3. Diese Lösung hätte eine Nachfrage beim Reload zur Folge und das Passwort könnte im Browser gespeichert werden, was ich eigentlich nicht will.

        Das Prinzip ist jedoch als AJAX Lösung dasselbe. Wie hättest Du denn gerne die Passwortabfrage, als Modaler Dialog Prompt per JS erzeugt oder als <form><input>?

        MfG

        PS: Alternative zu POST wäre HEAD. Und das Passwort in einem Custom Header ggf. als Crypt (sha256.js).

    2. Hallo Felix,

      das Minus ist nicht von mir - aber genau sowas führt doch zu den Browserabfrage ob die Daten erneut gesendet werden sollen. Und das möchte encoder nicht.

      Die korrekte Methode ist eigentlich eine einmalige Passwortabfrage, deren Ergebnis in der Session gespeichert wird. Ein Ajax-Request ist meines Wissens ebenfalls mit dem Session-Cookie unterwegs, so dass ein von Ajax gesetztes Session-Flag im nächsten regulären PHP Request ankommen sollte.

      Wenn keine Session erwünscht ist, wäre ein anderer Weg, den ich mir noch vorstellen könnte, ein spezieller Login-Cookie. In dem ist ein Wert drin, der von der Session-ID und einem längeren geheimen Wert auf dem Server abgeleitet ist. Geheimwert und Session-ID werden zusammengeworfen und mit password_hash verschlüsselt (was ein Zusatz-Salz einwirft). Das Ergebnis kommt in den Login-Cookie. Wenn der Server bei Seitenabrufen einen Login-Cookie findet, nimmt er den Geheimwert, die Session-ID und den Login-Cookie und macht damit den password_verify.

      Zur Steigerung der Sicherheit könnte man den Geheimwert auf dem Server noch täglich wechseln. Das kann man beliebig kompliziert gestalten.

      D.h. solange der serverseitige Geheimcode und die Session-ID des Users gleich bleiben, bleibt auch der Login-Cookie gültig. Ich bin aber kein Sicherheitsexperte und kann nicht sagen, ob ich hier Schlangenöl verkaufe. Ein Problem dieser Idee ist vor allem, dass man pro Seitenabruf einen password_verify aufrufen muss, und diese Funktion ist ja vorsätzliche langsam. Eine Session ist besser.

      Rolf

      --
      sumpsi - posui - clusi
      1. Hallo Rolf,

        Ein Ajax-Request ist meines Wissens ebenfalls mit dem Session-Cookie unterwegs,

        Kommt drauf an[tm]. Die neue fetch()-API erwartet, dass man { credentials: "same-origin" } setzt, damit Cookies und HTTP Authentication headers übertragen werden.

        Ansonsten: guter Beitrag. Mir hat gefallen, dass du nicht den üblichen Fehler propagiert hast, einfach nur ein Geheimnis als Login-Cookie zu übertragen, sondern die password-API vorgeschlagen hast. Eine kryptografische Verifikation ist wichtig.

        Allerdings würde ich bei so einem Verfahren eher dazu übergehen, die verschlüsselte User-ID in den Login-Cookie zu schreiben. Das könnte man mit der mcrypt-API erreichen. Ich denke aber nicht, dass man damit an Sicherheit gewinnt; das ist wohl eher eine Geschmacksfrage.

        LG,
        CK

        1. Cookies gibt es bei der Seite nicht, auch keine Sessions oder Users. Das Passwort ist tatsächlich fest, ich würde das als Hash fest eintragen und gegenprüfen. Es wäre auch egal wenn nach einem Reload der Seite wieder zunächst die Passwortabfrage erscheint.
          Daher die Überlegung mit AJAX die zwar für diese eine Seite etwas mehr Aufwand wäre, aber am bisherigen Prinzip nichts ändern würde.

          1. problematische Seite

            Daher die Überlegung mit AJAX die zwar für diese eine Seite etwas mehr Aufwand wäre, aber am bisherigen Prinzip nichts ändern würde.

            Soviel Aufwand ist das nun auch wieder nicht: Gucki

            MfG

            1. problematische Seite

              Hallo pl,

              Soviel Aufwand ist das nun auch wieder nicht: Gucki

              Was soll er denn dort sehen??

              Bis demnächst
              Matthias

              --
              Rosen sind rot.
  2. Hallo encoder,

    Ich suche den einfachsten Weg um eine einzelne Unterseite eines Webauftritts mit einem Passwort zu schützen.
    gibts da was speziell für diesen Zweck gedachtes?

    Was spricht gegen .htaccess und .htpasswd?

    Bis demnächst
    Matthias

    --
    Rosen sind rot.