claus ginsel: Wie prüft man ein Checkbox-Feld serverseitig effizient?

Hallo

für eine Auswahlliste möchte ich auf Checkboxen zurückgreifen, dabei stellt sich mir ausgehend von einem Zitat bei php.de

Achtung! Häufig gemachter Fehler:
Für Auswahlfelder und -listen werden die Gefahren von XSS oftmals unterschätzt, weil sie im Gegensatz zu Textfeldern keine direkte Eingabe von Schadcode ermöglichen. In Anbetracht der Tatsache, dass in einem gefälschten Formular allein die richtige Angabe des name-Attributs ausreicht, um beliebige Werte und Werttypen für beliebige Elemente übermitteln zu können, ist die Annahme jedoch haltlos und damit besonders gefährlich.

die Frage, wie man das sinnvollerweise anstellt.

Bisher habe ich, von einem Checkbox-Feld ausgehend, folgendes:

	if(isset($_POST['MailRecip'])) {
		foreach ($_POST['MailRecip'] as $name) {
		  //if(preg_match('/^[a-zA-ZäöüÄÖÜß\-]{3,40}$/', $name)==false) exit(header('Location: ...'));
			if (array_key_exists($name, array_Recip)==false) exit(header('Location: ...'));
		}
}

Was aber, wenn andere gleichbenannte Formularelemente kommen, wie ließe sich das umfassend lösen?

Gruß Claus

