Server-side template injection (SSTI)
CWE-1336OWASP A03:2021Bijgewerkt 31 augustus 20265 min leestijd
Server-side template injection (SSTI) is een kwetsbaarheid waarbij invoer van een aanvaller wordt geëvalueerd door de template-engine van de server, doordat die invoer in de sjabloonbroncode wordt opgebouwd in plaats van als gegevens te worden meegegeven. Op engines als Jinja2, Twig of Freemarker leidt dit meestal tot remote code execution. De oplossing is invoer als contextvariabele mee te geven.
Template-engines maken van een sjabloon plus wat gegevens een afgewerkte pagina, e-mail of configuratie. Server-side template injection verandert dat gemak in een van de gevaarlijkste injectiekwetsbaarheden die er zijn, want dezelfde engine die een pagina opmaakt, kan ook code uitvoeren. Hieronder leest u wat het is, hoe een aanval verloopt en hoe u gebruikersinvoer buiten het sjabloon zelf houdt.
Wat is server-side template injection?
Server-side template injection, meestal afgekort tot SSTI, is een kwetsbaarheid waarbij een aanvaller zijn eigen sjabloonexpressies laat evalueren door de template-engine van de server. Dat gebeurt zodra gebruikersinvoer in de broncode van het sjabloon wordt geplakt in plaats van als gegevens aan het sjabloon te worden meegegeven. Engines als Jinja2 (Python), Twig (PHP) en Freemarker (Java) zijn juist gebouwd om expressies te evalueren, dus elke invoer die de broncode bereikt, wordt als logica behandeld en niet als tekst.
Een alledaagse vergelijking: een sjabloon is een standaardbrief met invulruimtes. Normaal schrijft u de brief en komt alleen de naam van de lezer in de open plek. SSTI is wat er gebeurt als u de lezer een deel van de brief zelf laat schrijven: hij kan instructies toevoegen die de drukker braaf uitvoert. De engine kan uw formulering niet van de zijne onderscheiden, dus alles wat hij schrijft wordt onderdeel van het programma.
De fout zit niet in de engine, maar in het opbouwen van het sjabloon uit onvertrouwde invoer. Precies die ene misstap scheidt een onschuldige begroeting van een volledige serverovername.
Hoe werkt een SSTI-aanval?
De grondoorzaak is dezelfde als bij elke injectie: invoer en code worden tot één tekenreeks aan elkaar geplakt. Een begroetingsendpoint dat het sjabloon opbouwt uit de parameter name ziet er zo uit.
Kwetsbaar:
from flask import request
from jinja2 import Template
@app.route("/greet")
def greet():
name = request.args.get("name", "")
# De invoer van de gebruiker wordt onderdeel van de sjabloon-BRONCODE
source = "<h1>Hallo " + name + "</h1>"
return Template(source).render()
Bij een gewone naam werkt dit prima. Maar de aanvaller stuurt geen naam, hij stuurt een sjabloonexpressie. De klassieke test vermenigvuldigt twee getallen binnen de expressietekens van Jinja2:
GET /greet?name={{7*7}} HTTP/1.1
Host: example.com
Leest het antwoord “Hallo 49” in plaats van de payload letterlijk terug te geven, dan is de invoer geëvalueerd en is het endpoint injecteerbaar. Vanaf dat bruggenhoofd loopt een aanvaller langs de objecten die de engine blootgeeft tot hij de os-module van Python bereikt, en voert dan commando’s uit:
GET /greet?name={{cycler.__init__.__globals__.os.popen('id').read()}} HTTP/1.1
Host: example.com
Het antwoord bevat nu de uitvoer van id, en dat betekent het uitvoeren van willekeurige commando’s op de server. Twig en Freemarker hebben een andere syntaxis (Twig gebruikt dezelfde dubbele accolades, Freemarker een expressie met een dollarteken), maar de escalatie verloopt gelijk: bereik een runtime-object en van daaruit een systeemaanroep.
De oplossing is om gebruikersinvoer volledig buiten de sjabloonbroncode te houden. Laad het sjabloon uit een bestand of een vaste tekenreeks en geef de invoer mee als contextvariabele. De engine behandelt die dan als gegevens om af te drukken, nooit als logica om uit te voeren.
Veilig:
from flask import request
from jinja2 import Environment, FileSystemLoader
env = Environment(
loader=FileSystemLoader("templates"),
autoescape=True,
)
@app.route("/greet")
def greet():
name = request.args.get("name", "")
# De invoer gaat mee als gegevens, het sjabloon ligt vast
template = env.get_template("greet.html")
return template.render(name=name)
Met een vast sjabloon greet.html dat alleen de variabele name afdrukt, wordt dezelfde payload nu letterlijk als tekst weergegeven. De expressietekens zijn één keer gecompileerd, uit een bestand dat u beheert; de accolades van de aanvaller komen te laat om nog als expressie te worden gelezen.
Wat is de impact van SSTI?
De impact loopt van informatielekkage tot volledige remote code execution, en daarom ligt de ernst tussen hoog en kritiek. Op engines met een rijke runtime, zoals Jinja2 of Freemarker, bereikt een werkende payload doorgaans commando’s van het besturingssysteem, waarmee de aanvaller bestanden kan lezen, geheimen en omgevingsvariabelen kan oogsten, kan doorstoten naar het interne netwerk of een blijvende achterdeur kan plaatsen. Dat is een complete serverovername vanuit één enkel verzoek.
Zelfs waar de engine beperkter is, kan een aanvaller vaak willekeurige applicatiegegevens lezen, zoals sjabloonglobals, configuratie en verbindingsgegevens, of een denial of service veroorzaken door een zware expressie te laten evalueren. Omdat de payload via een gewone parameter binnenkomt en het antwoord er normaal uit kan zien, valt de inbraak in de logs makkelijk te missen. In de praktijk verdient een SSTI op een publiek endpoint de behandeling van een kritieke bevinding waar u alles voor laat vallen.
Hoe spoort u SSTI op?
De standaard eerste test is een rekenkundige probe binnen de expressietekens van de engine, verstuurd naar elk veld dat in een sjabloon terecht kan komen: een URL-parameter, een formulierwaarde, een profielveld, een e-mailonderwerp. Komt een waarde die een rekensom evalueert berekend terug in plaats van letterlijk, dan bereikt de invoer de engine. Omdat elke engine zijn eigen syntaxis heeft, gebruiken testers een kleine reeks probes om eerst te bepalen welke engine draait voordat ze escaleren.
Scanners als Burp Suite en sjabloongerichte tools als tplmap vinden de duidelijke, direct weerspiegelde gevallen, maar SSTI schuilt vaak in tweedelijns-sinks: een waarde die nu wordt opgeslagen en pas later in een rapport, factuur of notificatie-e-mail wordt weergegeven. Dat pad natrekken vraagt handwerk, en het is een vast onderdeel van een penetratietest van AssistSec, waarbij we vaststellen of een probe de engine echt bereikt en, met toestemming, de werkelijke impact aantonen.
Hoe voorkomt u SSTI?
- Bouw sjabloonbroncode nooit op uit gebruikersinvoer. Laad sjablonen uitsluitend uit bestanden of vaste tekenreeksen; dit haalt de kwetsbaarheid bij de wortel weg.
- Geef gebruikersinvoer mee als contextvariabele, zodat de engine die weergeeft als gegevens in plaats van als logica te evalueren.
- Kies waar mogelijk een logica-loze engine zoals Mustache of Handlebars, zodat een sjabloon geen enkele runtime-logica kan uitdrukken.
- Zet de sandbox of beperkte modus van de engine aan als extra laag en houd die gepatcht, maar reken er niet op als enige maatregel.
- Scheid contentauteurs van sjablonen. Moeten gebruikers opmaak aanleveren, gebruik dan een smalle, toegestane opmaaktaal zoals Markdown, nooit een volledige sjabloontaal.
- Draai de renderer met minimale rechten, in een container zonder shell en met minimale netwerktoegang, zodat een geslaagde payload zo weinig mogelijk oplevert.
- Laat de code periodiek testen. Een gerichte penetratietest en code review vangen de injecteerbare sinks die scanners overslaan.
Bronnen
Veelgestelde vragen
Is SSTI hetzelfde als cross-site scripting?
Nee. XSS draait in de browser van het slachtoffer, terwijl SSTI op de server binnen de template-engine wordt uitgevoerd. SSTI is doorgaans veel ernstiger, omdat het vaak rechtstreeks tot remote code execution leidt. Een payload die alleen HTML injecteert is XSS; een payload die als sjabloonlogica wordt geëvalueerd is SSTI.
Welke template-engines zijn kwetsbaar voor SSTI?
Elke engine die expressies evalueert kan kwetsbaar zijn, waaronder Jinja2, Twig, Freemarker, Velocity en Thymeleaf. Het risico is het grootst bij engines met een rijke runtime, waar escalatie naar het uitvoeren van commando's eenvoudig is. Logica-loze engines zoals Mustache verkleinen het risico sterk.
Voorkomt het escapen van uitvoer SSTI?
Nee. Output-escaping voorkomt cross-site scripting, maar SSTI wordt uitgevoerd tijdens het evalueren van het sjabloon, nog voordat de uitvoer wordt geëscaped. De enige betrouwbare oplossing is gebruikersinvoer buiten de sjabloonbroncode houden en als contextvariabele meegeven.
Hoe test ik snel op SSTI?
Stuur een eenvoudige rekenkundige expressie binnen de expressietekens van de engine naar elk veld dat een sjabloon kan bereiken. Komt het berekende resultaat terug in plaats van de letterlijke tekst, dan wordt de invoer geëvalueerd. Bepaal eerst welke engine draait voordat u escaleert, en test alleen systemen waarvoor u toestemming hebt.
Verwante artikelen
- KwetsbaarhedenCWE-78A03:2021OS command injectionOS command injection uitgelegd: hoe shell-tekens in een systeemaanroep belanden, wat een aanvaller ermee kan en hoe argumentarrays het voorkomen.
- KwetsbaarhedenCWE-79A03:2021Cross-site scripting (XSS)Cross-site scripting (XSS) laat aanvallers scripts injecteren die in de browser van uw bezoekers draaien. Zo werkt de aanval en zo voorkomt u het.
- KwetsbaarhedenCWE-94A03:2021Remote code execution (RCE)Remote code execution (RCE) uitgelegd: hoe aanvallers via ongefilterde invoer eigen commando's of code op uw server draaien, en hoe u het voorkomt.
- KwetsbaarhedenCWE-89A03:2021SQL injectionSQL injection uitgelegd: hoe aanvallers via ongefilterde invoer uw database uitlezen of wijzigen, en hoe u het met prepared statements voorkomt.
- KwetsbaarhedenCWE-918A10:2021Server-side request forgery (SSRF)Server-side request forgery (SSRF) uitgelegd: hoe aanvallers uw server dwingen interne systemen en clouddiensten te bereiken, en hoe u het voorkomt.