cr87: Verständnisfrage Formularvalidierung

Hallo zusammen, ich habe eine Frage bzgl. der Validierung von Eingabefeldern (Textfelder) in Userforms.

Ich habe dazu folgenden Beispielcode erstellt und lasse ihn in 3 unterschiedlichen weisen Ausgeben. Das Phänomen was ich mir nicht erklären kann ist, dass das Zeichen < nicht verarbeitet wird, alle anderen Zeichen werden unter 1 ganz normal ausgegeben.

Kann mir jemand sagen, woran das liegt (also warum der Filter so agiert) und was man für eine Validierung von Text besser nutzen kann/sollte unter Berücksichtigung aller Sonderzeichen?

echo "<form accept-charset=\"utf-8\" action=\"\" method=\"post\">";
echo "<input type=\"text\" id=\"value\" name=\"value\" value=\"\" autocomplete=\"off\">";
echo "<input type=\"submit\">";
echo "</form>";

$POST_value = ""; if(isset($_POST['value'])) { $POST_value = filter_input(INPUT_POST, 'value', FILTER_SANITIZE_STRING); }
	
echo "1".$POST_value."<br><br>";
echo "2".htmlentities($POST_value)."<br><br>";
echo "3".$_POST['value'];
  1. Hallo,

    Das Phänomen was ich mir nicht erklären kann ist, dass das Zeichen < nicht verarbeitet wird, alle anderen Zeichen werden unter 1 ganz normal ausgegeben.

    Das < ist halt ein Sonderzeichen.

    Gruß
    Kalk

    1. Hi,

      danke für deine Antwort. D.h. ich nehme einfach

      filter_input(INPUT_POST, 'value', FILTER_SANITIZE_SPECIAL_CHARS,FILTER_SANITIZE_STRING)
      

      ?

      Dadurch würde zuerst masikiert?

      1. Hallo cr87,

        filter_input(INPUT_POST, 'value', FILTER_SANITIZE_SPECIAL_CHARS,FILTER_SANITIZE_STRING)
        

        einfach htmlspecialchars($_POST['value']);

        htmlspecialchars maskiert bei der Ausgabe wirklich nur die notwendigen Zeichen. Im Gegensatz zu htmlentities. Es ist bei der Verwendung von UTF-8 nicht notwendig, dass etwa aus &euro; gemacht wird.

        Bis demnächst
        Matthias

        --
        Pantoffeltierchen haben keine Hobbys.
        ¯\_(ツ)_/¯
        1. Hallo Matthias,

          danke für deine Antwort. Okay!

          Andere Frage, bei einer Speicherung der Werte in einer lokalen SQL Tabelle (SQLITE). Wie würdest du hier sicherstellen, dass keine gefährlichen Zeichen im Text sind?

          1. Hallo cr87,

            https://wiki.selfhtml.org/wiki/Programmiertechnik/Kontextwechsel/erkennen_und_behandeln

            Bis demnächst
            Matthias

            --
            Pantoffeltierchen haben keine Hobbys.
            ¯\_(ツ)_/¯
      2. Tach!

        danke für deine Antwort. D.h. ich nehme einfach

        filter_input(INPUT_POST, 'value', FILTER_SANITIZE_SPECIAL_CHARS,FILTER_SANITIZE_STRING)
        

        ?

        Abgesehen von meinen Anmerkungen nebenan, wäre das auch so nicht richtig. Gemäß PHP-Handbuch kann man immer nur einen Filter angeben, und dessen ID (oder zugehörige Konstante) ist als drittes Argument zu notieren. Der vierte Parameter kann Optionen enthalten. Die Konstanten für die dort erwarteten Werte beginnen (meistens) mit FILTER_FLAG_. Der Code wird keinen Fehler werfen, aber ein unerwartetes Ergebnis bringen, weil der Wert der Konstante FILTER_SANITIZE_STRING vielleicht dem Wert einer der Optionen entspricht und damit gültig aussieht. Der Filter arbeitet aber lediglich gemäß der Werte und nicht anhand der Namen der Konstanten. PHP kann hier nicht erkennen, dass du was anderes meinst.

        dedlfix.

  2. Tach!

    Ich habe dazu folgenden Beispielcode erstellt und lasse ihn in 3 unterschiedlichen weisen Ausgeben. Das Phänomen was ich mir nicht erklären kann ist, dass das Zeichen < nicht verarbeitet wird, alle anderen Zeichen werden unter 1 ganz normal ausgegeben.

    Du verwendest FILTER_SANITIZE_STRING. Wenn du dazu im Handbuch nachschlägst, was das bewirkt, siehst du: Strip tags, optionally strip or encode special characters. Die Eingabe wird also verändert.

    Aber ist das der richtige Weg, Dinge aus Eingaben zu entfernen oder umzuschreiben? Was ist denn das eigentliche Problem? Nun, Eingabedaten an sich sind harmlos. Probleme entstehen erst dann, wenn man die Daten (und dabei spielt es keine Rolle, ob sie aus einer Eingabe stammen oder sonstwoher) in Richtung eines anderen Systems ausgibt und dieses System Teilen der Daten eine Sonderrolle jenseits von simplem Text zuweist. Zum Beispiel ist das System HTML ein Mischmasch aus anzuzeigendem Text und Steuerinformationen, um bestimmte Abschnitte in diesem Text besonders auszeichnen zu können: als Überschrift, Absatz, Tabelle, Liste, Formular und so weiter. Diese Steuerinformationen werden nun mit denselben Zeichen dargestellt, wie sie auch im Text vorkommen können, und dabei ist es wichtig, korrekt unterscheiden zu können, was nun eigentlich gemeint ist. Ist ein < nun der Beginn eines HTML-Tags oder ist es einfach nur ein Kleiner-Als-Zeichen? In HTML löst man das Dilemma für das < so, dass es als Tag-Bestandteil direkt geschrieben wird, als Text ohne weitere Bedeutung jedoch maskiert werden muss, und als &lt; zu notieren ist (alternative Schreibweisen existieren auch).

    Kann mir jemand sagen, woran das liegt (also warum der Filter so agiert) und was man für eine Validierung von Text besser nutzen kann/sollte unter Berücksichtigung aller Sonderzeichen?

    Ein Eingangsdatenfilter versucht nun, diese Eingabedaten bereits so zu gestalten, dass keine Unklarheiten entstehen. Aber kann er das denn überhaupt? Nein, nicht vollständig. Je komplexer ein System wird, desto weniger weiß die Eingabe, wo am Ende die Daten überall landen werden. Und neben HTML gibt es noch eine Menge anderer Systeme, die ihre eigenen Definitionen haben, was ein Sonderzeichen ist und was nicht, zuzüglich wie Maskierungen zu notieren sind. Zudem müssen diese Regeln für alle Daten beachtet werdenm, nicht nur die, die aus einer Nutzereingabe stammen. Die Eingabefilter sind also meistens ein nicht besonders brauchbares Instrument, den Problemen an den unterschiedlichen Ausgängen zu begegnen. Das Dilemma fällt bereits dann auf, wenn die Daten zwei Wege gehen sollen, einer könnte die SQL-Datenbank sein, der andere die HTML-Ausgabe. Man kann die Eingabe nicht so sanieren, dass alle Zeichen erhalten bleiben und gleichzeitig für alle Ausgabesysteme korrekt maskiert sind. Deshalb sind solche Eingabefilter, wenn überhaupt, nur bedingt brauchbar. Der bessere Weg ist, die Daten erst direkt vor der Übergabe in ein weiteres System zu behandeln und zwar nach den speziellen Regeln dieses Systems. Das nennt sich Beachtung des Konextwechsels und ist im Wiki ausführlich beschrieben.

    Konkrete Anmerkungen zum Code:

    echo "<form accept-charset=\"utf-8\" action=\"\" method=\"post\">";
    echo "<input type=\"text\" id=\"value\" name=\"value\" value=\"\" autocomplete=\"off\">";
    echo "<input type=\"submit\">";
    echo "</form>";
    

    Einen Block aufzuteilen und als einzelne echo-Anweisungen auszugeben ist recht umständlich, besonders wenn keine Variablenersetzungen oder andere Änderungen drin vorkommen. PHP verlassen ?>, das HTML direkt notieren und für PHP-Code wieder zu PHP wechseln, ist einfacher zu notieren und auch zu lesen. Alternativ gibt es auch die Heredoc- und Nowdoc-Syntax von Strings.

    $POST_value = ""; if(isset($_POST['value'])) { $POST_value = filter_input(INPUT_POST, 'value', FILTER_SANITIZE_STRING); }
    

    Der Filter klaut bereits das < und gegebenenfalls nachfolgende Zeichen. Das htmlentities() in nachfolgenden Code hat nun nichts mehr zu tun. Wenn der Filter weggelassen wird, sorgt htmlentities() dafür, dass Sonderzeichen richtig für die HTML-Ausgabe maskiert werden.

    echo "1".$POST_value."<br><br>";
    echo "2".htmlentities($POST_value)."<br><br>";
    echo "3".$_POST['value'];
    

    Apropos htmlentities(), diese Funktion macht viel zu viel. Sie maskiert nicht nur die für HTML notwendigen Zeichen, sondern auch Dinge wie Umlaute, was aber nicht erforderlich ist, wenn man mit dem Thema Zeichenkodierung korrekt umgeht. htmlspecialchars() hingegen kümmert sich lediglich um die notwendigen Zeichen. Es ist besser, diese Funktion zu verwenden, damit die Ausgabe nicht durch unnötige Maskierungen angereichert wird.

    dedlfix.

    Folgende Beiträge verweisen auf diesen Beitrag:

  3. Liebe(r) cr87,

    in Ergänzung zu meinen Vorrednern...

    echo "<form accept-charset=\"utf-8\" action=\"\" method=\"post\">";
    echo "<input type=\"text\" id=\"value\" name=\"value\" value=\"\" autocomplete=\"off\">";
    echo "<input type=\"submit\">";
    echo "</form>";
    

    Das liest sich echt übel. Wenigstens ein bisschen schöner läse sich das hier:

    $html = '<form accept-charset="utf-8" action="" method="post">'
      . '<input type="text" id="value" name="value" value="" autocomplete="off">'
      . '<input type="submit">'
      . '</form>';
    
    echo $html;
    

    Aber so will man das eigentlich nicht haben. Man will eigentlich PHP-Programmlogik und HTML-Code getrennt verwalten. Also speichert man den HTML-Code in einer HTML-Datei, deren Inhalt man dann einliest:

    <form accept-charset="utf-8" action="" method="post">
      <input type="text" id="value" name="value" value="" autocomplete="off">
      <input type="submit">
    </form>
    

    Daran fällt dann auch sofort auf:

    Also sollte Dein HTML eher so aussehen:

    <form accept-charset="utf-8" action="" method="post">
      <p>
        <label>
          <span>Wert:</span>
          <input type="text" name="value" value="" autocomplete="off">
        </label>
      </p>
      <p>
        <button>Daten senden</button>
      </p>
    </form>
    

    So, jetzt haben wir ein besseres HTML und dazu auch noch in einer externen HTML-Datei. Wenn Du die Beschriftung nicht sehen willst, dann nutze CSS, um das span-Element für sehende Benutzer (und nur diese!) unsichtbar zu machen.

    Jetzt holen wir uns das in unsere PHP-Programmlogik:

    $form_html = file_get_contents(__DIR__.'/templates/form.html');
    
    echo $form_html;
    

    Natürlich will man das gesamte HTML erst am Ende der PHP-Programmlogik ausgeben. Warum? Unterwegs könnte es sich ergeben, dass man anstelle von HTML lieber JavaScript, CSS oder gar die Daten einer Bild- oder PDF-Datei ausgeben möchte. Das wird bei komplexeren PHP-Programmen durchaus angeboten. Auch wenn man mit Sessions arbeitet, zerschießen voreilige Ausgaben an den Browser die Möglichkeit an den Cookie-Daten oder anderen HTTP-Headern noch etwas zu schrauben.

    $POST_value = ""; if(isset($_POST['value'])) { $POST_value = filter_input(INPUT_POST, 'value', FILTER_SANITIZE_STRING); }
    

    Auch das liest sich sehr schlecht. Für die if-Anweisung solltest Du eine neue Zeile notieren. Immerhin verwendest Du geschweifte Klammern um die Anweisung innerhalb Deines if-Anweisungsblocks, obwohl nur eine Anweisung darin steht. Das ist als grundsätzliches Vorgehen sehr empfehlenswert.

    Auch das Umkopieren in eine Variable verdeckt, wo Daten her kommen. Das ist bei Nutzereingaben konkret ein Sicherheitsrisiko, da Du den Daten später nicht mehr ansiehst, dass sie aus einer unsicheren Quelle kommen. Auch wenn Deine Variable im Namen noch POST enthält, so ist es aber nicht mehr das $_POST-Array. Arbeite lieber mit den originalen Daten bis kurz vor dem Speichern.

    echo "1".$POST_value."<br><br>";
    echo "2".htmlentities($POST_value)."<br><br>";
    echo "3".$_POST['value'];
    

    Nee, wenn Du Debugging betreiben willst, dann schreibe das besser in eine Textdatei, anstatt es im Browser auszugeben. Dann siehst Du genauer, was da passiert. Hier ein Vorschlag, wie ich das mache:

    function debug () {
      $arg_list = func_get_args();
    
      foreach ($arg_list as $v) {
    
        file_put_contents(
          __DIR__.'/debug.txt',
          (is_string($v)
            ? $v
            : print_r($v, true) // true = no immediate output to browser
          ),
          FILE_APPEND
        );
      }
    }
    
    debug("Hallo Welt!\r\n", array(1,2,3,'vier','V',6),"============");
    

    Die Funktion debug nimmt eine beliebige Menge an Argumenten entgegen und schreibt die übergebenen Werte in eine Textdatei, wobei jeder Schreibvorgang die Datei erweitert, anstatt vorhandene Inhalte zu überschreiben. Der obige Aufruf ergibt das hier:

    Hallo Welt!
    Array
    (
        [0] => 1
        [1] => 2
        [2] => 3
        [3] => vier
        [4] => V
        [5] => 6
    )
    ============
    

    So könntest Du nun den kompletten Inhalt von $_POST ausgeben lassen:

    debug("_POST: ",$_POST);
    

    Liebe Grüße

    Felix Riesterer

    1. Tach!

      Aber so will man das eigentlich nicht haben. Man will eigentlich PHP-Programmlogik und HTML-Code getrennt verwalten. Also speichert man den HTML-Code in einer HTML-Datei, deren Inhalt man dann einliest:

      Will man das? Ich nicht. Ich sortiere nicht nach Codearten sondern nach Verwendungszweck. Grob eingeteilt in Geschäftslogik und Ausgabelogik. Die Geschäftslogik ist das, was den eigentlichen Zweck des Programms ausmacht, das wofür die Daten verarbeitet und beschafft werden sollen. Die Darstellung hingegen ist wichtig für die Anwender, aber meist ist diese Nutzeroberfläche austauschbar. Statt HTML für Menschen könnte man genausogut JSON für den maschinenellen Zugriff entgegennehmen und produzieren. Die eigentliche Verarbeitung in der Geschäftslogik bleibt dabei gleich.

      Wenn für die Ausgabe Programmcode notwendig ist (Daten aus einem Array in einer Schleife ausgeben, if-else-Entscheidungen, die Darstellung betreffend), dann kommt der mit in den Teil, in dem das HTML steht. HTML kennt nämlich keine Programmlogik. Wenn man solche Wiederholungen ohne PHP-Code ausführen möchte, muss man eine Template-Sprache hinzunehmen. Und dann mischt man ebenso, nur nicht HTML mit PHP sondern mit Template-Syntax. Das Ziel, Code nach Sprache trennen zu wollen, sehe ich nicht als ein grundlegend sinnvolles an. Natürlich kann es fallbezogen gewichtige Gründe geben, statt PHP als Sprache für die Ausgabelogik eine andere oder eine Template-Engine mit eigener Syntax zu nehmen.

      Jetzt holen wir uns das in unsere PHP-Programmlogik:

      $form_html = file_get_contents(__DIR__.'/templates/form.html');
      
      echo $form_html;
      

      Natürlich will man das gesamte HTML erst am Ende der PHP-Programmlogik ausgeben. Warum?

      Umformuliert nach meinem Ansatz heißt das: Natürlich will man die Ausgabe erst nach der Geschäftslogik vornehmen. Das Folgende bleibt dann sinngemäß gleich.

      Unterwegs könnte es sich ergeben, dass man anstelle von HTML lieber JavaScript, CSS oder gar die Daten einer Bild- oder PDF-Datei ausgeben möchte. Das wird bei komplexeren PHP-Programmen durchaus angeboten. Auch wenn man mit Sessions arbeitet, zerschießen voreilige Ausgaben an den Browser die Möglichkeit an den Cookie-Daten oder anderen HTTP-Headern noch etwas zu schrauben.


      Auch das Umkopieren in eine Variable verdeckt, wo Daten her kommen. Das ist bei Nutzereingaben konkret ein Sicherheitsrisiko, da Du den Daten später nicht mehr ansiehst, dass sie aus einer unsicheren Quelle kommen.

      Nein, das ist kein Risiko. Die Daten und ihre Herkunft sind nicht das Problem, sondern wenn sie unsachgemäß in den Code des Ausgabemediums eingebaut werden. Nicht der Anwender (mit bösen Absichten) ist das Problem, sondern der Programmierer, der die Daten nicht korrekt verarbeitet. Jegliche Daten müssen korrekt maskiert ausgegeben werden, nicht nur Nutzereingaben. Es spielt also keine Rolle, ob man ihnen die Herkunft ansieht. Das Umkopieren ist nur schlecht, weil es oftmals nur ein unnötiger Vorgang ist.

      Auch wenn Deine Variable im Namen noch POST enthält, so ist es aber nicht mehr das $_POST-Array. Arbeite lieber mit den originalen Daten bis kurz vor dem Speichern.

      Auch das kann ich so pauschal nicht bekräftigen. Es gibt Frameworks die sind so nett und extrahieren die Daten aus dem GET/_GET/_POST-Array und stellen sie gleich als ein schönes Objekt zur Verfügung, genauso wie es die Geschäftslogik am liebsten mag. Schichtweg nur Daten, ohne die Umstände der Herkunft beachten zu müssen. Üblicherweise kann man solchen Frameworks Hinweise geben, wo die Daten herzuholen sind, wenn das wichtig für die Sicherheit ist, aber das ist für die eigentliche Geschäftslogik nicht relevant. Inhaltliche Prüfungen, also dass zum Beispiel Wertebereiche eingehalten werden, ist hingegen Aufgabe der Geschäftslogik. Auch dafür ist die Herkunft egal, es zählt nur, was der Geschäftsvorgang benötigt.

      Nee, wenn Du Debugging betreiben willst, dann schreibe das besser in eine Textdatei, anstatt es im Browser auszugeben. Dann siehst Du genauer, was da passiert.

      Das empfinde ich als unnötig umständlich. Zudem muss man dafür dem PHP Schreibrechte im Dateisystem geben und auch noch selbst direkten Zugriff darauf haben. Für mich spricht nichts grundlegendes dagegen, Debugging-Daten auch über den Browser sichtbar zu machen. Genauer oder auch nur übersichtlicher wird das Ergebnis nicht durch die Art des Ausgabemediums, sondern durch die Art der Darstellung.

      Hier ein Vorschlag, wie ich das mache:

      Hier mein Vorschlag: var_dump() erzeugt die genaueste Ausgabe. Auch Typinformationen kann man damit sehen. Ein echo konvertiert bestimmte Datentypen in eine für den Endanwender schöne Form, die aber dem Entwickler zu viel verbirgt. Ein false wird zum Beispiel als Leerstring ausgegeben und ein true als 1, was nicht unterscheidbar zur Zahl 1 ist. var_dump() zeigt beides schön als false und true an. - Da Browser Whitespace (wie Zeilenumbrüche) in der Ausgabe unbeachtet lassen, gebe ich bei Bedarf (sprich bei den komplexen Strukturen Array und Objekt) noch ein <pre> vorher aus, und das war es. Solche vorübergehenden Ausgaben müssen nicht schön aussehen, sondern nur brauchbare Informationen für das Debugging liefern. Alternativ zu var_dump() nehme ich mitunter auch print_r() für Arrays und Objekte. Das schreibt zwar keine Typinformationen in der Darstellung, ist dafür aber besser lesbar.

      Einen Haken hat die Sache noch, denn bei dieser einfachen Methode ist kein Maskieren für HTML dabei, woraufhin die Browser eventuell enthaltenes HTML oder was dem ähnlich sieht, zu interpretieren versuchen. Meistens stört das für das Debugging nicht, aber wenn doch, nimmt man halt noch ein htmlspecialchars() dazu. Debugausgaben in Dateien kommen bei mir nur in schwerwiegenden Fällen zum Einsatz, wenn ich dadurch einen Vorteil sehe durch irgendeine Funktion des Anzeigeprogramms, z.B. Code-Folding bei längeren Daten.

      dedlfix.

      1. Hier ein Vorschlag, wie ich das mache:

        Hier mein Vorschlag

        Bei wenigen kurzen Debugging-Sessions gehe ich auch so vor. Bei hartnäckigeren Debugging-Sessions lohnt sich der Einsatz eines interaktiven Debuggers. Der ist zwar aufwendiger zu konfigurieren, aber wenn er einmal läuft, kann er viel Zeit sparen. Man muss dann per Hand keine Ausgaben mehr in den Code schreiben und sie anschließend wieder entfernen, man kann einfach Haltepunkte setzen und den kompletten Stacktrace samt aller Variablen-Inhalte inspezieren und den Code schrittweise ausführen.

        1. Tach!

          Bei hartnäckigeren Debugging-Sessions lohnt sich der Einsatz eines interaktiven Debuggers. Der ist zwar aufwendiger zu konfigurieren, aber wenn er einmal läuft, kann er viel Zeit sparen.

          Ja, da wo ein solcher Debugger bereits vorhanden ist (Browser und Visual Studio/Rider), verwende ich ihn auch. Für PHP ist die Installation aufwendig und mit der Voraussetzung verbunden, eine IDE zu verwenden, die den Debugger unterstützt. PHP hat ja keine eigene Oberfläche und die meisten Editoren werden das nicht bieten. Eine IDE andererseits hat zwar eine Menge Vorteile, aber auch den "Preis", ein Schwergewicht zu sein. - Ist Visual Studio Code plus Plugins schon so weit, dass man das für die PHP-Entwicklung als IDE mit Debugger einsetzen möchte?

          dedlfix.

          1. Ist Visual Studio Code plus Plugins schon so weit, dass man das für die PHP-Entwicklung als IDE mit Debugger einsetzen möchte?

            Ich finde schon, meine Erfahrungen:

            • (Conditional) Breakpoints funktionieren zuverlässig
            • Stacktraces funktionieren
            • Read Eval Print Loop funktioniert und man kann sogar Statements im aktuellen Laufzeit-Kontext ausführen
            • Beim Debugging von mehreren parallel laufenden Anfragen funktioniert manchmal der Resume-Button nicht
            • "Pause on Exceptions" funktoniert grundsätzlich, allerdings gibt es keine Möglichkeit nur bei ungefangenen Expcetions zu pausiren
            • Hot Code-Swapping und Time-Travel-Debugging sind nicht unterstützt

            Aber grundsätzlich kann man damit bequem arbeiten.

  4. Hello,

    was Du beschreibst, ist die Parametervalidierung. Die Formularvalidierung solltest Du auf jeden Fall vorher durchführen.

    Generell möchte ich deshalb ergänzen, dass der Server wissen sollte, welche Eingabefelder er an den Client verschickt hat. Zusätzliche sollte er beim anschließenden Request nicht verarbeiten, sondern deren Auftreten als Angriff interpretieren.

    Bei den verschickten (offer) sollte der Server wissen, welche er beim Request auf jeden Fall zurückerwartet und welche optional sind. Sowohl bei den Pflicht- als auch bei den "Kürfeldern" sollte der Server die gültigen und ungültigen Repräsentationen kennen und kontrollieren. Das ist dann die von Dir vorgenommene Parametervalidierung.

    Spannend ist immer die passende Interpretation von nicht zurückgesendeten Radios oder Checkboxes. Muss man dann den Eintrag in der DB löschen, oder nicht? Besser ist sicherlich, man vermeidet derartige Unklarheiten von vorneherein durch passendes Design.

    Viel Spaß

    Glück Auf
    Tom vom Berg

    --
    Es gibt nichts Gutes, außer man tut es!
    Das Leben selbst ist der Sinn.