본문 바로가기
Write Up/webhacking.kr

Webhacking.kr 문제풀이_Part8_8번 문제

by 비원(Be one) 2019. 7. 9.
반응형

여러분 안녕하세요. POCI입니다.

꾸벅

원래 순서상 7번 문제를 풀어야 되지만, 현재 7번 문제는 데이터베이스 오류로 인해 풀 수 없습니다.

그렇기에 이번에는 8번 문제를 풀어보도록 하겠습니다.

 

 


1. 사이트 둘러보기

8번 문제에 들어가면 USER-AGENT done! (1/70)이라는 문구가 나옵니다.

 

새로고침을 하니 done의 수가 증가되는 걸 확인할 수 있습니다.

 

그럼 70번을 새로고침하라는 뜻인가??? 하고 70번까지 해봤습니다.

 

70번이 된 이후엔 다시 1부터 시작하더라고요.....

 

낚였다....

 

2. 소스코드 확인하기

소스코드를 확인하니 indext.phps이 주석으로 쓰여있습니다.

 

그래서 url 뒤에 /index.phps를 입력해서 들어갔더니 php 코드를 확인할 수 있었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<html>
<head>
<title>Challenge 8</title>
<style type="text/css">
body { background:black; color:white; font-size:10pt; }
</style>
</head>
<body>
<br><br>
<center>USER-AGENT
 
<?
 
$agent=getenv("HTTP_USER_AGENT");
$ip=$_SERVER[REMOTE_ADDR];
 
$agent=trim($agent);
 
$agent=str_replace(".","_",$agent);
$agent=str_replace("/","_",$agent);
 
$pat="/\/|\*|union|char|ascii|select|out|infor|schema|columns|sub|-|\+|\||!|update|del|drop|from|where|order|by|asc|desc|lv|board|\([0-9]|sys|pass|\.|like|and|\'\'|sub/";
 
$agent=strtolower($agent);
 
if(preg_match($pat,$agent)) exit("Access Denied!");
 
$_SERVER[HTTP_USER_AGENT]=str_replace("'","",$_SERVER[HTTP_USER_AGENT]);
$_SERVER[HTTP_USER_AGENT]=str_replace("\"","",$_SERVER[HTTP_USER_AGENT]);
 
$count_ck=@mysql_fetch_array(mysql_query("select count(id) from lv0"));
if($count_ck[0]>=70) { @mysql_query("delete from lv0"); }
 
 
$q=@mysql_query("select id from lv0 where agent='$_SERVER[HTTP_USER_AGENT]'");
 
$ck=@mysql_fetch_array($q);
 
if($ck)
echo("hi <b>$ck[0]</b><p>");
if($ck[0]=="admin")
 
{
@solve();
@mysql_query("delete from lv0");
}
 
 
}
 
if(!$ck)
{
$q=@mysql_query("insert into lv0(agent,ip,id) values('$agent','$ip','guest')") or die("query error");
echo("<br><br>done!  ($count_ck[0]/70)");
}
 
 
?>
 
<!--
 
index.phps
 
-->
 
</body>
</html>
cs

php 부분만 따로 보면 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?
 
$agent=getenv("HTTP_USER_AGENT");
$ip=$_SERVER[REMOTE_ADDR];
 
$agent=trim($agent);
 
$agent=str_replace(".","_",$agent);
$agent=str_replace("/","_",$agent);
 
$pat="/\/|\*|union|char|ascii|select|out|infor|schema|columns|sub|-|\+|\||!|update|del|drop|from|where|order|by|asc|desc|lv|board|\([0-9]|sys|pass|\.|like|and|\'\'|sub/";
 
$agent=strtolower($agent);
 
if(preg_match($pat,$agent)) exit("Access Denied!");
 
$_SERVER[HTTP_USER_AGENT]=str_replace("'","",$_SERVER[HTTP_USER_AGENT]);
$_SERVER[HTTP_USER_AGENT]=str_replace("\"","",$_SERVER[HTTP_USER_AGENT]);
 
$count_ck=@mysql_fetch_array(mysql_query("select count(id) from lv0"));
if($count_ck[0]>=70) { @mysql_query("delete from lv0"); }
 
 
$q=@mysql_query("select id from lv0 where agent='$_SERVER[HTTP_USER_AGENT]'");
 
$ck=@mysql_fetch_array($q);
 
if($ck)
echo("hi <b>$ck[0]</b><p>");
if($ck[0]=="admin")
 
{
@solve();
@mysql_query("delete from lv0");
}
 
 
}
 
if(!$ck)
{
$q=@mysql_query("insert into lv0(agent,ip,id) values('$agent','$ip','guest')") or die("query error");
echo("<br><br>done!  ($count_ck[0]/70)");
}
 
 
?>
cs

