2012. 9. 17.

DBMS/MSSQL]새로운 SQL 잘라내기 공격 및 대처 방법


새로운 SQL 잘라내기 공격 및 대처 방법

이 기사에서 다루는 내용:
  • 식별자 및 문자열 구분
  • 유용한 T-SQL 함수
  • 잘라내기 및 수정 공격
  • 버그 및 취약점 찾기
이 기사에서 사용하는 기술:
SQL Server
SQL 주입을 이용하는 공격은 방화벽과 침입 검색 시스템을 통과해서 데이터 계층을 손상시킬 수 있다는 점 때문에 많은 관심을 끌었습니다. 기본 코드 패턴을 보면 1차 또는 2차 주입 모두 문을 생성할 때 신뢰할 수 없는 데이터를 사용한 경우에 발생하는 다른 주입 문제와 비슷합니다. 대부분의 개발자는 백 엔드에서 매개 변수가 있는 SQL 쿼리를 저장 프로시저와 함께 사용하여 웹 프런트 엔드의 취약점을 완화하고 있지만, 사용자 입력 기반의 DDL(데이터 정의 언어) 문을 생성하는 경우 또는 C/C++로 작성된 응용 프로그램의 경우에는 동적으로 생성된 SQL을 여전히 사용하고 있습니다.
이 기사에서는 구분 문자가 이스케이프된 코드일지라도 SQL 문을 수정하거나 SQL 코드를 주입할 수 있는 몇 가지 새로운 아이디어에 대해 설명합니다. 구분 식별자와 SQL 리터럴을 생성하는 유용한 방법을 몇 가지 살펴본 다음 응용 프로그램을 보호하는 데 도움이 될 수 있도록 공격자가 SQL 코드를 삽입할 때 사용하는 새로운 방법을 설명합니다.
식별자 및 문자열 구분

SQL Server™에는 테이블, 뷰 및 저장 프로시저와 같은 SQL 개체를 고유하게 식별하는 SQL 식별자와 데이터를 나타내는 리터럴 문자열이라는 두 가지 문자열 변수가 있습니다. SQL 식별자를 구분하는 방법은 데이터 문자열을 구분하는 방법과는 다릅니다. 이러한 데이터 변수를 사용해야 하는 동적 SQL을 생성하는 유용한 방법을 살펴보겠습니다.

SQL 개체 이름에 키워드가 사용되거나 개체 이름에 특수 문자가 들어 있는 경우 구분 식별자를 사용해야 합니다. my_dbreader라는 이름의 로그인을 삭제한다고 가정해 봅시다. 이 경우 다음 문을 실행하여 작업을 수행할 수 있습니다.
DROP LOGIN my_dbreader
키워드이기도 한 DROP을 이름으로 사용하는 로그인을 삭제하려는 경우에는 어떻게 해야 합니까? 다음 SQL 문을 사용하면 SQL Server에서 잘못된 구문 오류가 반환됩니다.
DROP LOGIN DROP
my][dbreader라는 이름의 로그인을 삭제하려는 경우에는 어떻게 합니까? 이 경우에도 잘못된 구문 오류가 반환됩니다.
두 예제 모두 로그인 이름이 키워드이거나 로그인 이름에 특수 문자가 들어 있기 때문에 SQL Server에서 SQL 문에 있는 개체 이름을 식별할 수 있도록 시작 및 끝 표시를 입력해야 합니다.
큰따옴표나 대괄호를 SQL 식별자의 구분 기호로 사용할 수 있지만 연결 기반 설정인 QUOTED_IDENTIFIER 설정을 사용하도록 설정한 경우에는 큰따옴표만 사용할 수 있습니다. 복잡하지 않도록 하기 위해 항상 대괄호를 사용하는 것도 좋은 방법입니다.
로그인 이름인 DROP을 삭제하기 위해 다음과 같이 대괄호를 사용하여 SQL 문을 생성할 수 있습니다.
DROP LOGIN [DROP]
그러나 다음 문은 어떻게 처리되겠습니까?
DROP LOGIN [my][dbreader]
이와 같은 특별한 경우에는 로그인 이름인 my][dbreader에 구분 문자가 들어 있으므로 SQL에서는 대괄호로 둘러싸인 [my]를 로그인 이름으로 간주합니다. 로그인 이름 뒤에 오는 [dbreader]는 올바른 SQL 문이 아니기 때문에 구문 오류가 발생합니다. 오른쪽 대괄호를 하나 더 사용하여 오른쪽 대괄호를 이스케이프하면 이 문제를 해결할 수 있습니다. 따라서 다음 문을 실행하면 SQL Server에서 로그인 my][dbreader가 삭제됩니다.
DROP LOGIN [my]][dbreader]
이스케이프 메커니즘은 간단히 오른쪽 대괄호를 두 번 표시하는 것입니다. 왼쪽 대괄호를 포함한 다른 어떤 문자도 변경할 필요가 없습니다.
구분 리터럴을 사용하는 것은 구분 SQL 식별자를 사용하는 것과 비슷하지만 사용해야 하는 구분 문자가 다르다는 데 기본적으로 차이가 있습니다. 비슷한 규칙을 사용하여 구분 문자열 리터럴을 만들기 전에 몇 가지 예제를 살펴보겠습니다.
암호가 P@$$w0rd인 로그인 이름 dbreader를 만든다고 가정해 봅시다. 이 경우 다음 SQL 문을 사용할 수 있습니다.
CREATE LOGIN [dbreader] WITH PASSWORD = 'P@$$w0rd'
이 문에서 P@$$w0rd는 작은따옴표로 구분된 문자열 데이터이므로 SQL에서 문자열의 시작과 끝이 인식됩니다. 그러나 문자열 데이터에 작은따옴표가 들어 있다면 어떻게 되겠습니까? 문이 유효하지 않기 때문에 SQL Server에서 오류가 발생합니다.
CREATE LOGIN [dbreader] WITH PASSWORD = 'P@$$'w0rd'
유효한 SQL 문을 만들려면 문자열에 있는 모든 작은따옴표를 이스케이프해야 합니다.
CREATE LOGIN [dbreader] WITH PASSWORD = 'P@$$''w0rd'
이 문을 실행하면 SQL Server에서 암호가 P@$$'w0rd인 로그인 dbreader가 만들어집니다.
큰 따옴표를 구분 기호로 사용할 수도 있지만 앞에서 설명했듯이 이 방법의 성공 여부는 전적으로 QUOTED_IDENTIFIER 설정의 사용 여부에 달려 있습니다. 결과적으로 항상 작은따옴표를 문자열 리터럴의 구분 기호로 사용하는 것이 좋습니다.

