0%

Invoer die code wordt

Data die als instructies wordt gelezen

Hier is een titel: <b>Dune</b>. Volgens de regels die je in les 2 schreef, is het een geldige titel. Het is tekst, hij is 11 tekens lang, en niets in het contract verbiedt een punthaak. Voer dit blok uit en kijk wat er op twee plekken mee gebeurt:

Try it

De tweede regel uitvoer is het boek als : {"title": "<b>Dune</b>", "author": "Frank Herbert"}. De titel staat tussen twee aanhalingstekens, en wie die JSON parset, krijgt dezelfde 11 tekens terug. Het is data.

De derde regel is <li><b>Dune</b></li>. Niets geeft meer aan waar de titel begint of ophoudt. Een browser die deze regel leest, vindt een tag <b> in een lijstitem. De 11 tekens zijn niet veranderd. Wat veranderde, is wie ze leest, en die lezer neemt een deel ervan als instructies.

Het patroon

Elke zwakheid in de rest van dit hoofdstuk heeft dezelfde drie stappen.

  1. Tekst van buiten wordt samengevoegd tot een grotere tekst.

  2. Een parser leest het resultaat: een browser, een -client, een , een database.

  3. De parser kan niet zien welk deel de data was. Hij ziet één tekst, en hij leest die helemaal volgens zijn eigen regels.

Dit heet injectie (injection): data die in een tekst werd samengevoegd, wordt gelezen als deel van de instructies van die tekst.

Hoofdstuk 1 legde de vertrouwensgrens waar een verzoek binnenkomt. Injectie gebeurt waar een grens een tweede keer wordt overgestoken, op de terugweg: waar je server tekst doorgeeft aan iets anders dat hem gaat parsen.

Vier plekken

Hier zijn vier fragmenten. Elk heeft één regel waar data overgaat in tekst die geparset gaat worden.

Een databasequery. De database parset het resultaat als SQL:

# title is joined into SQL
query = f"SELECT author FROM books WHERE title = '{title}'"

Een header in een antwoord. De client parset de kop van het antwoord regel voor regel:

# target is joined into a header
line = "Location: " + target + "\r\n"

Een bestandspad. Het besturingssysteem leest / en .. erin als aanwijzingen:

# name is joined into a path
cover = pathlib.Path(folder) / name

Een pagina. De browser parset hem als HTML:

# title is joined into markup
row = "<li>" + title + "</li>"

Je leeslijst heeft de laatste drie sinds het begin van dit hoofdstuk. Om de tweede in actie te zien, bouwt het volgende blok de kop van een als gewone tekst, zoals hoofdstuk 2 van HTTP with Python verzoeken met de hand schreef. De titel erin bevat een regeleinde:

Try it

Een client splitst de kop bij elke \r\n, zoals de doet, en de haken laten zien waar elke regel begint en ophoudt. De server schreef twee regels, een statusregel en een regel Location. De client leest er drie, en de derde, [Set-Cookie: note=x], kwam uit de titel. De lege paren haken aan het eind zijn de lege regel die de kop afsluit, en de lege tekst daarna.

Een server bouwt een header met line = "Location: " + target + "\r\n". Waar wordt data instructies?

Dezelfde lus krijgt een titel met een lege regel erin. Welke regel drukt hij als derde af?

title = "Dune\r\n\r\n<p>hi</p>"

head = "HTTP/1.1 303 See Other\r\nLocation: /books?title=" + title + "\r\n\r\n"

for line in head.split("\r\n"):
    print("[" + line + "]")

Een eerste blik op SQL

Het eerste fragment is de bekendste van de vier. Het heet SQL-injectie (SQL injection), en dit is alles wat erbij hoort:

query = "SELECT author FROM books WHERE title = '" + title + "'"

Een titel met een enkel aanhalingsteken (') erin beëindigt de tekst tussen aanhalingstekens te vroeg, en wat er na het aanhalingsteken volgt, wordt gelezen als SQL. De oplossing is: stop met samenvoegen. De query houdt een placeholder, de ?, en de reist ernaast mee:

cursor.execute("SELECT author FROM books WHERE title = ?", (title,))

Nu krijgt de database de query en de waarde als twee aparte dingen, en hij parset de waarde nooit als SQL. Niets in deze cursus voert SQL uit, dus dit is een eerste blik en niet meer. De cursus SQL and Databases with Python leert placeholders grondig.

Nog twee plekken hebben hetzelfde patroon, en deze cursus noemt ze alleen. Tekst van buiten die in een shellcommando wordt samengevoegd, wordt door de shell geparset. En de pickle van Python kan code uitvoeren terwijl hij gepickelde bytes laadt, dus bytes van buiten worden nooit ge-unpickled.

Validatie is niet de oplossing

De titel was geldig. Dat is het ongemakkelijke deel.

Het is verleidelijk om terug te gaan naar validate_book en < in titels te weigeren. Dat breekt eerlijke titels die het teken bevatten, en het mist nog steeds de andere plekken. De header werd aangevallen met een regeleinde, niet met een punthaak. Het pad wordt aangevallen met ... Een validator zou elke plek moeten kennen waar een waarde ooit naartoe geschreven kan worden, en elk teken dat daar iets betekent.

validate_book accepteerde de titel <b>Dune</b>. Waarom is hij in de pagina toch een probleem?

De verdediging hoort dus op elke plek waar de tekst wordt gebruikt, in de code die weet wat hij schrijft. Het is op elke plek een andere verdediging, en de volgende vier lessen schrijven er elk één:

Waar de tekst heen gaatDe verdediging
Een headerwaardeWeiger een waarde met een regeleinde
Een doel van een doorverwijzingSta maar één vorm toe: een pad op deze site
Een bestandspadLos het pad op en vergelijk het daarna met de map
Een paginaCodeer de tekens die in HTML iets betekenen

Waar hoort de verdediging tegen een titel die markup wordt?

Validatie blijft belangrijk: die beslist wat er mag worden opgeslagen. Ze vervangt het werk aan de andere kant niet. Valideer op de heenweg, bewaak of codeer op de terugweg, en laat het ene nooit het andere vervangen.

De blokken op deze pagina bouwen alleen tekst. Hier wordt geen header verstuurd en geen bestand geopend. In de volgende les schrijft je eigen server de header, en bepaalt een verzoek wat erin staat.