LimeLee 2019. 7. 17. 21:25

소스코드 분석

<?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__);
?>

1. $db = mssql_connect(); > DB환경이 mssql

 

2. if($result['pw'] === $_GET['pw']) solve("nessie"); > 엄격한 비교.pw 값을 알 필요 있음.

 

3. if(sqlsrv_errors()) exit(mssql_error(sqlsrv_errors())); > 에러가 발생하면 에러메시지 출력. error based sql injection 가능

 

문제풀이

mssql 환경에서의 error based

 

에러 메시지에서 원하는 값을 탈취하여도 되고, 일부러 특정 조건일 때 에러 메시지를 띄워 blind sql injection으로 풀어도 된다.

 

먼저 특정 조건일 때 일부러 에러메시지를 띄움으로 참 거짓에 대한 결과 차이를 만드는 방법을 이용한다.
차이를 만드는 방법은 여러가지가 있겠지만 이번엔 case를 이용한다.

 

요청은 아래와 같다.

?id='admin' and 1=(case when pw>'a' then 'z' end)-- -'

 

 

pw가 'a'보다 크므로 case에서 'z'를 반환하고 1='z'가 되어 자료형 에러로 에러메시지를 출력한다.

?id='admin' and 1=(case when pw>'z' then 'z' end)-- -'

 

 

pw는 z보다 작으므로 에러메시지 없이 정상적으로 출력된 것을 확인할 수 있다.
이를 반복하여 pw를 찾는다.

 

mssql에서는 mysql보다 자료형 비교가 엄격하므로 이를 통해 에러메시지에 admin의 pw값을 출력 시키도록 한다.

?id='admin' and pw>1-- -

 

 

당연하지만 admin 외에도 guest/guest가 있고 guest가 admin보다 먼저 생성되었기 때문에 별 다른 조건을 주지 않으면 guest의 pw 값이 먼저 int 형인 1과 비교가 되버려 에러메시지에 guest의 pw가 출력되게 된다.

 

?pw=uawe0f9ji34fjkl

 

 

의문점

 

추후의 문제인 revenant도 그렇지만 SQL FIDDLE이나 직접 mssql 환경을 만들어서 시도한 것과 결과 차이가 나는 것이 있다.


?id='admin' and pw>1-- - 을 요청했을 때 admin의 pw가 출력되는 것은 and 앞의 조건에 해당하는 id='admin' 값에 대해서만 and 뒤의 조건인 pw>1를 비교하기 때문에 admin의 pw가 출력된다.

 

 

만약 ?id='guest' or pw>1-- -를 요청하면 어떤 값이 출력될까?
or 앞의 조건에서 guest 값을 불러왔으므로 or 뒤의 조건인 pw>1에서는 guest가 먼저 생성되었지만 비교를 하지 않기 때문에 그 다음 값인 admin부터 비교하여 admin의 패스워드가 출력된다.

 

 

이론적으로는 이렇게되고 실제 SQL FIDDLE 사이트에서는 이론과 동일한 값이 나왔다.


물론 예외적인 값은 존재한다. 예외는 아래와 같다.
guest의 pw가 int형과 비교가 가능한 값 일때(1, 12, 123, 1111.....)


id='admin' or pw>1을 요청했기 때문에 int형과 비교하는 값은 admin이 제외되고 guest의 pw만이 비교되는데 int형과 비교가 되므로 결과값으로 admin의 데이터가 출력되는 것이다.

 

 

하지만 실제 문제에서는 에러가 발생하지 않는다. 물론 admin의 pw가 int형이 아니다.


이론상으로는 admin의 pw가 에러메시지로 출력이 되어야하는 쿼리지만 출력이 되지 않는다.

 

추측일 뿐이지만

admin의 pw값과 요청한 pw값을 엄격한 비교하기 위해 $query = "select pw from prob_nessie where id='admin'"; 에서 admin 값을 들고오기 때문에 제외된것이 아닌가 생각 중이다.


추후 환경을 만들어서 시도해볼 것.