Transcript

Input Validation, Output Encoding

Keine Kür, sondern Pflicht

Lisa spricht diesmal mit Christoph darüber, wie man Ein- und Ausgaben in Web-Anwendungen absichert. Schließlich ist der laxe Umgang mit diesen Werten, die üblicherweise aus den Eingaben von Nutzer:innen entstehen, regelmäßig eines der größten Sicherheitsrisiken.

Back to episode

Transcript

Lisa:

Hallo und herzlich willkommen zu einer neuen Folge des INNOQ Security Podcast. Heute ist bei mir Christoph zu Gast. Hallo Christoph!

Christoph:

Hallo, Lisa!

Lisa:

Und wir wollen uns darüber unterhalten, wie ihr mit nicht vertrauenswürdigem Input umgehen könnt. Dabei befinden wir uns im Kontext einer Webapplikation und ich vermute, wir machen uns alle manchmal Gedanken, was passiert, wenn der Nutzer was Blödes ein- oder ausgibt. Eher eingibt. Und es gibt aber noch andere Probleme, also nicht nur in Formularen. Stimmt das Christoph?

Christoph:

Ja, also Input kann man ja in seiner Webapplikation an ganz verschiedenen Stellen bekommen. Das Typische ist natürlich, wenn man irgendwelche forms hat, wie du das ja gerade genannt hast, wo der User Daten eingeben kann. Aber moderne Webapplikationen oder Services, die bieten ja oft auch eine API an, die ich auch ansprechen kann. Wo auch ganz viele Daten reinkommen und wenn das eine öffentliche API ist, dann ist die natürlich ganz genauso angreifbar. Vielleicht muss man noch unterscheiden zwischen ganz öffentlichen APIs und einer, die irgendwie eine Art von Authentifizierung benötigt. Also wo ich dann irgendeinen Token oder ähnliches habe, um mich zu authentifizieren. Aber es kann ja schon sein, dass die sozusagen öffentlich ist, also dass ich mich bei einem Dienst registriere und dann einen Token kriege und dann die APIs benutzen kann. Braucht man nur an so Services wie GitHub oder so zu denken, da kann ich mir natürlich auch entsprechende Token holen, um GitHub per API zu bedienen. Und davon gibt es halt ganz viel und das wird halt auch ganz viel entwickelt. Immer mehr APIs oder Webapplikationen sind ja zum größten Teil öffentlich.

Lisa:

Bevor du mir jetzt erzählst, wie ich mich überhaupt vor Dingen schützen kann, würde ich mich freuen, wenn du mir überhaupt mal erst kurz beschreibst, was es für verschiedene Angriffsarten gibt, die wir überhaupt damit verhindern wollen, mit der heutigen Folge. Beziehungsweise mit dem was wir hier erklären.

Christoph:

Ja, also wichtig ist, dass sozusagen die verschiedenen Angriffsvektoren, die sich durch untrusted Input ergeben. Also die… Wenn ich jetzt vier erstmal nenne, das sind wahrscheinlich noch mehr, die dann in ganz bestimmten Kontexten auftreten können. Aber wenn wir uns auf zum Beispiel die OWASP Top 10 beschränken würden, dann würden da mindestens vier von den OWASP Top 10 dabei sein. Das die Nummer eins, das wäre dann Injection-Angriffe. Da am bekanntesten wahrscheinlich SQL-Injection, aber können auch command-Injection sein oder auch LDAP und XML und alles, was irgendwie geparst wird, also wenn der Input, der reinkommt, irgendwo mal geparst wird von irgendeiner Art von Interpreter. Dann haben wir Cross Site Scripting als ganz großen Angriffsvektor, der eigentlich ja auch eine Art von Injection ist, aber in der OWASP halt ein eigener Angriffs… also eine eigene Schwachstelle bildet. Die hätten wir da und dann hätten wir noch die Insecure Deserialization, das heißt Objekte, die wieder serialisiert und deserialisiert werden, die vom Nutzer kommen können, die sind da natürlich auch betroffen. Das ist natürlich hauptsächlich bei APIs der Fall. Oder auch XML external entities, die haben wir immer dann, wenn so eine API zum Beispiel XML annimmt. Ist heute nicht mehr ganz so oft der Fall, wenn man etwas Neueres entwickelt, da nehmen wir meistens irgendwie JSON oder YAML. Wenn man im Kubernetes Umfeld unterwegs ist, gibt es ganz viel YAML. Aber man sollte nicht vergessen, es gibt noch ganz viele Systeme, gerade im Enterprise Umfeld, die irgendwelche SOAP-Schnittstellen anbieten und da wird ja hauptsächlich XML gesprochen.

Lisa:

