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

第4回 URLとオリジン

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

URLは,Webブラウジングを開始するという意味でも,Webに関連する技術の入り口という意味でも,Webにおける起点そのものです。

攻撃者の視点から見ると,多くの場合,URLは攻撃者自身によってコントロール可能です。その複雑な構造から,プログラム内でURLをデータとして取り扱う際にまちがいが発生しやすく,セキュリティ上の問題点を引き起こしやすい格好のねらい目,攻撃の起点であるとも言えます。

また,Webアプリケーションにおいてセキュリティを確保するためには,特定のリソースがそれぞれのWebアプリケーション自身と同一の保護範囲にあるものなのか,あるいは第三者の用意した可能性があり信用できないものなのかを区別して取り扱う必要があります。この境界条件は,Webアプリケーション自身の動作しているURLを基とした「オリジン」と呼ばれる範囲によって決定されます。

今回は,JavaScriptでのセキュリティを学ぶ第一歩として,URLとオリジンという,Webアプリケーションセキュリティを考えるうえで最も基礎となる技術について説明します。

URLは想像以上に複雑なもの

URLは,Webアプリケーションにおいては「自身の位置を示すためにアドレスバーに表示される」というだけのものではありません。さまざまなリソースへアクセスするための,リソースの位置を示す基本的な情報であり,プログラム上で操作するデータとしても取り扱われます。

JavaScript上でURLをデータとして取り扱う場合,ほとんどの場合にはただの文字列として扱われますが,実際のURLはさまざまな情報を含有するデータの複合体でもあります。

「Webアプリケーションで取り扱うURL」と聞いて多くの人が最初に思い浮かべるのは,http://example.jp/のような特定のWebページを指すようなものでしょう。あるいは,もう少し複雑なhttp://example.jp:8080/foo/bar/image.jpgのようなものかも知れません。 さらに,http://example.jp/というURLのドキュメントを開いている状態では,/foo/bar/image.jpgのような文字列も,相対URLとして有効なものとなります。

プロトコルスキームだけでもさまざまのバリエーションが

上記はいずれもhttpスキームを指すURLですが,Webアプリケーションで取り扱うURLはhttpスキームおよびhttpsスキームに限りません。javascript:スキームやdata:スキームなどもありますし,Internet Explorerではvbscript:スキームも利用可能です。ローカルに保存されているファイルを直接ブラウザで開いた場合には,そのコンテンツのURLはfile:スキームを持つことになります。

さらに,navigator.registerProtocolHandler()を使用すれば,開発者は任意のプロトコルスキームをWebブラウザに登録させ,Webアプリケーションでそのプロトコルスキームを処理できるようにもなります。

プロトコルスキームだけでもこれだけのバリエーションがあり,さらにスキームに応じて以降の各パートの内容も異なるなど,URLは複雑な構造を持ちます。そのために,URLを取り扱う場合には,開発者の意図とは異なる解釈結果となることもあります。また,実際のブラウザの実装においても,仕様とは異なる解釈をされる場合が多くあります。

URLを基準にセキュリティ上の境界条件を定めていると,脆弱性につながる可能性が

URLを基準にセキュリティ上の境界条件を定めたコードを書くと,その構造の複雑さやブラウザごとの挙動差から,脆弱性を作りこむ原因になりがちです。

実際にJavaScript上のコードにおいては,開発者自身が無意識である場合も含め,以下のようにURLを基準としてセキュリティ上の境界条件を定めている場面が多々あります。

  • オープンリダイレクトが発生しないように,リダイレクト先のURLが現在のURLと同じサイトか調べる
  • <a>要素のhref属性として設定するリンク先URLがjavascriptスキームやvbscriptスキームでないか調べる
  • XMLHttpRequestで接続しようとしているリソースが現在のURLと同じサイトか調べる

こういった場面でURLの取り扱いにまちがいがあると,脆弱性へとつながります。たとえば,以下のようなコードがあったとします。

// bad code
// 与えられたURLが特定のサイトかどうかを判断する
function checkUrl( url ){
    var validSites = [ "http://example.jp", "http://example1.jp" ];
    var i, site;
    for( i = 0; i < validSites.length; i++ ){
        site = validSites[ i ];
        if( url.substr( 0, site.length ) === site ){
            return true;
        }
    }
    return false;
}

このcheckUrlという関数は,コードを書いたプログラマの意図としては「引数urlがhttp://example.jpまたはhttp://example1.jp内のコンテンツを指すものであった場合にはtrueを,そうでない場合にはfalseを返す」というものですが,実際にはurlがhttp://example.jp.attacker.example.com/のようなものであった場合にもtrueが返されます。

そのため,たとえばこの関数を用いてURLのチェックを行い,⁠関数の戻り値がtrueのときのみ,location.hrefに代入してリダイレクトする」といった処理の場合にはオープンリダイレクトが発生します。また,⁠関数の戻り値がtrueのときのみ,XMLHttpRequestでコンテンツを取得し,innerHTMLへ代入する」という処理を行っている場合には,XSSが発生することになります。

経験を積んだプログラマならこのコードを見た瞬間にまちがいに気づくくらい,わざとらしいコードに見えるかもしれませんが,現実にもこれとよく似たコードは多数存在しています。またこれ以外にも,URLを基準として判断を行う場面において,攻撃者が細工したURLを与えることで意図しない判断結果を引き起こし,セキュリティ上の問題点が発生する場面は非常に多くあります。

具体的な対策方法については,今後の回で解説します。今の段階では,⁠URLは考えている以上に複雑であり,それが原因でセキュリティ上の問題が発生することが多い」という点を理解しておいてください。

※)
現在の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/

コメント

コメントの記入