| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- c
- Dreamhack
- 악성코드 분석
- 우테코
- linux
- top-cert
- 루키즈31기
- 루키즈 31기
- 알고리즘
- SK쉴더스루키즈
- 리버싱
- 위상 정렬
- 동적분석
- 레나튜토리얼
- 루키즈
- sk 쉴더스
- sk 쉴더스 루키즈
- sk쉴더스 루키즈
- sk 쉴더스 루키즈 31기
- 웹개발
- 깃
- React
- 우아한테크코스
- Practical Malware Analysis Labs
- 자바
- 프리코스
- 백엔드
- webhacking
- 예술의 전당
- 레나 튜토리얼
- Today
- Total
yon11b
[SK 쉴더스 루키즈] SQL Injection 대응방안 본문
기존방식: statement
SQL문을 문자열 그대로 만들어서 실행하는 방식
대응방안1. Prepared Statement
SQL 구조를 미리 DB에 전달해서 준비해두는 방식
입력이 들어오면 준비된 SQL 구조에 값만 바인딩해서 실행하는 것이다.
이 기능은 원래 보안성 때문에 만들어진 것은 아니고 속도를 빠르게 하기 위해서 나온 기능이다.
바인딩하는 값은 무조건 데이터로만 사용되고 쿼리문으로 해석이 안 된다.
select title, content from board where id=?
Q. Prepared Statement가 있으니 SQLi는 완전히 차단되었는가?
아니다.
정확히는 ?로 바인딩된 값 부분은 안전하다.
하지만 ?로 처리하지 못하는 부분이 있다. 여기는 어쩔 수 없이 statement 방식으로 해야 하고, 그럼 SQLI가 발생할 수 있다.
?를 쓸 수 없는 부분
Prepared Statement의 ?는 값 자리 전용이다.
DB는 SQL을 준비할 때 미리 알아야 한다.
어느 테이블을 조회하는지
어떤 컬럼으로 정렬하는지
컬럼 타입이 뭔지
인덱스를 쓸 수 있는지
실행 계획을 어떻게 만들지
그런데 FROM ?이면 DB는 준비 단계에서 테이블을 모른다.
또 ORDER BY ?도 컬럼명 정렬이 아니다.
가능한 예:
- WHERE title LIKE ?
- WHERE id = ?
- WHERE name = ?
여기서 ?는 나중에 'test', 'admin', 123 같은 데이터 값으로 들어간다.
불가능한 예
1. FROM ? (불가능)
SELECT title, content FROM ? WHERE title LIKE ?
여기서 첫 번째 ?를 qna로 바인딩한다고 해서
SELECT title, content FROM qna WHERE title LIKE ?
처럼 해석되지 않는다. DB는 FROM ? 자체를 테이블명 자리로 사용할 수 없기 때문이다.
그래서 코드를 이렇게 짤 수밖에 없다.
board = request.args.get("board")
keyword = request.args.get("keyword")
query = f"""
SELECT title, content
FROM {board}
WHERE title LIKE ?
"""
cursor.execute(query, (f"%{keyword}%",))
이 코드는 Prepared Statement를 사용한다. 하지만 안전한 부분과 취약한 부분이 나뉜다.
안전한 부분: WHERE title LIKE ?
취약한 부분: FROM {board}
공격예시
board = qna WHERE 1=1 --
SELECT title, content
FROM qna WHERE 1=1 --
WHERE title LIKE ?
2. ORDER BY ? (불가능)
SELECT title, content FROM board WHERE title LIKE ? ORDER BY ?
여기서 두 번째 ?에 REG_DT를 바인딩해도
WHERE title LIKE ? ORDER BY REG_DT
로 해석되는 것이 아니라, DB는 값으로 처리한다. 즉 컬럼명이 아니라 'REG_DT'라는 리터럴 값으로 취급된다.
그래서 얘도 이런식으로 코드를 짤 수밖에 없다. (order by 부분은 prepared 안 됨)
sorting = request.args.get("sorting")
sorting_ad = request.args.get("sortingAd")
keyword = request.args.get("keyword")
query = f"""
SELECT *
FROM free
WHERE title LIKE ?
ORDER BY {sorting} {sorting_ad}
"""
cursor.execute(query, (f"%{keyword}%",))
안전한 부분: WHERE title LIKE ?
취약한 부분: ORDER BY {sorting} {sorting_ad}
-> 공격자가 ORDER BY 뒤에 SQL 표현식을 넣을 수 있다.
공격예시
입력:
sorting=CASE WHEN 1=1 THEN REG_DT ELSE 1/0 END&sortingAd=ASC
결과:
SELECT *
FROM free
WHERE title LIKE ?
ORDER BY CASE WHEN 1=1 THEN REG_DT ELSE 1/0 END ASC
조건(1=1)이 참이니까 REG_DT가 결과로 나온다.
그런데 만약 조건을 1=2 과 같은 거짓으로 두면 1/0이 실행되어 에러가 발생한다. -> error based sqli 실행 가능
좀 더 실제적인 예시
조건: 첫 번째 테이블명의 길이가 4인가?
입력:
sorting=CASE WHEN (SELECT LENGTH(table_name)
FROM (SELECT table_name, ROWNUM r FROM user_tables) WHERE r=1)=4
THEN REG_DT ELSE 1/0 END&sortingAd=ASC
결과:
SELECT *
FROM free
WHERE title LIKE ?
ORDER BY CASE WHEN (SELECT LENGTH(table_name)
FROM (SELECT table_name, ROWNUM r FROM user_tables) WHERE r=1)=4
THEN REG_DT ELSE 1/0 END ASC
CASE WHEN 이란?
CASE WHEN은 조건에 따라 값을 선택하는 SQL 표현식이다.
형태:
CASE WHEN 조건 THEN 참일때값 ELSE 거짓일때값 END
Oracle에서 에러를 일부러 만들려면 이런 식으로 쓸 수 있다.
CASE WHEN 조건 THEN REG_DT ELSE 1/0 END

조건이 참이면 REG_DT이 정상 실행된다.
조건이 거짓이면 1/0 -> Oracle에서 에러 발생 -> ORA-01476: 제수가 0 입니다
즉 응답이 정상인지 에러인지 보고 한 글자씩 알아내는 방식이다.
ORDER BY CASE
WHEN [내가 확인하고 싶은 조건]
THEN 1
ELSE 1/0
END
이 방식은 Error-based Blind SQLi에 가깝다.
데이터가 바로 출력되는 게 아니라 에러 발생 여부로 참/거짓을 판단하기 때문이다.
즉 ?는 값(Value) 이 들어가는 자리에서만 사용할 수 있다
ex) SELECT * FROM board WHERE id = ?
대응방안2. 필터링
', ", 등을 공백처리 하는 식
대응방안3. 형변환
query= select * from board where id=
query += int(input_id)
int로 자료형을 고정시켜 주면 됨
'보안 > SK 쉴더스 루키즈' 카테고리의 다른 글
| [SK 쉴더스 루키즈] Stored XSS 문제 풀기 (0) | 2026.06.27 |
|---|---|
| [SK 쉴더스 루키즈] Reflected XSS: Burp Suite Repeater 이용 (0) | 2026.06.25 |
| [SK 쉴더스 루키즈] 쇼핑몰 Blind SQL Injection(Oracle) (0) | 2026.06.25 |
| [SK 쉴더스 루키즈] Blind SQL Injection(Oracle 컬럼명 탈취) (0) | 2026.06.25 |
| [SK 쉴더스 루키즈] Blind SQL Injection(MySQL USER명 탈취) (0) | 2026.06.25 |