HTML Purifierを試した

Webメールでは、受信したHTMLメールのタグを残して、クライアントサイドスクリプトJavaScriptなど)のみを除去するサニタイズ処理を行ないます。

このようなスクリプト除去処理は、Blog、掲示板などでも行なわれる、比較的ポピュラーなものであるため、それ用のライブラリがいくつか存在します*1

HTML Purifierもその一つです。PHP4/5で動作します。バージョン1.0.0のリリースは2006年9月ですから、比較的新しいものです。私は昨年末に初めて触ってみました(バージョン1.3.2です)。

これまでのものと比べて、非常によく出来ているなと思いましたので、日記に書いてみます。

HTML Purifierの概要

特徴としては、以下が挙げられると思います。

ユニコード対応

UTF-8のHTMLに対応(既存のライブラリの多くはlatin1を暗黙の前提としていました)。EUC-JPやSJISなど、他の文字コードも使用できます*2

HTMLを再構築

(当然ですが)HTMLをParseして分解する⇒フィルタ適用⇒HTML再構築という手順で処理します。

HTML言語仕様知識を持つ

HTMLやCSS言語仕様がライブラリに収められており、言語仕様に基づくフィルタ処理を行ないます。これがこのライブラリの最大の売りです。

  • タグの包含関係をチェック。
  • 属性値を分類し(bgcolor属性は%Color、width属性は%Length、src属性は%URIなど)、分類毎のチェックパターンで属性値をチェック。
  • style属性値もホワイトリストでチェック

HTML Purifierに関するより詳細な情報は、以下のページで得られます。

HTML Purifier - Filter your HTML the standards-compliant way!

処理の流れ

もう少しだけ詳細に、HTML Purifierの処理の流れを追っていきます。

