
- 사용자 입력값이 SQL 문의 일부로 해석되도록 조작해, 개발자가 의도하지 않은 쿼리를 실행시키는 공격
- 입력값과 SQL 구문을 문자열로 이어 붙이는 코드에서 발생
String sql = "SELECT * FROM users WHERE id='" + id + "' AND pw='" + pw + "'";
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
만약 공격자가 위 쿼리의 id 칸에 ' OR '1'='1' -- 를 입력하면 쿼리가 아래와 같이 변경된다.
SELECT * FROM users WHERE id='' OR '1'='1' -- ' AND pw='...'
'로 id의 조건을 ''로 변경합니다.OR '1'='1'로 조건을 항상 참이 되게끔 합니다.--로 이후의 조건을 주석으로 처리합니다.' AND 1=CONVERT(int, @@version) --' UNION SELECT username, password FROM users --' AND 1=1 -- (정상) vs ' AND 1=2 -- (비정상)' AND IF(1=1, SLEEP(5), 0) --String sql = "SELECT * FROM users WHERE id = ? AND pw = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, id);
ps.setString(2, pw);
ResultSet rs = ps.executeQuery();
}
SQL 구조가 먼저 컴파일되고, 입력값은 나중에 "데이터"로만 바인딩됩니다.
따라서, ' OR '1'='1 을 넣어도 그냥 "id가 ' OR '1'='1인 사용자"를 찾는 문자열 비교가 됩니다.
// 형식 검증: 영문·숫자 4~20자만 허용
if (!id.matches("^[a-zA-Z0-9]{4,20}$")) {
throw new IllegalArgumentException("잘못된 아이디 형식");
}
// 정렬 컬럼처럼 ?로 바인딩할 수 없는 값은 허용 목록으로 제한
Set<String> ALLOWED = Set.of("name", "created_at");
if (!ALLOWED.contains(sortColumn)) {
throw new IllegalArgumentException("허용되지 않은 정렬 기준");
}
String sql = "SELECT * FROM users ORDER BY " + sortColumn;
ORDER BY 뒤의 컬럼명은 PreparedStatement의 ?로 바인딩할 수 없습니다. 그래서 이런 값은 허용 목록에 있는 것만 통과시키는 방식이 필요합니다.
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handle(Exception e) {
log.error("DB 오류", e); // 상세 내용은 서버 로그에만 기록
return ResponseEntity.status(500).body("요청 처리 중 오류가 발생했습니다.");
}
상세 오류는 로그에만 남기고 사용자에게는 일반 메시지만 보여줘 Error-based 공격을 차단합니다.
(나쁜 예) You have an error in your SQL syntax; ... near ''' at line 1
(좋은 예) 요청 처리 중 오류가 발생했습니다.
요청: GET /users?id=1' UNION SELECT username, password FROM users --
WAF : 'UNION SELECT' 패턴 탐지 → 403 Forbidden
(요청이 애플리케이션까지 도달하지 못함)
: 알려진 공격 패턴(예: UNION SELECT, OR 1=1, SLEEP()을 네트워크 단에서 차단하는 보조 수단(근본 해결은 PreparedStatement)