Ga naar hoofdinhoud

HTML van een bezoeker: de zwakheid

Hier bouw je op verder

In een gastenboek komt tekst van bezoekers in jouw HTML. Met een template blijft die tekst tekst. Maar een endpoint voor htmx geeft vaak een klein stukje HTML terug, en dat bouw je snel met een f-string. Dan gaat het mis.

Een gastenboek met een f-string​

Dit gastenboek zet de berichten niet in een template, maar in een f-string. De pagina zelf is een los bestand static/gastenboek.html:

<!doctype html>
<html>
<head>
<script src="/static/js/htmx.min.js" defer></script>
</head>
<body>
<form hx-post="/gastenboek" hx-target="#antwoord">
<input type="text" name="naam" placeholder="Je naam" required>
<input type="text" name="bericht" placeholder="Je bericht" required>
<button type="submit">Verstuur</button>
</form>
<p id="antwoord"></p>
<div id="lijst" hx-get="/berichten/lijst" hx-trigger="load, every 5s"></div>
</body>
</html>

Zet htmx.min.js uit Zonder herladen in static/js/. Het endpoint staat in main.py:

import time

from fastapi import FastAPI, Form
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from sqlitedict import SqliteDict

app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")

@app.post("/gastenboek")
async def gastenboek_opslaan(naam: str = Form(...), bericht: str = Form(...)):
with SqliteDict("gastenboek.db") as db:
db[f"bericht_{time.time_ns()}"] = {"naam": naam, "bericht": bericht}
db.commit()
return HTMLResponse(f"Bedankt, {naam}. Je bericht staat in het gastenboek.")

@app.get("/berichten/lijst")
async def berichten_lijst():
regels = ""
with SqliteDict("gastenboek.db") as db:
for bericht in db.values():
regels += f"<li>{bericht['naam']}: {bericht['bericht']}</li>"
return HTMLResponse(f"<ul>{regels}</ul>")
  1. Regel 16:

    Het antwoord na het versturen, zoals in het htmx-recept stond voordat er escape in kwam.

  2. Regel 20-24:

    Elk bericht wordt een <li>, met een f-string. De naam en het bericht komen erin zoals de bezoeker ze typte.

Doe het na, op je eigen server​

Alleen je eigen server

Doe dit uitsluitend op je eigen server op 127.0.0.1. HTML invullen op de site van een ander om te kijken wat er gebeurt, is een aanval op die site en op de bezoekers ervan, en strafbaar.

Start je server en open http://127.0.0.1:8000/static/gastenboek.html. Vul als naam Sara in en als bericht:

<b>vet</b>

Binnen vijf seconden, als de lijst zich ververst, staat daar vet, vetgedrukt. Open dezelfde pagina in een privévenster, als een tweede bezoeker: ook daar is het vet. De browser las wat Sara typte niet als tekst, maar als HTML.

Wat de server terugstuurt, zie je met een scriptje lijst.py:

import httpx

antwoord = httpx.get("http://127.0.0.1:8000/berichten/lijst")
print(antwoord.text)

Uitvoer, met alleen het bericht van Sara in de database:

<ul><li>Sara: <b>vet</b></li></ul>

De <b> staat er letterlijk in. Een vet woord is onschuldig, maar de browser maakt geen onderscheid: ook een <script> dat een bezoeker invult, komt zo in de pagina van iedereen die hem daarna opent, en de browser voert het uit. Dat heet XSS, cross-site scripting: code van een bezoeker die draait in de browser van een ander.

Er gaat iets mis​

Het formulier verstuurt niets, of er staat ?naam= in de adresbalk en de lijst blijft leeg.

Oorzaak: htmx is niet geladen. Zonder htmx kent de browser hx-post en hx-get niet, en verstuurt hij het formulier als gewone GET naar dezelfde pagina.

Oplossing: kijk in het tabblad Netwerk of htmx.min.js een 200 kreeg. Een 404 betekent dat het bestand niet in static/js/ staat.

Meer uitleg: Zonder herladen met htmx

Opdrachten​

Opdracht 1: Predict - Een schuine naam​

Een nieuwe bezoeker vult als naam <i>Noor</i> in en als bericht hoi.

Vraag: wat ziet Noor onder het formulier, direct na het versturen?

Tip

Kijk naar regel 16: wat staat er in de f-string?

Antwoord

"Bedankt, Noor. Je bericht staat in het gastenboek.", met Noor schuin. Het antwoord na het versturen gebruikt dezelfde f-string, dus ook daar leest de browser de naam als HTML. Dat raakt alleen Noor zelf; de lijst raakt iedereen.

Opdracht 2: Investigate - En met een template?​

Het recept Verversen in het htmx-overzicht maakt de lijst met een template, berichten_lijst.html. Stel dat Sara daar <b>vet</b> invult. Wordt het dan vet?

Antwoord

Nee. Je ziet <b>vet</b> staan, met de haakjes erbij. Jinja zet in een template van elke < een &lt; en van elke > een &gt;, en die tekens toont de browser als tekst. De f-string doet dat niet, en in de volgende stap los je het zo op.

Door naar stap 2: de oplossing.