[Web] SQL Injection

j-yong98·2026년 3월 4일

Web

목록 보기
3/3

SQL Injection

SQL Injection은 사용자의 입력값을 검증하지 않고 SQL에 그대로 포함 시켰을 때 발생하는 공격이다. 공격자는 입력값에 SQL 구문을 삽입하여 원래 의도하지 않은 쿼리를 실행하게 만든다. 에제를 통해 알아보자

String sql = "SELECT name FROM users WHERE id = '" + id + "' and pw = '" + pw + "'";

위 코드는 사용자의 id와 pw를 입력받아 사용자 정보를 조회하는 SQL을 생성한다. 하지만 사용자 입력값을 검증하거나 분리하지 않고 문자열로 SQL을 구성하고 있다.

만약 공격자가 다음과 같은 값을 입력한다면 문제가 발생한다.

pw = ' OR '1'='1

그러면 실제 실행되는 SQL은 다음과 같이 변한다.

SELECT name
FROM users
WHERE id = 'user'
AND pw = '' OR '1'='1'

여기서 '1'='1'은 항상 참이기 때문에 WHERE 조건이 무력화된다.

그 결과 조건을 만족하지 않는 데이터까지 조회될 수 있으며, 로그인 로직이라면 인증을 우회하는 상황이 발생할 수 있다.

이처럼 사용자 입력값이 SQL 문법의 일부로 해석되면 공격자가 쿼리의 동작을 조작할 수 있으며, 이를 SQL Injection이라고 한다.

SQL Injection 방어 방법

SQL Injection은 데이터와 SQL이 분리되지 않은 채로 실행되어 문제를 일으킨다.

바인딩 변수 사용

바인딩 변수를 사용하면 사용자 입력값을 SQL 문장에 직접 포함시키지 않고 데이터로 전달할 수 있다. 이 방식은 SQL 구조와 입력값을 분리하기 때문에 SQL Injection을 방지할 수 있다.

JDBC에서는 PreparedStatement를 통해 바인딩 변수를 사용할 수 있다.

String sql =
"SELECT * FROM users WHERE id = ? AND password = ?";

PreparedStatement ps = conn.prepareStatement(sql);

ps.setString(1, id);
ps.setString(2, password);

위 코드에서 ?는 바인딩 변수이며, 실제 값은 setString()을 통해 전달된다. 이때 데이터베이스는 SQL 구조를 먼저 파싱하고 이후에 값을 바인딩하기 때문에 사용자 입력값이 SQL 문법으로 해석되지 않는다.

프레임워크에서도 동일한 개념을 사용할 수 있다. MyBatis에서는 #{} 문법을 통해 바인딩 변수를 사용할 수 있다.

<select id="findUser">
SELECT *
FROM users
WHERE id = #{id}
AND pw = #{pw}
</select>

#{}는 내부적으로 PreparedStatement의 바인딩 변수로 변환되어 SQL Injection을 방지한다.

반면 MyBatis의 ${} 문법은 문자열 치환 방식이기 때문에 사용자 입력값이 SQL에 그대로 삽입된다.

WHERE id = '${id}'

이 방식은 SQL Injection 취약점이 발생할 수 있으므로 사용 시 주의가 필요하다.

입력값 검증

입력값 검증을 통해 SQL Injection을 방지할 수 있다. 입력값 검증은 사용자가 입력한 값이 애플리케이션에서 허용된 형식인지 확인하는 과정이다.

사용자 ID가 영문과 숫자만 허용되는 값이라면 다음과 같이 검증할 수 있다.

if(!id.matches("[a-zA-Z0-9]+")){
    throw new IllegalArgumentException("Invalid id format");
}

이처럼 애플리케이션에서 허용하지 않는 입력값을 사전에 차단하면 공격 시도를 줄일 수 있다.

0개의 댓글