이번에는 Mybatis로 구성한 application에서 mapper로 table명을 string으로 넘겨주고 동적테이블(Dynamic Table)로 조회(Select)하는 방법에 대해서 알아보겠습니다.
때로는 구성하는 application의 db구조가 동일한 구조의 데이터를 각 코드별로 구성을 하고, 해당 코드명으로 table명을 구성하는 경우가 생길 수 있습니다. 이럴 경우에는 mapper.java interface에서 parameter로 string의 table명을 전달하고, mapper.xml에서는 그 parameter를 sql의 from 이후에 배치하여 조회 query를 수행해야 합니다.
결국 parameter로 전송된 table명은 select query에 조합되어...
SELECT * FROM [table명];
이런 식으로 동작하기를 원하게 됩니다. 그렇다면, 우리에게 익숙한 방법으로 코딩을 해보겠습니다.
[Mapper.java]
@Mapper
public interface ABC {
ArrayList<DTO> EDF(@Param("id") String id);
}
[Mapper.xml]
<select id="DEF" resultType="com.ayoteralab.atproject.main.dto.DTO">
<![CDATA[
SELECT
*
FROM #{id} ORDER BY tDate DESC LIMIT 60;
]]>
</select>
자 이렇게 되면... 실제로 동작하는 query는...
SELECT * FROM #{id} ORDER BY tDate DESC LIMIT 60;
로 정상적으로 동작하기를 기대합니다. 하지만 아래의 Error가 발생합니다.
[ERROR]
### Error querying database.
Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax;
check the manual that corresponds to your MariaDB server version for the right syntax
to use near ''table_id' ORDER BY tDate DESC LIMIT 60' at line 3
### The error may exist in file [Mapper.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT * FROM ? ORDER BY tDate DESC LIMIT 60;
### Cause: java.sql.SQLSyntaxErrorException:
You have an error in your SQL syntax;
check the manual that corresponds to your MariaDB server version for the right syntax
to use near ''table_id' ORDER BY tDate DESC LIMIT 60' at line 3;
bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException:
You have an error in your SQL syntax; check the manual that corresponds to your
MariaDB server version for the right syntax to use near ''table_id' ORDER BY tDate DESC
LIMIT 60' at line 3] with root cause
그냥 느낌적인 느낌으로보면.... query문에 from옆에 table명이 들어가는 곳에 ' '가 추가되어 있는 것으로 보입니다. 실제로 parameter를 #{ }로 받게되면 String으로 입력시키기 위해서 작은따옴표가 추가됩니다. 하지만 동적으로 query를 하기 위해서는 작은따옴표가 추가가 되면 안 됩니다.
이때, 사용이 가능한 방법은 ${ }로 받는 방법이 있습니다. 이 방식은 작은따옴표(' ')가 추가가 되지 않기 때문에, 정상적으로 동작하게 됩니다. 하지만 여기에서는 큰 문제점이 존재합니다. 바로 SQL Injection 입니다.
SQL Injection은 개발자가 작성한 코드의 보안 상의 허점을 이용해, 문제를 야기할 수 있는 SQL문을 실행되게 함으로써 DB를 비정상적으로 조작하는 코드 인젝션 공격 방법입니다. 정리를 좀 해보자면....결국 mybatis를 사용해서는 parameter값을 #{ }, ${ } 이렇게 2가지로 받을 수 있고, 차이점은 아래와 같습니다.
- #{ } - 전달된 Parameter가 String형태로 인식하여, 자동으로 'Param' 으로 묶여서 Mapper.xml에 Parsing됨. 또한, 그 내부적으로는 PreparedStatement를 사용하여, 값을 Binding할 때 전달된 값의 특수문자나 쿼리등을 Filtering하여 SQL Injection을 막아줌.
- ${ } - 전달된 Parameter를 바로 Binding함. 따라서, SQL Injection에 취약할 수 밖에 없음.
결국... Error는 잡을 수 있지만 추가적인 SQL Injection처리를 해주어야 합니다.
SQL Injection 처리하기
위와 같이, ${ }를 통해서 Parameter를 바로 Binding하는 경우를 동적쿼리(Dynamic Query)라고 합니다. 이런 동적쿼리를 프로그램에 적용했을때, 발생할 수 있는 보안이슈가 바로 SQL Injection 입니다.
[발생조건]
중간에 체크로직이 없이 단순하게 입력을 받는 소스에서 전달된 Parameter를 받아 동적으로 SQL을 Binding하는 로직이 구현되어 있어, 해당 사이트를 노리는 해커가 해당 SQL Query의 의미를 수정하거나 원하는 명령을 실행할 수 있는 상태
[예시 1 - 특정 조건 데이터가 아닌 전체 데이터 출력]
SELECT
*
FROM detail_info
WHERE username = ${userName}
다음과 같이 userName을 Parameter로 받아 이를 동적으로 username과 비교하여, 세부 정보를 출력하는 query가 있다고 했을 때, 다른 사용자는 자신의 데이터를 제외하고는 나머지 정보가 보여지면 안 됩니다. 혹은 모든 사용자의 정보가 보이면 더더욱 안되겠죠??
하지만, 동적쿼리를 사용하기 때문에 아래와 같은 parameter를 전달해서 동적쿼리를 생성하면 어떻게 될까요??
String userName = "'name' OR 'a'='a'"
전달된 userName parameter는 동적으로 Binding되어서 아래와 같이 변경이 되고... 결국 전체를 select하는 query와 동일하게 동작하게 됩니다.
SELECT
*
FROM detail_info
WHERE username = 'name' OR 'a'='a';
===================================
SELECT
*
FROM detail_info;
결국, OR 'a' = 'a' 구문에 의해서 전체 query를 아래의 Select all 과 동일한 query로 동작합니다.
[예시 2 - 추가 Query를 적용]
SELECT
*
FROM detail_info
WHERE username = ${userName}
================================================
String userName = "'name'; DELETE FROM items;"
================================================
SELECT
*
FROM detail_info
WHERE username = 'name';
DELETE FROM items;
많은 종류의 DB에서는 여러개의 query를 ';'(세미콜론)으로 묶어서 한번에 실행하는 것을 지원합니다. 이 방법을 악용하여... 위의 예제와 같이, parameter에 추가 DELETE query를 넣어서 현재 Application 동작에 영향을 미칠 수 있습니다.
[해결방법]
우선적으로 동적쿼리 자체를 사용하지 않으면 됩니다. 즉 ${ } 대신에 #{ }을 사용합니다. 내부적으로 PreparedStatement가 사용되기 때문에 SQL Injection공격을 방지할 수 있습니다.
다른 방법은, 어쩔수 없이 ${ } 을 사용해야 한다면... parameter로 전달되는 값을 사전에 검증하여 처리하고, 안전한 값을 전달하는 방법입니다. 이 부분에 대해서는 추후에 계속 논의해 보겠습니다.
- Ayotera Lab -
댓글