Door Joost Schellevis

Redacteur

Sql-injectie en xss: de beste verdediging

19-03-2012 • 08:00

259

Singlepage-opmaak

Tot slot

Uiteraard zijn er nog meer beveiligingproblemen waarbij input van anderen een rol speelt en waarbij input eveneens gevalideerd moeten worden. Zo is het uitwisselen van gegevens via bijvoorbeeld jsonp of xml-feeds een beveiligingsrisico: je moet jezelf er grondig van verzekeren dat de servers waarmee je communiceert, wel veilig zijn.

Ook zijn in dit artikel geen methoden behandeld om sql-injectie en cross site scripting in andere programmeertalen tegen te gaan. In andere talen spelen dezelfde problemen, maar zijn de gevolgen en de oplossingen soms heel verschillend. Zo is sql-injectie in ASP.NET wel een bestaand probleem, maar zijn er andere manieren om het af te vangen, bijvoorbeeld door het gebruik van Linq.

Voor de gemiddelde programmeur die echter even een PHP/MySQL-website in elkaar wil flansen - en daar zijn er nogal wat van - mag de les duidelijk zijn: wantrouw de gegevens die van de gebruiker afkomstig zijn, wees selectief met wat je in je database opslaat en hou rekening met het ergste. Een paranoïde programmeur heeft bijna altijd gelijk.

Reacties (259)

259
253
115
21
1
113
Wijzig sortering
Ik had toch liever gezien dat prepared statements eerder aan bod kwamen / als belangrijker / beter beschreven worden dan mysql_real_escape_string; sterker nog, eigenlijk zou het helemaal niet uitgelegd moeten worden (of slechts als een voetnoot) in de verschillende tutorials dat je een SQL statement gewoon kunt maken door je strings in je query te pleuren.

Prepared statements: 100% veilig, betere performance, en leesbaarder code. Ik begrijp niet waarom mensen nog anders zouden willen.
Door inderdaad met bind variabelen te werken, hoeft het sql statement niet steeds opnieuw geparsed te worden. Bij de meeste databases levert dit een behoorlijke performance winst op, omdat eigenlijk maar een paar verschillende queries vanuit de website afgevuurd worden, met alleen andere waarden voor de bind variabelen.
Ben het hier volledig mee eens. Het handmatig aan elkaar plakken van query strings is echt niet meer van deze tijd en is gewoon altijd gevaarlijk, ook al weten alle programmeurs die aan het project werken dat ze input sanitation moeten toepassen. Je kan gewoon niet 100% afvangen dat het ergens in een grote codebase niet een keer fout gaat. En waarom zou je überhaupt het risico willen lopen als het alternatief nog andere voordelen met zich mee brengt zoals betere performance.

In de tijd dat PHP5 net uit was kan ik me nog voorstellen dat er redenen waren om niet de mysqli interface te gebruiken (aangezien veel providers tergend traag zijn met het ondersteunen van nieuwe PHP versies), maar zo langzamerhand kan dat toch echt geen argument meer zijn. Als je nu nog steeds bij een host zit die alleen maar PHP4 draait dan is het echt tijd om te verhuizen naar een provider die security wat meer serieus neemt.
Even een ASP.Net aanvulling:

Als je om wat voor reden dan ook geen gebruik wilt maken van Linq2Sql of het entity framework (en daar kan ik er wel een paar van bedenken), kan je gewoon de Parameters property van de SqlCommand class gebruiken. Op deze manier word er in ieder geval gechecked op sql injection.

Je kan eventuele input encoden door de HttpUtility class te gebruiken, waarmee je in ieder geval heel eenvoudig XSS kan voorkomen. Het is niet altijd de mooiste manier. Als een aanvaller bijvoorbeeld een <script> block invoert in een veld wat anders direct naar je html gerenderd word, zal dit script block leesbaar worden weergegeven in je pagina... maar het word tenminste niet uitgevoerd en daar gaat het om!

[Reactie gewijzigd door Laurens-R op 23 juli 2024 02:17]

Goede aanvulling!

De SqlCommand class gebruiken in combinatie met Parameters doet grofweg hetzelfde als de 'prepared statements' waarover gesproken wordt in het artikel; het houdt sql-injection tegen omdat de escaping geheel automatisch gaat. Het heet alleen 'parametrized queries' of 'parametrized statements'. Eventueel zou je ze in sommige situaties ook kunnen preparen, waardoor je exact hetzelfde krijgt. In elk geval bieden beide technieken een automatische bescherming tegen sql-injection.
Daarnaast natuurlijk een goed ingerichte database met stored procedures. Nog veiliger :)
Als extra: als je het Webforms framework van ASP.NET nog gebruikt:

