본문 바로가기 ← 메인

💉 SQL 삽입 (SQL Injection) - 실습 시뮬레이터

CWE-89: Improper Neutralization of Special Elements used in an SQL Command

KISA Guide 행정안전부·KISA 개발보안 가이드 (2021) - 1.1 SQL 삽입 (p. 16)
✅ 학습 진행도
  • ⬜ 진단 시작
  • ⬜ Statement 제거
  • ⬜ PreparedStatement 적용
  • ⬜ 바인딩 변수(?) 사용
✏️ Java 코드 에디터 취약한 코드
UserDao.java 💡 PreparedStatement를 사용하여 쿼리를 안전하게 만드세요
import java.sql.*;

public class UserDao {
    public void getUser(Connection conn, String userId) throws SQLException {
        Statement stmt = null;
        ResultSet rs = null;
        
        try {
            // [취약점] 외부 입력값(userId)이 쿼리 문자열에 그대로 연결됨
            // 공격자가 ' OR '1'='1 입력을 주면 모든 사용자 정보가 유출됨
            String query = "SELECT * FROM users WHERE id = '" + userId + "'";
            
            stmt = conn.createStatement();
            rs = stmt.executeQuery(query);
            
            while(rs.next()) {
                System.out.println("User: " + rs.getString("username"));
            }
        } finally {
            if (rs != null) rs.close();
            if (stmt != null) stmt.close();
        }
    }
}
💡 수정 가이드
1. 쿼리 문자열 수정

입력값을 연결하는 대신 물음표(?)를 사용하세요.

String query = "SELECT * FROM users WHERE id = ?";
2. PreparedStatement 객체 사용

createStatement() 대신 prepareStatement(query)를 사용합니다.

PreparedStatement pstmt = conn.prepareStatement(query);
3. 값 바인딩

물음표 위치에 실제 값을 할당합니다.

pstmt.setString(1, userId);
📊 분석 결과 및 설명
🎯 목표

현재 코드는 문자열 결합을 통해 SQL 쿼리를 동적으로 생성하고 있습니다. 이는 SQL Injection 공격에 매우 취약합니다. PreparedStatement와 ? 바인딩 변수를 사용하여 쿼리의 구조와 데이터를 분리하세요.

⚠️ 현재 취약점

Dynamic SQL Generation

입력값 userId가 검증 없이 쿼리에 포함됩니다. 만약 입력값이 admin' --라면, 비밀번호 검증 없이 관리자 계정으로 로그인이 가능해집니다.

✅ 학습 진행도
  • ⬜ 진단 시작
  • ⬜ 문자열 포맷팅 제거
  • ⬜ 쿼리 파라미터 분리
  • ⬜ 튜플 형태로 전달
🐍 Python 코드 에디터 취약한 코드
db_access.py 💡 %s 포맷 스트링과 파라미터 바인딩을 사용하세요
import sqlite3

def get_user_info(user_id):
    conn = sqlite3.connect('example.db')
    cursor = conn.cursor()
    
    # [취약점] Python의 f-string이나 % 연산자로 쿼리 조작
    # 입력값이 그대로 쿼리의 일부가 되어 실행됨
    query = f"SELECT * FROM users WHERE id = '{user_id}'"
    
    try:
        # 쿼리 문자열 하나만 전달하고 있음
        cursor.execute(query)
        result = cursor.fetchone()
        return result
    finally:
        conn.close()
💡 수정 가이드
1. 쿼리 문자열 분리

값을 직접 넣지 말고, 데이터베이스 드라이버가 제공하는 플레이스홀더(? 또는 %s)를 사용합니다. (SQLite는 ? 사용)

query = "SELECT * FROM users WHERE id = ?"
2. execute 메서드 수정

두 번째 인자로 데이터를 튜플 형태로 전달합니다.

cursor.execute(query, (user_id,))
📊 분석 결과 및 설명
🎯 목표

파이썬의 DB API는 Parameterized Query를 지원합니다. 문자열 포맷팅(f-string, %) 대신, 쿼리와 데이터를 분리하여 execute() 메서드에 전달하도록 수정하세요.

⚠️ 현재 취약점

Improper Query Construction

f-string이나 + 연산자로 쿼리를 만들면, 데이터베이스 엔진은 어디까지가 코드이고 어디까지가 데이터인지 구별할 수 없게 되어 공격이 가능해집니다.

⚔️ 인터랙티브 공격 시뮬레이터

Burp Suite 프록시 인터셉터 화면을 통해 실제 SQL Injection 공격 과정을 시뮬레이션합니다. 요청 본문(Payload)을 직접 편집하여 전송하고, 서버에서 생성되는 쿼리문, 데이터베이스의 전체 테이블 스캔 동작, 그리고 시큐어 코딩(PreparedStatement) 방어가 어떻게 이를 완벽히 차단하는지 실시간으로 모니터링하세요.

v2026.6
Target: http://bank.com/login
Raw
Params
Headers
Hex
💡 공격 페이로드 프리셋 (클릭 시 자동 삽입):
🛡️ 시큐어 코딩 방어 (PreparedStatement) OFF
⚙️ 서버측 SQL 생성 엔진 대기 중
// [Dynamic String Concatenation Mode]
String query = "SELECT * FROM users WHERE id = '" + userId + "'";
🗄️ 데이터베이스 엔진 테이블 스캔 대기 중
idx id password role
1 admin SuperP@ssw0rd! Administrator
2 guest guestpwd123 User
3 alice alicepass User
💻 브라우저 화면 출력 대기 중
http://bank.com/login_result
시뮬레이션을 실행하면 화면에 출력 결과가 렌더링됩니다.
📰 실제 사고사례 & 표준 매핑
2008년 Heartland SQL 인젝션으로 약 1억 건의 카드 정보가, 2015년 TalkTalk에서도 SQLi로 대량 고객정보가 유출됐습니다.
🏷️ CWE-89 OWASP A03 Injection
※ 공개적으로 알려진 사례 기반 교육용 요약 · CWE/OWASP는 공개 표준 분류