뭔가 복잡해 보이는데.... 하나씩 차근차근 확인해보겠습니다.

 

 

3. 코드 해석하기


$agent=getenv("HTTP_USER_AGENT");
$ip=$_SERVER[REMOTE_ADDR];

$agent=trim($agent);

$agent=str_replace(".","_",$agent);
$agent=str_replace("/","_",$agent);

$pat="/\/|\*|union|char|ascii|select|out|infor|schema|columns|sub|-|\+|\||!|update|del|drop|from|where|order|by|asc|desc|lv|board|\([0-9]|sys|pass|\.|like|and|\'\'|sub/";

$agent=strtolower($agent);


getenv("HTTP_USER_AGENT")은 클라이언트의 브라우저 환경을 의미하고, $_SERVER[REMOTE_ADDR]은 클라이언트의 IP를 의미니다.

즉, 클라이언트의 브라우저 환경을 $agent 변수에, IP주소는 $ip 변수에 대입함을 의미합니다.

 

trim()은 문자열의 각 양쪽에 있는 공백을 지우는 함수입니다.

즉, $agent 변수의 값의 각 양쪽에 있는 공백을 지우고, .와 /를 _로 치환합니다.

 

$pat에는 /\/|\*|union|char|ascii|select|out|infor|schema|columns|sub|-|\+|\||!|update|del|drop|from|where|order|by|asc|desc|lv|board|\([0-9]|sys|pass|\.|like|and|\'\'|sub/이 대입됩니다.

 

strtolower()는 문자열의 대문자를 소문자로 치환하는 함수이기에, $agent의 값을 소문자로 치환합니다.

 

즉, 이 코드는 클라이언트의 브라우저 환경 정보를 암호화하는 코드인 것입니다.

 


if(preg_match($pat,$agent)) exit("Access Denied!");


이 코드는 만약 $agent 변수의 값에 $pat 변수의 값이 있으면 Access Denied!를 출력한다는 의미입니다.

$pat 변수의 값들은 SQL Injection 공격을 사용할 때 사용되는 문자입니다.

즉, $agent 변수에 SQL Injection 공격을 하는 것을 방지하는 용도입니다.

 


$_SERVER[HTTP_USER_AGENT]=str_replace("'","",$_SERVER[HTTP_USER_AGENT]);
$_SERVER[HTTP_USER_AGENT]=str_replace("\"","",$_SERVER[HTTP_USER_AGENT]);

$count_ck=@mysql_fetch_array(mysql_query("select count(id) from lv0"));
if($count_ck[0]>=70) { @mysql_query("delete from lv0"); }


$q=@mysql_query("select id from lv0 where agent='$_SERVER[HTTP_USER_AGENT]'");

$ck=@mysql_fetch_array($q);


$_SERVER[HTTP_USER_AGENT]는 사용자의 웹 접속 환경 정보를 담은 변수입니다.

즉, 웹 접속 환경 정보에 '와 \를 공백으로 치환함을 의미합니다.

 

$count_ck 변수에는 "select count(id) from lv0" 쿼리를 실행한 결과를 대입합니다.

그리고 만약 $count_ck[0]의 값이 70 이상이면 delete from lv0 쿼리를 실행합니다.

 

$q 변수에는 select id from lv0 where agent='$_SERVER[HTTP_USER_AGENT]를 대입하고,

$ck 변수에는 $q 변수의 값을 실행한 결과를 가져옵니다.

 

이 부분은 데이터베이스와 연동되는 부분입니다.

 


if($ck)

echo("hi <b>$ck[0]</b><p>");
if($ck[0]=="admin")
{
@solve();
@mysql_query("delete from lv0");
}
}


만약 $ck 변수의 값이 참(데이터베이스에 쿼리를 전송)이라면 hi <b>$ck[0]</b><p>를 출력합니다.

그러면 웹페이지에는

hi

ck[0]의 값

이렇게 나올 겁니다.

 

그리고 만약 ck[0]이 admin이면 문제가 해결되고, delete from lv0가 실행된다는 의미입니다.

 


if(!$ck)
{
$q=@mysql_query("insert into lv0(agent,ip,id) values('$agent','$ip','guest')") or die("query error");
echo("<br><br>done!  ($count_ck[0]/70)");
}


만약 ck 변수의 값이 거짓(데이터베이스에 쿼리를 전송하지 못함)이라면

$q 변수에는 insert into lv0(agent,ip,id) values('$agent','$ip','guest')") or die("query error")이 대입하고

<br><br>done!  ($count_ck[0]/70)이 출력됩니다. 이 부분이 웹페이지에 처음 들어올 때 보이는 출력 화면입니다.

 

뭔 소리인지....

쉽게 설명드리겠습니다.

EditThisCookie 툴을 이용해 패킷을 잡으면 User-Agent를 확인할 수 있습니다.

 