De meeste controls al HTML escaping ingebakken. Als je de '.Text' eigenschap van een `<asp:TextBox>` aanpast, zal dit al geescaped eruit komen. Wat echter niet geescaped is, is de het `<asp:Label>` control. Hiermee kun je XSS mogelijk gemaakt hebben in de ASP.NET website.

Gebruik daarvoor `<asp:Label Mode="Encode">`, of gebruik de `HttpServerUtility.HtmlEncode` functie.

[Reactie gewijzigd door YaPP op 23 juli 2024 02:17]

Leuk artikel, de uitleg is makkelijk genoeg zodat het ruimte overlaat voor verder onderzoek.
Ik kan ook iedereen het boek Essential PHP Security aanbevelen. Hier staan veel tips in over hoe je als programmeur moet denken bij het maken van een veilige applicatie.
Maar pas op. Denk na het toepassen van beschreven maatregelen niet dat je website veilig is. Er zijn nog vele andere manieren waarop een website/database gehacked of gedefaced kan worden. Dan is dat boek van o'Reilly een aanrader.
Is inderdaad echt een aanrader. Het enige jammere vind ik de naam, beter was "Essential web application security" met als voetnoot, "explained using PHP". De taal is wat mij betreft van ondergeschikt belang, ook programmeurs die gebruik maken van een andere taal dan PHP zullen veel aan dit boek hebben.
Idd, veilig ben je nooit: Wij passen dit ook standaard toe (en nog wel meer), zijn uitermate strikt in acceptatie van externe scripts, hebben ook wel een goed geconfigureerde fail2ban, ssh (via ander poort) op gelimiteerde IP's, hard- en software firwall (csf),een rkhunter (doen!), fatsoenlijk user-beheer met weinig rechten en updaten apache/php/mysql/linux ongeveer om de week, maar toch nog een 0-day exploit via (waarschijnlijk) Apache opgelopen rond januari.

't is dat wij de logging op een externe server doen, anders hadden we het niet eens snel gemerkt dat er een keylogger (en nog wat meer rotzooi) was geinstalleerd op één van onze servers door een Koreaans IP-adres.

Maar goed, mocht je professionele aspiraties hebben en je de gevaren van (alleen al) sql-injection onderschat, reken dan maar op problemen in de nabije toekomst.
De database connectie die je gebruikt in je site moet je ook limiteren qua rechten. Een database connectie voor een website (inlog form, zoekopdrachten, etc) heeft echt geen DROP of DELETE rechten nodig.
Inderdaad: een nuttige uitbreiding op het artikel, maar zo zijn er nog, bvb dat de website laat uitschijnen dat user input enkel kan via invoervelden, maar ook de URL die geparsed wordt naar variabelen via $_GET kan door de gebruiker worden gewijzigd...

Ik vind het overall een zeer goed artikel, maar imho mag in de conclusie iets meer nadruk gelegd worden op het feit dat het hiermee niet gedaan is. De meeste reacties hier komen van mensen die dit reeds weten, maar het artikel heeft het meeste nut voor de beginnende programmeurs die online alles gaan leren.

Ik ben zelf geen beginner meer, maar ik vrees dat mijn applicaties niet volledig afgesloten zijn, ook al heb ik al specifieke boeken gelezen over de beveiliging van PHP-code...
Anoniem: 201824 @zkotz19 maart 2012 14:01
En een SELECT username LIMIT/TOP 1, scheelt misschien ook als je het over tabel inhoud op het scherm toveren hebt. Je maakt het denk ik wel wat lastiger daardoor, zo niet onmogelijk om een tabel uit te lezen.
Edit: parameters gebruiken is natuurlijk veel veiliger, zodat er geen select ster gedaan kan worden.

[Reactie gewijzigd door Anoniem: 201824 op 23 juli 2024 02:17]

Volgensmij is het voorbeeldje met prepared statements niet goed, dat zou moeten zijn:

$stmt->bind_param('ss', $username, $password); $stmt->execute();

Tenzij de username een integer veld is, zoals bij ICQ

[Reactie gewijzigd door hafkensite op 23 juli 2024 02:17]

Correct, http://php.net/manual/en/mysqli-stmt.bind-param.php

