springboot mybatis ${} 사용시 sql injection 방어하기

개발환경

springboot

jdk1.8

mysql

mybatis

들어가기

spring 개발을 할때, 난 일반적으로 mybatis를 사용했다.

음 빨리 JPA도 공부를 해야 할거 같다.

일반적으로 mybatis를 사용할 경우 ${param1}, #{param2} 이렇게 두가지 방식으로 파라미터를 받을 수 있다.

나는 보통 #{param2} 를 사용해서, 파라미터를 받는다.

1
2
3
4
SELECT *
FROM USERS
WHERE 1=1
AND USER_ID = #{userId}

#{} 는 내부적으로 PreparedStatement를 사용하기 때문에 SQLInjection 공격에 안전하기 때문이다.

참고로 PreparedStatement는 값을 바인딩 하는 시점에서 전달된 값에 대한 특수문자, 쿼리등을 필터링하여 SQLinjection을 막는다.

이렇게 mybatis를 사용할때 보통 #{}를 사용하면 평화롭고, 안전한 코딩을 할 수 있다.

하지만 간혹 ${}를 사용하고 싶을 때가 있다.

바로 쿼리의 파라미터로 컬럼명을 화면에서 받고 싶은 경우인데,

보통 select 쿼리에 호출시 정렬을 하고 싶을 때 이다.

1
2
3
4
5
6
7
SELECT
USER_ID
,USER_NAME
,USER_AGE
FROM USERS
WHERE 1=1
ORDER BY ${sort_column} ${sort_type}

위처럼 동적으로 파라미터를 외부에 주입 받기 위해서는 #{}가 아닌 ${}를 사용 해야 한다.

${}는 입력받은 파라미터를 쿼리에 직접 치환해주기 때문이다.

하지만 여기에는 SQLInjection에 대한 위험이 존재한다.

어떻게 하면 ${}를 사용하면서, SQL Injection을 방어 할 수 있을까?

스프링 필터를 사용해야하나? AOP를 사용해야 하나?

고민하다가 인터넷에서 괜찮은 라이브리러를 찾았다.

어노테이션으로 SQL Injection 방어

https://github.com/rkpunjal/sql-injection-safe/

sql-injection-safe 라는 라이브러리이다.

아이디어는 어노테이션으로 VO의 필드에 SqlInjection을 방어하는 방법이다.

아래로 가보면 간단하게 테스트 할수 있는 예제 샘플도 있다.

https://github.com/rkpunjal/sql-safe-annotation-example

일단 동적으로 sql의 컬럼을 받을 VO를 만들어보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
class GridDto{
private String sortColumn;
private String sortType;

//getter, setter 생략
}

class SomethingGridDto extends GridDto {
private int column_1;
private int column_2;
private int column_3;
//getter, setter 생략
}

위처럼 대충 DTO를 만들었다.

자 이제 라이브러리를 추가하자. pom.xml 아래처럼 추가하자.

1
2
3
4
5
<dependency>
<groupId>com.github.rkpunjal.sqlsafe</groupId>
<artifactId>sql-injection-safe</artifactId>
<version>1.0.2</version>
</dependency>

라이브러리가 추가 되었다면, Sql Injection 위험이 있는 DTO의 필드에 @SQLInjectionSafe 어노테이션을 추가해준다.

1
2
3
4
5
6
class GridDto{
private @SQLInjectionSafe String sortColumn;
private @SQLInjectionSafe String sortType;

//getter, setter 생략
}

그리고 해당 DTO를 받은 컨트롤러에서 @valid를 적용해주면 된다.

굉장히 간단하게 스프링 컨트롤러에서 전달되는 파라미터에 어노테이션으로 SQL Injection을 방어 할 수 있다.

1
2
3
4
@RequestMapping(value = "/queryGrid", produces=APPLICATION_JSON_UTF_8)
public @ResponseBody WebResponse queryGrid(@Valid @ModelAttribute() SomethingGridDto paramDto) {
// ...
}

@Valid 가 동작하면서 @SQLInjectionSafe 동작한다.

만약 sortColumn 멤버변수에 SQL Injection의 위험이 되는 문구가 있는 경우 BindException 이 발생하게된다.

1
2
3
4
5
6
@ExceptionHandler(BindException.class)
public @ResponseBody WebResponse handleBindException(BindException be ){
return new MyResponseObject(false,
getBindExceptionMessage(be) // custom method to find and send an appropriate response
);
}

https://github.com/rkpunjal/sql-safe-annotation-example

위 예제 샘플을 받아서 구동해보면 아래처럼 @SQLInjectionSafe 내용을 확인 할 수 있다.

SQLInjection을 정규식으로 잡아내는 것을 확인 할 수 있다.

뭔가 부족하다면 커스텀 하면 될거 같다.

SQLInjectionSafe

참고자료

https://lng1982.tistory.com/246

https://m.blog.naver.com/PostView.nhn?blogId=blogpyh&logNo=220675109307&proxyReferer=https%3A%2F%2Fwww.google.com%2F

https://hackbyr0k.tistory.com/2