SQL-injektiot muo­dos­ta­vat mer­kit­tä­vän uhan re­laa­tio­tie­to­kan­ta­mal­leil­le ja niissä oleville ar­ka­luon­tei­sil­le tiedoille. Siksi kattava suojaus näitä lu­vat­to­mia ulkoisia pää­syy­ri­tyk­siä vastaan – jotka ovat mah­dol­li­sia tie­to­tur­va-aukkojen vuoksi – on eh­dot­to­man vält­tä­mä­tön­tä.

Mikä on SQL-injektio?

SQL-injektio on hyök­käys­tyyp­pi, jossa hyö­dyn­ne­tään tie­to­tur­va-aukkoa re­laa­tio­tie­to­kan­ta­jär­jes­tel­mis­sä, jotka käyttävät SQL-ky­se­ly­kiel­tä käyttäjän syöt­tä­mien tietojen kä­sit­te­lyyn. Hyökkääjä hyödyntää käyttäjän syötteitä, joita ei ole suojattu asian­mu­kai­ses­ti ja jotka si­säl­tä­vät eri­kois­merk­ke­jä, kuten kak­sois­vii­vo­ja, lai­naus­merk­ke­jä tai puo­li­pis­tei­tä. Näillä merkeillä on erityisiä toi­min­to­ja SQL-tulkkissa, ja niiden avulla suo­ri­tet­ta­via komentoja voidaan ma­ni­pu­loi­da ul­koi­ses­ti. SQL-injektiot liittyvät usein PHP- ja ASP-so­vel­luk­siin, jotka pe­rus­tu­vat van­hen­tu­nei­siin ra­ja­pin­toi­hin. Monissa näistä ta­pauk­sis­ta syötettä ei puh­dis­te­ta riit­tä­väs­ti, mikä tekee siitä erin­omai­sen kohteen hyök­käyk­sil­le.

Li­sää­mäl­lä stra­te­gi­ses­ti toi­min­to­merk­ke­jä luvaton käyttäjä voi syöttää jär­jes­tel­mään yli­mää­räi­siä SQL-komentoja ja ma­ni­pu­loi­da tie­to­kan­ta­tie­tuei­ta lu­keak­seen, muo­ka­tak­seen tai pois­taak­seen tietoja. Pa­him­mis­sa ta­pauk­sis­sa hyök­kää­jät voivat jopa päästä käsiksi jär­jes­tel­män ko­men­to­ri­vil­le, minkä seu­rauk­se­na he voivat saada täyden hallinnan tie­to­kan­ta­pal­ve­li­mes­ta.

Esimerkki SQL-in­jek­tios­ta, joka ha­vain­nol­lis­taa tie­to­kan­ta­hyök­käyk­sen toi­min­ta­pe­ri­aa­tet­ta

Koska haa­voit­tu­vat tie­to­kan­ta­pal­ve­li­met voidaan tunnistaa nopeasti ja SQL-in­jek­tio­hyök­käyk­set ovat suh­teel­li­sen helppoja toteuttaa, tämä menetelmä on edelleen yksi maa­il­man­laa­jui­ses­ti ylei­sim­min käy­te­tyis­tä tek­nii­kois­ta ky­ber­ri­kol­lis­ten kes­kuu­des­sa. Hyök­kää­jät käyttävät erilaisia stra­te­gioi­ta ja hyö­dyn­tä­vät sekä äskettäin löy­det­ty­jä että jo pitkään tun­net­tu­ja tie­to­tur­va-aukkoja tie­to­jen­hal­lin­tapro­ses­siin liit­ty­vis­sä so­vel­luk­sis­sa. Jotta voimme paremmin ymmärtää, miten SQL-injektio toimii käy­tän­nös­sä, tar­kas­tel­laan esi­merk­ki­nä kahta yleistä hyök­käys­me­ne­tel­mää.

Esimerkki 1: Pääsy huonosti suojatun käyttäjän syötteen kautta

