Ohjelmistoturvallisuus - Johdanto

Tässä pääluvussa tutustutaan ohjelmistojen haavoittuvuuksiin ja tekniikoihin, joilla niitä voidaan tunnistaa ja estää tai vähentää haittavaikutuksia.

Ohjelmiston voi ajatella olevan turvallinen, kun se täyttää määritellyn tai ilmaistun tietoturvavaatimuksen. Tämä sisältää luottamuksellisuuden, eheyden ja saatavuuden vaatimukset järjestelmän toiminnalle ja datalle.

Kuvittele esimerkiksi sosiaalisen median ohjelmisto. Sille voidaan asettaa seuraavia vaatimuksia:

  • Käyttäjä voi muokata ainoastaan omia tietojaan. (Eheys)
  • Ainoastaan käyttäjän ystävät näkevät hänen tietonsa. (Luottamuksellisuus)
  • Ohjelmiston on oltava käytettävissä 99,9 % ajassa. (Saatavuus)

Vaatimukset voivat olla toisiaan kumoavia: Esimerkiksi eheysvaatimuksena voi olla järjestelmän sulkeminen hyökkäyksen tapahtuessa, mutta tällöin järjestelmä ei ole saatavilla.

Tietoturvapoikkeama on tilanne, jossa järjestelmä ei saavuta tietoturvatavoitetta. Haavoittuvuus eli tietoturva-aukko puolestaan voidaan määritellä niin, että se aiheuttaa tietoturvapoikkeaman. Tässä on oletuksena, että tietoturvatavoite on määritelty. Käytännössä monissa järjestelmissä ei ole määritelty tietoturvatavoitteita tai ne ovat monitulkintaisia. Tieturvatavoitteita voidaan tarkoituksella myös heikentää esim. käytettävyyden ja saavutettavuuden lisäämiseksi.

On siis myös sellaisia haavoittuvuuksia, joita ei ole sidottu tietoturvatavoitteisiin. Toki hyvin määritellyt tietoturvatavoitteet kattavat myös tällaisia toteutuskohtaisia haavoittuvuuksia, mutta näin ei aina ole ja silti ajatellaan, että toteutuskohtaiset haavoittuvuudet ovat todellisia haavoittuvuuksia.

Valitse oikeat vaihtoehdot

EU:n tietosuoja-asetuksen vaatimukset ohjelmistoille

Kuten aiempi luku jo kertoo, erityisesti EU:n tietosuoja-asetus säätelee henkilötietoja käsittelevien ohjelmistojen vaatimuksia EU:n alueella. Ne on siis otettava huomioon ohjelmistokehityksessä. Uusia henkilötietoja käsitteleviä ohjelmia kehitettäessä ja vanhoja korjattaessa on erittäin tärkeää, että ohjelmoijat ja heidän esihenkilönsä tietävät asetuksen vaatimukset tietosuojalle. Organisaation ylimmän johdonkin tulisi tietää, että asetuksen täyttäminen edellyttää resursseja ja että organisaatio on taloudellisessa vastuussa mahdollisesta asetuksen laiminlyönnistä.

Jos ohjelma on käytettävissä EU:n alueella, EU:n tietosuoja-asetus asettaa ohjelmistolle lukuisia vaatimuksia, kuten

  • Suostumuksen pyytäminen lain edellyttämällä tavalla.
  • Tietoturvaloukkausten raportointiin varautuminen sisältäen lokitietojen keräämisen ja hallinnan ja ilmoittamisen niille, joiden henkilötietoja on loukattu.
  • Varautuminen käyttäjien tekemiin tietopyyntöihin. Tietopyyntöjä varten käyttäjän henkilötiedoista voi muodostaa automaattisen raportin.
  • Osoitusvelvollisuuden täyttämiseen varautuminen.
  • Käsiteltävien henkilötietojen minimointi.
  • Henkilötietojen poistaminen, kun tiedolle ei ole enää tarvetta ja laillista perustetta säilyttää tietoa.
  • Erityisten tietojen käsittelyyn varautuminen tai tietojen keräämisen välttäminen.
  • Vaikutustenarvioinnin tekeminen, jos tälle on asetuksen mukainen vaatimus.
