Kyberfilosofia

Korjaa juurisyy, älä yksittäistä oiretta

Helppo
5 min

Juurisyyt vs. Oireet

Kun puhutaan haavoittuvuuksista on lähes aina eroteltavissa juurisyy ja siitä juontuvat yksittäiset oireet.

On yleinen käsitys, että haavoittuvuus tarkoittaa yksittäistä kohtaa, esimerkiksi koodiriviä, joka aiheuttaa tilanteen, jossa hyökkääjä pääsee tekemään ikävyyksiä sovelluksessa. Tällöin kyse on kuitenkin oireesta. Ja jos ongelman katsoo ratkaistuksi parantamalla yhden oireen, ongelma tulee takaisin aina uudestaan ja uudestaan.

Havainnollistetaan tätä yleisen haavoittuvuuden, SQL-injektion avulla ja katsotaan sitten pari muutakin esimerkkiä.

Esimerkki: SQL-injektio

SQL-injektio on klassinen esimerkki haavoittuvuudesta, joka syntyy, kun sovellus rakentaa SQL-kyselyitä suoraan käyttäjän syötteistä ilman asianmukaista käsittelyä. Tämä mahdollistaa hyökkääjien manipuloida kyselyjä ja päästä käsiksi tietokannan tietoihin.

Oletetaan, että meillä on sovellus, josta voi hakea käyttäjiä ja meillä on tällainen SQL-injektioille haavoittuvainen koodi.

def hae_kayttaja(kayttajanimi):
    query = f"SELECT * FROM users WHERE username = '{kayttajanimi}'"
    cursor.execute(query)
    kayttaja = cursor.fetchone()
    return kayttaja

Miten tällainen haavoittuvuus korjataan (tai miten sitä vältetään) parhaalla tavalla?

"Sanitoi"

Tietoturvatarkastaja ehdottaa, että "sanitoit" kaikki tiedot, jotka syötät (esimerkiksi käyttäjän syötteestä) SQL-kyselyyn. Kun hipsut, sun muut ikävät merkit on poistettua, vahinkoa ei välttämättä pääse sattumaan.

def yrita_epatoivoisesti_tehda_syotteesta_turvallinen(input_string):
  sanitized = input_string.replace("'", "''").replace(";", "")
  sanitized = sanitized.replace(r"\", r"\\")
  dangerous_keywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'EXEC', '--', '#', '/*', '*/']
    for keyword in dangerous_keywords:
        sanitized = sanitized.replace(keyword, "")
  sanitized = sanitized.encode('ascii', 'ignore').decode('ascii')
  sanitized = "".join([chr(ord(char) % 256) for char in sanitized])
  while "--" in sanitized or "/*" in sanitized or "*/" in sanitized:
        sanitized = sanitized.replace("--", "").replace("/*", "").replace("*/", "")
        
kayttajanimi = yrita_epatoivoisesti_tehda_syotteesta_turvallinen(kayttajanimi)

Mutta mitkä kaikki merkit ovat "ikäviä merkkejä" missäkin tietokannassa ja tietokannan versiossa? Mitä jos tarvitset hipsuja? Ne pitää sitten "eskeipata". Ja sitten pitää muistaa "eskeipata" myös "eskeippaukset", ettei hyökkäjä eskeippaa eskeippausta ja samalla koko sanitointia.

Entä moneenko kohtaan koodia tällainen "sanitointi" pitää tehdä? Joka puolelle. Monestako kohdasta se pitää unohtua, että sovellus on ihan yhtä haavoittuva, kuin että sanitointia ei tehtäisi ollenkaan? Yhdestä kohdasta. Kuinka todennäköistä on, että koodikannan kasvaessa kehittäjä ennen pitkää unohtaa sen yhdestä kohdasta? Lähes varmaa.

Korjaako parametrin "käyttäjänimi" sanitointi siis SQL-injektiohaavoittuvuuden sovelluksesta? Ei. Se voi oikein tehtynä parantaa yhden oireen, mutta se ei paranna koodikantaa haavoittuvuudesta.

"Validoi syöte"

Syötteen validointi on aina hyvä lisäsuoja. Koskaan ei voi olla täysin varma, mitä haavoittuvuuksia sovellukseen tai johonkin sen taustajärjestelmään on päässyt livahtamaan, joten käyttäjältä vastaanotettavan syötteen rajaaminen vain odotetun näköisiin arvoihin on hyvä idea.

Mutta korjaako se SQL-injektio haavoittuvuuden? Ei tietenkään. Se voi laskea todennäköisyyttä, että kyseinen oire sovelluksessa kytevästä haavoittuvuudesta löydetään tai, että oiretta voidaan hyödyntää. Se on siis lisäsuoja, ei korjaus.

Toki syötteen validointi kärsii myös samasta ongelmasta kuin sanitointi, eli se pitäisi muistaa tehdä joka kerta ja jokaiselle kyselylle.

"Ota käyttöön WAF -tuote"

Web-sovelluspalomuurin (Web Application Firewall) toiminta on käytännössä syötteen validointia, mutta valmiilla säännöstöllä joka osaa havaita selvästi törpön näköisiä HTTP-viestejä, sekä estää ne ennen kuin ne ikinä tavoittavat sovellustasi.

Kuten syötteen validointi, WAF on mainio lisäsuoja sekä hyökkäysten torjuntaan, että havaitsemiseen, mutta sekään ei korjaa haavoittuvuutta. Hankaloittaa vain sen löytymistä ja hyödyntämistä.

Juurisyyn korjaus: Käytä ORM-kirjastoa

