[webhacking.kr] 7번 문제 풀이[SQL]

3 분 소요

💡 Webhacking.kr challenge(old) 7번 문제에 대한 풀이입니다.

문제

문제 화면

image

소스코드

  <?php
    include "../../config.php";
    if($_GET['view_source']) view_source();
  ?><html>
  <head>
  <title>Challenge 7</title>
  </head>
  <body>
  <?php
  $go=$_GET['val'];
  if(!$go) { echo("<meta http-equiv=refresh content=0;url=index.php?val=1>"); }
  echo("<html><head><title>admin page</title></head><body bgcolor='black'><font size=2 color=gray><b><h3>Admin page</h3></b><p>");
  if(preg_match("/2|-|\+|from|_|=|\\s|\*|\//i",$go)) exit("Access Denied!");
  $db = dbconnect();
  $rand=rand(1,5);
  if($rand==1){
    $result=mysqli_query($db,"select lv from chall7 where lv=($go)") or die("nice try!");
  }
  if($rand==2){
    $result=mysqli_query($db,"select lv from chall7 where lv=(($go))") or die("nice try!");
  }
  if($rand==3){
    $result=mysqli_query($db,"select lv from chall7 where lv=((($go)))") or die("nice try!");
  }
  if($rand==4){
    $result=mysqli_query($db,"select lv from chall7 where lv=(((($go))))") or die("nice try!");
  }
  if($rand==5){
    $result=mysqli_query($db,"select lv from chall7 where lv=((((($go)))))") or die("nice try!");
  }
  $data=mysqli_fetch_array($result);
  if(!$data[0]) { echo("query error"); exit(); }
  if($data[0]==1){
    echo("<input type=button style=border:0;bgcolor='gray' value='auth' onclick=\"alert('Access_Denied!')\"><p>");
  }
  elseif($data[0]==2){
    echo("<input type=button style=border:0;bgcolor='gray' value='auth' onclick=\"alert('Hello admin')\"><p>");
    solve(7);
  }
  ?>
  <a href=./?view_source=1>view-source</a>
  </body>
  </html>

풀이

문제 분석

문제를 해결하기 위한 solve함수가 호출되는 부분은 다음과 같습니다.

  $data=mysqli_fetch_array($result);
  if(!$data[0]) { echo("query error"); exit(); }
  if($data[0]==1){
    echo("<input type=button style=border:0;bgcolor='gray' value='auth' onclick=\"alert('Access_Denied!')\"><p>");
  }
  elseif($data[0]==2){
    echo("<input type=button style=border:0;bgcolor='gray' value='auth' onclick=\"alert('Hello admin')\"><p>");
    solve(7);
  }

위 부분의 로직만 분석해 보면, DB쿼리 결과로 나온 result에서 데이터를 가져왔을 때

  1. 데이터가 없으면 : query error
  2. 데이터가 1이면 : 화면에 ‘auth’ 버튼을 만들고 누르면, Access_Denied가 표시
  3. 데이터가 2이면 : Solve

따라서 결론적으로 본 문제를 DB에서 쿼리 결과로 2라는 값이 나오게 해야 합니다.

그럼 DB의 쿼리문을 보면 아래와 같습니다.

  $rand=rand(1,5);
  if($rand==1){
    $result=mysqli_query($db,"select lv from chall7 where lv=($go)") or die("nice try!");
  }
  if($rand==2){
    $result=mysqli_query($db,"select lv from chall7 where lv=(($go))") or die("nice try!");
  }
  if($rand==3){
    $result=mysqli_query($db,"select lv from chall7 where lv=((($go)))") or die("nice try!");
  }
  if($rand==4){
    $result=mysqli_query($db,"select lv from chall7 where lv=(((($go))))") or die("nice try!");
  }
  if($rand==5){
    $result=mysqli_query($db,"select lv from chall7 where lv=((((($go)))))") or die("nice try!");
  }

