SQL Injection은 단순한 입력값 오류가 아니라, 애플리케이션 전체 보안의 허점을 파고드는 위험한 취약점입니다. 실무에서는 인증 우회, 고객 데이터 유출, DB 서버 제어 권한 획득 등 치명적인 결과를 초래할 수 있습니다.
이 글은 Java 11과 Spring Boot 환경을 기반으로, SQL Injection의 원리부터 취약한 코드 분석, 대응 전략, 그리고 안전한 코드 패턴까지 실제 사례 중심으로 설명합니다. 초급 개발자부터 보안 아키텍트까지 참고할 수 있는 실무 기반 가이드를 제공합니다.
전체 흐름은 다음과 같은 5단계로 구성됩니다. 각 단계에는 설명, 코드 예시, 실무 팁이 포함되어 있어 블로그, 강의, 보안 리뷰 문서로도 재활용이 가능합니다.
바로가는 목차
2. 취약한 코드 사례 – 인증 우회 가능한 SQL
아래 코드는 JDBC를 이용하여 사용자의 입력값을 직접 SQL 문자열에 삽입하는 방식으로 작성되었습니다. 이 방식은 매우 흔하며, 입력값에 따라 쿼리 구조가 변경되므로 SQL Injection에 매우 취약합니다.
// 로그인 검증을 위한 취약한 코드 예시
public boolean isAuthenticated(String username, String password) {
String sql = "SELECT * FROM users WHERE username = '" + username +
"' AND password = '" + password + "'";
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
return rs.next(); // 결과가 있으면 로그인 성공
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
위 코드에서 사용자가 username 입력에 ' OR '1'='1
과 같은 값을 입력하면 쿼리의 WHERE 조건이 항상 true가 되어 인증이 우회됩니다. 이로 인해 모든 계정에 접근이 가능하며, 심각한 보안사고로 이어질 수 있습니다.
3. 안전한 코드 설계 – PreparedStatement & JPA
SQL Injection을 막기 위해 가장 효과적인 방법은 쿼리 구조를 고정하고 사용자 입력을 파라미터 바인딩으로 처리하는 것입니다. JDBC에서는 PreparedStatement, Spring Boot에서는 JPA 또는 QueryDSL을 활용하는 것이 일반적입니다.
// JDBC + PreparedStatement 방식의 안전한 로그인 검증
public boolean loginSecure(String username, String password) {
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(query)) {
pstmt.setString(1, username);
pstmt.setString(2, password);
try (ResultSet rs = pstmt.executeQuery()) {
return rs.next();
}
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
// Spring Data JPA를 활용한 안전한 쿼리
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional findByUsernameAndPassword(String username, String password);
}
PreparedStatement는 입력값을 쿼리 구조에 영향을 주지 않는 파라미터로 처리하기 때문에, SQL Injection을 원천적으로 차단합니다. 또한 JPA는 내부적으로 이 구조를 자동화하여 제공하므로 보안과 유지보수 모두에 효과적입니다.
4. 실무 Q&A – 토큰 방식과 보안 설정
JPA는 기본적으로 PreparedStatement를 사용해 SQL Injection에 강하지만, Native Query 또는 문자열 조합 방식의 JPQL을 사용할 경우 취약해질 수 있습니다. @Param 바인딩을 통해 값을 고정하는 것이 필수입니다.
QueryDSL은 타입 세이프한 API로 대부분 안전하지만, Expressions.stringTemplate()
같은 함수로 문자열을 직접 조합하면 취약점이 발생할 수 있습니다. QueryDSL도 내부적으로 PreparedStatement를 사용하지만 동적 문자열은 예외입니다.
Spring Security는 인증과 인가 처리에 집중된 보안 프레임워크이며, SQL Injection 자체를 막아주진 않습니다. 데이터 접근 계층(Repository, DAO 등)은 여전히 개발자가 직접 관리해야 하며, 시큐어 코딩이 핵심입니다.
Native Query 자체가 문제는 아니며, 입력값을 문자열로 직접 연결하는 방식이 문제입니다. @Query 어노테이션과 함께 @Param을 사용하여 값 바인딩만 제대로 하면 안전하게 사용할 수 있습니다.
예. 대표적으로 sqlmap, Burp Suite, OWASP ZAP 등으로 자동화된 SQL Injection 테스트가 가능합니다. 특히 QA/보안 팀에서는 사전 정의된 쿼리 템플릿과 함께 동작 검증 테스트를 주기적으로 수행해야 합니다.
5. 요약 및 마무리
SQL Injection은 고전적인 공격이지만 지금도 여전히 많은 시스템에서 발생하고 있습니다. Spring Boot 환경에서는 PreparedStatement, JPA, QueryDSL 등을 적극 활용하여 쿼리 구조를 고정하고, 사용자 입력을 파라미터로 바인딩 처리하는 습관이 무엇보다 중요합니다.
- Statement → PreparedStatement 전환은 필수
- Native Query 사용 시 @Param 사용으로 바인딩
- 입력 검증 + 쿼리 고정 + ORM 사용이 3대 원칙
'시큐어코딩 > JAVA' 카테고리의 다른 글
[시큐어코딩] Java에서 NullPointerException 방지하는 안전한 코딩 전략 (2) | 2025.04.09 |
---|---|
[시큐어코딩]Java에서 CSRF(크로스사이트요청위조) 취약점 완벽 대응 가이드 (0) | 2025.04.09 |
[시큐어코딩]Java에서 서버사이드 요청 위조(SSRF) 완벽 대응 가이드 (0) | 2025.04.08 |
[시큐어코딩]Java에서 파일업로드 취약점, 완벽 대응 가이드!! (1) | 2025.04.07 |
[시큐어코딩]Java에서 XSS(크로스 사이트 스크립트) 취약점 완벽 대응 가이드 (0) | 2025.04.06 |