Tie­to­kan­taan pää­se­mi­sek­si käyt­tä­jien on yleensä ensin tun­nis­tet­ta­va itsensä. Tätä varten käytetään yleisesti skriptejä, jotka tuottavat kir­jau­tu­mis­lo­mak­keen, jossa on kentät käyt­tä­jä­tun­nuk­sel­le ja sa­la­sa­nal­le. Käyttäjät täyttävät lomakkeen, ja skripti tarkistaa sitten, onko tie­to­kan­nas­sa vastaavia tietueita. Ole­tusar­voi­ses­ti tie­to­kan­ta saattaa sisältää taulukon nimeltä users, jossa on sarakkeet username ja password. Tyy­pil­li­ses­sä verk­ko­so­vel­luk­ses­sa tie­to­kan­taan pääsyä koskevat sk­rip­ti­ri­vit (Python-tyyppistä pseu­do­koo­dia käyttäen) saattavat näyttää tältä:

uname = request.POST['username']
passwd = request.POST['password']
sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'"
database.execute(sql)
python

Hyökkääjä voi nyt ma­ni­pu­loi­da sa­la­sa­na­kent­tää SQL-injektion avulla, esi­mer­kik­si syöt­tä­mäl­lä password' OR 1='1, mikä tuottaa seuraavan SQL-kyselyn:

sql = "SELECT id FROM users WHERE username='' AND password='password' OR 1='1'"
python

Tämä antaa hyök­kää­jäl­le täyden pääsyn tie­to­kan­nan koko käyt­tä­jä­tau­luk­koon, koska sa­la­sa­nak­ri­tee­ri täyttyy aina (1='1'). Jos hyökkääjä kirjautuu sisään jär­jes­tel­män­val­vo­ja­na, hän voi vapaasti muokata mitä tahansa tie­to­kan­nan tietueita. Vaih­toeh­toi­ses­ti käyt­tä­jä­tun­nus­kent­tää voidaan ma­ni­pu­loi­da samalla tavalla.

Esimerkki 2: Tietojen poiminta tun­nis­tei­den muok­kaa­mi­sen avulla

Tietojen hakeminen tie­to­kan­nas­ta tun­nis­teen pe­rus­teel­la on käy­tän­nöl­li­nen ja yleinen menetelmä, mutta se avaa myös mah­dol­li­sen portin SQL-in­jek­tiol­le. Esi­mer­kik­si verk­ko­pal­ve­lin tietää URL-osoit­tees­sa lähetetyn tun­nis­teen pe­rus­teel­la, mitkä tiedot sen tulee hakea tie­to­kan­nas­ta. Vastaava PHP-skripti näyttää tältä:

<?php
    $mysqli = new mysqli("localhost", "username", "password", "database");
    $id = intval($_GET['id']);
    $result = $mysqli->query("SELECT * FROM table WHERE id=$id");
    while ($row = $result->fetch_assoc()) {
        echo print_r($row, true);
    }
?>
php

Odotettu URL noudattaa mallia .../script.php?id=22. Tässä ta­pauk­ses­sa haet­tai­siin taulukon rivi, jonka tunniste on ”22”. Jos luvaton henkilö pääsee muok­kaa­maan tätä URL-osoitetta ja lähettää sen sijaan pyynnön, kuten .../script.php?id=22+OR+1=1, tuloksena oleva kysely aiheuttaa sen, että taulukon kaikki rivit haetaan:

SELECT * FROM table WHERE id=22 OR 1=1;
sql

Miten ri­kol­li­set löytävät haa­voit­tu­via tie­to­kan­ta­jär­jes­tel­miä?

Pe­ri­aat­tees­sa mikä tahansa verk­ko­si­vus­to tai verk­ko­so­vel­lus, joka käyttää SQL-tie­to­kan­to­ja ilman esi­val­mis­tel­tu­ja kyselyitä (prepared sta­te­ments) tai muita suo­ja­toi­men­pi­tei­tä, voi olla alttiina SQL-in­jek­tioil­le. Löydetyt haa­voit­tu­vuu­det eivät pysy kauan piilossa in­ter­ne­tis­sä. Itse asiassa on olemassa verk­ko­si­vus­to­ja, jotka jul­kai­se­vat ajan­ta­sai­sia luet­te­loi­ta tun­ne­tuis­ta tie­to­tur­va-aukkoista – ja jopa se­lit­tä­vät, kuinka hyök­kää­jät voivat käyttää Google-hakuja löy­tääk­seen vastaavia verk­kopro­jek­te­ja. Jos verk­ko­si­vus­to palauttaa yk­si­tyis­koh­tai­sia SQL-vir­heil­moi­tuk­sia, ky­ber­ri­kol­li­set voivat käyttää näitä viestejä tun­nis­taak­seen mah­dol­li­sia haa­voit­tu­vuuk­sia. Esi­mer­kik­si apo­stro­fin li­sää­mi­nen URL-osoitteen loppuun, joka sisältää ID-pa­ra­met­rin, voi jo paljastaa heik­kou­den, kuten seu­raa­vas­sa esi­mer­kis­sä on esitetty:

