데이터베이스 (3) - SQL Injection
1. 개요
SQL 인젝션(SQL Injection) 은 사용자가 입력한 값이 SQL 문장의 일부로 해석되도록 만들어, 의도하지 않은 조회나 변경을 일으키는 공격이다. 로그인, 검색, 게시글 조회처럼 외부 입력을 데이터베이스 쿼리에 사용하는 기능에서 발생할 수 있다.
핵심 원인은 입력값을 SQL 문자열에 그대로 이어 붙이는 것이다. 입력값은 데이터로만 다루고, SQL 구조와 분리해야 한다.
2. 어떻게 발생할까?
다음처럼 문자열을 연결해 쿼리를 만들면 문제가 생길 수 있다.
String name = "민수' OR '1' = '1"; // 악의적으로 조작한 입력값
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
위 코드는 다음 SQL을 만든다.
SELECT * FROM users WHERE name = '민수' OR '1' = '1';
'1' = '1'은 항상 참이므로, 이 조건이 OR 뒤에 붙으면 WHERE 절 전체도 참이 된다. 즉 이름이 민수가 아닌 행까지 조회될 수 있다. 이름처럼 보여도 name은 사용자가 직접 입력하는 값이므로, 문자열을 직접 이어 붙이면 안 된다.
| 원인 | 가능한 영향 |
|---|---|
| 입력값을 SQL 문자열에 직접 연결 | 조회 조건 변경, 인증 우회 |
| 상세한 데이터베이스 오류를 화면에 노출 | 테이블·열 구조 추측 |
| 과도한 데이터베이스 권한 | 데이터 변경 또는 삭제 범위 확대 |
3. 파라미터 바인딩으로 막기
가장 중요한 대응은 준비된 쿼리(Prepared Statement) 또는 파라미터 바인딩을 사용하는 것이다. SQL 구조는 미리 작성하고, 입력값은 별도의 파라미터로 전달한다.
String sql = "SELECT * FROM users WHERE email = ?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, email);
ResultSet result = statement.executeQuery();
이 방식에서 email은 SQL 문법이 아니라 값으로 처리된다. 따라서 입력에 SQL처럼 보이는 문자가 있어도 쿼리 구조를 바꾸지 못한다. 사용하는 언어와 ORM이 달라도, 문자열 조합 대신 해당 도구의 바인딩 기능을 선택하는 원칙은 같다.
4. 함께 지켜야 할 점
특수문자를 막는 방식만으로는 모든 상황을 안전하게 처리하기 어렵다. 파라미터 바인딩을 기본으로 적용하고, 다음 원칙을 함께 지키는 것이 좋다.
- 데이터베이스 계정에는 기능 수행에 필요한 최소 권한만 부여한다.
- 사용자에게는 상세 SQL 오류 대신 일반적인 오류 메시지를 보여 준다.
- 입력값의 형식과 길이를 검증한다. 예를 들어 숫자 ID에는 숫자만 허용한다.
- 라이브러리와 데이터베이스 드라이버를 최신 보안 패치 상태로 유지한다.
5. 정리
SQL 인젝션은 사용자 입력과 SQL 문장을 섞을 때 발생한다. 입력값을 신뢰하지 않고, 항상 파라미터 바인딩으로 SQL 구조와 데이터를 분리해야 한다.
입력 검증, 오류 정보 제한, 최소 권한은 파라미터 바인딩을 보완하는 안전장치다. 데이터베이스를 사용하는 모든 기능에서 이 원칙을 일관되게 적용하는 것이 중요하다.