ORM (Object-Relational Mapping) tarjoaa kehittäjille mahdollisuuden käsitellä tietokantatoimintoja raa'an SQL:n sijasta ihan vain koodilla ja olioilla. Nimi tulee siitä, että relaatiotietokannan tietorakenteet "mapitetaan" luokkiin ohjelmakoodissa.

Jos käytetään ORM:ää niin tietokantarakenne on kuvattuna koodissa, esimerkiksi seuraavanlaisella tavalla:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    username = Column(String, unique=True)
    email = Column(String)

Sama funktio (joka hakee käyttäjän nimimerkillä) voidaan sitten toteuttaa tällä tavalla:

def hae_kayttaja(kayttajanimi):
    kayttaja = session.query(User).filter_by(username=kayttajanimi).first()
    return kayttaja

Kun koko koodikanta refaktoroidaan (tai alunperinkin rakennetaan) näin, SQL-injektiohaavoittuvuutta ei ole.

Vaikka sovellusta ei suojaisi WAF-tuote ja syötteen validointi olisi unohtunut kokonaan, sovellus ei silti olisi haavoittuvainen, koska siinä ei enää ole haavoittuvuutta. Kehittäjien ei enää tarvitse murehtia, että muistiko sen validoinnin nyt laittaa jokaiselle parametrille ja oikeaan kohtaan. Koodi on parantunut SQL-injektiosta.

Toinen esimerkki: HTML-injektiosta johtuva XSS

Tällaisella haavoittuvuudella viitataan koodiin, joka rakentaa HTML:ää turvattomasti niin, että käyttäjän sivustolle lähettämä syöte sekoittuukin osaksi sivuston HTML-rakennetta ja mahdollistaa sen, että hyökkääjä pääsee tekemään tietyin reunaehdoin haitallisia muutoksia sivustolle.

Tällainen haavoittuvuus syntyy erityisesti sovelluksissa, joissa HTML:ää rakennetaan merkkijonona ja lisää lisätään dynaamisesti sisältöä yrittäen manuaalisesti enkoodata jokainen parametri. Vanhat PHP-sovellukset ovat klassinen esimerkki tästä.

Lisäsuojia

  • Syötteen validointi
  • WAF
  • CSP (Content Security Policy)
  • Mahdollisesti DomPurify.

Juurisyyn korjaus

Käytä HTML:n rakennukseen templaattikirjastoa (Jinja2, Thymeleaf, jne), joka hoitaa HTML:n parameterisoinnin turvallisesti ja automaattisesti enkoodaa arvot oikealla tavalla oikeaan kontekstiin.

Toinen vaihtoehto on tietysti olla generoimatta HTML:ää ollenkaan ja siirtyä moderneihin JavaScript-frontendeihin, kuten React, Vue, Angular tai Svelte.

Kolmas esimerkki: Tiedostojen haavoittuva käsittely palvelimella

Tälllaisella haavoittuvuudella viitataan esimerkiksi tilanteeseen, jossa sovellus rakentaa tiedostopolkuja palvelimen päässä turvattomasti, jolloin hyökkääjä pääsee lukemaan/kirjoittamaan tiedostoja palvelimen levyllä, johtaen usein palvelimen kaappaamiseen.

Tai tilanteeseen, jossa hyökkääjä lataa palveluun vaikka "avatar kuvan", joka onkin oikeasti PHP- tai ASP -skripti, joka ajaa mielivaltaista koodia, kun se ladataan selaimella web-palvelimen juuresta.

Tai tilanteeseen, jossa hyökkääjä saa luotua symlinkin Linux-palvelimen levylle ja sitä kautta käsiteltyä mielivaltaisesti eri tiedostoja. Tai dotfilen joka muuttaa sovelluksen konfiguraatiota haitallisesti.

...tai tuhanteen ja yhteen muuhun vaaranpaikkaan joka tiedostojen käsittelystä palvelimella aiheutuu.

Lisäsuojia

  • Syötteen validointi
  • WAF
  • Sanitointi (esimerkiksi ../ -sekvenssien poistaminen)
  • Latausten tallentaminen web-juuren ulkopuolelle
  • Tiedosto-oikeuksien koventaminen, chroot, jne.

Juurisyyn korjaus

Käytä pilvitallennustilaa (S3, Google Cloud Storage, jne). Älä käsittele tällaisia tiedostoja palvelimen levyllä.

Summaus

Kehittäjien ja tietoturva-asiantuntijoiden tulisi keskittyä rakentamaan turvallista koodia alusta alkaen ja korjata haavoittuvuudet niiden juurisyistä.

Vanhanaikaisiin keinoihin, kuten jokaisen parametrin "sanitointi" yksitellen tai "enkoodaus" manuaalisesti ei kannata turvautua liikaa, koska muutoin sovelluksessa kytee haavoittuvuus, joka ennen pitkää nostaa päätään.

Lisäsuojien käyttö on erittäin suositeltavaa. Mutta niitä tulisi käyttää suojaamaan tervettä koodia, jossa ei ole juurisyitä haavoittuvuuksille.

Vältä siis haavoittuvuuksia kitkemällä ne juuresta.

hakatemia pro

Valmis ryhtymään eettiseksi hakkeriksi?
Aloita jo tänään.

Hakatemian jäsenenä saat rajoittamattoman pääsyn Hakatemian moduuleihin, harjoituksiin ja työkaluihin, sekä pääset discord-kanavalle jossa voit pyytää apua sekä ohjaajilta että muilta Hakatemian jäseniltä.