문제분석 & 풀이
sql inejection 문제다.
간단한 로그인 창이 구현됐다.
코드를 분석해보자.
<?php
if($_GET['id'] && $_GET['pw']){
$db = dbconnect();
$_GET['id'] = addslashes($_GET['id']);
$_GET['pw'] = addslashes($_GET['pw']);
$_GET['id'] = mb_convert_encoding($_GET['id'],'utf-8','euc-kr');
foreach($_GET as $ck) if(preg_match("/from|pw|\(|\)| |%|=|>|</i",$ck)) exit();
if(preg_match("/union/i",$_GET['id'])) exit();
$result = mysqli_fetch_array(mysqli_query($db,"select lv from chall50 where id='{$_GET['id']}' and pw=md5('{$_GET['pw']}')"));
if($result){
if($result['lv']==1) echo("level : 1<br><br>");
if($result['lv']==2) echo("level : 2<br><br>");
}
if($result['lv']=="3") solve(50);
if(!$result) echo("Wrong");
}
?>
1. id, pw 파라미터 값을 받아 각각을 addslashes()로 이스케이프 시킨다.
2. id 파라미터 인코딩 방식을 "utf-8" 에서 "euc-kr"로 바꾼다.
3. id, pw에 from , pw , ( , ) , 공백 , % , = , > , < 이 들어갈 경우 프로그램을 종료한다.
4. id에 union이 들어갈 경우도 프로그램을 종료한다.
5. sql 쿼리를 실행해 lv 값을 가져온다.
6. lv 값이 3이면 clear
저번에 풀었던 45번 문제와 유사한 문제인 것 같다.
[webhacking.kr 풀이] - webhacking.kr 45번
45번 문제와 마찬가지로 id, pw 파라미터가 이스케이프 됐지만 id의 경우 mb_convert_encoding()을 통해 "euc-kr"로 인코딩 방식이 변경되면서 이것과 관련된 취약점을 통해 이스케이프가 되는 것을 무효화하여 인젝션을 할 수 있었다.
(자세한 설명은 위에 있는 45번 링크 ㄱㄱ)
이번에도 이것을 이용해야 할 것 같다.
id='0' or lv=3#' and pw=md5('{$_GET['pw']}')
payload: 0' or lv=3#
쿼리 where 부분만 가져왔다.
or 로 id='0'을 무효화하고 뒤에 #을 붙여서 pw 부분을 무효한다.
위와 같이 인젝션을 하면 lv=3인 값을 DB에서 가져오고 클리어 될 것이다.
그러나 정규표현식 필터링이 존재하기 때문에 우회하는 버전의 페이로드를 작성했다.
id='0%fe%27%09or%09lv%09like%093%23' and pw=md5('{$_GET['pw']}')
payload: 0%fe%27%09or%09lv%09like%093%23
mb_convert_encoding()을 이용한 취약점을 활용하기 위해 '(%27)앞에 %fe를 붙였다.
공백은 TAB(%09)로 우회하고 있고 "= " 대신에 like를 사용하고 있다.
완성한 페이로드를 전송해보자.
예상과 다르게 Wrong이 뜬다.
코드분석 때 신경을 쓰지 않았던 lv값이 1인 경우와 2인 경우의 코드를 확인했다.
이런 예제를 왜 줬을까? 라고 생각을 해봤고 작성한 페이로드에서 lv를 3 대신 2로 바꿔서 전송했다.
정상적으로 출력된다.
그렇다면 lv가 3인 경우에 안 풀린 이유는 하나다. DB에 lv=3인 정보가 존재하지 않기 때문이다.
굉장히 골치 아픈 문제인 것 같다.
DB에서 정보를 빼내는 방식으로는 해결할 수 없고 직접 select를 이용해서 lv가 3인 정보를 출력시키는 방법밖에 없다.
그러나 id에서 union 사용이 금지되어 있어서 union 인젝션도 불가능하다.
어떻게 문제를 해결할까?
현재 상황을 살펴보면 다음과 같다.
1. DB에 lv=3인 정보가 존재하지 않기 때문에 select를 이용해 직접 값을 출력시키는 방법밖에 없다.
2. id는 union의 사용이 금지되어있기 때문에 1번의 방법을 사용할 수 없다.
결국 pw 파라미터를 이용해 인젝션을 시도해야 된다는 의미가 된다.
다행히도
union select 3
이라는 페이로드를 pw에 넣는다고 가정했을때 페이로드에는 ' , " 등의 이스케이프 처리되는 값이 사용되지 않기 때문에 인젝션이 불가능하지는 않다.
그러나 sql 쿼리에서 pw는 md5() 로 묶여있는데 ( , ) , " 등을 사용할 수 없어서 md5()를 무효화 시키기가 난감하다.
하지만 주석은 필터링되지 않는다.
그렇다면 주석을 이용해서 md5()를 지워버릴수 있지 않을까?
select lv from chall50 where id='0%fe%27/*' and pw=md5('*/union%09select%093%23')
id_payload: 0%fe%27/*
pw_payload: */union%09select%093%23
위와 같은 페이로드를 작성했다.
sql에서는 /* */ 를 통해 사이 내용을 주석처리를 할 수 있다.
그렇다면 id 끝부분에 /* , pw 앞부분에 */ 를 넣어주면 그 사이의 있는 모든 값은 주석처리가 된다.
주석처리가 되면 pw 부분의 md5()는 무효되고 우리가 원하는 union 인젝션을 시도함으로써 lv값이 3인 결과를 얻을 수 있다.
근데 union을 이용해서 select 3 을 넣어줬을 뿐인데 왜 lv가 3인 값이 출력되지? 라고 생각할 독자를 위해 추가적으로 설명하겠다.
select lv from chall50 where id='0' union select 3
공격시 완성될 쿼리를 알아보기 쉽게 만들면 위와 같은 쿼리다.
union을 기준으로 2개의 select문으로 나뉜다.
이때, 1번째 select 문의 경우는 id가 0인 값이 DB에 없기 때문에 아무런 정보를 가져오지 않는다.
이때까지 select문에 의해 가져온 결과를 테이블로 표현하면 다음과 같다.
lv
ㅡㅡㅡㅡㅡㅡ
(빈 데이터)
이후 2번째 select 문을 실행하면 3인 데이터를 가져온다.
이 결과를 테이블로 표현하면 다음과 같다.
lv
ㅡㅡㅡㅡㅡㅡ
3
최종적으로 위와 같은 결과가 완성되고 lv값이 3이 되면서 문제가 해결될 것이다.
만든 페이로드를 id, pw로 전송해보자.