iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 9
2
Modern Web

成為 Modern PHPer系列 第 9

Day 09:輸入資料過濾

「絕對不要相信來自於使用者的資料」

前言

寫 PHP 時常常會聽到一句「絕對不要相信來自於使用者的資料」,這麼多年過去了,SQL Injection、XSS、LFI 等問題在 PHP 上仍然層出不窮。

有人把原因歸咎於 PHP 的語言設計:過於複雜化的內建函式庫。就算是多年 PHP 經驗的老手,也可能會落入一些不是最佳實踐的陷阱。

SQL Injection

SQL Injection 是個很傳統但又不曾過時的議題。想當年,大學必修資料庫課就是靠 SQL Injection 學 SQL

SQL Injection 來自於程式與資料庫溝通時的美麗(?)誤會:

// 假設 DB 這個 class 會動處理資料庫連線等工作。
DB::execute(
    sprintf("SELECT * FROM users WHERE name = '%s'", $_GET['name'])
);

當使用 /?name=Jack 時,就可以得到 SELECT * FROM users WHERE name = 'Jack' 的結果,一切看起來相當美好

當使用 /?id=Jack' OR 1=1; -- 時,就會得到 SELECT * FORM users WHERE name = 'Jack' OR 1=1; --' 的結果,這時因為 1=1 是 true,所以會隨機取得一名存在於 users 的資料。

如何解決?

PHP 在處理 SQL Injection 共有兩種流派:escape 派與 prepared statement 派。

escape 派

對於 SQL 參數的 escape 有很多函式可以使用,不止初學者容易混淆,連一些老手也容易落入陷阱。

  • addslashes()
  • mysql_escape_string()
  • mysql_real_escape_string()
  • mysqli_escape_string()
  • mysqli_real_escape_string()

註:mysql_* 相關函式在 PHP 7 已被廢棄,請勿繼續使用

縱覽上面的五個函式,就有四個函式長得相當類似,然後又有 real escape 跟 escape,這真的很容易讓人困惑。

先說結論:只有 _real_ 才是真正防止 SQL Injection 的方式,但是 mysqli_escape_string()mysqli_real_escape_string() 是同一個函式。

Prepared Statement 派

Prepared Statement 是比較現代的 Database 所支援的一項 SQL 寫法。

通常會有兩個步驟

  1. 先執行 SQL Statement 的模版(Template)
  2. 再綁定步驟 1. 時所需的參數

這樣的做法有個好處:如果我希望多次執行同一個類似的 SQL Statement,但每次綁定的參數不同,只需要重複進行步驟 2. 即可。

PHP 的實作如下

$dbh = new PDO($dsn, $user, $password);
$sth = $dbh->prepare('SELECT * FROM users WHERE name = ?');
$result = $sth->execute([$_GET['name']]);

技術小筆記

基本上,escape 及 prepared statement 都是可以防範 SQL Injection 的手段。然而實際上通常建議以 Prepared Statement 為優先。

Discuz! SQL Injection

知名的論壇管理系統 Discuz! 曾經有過一個 SQL Injection 的漏洞,其成因是在正確 escape 參數之後,又進行字串截斷(因儲存時存在上限),截斷後的資料成為 SQL Injection 的手段。

PHP Prepared Statement 的技術實作

事實上,預設的 PHP PDO 的 Prepared Statement 並不是走真正的的 Prepared Statement,而是模擬出來的。

詳情可以參閱我之前寫的文章:PHP 騙你 PDO Prepare 並沒有準備好


上一篇
Day 08:輸出內容的過濾
下一篇
Day 10:PHP 的錯誤處理
系列文
成為 Modern PHPer30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言