Hopelijk is dit onderwerp bij veel developers al lang bekend en weet iedereen precies wat je er tegen moet doen. Tijdens de cursussen blijkt dat veel mensen SQL injection een interessant onderwerp vinden en aangezien het toch één van de belangrijkste beveiligingsrisico's is, wil ik er graag een blogje aan wagen. Ten eerste, wat is SQL injection, en ten tweede, wat kun je er aan doen.

Hopelijk is dit onderwerp bij veel developers al lang bekend en weet iedereen precies wat je er tegen moet doen. Tijdens de cursussen blijkt dat veel mensen SQL injection een interessant onderwerp vinden en aangezien het toch één van de belangrijkste beveiligingsrisico's is, wil ik er graag een blogje aan wagen. Ten eerste, wat is SQL injection, en ten tweede, wat kun je er aan doen.
Wat is SQL-injection
SQL injection is dus een beveiligingsrisico, hoofdzakelijk op websites. Om te weten hoe je deze aanvallen kunt pareren is het eerst van belang om te weten wat het precies inhoudt. Zoals de naam al een beetje doet vermoeden, wordt bij SQL injection door de hacker extra SQL statements toegevoegd aan een SQL query richting de database. Bijvoorbeeld: de website wil een query uitvoeren op de database om opzoek te gaan naar een bepaald product en de hacker voegt aan deze query extra statements toe om ook de creditcard gegevens van alle klanten op te vragen. Het is zelfs mogelijk om bestanden op server uit te lezen of rechten te veranderen. Behoorlijk serieus dus.
Ik zal aan de hand van een voorbeeldwebsite het proberen te demonstreren. Het voorbeeld is uitgewerkt in ASP.NET met C#, maar is net zo goed toe te passen op PHP.
Uitgangssituatie is een kleine database 'Shop'.
De gevaren van SQL injection
En een website waar ik kan zoeken op producten vanaf een bepaalde prijs. In mijn 'Product' tabel zitten bijvoorbeeld 3 producten duurder dan € 500,-. Deze worden dan onder elkaar getoond. De textbox heeft de naam 'tbPrice'.
De gevaren van SQL injection
De code om de producten op te vragen ziet er als volgt uit.
using (SqlConnection conn = new SqlConnection(connectionString))
        {
           conn.Open();
           string sqlstr = "select [name] from product where Listprice > " + tbPrice.Text + " order by [name]";
            SqlCommand comm = new SqlCommand(sqlstr, conn);
            using (SqlDataReader reader = comm.ExecuteReader())
            {
                StringBuilder sb = new StringBuilder();
                while (reader.Read())
                {
                    sb.AppendFormat("{0}<BR>",(reader[0]));
                }
                lblResults.Text = sb.ToString();
            }
        }
Er wordt een connectie aangemaakt, een query gemaakt waarin de prijs wordt geplaatst die ingevuld is in de textbox en vervolgens wordt de query uitgevoerd. Het resultaat wordt vervolgens getoond in de label 'lblResult'.
Het gevaar schuilt nu in het feit dat we erop vertrouwen dat de gebruiker daar netjes een bedrag invult. Het is echter mogelijk dat hij daar i.p.v. '500' een stuk sql-statement intypt. Bijvoorbeeld:
0 or 1=1
De statement die wordt uitgevoerd op de database is dan dus:
Select [name] from product where Listprice > 500 or 1=1 order by [name]
Aangezien '1 = 1' altijd waar is, zal elk product nu aan de voorwaarde voldoen ongeacht welke voorwaarden er in de 'where' worden opgenomen. In dit voorbeeld niet spectaculair, maar stel je de volgende situatie voor. Een login pagina met een textbox voor de username en een textbox voor het password. De code ziet er zo uit:
using (SqlConnection conn = new SqlConnection(connectionString))
        {
            conn.Open();
            string sqlstr = "select * from users where username = '" + tbUsername.Text + "' and password = '" + tbPassword.Text + "'";
            SqlCommand comm = new SqlCommand(sqlstr, conn);
            using (SqlDataReader reader = comm.ExecuteReader())
            {
                if (reader.HasRows)
                {
                    lblResults.Text = "U bent WEL ingelogd";
                }
                else
                {
                    lblResults.Text = "U bent NIET ingelogd";
                }
            }
        }
