security
CHAPTER 79 / 84
읽기 약 2분
FUNCTION
SQL Injection: 공격 원리와 방어
핵심 개념
⚠️ 방어 관점. 사용자 입력이 쿼리에 직접 삽입되는 패턴이 왜 위험한지, 파라미터화 쿼리·ORM·검증 3중 방어로 차단하는 법.
본문
SQL Injection 발생 원리
| 유형 | 동작 방식 | 탐지 난이도 |
|---|---|---|
| Classic | UNION SELECT로 다른 테이블 데이터 추출 | 쉬움 |
| Error-based | DB 에러 메시지로 정보 누출 | 쉬움 |
| Time-based Blind | SLEEP() 응답 지연으로 추론 | 중간 |
| Boolean Blind | 조건문 분기로 한 비트씩 추출 | 어려움 |
취약한 코드 vs 안전한 코드
# ⚠️ 이 코드는 교육 목적이며 허가된 환경에서만 사용하세요.
import sqlite3
# ❌ 취약: f-string으로 입력값 직접 삽입
def login_vulnerable(username: str, password: str) -> bool:
conn = sqlite3.connect('users.db')
cur = conn.cursor()
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
# 공격 입력: username = "admin' --" → 패스워드 우회
cur.execute(query)
return cur.fetchone() is not None
# ✅ 안전 1: 파라미터화 쿼리 (Prepared Statement)
def login_safe_v1(username: str, password: str) -> bool:
conn = sqlite3.connect('users.db')
cur = conn.cursor()
cur.execute(
'SELECT * FROM users WHERE username=? AND password=?',
(username, password),
)
return cur.fetchone() is not None
# ✅ 안전 2: ORM (SQLAlchemy)
from sqlalchemy.orm import Session
from sqlalchemy import select
def login_safe_v2(session: Session, username: str, password: str) -> bool:
stmt = select(User).where(
User.username == username,
User.password == password,
)
return session.execute(stmt).first() is not None
# ✅ 안전 3: 입력 검증 (정규식 화이트리스트)
import re
USERNAME_RE = re.compile(r'^[a-zA-Z0-9_]{3,20}$')
def validate_username(username: str) -> bool:
return bool(USERNAME_RE.match(username))취약점 자가 진단 체크리스트
# ⚠️ 이 코드는 본인 소유 코드 점검용입니다.
import ast
from pathlib import Path
class SQLInjectionDetector(ast.NodeVisitor):
def __init__(self):
self.warnings = []
def visit_Call(self, node):
# cursor.execute(f"...") 또는 cursor.execute("..." + var) 탐지
if (isinstance(node.func, ast.Attribute)
and node.func.attr in ('execute', 'executemany')):
for arg in node.args:
if isinstance(arg, (ast.JoinedStr, ast.BinOp)):
self.warnings.append({
'line': node.lineno,
'issue': '동적 SQL 문자열 (파라미터화 권장)',
})
self.generic_visit(node)
def scan_file(path: Path):
tree = ast.parse(path.read_text())
detector = SQLInjectionDetector()
detector.visit(tree)
return detector.warnings
for py in Path('src/').rglob('*.py'):
warnings = scan_file(py)
if warnings:
print(f'{py}:')
for w in warnings:
print(f" L{w['line']} {w['issue']}")방어 우선순위
- 파라미터화 쿼리 — 모든 DB 쿼리에 적용 (가장 효과 큼)
- ORM 사용 — Django/SQLAlchemy 등 자동 이스케이프
- 입력 검증 — 화이트리스트 정규식
- 최소 권한 원칙 — DB 사용자에 SELECT만 허용 (UPDATE/DELETE 분리)
- WAF (Web Application Firewall) — Cloudflare 등 추가 방어층
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 프로젝트의 DB 쿼리 코드를 보여줄게. 동적 SQL 패턴을 모두 찾아내고 파라미터화·ORM 안전 패턴으로 리팩터링해줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
SQL Injection 공격 시나리오 5가지(Classic/UNION/ Error/Time-based/Boolean Blind)와 각각의 방어 코드 패턴을 실제 예시로 비교해서 설명해줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 코드베이스 전체를 스캔해서 SQL Injection 가능성 있는 모든 위치를 라인 번호와 함께 보고하고 우선순위별 수정 가이드를 만들어줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 SQL Injection 관련 최신 CVE Top 10과 실제 침해 사례 + 방어 트렌드(파라미터화 vs WAF vs ORM)를 솔직히 알려줘.
⭐ 이것만 기억하세요
SQL Injection: 공격 원리와 방어는 이 3가지만 확실히 잡으세요
1.SQL Injection의 90%는 "동적 문자열로 쿼리를 만든다"는 단일 원인에서 발생 — 파라미터화로 한 번에 차단
2.ORM 사용 시에도 .raw() / .execute() 직접 호출은 동일 위험 — 모든 동적 SQL은 파라미터로
3.다음 챕터에서 XSS — 백엔드 SQL 방어와 별개로 프런트엔드 출력 인코딩이 필요한 이유 학습
공유하기
진행도 79 / 84