위의 코드에서 랜덤은 별로 중요하지 않습니다. 랜덤 값은 어차피 계속 시도하면 바뀌니까 헷갈리게 5가지를 생각하지 않고, $rand값이 1이라 가정하고 문제를 풀면 됩니다. 즉 다음의 쿼리문만 분석하면 됩니다.

  $result=mysqli_query($db,"select lv from chall7 where lv=($go)") or die("nice try!");

쿼리문은 select lv from chall7 where lv = ( '입력 값' )입니다.

아주 쉽게 생각하면 입력으로 2만 입력하면 끝인데, 코드를 보면 맨 처음 입력에 대해서 필터링을 수행합니다.

  if(preg_match("/2|-|\+|from|_|=|\\s|\*|\//i",$go)) exit("Access Denied!");

위의 필터링으로 필터링 되는 문자들은 다음과 같습니다.

[2, - , + , from , _ , =, \\s , * , /]

이중 \\s는 띄어쓰기 문자 %20, %0a, %09를 모두 필터링 합니다.

즉 위의 문자들을 이용하지 않고 다른 케이스를 생각해서 db의 결과로 2가 나오게 하면 됩니다.

일단 먼저 생각해야 할 것은 2가 입력이 안되니까 어떻게든 SQL에서 2를 만들어야 합니다. 2를 만드는 방법은 정말 다양한데, 다음 문서를 참고하면서 2를 만드는 방법을 찾으면 됩니다.

대부분의 연산자를 활용해서 2를 만들 수 있습니다. 예를 들어

  1. 6&10
  2. 16>>3
  3. 5%3

등등 매우 다양한 방법이 있습니다.

image

위의 방식들 모두 문제에서 주어진 사칙연산 및 다른 필터링을 모두 우회하기 때문에 입력 값으로 넣어봅니다.

image

하지만 응답 결과를 보면 만약 입력 값이 필터링에 걸렸다면 다음 함수에 의해 Access Denied가 걸려야 하는데, query error가 나왔습니다.

   if(preg_match("/2|-|\+|from|_|=|\\s|\*|\//i",$go)) exit("Access Denied!");

쿼리 에러가 나오는 부분은 DB 쿼리 결과로 부터 아무런 값을 받지 못했다는 것입니다.

  if(!$data[0]) { echo("query error"); exit(); }
  if($data[0]==1){
    echo("<input type=button style=border:0;bgcolor='gray' value='auth' onclick=\"alert('Access_Denied!')\"><p>");
   }

여기서 생각 해야 할 것은 이 부분까지 코드가 실행 된것은 정상적으로 쿼리가 실행 되었지만 DB에 lv = 2라는 컬럼이 없다는 뜻입니다.

즉, DB에 존재하지 않는 lv = 2 컬럼을 만들어서 반환을 해야 하므로 Union Select 구문을 이용해야 합니다.

아래 쿼리는 union select 문의 예시로 동일한 이름의 컬럼을 반환하는 select문을 합칠 수 있는 게 union select 문입니다. 만약 한쪽 select에서 반환 값이 없어도 다른 쪽에서 값을 주면 다음 그림처럼 값을 반환하게 할 수 있습니다.

image

필터링 문자를 다시 보면

[2, - , + , from , _ , =, \\s , * , /]

문제에 Union select 2를 입력하려면 \\s때문에 띄어쓰기를 우회하는 방법을 찾아야 한다.

그 방법은 다음과 같이 괄호를 이용하는 방법이 있습니다.

image

본 문제에서는 입력 값이 다음의 sql문에 그대로 삽입이 되므로 이를 지금까지 정리한 내용을 바탕으로

  1. 2를 다른 연산으로 표현하여 우회
  2. 띄어쓰기를 괄호를 사용하여 우회

하여서 나온 최종 쿼리는 다음과 같습니다.

입력 값 : 999)union(select(16>>3)

이렇게 되면 저의 쿼리가 웹서버에서 다음과 같이 처리 될 것입니다.

select lv from chall7 where lv=(999)union(select(16>>3))

image

시도하면 그림과 같이 rand = 1이 될때까지 틀린 값이 시도되다가 풀리는 것을 볼 수 있습니다.

image

image

댓글남기기