T-SQL 함수
지금까지 살펴본 것처럼 식별자와 문자열을 다루는 규칙은 비교적 간단하며 문자열을 미리 알고 있으면 수동으로 구분할 수 있습니다. 그러나 사용자 입력 기반의 동적 T-SQL 문을 생성할 경우에는 어떻겠습니까? 자동으로 이 작업을 수행할 수 있는 방법이 있어야 합니다. 구분 문자열을 준비하는 데 도움이 되는 QUOTENAME과 REPLACE라는 2개의 T-SQL 함수를 사용할 수 있습니다.
QUOTENAME은 입력 문자열을 유효한 식별자로 만들기 위해 구분 기호가 추가된 유니코드 문자열을 반환합니다. QUOTENAME 함수는 다음 구문을 사용합니다.
QUOTENAME ( 'string' [ , 'delimiter' ] )
QUOTENAME의 인수는 구분할 문자열과 구분 기호로 사용할 한 문자로 된 문자열입니다. 구분 기호로 대괄호, 작은따옴표 또는 큰따옴표를 사용할 수 있습니다.
이 함수는 주로 구분 SQL 식별자를 사용하기 위한 것이므로 SQL Server에서 nvarchar(128) 형식인 sysname만 받습니다. 또한 이 함수를 사용하여 구분 SQL 리터럴 문자열을 준비할 수 있지만 인수 길이 제한 때문에 128자 이하의 문자열에만 사용할 수 있습니다. 즉, 이러한 제한 때문에 REPLACE 함수를 사용하게 됩니다. 그림 1에서는 sp_addlogin이 QUOTENAME을 사용하여 구분된 로그인 이름 및 암호 문자열을 만드는 방법을 보여 줍니다. 그림에서 볼 수 있듯이 @loginname과 @passwd가 모두 sysname이므로 QUOTENAME 함수를 사용하여 구분 SQL 식별자와 구분 리터럴을 준비할 수 있습니다. 따라서 @loginname = 'my[]dbreader'와 @passwd = 'P@$$''w0rd'가 전달된다고 하더라도 QUOTENAME이 구분 문자를 올바르게 이스케이프하므로 SQL 주입 기회가 발생하지 않습니다.
create login [my[]]dbreader] with password = 'P@$$''w0rd'
Figure 1 QUOTENAME으로 문자열 구분
create procedure sys.sp_addlogin @loginame sysname ,@passwd
sysname = Null ,@defdb sysname = ‘master’ ,@deflanguage sysname = Null
,@sid varbinary(16) = Null ,@encryptopt varchar(20) = Null AS -- SETUP
RUNTIME OPTIONS / DECLARE VARIABLES -- -- some code ---- set @exec_stmt =
‘create login ‘ + quotename(@loginame, ‘[‘) if @passwd is null select
@passwd = ‘‘ if (@encryptopt is null) set @exec_stmt = @exec_stmt + ‘
with password = ‘ + quotename(@passwd, ‘‘‘‘) else -- some code GO
REPLACE 함수는 지정된 문자열을 모두 지정된 대체 문자열로 바꿉니다. QUOTENAME과는 달리 받는 인수에 대한 길이 제한은 없습니다.
REPLACE ( 'string1' , 'string2' , 'string3' )
REPLACE는 3개의 문자열을 받습니다. 즉, string1은 편집할 식이고 string2는 바꿔야 하는 string1 내의 항목이며 string3은 string2 대신 사용할 항목입니다. 모든 문자열 식은 문자 또는 이진 데이터일 수 있습니다.
구분 SQL 리터럴을 준비하기 위해 REPLACE를 사용하여 작은따옴표 수를 2배로 만들 수 있습니다. 그러나 이 경우 시작 및 끝 작은따옴표를 사용하는 것처럼 구분 기호를 수동으로 추가해야 합니다. 그림 2에서는 sp_attach_single_file_db에서 이 함수를 사용하여 이스케이프된 물리적 파일 이름을 준비하는 방법을 보여 줍니다. @physname은 nvarchar(260)이므로 QUOTENAME을 구분 리터럴을 준비하는 데 사용할 수 없습니다. 바로 이 점 때문에 REPLACE를 사용하는 것입니다. 따라서 작은따옴표가 있는 문자열을 전달한다고 하더라도 SQL 문을 수정하거나 SQL 코드를 주입할 수 없습니다.

Figure 2 REPLACE로 문자열 구분
create procedure sys.sp_attach_single_file_db @dbname sysname,
@physname nvarchar(260) as declare @execstring nvarchar (4000) -- some
code -- select @execstring = ‘CREATE DATABASE ‘ + quotename( @dbname ,
‘[‘) + ‘ ON (FILENAME =‘ + ‘‘‘‘ + REPLACE(@physname,N’’’’,N’’’’’’) +
‘‘‘‘ + ‘ ) FOR ATTACH’ EXEC (@execstring) -- some code -- GO

SQL 주입 취약점

이제 현재 암호의 유효성을 확인한 후 사용자 계정의 암호를 변경하는 저장 프로시저를 살펴보겠습니다(그림 3 참조).
Figure 3 암호 변경
CREATE PROCEDURE sp_setPassword @username varchar(25), @old
varchar(25), @new varchar(25) AS DECLARE @command varchar(100) SET
@command= ‘update Users set password=‘‘‘ + @new + ‘‘‘ where username=‘‘‘
+ @username + ‘‘‘ AND password=‘‘‘ + @old + ‘‘‘‘ EXEC (@command) GO
저장 프로시저를 간단하게 살펴보면 작은따옴표에 대해 이스케이프된 매개 변수가 없어서 SQL 주입 공격에 취약하다는 것을 알 수 있습니다. 공격자는 몇 가지 특정 인수를 전달하여 SQL 문을 다음과 같이 수정할 수 있습니다.
update Users set password='NewP@ssw0rd' where username='admin'
--' and password='dummy'
결과적으로 실제 암호가 없어도 admin 계정(또는 알려진 계정)의 암호가 설정됩니다. T-SQL에서는 REPLACE 또는 QUOTENAME을 사용하여 이 코드를 수정할 수 있습니다. 그림 4에서는 REPLACE 함수를 사용하여 수정한 코드를 보여 줍니다.
Figure 4 REPLACE를 사용하여 주입 방지
CREATE PROCEDURE sp_setPassword @username varchar(25), @old
varchar(25), @new varchar(25) AS -- Declare variables. DECLARE @command
varchar(100) -- Construct the dynamic SQL SET @command= ‘update Users
set password=‘‘‘ + REPLACE(@new, ‘‘‘‘, ‘‘‘‘‘‘) + ‘‘‘‘ + ‘ where
username=‘‘‘ + REPLACE(@username, ‘‘‘‘, ‘‘‘‘‘‘) + ‘‘‘‘ + ‘ AND password =
‘‘‘ + REPLACE(@old, ‘‘‘‘, ‘‘‘‘‘‘) + ‘‘‘‘ -- Execute the command. EXEC
(@command) GO
그림에서 볼 수 있는 것처럼 REPLACE는 매개 변수에 있는 모든 작은따옴표 수를 2배로 만듭니다. 따라서 공격자가 동일한 인수를 전달하는 경우 다음과 같은 문이 만들어집니다.
update Users set password='NewP@ssw0rd' where
username='admin''--' and password='dummy'
따라서 일반적인 SQL 주입 문제에 취약하지 않게 됩니다.

잘라내기를 통한 수정
앞에서 본 저장 프로시저를 자세히 살펴보면 @command 변수의 길이 제한이 100자임을 알 수 있습니다. 그러나 25자로 된 각 변수에 대한 REPLACE 함수는 모든 문자가 작은따옴표일 경우 50자를 반환할 수 있습니다. SQL Server 2000 SP4 및 SQL Server 2005 SP1에서는 변수의 버퍼가 충분하지 않으면 데이터가 자동으로 잘립니다. 공격자는 이러한 기회를 틈타 명령 문자열을 자를 수 있습니다.
이 예제에서 누군가가 username='username' 식의 바로 뒤에 있는 명령을 자를 수 있다면 알려진 사용자 계정의 현재 암호를 알지 못하더라도 해당 암호를 변경할 수 있습니다.

웹 응용 프로그램에 administrator라는 이름의 사용자가 있다는 것을 공격자가 알고 있다고 가정합니다. 이 경우 모든 사용자 계정이 대상이 될 수 있습니다. 명령이 너무 길어서 적절하게 잘리도록 하기 위해서는 공격자가 41자 길이의 새 암호를 제공해야 합니다. 즉, 전체 길이가 100자인 명령에서 27자는 update 문에 사용되고 17자는 where 절에 사용되고 13자는 "administrator"에 사용되며 2자는 새 암호를 둘러싸는 작은따옴표에 사용되기 때문에 암호에는 41자가 필요합니다.
공격자는 새 암호로 25자만 전달할 수 있습니다. 그러나 REPLACE 함수에 의해 2배가 되는 작은따옴표를 전달하면 이 문제를 해결할 수 있습니다. 따라서 공격자는 작은따옴표 18개, 대문자 1개, 기호 1개 및 소문자 2개를 전달하여 where username='administrator' 식의 바로 뒤에 있는 명령을 자를 수 있습니다. 공격자가 @new 매개 변수에 대해 ''''''''''''''''''!Abb1을 전달하고 username 매개 변수에 대해 administrator를 전달하면 @command는 다음과 같이 됩니다.
update Users set password=
'''''''''''''''''''''''''''''''''''''!Abb1' where
username='administrator'
그림 5에서는 REPLACE 대신 QUOTENAME을 사용합니다. 이전 예제에서는 개발자가 사용자 이름, 새 암호 및 기존 암호에 대해 작은따옴표를 구분 문자로 추가하지만 이 예제에서는 QUOTENAME 함수를 사용해서 작은따옴표를 추가한다는 점이 이전 예제와 이 예제의 유일한 차이점입니다. 사용자가 제공한 데이터는 변경되지 않기 때문에 이전 예제에 사용한 것과 동일한 공격 문자열을 이 예제에 사용할 수 있습니다. 그림 6에서는 동일한 기능을 수행하는 중간 계층 응용 프로그램에서 작성된 간략한 C/C++ 함수 버전을 보여 줍니다. 이 함수도 동일한 공격에 취약합니다.
Figure 6 C++의 잘라내기 문제점
DWORD ChangePassword(char* psUserName, char* psOld, char*
psNew) { char* psEscapedUserName = NULL; char* psEscapedOldPW = NULL;
char* psEscapedNewPW = NULL; char szSQLCommand[100]; HRESULT hr=0; //
Input Validation ... // Calculate and allocate the new buffer with
length // userdatalen*2 + 1 // Escape all single quotes with double
quotes ... //Construct the query hr = StringCchPrintf(szSQLCommand,
sizeof(szSQLCommand)/sizeof(char), "Update Users set password=‘%s’ where
username=‘%s’" "AND password=‘%s’, psEscapedNewPW, psEscapedUserName,
psEscapedOldPW); if (S_OK != hr) { // handle error cases } // Execute
and return }
Figure 5 QUOTENAME을 사용하여 주입 방지
CREATE PROCEDURE sp_setPassword @username varchar(25), @old
varchar(25), @new varchar(25) AS -- Declare variables. DECLARE @command
varchar(100) -- In the following statement, we will need 43 characters
-- to set an administrator password without knowing its current
password. -- 100 - 26 - 16 - 15 = 43 (26 for update stmt, 16 for where
clause, -- 15 for ‘administrator’). But @new only takes 25 characters,
which we -- can get around by using single quotes. So one can pass the
following -- parametes and set admin password. @new = 18 single quotes, 1
Capital -- letter, 1 symbol, 2 small case letters, 1 digit -- @username
= administrator -- @command becomes -- update Users set
password=‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘!Abb1’ -- where username=‘administrator’ SET
@command= ‘update Users set password=‘ + QUOTENAME(@new,’’’’) + ‘ where
username=‘ + QUOTENAME(@username,’’’’) + ‘ AND password = ‘ +
QUOTENAME(@old,’’’’) -- Execute the command. EXEC (@command) GO
잘라내기를 통한 SQL 주입

그림 7에서는 개별 변수를 고정적으로 사용하는 동일한 코드의 여러 변형을 보여 줍니다. 이 코드에서는 이스케이프된 문자열을 개별 변수에 저장하고 @command의 버퍼는 전체 문자열을 저장할 수 있을 정도로 큽니다. @escaped_username, @escaped_oldpw 및 @escaped_newpw는 varchar(25)로 선언되었지만 @username, @old 및 @new의 모든 문자가 25개의 작은따옴표 문자일 경우 50자를 저장해야 합니다. 이 경우 이스케이프된 문자로 구성된 문자열이 잘릴 가능성이 있습니다.
Figure 7 개별 변수를 사용하여 주입 방지
CREATE PROCEDURE sp_setPassword @username varchar(25), @old
varchar(25), @new varchar(25) AS -- Declare variables. DECLARE
@escaped_username varchar(25) DECLARE @escaped_oldpw varchar(25) DECLARE
@escaped_newpw varchar(25) DECLARE @command varchar(250) SET
@escaped_username = REPLACE(@username, ‘‘‘‘, ‘‘‘‘‘‘) SET @escaped_oldpw =
REPLACE(@old, ‘‘‘‘, ‘‘‘‘‘‘) SET @escaped_newpw = REPLACE(@new, ‘‘‘‘,
‘‘‘‘‘‘) SET @command = ‘update Users set password=‘‘‘ + @escaped_newpw +
‘‘‘‘ + ‘ where username=‘‘‘ + @escaped_username + ‘‘‘‘ + ‘ AND password
= ‘‘‘ + @escaped_oldpw + ‘‘‘‘ EXEC (@command) GO
공격자는 123...n'(여기서 n은 24번째 문자)을 새 암호로 제공하고 @escaped_newpw를 123...n'으로 만든 후(REPLACE 함수에서 반환된 두 번째 작은따옴표 문자가 잘림) 다음과 같은 마지막 쿼리를 만들 수 있습니다. 이 경우 공격자는 username 필드를 통해 코드를 주입하여 공격할 수 있습니다.
update users set password='123...n'' where username='<SQL
Injection here using Username>
이 코드 패턴의 경우 기존 SQL을 단순히 자르는 것에 그치지 않고 SQL 코드를 삽입할 가능성이 열려 있기 때문에 더 위험합니다.

그림 8에서는 REPLACE 대신 QUOTENAME 함수를 사용하는 동일한 코드의 변형을 예제로 보여 줍니다. QUOTENAME은 구분 기호를 추가하므로 페이로드는 다르지만 여전히 SQL 주입 공격에는 취약합니다.
Figure 8 개별 변수를 통해 QUOTENAME 사용
ALTER PROCEDURE sp_setPassword @username varchar(25), @old
varchar(25), @new varchar(25) AS -- Declare variables. DECLARE
@quoted_username varchar(25) DECLARE @quoted_oldpw varchar(25) DECLARE
@quoted_newpw varchar(25) DECLARE @command varchar(250) -- In the
following statements, all the variables can only hold -- 25 characters,
but quotename() will return 52 characters when all -- the characters are
single quotes. SET @quoted_username = QUOTENAME(@username, ‘‘‘‘) SET
@quoted_oldpw = QUOTENAME(@old, ‘‘‘‘) SET @quoted_newpw =
QUOTENAME(@new, ‘‘‘‘) -- By passing the new password as 123...n where n
is 24th character, -- @quoted_newpw becomes ‘123..n -- Observe carefully
that there is no trailing single quote as it gets -- truncated. -- So
the final query becomes something like this -- update users set
password=‘123...n where username=‘ <SQL Injection -- here using
Username> SET @command= ‘update Users set password=‘ + @quoted_newpw +
‘ where username=‘ + @quoted_username + ‘ AND password = ‘ +
@quoted_oldpw EXEC (@command) GO
이 코드에서는 구분 문자열을 개별 변수에 저장하고 @command의 버퍼는 전체 명령 문자열을 저장할 수 있을 정도로 큽니다. 이전 예제와 마찬가지로 따옴표 붙은 변수 @quoted_username, @quoted_oldpw 및 @quoted_newpw에 문제가 있습니다. 이러한 변수는 varchar(25)로 선언되었지만 @username, @old 및 @new의 모든 문자가 25개의 작은따옴표 문자일 경우 52자가 필요합니다. 또한 QUOTENAME은 시작 및 끝 구분 기호를 추가합니다. 이 경우 공격자는 구분 문자로 구성된 문자열을 자를 기회를 얻게 됩니다.
공격자는 123...n(여기서 n은 24번째 문자)을 새 암호로 제공하고 @escaped_newpw를 '123...n으로 만든 후(QUOTENAME 함수가 시작 작은따옴표를 추가함) 다음과 같은 마지막 쿼리를 만들 수 있습니다. 이 경우 공격자는 username 필드를 통해 코드를 주입하여 공격할 수 있습니다.
update users set password='123...n where username=' <SQL
Injection here using Username>
그림 9에서는 이 코드와 동일한 기능을 수행하면서 C/C++로 작성된 간단한 코드를 보여 줍니다. 이 코드 또한 동일한 공격 방식에 취약합니다.
Figure 9 C++의 변수 잘라내기 문제
DWORD ChangePassword(char* psUserName, char* psOld, char*
psNew) { char szEscapedUserName[26]; char szEscapedOldPW[26]; char
szEscapedNewPW[26]; char szSQLCommand[250]; // Input Validation //
Escape User supplied data Replace(psUserName, "’", "’’",
szEscapedUserName, sizeof(szEscapedUserName)); Replace(psPassword, "’",
"’’", szEscapedOldPW, sizeof(szEscapedOldPW)); Replace(psPassword, "’",
"’’", szEscapedNewPW, sizeof(szEscapedNewPW)); // Construct the query
StringCchPrintf(szSQLCommand, sizeof(szSQLCommand)/sizeof(char), "Update
Users set password=‘%s’ where username=‘%s’" "AND password=‘%s’,
szEscapedNewPW, szEscapedUserName,szEscapedOldPW); // Execute and return
}
여기에서는 설명을 위해 T-SQL 코드를 사용하기는 했지만 실제로는 DML(데이터 조작 언어) 코드가 포함된 대부분의 응용 프로그램이 이러한 문제에 취약하지 않으므로 DML 문에 동적 SQL을 사용할 필요는 없습니다.

그림 10에서는 사용자 입력에 따라 동적 DDL 문을 생성하는 예제를 보여 줍니다. 앞서 살펴본 다른 예제와 마찬가지로 다음 문에는 잘라내기 문제가 있습니다.
set @escaped_oldpw = quotename(@old, '''') set @escaped_newpw =
quotename(@new, '''')
공격자는 @new = '123...'을 전달하여 이러한 문을 공격할 수 있습니다. 여기서 127번째 문자(작은따옴표 아님)는 @old = '; SQL Injection'을 시작하고 다음과 같은 SQL 문이 만들어집니다.
alter login [loginname] with password = '123... old_password =
'; SQL Injection
Figure 10 동적 DDL 문 만들기
create procedure sys.sp_password @old sysname = NULL, -- the
old (current) password @new sysname, -- the new password @loginame
sysname = NULL -- user to change password on as -- SETUP RUNTIME OPTIONS
/ DECLARE VARIABLES -- set nocount on declare @exec_stmt nvarchar(4000)
declare @escaped_oldpw sysname declare @escaped_newpw sysname set
@escaped_oldpw = quotename(@old, ‘‘‘‘) set @escaped_newpw =
quotename(@new, ‘‘‘‘) set @exec_stmt = ‘alter login ‘ +
quotename(@loginame) + ‘ with password = ‘ + @escaped_newpw + ‘
old_password = ‘ + @escaped_old exec (@exec_stmt) if @@error <> 0
return (1) -- RETURN SUCCESS -- return (0) -- sp_password
저장 프로시저에서 이러한 문제가 쉽게 발생할 수 있는 것처럼 보이기는 하지만 모든 저장 프로시저가 보안에 취약한 것은 아닙니다. 다음 내용을 주의 깊게 검토할 필요가 있습니다.
SQL Server에서는 모든 저장 프로시저가 기본적으로 호출자의 컨텍스트에서 실행됩니다. 따라서 프로시저에 SQL 주입 문제가 있다고 하더라도 프로시저에 대한 실행 권한을 갖고 있는 악의적 로컬 사용자가 자신의 권한을 높일 수 없기 때문에 주입된 코드는 해당 사용자의 컨텍스트에서 실행됩니다. 그러나 EXECUTE AS 기능을 통해 소유자 또는 다른 특정 사용자가 실행할 수 있는 내부 유지 관리 스크립트가 있는 경우에는 호출자가 다른 사용자 컨텍스트에서 코드를 실행하여 호출자의 권한을 해당 사용자의 권한으로 높일 수 있습니다.
모든 잘라내기 문제는 명백히 버그이지만 반드시 보안 취약점이라고 할 수는 없습니다. 그러나 향후 누가 이러한 문제점을 발견하여 악용할 가능성은 있기 때문에 문제를 해결해 두는 것이 좋습니다.
SQL 코드의 주입 취약점을 완화하기 위해 취할 수 있는 다른 방법이 있습니다. 첫 번째는 저장 프로시저에서 DML 문에 대해 동적 SQL을 사용하지 않는 것입니다. 불가피하게 동적 SQL을 사용해야 한다면 sp_executesql을 사용하십시오. 두 번째는 이 기사의 예제에서 설명한 것처럼 버퍼 길이를 올바르게 계산해야 합니다. 마지막으로 C/C++ 코드의 경우 문자열 연산 반환 값을 확인하여 문자열이 잘렸는지 여부를 확인합니다. 문자열이 잘렸으면 실패한 것입니다. 취할 수 있는 단계에 대한 요약을 보려면 "취약점 검색 방법" 보충 기사를 참조하십시오.
잘라내기를 통한 주입 검색

잘라내기를 이용한 SQL 주입 문제를 자동화된 도구로 검색하려면 잘라내기 공격의 가능성을 남기는 모든 코드 패턴을 잘 파악하고 있어야 합니다. 서로 다른 문자열 데이터를 사용하여 개별 특정 코드 패턴에 적용할 수 있습니다. 다음 시나리오에서는 n이 입력 버퍼의 길이라고 가정합니다.
QUOTENAME 구분 문제를 검색하기 위해 먼저 QUOTENAME(또는 C/C++ 응용 프로그램의 경우 비슷한 함수)을 사용해서 구분 식별자 또는 리터럴을 준비했고, 구분된 문자열 버퍼 크기가 2*n + 2보다 작다고 가정합니다. 구분된 문자열 버퍼 길이가 n일 경우 이러한 문제를 검색하려면 비구분 문자로 구성된 긴 문자열을 전달합니다. 후행 구분 기호는 잘리고 다른 입력 변수를 사용하여 주입할 수 있는 기회가 생깁니다.
구분된 버퍼 길이가 홀수일 때 이러한 문제를 검색하려면 작은따옴표(또는 오른쪽 대괄호나 큰따옴표) 문자로 구성된 긴 문자열을 전달합니다. QUOTENAME은 모든 구분 기호 수를 2배로 만들고 시작 구분 문자를 추가하는 반면, 이스케이프된 문자열 버퍼에는 홀수 개의 문자만을 저장할 수 있으므로 후행 구분 기호는 잘립니다.
구분된 버퍼 길이가 짝수일 때 이러한 문제를 검색하려면 1', 1'', 1''', 1''''과 같이 각 반복에 대해 작은따옴표(또는 오른쪽 대괄호)의 수를 늘리는 방식으로 문자열을 전달합니다. QUOTENAME은 모든 작은따옴표 수를 2배로 만들기 때문에 시작 구분 기호와 1이 포함된 짝수 개의 작은따옴표가 들어 있는 문자열이 반환되어 짝수 개의 문자를 받게 됩니다. 결과적으로 후행 구분 기호는 잘립니다.
또한 REPLACE(또는 C/C++ 응용 프로그램의 경우 유사한 함수)를 사용하여 이스케이프된 문자열을 준비하고 이스케이프된 문자열 버퍼 크기가 2*n보다 작은 경우에도 이러한 문제를 검색할 수 있습니다. 이스케이프된 문자열 버퍼 길이가 n과 같을 때 이러한 문제를 검색하려면 1', 12', 123' 및 123...n'과 같이 각 반복에 대해 입력 문자열의 길이를 늘리는 방식으로 문자열을 전달합니다. 이 경우 올바른 길이를 입력하면 REPLACE 함수에 의해 마지막 작은따옴표 문자가 2배로 됩니다. 이스케이프된 문자열 변수에는 충분한 버퍼 공간이 없으므로 마지막 작은따옴표가 잘린 채로 저장되어 전달되기 때문에 SQL 문을 수정할 수 있는 기회가 발생합니다.
이스케이프된 버퍼 길이가 홀수일 때 REPLACE를 사용하여 이러한 문제를 검색하려면 ', '', ''' 및 ''''...'와 같이 길이가 길어지는 작은따옴표 문자로 구성된 문자열을 전달하거나 작은따옴표 문자로 구성된 긴 문자열을 전달합니다. 이 경우 REPLACE는 모든 작은따옴표 수를 2배로 만듭니다. 그러나 버퍼 크기가 홀수이기 때문에 마지막 작은따옴표가 잘리면서 SQL 문을 수정할 수 있는 기회가 발생합니다.
이스케이프된 버퍼 길이가 짝수일 때 이러한 문제를 검색하려면 1', 1'', 1''', 1''''과 같이 각 반복에 대해 작은따옴표(또는 오른쪽 대괄호)의 수를 늘리는 방식으로 문자열을 전달합니다. 맨 앞의 1을 제외한 반환 값에는 짝수 개의 문자가 포함되므로 전체 반환 값의 문자 수는 홀수가 됩니다. 그러나 버퍼 길이가 짝수이기 때문에 후행 작은따옴표가 잘리면서 SQL 문을 수정할 수 있는 기회가 발생합니다.
취약점 검색 방법
코드 검토 사용 코드 검토를 수행할 때 다음과 같은 방법을 사용하여 SQL 문의 문제를 검색할 수 있습니다.
1차 또는 2차 SQL 주입 검색
  • 동적 SQL 문을 실행하는 데 사용한 API를 확인합니다.
  • 동적 SQL 문에 사용된 데이터에 대해 데이터 유효성 검사가 수행되었는지 검토합니다.
  • 데이터 유효성 검사가 수행되지 않은 경우 데이터의 구분 문자(문자열 리터럴의 경우 작은따옴표, SQL 식별자의 경우 오른쪽 대괄호)가 이스케이프되었는지 검토합니다.
잘라내기를 통한 SQL 수정 문제 검색
  • 마지막 동적 SQL 문을 저장하는 데 사용된 버퍼 길이를 검토합니다.
  • 입력이 최대값을 초과하고 SQL 문을 저장하는 데 사용할 버퍼가 충분히 큰 경우 SQL 문을 저장하는 데 필요한 최대 버퍼를 계산합니다.
  • QUOTENAME 또는 REPLACE 함수의 반환 값에 특히 주의합니다. 이러한 함수는 입력 데이터의 길이가 n자일 때 모든 입력 문자가 구분 문자이면 2*n + 2 또는 2*n을 반환합니다.
  • C/C++ 응용 프로그램의 경우 SQL 문을 준비하는 데 사용한 StringCchPrintf와 같은 API의 반환 값에 대해 버퍼 부족 오류가 발견되었는지 확인합니다.
잘라내기를 통한 SQL 주입 문제 검색
  • 구분 문자열 또는 이스케이프된 문자열을 저장하는 데 사용된 버퍼 길이를 검토합니다.
  • n이 입력 문자열의 길이이면 QUOTENAME의 반환 값을 저장하는 데 2*n + 2가 필요하고 REPLACE의 반환 값을 저장하는 데 2*n이 필요합니다.
  • C/C++ 응용 프로그램의 경우 REPLACE에 상응하는 함수의 반환 값에 대해 버퍼 부족 오류가 발견되었는지 확인합니다.
블랙 박스 메서드 사용
자동화 도구나 지능형 퍼저(Fuzzer)가 있는 경우에는 다음과 같은 방법을 사용하여 SQL 문의 문제를 검색할 수 있습니다.
SQL 주입 문제 검색
  • 작은따옴표를 입력 데이터로 보내서 사용자 입력이 동적 SQL 문에서 문자열 리터럴로 관리 및 사용되지 않는 경우를 검색합니다.
  • 오른쪽 대괄호(] 문자)를 입력 데이터로 사용하여 정리되지 않은 사용자 입력이 SQL 식별자의 일부분으로 사용되는 경우를 검색합니다.
잘라내기 문제 검색
  • 버퍼 오버런 검색을 위해 문자열을 보내는 것처럼 긴 문자열을 보냅니다.
잘라내기를 통한 SQL 수정 문제 검색
  • 작은따옴표 문자(또는 오른쪽 대괄호나 큰따옴표)로 구성된 긴 문자열을 보냅니다. 이 경우 REPLACE 및 QUOTENAME 함수의 반환 값이 최대값을 초과하게 되어 SQL 문을 저장하는 데 사용되는 명령 변수가 잘릴 수 있습니다.

필자소개

Bala Neerumalla는 Microsoft의 보안 소프트웨어 개발자이며 응용 프로그램 보안 취약점 조사를 전문적으로 담당하고 있습니다.

댓글 없음:

댓글 쓰기