이 값이 $agent 변수로 들어가는 것입니다.

여러 가지 필터링을 거친 후에 agent는 다음의 값을 가집니다.

mozilla_5.0 (windows nt 10_0; win64; x64) applewebKit_537.36 (khtml, like gecko) chrome_75_0_3770_100 safari_537_36

 

그런 후, agent 변수에 pat 변수의 값이 있는지 확인합니다.

 

$_SERVER[HTTP_USER_AGENT]에도 User-Agent의 값이 들어가지만, 필터링이 조금 다릅니다.

필터링을 거친 후에 $_SERVER[HTTP_USER_AGENT]에는 다음의 값을 가집니다.

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36

 

$count_ck 변수에는 select count(id) from lv0 (lv0로부터 id의 개수를 구해라)를 실행합니다.

만약 id의 개수가 70개 이상이면 delete from lv0 (lv0를 지워라)를 실행합니다.

 

$q 변수에는 select id from lv0 where agent='$_SERVER[HTTP_USER_AGENT]' (lv0로부터 agent가 $_SERVER[HTTP_USER_AGENT]인 id의 개수를 구해라)를 실행한 값을 가져옵니다.

 

$ck는 $q의 값을 가져옵니다.

 

만약 $ck가 없다면($_SERVER[HTTP_USER_AGENT]인 id의 개수가 없음) 

insert into lv0(agent,ip,id) values('$agent','$ip','guest')를 실행합니다.

그러면 lv0에 $agent, $ip, guest의 값이 대입됩니다.

그리고 done!  ($count_ck[0]/70)가 출력됩니다.

 

이걸 통해서 DB의 구조가 다음과 같다는 걸 알 수 있습니다.

처음 웹페이지에 접속했을 때는 DB에 아무 값도 없기 때문에

select id from lv0 where agent='$_SERVER[HTTP_USER_AGENT]'가 만족되지 않았을 겁니다.

그래서 if(!$ck) 부분으로 와서 insert를 실행했을 겁니다. 그러면 DB는 다음과 같이 되겠죠.

그리고 id의 값이 하나 늘었으므로 $count_ck의 값이 1 늘어날 겁니다.

새로고침을 하면 여전히 select id from lv0 where agent='$_SERVER[HTTP_USER_AGENT]'이 만족되지 않으므로 DB에 똑같은 값이 들어가고 $count_ck의 값이 1 늘어날 겁니다.

 

그러면 select id from lv0 where agent='$_SERVER[HTTP_USER_AGENT]'을 만족시켜 봅시다.

 

User-Agent를 poci로 바꾸면 $agent와 $_SERVER[HTTP_USER_AGENT]가 모두 poci가 될 겁니다.

그러면 DB에는 다음과 같이 값이 대입됩니다.

그러면 이제 다시 User-Agent를 poci로 바꾸면 select id from lv0 where agent='$_SERVER[HTTP_USER_AGENT]'이 만족되겠죠??

 

그러면 if($ck) 부분이 실행되어 hi $ck[0]가 실행됩니다. 하지만 $ck[0]가 admin이 아니기에 문제는 풀리지 않습니다.

즉, DB의 id 부분을 조작해야 한다는 의미입니다. 

그려러면 SQL Injection 공격을 해야 합니다.

 

 

4. SQL Injection 공격

EditThisCookie를 이용해 User-Agent를 조작해봅시다.

 

User-Agent 부분을 poci2', '1', 'admin')#로 바꾸면 쿼리가

insert into lv0(agent,ip,id) values('poci2','1','admin')#'$ip','guest')

이렇게 될 겁니다.

#는 주석이니 실질적으로 insert into lv0(agent,ip,id) values('poci2','1','admin')이 실행됩니다.

 

그럼 DB의 값이 이렇게 될 겁니다.

그런 다음 다시 EditThisCookie를 이용해 User-Agent를 poci2로 바꾸면 

select id from lv0 where agent='$_SERVER[HTTP_USER_AGENT]'이 만족되므로 if($ck) 부분이 실행되어 hi $ck[0]가 실행됩니다.

 

poci2의 id는 admin이므로 문제가 풀리게 됩니다.

 

 


이번 강의는 여기까지입니다.

 

8번 문제는 처음 접했을 때는 어려운 감이 있었지만 php를 차근차근 해석해보면서 접근하면 간단하게 풀 수 있었습니다.

 

오늘도 수고하셨습니다.

파이팅


정리

- 소스코드를 확인하여 php코드를 확인

- php 코드를 해석

- User-Agent에 SQL Injecion 공격을 하여 원하는 값을 맞춰준다.


강의가 유익하셨거나 마음에 드셨으면 댓글과 좋아요 부탁드립니다.

궁금하신 점이나 질문은 댓글이나 메일 남겨주세요.

poci5003@gmail.com

 

반응형