やられアプリ BadTodo - 4.8 DOM Based XSS

前回:やられアプリ BadTodo - 4.7 XSS 対策方法(エスケープ処理) - demandosigno

DOM Based XSSについて。

BadTodoのトップページhttps://todo.example.jp/todolist.phpですが、このURLの後ろに#とスクリプトを追記するとXSSが発動します。
https://todo.example.jp/todolist.php#%22]%3Cimg%20src=/%20onerror=alert(1)%3E

 

#(フラグメント識別子、またはハッシュ)

URLに付く#以下の部分に応じて表示内容を変化させるアプリケーションがあります。 ブラウザのデベロッパーツールにてコードを見てみるとlocation.hashでURLの#以降の値を取得しています。

#はページ内リンクに使われたりもしますが、この例では#の値に従ってチェックボックスにチェックを付ける動作をします。#2,4とするとid[]=2,4にチェックが付きます(id[]=3のTodoは非公開のため表示されていない)。

最初からチェックが付いているページを用意するなどの用途に使えます。

jQueryの機能の不適切な利用

 jQueryには、jQuery( )という関数があり、多くの場合 $( )という別名で使用されます。この$関数に様々な引数を与えることで多様な動作を簡潔に指定できます。この機能はjQueryのセレクタと呼ばれ多用されています。
$('input[name="foo"]'):input要素で name 属性が foo のものを取得。
 一方で、$関数(jQuery関数)は、下記のようにHTMLタグ文字列を指定すると、DOM要素を生成します。
$('<p>Hello</p>')
 このため、jQueryのセレクタとして要素を指定しているつもりでも、セレクタ指定文字列に外部からの入力が混ざっていると、攻撃者が新しい要素を生成できる場合があります。
「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 p.437」

今回のコード内で対応する部分が
$('input[name="id[]"][value="' + id + '"]').prop('checked', true);
です。prop()は要素の属性を取得・設定するメソッドです。開発ツールで行数部分をクリックすることでブレークポイントを作って動作を確認できます。
id部分に2が入っており、結果<input type="checkbox" name="id[]" value="2">にchecked属性を設定します。
このように、DOMを使用することでスクリプトからHTMLを操作することが可能になります。

さて、このとき#"]<img src=/ onerror=alert(1)>だったとするとこうなります。

セレクタは$('input[name="id[]"][value=""]<img src=/ onerror=alert(1)>"]').prop('checked', true);となり、新たなimg要素が作られ、onerrorイベントによりJavaScriptが実行されます。

対策

  • 最新のライブラリを用いる
  • $( ) や jQuery( )の引数は動的生成しない
  • 適切なDOM操作あるいは記号のエスケープ
  • eval、setTimeout、Functionコンストラクタなどの引数に、文字列形式で外部からの値を渡さない
    など

jQueryの新しいバージョンは入力がハッシュ#で始まる場合にセレクターにHTMLを挿入できないようにすることでこの脆弱性を修正しました。
Download jQuery | jQueryより最新の jQueryをダウンロードし読み込み先を修正します。

<script src="./js/jquery-1.8.3.js"></script> // BadTodoの現状
                           ↓
<script src="./js/jquery-X.X.X.js"></script> // 最新版を使う

そうするとシンタックスエラーとなりXSSは実行されません。

これでセレクタによるDOM Based XSSは防げますが予防としてアプリケーション側でも引数は動的生成しないことを推奨します。
例えば以下のようにfindメソッドを用いることで、動的にHTML要素を生成されることはなくなります。入力値のチェックも行っています。

} else {
  var a = checklist.split(',');
  a.map(function(id) {
    var id = parseInt(id);
    if (!isNaN(id)) {
      $('#contents').find('input[name="id[]"][value="' + id + '"]').prop('checked', true);
    };
  });
}

その他

URL上の半角スペースに%20ではなく参考書籍と同様に+を使った場合はうまくいきませんでした。書籍ではクエリー文字列の取り扱いに URI.min.js を使っているなど少し差異があるためかと思います。

反射型・格納型XSSはサーバ上でHTMLの生成が行われます。BadTodoの場合、サーバー上のPHP実行エンジンがファイルや入力値の内容を解釈して処理を行い、HTMLを作成してレスポンスとして返します。一方 JavaScriptによってクライアント上でHTMLを組み立てると攻撃用のコード部分がサーバにリクエストとして送信されません。脆弱性検査ツールではリクエスト内に含まれる文字列がレスポンス内に現れるか否かで探しているものが多いためこのタイプの脆弱性は発見できない場合があります。

BurpSuiteの組み込みブラウザには「Dom Invader」という機能があり、DOM Based XSSのチェックに使えるようです。まだ使い方を理解していません。

DOM Invader - PortSwigger

参考にしたサイト:
第6回 DOM-based XSS その1 | gihyo.jp
DOM Based XSSとは|図でわかる脆弱性の仕組み | ユービーセキュア
What is DOM-based XSS (cross-site scripting)? Tutorial & Examples | Web Security Academy
IPA - DOM Based XSSに関するレポート
location: hash プロパティ - Web API | MDN
【jQuery入門】prop(attr)の使い方と属性値の取得・設定まとめ! | 侍エンジニアブログ

次回:やられアプリ BadTodo - 4.9 XSS URL属性値に対して - demandosigno

/* -----codeの行番号----- */