Also gibt es doch einige Angriffe, die wir jetzt versuchen zu verhindern gleich mit den Dingen. Genau, was ich schonmal gehört habe: Angenommen ein Nutzer gibt seinen Namen ein, dann könnte es sein, dass er ein Bösewicht ist und der gibt jetzt irgendwie einen script tag da rein und dann würde er einen Alert machen. Und kann ich jetzt nicht einfach sagen: „Ach, ich nehme dieses script tag raus und speichere einfach den Rest, was da noch übrig bleibt, wenn ich die bösen Dinge da rausgenommen habe“?

Christoph:

Ja, also du würdest ja damit den Input sozusagen bereinigen. Also „Sanitization“ ist dann der englische Fachbegriff, den man da üblicherweise benutzt. Das kann man machen, das liest man auch relativ häufig, dass man das machen sollte. Es gibt auch in einigen Programmiersprachen, so PHP zum Beispiel oder ob es das immer noch in PHP gibt, weiß ich nicht, aber das gab es früher mal, als ich das mal gemacht habe vor zehn Jahren, so sanitize Funktionen. Und ich weiß auch, bei Oracle gibt es auch so sanitize Funktionen, die haben aber so ein paar Probleme. Also Sanitization ist wahrscheinlich was, was man eher nicht machen würde, weil… Man kann das erstmal an zwei verschiedenen Stellen machen. Das kann man machen, wenn der Input reinkommt, dann kann ich das bereinigen oder wenn ich den Input wieder rausgebe. Weil die meisten der Angriffe, die wir vorhin besprochen haben, sind ja dann wirksam, wenn ich den Output wieder rausgebe. Wenn ich zum Beispiel das an meine SQL-Datenbank gebe und der SQL-Interpreter das macht. Das ist ja ein Output von meiner Applikation sozusagen an die SQL-Datenbank. Oder wenn ich die via Cross Site Scripting eine Webseite wieder ausgebe an den User, also an den Browser. Also kann ich das an zwei verschiedenen Stellen machen. Und beim Input ist halt das Problem, dass ich da noch gar nicht unbedingt weiß, in welchem Output Kontext ich das brauche, diese Daten. Also ich kann Daten, die der Nutzer eingibt, der Name, die könnten jetzt auf der SQL-Datenbank… Also wenn ich das speichere, in den SQL-Interpreter reinkommen. Dann müsste ich das da daraus gelöscht haben. Aber vielleicht kommen die ja auch auf der Webseite raus und da ist ein ganz anderer Kontext. Und da will ich vielleicht Sachen drin haben, die ich bei SQL rausgelöscht hätte. Ein einfaches Beispiel, SQL-Injection, könnte man ja sagen: Wir machen da irgendwie ein Hochkomma, so ein einfaches Anführungszeichen und beenden damit sozusagen das SQL-Kommando oder einen Teil und schreiben unser eigenes SQL dahin. Aber jetzt würde ich zum Beispiel einen Nachnamen haben, irgendwie O’Brien mit auch so einem Anführungszeichen sozusagen drin als Eingabe. Dann lösche ich das raus und bei der Ausgabe, wenn der Nutzer zum Beispiel mal seine Profilseite aufruft, würde er vielleicht gerne seinen richtigen Namen sehen. Oder auch sonst, wenn es irgendwie auch verschriftlicht wird. Der kriegt ein PDF dabei, was er sich ausdrucken kann und der Name steht da falsch drauf. Ein offizielles Formular oder was weiß ich, dann ist das natürlich problematisch. Also mit dem Output Kontext beschäftigen wir uns vielleicht auch gleich nochmal, also da geht das nicht. Und das andere Problem dabei ist, das ist wirklich nicht reversibel. Also ich kann das nicht rückgängig machen. Das heißt, ich könnte ja versuchen, nach den jeweiligen Output Kontexten das unterschiedlich zu handhaben, aber einmal bereinigt ist bereinigt. So. Das geht da nicht. Und das dritte größere Problem dabei ist die Fehleranfälligkeit. Da kommen wir vielleicht gleich nochmal drauf, gehen wir erstmal den Output. Also wir haben jetzt gesehen, Input ist nicht die beste Lösung, da gibt es verschiedene Probleme. Also können wir das ja auch anwenden, wenn wir einen Output machen. Also wir speichern erstmal alles so weit, beziehungsweise wir nehmen erstmal alles an und je nach Output machen wir dann ein sanitize. Also bei der Datenbank wird zum Beispiel dann das einfache Anführungszeichen dann einfach gesanitized werden und beim HTML nicht. Da haben wir jetzt nur noch ein Problem, dass natürlich, wenn ich das auf dem einen sanitize und auf dem anderen nicht, die Daten dann in den unterschiedlichen Output Kontexten auch unterschiedlich sind. Also in der Datenbank wird dann O’Brien vielleicht ohne das Hochkomma gespeichert und wenn ich die dann halt doch nochmal wieder woanders benutze, dann ist das halt trotzdem weg. Also es ist immer noch nicht reversibel. Und das andere Problem, und das ist bei Input und Output so, das ist extrem fehleranfällig Sanitization. Da gibt es ganz viel Möglichkeiten, das zu umgehen, da gibt es eine ganze OWASP Seite zu. Können wir dann in den Shownotes verlinken so für Cross Site Scripting, wie man solche sanitize Versuche umgehen kann. Den einfachsten, den wir jetzt mal versuchen vielleicht auch hier auf der Audiospur zu erklären, ist: Nehmen wir mal an, du hast, wie du das beschrieben hast, einen script tag, was der User eingibt. Das wollen wir natürlich ungern haben, weil wenn man das zum Beispiel in der HTML-Seite wieder ausgeben, würden dann halt JavaScript ausgeführt und wir haben halt ein klassisches Cross Site Scripting. Und jetzt könnt man einfach sagen: Okay, der Eingabe String wird nach script tags durchsucht und das wird ersetzt durch den leeren String. Dann sind die alle raus, allererste Möglichkeit. Jetzt wäre der einfachste Bypass, dass wir einen script tag nehmen als Input und dann innerhalb diese script tags nochmal ein script tag. Also wir hätten dann irgendwie <sc<script> und dann das ript weiter, wieder Klammer zu. Und dann würde, wenn wir das bereinigen, wird die Bereinigungsfunktion das erste script in dem String finden, wird das rausnehmen und durch das was wir rausgenommen haben wird sich der Rest wieder zu einem gültigen script tag zusammensetzen. Und wenn wir das dann ausgeben, dann hätten wir wieder eine Cross Site Scripting Lücke ausgenutzt. In dem Fall könnte man das natürlich jetzt versuchen rekursiv anzuwenden und dann sozusagen bis alles raus ist. Es gibt aber halt, wie gesagt, eine ganze Reihe Möglichkeiten, das zu umgehen, diese sanitize Funktion, und deshalb sollte man das eher lassen. Weil Komplexität und schwierige Implementierung sind ja eher die Feinde von der Security. Weil, wie gesagt, wenn es einen Fehler gibt und ich das umgehen kann, dann nützt mir das ganze Sanitizing gar nichts. Deshalb würde ich nicht empfehlen, irgendwie auf Sanitization zu setzen, sondern da gibt es halt bessere Wege.

