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

第5回 問題を発生させにくくするURLの扱い方

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

URLオブジェクト

たとえば,引数としてリソースのURLを示す文字列が与えられた関数において,そのリソースのオリジンが現在のドキュメントと同一オリジンである場合にのみtrueを返し,そうでない場合にはfalseを返すといった処理をJavaScriptで書く場合,どのようなコードを書けばいいでしょうか。

URLとしては,⁠http://example.jp/foo」といった絶対URLだけでなく,⁠/bar/text.txt」⁠text2.txt」のような相対URLも渡される可能性があります。

function isSameOrigin( url ){
    if( /* urlの指すリソースが同一オリジンのものなら */ ){
        return true;
    }
    return false;
}

相対URLの落とし穴

まず考えられるのは,引数urlを検査し,

  • 「先頭に英数字,続けて『:』があればスキーマを持つので,絶対URL」
  • 「絶対URLでも,先頭が現在のオリジンと同一であれば,trueを返す」
  • 「絶対URLでなければ相対URLであるので,trueを返す」

のようなコードを書くことです。

// bad code
function isSameOrigin( url ){
    var site = location.origin + "/";
    if( url.match( /^[\w]+:/ ) ){
        // urlは「http:」「https:」や「javascript:」などのスキーム名を持つ絶対URL
        if( site === url.substr( 0, site.length ) ){
            // 現在のドキュメントのオリジンと同一オリジンを持つ絶対URL
            return true;
        }
    }else{
        // スキームを持たないので相対URL
        return true;
    }
    return false;
}

この方法は,一見正しく思えるかもしれませんが,かんたんに検査を迂回することができます。

じつは,相対URLというものは,同一オリジン内だけでなく,⁠//」から始めることによって,現在のプロトコルと同じプロトコルの相対URLであると解釈されます。すなわち,攻撃者が引数urlに「//attacker.example.com/」のような文字列を与えた場合,このコードでは攻撃者のサイトであるhttp://attacker.example.com/を同一オリジンだと判断してしまうのです。

では,⁠先頭が//であればプロトコル相対URLである」といった判断を追加するのはどうでしょうか。そうすると,今度は攻撃者は「/\attacker.example.com/」のようなURLを与えます。これはRFCには違反したURLですが,一部のブラウザではURL先頭の「/\」「//」と等価に扱うため,攻撃者のサイトであるhttp://attacker.example.com/を同一オリジンだと判断してしまいます。

このように,検査を厳密に行おうとすると,RFCに従ってURLの仕様を知り尽くし,さらに各ブラウザごとの挙動の差までも把握していなければなりません。一方で,攻撃者はこういった複雑な仕様やブラウザの挙動差を丹念に調べ知り尽くしているので,このようなURLを自力でパースして検査するというアプローチでは対策は非常に困難なものになります。

ブラウザの機能でURLを絶対URLに変換する

そこで,URLをパースするコードは自分では書かずに,ブラウザの機能,すなわちDOMの機能を使って,与えられたURLをいったん絶対URLに変換してしまうのがいいでしょう。

現在の多くのブラウザでは,URLコンストラクタを呼び出すことで,locationオブジェクトと同じインタフェースを持つURLオブジェクトを生成できます。これを利用します。

var url = new URL( "http://example.jp/foo" );
console.log( url.href );        // "http://example.jp/foo"
console.log( url.protocol );    // "http:"
console.log( url.hostname );    // "example.jp"

相対URLを表す文字列を絶対URLに変換する場合には,URLコンストラクタの第2引数に,ベースとなるURLを与えます。

var url = new URL( "/foo", "http://example.jp/" );
console.log( url.href );        // "http://example.jp/foo"

url = new URL( "../foo", "http://example.jp/bar/baz.html" );
console.log( url.href );        // "http://example.jp/foo"
console.log( url.origin );      // "http://example.jp"

このように,URLコンストラクタを介してURLオブジェクトを生成すれば,文字列としてURLを操作することなく,相対URLを絶対URLに変換したり,そのオリジンを取り出したりできます。

function isSameOrigin( url ){
    var url = new URL( url, location.href );
    if( url.origin === location.origin ){
        return true;
    }
    return false;
}

IEへの対応

URLコンストラクタの代わりに<a>要素を用いる

Internet ExplorerではURLコンストラクタは使用できませんが,代わりに<a>要素を用いることで相対URLと絶対URLの変換を行うという方法が知られています。

<a>要素のhref属性に「/foo」のような相対URLを設定した状態で,その要素に対して以下のようにhref属性を取得すると,設定されている相対URLである「/foo」が返されます。

elm.getAttribute( "href" )

一方,elm.hrefのようにhrefプロパティを参照した場合には,⁠http://example.jp/foo」のような絶対URLが取得できます。こうした<a>要素の挙動を利用することで,ブラウザ自身の機能を使って絶対URLに変換することができるのです。

以下のコードでは,新たに生成した<a>要素のhrefプロパティにURLを設定することで,与えられたURLを強制的に絶対URLに変換しています。

function getAbsoluteUrl( url ){
    var elm;
    elm = document.createElement( "a" );
    elm.setAttribute( "href", url );
    return elm.href;
}

著者プロフィール

はせがわようすけ

株式会社セキュアスカイ・テクノロジー常勤技術顧問。 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/