SQL 인젝션 방어 방법: PreparedStatement 기본 사용부터 MyBatis, JPA 적용까지
SQL 인젝션은 애플리케이션에서 사용자로부터 입력받은 데이터를 적절히 처리하지 않을 때 발생하는 보안 취약점이다. 데이터베이스에 직접적으로 악의적인 SQL 쿼리를 주입하는 이 공격은 시스템의 보안을 심각하게 위협한다.
이를 방어하는 세 가지 방법을 알아보자
1. PreparedStatement 기본 방법
웹 애플리케이션에서 사용자의 로그인 정보를 확인하는 간단한 SQL 쿼리가 있다고 가정하자.
SELECT * FROM users WHERE username = '사용자입력값' AND password = '사용자입력값';
사용자가 정상적인 값을 입력하는 것이 기대되지만, 공격자가 아이디 입력란에 ' OR '1'='1
같은 값을 입력할 경우 쿼리는 다음과 같이 변형된다.
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '사용자입력값';
이 쿼리는 항상 참이 되므로, 공격자는 비밀번호를 모르더라도 시스템에 접근할 수 있게 된다.
PreparedStatement는 SQL 인젝션을 방어하는 전통적인 방법이다. 이 방식은 SQL 쿼리의 구조를 미리 정의하고 사용자 입력값을 파라미터로 처리한다.
방어 과정은 다음과 같다.
1. 쿼리 정의
먼저 SQL 쿼리의 구조를 정의한다. 이때, 사용자의 입력이 들어갈 부분은 ?로 표시한다.
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
2. 입력값 바인딩
사용자로부터 입력 받은 값을 ?에 바인딩한다. 이때 입력값은 문자열로 처리되어, 쿼리의 일부로 해석되지 않는다.
preparedStatement.setString(1, username); // 첫 번째 '?'에 username 바인딩
preparedStatement.setString(2, password); // 두 번째 '?'에 password 바인딩
3. 쿼리 실행
정의된 쿼리를 데이터베이스에 전송하고 실행한다.
ResultSet resultSet = preparedStatement.executeQuery();
전체 코드를 보면 다음과 같다. 이 과정을 거치면, 사용자의 입력값이 쿼리의 구조를 변경할 수 없게 되므로, SQL 인젝션 공격을 방어할 수 있다.
// PreparedStatement 사용 예시
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
try (Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
ResultSet resultSet = preparedStatement.executeQuery();
// 결과 처리
}
여기에 추가적인 보안 조치를 추가해줘도 좋다.
입력값 검증
사용자 입력값에 대한 검증을 실시한다. 예를 들어, 아이디나 비밀번호에 허용되지 않는 문자가 있는지 체크한다.
에러 메시지 관리
데이터베이스 오류가 발생했을 때 구체적인 에러 메시지를 사용자에게 보여주지 않는다. 이는 공격자에게 시스템에 대한 정보를 제공할 수 있다.
최소 권한 원칙
데이터베이스에 접근하는 계정은 필요한 최소한의 권한만을 가지고 있어야 한다. 예를 들어, 일반 사용자 정보를 조회하는데 관리자 권한이 필요하지 않다.
그러나 요즘 애플리케이션 개발에서는 직접 SQL 쿼리를 작성하여 데이터베이스에 접근하는 방식보다는 ORM(Object-Relational Mapping) 프레임워크를 사용하는 것이 일반적이다. MyBatis와 JPA(Java Persistence API)는 이러한 ORM 프레임워크의 예시이다.
2. MyBatis에서 SQL 인젝션 방어
MyBatis는 SQL 쿼리 매핑을 제공하면서도 개발자에게 SQL 쿼리에 대한 더 많은 제어를 허용한다. 이를 사용할 때 SQL 인젝션 방어를 위한 몇 가지 중요한 점이 있다:
파라미터 바인딩 사용
MyBatis는 파라미터 바인딩을 통해 SQL 인젝션을 방어한다. 이는 MyBatis가 내부적으로 PreparedStatement와 유사한 메커니즘을 사용하기 때문이다. MyBatis의 매퍼 XML 파일 또는 어노테이션에서 #{} 구문을 사용하여 파라미터를 안전하게 바인딩한다.
동적 SQL 사용 시 주의
MyBatis는 동적 SQL을 지원한다. ${} 구문을 사용하면 직접 문자열을 쿼리에 삽입할 수 있다. 하지만 이 방식은 SQL 인젝션의 위험이 있으므로, 가능한 한 #{} 구문을 사용해야 한다.
사용자 입력 검증
사용자로부터 받은 입력값에 대해 서버 측에서 검증을 수행해야 한다. 예를 들어, SQL 쿼리에 적합하지 않은 문자나 패턴이 있는지 확인한다.
<!-- MyBatis 매퍼 XML 파일 -->
<select id="selectUser" parameterType="map" resultType="User">
SELECT * FROM users
WHERE username = #{username} AND password = #{password}
</select>
이 예시에서 #{username}
과 #{password}
는 사용자로부터 받은 입력값을 안전하게 쿼리에 바인딩한다. MyBatis는 이 값을 적절히 이스케이프 처리하여 SQL 인젝션을 방어한다.
MyBatis를 사용할 때는 파라미터 바인딩(#{}
)을 적극적으로 활용하고, 사용자 입력값에 대한 검증을 철저히 수행해야 한다. 또한, 동적 SQL을 사용할 때는 SQL 인젝션 위험을 주의해야 한다. 이러한 방법을 통해 MyBatis 환경에서도 SQL 인젝션으로부터 애플리케이션을 보호할 수 있다.
MyBatis에서 ${}
와 #{}
는 SQL 쿼리에 파라미터를 삽입하는 방식을 나타내지만, 이들의 작동 방식에는 중요한 차이가 있다.
${} 사용법과 위험성
${} 구문은 파라미터 값을 SQL 쿼리에 직접 문자열로 삽입한다. 이것은 쿼리의 일부분으로 파라미터를 "문자 그대로" 넣는 것이다.
SELECT * FROM table WHERE column = ${value}
${}를 사용하면, 파라미터 값이 쿼리에 그대로 반영되므로 SQL 인젝션 공격에 취약해진다. 공격자가 악의적인 SQL 구문을 파라미터로 전달할 경우, 이 구문이 쿼리의 일부가 될 수 있기 때문이다.
#{} 사용법과 안전성
#{} 구문은 파라미터를 쿼리에 안전하게 삽입한다. 내부적으로는 PreparedStatement의 파라미터 바인딩과 유사하게 동작한다.
SELECT * FROM table WHERE column = #{value}
#{}를 사용하면, 파라미터 값이 쿼리의 구조를 변경할 수 없다. MyBatis는 이 값을 적절하게 이스케이프 처리하여 SQL 인젝션 공격을 방어한다.
아래는 예시 코드다.
<!-- 위험한 사용 예시: ${} 사용 -->
<select id="findUserByUsername" resultType="User">
SELECT * FROM users WHERE username = '${username}'
</select>
<!-- 안전한 사용 예시: #{} 사용 -->
<select id="findUserByUsername" resultType="User">
SELECT * FROM users WHERE username = #{username}
</select>
위험한 예시에서, 사용자가 username 파라미터로 ' OR '1'='1
와 같은 값을 전달하면, 이 값이 쿼리에 그대로 삽입되어 모든 사용자를 반환하는 쿼리가 될 수 있다. 반면, 안전한 예시에서는 MyBatis가 파라미터 값을 안전하게 처리하여 이러한 공격을 방어한다.
결론으로, #{}
구문은 SQL 인젝션 위험을 크게 줄이면서 파라미터를 안전하게 쿼리에 삽입한다. ${}
구문은 쿼리에 직접적으로 파라미터 값을 삽입하는 방식으로, SQL 인젝션 공격에 취약하다. 가능한 한 항상 #{}
구문을 사용하는 것이 안전하다.
3. JPA에서 SQL 인젝션 방어
요즘 더 많이 사용되는 JPA를 사용하는 상황에서도 SQL 인젝션을 방어하는 방법을 살펴보겠다. JPA(Java Persistence API)는 자체적으로 SQL 인젝션 방어 기능을 제공한다. 이는 JPA가 내부적으로 PreparedStatement를 사용하기 때문이다.
JPA에서는 엔티티 클래스와 리포지토리 인터페이스를 사용하여 데이터베이스와의 상호작용을 추상화한다. 이때, JPA의 메소드들은 SQL 인젝션 공격에 취약하지 않는 방식으로 쿼리를 생성한다.
예를 들어, 사용자의 username과 password를 기준으로 사용자를 찾는 JPA 리포지토리 메소드는 다음과 같이 작성될 수 있다:
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsernameAndPassword(String username, String password);
}
이 메소드는 username과 password를 파라미터로 받아 해당 조건에 맞는 사용자를 찾는다. JPA가 제공하는 메소드 네이밍 규칙을 사용하여 자동으로 적절한 쿼리를 생성하며, 이 쿼리는 PreparedStatement와 유사한 방식으로 작동하여 SQL 인젝션 공격에 강하다.
JPA가 내부적으로 PreparedStatement를 사용한다는 말은, JPA가 데이터베이스 쿼리를 실행할 때 자동으로 파라미터를 바인딩하는 안전한 방식을 채택한다는 의미이다.
이 과정을 자세히 설명하면 다음과 같다:
1. 쿼리 생성 및 파라미터 바인딩
JPA는 개발자가 작성한 메소드 이름(예: findByUsernameAndPassword)을 기반으로 적절한 SQL 쿼리를 자동으로 생성한다. 이때, 메소드의 파라미터(예: username, password)는 쿼리의 변수로 사용된다. JPA는 이러한 파라미터를 PreparedStatement의 바인딩 변수로 처리하여 SQL 쿼리에 안전하게 삽입한다.
2. PreparedStatement의 작동 원리
PreparedStatement는 SQL 인젝션 공격을 방지하기 위해 설계된 JDBC의 특별한 유형의 문장(statement)이다. 개발자가 SQL 쿼리를 작성할 때, 데이터 값이 들어갈 자리에 물음표(`?`)를 사용하여 구조를 정의한다. 실행 시, 이 물음표 자리에 실제 값이 바인딩되며, 이 값은 쿼리의 일부로 직접 해석되지 않는다.
3. 이스케이프 처리
PreparedStatement는 값을 쿼리에 삽입하기 전에 이스케이프 처리를 통해 SQL 인젝션 공격에 대한 취약점을 방어한다. 예를 들어, 사용자 입력값에 특수문자가 포함되어 있더라도, 이 값은 쿼리의 일부로 해석되지 않고, 단순한 문자열 값으로 처리된다.
4. 안전한 쿼리 실행
이렇게 생성된 안전한 쿼리는 데이터베이스에 전송되어 실행된다. JPA를 사용하면, 개발자는 이러한 세부적인 과정을 직접 관리할 필요 없이, JPA가 자동으로 처리해 주는 안전한 방식으로 데이터베이스 작업을 수행할 수 있다.
다시 말해 JPA는 PreparedStatement의 방식을 내부적으로 활용하여 자동으로 SQL 쿼리를 안전하게 생성하고 실행한다. 이를 통해 SQL 인젝션 공격으로부터 애플리케이션을 보호한다.
결국, JPA를 사용하는 경우, SQL 인젝션 방어는 크게 신경 쓸 필요가 없다. JPA가 제공하는 기본 메커니즘과 메소드들은 이미 이러한 보안 문제를 고려하여 설계되었다.
4. 간단 요약
SQL 인젝션은 사용자 입력을 제대로 처리하지 않을 때 발생하는 보안 취약점이다. 이를 방어하기 위한 방법으로 PreparedStatement 사용, MyBatis의 파라미터 바인딩, JPA의 내부 메커니즘 등이 있다.
PreparedStatement는 사용자 입력을 쿼리의 일부로 해석되지 않도록 처리한다. MyBatis는 #{}을 사용하여 파라미터를 안전하게 바인딩하고, JPA는 엔티티 클래스와 리포지토리 인터페이스를 통해 SQL 인젝션에 강한 쿼리를 자동으로 생성한다. 이러한 방법들을 통해 애플리케이션을 SQL 인젝션으로부터 보호할 수 있다.
'Spring > Spring 기초 지식' 카테고리의 다른 글
HTTP 메서드 이해: 멱등성, 안전성 및 사용자 권한 검증까지 (0) | 2024.02.05 |
---|---|
XSS 공격 방어: 입력 데이터 이스케이프, CSP, HTTPOnly 설정 (0) | 2023.12.17 |