Lisa:

Also ich merke mir schonmal, ich möchte auf jeden Fall nichts wegwerfen von meinem Ding, es gibt bessere Wege als Sanitization und du sagst mir jetzt auch gerne, welche Wege ich zum Beispiel für den Input habe? Dass das irgendwie besser gelöst ist als mit Sanitization.

Christoph:

Genau. Wir würden anstatt einer Sanitization auf der Input Seite immer eine Validation machen. Das ist die bessere Vorgehensweise. Und da müssen wir halt schauen, da gibt es zwei Möglichkeiten den Input zu validieren und die sollte man beide nutzen. Das ist einmal die Struktur und das andere ist die Semantik. Und die Semantik ist halt auch nochmal kontextabhängig. Wo ist der Unterschied? Also die Struktur sagt erstmal, sagen wir mal man gibt jetzt irgendwie was ein, der Nutzer, und der muss ein Datum eingeben. Und wenn der da ABC eingibt, dann weiß ich: die Struktur kann ich jetzt nicht irgendwie parsen und da kommt kein valides Datum raus. Oder bei einer Kreditkartennummer, da weiß ich zum Beispiel wie viele Stellen die haben kann und dass da nur Ziffern vorkommen. Da kann ich also die Struktur von Daten überprüfen. Das sind jetzt ganz einfache Datentypen, das geht aber auch komplizierter. Wenn ich jetzt zum Beispiel eine API bin und JSON annehme oder XML und ich habe ein Schema dazu, dann könnte ich zum Beispiel das Schema erstmal validieren. Die sind von der Struktur her und von der Syntax her vor allem dann auch richtig. Also bei XML und JSON kann ich erstmal eine einfache Syntaxprüfung machen. Und die andere Prüfung ist halt die Semantikprüfung. Also bleiben wir nochmal beim Datum und nehmen wir mal an, man müsste irgendwie eine Frist angeben in einem Formular, und diese Frist kann von der Businesslogik eigentlich nur in der Zukunft liegen, dann kann ich überprüfen: Liegt die jetzt in der Zukunft oder in der Vergangenheit? Dann ist das, was ich gerade gesagt habe, halt kontextabhängig. In welchem Kontext bewege ich mich? Dann kann die Semantik sehr unterschiedlich sein. Und dann könnte ich, wie im Beispiel gerade mit den Fristen, einfach sagen: Okay, ich lehne eine Frist ab, die in der Vergangenheit liegt. Und da mache ich erstmal eine semantische Überprüfung mit. Und wie mache ich das so am besten mit solchen Sachen? Das würde ich wahrscheinlich… Also die Struktur mit irgendwelchen Allow oder Block Lists machen, besser Allow List. Also ich lasse erstmal nur rein, wo ich sicher bin: Das ist ungefährlich. Also ich könnte zum Beispiel sagen: In ein Feld dürfen nur die Buchstaben A-Z in Groß- und Kleinschreibung und Ziffern von 0–9 und vielleicht ein paar Sonderzeichen. Zum Beispiel bei einem Passwortfeld. Da kann ich zum Beispiel sagen, ich erlaube nur bestimmte Zeichen da drin und die kommen auf eine Allow List. Ich könnte das auch umgekehrt machen, dass ich sage, eine Block List, die beschreibt, wo ich sicher bin, dass ich das nicht haben will. Aber Block Lists haben, nicht nur hier in dem Kontext von Input-Validation, fast immer das Problem, dass ich von vornherein meistens gar nicht weiß, was ich ablehnen möchte. Also es ist schwierig zu bestimmen, sozusagen die Gesamtmenge von allem was ich ablehnen würde. Deshalb ist es besser mit Allow Lists zu arbeiten und nur zu sagen: Da bin ich sicher, das kann ich auch entsprechend übernehmen. Und das funktioniert alles ganz gut für so einfache Datentypen. Also wenn ich jetzt irgendwelche numerischen Werte nehme und das in Int parsen kann, dann habe ich ja die Struktur relativ einfach und die Semantik habe ich vielleicht auch einfach, weil mein Int vielleicht zwischen 0 bis 1000 sein kann in diesem Kontext, in dem ich mich bewege. Und bei einem Datum geht das vielleicht noch und wie gesagt, für eine Kreditkarte geht das wahrscheinlich. Und so ist das ganz gut. Es gibt aber da noch ein Problem, dass manche Sachen halt nicht vernünftig validiert werden können. Schon von der Semantik her nicht und auch nicht von den, dass ich auf der Allow Liste nur Sachen rein… auf die Allow Liste nur Sachen schreibe, die ich auch wirklich haben will. Und das fängt schon bei den Namen an. Das haben wir vorhin gesehen mit diesen O’Brien Beispiel, dass da vielleicht ein Anführungszeichen drin ist. Aber Namen können sehr, sehr verschieden sein auf der Welt. Also wir gehen vielleicht von unserem westlich-zentrierten Bild aus und sind irgendwie, unsere Namen sind dann irgendwie in ASCII abbildbar, aber es gibt noch ganz verschiedene Namenskonzepte. Und wenn man halt exzentrisch genug, auch in der westlichen Welt, ist, wie der Elon Musk, der seine Tochter dann irgendwie X Æ A-12 nennt – und das auch noch relativ kompliziert geschrieben. Und seine Frau das auch noch anders ausspricht, weil sie das Æ dann als AI, wie artificial intelligence ausspricht, dann kann man schon Probleme bekommen. Oder was wir noch oft haben sind E-Mail-Adressen. E-Mail-Adressen sind unglaublich schwer und kompliziert zu validieren, weil die RFC dazu ganz schön viel zulässt. Zum Beispiel wäre eine zulässige E-Mail-Adresse, wo ein komplettes script tag drin ist. Also wo ich ein JavaScript drin hätte, das wäre eine valide E-Mail-Adresse. Vielleicht verlinken wir mal so ein Beispiel oder schreiben das mal in die Shownotes, das möchte ich jetzt hier nicht so vorlesen. Die wäre sehr valide. Und wenn ich E-Mail-Adressen zum Beispiel als Identifier zulasse, also als Anmeldename für meinen Dienst, den ich anbiete, dann kriege ich da natürlich ein Problem. Wir haben natürlich auch oft, dass wir auch einfach Freitext haben. Also, altbekannt: Forensoftware will halt irgendwie Freitext annehmen und dann muss ich mir halt überlegen: Was lasse ich da zu? Ich könnte jetzt erstmal sagen: Okay, HTML Text und sowas, das lasse ich nicht zu, sondern ich habe vielleicht einen minimale Markup Sprache da drin, die ich als save ansehe. Aber dann bin ich vielleicht in einem Forum für Programmiererinnen und die möchten auch Quelltext posten, auch JavaScript und HTML Text und da ist dann so ein Script trotzdem erlaubt. Und dann kann ich da halt einfach nicht sagen: Ich lehne das ab. Sondern das ist dann sowohl von der Struktur als auch der Semantik zulässiges HTML. Also zulässiges HTML wäre dann zulässiger Input davon. Und deshalb kommen wir nur mit Input Validation halt nicht so ran. Wir müssen dann halt noch mehr tun.

