DB

[MSSQL] 프로시저 동적쿼리 작성시 SQL Injection 우회 방법

91cm 2019. 6. 3. 23:37


팀Slack방에 에러가 올라와서 Kibana에서 trace를 찾아보니 


상품명 조회 파라미터에 ' (싱글쿼터)가 들어 온 것이었다.


" 엥? lucy필터를 사용하고 있을탠대??? "

(lucy필터는 네이버에서 만든 XSS 방지 라이브러리이다)

싱글쿼터가 들어오면 오면 치환이 되어야 됬는대 안된 것이다.


싱글쿼터가 들어오게되면 SQL Injection에 취약해 질수 있기 때문에 매우 위험한 상황이었다.


(최근에 "여기어*" 라는 숙박 서비스가 SQL Injection으로 약 100만명의 정보가 유출된 사건도 있었다.)


그래서 우리팀의 담당하고 있는 매뉴들에 대해서 a' or 1=1 or 'a'='a와 같은 문자열을 입력하니.


모든 데이터가 촥! ...OTL


이제서야 알게되었지만 lucy필터는 form-data에 대해서만 적용되고


Request Raw Body로 넘어가는 JSON에 대해서는 처리해 주지 않는다 


어설프게 그냥 용도 정도만 " lucy필터가 XSS처리해주는 구나~" 하고 대충 넘어 갔던게 후회됬다..


(참고 lucy-xss-servlet-filter의 한계)


팀내의 주요 API는 모두 RequestBody를 통해서 데이터를 받고 있었다. 그러니 당연히 필터링이 전혀안될 수 밖에.


이 이슈에대해서 팀원들과 회의를 한 결과 공수가 적게 드는 쪽으로 방향을 맞추기로 했다.


팀내 특성상(?) 쿼리를 프로시저에 작성하고있는대.


프로시저중에서도 문자열을 합쳐서 만든 동적쿼리들에 대해서만 문제가 발생했다.


아래는 검색화면에서 사용하는 SP와 유사하게 만든 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
USE [cm]
GO
/****** Object:  StoredProcedure [dbo].[SQLINJECTION]    Script Date: 2019-06-04 오후 9:27:27 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
 
ALTER PROCEDURE [dbo].[SQLINJECTION] 
    @pi_searchKeyword    NVARCHAR(MAX)
    ,@pi_start_date        NVARCHAR(10)
    ,@pi_end_date        NVARCHAR(10)
AS
BEGIN
 
    SET NOCOUNT ON;
    
    DECLARE 
        @v_select_sql NVARCHAR(MAX) = ''
        ,@v_from_sql NVARCHAR(MAX) = ''
        ,@v_where_sql NVARCHAR(MAX) = ''
        ,@v_sql  NVARCHAR(MAX) =''
 
    SET @v_select_sql += 'select top 100 * '
    
    SET @v_from_sql += ' from tb_user with(nolock) '
 
    IF @pi_start_date <> '' and @pi_end_date <> ''
    BEGIN
        SET @v_where_sql += ' where user_reg_date between ''' + @pi_start_date + ''' and '''+ @pi_end_date + ''''
    END 
 
    IF @pi_searchKeyword <> ''
    BEGIN
        SET @v_where_sql += 'and user_name='+ '''' + @pi_searchKeyword + ''''
    END
 
    SET @v_sql = @v_select_sql + @v_from_sql + @v_where_sql    
 
    PRINT @v_sql
 
    EXECUTE SP_EXECUTESQL @v_sql
 
END
 
cs



SQL Injection은 바로 35행에서 발생한다.


문자열인 싱글쿼터로 값을 감싸고 있기때문이다


myBatis의 #{}을 쓴다해도, 프로시저 내부에서 싱글쿼터를 붙이기때문에 무용지물이다.






그래서 어떻게 처리했는대?


QUOTENAME()함수를 사용해서 처리했다.


https://docs.microsoft.com/ko-kr/sql/t-sql/functions/quotename-transact-sql?view=sql-server-2017


특정 구분자를 양옆으로 감싸주는 것이다.(문자열에 대해서만 처리하였음)


22행, 35행, 36행이 수정한 내용이다


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
USE [cm]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
 
ALTER PROCEDURE [dbo].[SQLINJECTION] 
    @pi_searchKeyword    NVARCHAR(MAX)
    ,@pi_start_date        NVARCHAR(10)
    ,@pi_end_date        NVARCHAR(10)
AS
BEGIN
 
    SET NOCOUNT ON;
    
    DECLARE 
        @v_select_sql NVARCHAR(MAX) = ''
        ,@v_from_sql NVARCHAR(MAX) = ''
        ,@v_where_sql NVARCHAR(MAX) = ''
        ,@v_sql  NVARCHAR(MAX) =''
        ,@v_quote NVARCHAR(MAX)
 
    SET @v_select_sql += 'select top 100 * '
    
    SET @v_from_sql += ' from tb_user with(nolock) '
 
    IF @pi_start_date <> '' and @pi_end_date <> ''
    BEGIN
        SET @v_where_sql += ' where user_reg_date between ''' + @pi_start_date + ''' and '''+ @pi_end_date + ''''
    END 
 
    IF @pi_searchKeyword <> ''
    BEGIN
        SET @v_quote = QUOTENAME(@pi_searchKeyword,'''')
        SET @v_where_sql += 'and user_name=' + @v_quote
    END
 
    SET @v_sql = @v_select_sql + @v_from_sql + @v_where_sql    
 
    PRINT @v_sql
 
    EXECUTE SP_EXECUTESQL @v_sql
 
END
 
cs



수정전 

' or 1=1 or 'a'='a 입력시 실행되는 SP내용


1
select top 100 *  from tb_user with(nolock)  where user_reg_date between '2018-01-01' and '2019-12-31'and user_name='' or 1=1 or 'a'='a'
cs



수정후

' or 1=1 or 'a'='a 입력시 실행되는 SP내용

1
select top 100 *  from tb_user with(nolock)  where user_reg_date between '2018-01-01' and '2019-12-31'and user_name=''' or 1=1 or ''a''=''a'
cs


or 1=1 or 'a'='a 입력시 실행되는 SP내용

1
select top 100 *  from tb_user with(nolock)  where user_reg_date between '2018-01-01' and '2019-12-31'and user_name='or 1=1 or ''a''=''a'
cs


TO BE 쿼리의 뒤쪽 문자열을 보면 싱글쿼터로 인젝션을 무효화 시키는 것을 볼 수 있다.


처리하긴 하였지만 아쉬운점은 사실이다.  처음에 이 이슈가 발생했을 때 필터를 적용해보고 싶었지만..


그럴수 없는 상황이었기 때문에 이렇게 우회하는 방법으로 처리하였다.






더 좋은 방법이 있으면 댓글 부탁드립니다 ^^