소스코드 분석
<?php
include "./config.php";
login_chk();
$db = mssql_connect();
if(preg_match('/master|sys|information|prob|;|waitfor|_/i', $_GET['id'])) exit("No Hack ~_~");
if(preg_match('/master|sys|information|prob|;|waitfor|_/i', $_GET['pw'])) exit("No Hack ~_~");
$query = "select * from prob_revenant where id='{$_GET['id']}' and pw='{$_GET['pw']}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
sqlsrv_query($db,$query);
if(sqlsrv_errors()) exit(mssql_error(sqlsrv_errors()));
$query = "select * from prob_revenant where id='admin'";
$result = sqlsrv_fetch_array(sqlsrv_query($db,$query));
if($result['4'] === $_GET['pw']) solve("revenant"); // you have to pwn 5th column
highlight_file(__FILE__);
?>
1. $db = mssql_connect();
> db환경이 mssql
2. if($result['4'] === $_GET['pw']) solve("revenant"); // you have to pwn 5th column
> 5번째 컬럼의 값과 엄격한 비교, 5번째 컬럼 값을 알아야 할 필요
3. if(sqlsrv_errors()) exit(mssql_error(sqlsrv_errors()));
> 쿼리 실행 중 에러 발생 시 에러를 출력. Error based sql injection 가능
문제 풀이
mssql 환경에서 Error based sql injection을 할 수 있는 가에 대한 문제다.
mysql 환경 외에서 sql injection을 해보라는 의도로 Error를 발생시키는 데 별다른 필터링을 하고 있지 않다.
먼저 5번째 컬럼 명을 알아야하고, admin의 5번째 컬럼 값을 알아야 한다.
컬렴 명을 알아내는 것은 Having 절을 이용해서 알아내도록 한다.
?pw=' having 1=1-- -
having 절을 GROUP BY 없이 사용하여 에러가 발생한다. 그 과정에서 해당 테이블의 첫번째 컬럼이 노출된다.
id 컬럼은 원하는 컬럼이 아니므로 group by 를 통해 다음 컬럼 명을 알아낸다.
?pw=' group by id having 1=1-- -
2번째 컬럼 pw를 에러메세지에서 확인했다. pw 역시 우리가 원하는 컬럼이 아니므로 다음 컬럼을 찾는다.
++ 왜 에러가 발생하는가?
mssql error based로 검색한 글들에는 HAVING을 GROUP BY절 없이 사용하면 첫번째 컬럼 명이 에러메세지로 출력이 되는 것 까지는 설명되어 있지만 HAVING에 GROUP BY절을 사용하였는데 왜 두번째 컬럼 명이 나오는 에러메세지가 출력되는지 설명되어 있는 글이 없다.
mssql 문법에 대해 잘 아는 사람들은 너무나 당연할지도 모르지만 나같은 얕은 지식으로 보안 공부하는 사람은 모를수도 있으니 짚고 넘어간다.
아래의 두가지 예제를 보자.
1. SELECT id, count(pw) FROM prob_revenant GROUP BY id HAVING 1=1
2. SELECT id, pw FROM prob_revenant GROUP BY id HAVING 1=1
1. 은 정상적으로 실행이 되고, 2. 는 실행이 안된다. 대체 왜?
형태 자체는 SELECT * FROM [테이블] GROUP BY [컬럼] HAVING [조건] 이 맞는데 에러가 발생한다.
https://docs.microsoft.com/ko-kr/sql/t-sql/queries/select-group-by-transact-sql?view=sql-server-2017
"열은 SELECT 문의 FROM 절에 나타나야 하지만, SELECT 목록에는 나타나지 않아도 됩니다.
그러나 <select> 목록의 모든 비집계 식에 있는 각 테이블 또는 뷰의 열은 GROUP BY 목록에 포함되어야 합니다."
GROUP BY에 비집계식에 해당하는 컬럼은 다 포함이 되어있어야 한다.
2. 같은 경우 id 컬럼을 GROUP BY 절에 포함을 시켰지만 pw 컬럼을 포함시키지 않아 에러가 발생한다.
[Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Column 'prob_revenant.pw' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
그러면 1. 은 왜 에러가 발생하지 않을까?
https://docs.microsoft.com/ko-kr/sql/t-sql/functions/aggregate-functions-transact-sql?view=sql-server-2017
count(pw)는 집계 함수를 사용하여 비집계 식이 아니다. 집계 식은 GROUP BY 절에 포함시키지 않아도 되므로 1. 쿼리의 경우 에러가 발생하지 않는 것이다.
그래서 쿼리 select * from prob_revenant where id='' and pw='' group by id having 1=1-- -'
는 HAVING 절에서 에러가 발생하는 것이 아닌 group by id에서 발생한다.
이 글에는 사용하지 않았지만 ?pw=' group by id,pw having 1=1-- -이 아닌 ?pw=' group by id-- -' 를 요청해도 pw 컬럼 명을 출력하는 에러메세지를 볼 수 있다.
에러메세지로 3번째 컬럼명을 노출하도록 하는 쿼리를 요청한다.
?pw=' group by id,pw having 1=1-- -
3번째 컬럼 명으로 45a88487을 출력했다.
위의 쿼리와 비슷하게 요청을 시도한다.
?pw=' group by id,pw,45a88487 having 1=1-- -
4번째 컬럼 명이 아닌 syntax 에러 메시지를 출력한다. 컬럼 명이 숫자로 시작하여 이를 컬럼으로 인식하지 못하고 의도한 쿼리로 실행된 것이 아니다.
45a88487을 컬럼 명으로 구분하기 위해 []를 사용한다.
?pw=' group by id,pw,[45a88487] having 1=1-- -
45a88487을 컬럼 명으로 인식하여 4번째 컬럼을 출력하였다.
동일한 방식으로 5번째 컬럼까지 알아낸다.
컬럼 명을 알았내었으니 admin의 5번째 컬럼 값을 알아내면 된다. mssql에서는 varchar type과 int type을 비교하면 에러가 발생한다. 이를 이용해서 값을 알아내도록 한다.
?pw=' or [9604b0c8]>1-- -
5번째 컬럼 값이 나왔지만 prob_revenant 테이블에는 guest 값도 존재한다. 해당 값은 guest의 9604b0c8 컬럼 값이므로 admin의 5번째 컬럼 값을 알아낼 필요가 있다.
?id=admin&pw=' or id='admin' and [9604b0c8]>1-- -
해당 컬럼 값을 pw 파라미터에 요청하면 클리어 가능하다.
?pw=aa68a4b3fb327dee07f868450f7e1183
admin의 9604b0c8 컬럼 값을 출력하기 위한 쿼리에서 id 파라미터에 admin, pw 파라미터에 pw=' or id='admin' and [9604b0c8]>1-- -을 요청했다.연산자 우선순위는 or이 나중 and가 먼저이므로 pw 파라미터에 삽입한 id='admin'이 에러메세지에서 admin의 5번째 컬럼 값을 출력해주기 위한 조건이라 생각했는데 실제로는 한 쪽이라도 admin을 적어주지 않으면 guest의 컬럼 값이 나온다.가설은 and, or 연산자를 수행하고 [9604b0c8]>1에서 불러올 값의 최상위 값이 admin의 [9604b0c8] 값이기 때문에 나온다. 라고 생각을 하고 진행하고있다.간단하게 우리 머리속에 or 연산을 한다고 하면 A={1,2,3} , B={2,3,4} , A와 B의 합집합 ={1,2,3,4} A와 B의 값을 모두 생각한 뒤, A와 B의 해당되는 값을 한번씩 집어넣는다. 중복은 알아서 머리속에서 걸러내고.그런데 DB에서 or연산자를 인식하고 or 연산을 할 때 우리 머리속에서 처리하는 것 처럼 진행이 될까의 의문이다. 결과적으로만 A와 B의 합집합이 나오는 것만 알지 실제로 DB에서 합집합 결과를 출력할때 어떻게 값을 호출하고 합집합을 만들어내는지는 자세히 모른다. 그래서 쿼리를 집어넣어 예상해본 결과 or 연산자의 경우 A or B 에서 A와 B의 교집합에 해당하는 부분은 or의 왼쪽 즉 A에서만 값을 호출하고 B에서는 A와 중복되는 값을 호출하지 않고 제외한다. ?id='guest' or [9604b0c8]>1-- 를 요청할 시 admin의 [9604b0c8] 값이 나오는 이유로 [9604b0c8]>1 조건절은 admin과 guest의 값을 모두 호출해야하지만 이미 연산자 or 앞의 id='guest'에서 guest의 값을 호출했기 때문에 연산자 or 뒤의 [9604b0c8]>1에서는 guest값을 제외한 admin의 값만 호출하고 이 admin의 [9604b0c8] 값을 비교하는 과정에서 type 에러가 발생하여 에러문에 admin의 [9604b0c8] 값을 출력한다고 생각할 수 있다. 에러를 의도적으로 발생시키는 부분. revenant 문제라면 "[9604b0c8]>1" 해당 조건 절에서 값을 가져올 때 varchar형태의 값과 int의 1과 비교가 되면서 에러가 발생된다. [9604b0c8] 컬럼에 값이 하나도 존재하지 않는다면? 에러가 발생하지 않는다.진행 중 ssms에서 돌려본 테스트 쿼리의 결과와 문제 revenant에 쿼리의 결과가 다른 것이 있어서 일단 보류.계속해서 알게되면 수정할지도.
++ 왜 id 파라미터에 admin을 넣어줘야하는가?
테스트 중.. 너무 어렵다
여담
처음 문제를 풀 때 문제를 잘못 이해를 했다.
1. sqlsrv_fetch_array를 사용했으니 배열 키는 컬럼 명이겠구나.
2. 그래서 $result['4'] 는 4라는 컬럼 명이구나!
3. 컬럼 명이 숫자라서 이걸 우회를 해야하는구나!!
5번째 컬럼 명은 "4"가 아니지만 실제로 테이블 prob_revenant의 컬럼 중 일부는 숫자로 시작하는 것이 맞다. 문제를 잘못 이해해 풀이 방향을 잘못 잡았는데 결과적으로는 방향이 맞은(?) 아이러니한 상황이 되었다.
https://docs.microsoft.com/ko-kr/sql/connect/php/sqlsrv-fetch-array?view=sql-server-2017
sqlsrv_fetch_array는 $fetchType 매개 변수를 통해 배열 키를 숫자로 할 지, 컬럼 명으로 할지 정할 수 있다. 그리고 Default 값은 SQLSRV_FETCH_BOTH이다. 숫자 문자 배열이 둘 다 반환이 된다.
다른 문제들도 sqlsrv_fetch_array를 사용하는데 배열 키로 컬럼 명을 사용하길래 sqlsrv_fetch_array는 문자 배열을 반환하는 줄 알았고, 이번 문제도 컬럼 명일거라 생각을 해버린 것이다.
php, mssql의 얄팍한 지식에 다시 한번 반성의 시간을 가지게 된다.
'wargame > Lord Of SQL Injection' 카테고리의 다른 글
[LOS] mummy (0) | 2019.07.26 |
---|---|
[LOS] yeti (0) | 2019.07.26 |
[LOS] nessie (0) | 2019.07.17 |
[LOS] poltergeist (0) | 2019.07.17 |
[LOS] banshee (0) | 2019.07.17 |