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 | $username = $_POST['username']; |
如果我今天輸入的帳號或是密碼是 ' 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
如何防制
- 使用參數化查詢 (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()
來防範 - 輸入驗證 (Input Validation)
可能放在前端,或是後端查詢資料庫前針對輸入資訊透過 Regex 等方式驗證資料格式正確性。
放在前端的話,通常都還是有 Bypass 的可能(F12 大法,或是直接 POST 出去),所以還是在後端查詢資料庫前進行驗證會比較好 - Escaping 所有用戶輸入
我不太確定 Escaping 怎麼翻譯成中文,所以就保留他原本英文的意思owo
簡單來說,今天在前面幾個步驟都做完,我還想更近一步防制的話,我可以直接將'
轉成\'
,就可以最小程度防止它成為指令的一部分 - 最小權限原則 (Least Privilege)
別總想著用 root 搞定一切資料庫操作,像是處理搜尋欄位的資料庫用戶就不需要有 write access,永遠把權限放到最小,才能減少不必要的損失 :)