[DomainName].com/news.php?id=5’

Haa­voit­tu­va verk­ko­si­vus­to palauttaa vir­heil­moi­tuk­sen seu­raa­vas­sa muodossa:

Query failed: You have an error in your SQL syntax…

Sa­man­lai­sia me­ne­tel­miä voidaan käyttää myös sa­rak­kei­den lu­ku­mää­rän, taulukko- ja sa­rak­kee­ni­mien, SQL-version tai jopa käyt­tä­jä­tun­nus­ten ja sa­la­sa­no­jen sel­vit­tä­mi­seen. Lisäksi on olemassa erilaisia työkaluja, joilla voidaan au­to­ma­ti­soi­da sekä tietojen ke­rää­mispro­ses­si että SQL-in­jek­tio­hyök­käys­ten to­teut­ta­mi­nen.

Kuinka suojata tie­to­kan­taa­si SQL-in­jek­tiol­ta

Tie­to­kan­ta­jär­jes­tel­mää­si koh­dis­tu­via SQL-in­jek­tio­hyök­käyk­siä voidaan estää monin eri tavoin. Sinun tulee huomioida kaikki asiaan liittyvät osa­te­ki­jät – palvelin ja yk­sit­täi­set so­vel­luk­set sekä tie­to­kan­nan hal­lin­ta­jär­jes­tel­mä.

Vaihe 1: Seuraa so­vel­luk­sis­ta tulevia au­to­maat­ti­sia syötteitä

Kun kä­si­tel­lään ul­koi­sis­ta tai si­sään­ra­ken­ne­tuis­ta so­vel­luk­sis­ta tulevia syötteitä, on eh­dot­to­mas­ti tar­kis­tet­ta­va ja suo­da­tet­ta­va lähetetyt arvot SQL-hyök­käys­ten es­tä­mi­sek­si.

1. Tarkista tie­to­tyy­pit

Jokaisen syötteen on vas­tat­ta­va odotettua tie­to­tyyp­piä. Jos esi­mer­kik­si vaaditaan nu­mee­ris­ta syötettä, yk­sin­ker­tai­nen va­li­doin­ti PHP:ssä voisi näyttää tältä:

if (filter_var($input, FILTER_VALIDATE_INT) === false) {
    throw new InvalidArgumentException("Invalid input");
}
php

Vastaavia tar­kis­tuk­sia tulisi toteuttaa merk­ki­jo­no­jen, päi­vä­mää­rien tai muiden eri­tyis­ten muotojen osalta.

2. Suodata eri­kois­mer­kit

Eri­kois­mer­kit voivat aiheuttaa tie­to­tur­va-aukkoja, etenkin SQL- tai HTML-yh­teyk­sis­sä. Tur­val­li­nen tapa on käyttää htmlspecialchars() HTML-syöt­teis­sä ja PDO::quote() SQL-ky­se­lyis­sä.

3. Vältä vir­heil­moi­tus­ten näyt­tä­mis­tä

Tie­to­kan­nan tai jär­jes­tel­män teknisiä yk­si­tyis­koh­tia pal­jas­ta­via suoria vir­heil­moi­tuk­sia tulisi välttää. Sen sijaan tulisi näyttää yleinen viesti, kuten:

echo "An error occurred. Please try again later.";
error_log("Unexpected error encountered. See system log for details.");
php

4. Käytä valmiita lauseita