Lisa:

Wo du das gerade mit den Namen sagtest, fiel mir noch diese Geschichte ein mit der Dame, die sich nicht bei, ich glaube Apple war es, anmelden konnte, weil ihr Nachname „True“ war. Das fiel mir gerade ein.

Christoph:

Genau, das ist auch sowas. Also wer True, False oder Null heißt, der hat Probleme. Und es gibt auch immer die Geschichte, ich weiß nicht, ob die wahr ist oder eine urban legend, wo in den Vereinigten Staaten, wo man ja seine Autokennzeichen relativ frei wählen kann, da auch so Null draufhat oder so einen SQL Injection String und sowas, das ist halt auch schwierig. Also wenn der Input halt beliebig sein kann, also in Deutschland könnte man Kennzeichen vielleicht an den Mautbrücken noch validieren, weil wir wissen, wie deutsche Kennzeichen aussehen, aber das reicht ja nicht. Auch die anderen… also aus anderen Staaten müssen ja auch die Maut bezahlen. Also muss ich schonmal erstmal wahrscheinlich alle, mindestens alle europäischen Kennzeichen erstmal irgendwie parsen können. Das wird sehr schnell sehr komplex und ich muss relativ viel Input zulassen, wo ich eigentlich sagen würde: Will ich eigentlich gar nicht haben, muss ich aber.