Tietosuojalainsäädännön vuoksi ohjelmistoa suunniteltaessa ja toteutettaessa on otettava huomioon:

Haavoittuvuuksien luokittelu

Haavoittuvuuksia voidaan luokitella eri tavoin, kuten yleisyyden, seurausten vakavuuden, seurausten luokittelun ja hyödynnettävyyden vaikeusasteen perusteella. Ennalta sovitulla luokittelulla saadaan haavoittuvuuksista yleisesti ymmärretty näkemys. CVE (Common Vulnerabilities and Exposures) on yksi yleinen tapa luokitella haavoittuvuuksia sekä samoin CWE (Common Weakness Enumeration). Suomessa haavoittuvuuksista tiedottaa ja niitä koordinoi Liikenne- ja viestintäviraston alaisuudessa toimiva Kyberturvallisuuskeskuksen CERT-toiminto.

Seuraavaan on tehty CyBOK-aineistoa mukaileva luokittelu haavoittuvuuksista. Luokitteluja on erilaisia eikä tämäkään ole kaiken kattava. Ajatus on kuitenkin kuvata yleisimmät toteutuskohtaiset haavoittuvuudet.

Muistinhallinnan haavoittuvuudet (syventävä)

Ohjelmoinnissa tarvitaan muistivarauksia ja osa haavoittuvuuksista liittyy niihin. Erityisen alttiita ovat kielet, joissa ei ole automaattista muistinvarausta, kuten C ja C++. Jos ohjelmoija käyttää muistia varatun muistialueen ulkopuolelta, voi tapahtua ylivuoto (buffer overflow), jossa toiminta on määrittelemätöntä. Ohjelma on haavoittuva, vaikka käyttöjärjestelmä estäisi ylivuodon.

Esimerkiksi jos taulukolle on varattu tilaa 10 alkiota varten, mutta ohjelma kirjoittaa 11 tai enemmän, tämä voi johtaa haavoittuvuuteen mainituissa ohjelmointikielissä. Haavoittuvuuden haittahyödyntäminen perustuu taulukon perässä olevien muistiosoitteiden muokkaamiseen siten, että hyökkääjän oma koodi tulee suoritukseen. Esimerkiksi ohjelman suoritus siirtyy taulukon sisälle, mutta on myös kehittyneempiä hyökkäyksiä. Tilanne on vaarallinen, jos hyökkääjä pystyy täyttämään ylivuotavan muistin esim. verkkolomakkeesta, URL-osoitteesta tai muulla syötteellä. Kääntäjään tai järjestelmään on saatettu toteuttaa suojauksia, mutta näihin ei pidä luottaa, vaan ohjelma tulee toteuttaa turvallisesti.

Muistin käyttöön voi liittyä muitakin haavoittuvuuksia. Muistia pitää varata, kun sen tarve vaihtelee ohjelman suorituksen aikana eikä ennalta tiedetä paljonko muistia tarvitaan. Esimerkkinä haavoittuvuudesta on, että ohjelma poistaa muistialueen varauksen, mutta kuitenkin ohjelmasta edelleen viitataan samaan vapautettuun muistialueeseen, jota hyökkääjä voi päästä nyt muokkaamaan.

Muistinhallintaan liittyvä ongelma voi tulla myös siten, että ohjelma varaa muistin, mutta siihen on pääsy myös muilla ohjelmilla. Esimerkkinä tästä voi olla varatun muistin tyhjentämättä jättäminen, tilapäisen muistialueen käyttö, selaimen evästeet (cookie), ympäristömuuttujat tai välimuisti (cache). Jokin toinen resurssi voi muuttaa tai lukea muistia. Muistinvaraus ja varauksen poisto voi tapahtua sinänsä oikein, mutta jos tallennettua tietoa ei ylikirjoiteta, luottamuksellista tietoa, kuten salasanoja, maksutietoja tai henkilötietoja voi paljastua.

Muistinhallinnan haavoittuvuuksia voi estää mm. koodikatselmoinneilla, testauksella ja ohjelman oikealla suunnittelulla, esim. käyttämällä taulukoita vain rajapintojen tai turvallisten ohjelmakutsujen kautta. Tämä tosin voi tapahtua suorituskyvyn kustannuksella.

