RDocの脆弱性情報に見るjQueryの安全な使い方

0-9:

RDocの脆弱性情報

2013-02-06に以下の脆弱性情報が公開されました。

RDoc で生成した HTML ドキュメントにおける XSS 脆弱性 (CVE-2013-0256)

これはRDocの脆弱性情報ですが、実際にはdarkfish.jsというファイルの修正のみでありJSの問題であることがわかります。

問題のdarkfish.jsを確認すると該当の処理は「var anchor = window.location.hash.substring(1);から取得した値を$(“a[name=” + anchor + “]”);に渡した」処理であったことがわかります。
(このファイルが脆弱性情報のファイルと同じかは確認してないですが、ファイル名とコードから同一と判断しました)

修正方法としては$(“a[name=” + anchor + “]”)でのセレクター埋め込みをやめて$(“a[name]”).eachのeach内で要素と変数を比較するようにした($(this).attr(“name”) == anchor)なっています。

何が問題だったのか

外部から受け取った値(location.hash)を$(“a[name=” + anchor + “]”)に渡すことでXSSが可能になっています。

これは作者が$()で要素の選択のみを想定していたはずが、意図せずに要素の生成まで出来てしまうことが原因です。

この問題は「location.hashでページ内の要素をJSで指定する」場合の実装に多く存在しており、jQueryを使ったサイトで脆弱性がある場合高い頻度で報告されます。

jQueryにおけるXSSを引き起こしやすい問題について - 金利0無利息キャッシング – キャッシングできます - subtech

どう回避すればいいのか

RDocの脆弱性情報では変数をセレクターに埋め込むことをやめて変数比較にしていますが、jQueryを使う場合には以下のようなコードでも回避することが可能です。

  1. 要素の選択系APIを使う

    $(“a[name]”).filter(“[name=”+anchor+”]”)

$()は「要素の生成」と「要素の選択」両方の機能を持っているため「意図しない要素の生成」が発生しますが、.filterや.findは「要素の選択」の機能しか持っていないため「意図しない要素の生成」が発生しません。
(ただし、極端にブラウザのリソースを奪うことはできる可能性があるので、それが問題になる場合は次の回避策を使ってください)

  1. functionを使って要素を選択する

    $(“a[name]”).filter(function () { return $(this).attr(“name”) === anchor })

.filterは引数にfunctionを受け取り、その中でtrueを返した要素を選択する機能があるため、この機能を使うことで意図しない要素の生成が発生しません。

  1. $()に渡す内容を制限する

    $(“a[name=” + anchor.replace(/\W/g, ”) + “]”)

anchorの内容を[a-zA-Z0-9_]に制限することで意図しない要素の生成を回避出来ます。
(ただし、RDocのname属性が全て\wで記述されているかは確認しています)

そもそもの問題点

そもそもの問題点として、コード内で文字列を組み立てて外部のシステムに渡す構成自体が安全ではありません。

  • SQLインジェクション(SQL文字列に変数を埋め込む場合に多く発生)
  • コマンドインジェクション(コマンドライン文字列に変数を埋め込む場合に多く発生)
  • XSS(html文字列に変数を埋め込む場合に多く発生)

システム的に文字列で値をやり取りすること自体はやむを得ない場合もありますが、こういったコードを書く場合は常にインジェクション系の問題が発生しないか注意してコードを書く必要があります。

類似問題

今回の脆弱性情報とか無関係ですが、同じようにlocation.hash経由でよく指定される内容として「サーバサイドのファイル名」があります。

これは以下の様なコードで、やはり脆弱性があります。

$.get(location.hash.substring(1), function (html) {
    $('body').html(html);
});

古い(XHR level2をサポートしていない)ブラウザであれば、上記のコードで取得できるのは同じドメイン上のファイルのみとなるため、同じドメイン上に問題のあるファイルが存在しない場合安全ですが、XHR level2をサポートしているブラウザであれば上記コードでは外部サーバのデータを取得することが可能なため危険なコードとなります。

この問題はlocation.hashを「ファイルパスでしか使用できない文字列に制限する」ことで回避可能ではありますが、ChromeやIEはロングIPがサポートされていることと、スキーマの省略をできるので文字列制限だけでは回避が難しい問題があります。

// XMLHttpRequest cannot load http://192.168.0.1/.
$.get('//3232235521', function (html) {
    $('body').html(html);
});

IPアドレス - Wikipedia
The Sexy Assassinで紹介されてるCSS HTML Attribute Readerがどこまで危険か検証してみた

まず、外部から任意のファイルを指定してのアクセスできる構成はできるだけ避けるほうが無難ですが、もしそういう構成をとらざるをえない場合以下の点に注意してください。

  • アクセスできるディレクトリを指定する
    アクセスできるディレクトリを「/html/」等の文字列を追加することでschemeを省略したURLや同じドメイン上の想定していないディレクトリへのアクセスを回避出来ます。また、アクセスできるディレクトリ以下には実際に使用するhtml以外のファイルは存在しないようにしましょう(ユーザがファイルをアップできないように)。
  • .replace(/.+/g, ‘.’)を行う 「../」と言った方法で指定ディレクトリ外へアクセスされることを制限します。これに関しては単純に.replace(/../g, ”)といったコードでは「../」で回避できてしまうことに注意してください。

この問題は古いjQuery Mobileでも存在した問題で、元々安全に実装するのが難しい構成です。

可能であればファイル名を\wに制限し、以下の様な実装をおすすめしますが、この構成でもサーバ上の実装によっては危険な状況になる場合もあるので注意してください。

$.get('/html/'+(location.hash.replace(/\W/g, ''))+'.html', function (html) {
    $('body').html(html);
});