application이 SQL injection에 취약하고, 쿼리 결과가 application의 응답에 반환되는 경우 UNION
을 사용하여 데이터베이스 내의 다른 테이블에서 데이터를 검색할 수 있다. 이것이 바로 SQL injection UNION 공격이다.
UNION
을 사용하여 추가적인 SELECT
문을 실행하고, 그 결과로 원본 쿼리에 추가되는 방식으로 작동된다.
예를 들어
SELECT a, b FROM table1 UNION SELECT c, d FROM table2
이 SQL 쿼리는 table1
의 columns a
, b
그리고 table2
의 columns c
, d
값이 포함된 두 개의 columns이 하나의 결과 묶음으로 반환한다.
UNION
쿼리가 작동 하려면 두 가지 요구사항이 충족되어야 한다.
SQL injection UNION 공격을 수행하려면 공격이 두 가지 요구사항이 충족하는지 확인해야 한다. 일반적으로 다음 사항을 파악해야 한다.
SQL injection UNION 공격을 수행할 때 원래 쿼리에서 얼마나 많은 columns을 반환되는지 확인하는 두 가지 방법이 있다.
첫번째 방법은 ORDER BY
절을 계속 넣으면서 오류가 발생할 때까지 지정된 columns 인덱스를 증가시킨다. 예를 들어 injection 넣는 위치가 원래 쿼리의 WHERE
절이라면 다음을 제출해야 한다.
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--
etc.
이 페이로드는 결과를 각각의 columns별에 따라 정렬한다. ORDER BY절에서 columns을 인덱스로 지정할 수 있기 때문에 columns의 이름을 알 필요는 없다. 지정된 columns의 인덱스가 실제 columns 수를 초과하면 데이터베이스는 오류를 반환한다. 예를 들어 다음과 같이 오류를 반환한다.
The ORDER BY position number 3 is out of range of the number of items in the select list.
application은 실제로 HTTP 응답에서 데이터베이스 오류를 반환하거나 일반 오류를 반환하거나 단순히 결과를 반환하지 않을 수도 있다. application 응답에서 어떤 차이점을 찾아낼 수 있으면, 쿼리에서 얼마나 columns이 있는지 알 수 있다.
두번째 방법은 null값 하나씩 UNION SELECT
페이로드를 넣고, 제출해야 한다.
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--
etc.
null 갯수가 columns 갯수와 일치하지 않으면 다음과 같은 오류를 반환한다.
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
아까 위와 같이 application이 실제로 이 오류 메세지를 반환하거나 일반적인 오류를 반환하거나 결과가 없을 수 있다. null 갯수와 columns의 갯수가 일치하면 데이터베이스는 각 columns에 null값이 포함된 결과가 반환된다. HTTP응답 결과에 대한 영향은 application 코드에 따라 다르다. 운이 좋다면 HTML 테이블의 추가 행과 같은 추가 콘텐츠를 볼 수 있다. 운이 나쁘면 null 값이 NULLPointerException
과 같은 다른 오류를 발생시킬 수 있다. 최악의 경우, 응답은 잘못된 갯수의 null로 인한 응답과 구별할 수 없게 되어 columns 갯수를 알아내는 방법에 효과가 없을 수 있다.
메모
SELECT
가 들어간 쿼리에서 반환되는 값으로NULL
을 사용한 이유는 원래 쿼리와SELECT
가 들어간 쿼리 간의 각 columns의 데이터 유형이 호환되어야 한다.NULL
은 일반적으로 모든 데이터 유형으로 변환이 가능하므로,NULL
을 사용하면 columns 갯수가 맞을 경우 페이로드가 성공할 가능성이 높아진다.- Oracle에서는 모든
SELECT
쿼리에FROM
키워드를 사용해야 하고 유효한 테이블을 지정해야 한다. Oracle에는 dual이라는 내장 테이블이 있어 이를 이용할 수 있다. 따라서 Oracle에서 쿼리가 들어갈 때 다음과 같아야 한다.
' UNION SELECT NULL FROM DUAL--
- 위 페이로드는 주석처리 하기 위해 이중 대시
--
를 사용하여 쿼리가 들어간 지점 이후 부분인 원래 쿼리의 나머지 부분을 주석처리한다. MySQL에서는 이중 대시 뒤에 공백이 있어야 한다. 또는 해시문자#를 사용하여 주석처리할 수 있다.
SQL injection UNION 공격을 수행하는 이유는 삽입된 쿼리에서 결과를 검색할 수 있기 때문이다. 일반적으로 검색하려는 데이터는 문자열 형식이므로 원래 쿼리 결과에서 데이터 유형이 문자열 데이터와 일치하거나, 호환되는 하나 이상의 columns을 찾아야 한다.
columns의 갯수를 찾았으면 문자열을 각 columns에 차례대로 배치하는 UNION SELECT 페이로드를 제출해서 각 columns을 조사해서 문자열 데이터를 넣을 수 있는지 확인한다. 예를 들어 쿼리가 4개의 columns을 반환하는 경우 다음을 제출한다.
' UNION SELECT 'a', NULL, NULL, NULL--
' UNION SELECT NULL, 'a', NULL, NULL--
' UNION SELECT NULL, NULL, 'a', NULL--
' UNION SELECT NULL, NULL, NULL, 'a'--
columns의 데이터 유형이 문자열과 호환되지 않는 경우 삽입된 쿼리로 인해 다음과 같은 데이터베이스 오류가 발생한다.
Conversion failed when converting the varchar value 'a' to data type int.
오류가 발생하지 않고 application의 응답에 들어간 문자열 값이 포함된 추가 콘텐츠가 있으면 해당 columns은 문자열 데이터를 검색하기에 적합한 열이다.
원래 쿼리에서 columns 갯수를 알아내고 문자열 데이터를 넣을 수 있는 columns을 찾았으면 데이터를 검색할 수 있다.
예를 들어 아래같이 가정했을 때
WHERE
절이다.username
과 password
이라는 columns이 있는 users
라는 테이블이 포함되어 있다.users
테이블의 콘텐츠를 가져올 수 있다.' UNION SELECT username, password FROM users--
물론 이 공격을 수행하는 데 필요하고 중요한 정보는 username
와 password
라는 두 개의 columns이 있는 users
테이블이 있다는 것이다. 이 정보가 없으면 테이블과 columns의 이름을 추측해야 한다.
앞에 예에서 쿼리가 단일 columns만 반환한다고 가정한다.
단일 columns 내에서 여러 값을 함께 검색하기 위해 값을 연결하는 것은 쉽다. 결합한 값을 구별할 수 있는 적절한 구분자를 넣어야 한다. Oracle 같은 경우 다음 같은 입력을 제출할 수 있다.
' UNION SELECT username || '~' || password FROM users--
Oracle에서는 문자열 연결 연산자인 이중 파이프||
를 사용한다. 들어간 쿼리에 username
과 password
의 값을 ~
문자를 넣어 값을 구분하고, 함께 연결한다.
쿼리 결과를 통해 모든 username과 password를 읽을 수 있다. 예를 들면 다음과 같다.
...
administrator~s3cure
wiener~peter
carlos~montoya
...