[web]SQL injection UNION attacks

zzsla·2023년 7월 6일
0
post-custom-banner

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 쿼리가 작동 하려면 두 가지 요구사항이 충족되어야 한다.

  • 각각 쿼리는 동일한 수의 columns으로 반환되어야 한다.
  • 각 columns의 데이터 유형은 각각 쿼리 간에 호환되어야 한다.

SQL injection UNION 공격을 수행하려면 공격이 두 가지 요구사항이 충족하는지 확인해야 한다. 일반적으로 다음 사항을 파악해야 한다.

  • 원래 쿼리에서 반환되는 columns 갯수를 파악해야 한다.
  • 원래 쿼리에서 반환된 columns은 쿼리 결과나오는데 적합한 데이터 유형인지 파악해야 한다.

SQL injection UNION 공격에 필요한 columns 수 결정

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 찾기

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은 문자열 데이터를 검색하기에 적합한 열이다.

SQL injection UNION 공격을 사용하여 데이터 검색

원래 쿼리에서 columns 갯수를 알아내고 문자열 데이터를 넣을 수 있는 columns을 찾았으면 데이터를 검색할 수 있다.
예를 들어 아래같이 가정했을 때

  • 원래 쿼리는 데이터를 넣을 수 있는 두 개의 columns을 반환한다.
  • SQL injection 시작위치는 WHERE절이다.
  • 데이터베이스에는 usernamepassword이라는 columns이 있는 users라는 테이블이 포함되어 있다.
    이 경우 다음 페이로드를 제출해서 users테이블의 콘텐츠를 가져올 수 있다.
' UNION SELECT username, password FROM users--

물론 이 공격을 수행하는 데 필요하고 중요한 정보는 usernamepassword라는 두 개의 columns이 있는 users테이블이 있다는 것이다. 이 정보가 없으면 테이블과 columns의 이름을 추측해야 한다.

단일 columns 내에 여러 값 검색

앞에 예에서 쿼리가 단일 columns만 반환한다고 가정한다.
단일 columns 내에서 여러 값을 함께 검색하기 위해 값을 연결하는 것은 쉽다. 결합한 값을 구별할 수 있는 적절한 구분자를 넣어야 한다. Oracle 같은 경우 다음 같은 입력을 제출할 수 있다.

' UNION SELECT username || '~' || password FROM users--

Oracle에서는 문자열 연결 연산자인 이중 파이프||를 사용한다. 들어간 쿼리에 usernamepassword의 값을 ~문자를 넣어 값을 구분하고, 함께 연결한다.
쿼리 결과를 통해 모든 username과 password를 읽을 수 있다. 예를 들면 다음과 같다.

...
administrator~s3cure
wiener~peter
carlos~montoya
...
profile
[README]newbi security hacker :p
post-custom-banner

0개의 댓글