메인 콘텐츠로 건너뛰기
Deep Thought
← 목록으로
Security

SQL 인젝션 공격과 방어 기법

신중선-- views
sql-injectionweb-securitydatabase-securitybackend-securityprepared-statement

SQL 인젝션이란?

SQL 인젝션(SQL Injection)은 웹 애플리케이션에서 사용자 입력값이 SQL 쿼리에 안전하게 처리되지 않을 때 발생하는 심각한 보안 취약점입니다. 공격자는 악의적인 SQL 코드를 주입하여 데이터베이스를 조작할 수 있습니다.

이 공격을 통해 인증 우회, 민감한 데이터 탈취, 데이터 변조, 심지어 테이블 삭제까지 가능합니다. OWASP Top 10에서 지속적으로 상위권을 차지할 정도로 중요한 보안 이슈입니다.

핵심 개념

1. 공격 원리와 예시

취약한 로그인 검증 코드를 살펴보겠습니다:

public boolean login(String username, String password) {
    String sql = "SELECT * FROM users WHERE username = '" + username + 
                 "' AND password = '" + password + "'";
    
    try (Connection conn = DriverManager.getConnection(url);
         Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery(sql)) {
        return rs.next();
    } catch (SQLException e) {
        throw new RuntimeException("Database error", e);
    }
}

공격자가 다음과 같이 입력하면:

  • username: admin' --
  • password: anything

생성되는 SQL 쿼리는:

SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'anything'

-- 이후는 주석 처리되어 비밀번호 조건이 무시되고, admin 계정으로 인증이 우회됩니다.

2. 다양한 공격 페이로드

공격자들이 자주 사용하는 페이로드들입니다:

-- 항상 참이 되는 조건으로 인증 우회
' OR '1'='1' --

-- 다른 테이블 정보 조회
' UNION SELECT username, password FROM admin_users --

-- 테이블 구조 파악
' UNION SELECT table_name, column_name FROM information_schema.columns --

-- 데이터베이스 삭제 (매우 위험)
'; DROP TABLE users; --

3. PreparedStatement를 활용한 방어

가장 효과적인 방어 방법은 PreparedStatement를 사용하는 것입니다:

public boolean login(String username, String password) {
    String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
    
    try (Connection conn = DriverManager.getConnection(url);
         PreparedStatement pstmt = conn.prepareStatement(sql)) {
        
        pstmt.setString(1, username);
        pstmt.setString(2, password);
        
        try (ResultSet rs = pstmt.executeQuery()) {
            return rs.next();
        }
    } catch (SQLException e) {
        throw new RuntimeException("Database error", e);
    }
}

PreparedStatement는 매개변수를 자동으로 이스케이프 처리하여 SQL 코드로 해석되지 않도록 합니다.

4. 종합적인 방어 전략

ORM 프레임워크 활용:

// JPA Query Methods (안전)
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsernameAndPassword(String username, String password);
}

// JPQL with Parameters (안전)
@Query("SELECT u FROM User u WHERE u.username = :username AND u.password = :password")
Optional<User> findByCredentials(@Param("username") String username, 
                                @Param("password") String password);

입력 검증 및 화이트리스트:

public boolean isValidUsername(String username) {
    // 알파벳, 숫자, 언더스코어만 허용
    return username.matches("^[a-zA-Z0-9_]+$") && username.length() <= 20;
}

최소 권한 원칙:

-- 애플리케이션용 DB 계정에는 최소 권한만 부여
GRANT SELECT, INSERT, UPDATE ON app_schema.users TO 'app_user'@'%';
-- DROP, ALTER 등의 DDL 권한은 부여하지 않음

정리

방어 기법 설명 효과성
PreparedStatement 매개변수를 별도로 바인딩하여 SQL 코드 주입 방지 매우 높음
ORM 프레임워크 JPA, Hibernate 등으로 직접 SQL 작성 회피 높음
입력 검증 화이트리스트 기반으로 허용된 문자만 통과 보조적
최소 권한 원칙 DB 계정에 필요한 최소 권한만 부여 보조적
에러 메시지 숨김 DB 오류 정보를 사용자에게 직접 노출 금지 보조적

SQL 인젝션은 예방 가능한 취약점입니다. PreparedStatement를 기본으로 사용하고, 다층 방어 전략을 구축하여 안전한 웹 애플리케이션을 개발해야 합니다.

References