웹서비스는 거의 항상 DB(RDBMS) 와 커뮤니케이션합니다. 문제는 개발자가 사용자 입력값(User Input) 을 그대로 SQL 문장에 붙여버리면, 그 입력값이 데이터가 아니라 SQL 명령(코드) 로 해석될 수 있다는 점이에요.
그래서 SQL 인젝션은 단순 유출을 넘어 데이터 위·변조, 로그인 우회, 권한 상승, 심지어 서버 장악(환경에 따라) 까지 이어질 수 있습니다. OWASP가 꾸준히 치명적 위험으로 분류하는 이유도 여기 있습니다.
이 글에서는 SQLi를 아래 4개로 나눠서 작동 방식(메커니즘) → 난이도 → 방어법 순으로 정리해 드립니다.
- [CASE A] 폼(Form) 기반(클래식)
- [CASE B] 유니온(UNION) 기반
- [CASE C] 오류(Error) 기반
- [CASE D] 블라인드(Blind) 기반(불리언/시간)
[본론] 0. 핵심 원리: “데이터 평면 vs 제어 평면”
SQLi를 한 줄로 끝내면 이겁니다.
사용자 입력값(데이터)이 SQL 문법(제어/명령)으로 해석되는 순간 공격이 됩니다.
예를 들어 원래 의도는 “id가 1인 상품만” 조회인데,
- 원래 의도:
... WHERE id = 1 - 입력 오염:
... WHERE id = 1 OR 1=1
이렇게 되면 DB는 “조건이 항상 참이니 전부 다 줘”로 이해해버립니다.
즉, 입력값이 값이 아니라 로직으로 기능하게 되는 거죠.
[CASE A] 폼(Form) 기반 SQLi — 로그인 우회/논리 조작 (난이도: 하)
1) 메커니즘
로그인 쿼리의 전형은 아래 형태입니다.
SELECT * FROM users WHERE username = 'INPUT_USER' AND password = 'INPUT_PASSWORD';
공격자는 여기서 AND 검증을 깨고, OR로 “무조건 참”을 만들어 인증을 우회합니다(동어반복/Tautology).
2) 왜 성공하나? (초보자용 비유)
은행 창구에 “100만 원 인출” 요청서를 내면서, 옆에 작은 메모로 “그리고 금고 문도 열어” 를 붙여놓는 겁니다.
창구 직원(DB)이 그 메모를 요청 데이터가 아니라 ‘추가 지시’로 읽어버리면 사고가 납니다.
3) 난이도 평가: 하(Low)
- 브라우저만 있어도 시도 가능
- 성공/실패 피드백이 즉시 보임(로그인 성공, 결과 변화)
- SQL 기초 문법(AND/OR/주석) 정도면 공격 이해 가능
[CASE B] 유니온(UNION) 기반 SQLi — “원래 결과 + 내가 원하는 결과” 합치기 (난이도: 중)
1) 메커니즘
UNION은 SELECT 결과를 합치는 연산자입니다. 공격자는 이를 이용해
“상품 목록”을 보여주는 페이지에 users 테이블(계정/비번 등) 을 끼워 넣어 출력시키려고 합니다.
2) 성공 조건
UNION 기반은 아무 때나 되는 게 아니라 조건을 맞춰야 합니다.
- 컬럼 수가 동일해야 함
- 컬럼 타입이 호환되어야 함(숫자 자리엔 숫자/호환 타입)
3) 공격 흐름
- (1) 컬럼 개수 파악
- (2) 화면에 “어느 컬럼이 출력되는지” 확인
- (3) 출력 위치에 원하는 데이터 SELECT로 끼워 넣기
4) 난이도 평가: 중
- 컬럼 수/타입 맞추는 시행착오가 필요
- 대신 In-band라 데이터 추출 속도는 매우 빠름(화면으로 바로 뜸)
[CASE C] Error 기반 SQLi — 고의적으로 에러 메시지 나오게 하기 (난이도: 중)
1) 메커니즘
DB는 오류가 나면 친절하게 이유를 설명합니다.
운영환경에서 상세 에러를 사용자에게 그대로 보여주면, 공격자는 그 메시지에 원하는 데이터를 점진적으로 빼낼 수 있어요.
핵심은 이거예요.
“내가 원하는 값이 에러 메시지에 찍히도록” 오류를 유도한다.
2) 환경 제약(중요)
요즘 서비스는 보통 “오류가 발생했습니다”만 보여주고, 상세 에러는 숨깁니다.
이 경우 Error-based는 잘 안 먹고, 공격자는 [CASE D] 블라인드로 넘어갑니다.
3) 난이도 평가: 중(Medium)
- DBMS별로 “어떤 에러가 어떤 메시지를 뱉는지” 지식이 필요
- 하지만 성공하면 비교적 빠르게 단서/데이터를 확보할 수 있음
[CASE D] 블라인드(Blind) SQLi — 화면에 데이터가 안 떠도, “스무고개”로 뽑아냄 (난이도: 상)
블라인드는 에러도 없고, 결과 출력도 없는 상황에서 데이터를 빼내는 방식입니다.
그래서 가장 까다롭고, 실무에서 더 자주 마주칩니다.
1) 불리언(Boolean) 기반 블라인드
페이지 반응이 “참/거짓”에 따라 미세하게 달라질 때 씁니다.
- 조건이 참이면 정상 페이지
- 조건이 거짓이면 빈 페이지/다른 문구
이 차이를 이용해서 “첫 글자가 a냐?” 같은 질문을 계속 던져서 한 글자씩 복원합니다.
효율을 위해 보통 이진 탐색(> ‘m’ 같은 비교) 로 속도를 끌어올립니다.
2) 시간(Time) 기반 블라인드
참/거짓에 따른 화면 차이조차 없으면, 마지막 단서는 응답 시간입니다.
- 조건이 참이면 5초 지연
- 조건이 거짓이면 즉시 응답
즉 “맞으면 일부러 천천히 대답해” 전략이에요.
3) 난이도 평가: 상(High)
- 수작업으로는 거의 불가능(요청이 수백~수천 번)
- 자동화 도구/스크립트가 사실상 필수
- 트래픽/로그가 많이 남아 탐지 가능성도 커짐
공격 유형별 비교 요약표
| 구분 | 난이도 | 추출 속도 | 피드백(단서) | 한 줄 요약 |
|---|---|---|---|---|
| 폼(Form) 기반 | 하 | 빠름 | 화면 변화(로그인/검색) | 논리 조작으로 바로 우회 |
| UNION 기반 | 중 | 매우 빠름 | 화면에 데이터 직접 출력 | 결과 집합 합쳐서 덤프 |
| Error 기반 | 중 | 보통 | 에러 메시지 | 에러에 정보를 실어 유출 |
| Blind 기반 | 상 | 매우 느림 | 참/거짓 변화 또는 시간 지연 | 스무고개로 한 글자씩 |
방어 전략: “데이터와 SQL 코드를 분리”하면 끝납니다
SQLi 대응은 철학이 단순합니다.
입력값은 입력값으로만 처리하고, SQL 문법(코드)로 절대 해석되지 못하게 만든다.
1) [1순위] Prepared Statement(매개변수화 쿼리) — 사실상 정답
문장을 먼저 확정(Prepare) 하고, 값은 나중에 바인딩(Bind) 합니다.
그러면 공격자가 ' OR '1'='1을 넣어도 DB는 그걸 명령어가 아니라 문자열 값으로 취급합니다.
취약한 방식(금지)
query = "SELECT * FROM users WHERE username = '" + username + "'"cursor.execute(query)
안전한 방식(권장)
query = "SELECT * FROM users WHERE username = %s"cursor.execute(query, (username,))
실무 팁: “입력값 검증 잘하면 되지 않나?”라고 생각하기 쉬운데, 검증은 보조고 Prepared Statement가 본체입니다.
2) 입력값 검증(Validation) — 화이트리스트 우선
- 화이트리스트(Allow-list): 허용 가능한 형식만 통과(가장 안전)
- 예: 나이=숫자만, 아이디=영문+숫자만
- 블랙리스트(Block-list):
',--같은 위험 문자 차단- 우회(인코딩/변형)가 많아서 단독 방어로는 부족합니다.
3) 최소 권한 원칙(Least Privilege) — 뚫려도 “피해를 제한”
웹앱 DB 계정에 필요 최소 권한만 부여하세요.
- 읽기 기능만 있으면 SELECT만
- DROP/GRANT 같은 관리 권한 금지
- (환경에 따라) OS 명령 실행 기능도 차단
4) 운영 보안 세팅 — “단서”를 지워라
- 상세 DB 에러 메시지 사용자에게 미노출(로그로만 확인)
- WAF는 보조 수단(우회 가능성 있음)
- 모니터링/레이트리밋/탐지 룰로 Blind 트래픽(다량 요청) 탐지
[결론] 3줄 요약
- SQL 인젝션은 입력값이 SQL 코드로 해석되는 순간 발생해요.
- 폼/UNION/Error/Blind는 피드백 채널(화면·에러·시간) 차이로 구분됩니다.
- 방어는 Prepared Statement + 화이트리스트 검증 + 최소권한 + 에러 숨김이면 실무급으로 막을 수 있어요.