akzeptierte Antworten

  1. Tach!

    Bei dem zitierten Text geht es im Werte. Deine Frage bezieht sich aber anscheinend auf Keys.

    Bisher habe ich, von einem Checkbox-Feld ausgehend, folgendes:

    	if(isset($_POST['MailRecip'])) {
    		foreach ($_POST['MailRecip'] as $name) {
    		  //if(preg_match('/^[a-zA-ZäöüÄÖÜß\-]{3,40}$/', $name)==false) exit(header('Location: ...'));
    			if (array_key_exists($name, array_Recip)==false) exit(header('Location: ...'));
    		}
    }
    

    Die sinnvollere Methode finde ich, dass man eine Liste der erwarteten Felder hat und durch diese laufend schaut, ob dazu Eingaben vorliegen. Überzählige Keys im $_POST ignoriert man.

    Was aber, wenn andere gleichbenannte Formularelemente kommen, wie ließe sich das umfassend lösen?

    In einem PHP-Array können Keys nicht mehrfach auftreten, auch nicht in $_POST. Bei gleich benannten Formularfeldern gewinnt das spätere.

    Zu beachten ist, wenn der Name ein [] am Ende hat, dann gibt es zwar auch nur einen Key, aber der Wert ist kein Skalar sondern ein Array.

    dedlfix.

    1. Hallo dedlfix,

      das Statement foreach ($_POST['MailRecip'] as $name) kann nur funktionieren, wenn im HTML name="MailRecip[]" mit unterschiedlichen Values notiert ist.

      Wäre das anders, wäre Claus niemals bis zu dem Punkt gekommen, an dem er jetzt seine Frage stellt.

      Die Problemstellung ist doch, wie man mit der Warnung umgehen sollte, die php.da da gibt.

      Prinzipiell ist sie richtig. Man kann in den values eines Buttons oder eine Checkbox allen möglichen Schabernack, bis hin zu schönen Grüßen von Little Bobby Tables, eintragen und darf sie deshalb nicht blindlings in ein SQL einbauen oder wieder auf den Bildschirm schreiben.

      Wenn also in den value-Attributen der Checkboxen bereits die Mailadressen stehen, die Du anschreiben willst, dann könnte es passieren, dass ein böser Mensch das HTML editiert und da Fremdadressen eintippt. Oder einfach die Mailadressen absaugt und als Spam-Target verkauft. Lösung: Möglichst keine abgreifbaren oder fälschbaren Informationen auf die Webseite bringen, sondern nur unschädliche Referenzen auf diese Informationen. Den Bezug zwischen Referenz und eigentlichem Wert stellst Du über eine Tabelle in der Session her. In diesem Fall sähe das so aus: Nicht die Mailadressen oder deren DB-ID eintragen, sondern nur eine laufende Nummer. Und in der Session speicherst Du, welche laufenden Nummern Du für Mailadressen auf der Seite hast und stellst die Mailadresse (oder die DB-ID) daraus her. Ggf. passt es in der Session nicht. Dann könntest Du auch ein hidden input verwenden (anderswo ViewState oder PageState genannt), aber DAS muss zufallsgesalzen, prüfgesummt und mit einem serverseitig gespeicherten Key verschlüsselt werden, so dass keine Known Plaintext Attacke und keine Fälschung möglich ist. Session ist einfacher, kann aber schiefgehen wenn der User mehrere Fenster zur gleichen Session öffnet.

      Die Frage, die sich Claus stellt, ist aber eine andere. Was tun, wenn im Formular zwei Elemente den gleichen Name haben und sich gegenseitig überschreiben. Im Falle der Checkboxliste könnte es sogar passieren, dass es außer den Checkboxen mit name="MailRecip[]" noch ein Input-Feld mit name="MailRecip" gibt - in dem Fall würde der foreach auf die Nase fallen.

      Das ist dann eigentlich keine Frage der Security, sondern der Robustheit. Die Situation kann durch fehlerhaft geschriebenes HTML entstehen. Die auf den Produktionsserver geladene Version der Seite sollte solche Programmierfehler nicht mehr enthalten, das muss im Test auffallen. Ein Fatal Error ist besonders auffällig dafür und deshalb eine gute Antwort auf die Situation.

      Oder ein Script Kiddie postet seine Attacke fehlerhaft, auch in dann ist ein Fatal Error mit stumpfem HTTP 500 Abschluss des Requests die passende Antwort. Den Absturzgrund schreibt man in den Serverlog.

      Man ist geneigt, technische Fehler vom User möglichst fernzuhalten. Bestimmte Dinge müssen in einer Anwendung aber einfach passen, um sie produktiv schalten zu dürfen. Dazu gehört, dass der PHP Code alle Requests aus der HTML-Seite, die er zuvor hinausgeschickt hat, technisch auch verarbeiten kann. Kann er das nicht, MUSS es deutlich und katastrophal auffallen, sowas darf sich nicht im Log verstecken. Dass man mit bestimmten Benutzereingaben nicht klarkommt ist etwas anderes. Benutzereingaben sind von Programmen als grundsätzlich falsch, unsinnig und destruktiv vorauszusetzen. Das führt dazu, dass man als Programmierer eine gewisse Grundparanoia als Geisteshaltung entwickelt und überall erstmal die potenziellen Fehler sucht. Die muss man zügeln. "Du siehst immer alles nur negativ" - ist sonst der Satz, den man zu hören bekommt.

      Dass die eigentlichen Werte aus dem erwarteten Wertebereich kommen (sprich: numerisch oder ein existierender Key in die Referenztabelle in der Session), ist dann die Nachfolgeprüfung. Aber wenn ich einen Wert nur verwende, um damit einen Array-Eintrag auszulesen, reicht array_key_exists vollkommen als Validierung.

      Rolf

      --
      sumpsi - posui - obstruxi
  2. Was aber, wenn andere gleichbenannte Formularelemente kommen, wie ließe sich das umfassend lösen?

    Also in $_GET oder $_POST tauchen identisch benannte Formularelemente nicht auf. Genau genommen stets nur das letzte.

    Es sei denn, der Name enthalte den Hinweis, dass es ein Array sei: <input name="test[]">, dann erhältst Du ein Array.

    Alternativ kannst Du z.B. $_SERVER['QUERY_STRING'] selbst zerlegen...

    1. Also ich habe das Zitat von php.de so verstanden, dass ein Angreifer ein Formular nachbaut und darin statt Checkboxen zB ein Textfeld mit 'MailRecip' einfügt, um damit irgendwas unerwünschtes zu erwirken.

      Die sinnvollere Methode finde ich, dass man eine Liste der erwarteten Felder hat und durch diese laufend schaut, ob dazu Eingaben vorliegen

      Das mach ich doch, ich schaue, ob der Key in einem Array (als Konstante) existiert.

      Könnte das die Lösung sein:

      is_array($_POST['MailRecip'])
      

      bevor ich die Werte prüfe, ein Textfeld als 'MailRecip' müsste dann ja auffallen?

      Claus

      1. dedlfix, mit Liste der Felder meinst Du die Formularfelder?

        das mach ich doch mit

        if(isset($_POST['MailRecip'])) 
        

        mehr Felder gibt es nicht.

        Claus

      2. Hallo Claus,

        Also ich habe das Zitat von php.de so verstanden, dass ein Angreifer ein Formular nachbaut und darin statt Checkboxen zB ein Textfeld mit 'MailRecip' einfügt, um damit irgendwas unerwünschtes zu erwirken.

        nenee, vergiss mal für einen Moment, dass es ein Formular gibt. Ein Angreifer in Spiellaune kann auch ohne Formular und ohne Browser einen HTTP-Request an dein Script absetzen, z.B. mit dem Kommandozeilentool wget.

        Und damit kann ich dann beliebige Parameter übergeben - beliebig, sowohl was deren Namen angeht, als auch die Werte.

        Die sinnvollere Methode finde ich, dass man eine Liste der erwarteten Felder hat und durch diese laufend schaut, ob dazu Eingaben vorliegen

        Das mach ich doch, ich schaue, ob der Key in einem Array (als Konstante) existiert.

        Ein guter Ansatz, wobei die von dedlfix angedeutete Methode, unerwartete Parameter einfach zu ignorieren, auch in Ordnung ist[1].
        Und dann solltest du zu jedem erlaubten Parameter prüfen, ob auch der gelieferte Wert plausibel und vom richtigen Typ ist.

        Könnte das die Lösung sein:

        is_array($_POST['MailRecip'])
        

        bevor ich die Werte prüfe, ein Textfeld als 'MailRecip' müsste dann ja auffallen?

        In diesem Beispiel bestimmt vernünftig. Denn wenn dein Script ein Array erwartet, stattdessen aber nur einen einzelnen String bekommt, wird es einen Scriptfehler beim Zugriff auf ein Arrayelement geben.

        May the Schwartz be with you
         Martin

        --
        Ein Student ist im Begriff, sich in der Mensa zu einem Professor an den Tisch zu setzen.
        Professor: Seit wann essen denn Adler und Schwein an einem Tisch?
        Student: Okay, dann flieg' ich halt einen Tisch weiter.

        1. Wobei man sich fragen kann, ob es sich lohnt, den Request überhaupt weiter zu bearbeiten, wenn er offensichtlich fake ist, oder ob man dann lieber das Script kontrolliert abbricht (z.B. mit Status 400). ↩︎

        1. Tach!

          Die Frage ist für mich, ob es sich lohnt, sowas zu untersuchen. Kann man machen, wenn man will. Aber man bekommt auch mit ordnungsgemäßen Requests Werte rein, die man nicht haben möchte, und die man nicht unbedingt mit technischen Mitteln finden kann, oder wo der Aufwand zu hoch ist, diese zu erkennen. Man muss also auch so ein Auge auf seine Daten haben.

          Ich würde nur nehmen, was mich interessiert und den Rest ignorieren. Wenn das verwendete Framework eine Erkennung unangeforderter Keys enthält, dann ok, sonst mach ich mir den Aufwand nicht, ohne dass ein konkreter Angriff das als wirksame Gegenmaßnahme erzwingt.

          dedlfix.

        2. Wobei man sich fragen kann, ob es sich lohnt, den Request überhaupt weiter zu bearbeiten, wenn er offensichtlich fake ist, oder ob man dann lieber das Script kontrolliert abbricht (z.B. mit Status 400)

          Ich breche ab mittels Umleitung auf Fehlerseite.

          Student und Prof treffen sich am Pissoir. Student: dass wir uns hier mal treffen als ebenbürtige Personen :) Prof: und schon haben Sie wieder den kürzeren gezogen ;)

          Gruß Claus

  3. Moin Claus,

    für eine Auswahlliste möchte ich auf Checkboxen zurückgreifen, dabei stellt sich mir ausgehend von einem Zitat bei php.de

    Achtung! Häufig gemachter Fehler:
    Für Auswahlfelder und -listen werden die Gefahren von XSS oftmals unterschätzt, weil sie im Gegensatz zu Textfeldern keine direkte Eingabe von Schadcode ermöglichen. In Anbetracht der Tatsache, dass in einem gefälschten Formular allein die richtige Angabe des name-Attributs ausreicht, um beliebige Werte und Werttypen für beliebige Elemente übermitteln zu können, ist die Annahme jedoch haltlos und damit besonders gefährlich.

    die Frage, wie man das sinnvollerweise anstellt.

    Du musst prüfen, ob der vom Auswahlfeld übermittelte Wert dem entspricht, was du erwartest – stell dir dabei vor, dass jemand das Auswahlfeld oder die Auswahlliste durch eine textarea ersetzt hätte.

    Bisher habe ich, von einem Checkbox-Feld ausgehend, folgendes:

    	if(isset($_POST['MailRecip'])) {
    		foreach ($_POST['MailRecip'] as $name) {
    		  //if(preg_match('/^[a-zA-ZäöüÄÖÜß\-]{3,40}$/', $name)==false) exit(header('Location: ...'));
    			if (array_key_exists($name, array_Recip)==false) exit(header('Location: ...'));
    		}
    }
    

    Was ist denn array_Recip für eine Konstante?

    Was aber, wenn andere gleichbenannte Formularelemente kommen, wie ließe sich das umfassend lösen?

    Du erwartest, dass MailRecip ein Array enthält. Was passiert denn im foreach, wenn das nicht der Fall ist?

    Viele Grüße
    Robert

    1. Moin Robert

      Was ist denn array_Recip für eine Konstante?

      das ist ein Array mit Name => Mailadresse, ausgelagert in eine Datei und included, relativ unveränderbar, sprich es gibt selten Änderungen und wenn, dann mach ich das über direkten Zugriff auf die Datei.

      Allerdings sind nicht immer alle Personen zu adressieren, dafür braucht der User ein Auswahlformular.

      Du erwartest, dass MailRecip ein Array enthält. Was passiert denn im foreach, wenn das nicht der Fall ist?

      Das war der Ausgangspunkt der Frage. Ich hatte foreach zum Test mal angewendet auf eine String-Variable, und da gab es dann einen Fehler.

      Claus

    2. Du musst prüfen, ob der vom Auswahlfeld übermittelte Wert dem entspricht, was du erwartest – stell dir dabei vor, dass jemand das Auswahlfeld oder die Auswahlliste durch eine textarea ersetzt hätte.

      Ja, genau. Das war mein Problem.

      Das werde ich vorab mit is_array ausschließen.

      Gruß Claus

  4. Besten Dank in die Runde

    Ein schönes Wochenende

    Gruß Claus