SQL 인젝션이란?
사용자 입력값을 조작하여 데이터베이스를 공격하는 해킹 기법
SQL 인젝션(SQL Injection)은 웹 애플리케이션이 사용자 입력의 유효성을 제대로 검사하지 않을 때 발생하는 보안 취약점이다.
해커는 악의적인 SQL 명령어를 입력창에 삽입하여 데이터베이스를 마음대로 조작할 수 있다.
SQL 인젝션의 위험성
- 데이터베이스 정보 유출(사용자 정보, 비밀번호 등)
- 데이터 조작 및 삭제(전체 데이터 손실 가능)
- 관리자 권한 탈취(시스템 완전 장악)

예시
예를 들어 로그인 검증 시 해당 코드를 사용한다고 가정해보자
// 취약 코드
public ResponseEntity<?> 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);
}
}
공격 시나리오
입력값:
- 아이디: admin' --
- 비밀번호: 1234 (아무거나)
생성되는 SQL 쿼리:
SELECT * FROM users WHERE username = 'admin' --' AND password = '1234';
실제 실행되는 쿼리:
SELECT * FROM users WHERE username = 'admin'
-- 여기서부터 주석 처리: ' AND password = '1234';
분석:
- --는 SQL에서 주석을 의미
- 비밀번호 검증 부분이 통째로 무시됨
- 결과: 비밀번호 없이 admin 계정 로그인 성공!
이처럼 해커는 인증을 우회하거나 DB 정보를 탈취할 수 있다.
SQL Injection 방지방법
1. PreparedStatement 사용
PreparedStatement는 SQL의 틀을 미리 만들어 놓고 사용자의 입력은 문법이 아닌 단순 데이터로만 취급한다. 그래서 악의적인 코드가 주입되어도 원래의 쿼리에 영향을 주지 않아 SQL 인젝션공격을 원천적으로 막을 수 있다.
// 안전한 코드
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); // 두 번째 ? 에 값 설정
ResultSet rs = pstmt.executeQuery();
return rs.next();
} catch (SQLException e) {
throw new RuntimeException("Database error", e);
}
}
왜 안전한가요?
사용자가 admin' -- 를 입력해도
→ PreparedStatement는 이것을 "admin' --" 라는 단순 문자열로만 인식
→ SQL 명령어로 해석하지 않음
→ 실제 username이 "admin' --"인 사용자를 찾으려 시도
→ 공격 실패!
동작 원리는 이러하다
// 1단계: SQL 틀 생성 (컴파일)
"SELECT * FROM users WHERE username = ? AND password = ?"
// 2단계: 데이터 삽입 (문자열로만 처리)
? → "admin' --" (그냥 문자열 데이터)
? → "1234" (그냥 문자열 데이터)
// 최종 쿼리
SELECT * FROM users WHERE username = 'admin'' --' AND password = '1234'
↑ 작은따옴표가 이스케이프 처리됨
추가로 JPA, Hibernate와 같은 ORM을 사용하면 SQL 쿼리를 직접 작성하지 않아도 데이터베이스와 상호작용할 수 있다.
MyBatis
<!-- 안전: #{} 사용 -->
WHERE username = #{username}
<!-- 위험: ${} 사용 -->
WHERE username = '${username}'
JPA
// JPA는 자동으로 PreparedStatement 사용
@Query("SELECT u FROM User u WHERE u.username = :username")
User findByUsername(@Param("username") String username);
실무 팁
하지 말 것
// 문자열 연결
String sql = "SELECT * FROM users WHERE id = " + userId;
// MyBatis에서 ${} 사용
WHERE username = '${username}'
지켜야 할 것
// PreparedStatement
String sql = "SELECT * FROM users WHERE id = ?";
pstmt.setString(1, userId);
// MyBatis에서 #{} 사용
WHERE username = #{username}