Cookies voor statistiek en advertenties
We gebruiken cookies voor statistiek en voor advertenties, allebei naar Google. Weigeren verandert niets aan wat je te zien krijgt.Lees de privacypagina
Plak tekst of een stuk markup en krijg het terug met de tekens ontsnapt die anders als HTML gelezen zouden worden. Standaard zijn dat de vijf die de parse veranderen — `&`, `<`, `>`, `"` en `'` — en er is een tweede stand die alles boven ASCII er ook in zet, voor een keten die geen UTF-8 aankan. Het gebeurt allemaal op deze pagina.
Waar het draait
Er wordt niets geüpload, want er is geen bestand — het wordt in deze pagina uitgerekend.
Geen wachtrij, geen account
Het antwoordt zo snel als je machine kan, en vraagt nooit wie je bent.
Zo vaak als je wilt
Er wordt niets geteld en niets begrensd — nog eens antwoorden kost ons niets.
Het kleiner-dan opent een tag, de ampersand opent een verwijzing, en het aanhalingsteken — enkel of dubbel — sluit een attribuutwaarde af. Het groter-dan is strikt genomen niet altijd nodig en gaat mee omdat het inconsequent zou zijn het weg te laten. Samen zijn dat de tekens die van tekst structuur maken.
Al het andere is gewoon tekst. Een pagina die elk leesteken ontsnapt, is niet veiliger maar wel onleesbaar in de bron, en levert bij een latere bewerking eerder problemen op. De vraag is nooit «hoeveel kan ik ontsnappen» maar «wat verandert hier de betekenis».
Wie zelf ontsnapt met een reeks vervangingen, moet de ampersand als eerste doen. Doe je `<` eerst, dan staat er `<`, en vervangt de volgende stap die ampersand alsnog — met `&lt;` als resultaat, dat op de pagina als de letterlijke tekst `<` verschijnt.
Het is de klassieker onder de zelfgebouwde ontsnappingsfuncties, en de reden dat dubbel ontsnapte tekst zo vaak op websites staat. Hier wordt in één doorgang per teken gewerkt, zodat de volgorde geen rol speelt — en dat is meteen het argument om zulke functies niet zelf te schrijven.
Tekst tussen twee tags heeft aan `&` en `<` genoeg. Tekst binnen een attribuutwaarde heeft daarnaast het aanhalingsteken nodig waarmee die waarde is afgebakend, want daarmee kun je de waarde verlaten en er een tweede attribuut achter zetten.
Nog gevaarlijker is een attribuut zonder aanhalingstekens, wat HTML toestaat. Dan volstaat een spatie om eruit te breken, en dan helpt ontsnappen van aanhalingstekens niets. De regel die overal werkt: zet attribuutwaarden altijd tussen aanhalingstekens, en ontsnap ook het teken waarmee je ze hebt afgebakend.
De tweede stand zet ook letters met een trema, accenten, aanhalingstekens en emoji om in numerieke verwijzingen. Dat is voor HTML niet nodig — een pagina met `<meta charset="utf-8">` toont die tekens gewoon — en het maakt de bron aanmerkelijk langer.
Nuttig is het waar de keten onderweg niet te vertrouwen is: een oud CMS dat de tekenset verhaspelt, een mailsjabloon dat door een systeem gaat dat op Latin-1 blijft hangen, een export die door software van jaren geleden wordt ingelezen. Dan is `ë` een teken dat de reis overleeft en `ë` misschien niet.
Ontsnappen hoort thuis op het moment dat tekst in HTML terechtkomt, en niet bij het opslaan. Sla je ontsnapte tekst op in de database, dan weet je later niet meer of een waarde al bewerkt is, en beland je bij dubbele ontsnapping of bij een gat, afhankelijk van welke kant je vergist.
De regel die stand houdt: bewaar de tekst zoals de gebruiker hem heeft ingevoerd, en ontsnap bij het uitvoeren, telkens passend bij de context waarin hij belandt. Elke gangbare sjabloonmotor doet dat standaard, en het uitzetten daarvan is de bekendste manier om een cross-site scripting-gat te maken.
De ampersand kan `&`, `&` of `&` zijn: een naam, een decimaal getal of een hexadecimaal getal. Alle drie zijn ze geldig en betekenen ze hetzelfde, en een browser leest ze zonder onderscheid.
Voor tekstvergelijkingen is dat lastig: drie schrijfwijzen van dezelfde inhoud vergelijken niet gelijk. Wie HTML-fragmenten met elkaar vergelijkt — in een test, in een diff, in een cachesleutel — moet ze eerst decoderen en dan pas vergelijken, anders meet hij de schrijfwijze en niet de inhoud.
De naam `'` staat in XML en in XHTML, maar niet in HTML 4. Oude browsers die een pagina als HTML 4 lezen, tonen daarom letterlijk `'` waar een apostrof hoorde te staan.
De numerieke vorm `'` heeft dat probleem niet: numerieke verwijzingen werken in elke versie en in elke parser. Het is een klein detail met een lange staart, en het is de reden dat vrijwel elke serieuze ontsnappingsbibliotheek voor de apostrof de numerieke vorm kiest en voor de andere vier de namen.
Belandt de tekst binnen een `<script>`-blok, dan helpt HTML-ontsnappen niet, want daarbinnen worden entiteiten niet gelezen. Hetzelfde geldt voor een `href` die met `javascript:` begint en voor een waarde die in CSS terechtkomt: elke context heeft eigen regels.
De praktische regel is dat er niet één «veilige» vorm bestaat maar één per bestemming. Voor JavaScript is dat JSON-codering, voor een URL procentcodering, voor CSS de eigen ontsnapping. Wie overal HTML-ontsnapping toepast, heeft drie van de vier gevallen niet afgedekt en denkt van wel.
Een waarde die in een `href` of een `src` komt te staan, heeft eerst procentcodering nodig en daarna pas HTML-ontsnapping voor het attribuut. Alleen HTML-ontsnappen laat een `&` in de URL staan als parameterscheiding en breekt het adres precies daar.
Bij bestandsnamen geldt iets vergelijkbaars: HTML-entiteiten in een naam die op een schijf terechtkomt, worden geen tekens meer teruggelezen. Ontsnappen is altijd gekoppeld aan de bestemming, en de veelgemaakte fout is het ergens toe te passen waar het niet gelezen wordt.
Zie je op een pagina `&lt;` staan of leest een bezoeker letterlijk ` `, dan is er twee keer ontsnapt: eerst in de applicatie en daarna nog eens in het sjabloon, of andersom. Voor geen van beide stappen is dat een fout; samen leveren ze onzin op.
Het is de tegenhanger van dubbele procentcodering in URL’s en de diagnose is dezelfde. Zoek de twee plekken en haal er één weg, in plaats van aan het eind een keer te decoderen — want die reparatie werkt tot de dag dat er ergens maar één keer wordt ontsnapt.
Wat hier wordt ontsnapt is vaak tekst die van gebruikers komt: een reactie, een supportbericht, een productomschrijving met een naam erin. Omdat het op de pagina gebeurt, wordt die tekst niet aan ons verstrekt en staat hij in geen enkel verzoeklogboek van ons.
Dat is precies het geval waarin een onlinehulpmiddel anders lastig te verantwoorden is. Een bericht van een klant in het formulier van een derde plakken om het te ontsnappen, is een verstrekking; hier gebeurt dat niet, en het netwerktabblad laat zien dat er niets vertrekt.
Nee. Het ontsnappen gebeurt op deze pagina, in je browser. Dat telt, want wat hier wordt ontsnapt is vaak tekst van gebruikers: reacties, supportberichten, omschrijvingen met namen erin.
Omdat ze de parse niet veranderen. Een pagina met UTF-8 toont ze gewoon. Wil je ze toch als numerieke verwijzing, kies dan de tweede stand — nuttig als de keten onderweg niet met UTF-8 overweg kan.
Voor tekst die in HTML belandt wel, mits het bij het uitvoeren gebeurt en per context. Binnen een script-blok, in een javascript:-URL of in CSS gelden andere regels, en daar helpt HTML-ontsnapping niet.
Omdat de invoer al ontsnapt was en er nu een tweede ronde overheen is gegaan. Zoek de twee plekken in je keten en haal er één weg; aan het eind een keer decoderen is een reparatie die later stukgaat.
Alleen wat je eigen browser in een veld aankan. Er wordt niets geteld of begrensd, want er is geen server die het werk doet.