Esimerkki oikeasta haavoittuvuudesta

Tietoturvatestauksessa voidaan usein huomata tilanteita, missä kehittäjät rakentavat tietoturvakontrolleja selaimeen, vaikka selainpuoli on täysin käyttäjän hallinnassa ja nämä kontrollit on aina mahdollista kiertää. Tämä johtuu yleensä siitä, että kehittäjät eivät ole välttämättä aina tietoisia siitä, mitä kaikkea käyttäjä kykenee tekemään suhteessa selaimen sekä sovelluksen väliseen yhteistyöhön.

Tämä piti paikkansa myös eräässä oikean elämän sovellustestauksessa, jossa kehittäjät luottivat rakentamiinsa tietoturvakontrolleihin, jotka lopulta eivät kuitenkaan riittäneet estämään testaajaa löytämästä ja hyväksikäyttämästä haavoittuvuutta sovelluksessa. Olemme rakentaneet vastaavanlaisen sovelluksen, jossa sovellus omaa saman haavoittuvuuden sekä samankaltaisen tietoturvakontrollin. Voit yrittää ratkaista labran itse tai jatkaa lukemista, sillä seuraavaksi analysoimme ja ratkaisemme labran. Löydät sovelluksen täältä.

Haavoittuvuuden löytäminen

Kuten oikean elämän tapauksessa, meillä on tässäkin lähdekoodit saatavilla, joten aloitetaan siitä. Koodista näemme helposti, että sovellukseen on ohjelmoitu neljä eri reittiä, jotka vastaanottavat HTTP-pyyntöjä.

  • /internal-api/v1/recipes_api (Sisäinen rajapinta)
  • /api/user/<user_mail> (Sisäinen rajapinta)
  • /varmista
  • / (juuri)

Kaksi näistä ovat niin sanottuja sisäisiä API-rajapintoja, sillä näihin on ohjelmoitu erillinen tarkistus, jolla sallitaan pääsy ainoastaan osoitteesta 127.0.0.1 (localhost). Eli ainoastaan sovellus itse kykenee suorittamaan kyselyitä näihin reitteihin.

Tarkistus, jolla sallitaan pääsy ainoastaan localhost-osoitteesta.

ip_address = flask.request.remote_addr
if ip_address not in ('127.0.0.1', '::1', 'localhost'):
    abort(404)

Lisäksi näemme koodista, että sovelluksessa on käytössä jonkin sortin apu-funktio, jonka avulla lasketaan jokin allekirjoitus ja suoritetaan vertaus-operaatio toiseen allekirjoitukseen. Jos laskettu allekirjoitus on sama kuin annettu allekirjoitus, funktio palauttaa "Tosi". Muuten funktio palauttaa "Epätosi".

Apu-funktio, joka laskee ja vertaa allekirjoituksia.

# Apufunktio joka laskee ja vertaa allekirjoituksen annettuun arvoon
def laskeAllekirjoitusJaVertaa(sposti, allekirjoitus):
  try:
    key = variables.secret
    hmac_value = hmac.new(key=key.encode(), msg=sposti.encode(), digestmod="sha256")
    calculated_signature = hmac_value.digest().hex()
    if calculated_signature == allekirjoitus:
      return True

    return False
  except Exception as e:
    print(e)
    return False

Katsotaan seuraavaksi, miltä verkkosivu näyttää, eli pistetään labra käyntiin ja ihmetellään sitä BurpSuite-ohjelmalla tarkemmin.

Sovelluksen etusivu palauttaa meille yksinkertaisen lomakkeen, jonka avulla käyttäjä voi liittyä sähköpostilistalle. Lähetetään lomake ja katsotaan BurpSuite-ohjelmasta, miltä kyseinen HTTP-pyyntö näyttää.

Lomakkeen lähettäminen suorittaa HTTP-pyynnön POST-metodilla /varmista-rajapintaa vasten. Panemme myös merkille otsakkeissa olevan, merkillisen X-Signature otsakkeen. Katsotaan jälleen tarkemmin koodista, miltä kyseisen rajapinnan logiikka näyttää.

Varmista rajapinnan koodi.

@app.route("/varmista", methods=['POST'])
def varmista():
  sposti = request.form.get("sposti")    
  allekirjoitus = request.headers.get('X-Signature')
  if laskeAllekirjoitusJaVertaa(sposti, allekirjoitus):
    resp = requests.get("http://127.0.0.1:5000/api/user/"+sposti)
    return jsonify(resp.json())
  else:
    return jsonify({'Allekirjoitus':'Virheellinen'}) 

Kyseinen rajapinta näyttää hyväksyvän vain POST-pyyntöjä. Rajapinta ottaa vastaan myös kaksi arvoa käyttäjältä, eli X-Signature-otsakkeen sekä annetun sähköpostin. Tämän jälkeen varmistetaan, että annettu X-Signature-arvo on sama kuin laskettu arvo, käyttämällä hyväksi apu-funktiota. Jos funktio palauttaa "Tosi", sovellus suorittaa itse HTTP-pyynnön sisäiseen rajapintaan. Sovellus vaikuttaa myös rakentavan kyseisen URL-osoitteen epäturvallisesti, nimittäin lisäämällä ainoastaan annetun sähköpostin URL-osoitteen perään. Sovellus on siis mahdollisesti haavoittuvainen URL-injektiolle.

Näemme myös koodista, että sovellukseen on rakennettu toinen sisäinen rajapinta, joka vaikuttaa palauttavan sensitiivistä tietoa (labran lipun).

