Varo SQL-ruiskutusta
Kirjoittanut J-P Julkaistu 23.2.2009
SQL-ruiskutus (SQL injection) on yksi tyypillisimmistä tietoturvaongelmista tietokantapohjaisissa järjestelmissä. Viime aikoina tällaiset tietoturva-aukot ovat olleet uutisotsikoista suurten organisaatioiden www-sivuilta löytyneiden haavaoittuvuuksien vuoksi. SQL-ruiskutukselle altis järjestelmä syntyy helposti kun ongelmaa ei tiedosteta järjestelmää kehitettäessä. Viimeistään järjestelmän testausvaiheessa tulisikin aina miettiä myös sitä, mitä mahdollinen ilkeämielinen ja pahansuopa käyttäjä voisi saada aikaan. Onneksi tämä yleinen ongelma on myös helposti vältettävissä, kunhan riski vain tiedostetaan.
Mikä SQL-ruiskutus on?
SQL-ruiskutuksella tarkoitetaan tilannetta, jossa järjestelmän käyttäjä pystyy muokkaamaan järjestelmän suorittamaa SQL-lausetta siten, että se tekee jotain muuta kuin mitä järjestelmän kehittäjä on ajatellut. Muuttamalla järjestelmän suorittamaa SQL-lausetta hyökkääjä voi päästä käsiksi tietoihin, joihin hänellä ei muutoin olisi oikeutta. Otetaan esimerkiksi järjestelmä, joka sisältää tietoja esimiehistä ja heidän alaisistaan ja oletetaan, että esimies saa saada tietoonsa vain omien alaistensa kuukausipalkan. Tiedot on talletettu tietokanta tauluun (employees), jonka kuvaus on seuraavanlainen:
+------------+---------------+ | Field | Type | +------------+---------------+ | id | int(11) | | manager_id | int(11) | | fname | varchar(255) | | lname | varchar(255) | | salary | decimal(10,0) | +------------+---------------+
Taulussa on seuraavat rivit: +----+------------+--------+-------------+--------+ | id | manager_id | fname | lname | salary | +----+------------+--------+-------------+--------+ | 1 | NULL | Matti | Meikäläinen | 3800 | | 2 | 1 | Heikki | Heksala | 2600 | | 3 | 1 | Matti | Menevä | 2600 | | 4 | 1 | Lasse | Luihu | 2000 | | 5 | 4 | Matti | Tekijä | 1800 | +----+------------+--------+-------------+--------+
Järjestelmän käyttöliittymä on rakennettu siten, että tietoja voidaan hakea antamalla henkilön etunimi. SQL-tasolla tarkistetaan, että tietokannasta palautetaan vain niiden henkilöiden tiedot joiden esimies nykyinen käyttäjä on. Lassen tapauksessa (id 4) Matin tiedot haettaisiin kyselyllä:
SELECT * FROM employees WHERE manager_id=4 AND fname='Matti';
Haku palauttaa ainoastaan Lassen alaisen Matti Tekijän tiedot.
Kaikkihan toimii niin kuin pitääkin, Lasse ei saa käsiteltäväkseen Matti Menevän tai Meikäläisen tietoja, koska he eivät kuulu hänen alaisiinsa. SQL:ssä ei siis ole mitään vikaa, eihän? Vastaus riippuu siitä miten tuo SQL-suoritetaan ja miten syötteet tarkistetaan. Tyypilliseen tapaani käsittelen asiaa Javan-näkökulmasta, mutta periaate toimii myös muissa ohjelmointikielissä.
Jos kysely suoritetaan seuraavan kaltaisella metodilla:
public void getEmployeeInformation(int managerId, String name) { Connection con = DriverManager.getConnection(DB_URI); String sql = "SELECT * FROM employees WHERE manager_id=" + managerId+" AND fname='"+name+"'"; ResultSet rs = con.createStatement().executeQuery(sql); .... }
Olemme juuri rakentaneet järjestelmäämme SQL-haavoittuvuuden. Rakentamalla SQL-kyselyn käyttäjän antaman merkkijonon (name) perusteella. Mitä jos palkkaansa tyytymätön lasse haluaisi tarkistaa kaikkien työntekijöiden palkat? Sen sijaan, että hän kirjoittaisi hakukenttään oman alaisensa nimen hän voisi kirjoittaa merkkijonon:
' OR '1'='1
Suoritettava SQL-lause tulisi muotoon:
SELECT * FROM employees WHERE manager_id=4 AND fname='' OR '1'='1';
Tämän seurauksena Matti näkisi kaikkien työntekijöiden palkat, sillä kysely palauttaa employees taulun kaikki rivit.
Miten ehkäiset SQL-ruiskutuksen?
SQL-ruiskutus on varsin helppo ehkäistä kun sen mahdollisuus tiedostetaan, yksinkertaisimmillaan se on käyttäjän antamien syötteiden tarkistamista, ja muokkaamista siten, että syöte ei muuta alkuperäisen SQL:n tarkoitusta. Esimerkkitapauksessamme olisi ollut jo suuri apu siitä, että heittomerkit olisi muutettu SQL-lle kelpaavaan muotoon sijoittamalla jokaisen heittomerkin eteen \ -merkki (edellyttäen että tietokantana toimii MySQL).
Parempi tapa korjata esimerkki on kuitenkin käyttää tavallisen Statement-olion sijasta PreparedStatement-oliota, jolloin käyttäjän antama syöte voidaan antaa parametrina kyselylle eivätkä heittomerkit aiheuta ongelmia:
public void getEmployeeInformation(int managerId, String name) { Connection con = DriverManager.getConnection(DB_URI); String sql = "SELECT * FROM employees "+ "WHERE manager_id=? AND fname=?"; PreparedStatement stat = con.prepareStatement(sql); stat.setInt(1, managerId); stat.setString(2, name); ResultSet rs = stat.executeQuery(); .... }
Lisää tietokantaohjelmoinnista ja siihen liittyvistä parhaista käytännöistä Comian kurssilla Java-tietokantaohjelmointi.



