Elke SQL-statement die wordt opgebouwd door strings aan elkaar te plakken met onbetrouwbare waarden, kan worden geïnjecteerd. De oplossing is in elke taal dezelfde: houd de SQL vast en bind de waarden als parameters. De OWASP Query Parameterization Cheat Sheet rekent SQL injection onder A05:2025-Injection in de OWASP Top 10:2025 en noemt het een van de gevaarlijkste kwetsbaarheden in webapplicaties.
Waarom concatenatie faalt
SQL maakt geen echt onderscheid tussen het control plane en het data plane. Een aanvaller plaatst een metateken in een invoerveld, waarna er SQL-commando's verschijnen die er eerder niet waren. De OWASP-communitypagina over SQL injection toont een C#-query die door concatenatie is opgebouwd:
string query = "SELECT * FROM items WHERE owner = '"
+ userName + "' AND itemname = '"
+ ItemName.Text + "'";
Voert de aanvaller name' OR 'a'='a in als itemnaam, dan is de voorwaarde altijd waar. De query geeft dan elke rij uit de tabel items terug, ongeacht de eigenaar. Afhankelijk van de functies en rechten van de database kan dezelfde fout ook gegevens wijzigen, bestanden raken of commando's op het besturingssysteem uitvoeren.
De herkomst van de waarde doet er niet toe. De maatregelen gelden evengoed als de data afkomstig is van een HTTP-request, een andere service of eerder opgeslagen gegevens.
Zoek de samengestelde queries
Begin met een grep op SQL-sleutelwoorden in de buurt van concatenatie of opmaak van strings. Deze patronen zijn een grove eerste ronde, geen scanner. Ze geven veel ruis en missen queries die over meerdere regels zijn verdeeld.
# concatenation (Java, C#, JavaScript, PHP uses "." instead of "+")
grep -rnEi "(select|insert into|update|delete from)[^;]*[\"'] *\+" src/
# Python f-strings and .format()
grep -rnEi "f[\"'](select|insert|update|delete)|(select|insert|update|delete).*\.format\(" .
# JavaScript template literals
grep -rnEi '`(select|insert|update|delete)[^`]*\$\{' src/
# sprintf-style formatting
grep -rnEi "sprintf\(.*(select|insert|update|delete)" .
Lees elke treffer en controleer of onbetrouwbare waarden zijn gebonden en niet aan elkaar geplakt. Kijk daarna op de plekken die een grep vaak overslaat:
- ORM-querystrings. Hibernate-queries kunnen kwetsbaar zijn wanneer waarden in de querystring worden gevoegd in plaats van gebonden.
- Stored procedures. Hierbij blijft de SQL in de database staan, maar ze zijn niet automatisch veilig. OWASP toont een PL/SQL-procedure die
usrenitmaan elkaar plakt tot een querystring en zegt dat die "kwetsbaar is voor dezelfde SQL injection-aanval als in het eerste voorbeeld". Het binden van de argumenten van de aanroep beschermt de aanroep, niet de dynamische SQL die binnen de procedure wordt opgebouwd. - Parameterisatie aan de clientzijde. Veel clientbibliotheken bouwen queries op via string-concatenatie en sturen de ruwe query naar de server. Controleer dat de parameterisatie server-side plaatsvindt.
- Dynamische identifiers en sorteerrichtingen. Deze kunnen niet worden gebonden, dus hier overleeft concatenatie een opschoonronde voor parameterisatie.
Test vervolgens. De OWASP Web Security Testing Guide bevat SQL injection-tests voor handmatig en geautomatiseerd testen, inclusief blind- en out-of-band-technieken. Voor een externe controle op wat uw review heeft gemist is een penetratietest de aangewezen route.
Vervang ze door parameters
Definieer de SQL-structuur los van de datawaarden en bind de waarden via de database-API. Dit is het voorbeeld uit de OWASP SQL Injection Prevention Cheat Sheet, zonder opruimen en foutafhandeling:
String query = "SELECT account_balance FROM user_data WHERE user_name = ?";
PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, customerName);
ResultSet results = statement.executeQuery();
Een waarde als tom' or '1'='1 wordt nu behandeld als de letterlijke gebruikersnaam waarop wordt gezocht. Als u een onbetrouwbare waarde in de query plakt voordat u die prepared, is de bescherming weg.
Bind bij een ORM ook in de querystring. De OWASP-voorbeelden gebruiken een benoemde parameter in HQL, FROM Inventory i WHERE i.productDescription = :productDescription, en geven de waarde mee met setParameter.
Gebruik voor dynamische SQL in stored procedures bind variables. In Oracle PL/SQL betekent dat EXECUTE IMMEDIATE met USING. In SQL Server betekent het sp_executesql met een gedeclareerde parameterlijst. Procedures zonder dynamische SQL hebben niets bijzonders nodig, omdat hun parameters al gebonden zijn.
Binden voorkomt dat een waarde de SQL-syntaxis verandert. Het bepaalt niet of de aanroeper toegang mag hebben tot het bijbehorende account, dus uw autorisatiecontroles blijven nodig.
Wat parameters niet kunnen dekken
Parametermarkeringen staan voor datawaarden. Ze kunnen geen tabelnamen, kolomnamen of sleutelwoorden als ASC en DESC vervangen. Moeten gebruikers toch kiezen, koppel die keuze dan aan een kleine set tokens die de applicatie zelf beheert en weiger alles wat onbekend is. Vóór:
String query = "SELECT id, name FROM items ORDER BY name " + sortParam;
Na:
Map<String, String> directions = Map.of("asc", "ASC", "desc", "DESC");
String direction = directions.get(sortParam);
if (direction == null) {
throw new IllegalArgumentException("Unknown sort direction");
}
String query = "SELECT id, name FROM items ORDER BY name " + direction;
De tekst die aan de query wordt toegevoegd is nu altijd een van twee literals, nooit wat de gebruiker heeft gestuurd. Blijf elke datawaarde in dezelfde query binden, ook als die door controles op type, lengte of formaat komt.
Val niet terug op handmatig escapen. Dat hangt af van het SQL-dialect en de configuratie: de modus NO_BACKSLASH_ESCAPES van MySQL schakelt bijvoorbeeld backslash-escaping uit. OWASP raadt escapen sterk af en noemt deny lists doorspekt met achterdeurtjes.
Beperk de schade
Least privilege voorkomt geen SQL injection. Het beperkt wat een aanvaller kan doen via een databaseaccount, dus pas het toe naast parameters.
- Begin bij niets en voeg de rechten toe die elk applicatieaccount nodig heeft. Wie moet uitzoeken wat er van een ruime toekenning af moet, vergeet gemakkelijk rechten weg te nemen.
- DBA- of beheerderstoegang voor een applicatieaccount is de gebruikelijke sluiproute, want dan werkt alles "gewoon". OWASP noemt dit zeer gevaarlijk.
- Een loginpagina heeft leestoegang nodig tot de velden voor gebruikersnaam en wachtwoord en geen schrijftoegang. Een registratiepagina heeft insert nodig. Die scheiding is alleen af te dwingen als beide pagina's verschillende databasegebruikers gebruiken, dus geef elke webapplicatie een eigen gebruiker.
- Aanvallers hebben geen injection nodig om een account met te ruime rechten te misbruiken. Ze kunnen een parameter wijzigen van een toegestane waarde naar een waarde waarvoor zij niet geautoriseerd zijn maar de applicatie wel, en daarom helpen smalle rechten ook als de queries schoon zijn.
- Een view kan alleen de velden tonen die een account nodig heeft, waarbij
SELECTop de view wordt toegekend en niet op de tabel. Controleer eerst de regels van uw database: PostgreSQL controleert de toegang tot onderliggende tabellen normaal gesproken met de rechten van de eigenaar van de view. Queries op views hebben nog steeds parameters nodig. - Het account op besturingssysteemniveau telt ook mee. Draai het DBMS niet als root of system. OWASP merkt op dat MySQL op Windows standaard als system draait.
Waar u begint
- Maak een lijst van alle plekken waar SQL wordt opgebouwd: applicatiecode, ORM-querystrings en stored procedures met dynamische SQL. Gebruik de grep-patronen hierboven als eerste ronde.
- Vervang aaneengeplakte waarden door gebonden parameters, server-side, en bouw dynamische SQL binnen procedures opnieuw op met bind variables.
- Vervang dynamische keuzes voor tabellen, kolommen en sortering door vaste mappings die onbekende invoer weigeren.
- Audit de databaseaccounts. Haal DBA- en beheerrechten weg bij applicatieaccounts, geef elke applicatie een eigen gebruiker en breng alleen-lezentoegang onder in smalle toekenningen of views.
- Voer de SQL injection-tests uit de Web Security Testing Guide uit op het resultaat, of laat de wijzigingen beoordelen door ons team voor veilige softwareontwikkeling.
