Kirjoittaja: Antti Laaksonen (2011).
Nettisivujen tietoturva-aukot ja niihin kohdistuneet hyökkäykset ovat saaneet paljon julkisuutta. Esimerkiksi syksyllä 2007 teini-ikäinen hakkeri sai käsiinsä yli 78000 suomalaisen käyttäjän salasanan hyödyntämällä alkeellisia puutteita sivustojen tietoturvassa.
Tietoturvassa on oleellista ymmärtää, mitä on tekemässä. Jos koodiin lisää "turvallisuutta" ilman ymmärrystä, ei ole mitään varmuutta siitä, että lopputulos on aidosti turvallinen. Toisaalta tuntemalla muutaman perustekniikan pystyy estämään kaikki yleisimmät tietoturva-aukot.
Kriittinen kohta sivuston turvallisuudessa on, kun käyttäjä syöttää tietoa esimerkiksi lomakkeen tai sivun osoitteen kautta. Käyttäjän antama tieto voi olla mitä tahansa, minkä vuoksi siihen tulee suhtautua epäluuloisesti. Tietoturvassa oletuksena on, että käyttäjä on vihollinen.
Tarkastellaan esimerkiksi järjestelmää, jossa käyttäjät voivat lähettää toisilleen yksityisviestejä. Viestit tallennetaan tietokantaan, ja jokainen viesti saa oman id-numeronsa. Esimerkiksi järjestelmässä voi olla linkki seuraavalle sivulle, joka näyttää id-numeroa 1722 vastaavan viestin:
naytaviesti.php?id=1722
Nyt käyttäjä voi alkaa kokeilla, mitä tapahtuu, jos id-numeroa muuttaa. Seuraava sivu saattaa paljastaa toiselle käyttäjälle kuuluvan viestin, jos sivuston tietoturva ei ole kunnossa:
naytaviesti.php?id=1723
Tällaiset aukot saa estettyä tarkistamalla joka vaiheessa, että käyttäjällä on oikeus nähdä pyytämänsä tieto. Esimerkiksi jos id-numeroa 1723 vastaavan viestin vastaanottaja ei ole sama kuin kirjautuneena oleva käyttäjä, järjestelmän tulee havaita asia ja näyttää sopiva virhesivu.
SQL-injektio on yleinen tietoturva-aukko, jonka syynä on, että käyttäjän antama tieto yhdistetään sellaisenaan tietokantaan menevään SQL-kyselyyn.
Tarkastellaan esimerkiksi seuraavaa kyselyä:
SELECT id FROM kayttajat WHERE tunnus = '$tunnus' AND salasana = '$salasana'
Kyselyn tarkoituksena on hakea tietokannasta käyttäjän antamaa tunnusta ja salasanaa vastaava id-numero. Ongelmana on, että tunnus ja salasana yhdistetään sellaisenaan kyselyyn. Nyt käyttäjä voi antaa esimerkiksi tunnukseksi admin' --
ja salasanaksi tyhjän, jolloin kyselystä tulee seuraava:
SELECT id FROM kayttajat WHERE tunnus = 'admin' -- ' AND salasana = ''
Merkintä --
tarkoittaa kommenttia, joten kysely on käytännössä seuraava:
SELECT id FROM kayttajat WHERE tunnus = 'admin'
Käyttäjä pääsee siis kirjautumaan sisään tunnuksella "admin" tietämättä oikeaa salasanaa.
Tehokas tapa suojautua SQL-injektiolta on käyttää tietokannan käsittelyyn PDO-kirjastoa Ohjelmointiputkan oppaiden mukaisesti. Tällöin käyttäjän antamat tiedot yhdistetään kyselyihin turvallisesti eivätkä ne voi muuttaa kyselyiden rakennetta. Käytännössä PDO-kirjasto muuttaa esimerkiksi merkkijonossa olevat '
-merkit muotoon \'
, jolloin merkkijonon lopettaminen kesken ei ole mahdollista.
XSS-aukko syntyy, kun käyttäjän lähettämä tieto tulostetaan sellaisenaan sivulle. Tällöin käyttäjä pystyy lisäämään sivulle haluamaansa HTML- tai JavaScript-koodia. Tarkastellaan esimerkiksi seuraavaa koodia:
<?php echo "Nimi: " . $_POST["nimi"]; ?>
Nyt jos käyttäjä antaa nimekseen <b>Pepe</b>, lopputulos on seuraava:
Tavallisin seuraus XSS-aukosta on, että käyttäjät voivat sekoittaa sivuston HTML-muotoilua (vahingossa tai tahallaan). JavaScriptin avulla myös ikävämmät seuraukset ovat kuitenkin mahdollisia. Esimerkiksi hyökkääjä voi yrittää kaapata sivun käyttäjän istunnon lisäämällä sivulle JavaScript-koodin, joka lähettää käyttäjän evästetiedot toisella palvelimella olevalle sivulle.
XSS-aukot pystyy estämään käyttämällä funktiota htmlspecialchars
aina, kun sivulle tulostetaan tekstiä, jossa olevaa HTML-koodia ei ole tarkoitus käsitellä selaimessa. Funktio muuttaa käytännössä esimerkiksi merkit <
ja >
muotoon <
ja >
, jolloin mahdolliset tekstissä olevat tagit näkyvät sivulla sellaisenaan.
Esimerkissä tarvittava muutos on seuraava:
<?php echo "Nimi: " . htmlspecialchars($_POST["nimi"]); ?>
Nyt jos käyttäjä antaa nimekseen <b>Pepe</b>, lopputulos on seuraava:
Nimi: <b>Pepe</b>
Tavallinen tapa tallentaa salasana tietokantaan on tallentaa selväkielisen salasanan sijasta salasanasta laskettu tiiviste. PHP:ssä on salasanan tiivisteen laskemiseen funktio password_hash ja tarkastamiseen funktio password_verify. Seuraavat koodit esittelevät yksinkertaisesti näiden funktioiden toimintaa:
<?php $salasana = "selleri"; $tiiviste = password_hash($salasana, PASSWORD_DEFAULT); echo htmlspecialchars("{$salasana} → {$tiiviste}\n"); // Yleensä tiiviste tallennettaisiin tietokantaan. ?>
<?php $tiiviste = '$2y$10$k/Aeui2BCA6OwOwEG8XyD.mns8YuUlp5ep0xC9iJHTQnUHO9WcbPa'; // Yleensä tiiviste haettaisiin tietokannasta. $salasana = $_POST["salasana"]; if (password_verify($salasana, $tiiviste)) { echo "Salasana on oikea!\n"; } else { echo "Salasana on väärä!\n"; } ?>
Tiivisteen tarkoituksena on, että siitä ei pysty selvittämään helposti selväkielistä salasanaa. Salasanan tarkistaminen on kuitenkin mahdollista vertaamalla oikean salasanan ja annetun salasanan tiivisteitä toisiinsa.
Ajatellaan sitten tilannetta, jossa hyökkääjä on saanut käsiinsä salasanan tiivisteen ja haluaa murtaa salasanan. Hyökkääjä voi käydä järjestelmällisesti läpi lyhyitä merkkijonoja ja verrata niiden tiivisteitä salasanaan. Hän voi myös hyödyntää tietokantoja, joihin on laskettu etukäteen suuri määrä eri merkkijonojen tiivisteitä.
Hyökkääjän työtä voi vaikeuttaa lisäämällä salasanan perään suolan ennen tiivisteen laskemista. Suola on satunnainen merkkijono, jonka tarkoituksena on pidentää salasanaa niin, ettei salasanaa voi löytää etukäteen lasketusta tietokannasta. Kun vielä jokaiselle salasanalle valitaan eri suola, hyökkääjä voi murtaa vain yhtä salasanaa kerrallaan.
Toinen lähestymistapa on hidastaa tiivisteen laskemista esimerkiksi soveltamalla tiivistefunktiota tuhat kertaa peräkkäin. Tällöin hyökkääjältä vie paljon enemmän aikaa käydä läpi mahdollisia salasanoja.
PHP:n funktio password_hash luo automaattisesti satunnaisen suolan (osaksi tiivistettä) ja toistaa varsinaista tiivistefunktiota useita kertoja. Salasanoja voi tiivistää myös esimerkiksi MD5-funktiolla, mutta silloin täytyy itse toteuttaa suolan lisäys ja funktion toisto.
Tietyt PHP:n funktiot voivat huolimattomasti käytettyinä tarjota hyökkääjälle vapaat kädet tehdä tuhoja sivustolla. Vaarallisia funktioita ovat esimerkiksi eval
, joka suorittaa annetun PHP-koodin palvelimella, sekä shell_exec
, joka suorittaa annetun komennon palvelimen tiedostojärjestelmässä.
Tiedoston lähettäminen selaimen kautta palvelimelle on myös vaarallinen toiminto, jos tiedostonimen tarkistus on puutteellinen. Pahimmillaan hyökkääjä pystyy lähettämään palvelimelle tiedoston, jonka päätteenä on .php
ja sisältönä on mitä tahansa PHP-koodia. Aiheesta kertoo enemmän koodivinkki.
Myös tuttu include
-komento saattaa olla vaarallinen:
Tässä ideana on, että sivun osoitteen perässä on näytettävän tiedoston nimi (esimerkiksi index.php?sivu=palaute.php
). Ongelmana on kuitenkin, että osoitetta muuttamalla hyökkääjä pystyy liittämään sivun osaksi minkä tahansa palvelimella olevan tiedoston niin, että PHP-tulkki käsittelee tiedoston sisällön.
muutoin kyllä erittäin, erittäin hyvä opas :D pointsit siitä, sekä kiva että koko sarja uudistui ja laajeni.
Hyvä PHP-tietoturvaopas.
Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.