Ohjelmointikielen valinnalla voi estää haavoittuvuuksia: esim. Java, Python, Perl, Rust ja Ruby sisältävät automaattisen muistinhallinnan, jolloin puskuriylivuodot ja muistivaraukset eivät aiheuta samalla tavalla ongelmia. Kieltä ei voi kaikissa tapauksissa valita, mutta myös kääntäjän valinnalla ja asetuksilla voi estää haavoittuvuuksia. Niissäkin suojauksia, jotka tunnistavat ja estävät haavoittuvuuksia. Täydellinen suoja tämä ei kuitenkaan ole, sillä kaikkia virheitä ei ole mahdollista tunnistaa.

Syötteen muodostamisen haavoittuvuudet (syventävä)

Ohjelmien täytyy usein muodostaa ajon aikana syötteestä uusia rakenteita, kuten kyselyjä. Tästä esimerkkeinä ovat SQL-kyselyt ja html:n käsittely. Esimerkiksi käyttäjän kirjautuessa ohjelmaan käyttäjätunnus ja salasanan tiiviste saatetaan hakea SQL-kyselyillä tietokannasta. Vastaavasti haettaessa tietoa verkkolomakkeella, haku voi tapahtua SQL-kyselyillä. Ongelma tulee, jos käyttäjä pystyy syöttämään SQL-komentoja, jotka ohjelma tulkitsee osaksi SQL-kyselyä. SQL-komennot voivat olla monimutkaisia ja pahimmillaan tällainen haavoittuvuus voi johtaa järjestelmän haltuunottoon, mutta lievemmilläänkin tiedon saaminen tietokannasta tai palvelunesto on mahdollista.

Ohjelmoijan tehtävä on huolehtia turvallisesta SQL-tietojen käsittelystä. Usein on mahdollista estää käyttäjän antaman syötteen tulkitseminen SQL-kyselyinä. Toisaalta, jollei estoa ole, haavoittuvuus on syntynyt.

Vastaavalla tavalla muissa syötteen muodostuksen haavoittuvuuksissa ohjelmoijan on tunnettava suojautumismekanismit. Ne ovat usein yksinkertaisia, mutta ne on erikseen osattava rakentaa ohjelmakoodiin. Toisin sanoen ohjelmoijan on oltava valveutunut ja seurattava myös haavoittuvuuksien kehittymistä.

Yksi syötteisiin liittyvä haavoittuvuus on puutteellinen tiedonkulun hallinta (“incomplete mediation”). Ohjelma välittää jonkin tiedon, parametrin, toiselle ohjelmalle, joka jättää tekemättä sellaiset tarkistukset, joihin se kykenisi. Vaarana ovat paitsi tietoa välittävän ohjelman tekemät virheet myös se, että ohjelma onkin hyökkääjän hallussa. Yksinkertainen esimerkki on selaimen lähettämä tilaus web-kauppaan. Vaikka tilaaja olisi autentikoitukin, vastaanottajan eli palvelimen ei pidä uskoa esim. tilauksen mukana kerrottuun loppusummaan, vaan se pitää laskea ja estää virheellinen tilaus.

Ohjelmointirajapinnan haavoittuvuudet (syventävä)

Ohjelmointirajapinta tarkoittaa ohjelmistojen tapaa kommunikoida keskenään. Ohjelma esimerkiksi kutsuu toisen ohjelman rajapintaa, jolloin kutsuttu ohjelma suorittaa kutsuvan ohjelman antamien määritysten perusteella ennakkoon ohjelmoidut toiminnot. Esimerkiksi puhelimessa ohjelma voi haluta laitteen sijaintitiedot ja puhelimen ohjelmistorajapintaa kutsumalla saa tämän tiedon, jos ohjelmiston ja järjestelmän oikeudet riittävät tiedon välittämiseen. Muussa tapauksessa rajapinnasta saatu vastaus kertoo, että sijaintitietoa ei voi antaa sekä ehkä syyn tähän.