Lisa:

Was ich, glaube ich, bei Input Validation gerade noch dazu sagen möchte, weil mir das schon oft über den Weg gelaufen ist, dass Leute da vergessen haben. Bei APIs ist das total klar, dass ich den Input im Backend validiere, aber bei HTML Formularen könnte man auf die Idee kommen, dass das ausreicht, das im Formular zu validieren. Aber ich glaube, das ist relativ wichtig zu sagen, dass das sehr, sehr sinnvoll ist, wenn man was im Frontend validiert, also HTML, dann sollte man es auf jeden Fall im Backend nochmal validieren, weil man ja die Validierung im Frontend sehr einfach umgehen kann. Ich wollte das nur einmal gesagt haben.

Christoph:

Genau, also das würde ja voraussetzen, dass JavaScript sozusagen immer an ist, aber wenn jemand natürlich Böses vor hat, wird er das nicht machen und vielleicht auch gar keinen Browser benutzen, sondern mit cURL oder mit ähnlichen einfach Input schicken. Und da ist es herzlich egal, was JavaScript sagen würde. Und das ist ganz richtig, was du gesagt hast, auf jeden Fall Input Validation immer auf der Serverseite und manchmal kommen dann natürlich auch so subtile Probleme, dass JavaScript anders ist als das Backend in der Validierung zum Beispiel. Also dass man sagen kann, bei dem einen wird es validieren und bei dem anderen nicht. Subtile Unterschiede, wie zum Beispiel, wenn man numerische Datentypen parst, JavaScript hat zum Beispiel ja nur einen numerischen Datentyp, während ich auf der Serverseite eine Programmiersprache vielleicht habe, die mehr als einen Datentyp hat.

Lisa:

Jetzt haben wir ziemlich viel über den Input gesprochen, aber noch gar nicht über den Output. Was muss ich denn mit dem Output machen, nachdem ich den Input gut validiert habe?

Christoph:

Den Output müsste man natürlich an der Stelle… also den muss man, weil die Input Validierung an der Stelle nicht reicht, entsprechend anpassen. Normalerweise spricht man da von Output Encoding, manchmal aber auch von Escaping. Wo der Unterschied ist, da können wir vielleicht später mal drauf eingehen. Ich würde es immer als Encoding bezeichnen. Und da müssen wir halt schauen erstmal in welchem Kontext sind wir? Also geben wir… ist der Output jetzt HTML? Ist der vielleicht JavaScript? Ist das ein PDF? Ist das eine URL, die wir geben? Ist das ein SQL Kommando, was wir an die Datenbank geben? Und so weiter und so fort. Und je nachdem müssen wir ein unterschiedliches Encoding machen. Also da müssen wir dann halt sehen, dass wir zum Beispiel im HTML bestimmte Sachen anders ausgeben. Zum Beispiel, wenn so eine spitze Klammer kommt, so eine öffnende, die so ein HTML tag öffnen will, da wollen wir ja eigentlich nicht, dass der Browser oder der das HTML interpretiert, das als tag interpretiert, sondern das wollen wir vielleicht in der Ausgabe haben, weil wir in einen Programmierinnen Forum einen Quelltext angeben wollen. Der soll dann sozusagen literal erscheinen und nicht ausgeführt werden. Und da müssen wir diese spitzen Klammern zum Beispiel mit HTML Entities, das ist immer dieses „kaufmännische Und“ dann das Zeichen, wäre zum Beispiel für die spitzen Klammern jetzt gt für „greater than“ und lt für „lower than“ und Semikolon eingeben. Und dann würde der Browser das halt nicht als Anfang eines Texts interpretieren, sondern literal als die spitzen Klammern. Und wir müssen sehr aufpassen, gerade bei HTML, also da ist ja eine große Angriffsoberfläche für Cross Site Scripting und HTML selber hat noch verschiedene Kontexte. Also das ist nicht einfach HTML, wenn wir so eine Seite ausgeben, sondern im HTML haben wir zum Beispiel die normalen tags, dann haben wir das script tag. In dem steht dann aber JavaScript, JavaScript müssen wir anders encodieren. Und in solchen Attributen, die dann in den tags drin stehen, müssen wir dann auch nochmal anders kodieren. Und zum Beispiel auch in einem style tag, wo dann CSS drin stehen kann, müssen wir auch anders kodieren. Also HTML selber hat noch verschiedene Kontexte, da müssen wir aufpassen beim Codieren. Und wie gesagt, da müssen wir sehen: In welchem Kontext geben wir das aus? Können ganz viele sein. Wie gesagt, HTML, JavaScript, das könnte aber auch JSON oder XML sein. Da müssen wir das entsprechend codieren, also ein richtiges Encoding machen an der Stelle. Dann sollte eigentlich die Seite, die das dann konsumiert, kein Problem mehr haben, sondern das dann nicht… also keine Angriffsoberfläche mehr bieten.