Dan is de '1 = 1' mogelijkheid wel erg vervelend. Kijk maar:
De gevaren van SQL injection
Het dubbele min-teken achteraan is ervoor om aan te geven dat alles wat er achter mijn injectie komt, als commentaar gezien moet worden. De statement die wordt uitgevoerd op de database is dus:
select * from users where username = 'Chris' and password = 'geen idee' or 1=1
Een andere mogelijkheid is om extra gegevens uit de database op te halen. We gaan weer even terug naar het eerste voorbeeld waar de producten worden opgehaald. Ik wil niet alle producten, maar eigenlijk wil ik weten hoe de database eruit ziet. Daarvoor kan ik de system-views van SQL Server gebruiken.
In de textbox typ ik nu:
10000 union select [name] from sys.tables --
Door de 'union' wordt het resultaat van de producten query samengevoegd met de query die ik als hacker zou willen zien, namelijk alle tabellen uit de database. Op het scherm worden nu de namen van de producten duurder van 10000 (in dit geval geen) getoond en alle tabellen uit mijn database.
De gevaren van SQL injection
Door degelijke views te raadplegen kan ik dus uitzoeken hoe de database eruit ziet en welke informatie in die database dan interessant is om op te vragen. Dit zou dus potentieel ook gegevens van klanten kunnen zijn!
Als het mogelijk is om select statements uit te voeren, dan is het ook mogelijk om andere statements uit te voeren. Om het bestelproces voor mezelf veel aantrekkelijker te maken zou ik bijvoorbeeld het volgende in de textbox kunnen invullen:
0; update product set [ListPrice] = 0 where [name] = 'Laptop' --
En vervolgens die laptop bestellen natuurlijk ;-)
De gevaren van SQL injection
Je snapt dat ook alle andere mogelijkheden, zoals inserts, truncates, deletes en drops, mogelijk zijn.
Wat is er tegen SQL injection te doen
Er zijn een aantal dingen die je kunt doen en waar je rekening mee moet houden. Één van de basisregels is natuurlijk: Input validatie. Niet specifiek voor SQL injection, maar in z'n algemeenheid moet je altijd checken of de input van de gebruiker zinvol is. Let er ook op dat de validatie op de server wordt uitgevoerd. Client-side validatie (d.m.v. Javascript) is leuk voor een betere user-expierience, maar er kan erg gemakkelijk omheen gewerkt worden. Check het dus ook server-side.
Je zou er bijvoorbeeld voor kunnen zorgen dat speciale characters zoals de ‘ (quote) niet geïnterpreteerd worden als SQL, maar als een onderdeel van een tekst, door deze te escapen. PHP heeft daar o.a. de functies 'addslashes' of 'mysql_real_escape_string' voor. Een verder is het verstandig om met parameters te werken. Hier onder de uitwerking van het zelfde voorbeeld, maar dan met gebruik van parameters.
string sqlstr = "select [name] from product where Listprice > @searchPrice order by [name]";
SqlCommand comm = new SqlCommand(sqlstr, conn);
SqlParameter priceParam = new SqlParameter("searchPrice", SqlDbType.Money);
priceParam.Value = tbPrice.Text;
comm.Parameters.Add(priceParam);
Er wordt een parameter 'priceParam' aangemaakt die wordt ingevuld op de plaats van '@searchPrice' in de query.De paramater zorgt o.a. voor het escapen en de conversie naar het juiste type. Eventueel ook de lengte van de input kan door de parameter worden afgehandeld.
Verder is het belangrijk om gedetailleerde foutmeldingen af te vangen en de gebruiker bijvoorbeeld naar een algemene foutpagina te sturen. In gedetailleerd foutmeldingen kan een hoop informatie zitten die het een stuk makkelijker maken om te hacken. Kijk maar eens naar onderstaande afbeelding. Ik vul alleen een ‘ (quote) in om een foutmelding uit te lokken en vervolgens krijg ik mooi alles te zien over de query. Zo wordt het wel heel eenvoudig.
De gevaren van SQL injection
Als laatste de rechten. De gebruiker zou niet meer rechten moeten hebben op de database, dan strikt noodzakelijk. Bijvoorbeeld het recht om een 'update' statement uit te voeren op een product tabel, of het opvragen van system-views, is wellicht niet nodig. Zorg dan dat deze rechten ook niet verleend zijn.
Door gebruik te maken van Stored procedures, verplicht je jezelf direct tot het gebruik van parameters. Tevens kun je ook bij het instellen van de rechten zorgen dat de gebruiker wel rechten heeft op bepaalde Stored procedures en kun je overige rechten makkelijker beperken. Daarnaast hebben Stored procedures nog genoeg andere voordelen, dus eigenlijk is dat toch al de betere manier van werken :).
Overigens heb ik bij alle voorbeelden in dit artikel gewerkt met input via het web-form. Maar het is ook mogelijk om SQL injection toe te passen via de querystring. Bijvoorbeeld: http://www.somewebsite.com/info.aspx?location=’union all select * from users
Let daar dus ook op!
Meer Informatie
Ik wil even benadrukken dat deze blog bedoeld is om informatie te verstrekken over SQL injection zodat je je applicaties beter kunt beveiligen en dus niet om websites van anderen aan te vallen! Op het internet is op diverse plaatsen veel informatie te vinden over SQL injection en wat er tegen te doen is. Tevens zijn er tools beschikbaar om je website te testen op de kwetsbaarheid voor SQL injection, bijvoorbeeld deze van HP: http://www.communities.hp.com/securitysoftware/blogs/spilabs/archive/2008/06/23/finding-sql-injection-with-scrawlr.aspx
Veel succes met het testen!