前処理
  • HTMLのBODY内を抽出する。
  • CDATAを展開する。Big5(< > " & ')は文字参照表現。
  • Big5を除く文字参照UTF-8バイト列に展開する。
  • UTF-8として不正なバイト列を削除する。
  • non-SGML文字(制御文字類)を削除する。
Parse処理
  • PHP4では独自Parser、PHP5ではDOM関数を使う。
  • 開始・終了タグ、空タグ(空要素)、Textに分解する。
  • Text、属性値内のBig5文字参照を展開する。
フィルタリング
  • 許可するタグ以外(コメント含む)を削除。
  • 終了タグの追加などで、整形式(Well-formed)にする。
  • 包含関係が不正なタグを削除。
  • 許可する属性以外を削除。
  • 属性タイプに応じて属性値をチェック。不正な属性は削除。
HTML生成
  • 開始・終了タグ、空タグ、TextをHTMLに展開する。
  • Text、属性値内の< > " &を文字参照化する。

ベースとなる言語仕様

このライブラリで使用できるタグ、属性、CSSプロパティは、HTML Purifier Printer Smoketestに記述されています。基本的に、HTML4.01(Transitional)、CSS Level 1をベースにしているように思われます。

しかし、言語仕様で定義されていながら、このライブラリでは使用できないものもあります(SCRIPTタグやイベント系属性だけではありません)。例えば、以下です。

  • IFRAME、STYLE、OBJECT/APPLET、AREA、FORMタグ関連
  • テーブル類の背景色・幅指定属性
  • Aタグのtabindexやaccesskeyなどの属性
  • 背景画像指定のCSSプロパティ

このライブラリはページの一部分に、外部のHTMLを挿入するような使い方を前提としています。そのような使い方にそぐわないタグ、属性などを使用不可にしているという理由もあるようです。

また、一部の属性値やCSSプロパティ値に、必要以上に厳しい制限を掛けています。例えば、CSSで「MS明朝」のようなマルチバイト文字を含むフォント名は指定できません(/* foo */ 形式でのコメントなども使用できません)。

利点と欠点

既存ライブラリと比較しての、利点と欠点です。

利点:安全性が高い

真っ当な(かつ安全側に倒した)処理をしており、XSSの危険性は低いと思います。少なくとも私は攻略できませんでした。興味のある方は、デモページで試せます。

欠点:HTML記述の制約が多い

本質的な問題ですが、このライブラリは言語仕様準拠でないHTMLを許容しません(違反している要素を無視するだけで、HTML全体を拒絶するわけではありません)。世の中の多くのHTMLは、言語仕様に準拠しているとは思えませんので、何らかの影響が出ます。さらに、実際に適用されるフィルタは、言語仕様より厳しい基準のものです。

欠点:サーバリソースを消費する

既存のライブラリと比べて処理は複雑です。測定していませんが、消費するCPU、メモリ資源は相対的に多いと考えられます。処理時間も掛かるでしょう。

感想

CSSにさえもホワイトリストを適用しているのは驚きです。欠点もありますが、進んだ発想のライブラリだと思います。少なくとも、既存のPHPスクリプト除去用ライブラリや、巷のポータルサイトWebメールのフィルタよりも、はるかに良いです。

通常のページと、外部由来のHTMLを表示するページを分離する対策(ドメインや認証用トークンなどを分離し、XSS被害が波及しないようにする)が難しい場合には、使用を検討する価値があると思います。

*1:PHPのライブラリは、拙文「よりセキュアなWebサイト構築 - 4.7 JavaScriptの除去」にまとめています。HTML_Safe、kses、HTML Filter for PHPなどを試しましたが、いずれも安全とはいえないものでした。

*2:ライブラリへの入出力時にコード変換を行います。ライブラリの内部処理はUTF-8です。他の文字コードを使う場合、コード変換ロジックによっては、スクリプト挿入の危険性があるので要注意です(参考:UNICODE とサニタイジング回避テクニック)。コード変換にはiconvが使用されます。

httponlyは普及するのか(追記1)

#遅ればせながら、明けましておめでとうございます。

httponlyは普及するのか

という日記を昨年末(12/26)に書きました(今更ながら、日記のタイトルが内容を現してなかったな…)。

httponlyに関して追加の情報がありましたので、本日の日記に記します。

情報の出元は、SLA.CKERSへの投稿です。

Stealing httpOnly cookies with XHR

投稿の内容は、XHRで、httponlyなSet-Cookieヘッダ(レスポンスヘッダ)が取得できてしまうというものです。この投稿には、リクエストで「GET x」など不正なメソッドを使う必要があると書かれています。

しかし、私の環境でのテストでは、「GET」メソッド+getAllResponseHeadersで、レスポンスヘッダ全体を取得することが出来ました。その中には、httponlyなSet-Cookieヘッダも含まれていました。

「GET」「GET x」のいずれにせよ、セッションIDなどのcookieをhttponlyにしても、cookieの値を付け替えるページが存在する場合、cookieを奪われることになります。

なお、昨年末の日記に書いたように、XHRではSame Origin制約が働くため、XSSが存在するページとcookie付け替えのページが、同一のドメインプロトコルにあることが、攻撃成功の条件となります。また、面白いことに、Firefoxのhttponlyアドオンでは、この攻撃は成功しません。

昨年末の日記では、httponlyが効果的な状況として、サブドメイン上の多数のサービスを、cookieでSSOさせているサイトを挙げました。先の攻撃を前提とすると、このようなサイトでhttponlyを有効に機能させるためには、SSO用cookieを発行・付け替えするサーバを少数に絞り込む必要が出てきます(当然そのサーバの守りは固めなければなりません。もしもそのサーバにXSS脆弱性があると、サイト全体がアウトです)。

ところで、この現象はブラウザ(IE6)の「バグ」なんでしょうか。それとも「仕様」と捉えるべきなんでしょうか。私の感覚では非常に微妙です。どちらにしろ、httponlyという機構自体が後付けの機能であって、ブラウザ全体の機能とうまく整合していないような印象を受けました。