Lisa:

Und der Vorteil an der Stelle ist auch, dass wir keine Daten verloren haben, weil wir bei der Eingabe ja nichts weggeworfen haben und jetzt einfach bei der Ausgabe nur den Consumer quasi die leichtere Kost, also die, die er auch verdauen kann ohne Schwierigkeiten, bereitstellen.

Christoph:

Genau, also so können wir, wenn wir das Beispiel nochmal von O’Brien nehmen, das wir an eine SQL Datenbank schicken, was beim Sanitizing dann verloren gegangen wäre zum SQL Interpreter, aber nicht, wenn wir es direkt wieder im HTML ausgeben. Da würden wir es halt so speichern, weil encoded ist entsprechend wie die Datenbank das gerne haben möchte, dass das auch wieder in der Datenbank steht und für später dann in anderen Kontexten wieder benutzt werden kann und richtig encodiert werden wird. Muss man nur ein bisschen aufpassen, dass man halt, was man vielleicht schon öfter gesehen hat, das Problem des double encoding halt öfter mal hat. Wenn man schon was encodiert gespeichert hat und zum Beispiel speichere ich schon encodiert für die HTML Ausgabe und im HTML vielleicht nochmal encodiere und so weiter, dann kennt man das, dann hat man manchmal so ein double encoding drin. Dann hat man nämlich die Stelle eigentlich verpasst, wo es richtig wäre zu encodieren und das vielleicht schon zu früh gemacht. Weil eigentlich sollte ich es ja nicht in der Datenbank schon so abspeichern, weil wenn ich es aus der Datenbank wieder raushole, kenne ich den Ausgabekontext nicht. Ich könnte aus der Datenbank ja auch dann irgendwie ein PDF generieren und da herrschen ganz andere Encoding Regeln, als wenn ich es per HTML an den Browser sende. Oder wenn es eine API ist und… also es ist zum Beispiel als HTML verfügbar und in einer API und ich gebe es dann über JSON aus, also je nach content type, der angefragt wird vielleicht, da muss ich auch wieder ein anderes Encoding nehmen. Also da muss man ein bisschen aufpassen. Das ist also… ist vielleicht nicht so fehleranfällig und in der Auswirkung nicht so schlimm wie beim fehlgeschlagenen Sanitize, weil double encoding hat weniger katastrophale Auswirkungen wie wenn auf einmal doch ein script tag in meinem HTML drinsteht, ist dann halt… kann nur unschön sein. Und der Browser zeigts dann halt vielleicht falsch an oder ein PDF wird falsch gerendert oder so. Von daher nicht ganz so schlimm, aber muss man trotzdem aufpassen.

Lisa:

Ich merke mir jetzt also als Regel, ich vergesse komplett das sanitizen und merke mir: Den Input will ich validieren und den Output möchte ich encoden. Und dann bin ich eigentlich ganz zufrieden mit meiner Webanwendung, die ich gebaut habe.

Christoph:

Genau! Also das ist eine der wichtigsten Regeln bei Security von Webanwendungen: Immer Input Validation anwenden soweit es geht und was man nicht validieren kann: Output Encoding. Und das kann man sich vielleicht so ein bisschen wie eine Waage vorstellen. Also desto besser ich valideren kann, desto weniger Kraft oder desto weniger kompliziert wird das Output Encoding. Also wenn ich Sachen halt sozusagen vollständig validieren kann, dann ist es halt im Encoding einfacher. Desto mehr ich zulasse, also wie gesagt so Freitexte in irgendeiner Form oder E-Mail-Adressen wirklich so, wie die RFC das vorschlägt, dann kann ich sie halt weniger gut validieren und muss wieder beim Encoding aufpassen. Wobei bei den E-Mail-Adressen das auch so ist, dass viele Server gar nicht… also E-Mail-Server und E-Mail-Programme solche Adressen, die zwar erlaubt werden, gar nicht annehmen und einfach das trotzdem ablehnen, weil… Vielleicht war diese RFC auch keine gute Idee, da verlinken wir vielleicht auch nochmal was in den Shownotes, was so alles erlaubt ist dabei. Von daher, das ist auf jeden Fall eine der wichtigsten Grundlagen, um sichere Webapplikationen zu schreiben. Input Validierung, Output Encoding darf man nicht vergessen. Das gut ist dabei, manche Dinge werden einem halt abgenommen. Zum Beispiel wenn ich HTML Ausgaben habe, viele Template-Sprachen bieten halt, oder Template-Bibliotheken, bieten automatisches Encoding an. Also die dann, wenn ich da eine Variable einsetze, die dann ersetzt wird, dann kümmert sich die Bibliothek darum, dass das entsprechende Encoding da ist und zum Beispiel HTML Entities benutzt werden oder nicht. Beziehungsweise dass die benutzt werden. Das ist schon ganz gut. Im HTML ist das wahrscheinlich bis jetzt am besten gelöst, also bei Template-Libraries. Bei anderen Kontexten muss man schon ein bisschen mehr aufpassen, also da gibt es nicht so viel an Libraries, die das unbedingt übernehmen. Es gibt so ein paar generische Template-Libraries, die zum Beispiel auf, was weiß ich, PDF oder… also andere Ausgabe oder Markup Sprachen rausgeben und entsprechend das Encoding anpassen könne, aber da muss man ein bisschen schauen. Aber im Webumfeld sind wir halt auch viel mit HTML unterwegs.

