사용자 입력이 데이터가 아니라 SQL 문법으로 해석되게 만드는 공격. 웹 취약점의 고전이자 여전히 흔하다.
어떻게 일어나나
// ❌ 문자열을 이어 붙였다
String sql = "SELECT * FROM member WHERE email = '" + input + "'";
-- input 이 정상일 때
SELECT * FROM member WHERE email = 'a@b.com';
-- input 이 ' OR '1'='1 일 때
SELECT * FROM member WHERE email = '' OR '1'='1'; -- 전체 행이 반환된다
-- input 이 '; DROP TABLE member; -- 일 때
SELECT * FROM member WHERE email = ''; DROP TABLE member; --';
따옴표를 닫고 문법을 이어 쓰는 것이 공격의 본질이다.
왜 파라미터 바인딩이 막아 주나
// ✅ SQL 뼈대와 값을 분리해 보낸다
PreparedStatement ps = con.prepareStatement("SELECT * FROM member WHERE email = ?");
ps.setString(1, input);
-
① SQL 뼈대를 먼저 DB 에 보내 파싱시킨다 → 실행 계획의 구조가 이 시점에 확정된다
-
② 값은 나중에 별도로 보낸다 → 파싱이 이미 끝났으므로 문법으로 해석될 여지가 없다
-
입력에 무엇이 들어와도 '값' 으로만 취급된다
이스케이프 처리가 아니라 구조적 분리라는 점이 중요하다. 이스케이프는 빠뜨릴 수 있지만 바인딩은 원천적으로 불가능하게 만든다.
바인딩으로 못 막는 곳
// 테이블명 · 칼럼명 · ORDER BY · ASC/DESC 는 바인딩할 수 없다
String sql = "SELECT * FROM member ORDER BY " + sortColumn; // ❌ 위험
// ✅ 화이트리스트로 검증한다
Set<String> ALLOWED = Set.of("created_at", "name", "email");
if (!ALLOWED.contains(sortColumn)) throw new IllegalArgumentException();
정렬 칼럼을 파라미터로 받는 목록 API가 실무에서 가장 자주 뚫리는 지점이다.
ORM을 써도 안전하지 않은 경우
// JPQL 도 문자열을 이어 붙이면 똑같이 뚫린다
em.createQuery("SELECT m FROM Member m WHERE m.email = '" + input + "'"); // ❌
em.createQuery("SELECT m FROM Member m WHERE m.email = :email") // ✅
.setParameter("email", input);
방어를 겹친다
- ① 파라미터 바인딩 — — 근본 대책
- ② 화이트리스트 검증 — — 바인딩 못 하는 식별자
- ③ 최소 권한 계정 — — 뚫려도 DROP TABLE 이 실패하게
- ④ 오류 메시지를 노출하지 않는다 — 스키마 정보가 공격자에게 흘러가지 않게
면접 함정
- ❌ "특수문자를 필터링하면 된다" → 우회 기법이 많다. 바인딩이 정답이다.
- ❌ "ORM을 쓰면 안전하다" → 네이티브 쿼리나 문자열 결합에서는 그대로 뚫린다.