Copyright

昨天 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 常用的字元之一。

情境

試想這個情境,

  1. 駭客送出 ʼ (U+02BC)' (U+FF07)
  2. 繞過 WAF (網頁應用程式防火牆,如果有且沒設定好)。
  3. PHP json_encode 因為加上 JSON_UNESCAPED_UNICODE 而未進行安全編碼,保留了駭客送的 ʼ (U+02BC)' (U+FF07)
  4. 資料庫不支援該字元,然後強制轉成 ' (U+0027),加上剛好工程師又沒有完善避免 SQL Injection。
  5. 爆炸。

感覺爆炸的機率滿低的 (?),而這依賴於,

  1. 我們覺得除了 ʼ (U+02BC)' (U+FF07) 外,現在及以後的 Unicode 擴展不會再新增類似的。
  2. 我們對公司過去及現任的工程師 / Legacy system (遺舊系統) / 外包非常有信心,每個人在每個地方都能夠完善避免 SQL Injection 或 XSS 等。
  3. 我們對系統或資料庫的字元集支援非常有信心,甚至是面對 Legacy system /database (遺舊系統)。

關於 MySQL 字元集 (character set) 及校對規則 (collation)

雖然目前技術社群大多使用 MySQL 時,字元集已選擇 utf8mb4 而不是 utf8,但仍有不少人在校對規則上使用 utf8mb4_unicode_ci。其實,

  1. MySQL 8+ 支援 UCA version 9 (Unicode character set version 9),可以開始用 utf8mb4_0900_ai_ci
  2. MySQL 5.6.0+ 其實就支援 UCA version 5.2.0,已可以用 utf8mb4_unicode_520_ci,比大家常用的 utf8mb4_unicode_ci (UCA 4.0.0) 更新。

最後,json_encode 預設 (不額外加 options) 的安全性還不錯,但若想要更保險,可以加上哪些 options?這就留給有興趣的人去討論了。