PHP 的 JSON_UNESCAPED_UNICODE (json_encode option) 安全嗎?
昨天 PHP 技術社群有人提問 JSON_UNESCAPED_UNICODE 是否有潛在的安全問題。
預設 PHP 的 json_encode 會將 Unicode 編碼為 \uXXXX
,若要保留 Unicode,例如想要保留原本中文字而不轉換為 \uXXXX
,則需額外開啟 JSON_UNESCAPED_UNICODE 選項。但這個選項會不會造成潛在的安全問題?
我的回答是,雖然 JSON_UNESCAPED_UNICODE 帶來諸多優點,但缺點是會弱化應用安全。
Character Set (字元集)
每個系統都會自帶 Character Set
(字元集),可以把 Unicode 轉成 Local charsets (本地字元集),同時我們也必須認知 Local charsets 不可能支援所有的 Unicode。
當遇到不支援的字元時,工程師通常想到的就是依「錯誤」(拋出異常) 或是「忽略」(移去該字元) 來處理,但這對於顯示大多會是個災難。當一段訊息文字因為其中幾個字元不支援而發生異常或略過時,這對人類解讀與理解會產生不少障礙。
為了盡可能的正常顯示出來,通常系統會再納入「容錯」的設計,也就是會盡量找出類似的字元來替代不支援的。例如若不支援 Ā (U+0100)
,就用最相近的 A (U+0041)
替代,於是 Ā (U+0100)
會被強制轉成 A (U+0041)
。
這看起來沒什麼問題,特別對於人類來說是個不錯的設計,但對於電腦可能就不一定了。
倘若案例換成 ʼ (U+02BC, MODIFIER LETTER APOSTROPHE)
或 ' (U+FF07, FULLWIDTH APOSTROPHE)
,而當系統不支援時,很可能會強制轉成 ' (U+0027)
- 即 apostrophe (撇號) 或 single quote (單引號)。
沒錯,' (U+0027)
剛好就是 SQL Injection 常用的字元之一。
情境
試想這個情境,
- 駭客送出
ʼ (U+02BC)
或' (U+FF07)
。 - 繞過 WAF (網頁應用程式防火牆,如果有且沒設定好)。
- PHP json_encode 因為加上 JSON_UNESCAPED_UNICODE 而未進行安全編碼,保留了駭客送的
ʼ (U+02BC)
或' (U+FF07)
。 - 資料庫不支援該字元,然後強制轉成
' (U+0027)
,加上剛好工程師又沒有完善避免 SQL Injection。 - 爆炸。
感覺爆炸的機率滿低的 (?),而這依賴於,
- 我們覺得除了
ʼ (U+02BC)
或' (U+FF07)
外,現在及以後的 Unicode 擴展不會再新增類似的。 - 我們對公司過去及現任的工程師 / Legacy system (遺舊系統) / 外包非常有信心,每個人在每個地方都能夠完善避免 SQL Injection 或 XSS 等。
- 我們對系統或資料庫的字元集支援非常有信心,甚至是面對 Legacy system /database (遺舊系統)。
關於 MySQL 字元集 (character set) 及校對規則 (collation)
雖然目前技術社群大多使用 MySQL 時,字元集已選擇 utf8mb4
而不是 utf8
,但仍有不少人在校對規則上使用 utf8mb4_unicode_ci
。其實,
- MySQL 8+ 支援 UCA version 9 (Unicode character set version 9),可以開始用
utf8mb4_0900_ai_ci
。 - MySQL 5.6.0+ 其實就支援 UCA version 5.2.0,已可以用
utf8mb4_unicode_520_ci
,比大家常用的utf8mb4_unicode_ci
(UCA 4.0.0) 更新。
最後,json_encode 預設 (不額外加 options) 的安全性還不錯,但若想要更保險,可以加上哪些 options?這就留給有興趣的人去討論了。