Dette er en samling med oppgaver for å lære om frontendsikkerthet og selv kjenne på farene. Presentasjon tilhørende workshopen kan du finne her: https://docs.google.com/presentation/d/1KuSQ_mHXdbo8a52P6HKayrbymYorcrh3/edit?usp=sharing&ouid=118167277495517818275&rtpof=true&sd=true
Før vi kommer i gang med oppgavene skal vi sette opp et minimalt oppsett som vi kan bruke for å se sikkerhetshull i praksis.
Sørg for at du har node og npm installert (https://nodejs.org/en/download/) og klon dette prosjektet:
git clone https://github.com/JulieHillRoa/frontendsikkerhet.git
Gå så inn i mappen, installer npm pakkene og start applikasjonen:
cd frontendsikkerhet
npm install
npm start
Denne oppgaven går ut på å utforske noen av de sårbare elementene ved utvikling av en webapplikasjon. Oppgavene vil være basert på Reactjs, men sikkerhetshullene er ikke nødvendigvis kun tilfelle i React.
Rammeverket er i utgangspunktet ganske sikkert når det gjelder beskyttelse mot XSS-angrep.
Det er flere tiltak som blir gjort for å hindre angrep, som for eksempel å escape streng-variabler i views automatisk. Et annet tiltak er at
med JSX sender man en funksjon som event handler isteden for en streng som kan inneholde ondsinnet kode.
HTML:
<button onclick="onButtonClick()">Klikk her</button>
JSX:
<button onClick={ onButtonClick }>Klikk her</button>
Vi skal nå gå igjennom 4 oppgaver rundt fallgruver som webutviklere burde vite om.
Koden finner du i src/webapp
, les over koden til oppgaven og se om du finner noen måter å "hacke" den på. Dersom du står fast finner du hint lengre ned på siden, du kan bruke google eller selvfølgelig rekke opp en hånd for hjelp. Fasit eller løsningsforslag finnes også sammen med hintene.
Når du har utført en oppgave se info om problemet før du hopper videre til neste oppgave.
Key take-away fra ALLE oppgavene: Ikke stol på brukerne og ikke stol på brukerinput.
- Åpne /webapp/ in nettleseren, klikk på knappen: Oppgave1 og følg teksten på siden.
🕵️ Klarte du å få applikasjonen til å kjøre scriptet?
Som du sikkert opplevde går det ikke ann å skrive alert("hacked") direkte i feltene. Dette er fordi React escaper input og tolker det som tekst isteden for kjørbar kode. Dette beskytter oss på god vei mot onsinnede som prøver å utnytte våre inputfelt. Det man derimot ikke får like mye beskyttelse mot er å ta i bruk brukerinput rett i enkelte html-atributter som blir eksekvert når man klikker på elementet. I tilfeller hvor man får kjørt en alert("hacked") er ikke alert med en ufarlig streng ondsinnet i seg selv, poenget her er at dersom du får kjørt en alert får du kjørt mye annen skummelt JavaScript og kan i praksis ta ned hele nettlesere.- Åpne /webapp/ in nettleseren, klikk på knappen: Oppgave2 og følg teksten på siden.
🕵️ Klarte du å få applikasjonen til å kjøre scriptet?
I likhet med oppgave 1 hjelper React oss med å escape og encode enkelte tegn og input som f.eks <script>-tags. Fordi dangerouslySetInnerHTML setter input direkte på DOMen er det likevel ikke alt React hjelper oss med: Som f.eks events på HTML-attributter. Man skal aldri stole på brukerinput og man burde generelt tenke seg om flere ganger før man bruker denne funksjonen eller lar brukere manipulere DOM'en direkte.En måte å beskytte seg litt mer fra angrep er å Sanatize dataen før den blir eksekvert. Dette finnes det forskjellige pakker som hjelper deg å gjøre. Blandt annet DOMPurify som i vårt eksempel ville fjernet
onerror=alert("Hacked!")
delen av
<img onerror=alert("Hacked!") src="feil">
og etterlatt den slik:
<img src="feil">
2b. Installer og implementer DOMpurify i koden til oppgave 2 og se hvordan markup som blir skrevet inn i textArea vil bli endret etter en vask (Sanatizing).
- Åpne /webapp/ in nettleseren, klikk på knappen: Oppgave3 og følg teksten på siden.
🕵️ Klarte du å få applikasjonen til å kjøre scriptet?
I javascript finnes det en funksjon: eval(). Denne evaluerer koden som blir sendt inn som også vil si at koden blir kjørt. Ved å gjøre en logisk operasjon her kan man også få kjørt ondsinnet kode noe som gjør at det kan være stor fare for et XSS-angrep. Det vil derfor være lurt å finne andre alternativer til å evaluere koden - ikke bruk eval()- Åpne /webapp/ in nettleseren, klikk på knappen: Oppgave4. Prøv å se om du kan få siden til å kjøre
alert("Hacked")
.
🕵️ Klarte du å få applikasjonen til å kjøre scriptet?
Her bruker man localStorage. Dette kan være et nyttig verktøy å bruke, men det er veldig lett å manipulere. Hvem som helst kan manipulere localStoragen om man har tilgang til browser-vinduet. Det er derfor viktig å gjøre tiltak som escaping og encoding på denne dataen før man tar den i bruk.Et annet stort sikkerhetshull i denne oppgaven er bruken av spread props (...props). Det vil si at man bare sender det som ligger i props nedover treet i steden for å hente ut spesifikke properties man trenger i den spesifikke komponenten. Dette gjør det mulig å sende inn komponenten i vårt eksempel som tar i bruk dangerouslySetInnerHTML.
💡 Hint 1
Ikke forvent at alert-koden blir kjørt før linken er klikket på. Er det noen måte å kjøre javascript-kode på når du er inne i en a-tags href-attributt?
🚨 Løsningsforslag 1
Én fasit: javascript:alert("Hacked!")
Dersom man kommer på en side som validerer mot javascript:
kan man sende inn base64: f.eks <script>alert("Hacked!");</script>
encodet:
data:text/html;base64,YWxlcnQoImhhY2tlZCEiKQ==
Edit: De nyeste versjonene av nettleserne har nå satt en stopper for at man kan navigere til data-url'er. Selv om de nyeste nettleserversjonene kommer med fler sikkerhetstiltak mot sikkerhetshull, forteller alternativet med base64 at det finnes farer selv med valideringer. Om man validerer mot spesifikke ting (f.eks sjekk på "javascript" i start av url) for å beskytte seg selv, kan en angriper muligens finne andre måter å oppnå det samme. Det er derfor alltid bedre å spesifisere hva som er lov enn å spesifisere hva som ikke er lov.
💡 Hint 2
Her brukes dangerouslySetInnerHTML til å bytte ut innholdet.
Heldigvis vil ikke script-tager bli kjørt hvis man setter de inn med dette attributtet. Det var det første jeg prøvde også. Men det finnes attributter som blir kjørt når spesielle hendelser skjer, vet du om et slikt?
🚨 Løsningsforslag 2
Én fasit: <img onerror=alert("Hacked!") src="feil">
💡 Hint 4.1
Her brukes eval til å hente ut verdiene i et objekt.
const getSvaret = () => {
input && setSvar(eval('gjest.' + input ))
};
Kan du sende inn noe i inputfeltet slik at den fortsetter å lese kode etter at han har funnet eller ikke funnet propertien til gjest?
💡 Hint 4.2
Pst. Du husker kanskje at logiske operatorer i JavaScript leses fra venstre til høyre?
🚨 Løsningsforslag 4
Dersom man velger en property som finnes kan man skrive inn: navn && alert("hacked!")
eventuelt kan du skrive: hvaduvil || alert("hacked!")
Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#Never_use_eval!
💡 Hint 5
Det kan se ut som at tekstfeltet laster data fra localStorage. Tekstfeltet er også veldig dynamisk, det ser nesten ut som at man
kan sende inn helt vilkårlige props. Det er spesielt en prop som utvikleren er veldig stolt av, hva skjer om den f.eks. endres
til å være en div
? Kan det da være mulig å låne triks fra tidligere oppgaver?
🚨 Løsningsforslag 5
Her er det ingen validering av props lagret i local storage, vi kan f.eks. gi inn i dev console og lagre følgende:
{
"value": "Oops",
"element": "div",
"dangerouslySetInnerHTML": { "__html": "<img src='asdfasdf' onerror='alert(\"Hacked\")'>" }
}
Ref. https://medium.com/dailyjs/exploiting-script-injection-flaws-in-reactjs-883fb1fe36c1
Sjekk hobbyprosjekt eller jobbprosjekt om noen av disse sårbarhetene finnes.
Gå inn på Hacker101 og jobb med en CTF etter ditt nivå. Gjerne hvor skills er web.
Kilder:
For å lære mer om spesifikke tips for å unngå XSS angrep, se: XSS cheat sheet
For å lære mer om spesifikke tiltak mot CSRF se: CSRF cheat sheet
For å lære mer om sikkerthet i HTML5 se: HTML5 security cheat sheet
Denne delen er bygget opp slik at du for hvert steg får mer informasjon som etterhvert leder deg til to sikkerhetshull som vi har lagt inn i applikasjonen. Begge hull gir brukere mulighet til å utføre stored XSS-angrep. Se an tiden, ikke bruk for lang tid på å lete i steg 1, hopp videre til neste steg hvis du setter deg fast.
- Åpne /npm/ in nettleseren, prøv ut løsningen, eksperimenter litt for å se om du klarer å lure inn en kodesnutt (kjørt en alert("Hacked")
- Let gjennom kildekoden
/src/npm/
for å finne potensielle sikkerhetshull. Ta en ekstra kikk på pakker man tar i bruk. - Kjør
npm outdated
og se om det er pakker som bør oppdateres - Kjør
npm audit
og se om du klarer å benytte informasjonen derfra til å utføre et XSS-angrep. - Gå inn på https://snyk.io/vuln/ og søk opp pakkene som brukes i dette prosjektet (eller installer
snyk
og kjørsnyk monitor
) - Fiks problemene du har funnet og aktiver audit slik at den kjører ved
npm install
💡 Hint 1
Du kan bruke informasjonen fra https://snyk.io/vuln/SNYK-JS-MARKDOWNTOJSX-174624 til å lure inn HTML-kode i meldingsfeltet.
💡 Hint 2
Det er mulig å legge inn et felt, f.eks. navngitt href
i prototype for alle objekter ved å benytte svakhet i lodash,
trykk på lenken du får opp fra npm audit
.
💡 Hint 3
Det ryktes at backend på denne applikasjonen ikke har helt optimal validering. Det er lov å kalle API-et fra postman eller curl.
🚨 Løsningsforslag 1
Pakke: markdown-to-jsx
Finn rapportert sikkerhetshull på https://snyk.io/vuln/SNYK-JS-MARKDOWNTOJSX-174624 .
Send inn <SCRIPT>alert(1)</SCRIPT>
i meldingsfeltet.
🚨 Løsningsforslag 2
Pakke: lodashFinn rapportert svakhet med npm audit
og benytt prototype pollution til å legge inn href
-verdi.
curl 'http://localhost:3000/api/message' \
--data '{"content": "Trykk på hjelp","constructor":{"prototype":{"href": "javascript:alert(1)"}}}' \
--header 'Content-Type: application/json'
Kjør npm audit
på eget prosjekt og vurder resultatet.
Søk opp pakker på https://snyk.io/vuln/ se om du finner noe spennende (finner du f.eks. en "Malicious Package" som du kunne ha installert uten å tenke over det).