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

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

✅ 학습 진행도
  • 진단 시작
  • 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이나 + 연산자로 쿼리를 만들면, 데이터베이스 엔진은 어디까지가 코드이고 어디까지가 데이터인지 구별할 수 없게 되어 공격이 가능해집니다.