Yksi te­hok­kaim­mis­ta tavoista estää SQL-injektiot on käyttää valmiita lauseita. Tässä me­ne­tel­mäs­sä SQL-komennot ja pa­ra­met­rit lä­he­te­tään erikseen, joten hai­tal­lis­ta koodia ei voida suorittaa. Tässä on esimerkki to­teu­tuk­ses­ta PHP:llä PDO:ta (PHP Data Objects) käyttäen:

$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->bindParam(':id', $user_id, PDO::PARAM_INT);
$stmt->execute();
php

Tie­to­kan­nan hal­lin­ta­jär­jes­tel­mä varmistaa au­to­maat­ti­ses­ti, että syötetyt tiedot kä­si­tel­lään tur­val­li­ses­ti.

Vaihe 2: Varmista pal­ve­li­men kattava suojaus

Myös sen pal­ve­li­men tie­to­tur­va, jolla tie­to­kan­ta­jär­jes­tel­mä­si toimii, on rat­kai­se­vas­sa asemassa SQL-in­jek­tioi­den tor­jun­nas­sa. Keskeinen toi­men­pi­de on käyt­tö­jär­jes­tel­män suo­jaa­mi­nen nou­dat­ta­mal­la seuraavia parhaita käy­tän­tö­jä:

  • Asenna tai ota käyttöön vain ne so­vel­luk­set ja palvelut, jotka ovat vält­tä­mät­tö­miä tie­to­kan­nan toi­min­nal­le.
  • Poista kaikki käyt­tä­mät­tö­mät tai tar­peet­to­mat käyt­tä­jä­ti­lit.
  • Varmista, että kaikki tar­vit­ta­vat jär­jes­tel­mä- ja oh­jel­mis­to­päi­vi­tyk­set asen­ne­taan vii­py­mät­tä.
  • Sovella vä­him­mäi­soi­keuk­sien pe­ri­aa­tet­ta var­mis­taak­se­si, että käyt­tä­jil­le ja pal­ve­luil­le myön­ne­tään vain vält­tä­mät­tö­mät vä­him­mäi­soi­keu­det.

Verk­kopro­jek­ti­si tur­val­li­suus­vaa­ti­mus­ten mukaan sinun tulisi harkita seuraavia li­sä­suo­ja­toi­men­pi­tei­tä:

  • Tun­keu­tu­mi­sen ha­vait­se­mis­jär­jes­tel­mät (IDS) ja tun­keu­tu­mi­sen es­to­jär­jes­tel­mät (IPS): Nämä jär­jes­tel­mät käyttävät erilaisia ha­vait­se­mis­me­ne­tel­miä hyök­käys­ten var­hai­seen tun­nis­ta­mi­seen, hä­ly­tys­ten lä­het­tä­mi­seen ja – IPS-jär­jes­tel­män ollessa käytössä – vas­ta­toi­men­pi­tei­den au­to­maat­ti­seen käyn­nis­tä­mi­seen.
  • So­vel­lus­ker­rok­sen yh­dys­käy­tä­vä (ALG): ALG valvoo ja suodattaa so­vel­lus­ten ja verk­ko­se­lain­ten välistä lii­ken­net­tä suoraan so­vel­lus­ta­sol­la.
  • Verk­ko­so­vel­lus­ten palomuuri (WAF): WAF suojaa eri­tyi­ses­ti verk­ko­so­vel­luk­sia SQL-in­jek­tiol­ta ja si­vus­to­jen väliseltä sk­rip­tauk­sel­ta (XSS) estämällä tai puh­dis­ta­mal­la epäi­lyt­tä­vät pyynnöt.
  • Zero Trust -lä­hes­ty­mis­ta­pa: Tämä moderni tie­to­tur­va­mal­li varmistaa, että jokainen pää­syy­ri­tys – sen al­ku­pe­räs­tä riip­pu­mat­ta – tar­kis­te­taan ja to­den­ne­taan ennen kuin pääsy myön­ne­tään.
  • Pa­lo­muu­ri­sään­nöt ja verkon seg­men­toin­ti: Nämä ovat vält­tä­mät­tö­miä hyök­käys­pin­nan mi­ni­moi­mi­sek­si pitkällä ai­ka­vä­lil­lä.
  • Sään­nöl­li­set IT-tie­to­tur­va­tar­kas­tuk­set ja tun­keu­tu­mis­tes­tit: Nämä auttavat ha­vait­se­maan ja kor­jaa­maan haa­voit­tu­vuu­det var­hai­ses­sa vaiheessa.

