wargame/Lord Of SQL Injection

[LOS] alien

LimeLee 2018. 8. 19. 15:04

$password = md5('L0rd_Of_SQL_1nject1On');공개


현 시각 기준 마지막 문제기도 하고 삽질을 많이 해서 그만큼 푼 보람이 있었던 문제인 거 같다.


소스코드 분석


<?php
  include "./config.php";
  
login_chk();
  
dbconnect();
  if(
preg_match('/admin|and|or|if|coalesce|case|_|\.|prob|time/i'$_GET['no'])) exit("No Hack ~_~");
  
$query "select id from prob_alien where no={$_GET[no]}";
  echo 
"<hr>query : <strong>{$query}</strong><hr><br>";
  
$query2 "select id from prob_alien where no='{$_GET[no]}'";
  echo 
"<hr>query2 : <strong>{$query2}</strong><hr><br>";
  if(
$_GET['no']){
    
$r mysql_fetch_array(mysql_query($query));
    if(
$r['id'] !== "admin") exit("sandbox1");
    
$r mysql_fetch_array(mysql_query($query));
    if(
$r['id'] === "admin") exit("sandbox2");
    
$r mysql_fetch_array(mysql_query($query2));
    if(
$r['id'] === "admin") exit("sandbox");
    
$r mysql_fetch_array(mysql_query($query2));
    if(
$r['id'] === "admin"solve("alien");
  }
  
highlight_file(__FILE__);

?> 


테이블 안에 값은 존재하지 않는다. 그리고 admin, and, or, if ,coalesce, case, time, prob, ., _ 필터링 한다.

싱글쿼터안에 들어간 쿼리와 싱글쿼터가 없는 쿼리 두개의 쿼리를 만든다.

한번의 요청으로 총 4개의 조건 분기문을 통과해야 문제를 해결할 수 있다.


1. 싱글쿼터가 없는 쿼리가 admin이 아니여야한다.

2. 싱글쿼터가 없는 쿼리가 admin이여야한다.

3. 싱글쿼터가 있는 쿼리가 admin이 아니여야한다.

4. 싱글쿼터가 있는 쿼리가 admin이여야한다.


심지어 해당 조건 분기문을 if ,coalesce, case 없이 통과해야한다.


문제 풀이



삽질한 모든 시도를 적어놓았기 때문에 정답인 페이로드에 대한 풀이만 보려면 여기부터 보자



먼저 @a:= @a + 1 이런식으로 하고 특정 숫자에 replace를 하여 'admin'을 출력하게 하는 방식을 생각했다. replace(@a,1,'admin') 식으로 @a가 1이 아니면 숫자 그대로, 1이면 'admin'으로 replace. admin 필터링은 hex로 쉽게 해결할 수 있다. 



하지만 문제는 사용자 정의 변수에 아무런 정의를 해주지 않으면 NULL이 뜬다. if, coalesce ,case, ifnull 등 분기문에 사용되는 함수를 사용할 수 없으므로, @a := @a + 1를 하더라도 @a 가 NULL 값이므로 @a+1 값도 NULL이 되고, 분기문 없이 쿼리 내 정의를 해주자니 2번째 쿼리에서도 정의가 되어 사용자 정의 변수로 구분을 지어줄 수 없다. 


그래서 다음으로 생각한 것이 order by를 생각했다.



union 구문에 @b를 abc로 정의를 해주었지만 그 전에 출력한 @b는 정의되지 않은 NULL값을 출력한다.

이를 이용해서 첫번째 쿼리는 NULL을 출력하지만 두번째 쿼리는 NULL이 아닌 값으로 정의를 해주어 다른 구분을 주는 것이다.



첫번째 쿼리일때는 @c에 아무런 정의를 해주지 않아 NULL이 되고, 두번째는 첫번째 쿼리에서 정의해준 @c:='b'에 의해 @c는 'b'를 출력한다. insert를 이용해준 이유는 select의 결과 값으로 반환되진 않으면서 @c 변수에 'b'를 저장해주려고 사용하였다.

order by desc로 인해 첫번째 쿼리는 'admin'이 먼저 출력되지만 @c변수가 'b로 저장된 뒤에는 b가 먼저 출력되어 sandbox1과 sandbox2를 우회할 수 있게 된다.



테스트 서버에서 실험했을 때, alien을 출력했다. query와 query2를 구분할때는 주석을 이용했다. (테스트 서버에는 필터링을 없이 돌림)


0 union select insert(@a,1,1,@a:=0x62) union select 0x61646d696e order by id desc -- -' union select insert(@b,1,1,@b:=0x62) union select 0x61646d696e order by id asc -- -


query에서 사용할 쿼리는 싱글 쿼터 앞의 구문으로 -- -의 주석이 먹혀 싱글 쿼터 뒤의 구문은 주석 처리가 되지만

query2에서는 query에 사용한 구문이 싱글 쿼터 안에 들어가서 주석마저도 문자열 처리가 된다. 싱글 쿼터 뒤의 구문은 query2에서 사용하고 맨 뒤에 주석처리를 하여 싱글쿼터의 갯수를 맞춰준다.


4개의 요청의 반환으로 'admin','b',NULL,'admin'을 출력해서 클리어한다.

이제 실제사이트에 넣어보자.



los 서버에서는 or을 필터링 하고 있어 order by를 사용할 수 없다.


다른 방법을 생각해야한다.

다음 방법으로는 rand 함수를 생각했다.


rand()함수는 0에서 1사이의 값을 가져오는데 round라는 반올림 함수와 replace 함수를 함께 사용하여 0일 땐 'admin' 아니면 바꾸고 아니면 1을 출력하는 방식으로 접근해본다.



확률은 0.0625지만 요청 횟수를 생각해보면 나쁘지 않은 확률이다. 역시 테스트 서버에서는 클리어가 된 것을 확인 할 수 있다


0 union select replace(round(rand()),1,0x61646d696e) #' union select replace(round(rand()),1,0x61646d696e) #


이제 실제 사이트에 넣어보자


los 서버에서는 and를 필터링 하고 있어 rand를 사용할 수 없다.


또 다른 방법을 생각해야한다.

이번엔 now 함수를 생각했다.


사실 order by와 rand 함수를 생각하기 전에는 insert 함수로 최대한 어찌해보려 했지만, 처음이 무조건 NULL이다. 그래서 sandbox, alien에서는 사용할 수 있지만 sandbox1, sandbox2 어떻게 하든 넘어갈 수가 없었다.



CTF에서는 문제 출제 의도를 위해 일부러 필터링 해놓은 걸 역으로 힌트를 찾을 수 있다는 꼼수를 이용해서 time 함수와 관련된 함수를 찾아보았다. 


눈에 띄는 것이 second함수와 now함수이다. now는 현재 시각을 반환하고, second 함수는 시간의 초만 반환해주는 함수이다. 특정 시간 초에 요청을 하면 admin으로 반환하고 아닐경우 초를 그대로 반환할 수 있을 것이다. 


쿼리의 실행 시간은 대체로 1초 미만이여서 일부러 sleep을 걸어주어야 한다. 이는 concat을 통해 sleep과 second, now 함수를 묶어주어 해결했다. 또한 해당 쿼리가 실행되어 clear를 출력할 확률은 60초 중에 단 1초를 1/60(0.017)인 것을 고려하여 SECOND(now())에 %2를 추가하여 성공 확률을 0.5로 올렸다.



0 union select concat(replace(sleep(1),0x30,0x61),replace(SECOND(now())%2,0,0x646d696e))#' union select insert(@c,INSTR(@c,'a'),5,@c:=0x61646d696e)#


최종 페이로드는 위와 같다. sandbox와 alien에서는 sleep을 하게 되면 clear를 출력할 확률이 작아지고, 또한 sandbox1과 sandbox2에서 sleep을 사용하여 같은 시간 요청할 수 있는 쿼리의 수가 rand를 사용한 쿼리 때 보다 적어지게 되므로 무조건 sandbox와 alien을 우회할 수 있는 insert 구문을 이용했다. (처음 삽질한 insert 구문을 이용. order by 디폴트는 asc이므로 asc를 사용한 페이로드 부분은 문자열 or 필터링및 sandbox, alien에 걸리지 않는다.)



이번엔 los 서버에서도 필터링에 걸리지 않고 clear를 출력한 것을 확인하였다.






ALL CLEAR!

'wargame > Lord Of SQL Injection' 카테고리의 다른 글

[LOS] death  (0) 2019.05.10
[LOS] cthulhu  (0) 2019.05.10
[LOS] zombie  (0) 2018.08.18
[LOS] ouroboros  (0) 2018.08.09
[LOS] phantom  (0) 2018.08.09