@app.route("/internal-api/v1/reseptit", methods=['GET'])
def recipes_api():
    ip_address = flask.request.remote_addr
    if ip_address not in ('127.0.0.1', '::1', 'localhost'):
        abort(404)
    
    return jsonify({
        'reseptit': [
            {
                'Salainen resepti': [
                    'Sokeria',
                    'Jauhoja',
                    'Hakatemia lippu %s' % os.environ['FLAG']
                ]
            }
        ]
    })

Yritetään suorittaa URL-injektio-hyökkäys muokkaamalla sähköpostia siten, että kun sovellus rakentaa mainitun URL-osoitteen, osoittaa lopullinen osoite tuohon toiseen rajapintaan. Käytetään sähköpostina seuraavaa arvoa:

../../internal-api/v1/reseptit

Muutetun HTTP-pyynnön vastaus.

Sovellus palautti meille tiedon siitä, että allekirjoitus on virheellinen. Tutkitaan lisää.. Sovellus siis ottaa HTTP-otsakkeista X-Signature-arvon ja käyttää sitä apu-funktiossa, jossa lasketaan myös toinen arvo. Vaikuttaa siltä, että sovellukseen on rakennettu jonkin tason allekirjoitus-mekanismi, jonka toivotaan estävän hyökkääjiä muuttamasta HTTP-pyyntöjä manuaalisesti.

Allekirjoitus-mekanismi

Allekirjoitukset ovat kaikessa yksinkertaisuudessaan vain varmistusarvoja, jotka on laskettu käyttämällä tietoa, jonka koskemattomuus halutaan varmistaa. Tätä kyseistä varmistusarvoa voidaan sitten käyttää, kun halutaan varmistaa, ettei alkuperäistä tietoa ole peukaloitu. Varmistava osapuoli voi vain toistaa saman laskuoperaation ja verrata annettua allekirjoitusta omaan allekirjoitukseensa. Jos allekirjoitukset eroavat, tämä viittaa joko tiedon korruptioon tai peukalointiin, ja täten annettu tieto voidaan hylätä ilman jatkoprosessointia. Tässä mekanismissa on kuitenkin yksi vaatimus; molempien osapuolten täytyy nimittäin käyttää yhteistä salaisuutta, jota käytetään laskennassa. Ilman tätä, kuka vain voisi suorittaa saman laskuoperaation, eikä allekirjoitus-mekanismi loisi mitään suojaa hyökkääjiä vastaan. Eli jokainen allekirjoitus-mekanismia käyttävän tahon on tiedettävä yhteinen salaisuus, jolla varmistetaan, että sekä allekirjoitus kuin myös alkuperäinen tieto on varmasti autenttinen ja koskematon.

Allekirjoitus-mekanismin ohittaminen

Nyt, tarkkoina tietoturvatestaajina, ihmettelemme seuraavaksi miten on mahdollista, että laskettu allekirjoitus on asetettu selaimessa suoritettavaan HTTP-pyyntöön. Katsotaan tarkemmin, mitä sovellus oikein palauttaa selaimeen.

Aivan, eli sovellus suorittaa X-Signature-allekirjoituksen laskennan selaimessa. Näemme selkeästi JavaScript-koodista, että selaimessa lasketaan allekirjoitus käyttämällä annettua sähköpostiosoitetta ja täten myös paljastetaan käytetty avain/salaisuus. Tämä on merkittävä ongelma allekirjoitus-mekanismin tietoturvan kannalta, sillä kuka vain voi käyttää vuodettua salaisuutta ja laskea itse omat allekirjoitukset, jotka ovat täysin valideja varmistavan osapuolen näkökulmasta.

Nyt meillä on useita eri vaihtoehtoja miten toimia. Voimme esimerkiksi kirjoittaa oman ohjelman, joka suorittaa saman laskuoperaation, käyttää tuota salaisuutta ja laskee meille halutun allekirjoituksen. Tai voimme käyttää selaimen omia työkaluja ja hyväksikäyttää tuota jo olemassa olevaa JavaScript funktiota.

Avaamme selaimessa olevan JavaScript-konsolin ja laskemme uuden allekirjoituksen, käyttämällä alkuperäistä injektiota sähköpostikentän arvona.

Lopuksi yritämme suorittaa samaa URL-injektio-hyökkäystä korvaamalla X-Signature-arvon uudella allekirjoituksella.

Tsädäm! – Sovellus hyväksyi annetun sähköposti-arvon sekä tästä lasketun allekirjoituksen ja onnistuimme hyväksikäyttämään sovelluksessa piilevää URL-injektio-haavoittuvuutta.

Loppusanat

Vaikka labra on simuloitu, niin kyseessä on tilanne, joka tuli oikeassa elämässä vastaan. Oikeassa sovelluksessa oli samankaltainen URL-injektio-haavoittuvuus ja samankaltainen allekirjoitus-mekanismi, joka oli toteutettu epäturvallisesti. Kehittäjät paljastivat salausavaimen käyttäjälle JavaScript-koodissa ja tämän avulla oli mahdollista laskea itse omia allekirjoituksia, sekä täten hyväksikäyttää URL-injektiota.

Olemme rakentaneet myös muita tapoja ratkaista kyseisen labran, joten kokeile keksitkö miten se voisi luonnistua! – Vinkkinä, kannattaa keskittyä muihin selaimeen palautettuihin JavaScript-funktioihin, sillä näihin et tarvitse BurpSuite-ohjelmaa.

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ä.