Lisa:

Da hast du Recht! Du hattest eben beim Output Encoding noch gesagt: „Escaping, aber da kommt ich gleich nochmal drauf zurück“. Was ist denn Escaping? Und wo liegt der Unterschied zum Output Encoding?

Christoph:

Ja, das… Also wenn man die Literatur liest, dann kann man sagen, das könnte man gleichwertig verwenden. Das ist halt nicht so eindeutig definiert, ob man das jetzt als Output Encoding oder Escaping kennzeichnet. Ich finde, da ist die Go Template-Library ein schönes Beispiel – die sagt dann sozusagen ihr Sicherheits-Feature und die sagt: Okay, das muss alles encoded werden. Und im nächsten sagt sie so: Das wird dann auch richtig… das Escaping wird dann richtig gemacht. Also da werden direkt beide Begriffe für das selbe genutzt. Wo kommt der Unterschied jetzt her? Das liegt daran, dass die eigentlich aus einem anderen Zusammenhang gerissen sind das Wort Encoding und Escaping. Also Encoding, fangen wir mal damit an, das ist einfach erstmal eine Transformation und das ist sozusagen die… Man kann es am besten als String und Unicode Encoding darstellen. Also ich habe irgendwie einen String ABC und den möchte ich jetzt speichern. Und dann ist es ein bestimmtes Encoding, wie ich den speichere. Ich könnte jetzt zum Beispiel UTF-8 nehmen oder UTF-16. Dann weiß ich jedes Zeichen, also A, B und C, werden in ein bestimmtes Bitmuster übertragen und dann kann ich aus dem Bitmuster sozusagen wieder einen String herstellen. Das ist das Encoding. Also das ist eine Transformationsfunktion bei und wie gesagt… Am besten, wie gesagt mit Unicode oder mit beliebigen Zeichen-Encodings kann ich das machen. Also ich könnte… bestimmte Buchstaben sind dann in UTF-8 natürlich ganz anders codiert als in UTF-16. UTF-16 nimmt zum Beispiel zwei Bytes immer, also das Bitmuster ist viel länger als UTF-8, wo ich eine variable Länge habe, da können die ein Byte sein, aber maximal vier Bytes. Aber da gibt es ganz klare Regeln, da kommt eigentlich der Begriff „Encoding“ her. Und der Begriff „Escaping“ kommt aus einem anderen Kontext. Da kommt das nämlich sozusagen aus der Sicht eines Parsers oder Interpreters. Und zwar in einer Markup Language wie HTML oder auch in Programmiersprachen, da kann ich bestimmte Zeichen in einen bestimmten Kontext nicht darstellen. Also im HTML ist es die spitze Klammer, die so… wo der Parser sagen würde: Jetzt beginnt ein neues tag. Jetzt behandele ich den Input halt anders. Da schalte ich das irgendwie um und… Also sonst gebe ich zum Beispiel alle Zeichen literal aus, da steht jetzt irgendwie ABC und dann beginnt ein tag. Und das tag gebe ich halt nicht aus, sondern damit macht der Parser was. Kann er irgendwelche Darstellungsinformationen rausholen oder semantische Information, das ist jetzt irgendwie ein Absatz und so weiter und so fort. Oder in Programmiersprachen kann ich zum Beispiel in einem String literal, wo ich das in Anführungszeichen habe, in dem String literal nicht noch ein Anführungszeichen verwenden, weil dann würde das String Literal ja beendet sein. Sowas muss ich aber manchmal machen. Und um das zu lösen gibt es sogenannte „Escape-Sequenzen“, die dann sagen, dem Parser sagen: Wenn du auf so eine Escape-Sequenz stößt, dann änderst du dein Verhalten. Bei dem Programmiersprachenbeispiel von gerade, wo ich die Anführungszeichen habe, würde ich so in viele Programmiersprachen einfach einen Backslash vorschreiben. Dann weiß er der Backslash kommt und dann weiß er: Okay, dieses nächste Zeichen muss ich zum Beispiel ganz literal behandeln. Und ändere meine Parsing-Semantik jetzt nicht und beende dieses String literal nicht. Bei HTML, das hatten wir vorhin, sind das halt diese Entities. Da ist die Escape-Sequenz dieses Kaufmanns-Und und am Ende das Semikolon. Da weiß der: Das behandele ich jetzt nicht mit der Semantik, die ich normalerweise bin, sondern da kommt irgendwas anderes raus. Da muss ich halt irgendein Zeichen Lookup machen. Und das ist Escaping, das ist halt aus der Sicht eines Parsers sozusagen. Und jetzt hat man das in den Security Kontext gebracht, diese beiden Wörter und irgendwie sind die auch richtig. Also wen ich Output rausgebe, dann setze ich… also beim HTML würde ich eine Escape-Sequenz für HTML Entities einsetzen. Dann würde ich die ja auch ersetzen. Gleichzeitig ist das aber auch eine feste Regel für eine Transformation. Also spitze Klammern werden halt in diese HTML Entity übersetzt. Deshalb gibt es da verschiedene Meinungen dazu, wie man das bezeichnet. Manche bezeichnen Escaping sozusagen als Untermenge von Encoding, weil nur einzelne Teile speziell encodiert werden, zum Beispiel die HTML Entities. Ich würde immer den Begriff Output Encoding verwenden, der ist relativ gebräuchlich und der umfasst halt entsprechend sozusagen beides. Wenn man das als Untermenge sind, dann habe ich halt beides drinnen. Und das andere ist, es geht ja um den Output und wie ich das Codiere, weil im File bin ich ja nicht der Parser. Meine Anwendung ist nicht der Consumer und parst das nicht, also würde ich die Parser-Sicht, wo man es Escaping nennen würde, dann eher nicht verwenden, sondern immer sagen „Output Encoding“. Aber wie gesagt, das ist halt nicht festgelegt einheitlich, kann man sich schön drüber streiten. Wenn man das dann liest, dann kann man wahrscheinlich Output Encoding und Escaping auch gleichstellen. Ich empfehle immer das einfach „Encoding“ zu nennen, aber jeder ist da frei, das so zu machen wie ihm das beliebt oder ihr das beliebt.