Lähes kaikki ohjelmat käyttävät yhtä tai useampaa ohjelmointirajapintaa. Ohjelmiston suunnittelija saattaa tehdä oletuksia rajapinnan toiminnasta eikä siten osaa varautua rajapinnan poikkeukselliseen toimintaan, mikä voi johtaa haavoittuvuuteen.

Myös ohjelmointirajapinnan toteutuksessa voi olla haavoittuvuus. Ohjelmoijan on tärkeää selvittää, että rajapinnat toimivat turvallisesti sekä varautua mahdollisuuteen päivittää kutsuttava rajapinta, siltä varalta, että siitä löydetään haavoittuvuuksia.

Ohjelmointirajapinnan oikea käyttö on erityisen tärkeää toteutettaessa kryptografiaa, jotta se tulee tehdyksi kryptografisen standardin edellyttämällä tavalla ja turvallisesti. Käytännössä ohjelmoijan on oltava selvillä turvallisista algoritmeista, niiden toteutuksesta ja alustuksesta, eri moodeista ja niiden turvallisuudesta sekä kutsujen oikeasta järjestyksestä. Valmiita ratkaisuja on tietenkin käytettävä, jos ne on jo toteutettu turvallisesti. Jotkin rajapinnat osaavatkin huolehtia asiat suoraan oikein, mutta joissain jää paljon ohjelmoijan osaamisen varaan. Jotkin rajapinnat voivat kuitenkin sisältää virheitä tai käyttää heikkoa salausta, joten kryptografiaa käytettäessä on varmistettava rajapinnan ja toteutuksen turvallisuus.

Haavoittuvuudet kilpatilanteissa (syventävä)

Kun ohjelma käsittelee resursseja (esim. tiedostot, muisti ja tietokannat), jotka on jaettu rinnakkaisten toimijoiden kesken, voi syntyä haavoittuvuus kilpatilanteessa (race condition) Tätä kuvaa tarkistuksen ja käytön välinen aika (TOCTTOU, time-of-check-to-time-of-use). Perinteinen esimerkki: “Jos asiakas laskee sinulle setelit ja joudut kääntymään toisaalle kirjoittaaksesi kuitin, niin lasketa tai laske itse uudestaan ennen kuin annat kuittia asiakkaalle.” Tätä vastaava tilanne on yleisempi eheysongelma pääsynvalvonnan yhteydessä: oikeuksien tarkistaminen tehdään tietyllä hetkellä ja niiden mukainen operaatio jonkin verran myöhemmin. Jos oikeudet tällä välillä muuttuvat, voi tapahtua vahinkoja. Tietokannan tapauksessa peruste tietyn arvon kirjoittamiselle johonkin kenttään voi häipyä, jos joku muu ehtii muuttaa kentän arvoa ensin. Yhtenä esimerkkinä ajoitusongelma tulee myös, kun tarkistetaan varmenteiden voimassaoloa peruutuslistalta. Tämän vuoksi peruutuslistoja päivitetään melko tiuhaan.

Haavoittuvuuden kilpatilanteessa voi välttää ohjelman hyvällä suunnittelulla ja toteutuksella. Kilpatilanteet pitää ennakoida ja toteuttaa hyvien ohjelmointikäytäntöjen mukaisesti. Esimerkiksi jotkin ohjelmointikäskyt estävät kilpatilanteen syntymisen. Rinnakkaisuudessa oikeuksien hallinta tulee tärkeäksi, jotta kilpatilanne ei aiheuta ongelmia. Esimerkiksi muistialueen varauksen jälkeen mikään muu prosessi ei saa kirjoittaa muistialueelle.

Sivukanavahaavoittuvuudet (syventävä)

Sivukanavahaavoittuvuuksissa hyödynnetään varsinaisen ohjelmiston suorituksesta syntyvää ylimääräistä tietoa. Tämä tieto on tyypillisesti kohinaa, jota on mahdollista analysoida. Esimerkiksi prosessorin toimintaa on mahdollista analysoida laitteistolla tai ohjelmistolla. Prosessorin käsittelemää tietoa voi analysoida esim. tarkkailemalla suoritusaikaa tai virrankulutusta. Jos avaimen sisältö vaikuttaa seurattuun suoritusaikaan tai virrankulutukseen, on mahdollista saada selville avain tai sen osa.

