Statisk kildekodeanalyse (på utenlandsk også kjent som Static Application Security Testing – SAST) betyr å passivt søke gjennom kildekode uten å kjøre den. Det kan inngå som en del av såkalt hvitboks-testing, hvor vi ser på koden mens den er “i ro”; primært dreier det seg om å studere kildekode, men i enkelte tilfeller kan det også omfatte byte-kode (typisk: Java) eller binærkode. Resultatene er ofte i form av en generert rapport, og statisk analyse er ofte integrert i utviklingsmiljøer (IDE, f.eks. Eclipse) og/eller i verktøy for kontinuerlig integrasjon/utrulling (CI/CD).
Svakheter vs. Sårbarheter vs. Exploits
Svakheter i programvare er implementasjonsfeil i kode, design eller arkitektur som – hvis de ikke rettes opp – kan resultere i at systemer og nettverk er sårbare for angrep. Mitre vedlikeholder en liste av slike svakheter kalt Common Weakness Enumeration (CWE); eksempler omfatter buffer som flyter over, formatstrenger, struktur- og validitetsproblemer, kodeevaluering og injisering, autentiseringsfeil, ressursstyringsfeil, og utilstrekkelig verifikasjon av data. CWE ble opprettet for å tilby et felles språk for å beskrive svakheter i programvare, og være en felles målestokk for verktøy som retter seg mot slike svakheter.
En sårbarhet i programvare er en svakhet som kan utnyttes direkte av en ondsinnet person (eller ondartet programvare) til å få tilgang til et system eller nettverk. Sårbarheter dokumenteres i Mitres Common Vulnerabilities and Exposures (CVE).
Et Exploit (her savner jeg et godt norsk ord) er et stykke programvare som inneholder kode som direkte utnytter en sårbarhet i et system. Det dreier seg altså her om konkrete angrepsvektorer.
Det som er viktig å skjønne, er at statisk analyse fokuserer på svakheter. Dette betyr også at de fleste verktøy sliter med falske positiver; ikke alt et statisk analyseverktøy klager på vil nødvendigvis være sårbarheter som må fikses.
Hvorfor statisk kildekodeanalyse for sikkerhetsrevisjon?
Statisk kildekodeanalyse gjør det mulig å finne feil tidlig i programvareutviklingssyklusen, og er et betydelig bidrag til kodegjennomgang. Hvis du har planer om å håndtere kredittkortdata, må du forholde deg til spesifikasjonen i PCI-DSS, og ett av kravene her er å utføre kodegjennomgang. Verktøy for statisk kodeanalyse kan oppdage sårbarhetstyper som dynamiske verktøy ikke er i stand til å finne, og ettersom de er automatiserte, kan SAST elegant skalere til hundrevis eller tusenvis av applikasjoner på en måte som er ganske enkelt umulig med manuell analyse alene.
Selv når man begår smidig utvikling, er det ofte et visst minimum av struktur. På en eller annen måte må man formulere krav til det som skal utvikles, herunder sikkerhetskrav. For å finne sikkerhetskrav, må man gjerne utføre en form for risikovurdering. All programvare har en arkitektur, også i de tilfellene den ikke er eksplisitt definert. Derfor er det gunstig å gjøre designarbeid, og i tillegg se på sikkerhetskrav på designnivå. Som tidligere nevnt er trusselmodellering en nyttig øvelse for å avdekke svakheter som kan avbøtes med sikkerhetskrav.
Når man så endelig slipper til med den faktiske implementasjonen eller kodingen, er det viktig at god praksis for sikker koding følges. Statisk analyse kan hjelpe til med å avdekke avvik fra slik praksis, og kan på dette planet også gjerne integreres med utviklingsmiljøet for å gi umiddelbar tilbakemelding på uheldige programmeringsvalg. Når hele moduler er ferdige, kan testverktøyene gjøre mer avansert sårbarhetsanalyse, og kjørende kode kan også utsettes for fuzzing, hvor alle mulige og umulige inngangsverdier kan prøves ut.
Til slutt skal jo den ferdige versjonen rulles ut, og da er det betimelig med en gjennomgang av konfigurasjonen på tjeneren, samt gjennomgang av konfigurasjonen av nettverket.
Statisk analyse og forsvarlig programmering
Statisk analyse er velegnet til å sjekke at koden foretar ordentlig validering av data som leses inn, og at data som skrives ut kodes forsvarlig. Vi kan også sjekke at sikre applikasjonsprogrammeringsgrensesnitt (APIer) brukes riktig, og at APIer som evt. er usikre unngås. I fortsettelsen av dette, kan man sjekke at variabler behandles på en sikker måte, dvs. at det ikke går an å skrive en streng inn i en variabel som er ment å være et heltall. Dette håndteres automatisk av mange programmeringsspråk, men det finnes fortsatt anvendelser for mer risikable språk som C og C++, og i slike tilfeller kan statisk analyse være en redningsplanke.
Det går også an å sjekke hvordan autentisering og passordhåndtering gjøres, og at kryptografi brukes på korrekt måte. Man kan foreta generell sporing av potensiell sensitiv informasjon som kan ha konsekvenser for brukernes personvern. Her ser vi at statisk analyse også kan bidra til innebygget personvern.
På utenlandsk heter det at “djevelen er i detaljene”, og når man driver med avansert programmering er det mange detaljer som må gjøres riktig når man skal håndtere flere sesjoner (både samtidig og etter hverandre), samt generelle utfordringer med samtidig databehandling (bl.a. det som refereres til som TOCTOU, hvor tidspunktet for sjekk av verdier avviker fra tidspunktet de brukes) og administrasjon av minne. Statisk analyse kan nøste opp i mange slike utfordringer.
Et viktig område er hvordan feil og unntak håndteres, noe som henger tett sammen med logging og revisjon. Statisk analyse kan vurdere om filhåndtering gjøres på en forsvarlig måte, og kan også bidra til evaluering av hvordan konfigurasjonsstyring foretas.
Sikkerhetsrevisjon kontra “kodelukt”/stil/kodingsstandarder
Advarsel: Det å kjøre et verktøy vil i seg selv ikke gjøre deg “sikker”! Statisk analyse kan brukes for sikkerhetsrevisjon, men det er fullt mulig å bruke det for kun sjekke at koden føyer seg etter en gitt kodestandard. Elegant kode er ikke det samme som “sikker” kode, på samme måte som vedlikeholdbarhet ikke er det.
På arkitekturnivå, kan statisk analyse nøste opp i avhengigheter mot biblioteker, og dessuten kjeder av avhengigheter – A avhenger av B som så avhenger av C, osv. Duplisering av funksjonalitet kan være litt mer krevende å oppdage, men statisk analyse vil kunne avdekke om det er store blokker med identisk kode forskjellige steder i systemet. Hvis man antar at disse skal være identiske, vil vedlikehold fort kunne bli et mareritt, men det kan også bli et sikkerhetsproblem dersom noe endres bare ett sted, og at antakelser om den andre koden derfor ikke holder (ikke gjør antakelser!).
Statisk analyse er svært velegnet til å verifisere at kodingsregler følges (i den grad de kan formuleres formelt); f.eks. det å verifisere at metoder/funksjoner ikke overskrider en viss størrelse er jo trivielt. Det samme gjelder å telle antall kommentarlinjer (men å verifisere at kommentarene faktisk gir mening er ikke fullt så enkelt). Kompleksitet regnes ofte å være negativt for programvaresikkerhet, ettersom kompleks kode er vanskelig å verifisere; statisk analyse kan gjøre en rekke standard beregninger av kompleksitet, og advare dersom det overskrider en gitt grense for den aktuelle metrikken.
Teknikker brukt av et statisk kodeanalyseverktøy
Statisk kodeanalys har en lang historie, og dekker et vidt spekter av teknikker. I sin enkleste form har det et linjeorientert fokus, dvs. leter gjennom kode linje for linje vha. “grep” eller tilsvarende for å finne farlige funksjonskall. Eksempelvis kan regelen være at C-funksjonen gets() er uønsket, og hvert tilfelle man finner av gets() i koden flagges som en potensielt sårbar funksjon. Dette kan være nyttig for en kjapp gjennomgang, men er svært grunnleggende og vil generere masse støy.
Statiske analyseverktøy er komplekse; for å fungere skikkelig krever det at de har en semantisk forståelse av koden, avhengigheter, konfigurasjonsfiler, og mange andre bevegelige deler som ikke engang trenger være skrevet i samme programmeringsspråk.
For å sitere Rohit Sethi (fritt oversatt): “De må gjøre dette samtidig som de sjonglerer hastighet med nøyaktighet, og redusere antallet falske positiver til et tilstrekkelig lavt nivå slik at verkøyet i det hele tatt er brukbart. Effektiviteten utfordres av dynamisk-typede spark som JavaScript og Python, hvor det å inspisere et objekt ved kompilering ikke nødvendigvis er nok til å avgjøre hva slags klasse/type det er. Dette betyr at mange programvaresikkerhetssvakheter er enten upraktiske eller umulige å finne.” (I Understanding Strengths and Limitations of Static Analysis Security Testing (SAST), Rohit Sethi 2015)
En mer sofistikert tilnærming innebærer å bygge opp en modell for kildekodeanalyse. Først vil man foreta leksikalsk analyse, hvor man deler koden opp i symboler (tokens) for å identifisere språkkonstruksjoner korrekt. Uviktige symboler (mellomrom, kommentarer etc.) fjernes. Etter den leksikalske analysen kan må så ta fatt med semantisk analyse, hvor man sjekker representasjonen av hvert symbol for mening. Til slutt gjør man kontrollflytanalyse, hvor man analyserer de forskjellige stiene programmet kan ta gjennom koden, noe som resulterer i en eller flere kontrollflytgrafer som representerer alle mulige dataflyt-stier.
Skjending for sikkerhetsrevisjon
Vi kan bruke “skjendingsanalyse” (taint analysis) for å avgjøre hvor sårbarheter oppstår ved å bruke konseptene datakilde (data source) og dataende (data sink) i en dataflytgraf. Hvis kilden til inndata ikke er tilitsverdig, sies den dataen å være skjendet (tainted). Dette omfatter wevparametre og infomasjonskapsler; data fra filer, databaser eller vevtjenester; og miljøvariable (environment varables). Hvis skjendet data når en sårbar ende, kan vi ha fått et sikkerhetsproblem i fanget. Skjending kan stamme fra nettverket, fra en konfigurasjonsfil, eller fra en database.
Vanlige fallgruber
Dersom man kjører koden gjennom statisk analyse og ikke finner noen sårbarheter, kan det være fristende å slå seg på brystet og erklære seg “ferdig” med programvaresikkerhet – og dessuten hevde at det ikke er behov for sikkerhetsinnsats eller evaluering i andre utviklingsfaser. En annen fallgrube kan være at ledelsen kan komme til å resonnere som følger: “Nå har vi investert vanvittig mye i statiske analyseverktøy, så derfor kan vi ikke bruke mer ressurser på sikkerhet!”
Et eksempel fra virkeligheten
Bakgrunnen var en programvarepakke for å håndtere forskjellige aktiviteter i en virksomhet (kontakter, leverandører, fakturaer, bestillinger, lager, kundehåndtering (CRM). Denne pakken var åpen kildekode, og hadde 27000 nedlastinger per måned. Det var over 60 utgivelser siden 2008, og var et veldig aktivt prosjekt med over 120 bidragsytere.
Noen hadde kommet på å bruke utvalgte skybaserte analyseverktøy for å søke gjennom prosjektets kildekode, og de fant mange tilfeller av SQLi og XSS. De tre verktøyene de brukte hadde imidlertid en stor grad av variasjon i resultater:
SAST1: Ingenting
SAST2: Diverse (~10)
SAST3: Masse (~69)
Dette bygger opp om resultater fra Laurie Williams, som konkluderte at en enkelt teknikk ikke er nok.
Inspirert av dette kan vi prøve å håndtere svakhetene som ble identifisert ved å implementere et filter, og deretter sørge for at filteret lastes inn som en del av alle moduler:
<?php
require(‘../util.inc.php’);
…
For å teste at dette fungerer for et program som brukes til å søke om permisjon, kan vi så prøve å skrive inn følgende enkle XSS-angrep i begrunnelsen for permisjonsskjemaet:
<script>alert(“Reflect-My-XSS”);</script>
Vi får da svaret “Access refused by SQL/Script injection protection in util.inc.php (type=0 key=description value=<script>alert(“Reflect-My-XSS”);</script> page=/…/…/…/file1.php)“, noe som forteller oss at filteret gjorde jobben sin.
Vi kan imidlertid gjøre angrepet hakket mer sofistikert, ved å skrive inn <img src=”/../../../../img/info.png” onload=”alert(document.cookie)”>. Denne koden laster inn et bilde, og utfører en kommando samtidig med at bildet lastes inn. Filteret vårt stopper “onerror”, så det er nødvendig å bruke et bilde som faktisk finnes på systemet, men det er ikke så vanskelig å finne når man kan studere HTML-kildekoden. I eksempelet vårt ble dette IKKE luket ut av filteret, og resultatet er at angriperen får tak i sesjons-IDen til administratoren som skal godkjenne permisjonen, og dermed sannsynligvis mulighet til å utføre kommandoer på vegne av denne.
Feltstudier hos Telenor Digital
Bisera Milosheska utførte et studium av Telenor Digital i 2017 for å se hvordan statisk kildekodeanalyse ble brukt der. Arbeidet ble utført i tre faser; først intervjuer med utviklere for å avdekke deres utfordringer og erfaringer, deretter uavhengig vurdering av et utvalg verktøy for å legge til rette for et informert valg, og til slutt innhenting av tilbakemeldinger etter at verktøy er rullet ut.
Utviklerne ble spurt om hvilke utfordringer de så i bruk av statisk analyse, hvilke analyseverktøy de bruker i dag, og om de har brukt disse verktøyene for å avdekke sikkerhetssvakheter. Svarene tydet på at det var bekymring rundt at verktøyene rapporterer svakheter som egentlig ikke er problemer (falske positiver), og selv om de allerede hadde tatt verktøyet SonarQube i bruk, så ble det ikke brukt spesifikt for sikkerhetssvakheter.
Blant de rapporterte utfordringene kan vi nevne oppsett av verktøyet (i det hele tatt få det til å virke), samt hvor invaderende/forstyrrende det var; varsler kunne være distraherende og ødelegge flyten i programmeringsarbeidet, og verktøyet var ikke alltid forenelig med den fremherskende utviklingsprosessen. Det ble også opplevd som tidkrevende, ikke bare for å håndtere falske positiver, men også ved rapporter om trivielle saker. Graden av rigiditet ved tolkning av enkelte regler var også opplevd som et problem. På mer generelt grunnlag var det et vidt spekter av programmeringsspråk i bruk, og vanskelig å finne ett verktøy som kunne håndtere alle aktuelle språk. Til slutt ble det opplyst at i den totale kodebasen var det en betydelig teknisk gjeld, og selv om verktøy korrekt kunne identifisere sikkerhetssvakheter, var det ikke gitt at disse kunne korrigeres.
Utviklerne nevnte flere ønskede egenskaper, hvorav verktøyets egnethet til å identifisere “virkelige problemer” ikke var overraskende. Et spesifikt ønske var muligheten til å angi at deler av koden ikke skulle sjekkes i et gitt tilfelle. Utviklerne ønsket et verktøy som var mest mulig automatisert og enkelt å bruke, og som ikke krevde tung interaksjon. Rapportene måtte utformes på en måte som utviklerne kunne forstå.
Gevinster av statisk kodeanalyse ble oppgitt som ikke bare forbedret sikkerhet, men også forbedrede programmeringsferdigheter. Bruk av verktøyet ble opplevd som en læringsprosess, og umiddelbar tilbakemelding på feil ble sett på som positivt. Utviklerne mente de lærte mer om det aktuelle programmeringsspråket, og om de interne egenskapene til plattformen. Verktøyet bidro også til å skape en oversikt over ytelsesmetrikkene til utviklergruppen.
Evaluering
NIST har laget et testsett (Software Assurance Reference Dataset – SARD) for å sammenligne korrektheten til forskjellige statiske analyseverktøy, basert på Common Weakness Enumeration(CWE). Dette gjør det mulig å foreta uavhengig testing som innspill til å velge statiske analyseverktøy. Testsettet inneholder syntetiske “dårlige” og “gode” filer og funksjoner. De dårlige inneholder faktiske svakheter/sårbarheter, mens de gode skal ikke inneholde utnyttbare svakheter eller sårbarheter.
Resultatene havner i kategoriene:
- Sann positiv (True Positive – TP) – Verktøyetrapporterer korrekt svakheten som er målet for testen
- Falsk positiv (False Positive– FP) – Verktøyetrapporterer en svakhet som er målet for testen, men svakheten rapporteres i “god” kode (som ikke inneholder svakheten).
- Falsk negative (False Negative – FN) – Dette er ikke et resultat fra verktøyet, men er summen av “dårlige” filer/funksjoner som ikke ble funnet av verktøyet
Forholdet mellom antall sanne positive og totalt antall “dårlige” tilfeller i testsettet kalles gjenkallelse (Recall), dette er altså andelen av mulige svakheter som faktisk ble funnet. Presisjonen til et verktøy er antall sanne positive delt på summen av rapporterte sanne positiver og falske positiver (TP/(TP+FP)). Diskrimineringsraten er gitt av antall ganger verktøyet korrekt identifiserer svakheter, samtidig som svakheten ikke feilaktig finnes i “god” kode.
Verktøyene ble evaluert vha. Juliet_Test_Suite_v1.2_for_Java, med totalt 25 477 testtilfeller som til sammen dekket 112 CWEer og 841 feiltyper. Disse dekket 11 av SANS Top 25; 10 av de gjenværende omhandlet designforhold som ikke kan avdekkes med statisk analyse, og 2 var ting man ikek finner i javakode.
Verktøy har ulike styrker og svakheter når det kommer til å finne forskjellige klasser av svakheter/sårbarheter. LapsePlus var best for å finne feil relatert til filhåndtering, mens det kommersielle verktøyet var i stand til å detektere informasjonslekkasje, noe ingen av verktøyene som var åpen kildekode klarte. Man kan forbedre gjenkallelse (recall) ved å integrere og sammenstille flere forskjellige verktøy, men en begrensing kan være hvordan man skal håndtere falske positiver fra flere integrerte verktøy.
I 2011 foretok National Security Agency (NSA) Center for Assured Software en evaluering av hvordan 7 kommersielle og 2 åpen-kildekode verktøy håndterte Java Test Suite. Gjennomsnittlig gjenkallelse (recall) varierte avhengig av hvilke typer svakheter det var snakk om, men den beste var bare litt over 0,5, og de fleste var dårligere enn 0,3. Gjennomsnittlig diskrimineringsrate var vesentlig dårligere , fra mindre enn 0,3 ned mot 0,02.
Valg av løsning
Når man skal velge løsning, er ikke alle egenskapene fødtr likeverdige – enkelte er viktigere enn andre. Kostnad til verktøyet er overraskende lite viktig, mens ytelse og mulighet for integrasjon med eksisterende plattform er av høy viktighet. En del krav er implisitte, i den grad at hvis de ikke er tilfredsstilt, vil ikke verktøyet komme i betraktning i det hele tatt.
Metrikk | Viktighet (Høy, Medium, Lav) |
Ytelse | Høy |
Integrasjon | Høy |
Kostnad | Lav |
Installasjon | X (Implisitt) |
Støtte | X (Implisitt) |
Brukervennlighet | X (Implisitt) |
Språk | X (Implisitt) |
Kommersiell kontra åpen kildekode
Selv om kostnaden til verktøyet er av mindre viktighet for store virksomheter, kan lisenskostnader være avgjørende for SMBer eller oppstartsbedrifter. Det finnes også nettskybaserte løsninger, men både personvern og konfidensialitetsbetraktninger kan gjøre slike løsninger mindre attraktive – det er uansett viktig å tenke seg godt om! Kvaliteten til verktøyet er selvfølgelig viktig, men når det gjelder ytelse er det ikke nødvendigvis slik at dess mer du betaler, dess bedre blir det. Det er uansett helt klart at også fritt tilgjengelige verktøy kan ha en betydelig verdi! Til syvende og sist er det viktig å vite hvem det er som skal bruke verktøyene.
Utfordringer og begrensninger
Falske negative er åpenbart ikke gunstig, ettersom det betyr at svakheter som finnes i koden ikke blir oppdaget. Imidlertid kan det nor det gjelder brukbarheten av verktøyet være vel så ille med falske positiver, ettersom utviklerne da må kaste bort masse tid med å lete etter svakheter som ikke finnes.
Som tidligere nevnt kan det være utfordrende å bygge opp modellen når man har situasjoner som at dynamiske strenger bygges når kode kjøres (da må man bevege seg fra statisk til dynamisk analyse, som er noe helt annet). Det er ikke sikkert at statisk analyse klarer å detektere at du har implementert et filter for å håndtere enkelte svakheter, og kan heller ikke nødvendigvis verifisere om filteret er sterkt nok.
Det ligger i statiske analyseverktøys natur at de ikke kan avdekke forhold som er relatert til miljøet koden kjører i. Gitt at statiske analyseverktøy fokuserer på svakheter/sårbarheter i kodefeil, kan de også bare dekke rundt halvparten av sikkerhetsfeil i programvare; designfeil og arkitekturproblemer kan generelt ikke avdekkes av statiske analyseverktøy. Dette omfatter slikt som (u)fullstendig megling (mediation) (dvs. at alle elementer som ikke skal være åpne for alle faktisk krever autentisering før tilgang) og autorisasjon, minste privilegium, ansvarsseparasjon (separation of duty), dybdeforsvar (inkl. multifaktor-autentisering), om ting feiler på en trygg måte (hvis det feiler), og revisjon (hvor mye data logges egentlig?).
Statisk analyse kan vanligvis ikke gi noe definitivt svar på hvor vanskelig det kan være å utnytte den aktuelle svakheten/sårbarheten, og vil ikke nødvendigvis gi et nøyaktig bilde av risikoen. Til tross for alle disse forbehold, må vi likevel si at statisk analyse er en av de viktigste hjelpemidlene vi har for å nærme oss målet om sikrere programvare, og vi trøster oss med at utviklingen går videre!
Illustrasjon ved Chokniti Khongchum fra Pexels