因此我特地撰寫此篇文章,期望能宣達給網頁後端的新血,告訴大家如何保障自己開發的網站的安全,降低遭到黑帽駭客攻擊的風險。
* 此篇介紹將以最廣為人知、容易入門的 PHP 語言搭配 MySQL 為例。
何謂 Web Security (網頁安全)
泛指針對網頁、網站上的資訊安全,涵蓋程式漏洞、邏輯漏洞、資訊洩漏等。
何謂 SQL Injection
這是本篇文章的重點,相信有接觸網頁後端的朋友對 SQL 並不陌生,SQL 全稱為「Structured Query Language」,就是用來操作資料庫、存取出所需資料的一套「查詢語言」。
在資訊安全中有一項針對資料庫的漏洞攻擊稱作 SQL Injection,不僅僅是網頁程式,只要是有和資料庫進行連結的任何程式都可能產生此項漏洞。
此漏洞的成因很簡單,就是允許使用者輸入的字串在代入 SQL 查詢語句時,沒有過濾非法字元,導致字串成為查詢語句的一部分,達到讓攻擊者能執行任意 SQL 語句。
假設現在某個資料庫中,共有兩個資料表demo、users,其結構如下:
資料表 demo
資料表 users
其中 demo 共有 2 筆記錄,users 則有 1 筆記錄,兩個資料表是毫無關聯的。
資料表 demo
資料表 users
現在我擁有一個網頁,它只能隨意存取 demo 資料表內的任何記錄。
假如輸入不存在的 id,會得空白的結果。
不知道各位是否注意到問題?問題在於這個網頁「預期使用者輸入的 id 一定是整數」,所以當使用者輸入非整數資料時,會發生錯誤,得到完全空白的結果。
因為預期 id 是整數的關係,網頁直接將 id 的參數串接進 SQL 語句中,當攻擊者嘗試輸入「-9527 OR 1=1」,發生了有趣的事情。
為何明明是錯誤的 id,卻出現了資料?
原因非常簡單,因為「OR 1=1」變成 SQL 語句的一部分了。
這是處裡 SQL 語句的 PHP 的部份:
$sql = 'SELECT * FROM demo WHERE id='.$id; |
原本預期完成的查詢語句應該長這個樣子:
/demo.php?id=1 |
SELECT * FROM demo WHERE id=1 |
但是當攻擊者傳入 id=-9527 OR 1=1時,SQL 語句會變成:
/demo.php?id=-9527 OR 1=1 |
SELECT * FROM demo WHERE id=-9527 OR 1=1 |
因為 OR 後方的條件永遠會成立,才會導致明明 id 不存在卻能出現結果。
假如配上 UNION 語句,與猜測對應的資料表,駭客可以輕易取得任何資料。
/demo.php?id=-1 UNION SELECT id,username,password FROM users |
SELECT * FROM demo WHERE id=-1 UNION SELECT id,username,password FROM users |
不單單只是如此,若資料庫擁有的權限過高,駭客甚至可以竄改首頁、控制整台伺服器主機,只要透過 INTO OUTFILE 相關指令即可。
而依據不同伺服器的設定,錯誤的查詢語句也會有不同反應,例如空白頁、PHP Warning等。
所以 SQL Injection 大致上能以此分成以下幾類:
- In-Band:
- Union-Based SQL Injection - 透過 Union 語句的攻擊
- Error-Based SQL Injection - 利用錯誤訊息顯示子查詢結果的攻擊
- Blind:
- Boolean-Based SQL Injection - 透過條件式是否相符猜測資料字元的攻擊
- Time-Based SQL Injection - 同上,但改以延遲時間來作判定
- Out-of-Band
在竊取資料方面, In-Band 類型的是最危險的,因為可以直接顯示資料;而 Out-of-Band 其中一種方式可能允許進行各種插入、刪除所有資料表的動作,所以風險甚高。
如何防範 SQL Injection?
一般人直覺上會選擇使用 str_replace、strpos 等函式偵測、過濾惡意字元,例如「 ' 」、「 " 」、「 (空白)」等,甚至是過濾關鍵字「SELECT」、「UNION」、「AND」。但這麼做其實完全不足以防範惡意的駭客,因為空白、單引號的字元繞過是十分容易的,甚至大部分的 SQL Injection 測試工具都很少會使用單引號作測試,作者我在這邊提供兩種參考的防範方式。
1. mysqli_real_escape_string 函式 (不推薦)
只要在參數串入 SQL 語句之前,用此函式先將欲串入的參數作過濾,它會將所有 SQL 的關鍵字元全部替換成跳脫字元,使其在 SQL 查詢中成為普通的字串,而非語句的一部分。從結果看出來,real_escape_string 把單引號給過濾成「\'」的字串。
這是最推薦的作法,因為可以從根本上澈底解決 SQL Injection 的問題,除非是 PHP 官方本身附帶的函式爆出漏洞。Prepared Statement 會替 SQL 語句進行預處理,再利用它提供的 bindValue 或 bindParam 函式將欲查詢的參數的值或變數綁定上去,底層查詢時,其參數會保證作為數值傳遞,不可能成為 SQL 語句的一部分,也因此就不會產生 SQL Injection 的問題。即使輸入錯誤的參數,也不會發生錯誤,只會查詢失敗而得到空結果而已。大部分的網頁後端語言都有提供相對應類似的 Prepared Statement 機制,所以十分建議無論用何種語言撰寫網頁後端,都應該花時間找找有無類似的功能可使用。
以上就是我的心得和建議提供給網頁後端的新人們,希望能藉此提升各位對網頁安全的意識。
若有其他大大覺得有錯誤的地方也歡迎告知,讓我能及時修正。