Sivukanavahyökkäyksiltä suojaudutaan ohjelmiston oikealla suunnittelulla ja toteutuksella tekemällä analysointi vaikeaksi. Esimerkiksi suorituksen ajallisen analysoinnin voi estää toteuttamalla ohjelmiston käskyt niin, että salausavaimesta riippuvaa ajallista eroa ohjelman suorituksessa ei synny.

Sivukanavahyökkäyksien yhtenä muotona on tiedon lukemisen lisäksi ohjelman suoritukseen vaikuttaminen. Näitä voidaan kutsua viansyöttöhyökkäyksiksi (fault injection attacks). Vian voi aiheuttaa esim. lämpötilamuutoksilla, kellon muutoksilla tai sähkömagneettisella säteilyllä. Yhtenä esimerkkinä on ns. RowHammer-hyökkäys, jossa lukuisilla toistuvilla muistiviittauksilla saadaan aikaan bittien kääntymistä viereisillä DRAM-muistin riveillä.

Kokonaisvaltaiseen turvallisuuteen tarvitaan monimutkaisia määrityksiä (syventävä)

Edellä on käsitelty toteutuskohtaisia haavoittuvuuksia. Järjestelmää tulee kuitenkin tarkastella kokonaisuutena. Esimerkiksi toteutuskohtainen haavoittuvuus ei aiheuta kokonaisuutena ongelmaa, jos siihen on varauduttu muualla järjestelmässä. Tällöin on rakennettu turvakerroksia, joilla estetään yksittäisen haavoittuvuuden aiheuttama ongelma.

Voi olla myös niin, että vaikka järjestelmässä ei ole toteutuskohtaisia haavoittuvuuksia, järjestelmän tietoturvatavoite ei silti täyty. On tärkeää ottaa huomioon turvallisuus kokonaisuutena. Järjestelmä voidaan jakaa osajärjestelmiin, joiden turvallisuutta tarkastellaan myös osana kokonaisuutta. Esimerkiksi: Miten yhden osajärjestelmän ongelmaan varaudutaan kokonaisuutena ja miten osajärjestelmät tukevat turvallisuuden kokonaistavoitetta?

Toteutuskohtaisten haavoittuvuuksien turvallisuustavoite on helppo määritellä: joko se täyttyy tai ei täyty, esim. joko on haavoittuvuus tai sitä ei ole. On kuitenkin olemassa monimutkaisempia turvallisuusmäärityksiä. Näistä yksi on tietovuoturvallisuus (information flow security). Tällöin tietoturvallisuutta tarkastellaan tiedon siirtymisen näkökulmasta, jolloin kullekin tietovuolle määritellään julkisuus- ja luottamuksellisuusvaatimukset. Esimerkkinä vaatimuksista on, että luottamuksellinen syöte ei vaikuta mitenkään julkiseen tulosteeseen. Esimerkiksi hakemalla palvelusta myyntihintoja ei voi vaikuttaa siihen, mitä muut näkevät palvelujen julkisina myyntihintoina omissa hauissaan.

Tietovuomäärityksissä tulee arvioida myös syötteen toisteisuutta. Hyökkääjä voi esimerkiksi toistaa hakuja hieman eri muodoissa. Näin saatuja tietoja voi olla mahdollista yhdistellä siten, että luottamuksellisuusvaatimus rikkoutuu.

Haavoittuvuudet ovat vikojen osajoukko (syventävä)

Haavoittuvuuden käsite on hyödyllinen, mutta ei kata kaikkea ohjelmistoturvallisuutta. Ohjelmistojen turvallisuutta voi edistää hyvillä ohjelmoinnin periaatteilla, testauksella ja erityisesti korkean laadun vaatimusten täyttämiseksi myös todistamisella. Haavoittuvuudet voidaan nähdä vikojen (fault) osajoukkona. Vika puolestaan on virhe suunnittelussa tai toteutuksessa. Vikojen taksonomiaa eli tieteellistä luokittelua on kehitetty dependable computing -alueen tutkimuksessa.

Palautusta lähetetään...