Vaihe 3: Suojaa tie­to­kan­ta ja käytä tur­val­lis­ta koodia

Aivan kuten käyt­tö­jär­jes­tel­män­kin, myös tie­to­kan­nas­ta tulisi poistaa kaikki tar­peet­to­mat osat ja pitää se ajan tasalla. Poista kaikki tar­peet­to­mat tal­len­ne­tut me­net­te­lyt ja poista käytöstä kaikki käyt­tä­mät­tö­mät palvelut ja käyt­tä­jä­ti­lit. Luo erillinen tie­to­kan­ta­ti­li, joka on tar­koi­tet­tu yk­si­no­maan verk­ko­käyt­töön, ja myönnä sille vain vält­tä­mät­tö­mät käyt­tö­oi­keu­det.

Valmiiden lauseiden käytön mu­kai­ses­ti on erittäin suo­si­tel­ta­vaa olla käyt­tä­mät­tä mysql (joka pois­tet­tiin PHP 7:stä). Valitse sen sijaan mysqli tai PDO, jotta varmistat paremman tie­to­tur­van ja yh­teen­so­pi­vuu­den. Tur­val­li­nen mysqli voisi näyttää tältä:

$mysqli = new mysqli("localhost", "username", "password", "database");
if ($mysqli->connect_error) die("Connection failed");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$stmt->bind_result($hashedPassword);
if ($stmt->fetch() && password_verify($_POST['password'], $hashedPassword)) {
    echo "Login successful";
} else {
    echo "Invalid login credentials";
}
$stmt->close();
$mysqli->close();
php

Sa­la­sa­no­ja ei saa koskaan tallentaa suoraan tie­to­kan­taan tai hakea sel­vä­kie­li­se­nä. Sen sijaan kannattaa käyttää ha­jau­tus­me­ne­tel­mää, kuten password_hash(), yhdessä password_verify() kanssa käyt­tä­jä­tun­nus­ten suo­jaa­mi­sek­si tur­val­li­ses­ti. Tur­val­li­nen toteutus voisi näyttää tältä:

$mysqli = new mysqli("localhost", "username", "password", "database");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
if ($row && password_verify($_POST['password'], $row['password'])) {
    echo "Login successful!";
} else {
    echo "Incorrect username or password.";
}
php

Mitä yhteistä bobby-pöydillä on SQL-injektion kanssa?

Verk­ko­si­vus­to bobby-tables.com käyttää xkcd-verk­ko­sar­ja­ku­via ha­vain­nol­lis­taak­seen hu­mo­ris­ti­ses­ti tie­to­kan­toi­hin syö­tet­tä­vien suo­jaa­mat­to­mien käyt­tä­jä­tie­to­jen vaaroja. Sar­ja­ku­vas­sa äiti saa puhelun poikansa (jota kutsutaan hellästi nimellä Little Bobby Tables) koulusta. Soittaja kysyy, onko pojan nimi todella Robert'); DROP TABLE Students;--, mihin äiti vastaa myön­tä­väs­ti. Soiton syy selviää pian: yritys syöttää nimi Robert op­pi­las­re­kis­te­riin johti koko op­pi­las­tie­to­kan­nan pois­tu­mi­seen. Äiti ei ole kovin myö­tä­tun­toi­nen – hän toivoo vain, että koulu on oppinut läksynsä ja puhdistaa jatkossa tie­to­kan­taan syö­tet­tä­vät tiedot.

Sarjakuva tuo selvästi esiin ne ka­ta­stro­faa­li­set seu­rauk­set, joita voi aiheutua siitä, ettei käyttäjän syöttämiä tietoja tar­kis­te­ta ja puh­dis­te­ta asian­mu­kai­ses­ti tie­to­kan­ta­so­vel­luk­sis­sa.

Siirry pää­va­lik­koon