Lisa:

Wenn wir jetzt schon bei so wording Thematiken sind, ich habe auch schon mal von Input/Output Filtering gelesen. Kannst du dazu noch was sagen?

Christoph:

Ja, das wird auch manchmal benutzt. Ist in der Security nicht so ganz gebräuchlich und da ist das Problem, das ist noch viel schwammiger definiert. Weil unter Input Filtering wird sowohl Validation als auch Sanitization subsumiert und beim Output Filtering wird Sanitization und Encoding, beziehungsweise Escaping subsumiert. Also das ist so total schwammig, von daher würde ich diesen Begriff auf jeden Fall meiden. Also bei Encoding/Escaping, da kann man halt sozusagen noch seine eigene Meinung zu haben und sagen: Das finde ich besser, das finde ich besser. Aber dieses Filtering, das ist einfach zu undefiniert und das sollte man als Begriff auf jeden Fall meiden. Von daher Finger weg davon! Also das sollte man tun, was sich da drunter verbirgt, aber man sollte genau definieren was man macht und das haben wir gesagt: Die Regel ist Validation und Encoding. Und dann sollte man das auch so benennen.

Lisa:

Okay. Christoph ich glaube das wichtigste ist gesagt oder möchtest du noch irgendetwas ergänzen zu den gerade gesagten Dingen?

Christoph:

Ich glaube, wir haben so das meiste eingefangen, was man da wissen muss. Und wichtig für alle Zuhörerinnen, merkt euch die Regel: Input Validation und Output Encoding ist kein Luxus, sondern das muss man einfach machen, wenn man eine Webapplikation baut.

Lisa:

Vielen Dank Christoph, dass du heute hier zu Gast warst!

Christoph:

Gerne, Lisa!

Lisa:

Und dann danke ich euch Zuhörerinnen und Zuhörern für das Zuhören! Falls es euch gefallen hat, würde es uns natürlich freuen, wenn ihr das irgendwie zeigt, indem ihr uns liket oder was man auch immer auf eurer Podcast Plattform eurer Wahl bei einem Podcast tun kann. Falls ihr Fragen habt, Ideen, Feedback, uns einfach mal kontaktieren möchtet, könnt ihr das gerne tun. Wir haben eine Mailadresse für diese Zwecke, und zwar security-podcast@innoq.com. Das schreibt der Christoph auch auf jeden Fall in die Shownotes rein, damit ihr das einfach klicken könnt. Und ich würde sagen: Ja, vielen Dank, dass ihr dabei wart! Bis zum nächsten Mal!