Bizar dat de hier beschreven oplossingen al voldoende zouden moeten zijn: ik als 'scriptkiddie' heb een android programma in Google Play staan wat ook een PHP scriptje gebruikt voor trial verificatie. Ik laat de telefoon zijn ID sturen (OK, dat is inderdaad theoretisch aan te passen, maar da's wel erg moeilijk doen voor een programma van €1 ;)), en check vervolgens in mijn mysql database hoe lang die gebruiker nog het programma mag uitproberen gebaseerd op wanneer ie zich t eerst meldde.

Alles wat hier staat vond ik ook gewoon in de tutorials die ik heb gevolgd om mijn scriptje te maken, van de escape strings & prepared statements (ik gebruik ze beiden, dan moet t wel goed gaan ;)). Verder had ik ook zelf al bedacht dat whitelisten handig kan zijn, dus ik check met regex of het inderdaad een geldige android ID is door te kijken of de input gelijk is aan [0-F]{16}
Niks over CSRF tokens? :)

Edit:

Nou dan licht ik maar even toe,

Van http://www.squarefree.com.../web-developers.html#CSRF :

"A Cross-site request forgery hole is when a malicious site can cause a visitor's browser to make a request to your server that causes a change on the server. The server thinks that because the request comes with the user's cookies, the user wanted to submit that form."

Om dit tegen te gaan is het makkelijkste een zogenaamde CSRF token te genereren per formulier, deze dan te checken wanneer de formulier terugkomt. Zo is het zeker dat iemand op een andere site niet zomaar een request op jou site kan genereren.

Veel frameworks (bijv Django) hebben deze functionaliteit al ingebakken voor het gemak.

[Reactie gewijzigd door shadylog op 23 juli 2024 02:17]

Wat ik ook wel eens zie bij het gebruik van mysql_real_escape_string, is dat sommigen lijken te vergeten dat deze functie alleen van toepassing is op strings. Zodra je voor een query ints gaat escapen, zal deze methode niet goed werken.

voorbeeld:


$int = 3; // kan ook uit $_GET['id'] komen
mysql_query("SELECT * FROM user WHERE id = " . mysql_real_escape_string($int));


Hierboven is er nog niets aan de hand. Als ik er echter voor zorg dat ik 3 OR id = 4 invoer, zal dit niet ge-escaped worden. Het bevat namelijk geen gevaarlijke karakters.

offtopic:
Of wat YopY zegt, gebruik prepared statements. Dan heb je hier ook geen last van.

[Reactie gewijzigd door X_lawl_X op 23 juli 2024 02:17]

Maar dat heeft natuurlijk ook weer met input validatie te maken.
Overigens cast je dan je input naar het juiste type of knal hem door intval().
Een beetje zonde dat dit artikel totaal geen melding maakt van cross-site request forgery. Heb je een usersysteem dat hiervoor vatbaar is en waarbij gebruikers bijvoorbeeld hun emailadres of wachtwoord zonder problemen kunnen aanpassen, dan is de kans groot dat op kinderlijk eenvoudige wijze een (admin)account gekaapt kan worden en alle gegevens alsnog te benaderen zijn, ook al is de site bestand tegen XSS en injecties.

Stuurt een site niet voor elke user bij elk formulier een hidden form input mee met een unieke waarde die wordt gecontroleerd, dan is de site vrijwel zeker vatbaar.

Ook zonder HTML-code kunnen er verder bij sites met een bijvoorbeeld een slecht forum/gastenboek gevaarlijke situaties ontstaan. Een link die begint met javascript: of een site waarbij de GET variabelen worden gebruikt om data te submitten. Dat laatste is vooral bij oude PHP-sites die gebruik maken van global vars, een img-tag is dan al genoeg.

[Reactie gewijzigd door BarôZZa op 23 juli 2024 02:17]

Ik gebruik zelf HTML Purifier
HTML Purifier is a standards-compliant HTML filter library written in PHP. HTML Purifier will not only remove all malicious code (better known as XSS) with a thoroughly audited, secure yet permissive whitelist, it will also make sure your documents are standards compliant, something only achievable with a comprehensive knowledge of W3C's specifications. Tired of using BBCode due to the current landscape of deficient or insecure HTML filters? Have a WYSIWYG editor but never been able to use it? Looking for high-quality, standards-compliant, open-source components for that application you're building? HTML Purifier is for you!
Wat wel interessant is, is dat er wel een manier is om mysql_real_escape_string() te bypassen. Dit werkt alleen in bepaalde situaties waarin SET NAMES is gebruikt om de encoding aan te passen. Met een bepaalde reeks karakters (BIG5 of GBK) kun je de escape omzeilen en gewoon injecteren. Dit geldt alleen op oude MySQL versies en is al in 2006 opgelost, (zie bijv: http://bugs.mysql.com/bug.php?id=8378). Als je UTF-8 hanteert is er niets aan de hand. Beter nog zijn toch prepared statements.

Op basis van de lekken van de afgelopen tijd kun je wel stellen dat dit toch nog steeds een vulnerability kan zijn, met het over het algemeen zorgwekkend serverbeheer..

Op dit item kan niet meer gereageerd worden.