Thomas: Frage zu sicherer Behandlung von User Input

Hallo,

Ich fühle mich ein wenig erschlagen, von den vielen Möglichkeiten, User Input sicher zu machen.

Da gibt es mysqli_real_escape_string(), zur Entschärfung böser Zeichen für eine SQL Query, da gibt es FILTER_SANITIZE_SPECIAL_CHARS, was sich um '"<>& und alles unter ASCII 32 zu kümmern scheint, htmlspecialchars(), was wiederum FILTER_SANITIZE_FULL_SPECIAL_CHARS entspricht (...richtig?) und sowieso sind Prepared Statements eine gute Idee um DROP TABLE Injection etc. zu vermeiden; - da steht die Frage im Raum, ob FILTER_SANITIZE_EMAIL nicht noch zusätzlich durch FILTER_SANITIZE_FULL_SPECIAL_CHARS zu jagen wäre etc. etc. etc.

Kurzum - ich sehe ein bisschen den Wald vor lauter Bäumen nicht.

…wäre da ECHT dankbar, wenn da jemand einen Leitfaden kennen würde, in erster Linie mal für Procedural (aber auch gerne im Kontrast zu OOP).

Ich meine, da muss es doch Best Practices für sicheres Auffangen und Datenbankspeicherung von User Input (sei es jetzt Username, E-Mail, etc.) geben...?! Spreche hier mal nicht von Passwort Hashing und Salting, fürchte, das würde wohl etwas zu weit führen…

Danke, Thomas

  1. Hallo Thomas,

    es gibt viele Möglichkeiten, richtig. Und nicht jede Möglichkeit ist für jeden Einsatzzweck gemacht. Und nicht jede Möglichkeit ist sinnvoll.

    Unser Wiki hat einen zweiteiligen Aufsatz dazu, das Thema heißt Kontextwechsel.

    Die relevanten Kontextwechsel am Server sind:

    • Programmdaten nach SQL-Input
    • Programmdaten nach HTML-Output

    Form-Input nach Programmdaten? Nö. Das ist kein Kontextwechsel. Das sind zwar potenziell böse Daten, aber solange Du sie nur in Variablen speicherst, sind sie ungefährlich. Es wäre im Gegenteil sogar falsch, Benutzereingaben mit einer HTML-Maskierung zu versehen (z.B. htmlspecialchars) und das Ergebnis in der Datenbank zu speichern.

    Was man mit Benutzereingaben machen sollte, ist

    • prüfen, ob sie dem gewünschten Typ entsprechen. Wenn Du eine Zahl erwartest, lass nur Zahlen zu. Wenn Du eine Telefonnummer erwartest, prüfe im möglichen Rahmen, ob das eine Tel.-Nr sein könnte. Erwartest Du eine Mailadresse, eine Postleitzahl, ein Datum - all das kann man prüfen. Erwartest Du Eingaben ohne Zeilenumbruch, lass keine Zeilenumbrüche zu. Die Validate- und Sanitize-Filter können Dir dabei helfen, sind aber längst nicht immer das Gelbe vom Ei. Ich hatte vor Jahren mal richtig Mühe, weil ich Zahleneingaben auch mit führenden Nullen akzeptieren wollte und FILTER_VALIDATE_INT darauf besteht, dass eine führende 0 eine Oktalzahl anzeigt, und deswegen "012" entweder als Fehler ansieht oder eine 10 draus macht. Ein stumpfer intval() dagegen nimmt auch "012a" entgegen und macht 12 draus. Wollte ich auch nicht.

    Und die Postleitzahl ist auch ein Drama für sich, weil jedes Land eigene Regeln hat und die Postleitzahl gar keine Zahl ist. Sondern ein String, der - in Deutschland - aus 5 Ziffern besteht.

    • eventuelle "leichte Fehleingaben" säubern, z.B. Leerstellen aus PLZ rausnehmen, bei einem Geburtsdatum ein- und zweistellige Tages- und Monatsangaben zulassen, "11. September" gleich wie "11.09." behandeln - da kann man sich beliebig austoben.

    • aber keinesfalls "böse" Zeichen rauswerfen, die möglicherweise sinnvolle Eingaben sein könnten. Zeichen an sich sind nicht böse. Sie können nur bei einem Kontextwechsel böse gemacht werden.

    Den Wechsel nach SQL-Input machst Du mit der von Dir genannten mysqli_real_escape_string oder der real_escape_string-Methode auf einem mysqli-Objekt. Verwendest Du PDO, heißt die Methode quote. Besser ist ein prepare, ja, aber für ein einzelnes Statement bedeutet das Overhead, weil der prepare normalerweise einen Roundtrip zum DB Server bedeutet.

    Der Wechsel von SQL Output ins Programm wird wieder 1:1 gemacht.

    Bei der Ausgabe an den Browser ist dann Arbeit angesagt. JEDE Zeichenkette, deren Herkunft auch nur ein bisschen dubios ist, muss mit htmlspecialchars aufbereitet werden. Texte aus einer Ressourcentabelle von Dir gehören nicht dazu - aber es stört auch nicht, die nochmal zu maskieren.

    Wenn Du natürlich irgendwoher fertige HTML Fragmente bekommst (aus einer DB oder einer Funktion), dann darfst Du sie nicht maskieren. Du musst allerdings dem HTML Lieferanten vertrauen können (also zumeist Dir oder deinen Mitprogrammierern). Wenn du HTML über einen Webservice von irgendwoher beziehst, sieht die Sache dagegen anders aus. Sowas würde ich grundsätzlich nur nach reiflicher Prüfung der Vertrauenswürdigkeit tun.

    Und das war's eigentlich, was PHP angeht.

    Rolf

    --
    sumpsi - posui - obstruxi
  2. Kurzum - ich sehe ein bisschen den Wald vor lauter Bäumen nicht.

    Ganz einfach vergiss den Wald.

    Jeder der Filter hat seinen Einsatzzweck. Die Einsatzwecke (→ Datenbank, → Mailadresse, → Ausgabe ins HTML, → Übergabe an Programme als Parameter( escapeshellarg() hast Du vergessen) , ... ) sind die Bäume.

    Und jeder Bäume hat eine andere Allergie.

    Dedlfix war so nett und hat das schön beschrieben:

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

  3. Hallo, danke!, genau das suchte ich - (glaube ich, muss mich noch durcharbeiten, die Woche bisher wenig Zeit 😜) - einen Leitfaden, der die Hintergründe ein wenig beleuchtet.

    LG Thomas