2016年12月2日金曜日

アイ・オー・データのポケットルーターWFS-SR01の脆弱性について調べてみた

2016年11月2日に、アイ・オー・データ機器が販売する「ポケドラ」ことWFS-SR01に複数の脆弱性があることが報じられました。
株式会社アイ・オー・データ機器が提供する WFS-SR01 は、無線 LAN ルータ機能を搭載したポータブルストレージ機器です。WFS-SR01 のポケットルーター機能には、次の複数の脆弱性が存在します。
  • 任意のコマンド実行 - CVE-2016-7806
  • アクセス制限不備 - CVE-2016-7807
JVN#18228200: WFS-SR01 における複数の脆弱性より引用
任意のコマンド実行ということは、OSコマンドインジェクションでもあるのだろうかと思い、腕試しの目的で当該機種を入手して調べてみました。その結果を報告します。

ポケドラとは

ポケドラは、Wi-Fiモバイルストレージと呼ばれるカテゴリに属し、以下の機能を提供しています。
  • Wi-Fi経由で使用できるストレージ
  • モバイルバッテリー
  • SDカードリーダー
  • ポケットルーター
これらのうち、「ポケットルーター」機能が問題になりました。名称がややこしいのですが、よく普及している「モバイルーター」とは異なり、「ポケットルーター」には 3GやLTEのような自力のネット接続機能はありません。ホテル等が備える有線LANに接続して、それをスマホ等のWi-Fi機器に中継する機能を提供しています。
もう少し技術的にいえば、有線LAN側はDHCPクライアントとして有線LANに接続(固定IPも可能ですが)し、それをNATで複数のWi-Fi機器で共有します。そのためのDHCPサーバー機能もあります。以下に設定画面の一部を示します。



試してみる

私が入手した端末は、ファームウェアバージョン 2.000.034 というものです。これを仮想的なインターネット環境(IPアドレスはグローバルを想定して203.0.113.0/24を使用)につなげました。有線LAN側(インターネット側)は、実験環境で用意したDHCPにより 203.0.113.10 がポケドラに割り当てられています。
お約束にしたがい、まずはポートスキャンにかけてみましょう。以下は、有線LAN側、すなわち場合によってはインターネットに接続されている側からの結果です。
$ nmap -p 1-65535 203.0.113.10

PORT     STATE SERVICE
23/tcp   open  telnet
80/tcp   open  http
81/tcp   open  hosts2-ns
139/tcp  open  netbios-ssn
445/tcp  open  microsoft-ds
5880/tcp open  unknown
おやおや、いきなり興味深い結果が表示されました。80のhttpは管理画面でしょうが、一般的なルーターでは、インターネット側からアクセスは通常できないように制御されています。23のtelnet、139と445のファイル共有も興味深いですね。
それでは、まずtelnetでログインしてみましょう。管理画面のデフォルトアカウントはadmin/adminなので、それで試してみます…
$ telnet 203.0.113.10
Trying 203.0.113.10...
Connected to 203.0.113.10.
Escape character is '^]'.

WFS-SR01 login: admin
Password:

BusyBox v1.12.1 (2012-04-26 15:28:18 PHT) built-in shell (ash)
Enter 'help' for a list of built-in commands.

$ uname -a
Linux WFS-SR01 2.6.21 #5 Fri Nov 1 13:36:46 CST 2013 mips unknown
$ whoami
-sh: whoami: not found
$ who
USER       TTY      IDLE      TIME            HOST
admin      pts/0    00:00     Feb  7 15:50:37
$ free
              total         used         free       shared      buffers
  Mem:        28000        26340         1660            0         1388
 Swap:            0            0            0
Total:        28000        26340         1660
$ df
Filesystem           1k-blocks      Used Available Use% Mounted on
rootfs                    4800      4800         0 100% /
/dev/root                 4800      4800         0 100% /
/dev/sda1              1951676      1580   1950096   0% /data/UsbDisk1/Volume1
$
なんと、いきなりtelnetでログインできてしまいました。
unameの表示から、Linux 2.6.21、CPUはMIPSであることがわかります。
それにしても…「任意のコマンド実行」とはこれのことですか…あまりのあっけなさに、腕試しをしようという目論見はなんとも不発な感じで微妙な気持ちになりましたが、これはこれで興味深いので、さらに試してみました。

インターネットに向けてファイルを公開してしまう

telnetはいったん中断して、今度はファイル共有を試します。
仮想インターネットに接続したWindows XP端末から、\\203.0.113.10にアクセスしてみます。すると、下記のようにログオンプロンプトが表示されます。


ここで、先程同様 admin/admin を試すと、以下のようにファイル共有ができました。


ファイルの読み込みだけでなく、書き込みも可能でした。
しかし、ここまでの問題であれば、デフォルトパスワードを変更しておけば悪用はできないはずで、「違法ではないが一部不適切」と強弁できなくもないなと思いました。


rootログインしてftpdを動かす

そこで再びtelnetに戻り、rootのパスワードを試してみました。いくつかの方法を試した後、rootのパスワードが判明しましたが、詳細は伏せます。判明したパスワードを見て、以下のような感想を持ちました。

パスワード-もっと強くキミを守りたい- : IPA情報処理推進機構より引用

それはともかく、rootでログインしてみましょう。
$ telnet 203.0.113.10
Trying 203.0.113.10...
Connected to 203.0.113.10.
Escape character is '^]'.

WFS-SR01 login: root
Password:
login: can't chdir to home directory '/root'

BusyBox v1.12.1 (2012-04-26 15:28:18 PHT) built-in shell (ash)
Enter 'help' for a list of built-in commands.

# who
USER       TTY      IDLE      TIME            HOST
root       pts/0    00:00     Nov 30 20:50:40
# pwd
/
# 
まぁ、パスワードがわかればログインできますよね…
rootがとれましたので、ちょっと遊んでみようということで、なにか外部からプログラムをアップロードして動かしてみることにします。といってもあらたにビルドするのは面倒なので、busybox本家サイトから、MIPS用のフル版busyboxバイナリをダウンロードして、ホケドラに挿しているSDカードに保存しました。
# ./busybox-mipsel
BusyBox v1.21.1 (2013-07-08 11:09:23 CDT) multi-call binary.
BusyBox is copyrighted by many authors between 1998-2012.
Licensed under GPLv2. See source distribution for detailed
copyright notices.
...
動いていますね。
ポケドラに元々インストールされているbusyboxではできないこととして、ftpdを起動してみます。
# ./busybox-mipsel  tcpsvd 0 21 ./busybox-mipsel  ftpd -w /
別の端末から接続してみましょう。
$ ftp 203.0.113.10
Connected to 203.0.113.10.
220 Operation successful
Name (203.0.113.10:ockeghem): anonymous        ← パスワードは要求されない
230 Operation successful
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> pwd
257 "/"
ftp> ls
200 Operation successful
150 Directory listing
total 0
drwxr-xr-x    2 root     root           802 Apr  4  2014 bin
drwxr-xr-x    3 root     root            20 Apr  4  2014 boot
drwxr-xr-x    3 root     root             0 Nov 30 20:50 data
drwxr-xr-x    6 root     root             0 Nov 30 20:50 dev
drwxr-xr-x   14 root     root             0 Nov 30 20:50 etc
...
226 Operation successful
ftp> cd /data/UsbDisk1/Volume1/
250 Operation successful
ftp> pwd
257 "/data/UsbDisk1/Volume1"
ftp> ls
200 Operation successful
150 Directory listing
total 1596
drwxrwxrwx    2 root     root          4096 Nov  9 22:30 System Volume Information
-rwxrwxrwx    1 root     root       1576152 Nov 30 20:58 busybox-mipsel
-rwxrwxrwx    1 root     root         10923 Dec  2  2016 ockeghem.png
drwxrwxrwx    2 root     root          4096 Nov 30 20:57 test
226 Operation successful
ftp> put shop.zip                        ← shop.zipをアップロード
local: shop.zip remote: shop.zip
200 Operation successful
150 Ok to send data
226 Operation successful
2753 bytes sent in 0.00 secs (10380.2 kB/s)
ftp> ls
200 Operation successful
150 Directory listing
total 1596
drwxrwxrwx    2 root     root          4096 Nov  9 22:30 System Volume Information
-rwxrwxrwx    1 root     root       1576152 Nov 30 20:58 busybox-mipsel
-rwxrwxrwx    1 root     root         10923 Dec  2  2016 ockeghem.png
-rwxrwxrwx    1 root     root          2753 Nov 30 21:54 shop.zip
drwxrwxrwx    2 root     root          4096 Nov 30 20:57 test
226 Operation successful
ftp> quit
221 Operation successful
$
認証なしで書き込みもできるので、攻撃者にとっては便利そうです。これはほんの一例ですが、ポケドラに挿したSDカードの内容が読み取れることはもちろんですが、DNSの設定を変更されることで、意図しないサイトに接続を誘導される等の攻撃も考えられます。

いったんまとめ

ここまで検証したことをまとめると、下記のようになります。

  • 任意のコマンド実行 - CVE-2016-7806  telnetポートが有効であり、外部からadmin/adminという容易に推測できるアカウントでログインでき、rootログインされてしまう可能性もある
  • アクセス制限不備 - CVE-2016-7807 telnetに加え、Windowsファイル共有、管理画面等がインターネット側からも使え、情報漏えいや不正な操作に使われてしまう

最新版での対策

2016年11月15日に最新のファームウェアが公開されました(画面上は2.000.040と表示)。このバージョンでは、以下の変更がなされたようです。
  • telnetポートが塞がれた(有線LAN、無線LANとも)
  • 有線LAN(インターネット)側のポートを塞いだ
これらにより、上記で紹介した問題は対策されていると考えます。

そもそもこれは脆弱性だったのか

見出しをご覧になって、「おかしなことを言う」と思われたでしょうが、そもそもポケドラはインターネットに直接接続することを想定していたのかという疑問があります。ネットの商品説明や取扱説明書を見ても、「ホテルの有線LANをWi-Fi化」などという表現であり、インターネットにつなぐことができるという表現ではありません。
しかしながら、「ポケットルーター」機能と銘打っており、実際にインターネットに接続すれば一応のWi-Fiルーターとして使用できることから、ユーザーの中にはインターネットに直接接続する方もいるでしょう。そのようなユースケースを重く見て、株式会社アイ・オー・データ機器は、当該問題を脆弱性して公表し、製品回収までしたのだと予想します。そのような株式会社アイ・オー・データ機器の姿勢に私は敬意を表します。

まとめ

本稿で紹介した「脆弱性」は、実装上のミスというよりは、商品コンセプトにやや曖昧なところがあり、意図しない使われ方をした場合に危険な状態になることを想定できなかったことによるものと考えます。そして、今まで発表された IoT機器のセキュリティ問題の多くは、「ユースケースを想定しきれていなかった」ことによるわけで、その意味で象徴的な事例とも言えます。

また、改修後のポケドラは、必要最低限の対策であり、一般的な家庭用Wi-FIルーターが備えるセキュリティ機能はないことから、あくまでホテル等で提供されるインターネット接続をWi-Fiに変換するような目的で使用されることを推奨します。つまり、インターネットに直接接続することは、直ちに危険ということではないにしても、避けた方がよいということです。

追記

複数の方から、ホテルのインターネットでも部屋間で通信できるのでインターネットと同等のリスクとなり得るという指摘をいただきました。たしかにその通りです。ご指摘いただきありがうございました。この件ついては更に追記するかもしれません。

2016年6月2日木曜日

Ruby on Railsの潜在的なリモートスクリプトインジェクション脆弱性CVE-2016-2098

今年の2月末に、Ruby on Railsに潜在的なリモートスクリプトインジェクションの脆弱性CVE-2016-2098が報告されています。攻撃コード(PoC)も公開されていますが、現実の攻撃が行われているという発表はないようです。この脆弱性の内容と対策について報告いたします。

背景

Hello Worldのような以下のシンプルなアプリケーション(コントローラ)を考えます。
class HelloController < ApplicationController
  def index
    render 'hello/hello'
  end
end
これに対するテンプレート hello/hello.html.erb は以下だとします。
<div>Hello world</div>
ご覧のように、上記テンプレートを指定した場合、Hello worldが表示されます。
次に、以下のテンプレート hello/bye.html.erb を用意します
<div>Good bye world</div>
これを呼び出すには、コントローラにてrender 'hello/bye'を実行すればよいわけですが、helloとbyeを切り分けられるように、コントローラを以下のように修正します。
class HelloController < ApplicationController
  def index
    render params[:template]
  end
end
これを呼び出すには、以下のようにします。


しかし、renderメソッドの引数を外部から自由に指定できるようにするのは、危なっかしい感じです。

renderメソッドの inlineオプション

renderメソッドにはinlineオプションというものがあり、テンプレート文字列を指定することができます。
render :inline => "<%= Time.now %>"
実行結果を以下に示します。Time.nowメソッドの呼び出しにより、現在時刻が表示されています。


すなわち、外部からrenderメソッドを操作してinlineオプションを指定できれば、任意のRubyスクリプト(上記例ではTime.now)を実行できることになります。具体的には、renderメソッドに下記のハッシュを指定すればよいことになります。
hash = {:inline => "<%= Time.now %>"}
render hash
しかし、そんなことができるのでしょうか?

JSONによりハッシュを外部から指定できる

公開されているCVE-2016-2098のPoCでは、JSONによりrenderメソッドにハッシュを指定する方法が示されています。その様子を以下に示します(少し変えています)。以下の攻撃例では、FileUtils.touchメソッドにより、rootedというファイルを作成することで攻撃の証拠を示しています。


上記の攻撃で、ファイル rooted が作成された様子を下図に示します。
$ ls -l rooted
-rw-rw-r-- 1 ockeghem ockeghem 0  6月  1 22:29 rooted

対策

Ruby on Railsの以下のバージョンで修正されています(今年の2月29日公開)。
  • Rails 3.2.22.2
  • Rails 4.1.14.2
  • Rails 4.2.5.2
これらにバージョンアップすることで攻撃は防がれるようになりますが、そもそもrenderメソッドに任意の値を指定できることがとても危険な状態であると考えます。このため、以下を推奨します。
  • 可能な限りrenderメソッドの引数には外部由来の値を指定しない
  • やむを得ずrenderメソッドの引数を外部から指定する場合は、ホワイトリスト(たとえば指定可能なテンプレートファイル名の一覧)による検証を行う

まとめ

Ruby on Railsの潜在的なリモートスクリプトインジェクションの脆弱性CVE-2016-2098について説明しました。renderメソッドには強力なオプション指定機能があるため、外部から自由な値を指定できると危険です。そもそも、renderメソッドのinlineオプションでrubyスクリプトが実行できることは、脆弱性ではなく仕様です。
このため、この問題は本来Ruby on Railsの脆弱性というよりは、アプリケーション側の問題であと考えます。この問題が「潜在的な」脆弱性と表現されているのは、このような背景からだと思います。


【HASHコンサルティング広告】
HASHコンサルティング株式会社は、ウェブアプリケーションのセキュリティに関心のあるセキュリティエンジニアを募集しています。
興味のある方は、twitterfacebookのメッセージ、あるいは問い合わせページからお問い合わせください。

2016年4月18日月曜日

PDOのサンプルで数値をバインドする際にintにキャストしている理由

先日PHPカンファレンス北海道2016にて「『例えば、PHPを避ける』以降PHPはどれだけ安全になったか」と題して基調講演を担当致しました。その際のスライドはこちら

そうしたところ、以下のご指摘をいただきました。
39番目のスライドは下記ですね。intへのキャストは下から3行目の (int) $id を指します。



SQLデータベースは、int型よりも大きな桁数を扱える場合があるので、intへのキャストを避けた方がよいという指摘は一般論としてはもっともなものだと考えます。PHPの場合、9223372036854775807を越える数字文字列をint型にキャストすると、9223372036854775807が返ります(64ビット環境の場合)。これを考慮していない場合、悪用される可能性はあります。
$ php -r "var_dump((int)'999999999999999999999999999999');"
int(9223372036854775807)
それにも関わらず、MySQLとPDOの組み合わせの場合、int型へのキャストが望ましい状況があります。その理由を説明します。

PoC

PDOのプレースホルダの挙動について、以下のサンプル(PoC)で紹介します。

テーブル定義とデータ
CREATE TABLE xdecimal (id DECIMAL(20));            -- DECIMAL(20)は10進20桁の数値型
INSERT INTO xdecimal VALUES (18015376320243459);
INSERT INTO xdecimal VALUES (18015376320243460);
INSERT INTO xdecimal VALUES (18015376320243461);

PHPサンプル
<?php
  $db = new PDO("mysql:host=127.0.0.1;dbname=test;charset=utf8", DBUSER, DBPASSWD);
  $ps = $db->prepare("SELECT id FROM xdecimal WHERE id=:id");
  $id = '18015376320243461';
  $ps->bindValue(':id', $id, PDO::PARAM_INT); // intへのキャストはしない
  $ps->execute();
  $row = $ps->fetch();
  echo "$id -> ${row[0]}\n";
  $db = null;
このスクリプトはテーブル xdecimal からid=18015376320243461を検索して表示します。

生成されるSQL文

上記スクリプトのプレースホルダにより生成されるSQL文は下記のとおりです。
SELECT id FROM xdecimal WHERE id='18015376320243461'
ポイントは、PDO::PARAM_INTと整数型を指定しているのに、文字列リテラル(赤字部分)として値が生成されているところです。

MySQLと暗黙の型変換の問題

ここで問題は、列idの型がDECIMAL(20)という数値型なのに、文字列型の値と比較しているところです。MySQLは、この場合、両者を浮動小数点型に変換してから比較します。以下は、MySQL 5.6のリファレンスマニュアルから該当部分の引用です。
次のルールでは、比較演算の際にどのように変換が発生するのかについて説明します。
  • NULL-safe <=> 等価比較演算子の場合を除いて、一方または両方の引数が NULL の場合は、比較の結果も NULL になります。NULL <=> NULL の場合は、結果が true になります。変換は必要ありません。
  • 比較演算の両方の引数が文字列の場合は、文字列として比較されます。
  • 両方の引数が整数の場合は、整数として比較されます。
  • 16 進値が数字と比較されない場合は、バイナリ文字列として処理されます。
  •  引数の一方が TIMESTAMP または DATETIME カラムで他方が定数の場合は、比較が実行される前に定数がタイムスタンプに変換されます。これは、ODBC により適合させるために実行されます。これは、IN() への引数には実行されません。念のため、比較を行う際は、常に完全な日付時間、日付、または時間文字列を使用してください。たとえば、日付または時間の値とともに BETWEEN を使用したときの結果を最適にするには、CAST() を使用して、明示的に値を目的のデータ型に変換します。
  • テーブル (複数可) からの単一行のサブクエリーは、定数とみなされません。たとえば、サブクエリーで DATETIME 値と比較される整数が返される場合は、比較が 2 つの整数として実行されます。整数は時間値には変換されません。オペランドを DATETIME 値として比較するには、CAST() を使用して、明示的にサブクエリーの値を DATETIME に変換します。
  • 引数のいずれかが 10 進値の場合、比較はその他の引数に依存します。その他の引数が 10 進値または整数値の場合、引数は 10 進値として比較され、その他の引数が浮動小数点値の場合、引数は浮動小数点値として比較されます。
  • ほかのすべてのケースでは、引数は浮動小数点 (実) 数として比較されます
12.2 式評価での型変換(MySQL 5.6 リファレンスマニュアル)より引用
すなわち、列 id と、文字列リテラル'18015376320243461' の双方を浮動小数点数に変換してから比較することになります。

PoCの実行結果

前記PHPスクリプトの実行結果は以下の通りです。

$ php xdecimal.php
18015376320243461 -> 18015376320243459

18015376320243461を検索したのに、18015376320243459が返るという不思議な結果となっています。SQL文単体の実行では下記となります。
mysql> SELECT id FROM xdecimal WHERE id='18015376320243461';
+-------------------+
| id                |
+-------------------+
| 18015376320243459 |
| 18015376320243460 |
| 18015376320243461 |
+-------------------+
3 rows in set (0.00 sec)
このような奇妙な結果となる原因は、MySQLの「引数は浮動小数点 (実) 数として比較されます」という仕様に起因します。浮動小数点数(倍精度)の仮数部の桁数は52ビット(暗黙のビットを足して53ビット、10進16桁弱)しかなく、18015376320243461という10進17桁の整数を正確に表現できないことが原因です。

バインド値を整数にキャストした場合

一方、bindValueでバインドする値をint型にキャストすると、生成されるSQL文と実行結果は下記となります。
mysql> SELECT id FROM xdecimal WHERE id=18015376320243461;
+-------------------+
| id                |
+-------------------+
| 18015376320243461 |
+-------------------+
1 row in set (0.00 sec)
今度は浮動小数点数を経由しないため、正確な結果が返ります。先のスライドでint型へのキャストをいれていた理由は、このためです。

もっとよい方法はないか?

64ビット版のPHPを使った場合でも、int型の最大値は9223372036854775807なので、これを超えると冒頭の指摘のように不具合がおきます。この場合はどうしたらよいでしょうか?
そもそも数値型を使わずに文字列型を使う方法もありますが、その場合は数値計算が出来ません。せっかくDECIMAL型は65桁までの十進数が使えるのにもったいないですね。

ちょっと面倒ですが、以下のように型変換を明示すれば、暗黙の型変換およびそれに伴う浮動小数点数への変換を防ぐことが出来ます。
SELECT id FROM xdecimal WHERE id=CAST(:id AS DECIMAL(20))
実行結果は以下の通りです。大丈夫ですね。
mysql> SELECT id FROM xdecimal WHERE id=CAST('18015376320243461' AS DECIMAL(20));
+-------------------+
| id                |
+-------------------+
| 18015376320243461 |
+-------------------+
1 row in set (0.01 sec)

まとめ

PDOのサンプルスクリプトで、バインド時に整数型の値をintにキャストしていた理由を説明しました。intへのキャストは桁あふれの危険性はあるものの、浮動小数点数への暗黙の型変換よりはマシという意味で、一種のバッドノウハウだと思います。
MySQLの暗黙の型変換は本当にやっかいで、詳しくは下記の参考文献をお読み下さい。本当に望ましい書き方は、PHPスクリプトではなくSQL文側にキャストを書くことでしょうが、もっと良い方法があれば、ご教授下さい。

参考文献


蛇足

実は、先のid型にインデックスをつけた場合は、先ほどとは挙動が変わります。
mysql> ALTER TABLE xdecimal ADD INDEX(id);
Query OK, 0 rows affected (0.15 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SELECT id FROM xdecimal WHERE id='18015376320243461';
+-------------------+
| id                |
+-------------------+
| 18015376320243461 |
+-------------------+
1 row in set (0.00 sec)
インデックスの有無で挙動が変わるのは、MySQLのバグではないかと思いますが、この「バグ」を修正するのは中々やっかいだなと思います。


【HASHコンサルティング広告】
HASHコンサルティング株式会社は、ウェブアプリケーションのセキュリティに関心のあるセキュリティエンジニアを募集しています。
興味のある方は、twitterfacebookのメッセージ、あるいは問い合わせページからお問い合わせください。

2016年4月14日木曜日

hiddenなinput要素のXSSでJavaScript実行

脆弱性診断をやっていると、たまにtype=hiddenのinput要素にXSSがあるけど、現実的な攻撃には至らないものにぶちあたることがあります。サンプルコードを以下に示します。
<body>
入力確認をお願いします。
<?php echo htmlspecialchars($_GET['t']); ?><br>
<form action='submit.php'>
<input type='hidden' name='t' value='<?php
  echo htmlspecialchars($_GET['t']); ?>'>
<input type='submit'>
</body>
正常系の呼び出しは下記のようになります。

http://example/hidden-xss.php?t=yamada


HTMLソースは下記の通りです。
<body>
入力確認をお願いします。
yamada<br>
<form action='submit.php'>
<input type='hidden' name='t' value='yamada'>
<input type='submit'>
</body>
このスクリプトの何が悪いかというと、属性値をシングルクォートで囲っているのに、htmlspecialcharsのENT_QUOTESオプションを指定していないために、シングルクォートがエスケープされないところにあります。しかし、現実的な攻撃は難しいとされていました。

'><script>alert(1)</script>を指定すると、以下のHTMLが生成されますが、JavaScriptは実行されません。
<input type='hidden' name='t' value=''&gt;&lt;script&gt;alert(1)&lt;/script&gt;'>
' onmouseover='alert(1) を指定する方法はどうか。以下のように、onmouseover属性は作れますが、type=hiddenの場合、マウスカーソルを合わせることができずイベントも発生しません。
<input type='hidden' name='t' value='' onmouseover='alert(1)'>
ところが、malaさんのツイートで知りましたが、PortSwigger Web Security Blogに以下のPoCが発表されていました
<input type="hidden" accesskey="X" onclick="alert(1)">
上記のタグをXSSで生成させると、下記の条件でJavaScriptが実行されます。

  • 被害者ユーザがFirefoxを使っている かつ
  • 被害者がSHIFT+ALT+X キーを押す

これを応用して、前記のサンプルコードを攻撃してみましょう。

http://example/hidden-xss.php?t='+accesskey%3d'X'+onclick%3d'alert(1)

HTMLソースは下記となります。
<body>
入力確認をお願いします。
' accesskey='X' onclick='alert(1)<br>
<form action='submit.php'>
<input type='hidden' name='t' value='' accesskey='X' onclick='alert(1)'>
<input type='submit'>
</body>
ブラウザ側で SHIFT+ALT+X を押すと、下記のようにJavaScriptが実行されます。


ということで、type=hiddenなinput要素に閉じたXSSであっても、被害者がFirefoxを使っている場合、JavaScriptを起動できる場合があることが分かりました。

問題は、被害者にどうやって SHIFT+ALT+X を押させるかですが、以下のようにiframeを使う手があります。攻撃対象サイトは半透明にしていますが、実際の攻撃では透明にするなど、工夫の余地があります。


被害者が罠の誘導にだまされて SHIFT+ALT+X を押してしまうと、下記のようにJavaScriptが動きます。


まとめ

type=hiddenなinput要素に閉じたXSSでは、従来現実的な攻撃は難しいと思われていた(要出典)と考えますが、accesskeyとユーザーへの誘導により、JavaScriptを実行できる場合があることが分かりました。
脆弱性診断の実務では、従来でもこのような「エスケープ漏れ」に対しては指摘は行っていたと思いますが、その危険度の判定が変わる可能性があります。具体的には、元々「Information(念のためお知らせ)」としていた場合は、「Low(低)」くらいが妥当ではないでしょうか。元々Lowでつけていた場合は、Lowのままでもよいかと思いますが、現実的なリスクは変わることになります。

アプリケーション開発の立場においては、現実的な攻撃の可能性にまどわされないで、エスケープすべきものは淡々と正しくエスケープするようにしておけば、この手の「新たな攻撃経路」に右往左往する必要はありません。


【HASHコンサルティング広告】
HASHコンサルティング株式会社は、ウェブアプリケーションのセキュリティに関心のあるセキュリティエンジニアを募集しています。
興味のある方は、twitterfacebookのメッセージ、あるいは問い合わせページからお問い合わせください。

2016年3月23日水曜日

StartSSLにドメイン認証不備の脆弱性

Osama almanna's blogにて、StartSSLにドメイン認証に脆弱性があったと報告されています。
In 9 March, 2016 During my research I was able to replicate the attack and issue valid certificates without verifying the ownership of the website which I will explain later in my post, the vulnerability was reported and fixed within hours. 
ウェブサイトの所有権を検証しないで、正当な証明書が交付されるというものですね。脆弱性は報告の後数時間で修正されたとのことです。

以下、彼のブログ記事を元に、脆弱性の内容と修正方法について説明します。

問題の説明

StartSSLのドメイン認証証明書は無料で交付されるため、私も実験用サイトなどで利用しており、アカウントを既にもっています。以下、tokumaru.orgというドメイン名の認証を試みます。
以下は、Validation WizardからDomain Validationを選択して、ドメイン名(tokumaru.org)を入力しているところです。

その後サイト側でwhois情報を参照(以前はこのステップはなかったと記憶していますが)し、whoisに登録されたメールアドレスその他、認証に用いるメールアドレスを返します。
POST /Validate/GetWhois HTTP/1.1   ← whois情報を得るためのリクエスト
…

domainName=tokumaru.org&isNewWhois=1

HTTP/1.1 200 OK                       ← whois情報その他からのメールアドレス一覧
Content-Type: application/json; charset=utf-8
Content-Length: 105
...

"0|proxy@whoisprotectservice.com|postmaster@tokumaru.org|hostmaster@tokumaru.org|webmaster@tokumaru.org|"
上記のレスポンスは、ドメイン名の所有者であることを確認するためのメールアドレスの一覧ですね。これを元に、画面は以下のように変わります。


ラジオボタンは動的に生成されていますが、DOMの内容を見ると、以下の内容になっています。
<input name="ValidateEmails" type="radio" checked="checked" value="proxy@whoisprotectservice.com">proxy@whoisprotectservice.com
<input name="ValidateEmails" type="radio" value="postmaster@tokumaru.org">postmaster@tokumaru.org
<input name="ValidateEmails" type="radio" value="hostmaster@tokumaru.org">hostmaster@tokumaru.org
<input name="ValidateEmails" type="radio" value="webmaster@tokumaru.org">webmaster@tokumaru.org
ここで「Send Verification Code」ボタンを押すと、以下のリクエストがAJAXで送信されます。
POST /Validate/SendDomainVerifyEmail HTTP/1.1               ← 認証コードを送信するリクエスト
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 69
Cookie: fid=DE64D0FE74B0…

domainName=tokumaru.org&sendToEmail=postmaster%40tokumaru.org&index=0

HTTP/1.1 200 OK                                 ← レスポンス
Content-Type: application/json; charset=utf-8
Content-Length: 12

{"status":1}
赤字で示したpostmaster%40tokumaru.orgに検証コードを送り、それを受信できることでドメイン名の正当な所有者であることを検証するという、ドメイン認証証明書の検証方法としてよくある方法です。ここで指定したメールアドレスに以下のようなメールが届きます。

このメール中のverification codeを先の画面上で入力してValidationボタンを押せば、ドメイン認証は完了です。

ここで問題は、さきほど赤字で示したメールアドレスを別のものに差し替えてもそのまま動いてしまうことでした。例えば、cracker@gmail.comのようなメールアドレスに差し替えても、そこにverification codeが送信され、tokumaru.orgのドメイン名の認証が通ってしまうということです。これはまずい…

どう修正すべきか

Osama almanna's blogでは、わざわざ古いOWASP Top 10 2004を参照して、これはinvalidated input vulnerability(検証されていない入力値の脆弱性)と指摘しています。確かに、正しいメールアドレスのリスト(ホワイトリスト)はサイト側は分かっているので、これと比較検証することで、正しいメールアドレスであることは確認できます。
しかし、メールアドレスは4つの選択肢から選ぶわけなので、フルのメールアドレスは表示のためだけに用い、ラジオボタンの属性値は1から4の数字にしてしまえば、仮に検証で多少ミスをしたとしても、まったく関係のないメールアドレスを指定できてしまうことはなかったはずです。つまり、メールアドレスの選択肢はユーザ入力ではなく、システム側で生成した値なのに、それを文字列として受け取ることが問題と言えます。
まとめると、以下のようになります。
  • 利用者本人からも改変されると困る値はtype=hiddenのinput要素やラジオボタン、セレクト要素などで受け渡しせずに、セッション変数を用いるべし
  • 上記で複数の値から利用者に選択させる場合は、選択肢の中身はセッション変数におき、数字等で選択の指定をさせるとよい


【HASHコンサルティング広告】
HASHコンサルティング株式会社は、ウェブアプリケーションのセキュリティに関心のあるセキュリティエンジニアを募集しています。
興味のある方は、twitterfacebookのメッセージ、あるいは問い合わせページからお問い合わせください。


2016年3月22日火曜日

ウェブアプリケーションにおいて「ホワイトリスト」と"White List"は用法が異なる

海外(主に米国)のウェブアプリケーションセキュリティのドキュメントを読むと、"white list input validation" という言い方がたびたび出てきます。たとえば、OWASPのSQL Injection Prevention Cheat Sheetには、まさにWhite List Input Validationという節があります。
3.2 White List Input Validation
Input validation can be used to detect unauthorized input before it is passed to the SQL query. For more information please see the Input Validation Cheat Sheet.

【私訳】
3.2 ホワイトリスト入力値検証
SQLクエリに渡される前に無許可の入力値を検知するために入力バリデーションを用いることができます。詳細は、入力値検証チートシートを参照ください。
しかし、長年このwhite listの意味が私には謎でした。というのは、引用した文に続いて、以下の文があるからです。
Validated data is not necessarily safe to insert into SQL queries via string building.

【私訳】
バリデーションされたデータは、文字列組み立てを通じてSQLクエリに挿入する上では必ずしも安全ではありません
ホワイトリストで検証した値が安全ではない、ですと?
しかし、このような文章は珍しくありません。以下は、OWASP Top 10 2016からA1-Injectionの解説です。
3. Positive or “white list” input validation is also recommended, but is not a complete defense as many applications require special characters in their input. If special characters are required, only approaches 1. and 2. above will make their use safe.

【私訳】 3.ポジティブ(いわゆる「ホワイトリスト」)入力値検証も推奨されますが、 多くのアプリケーションが特殊文字の入力を必要とするため、完全な防御ではありません。特殊文字が要求される場合、上述の1.と2.(訳注: 安全なAPIの使用とエスケープ処理)のみにより、特殊文字の使用が安全になります。
このような用例から、私は英語圏のウェブアプリケーションセキュリティに関する文書では、white listの定義は「アプリケーションが許可した入力値」、もっと言えば「アプリケーションの入力値に対する仕様」であると解釈するしかないと考えるに至りました。このあたり、海外のドキュメントに対する知識が豊富というわけではないので、私の理解が間違いであればご指摘下さい。

しかし、この定義は私の「ホワイトリスト」の語感とは異なります。ホワイトリストというからには、

ホワイト = 安全
リスト = 列挙されたもの

であるはずであり、「安全な値の列挙」がホワイトリストの定義だと思うからです。そして、用語辞典でのホワイトリストの定義もそのようになっています。
ホワイトリストとは、警戒する必要のない対象の一覧表のこと。対義語はブラックリスト。
ホワイトリスト - Wikipedia より引用
ホワイトリストとは、対象を選別して受け入れたり拒絶したりする仕組みの一つで、受け入れる対象を列挙した目録を作り、そこに載っていないものは拒絶する方式。また、そのような目録のこと。対義語は「ブラックリスト」(black list)で、目録に載っているものだけを拒絶し、それ以外は受け入れる方式である。
ホワイトリストとは|ホワイトリスティング|white list|WL - 意味/定義 : IT用語辞典 から引用
私の語感や用語集での意味は上記の通りだとしても、日本のウェブアプリケーション開発者の語感はどうだろう、それを知りたいと思うようになりました。そこで、簡易な調査ではありますが、Twitter上で以下の選択肢によるホワイトリストに対するアンケートを実施しました。
  • 許可された入力値をリストとして列挙したもの
  • 入力値が安全になるように文字種等を制限したもの
  • 入力値に対するアプリケーションの仕様
  • その他(よろしければメンションで教えて下さい)
実に580名もの方にご協力いただき、ありがとうございました。結果は下記の通りで、「許可された入力値をリストとして列挙したもの」が84%と大半を占める結果とりました。

ということで、私の語感は、日本の多くのウェブアプリケーション開発者と共通しているようだと考えました。

このエントリの結論は下記のとおりです。
  • ウェブアプリケーションの分野で、日本語の「ホワイトリスト」と英語の"White List"では、用法に違いが見られる
  • ホワイトリスト=アプリケーション仕様という意味だと、どんな場合にでも使えるが、必ずしも安全ではない
  • ホワイトリスト=許可リストという意味だと、使える局面は限定されるか、安全なものとして扱える
  • 両者の意味を混同することは危険である
  • 「ホワイトリスト」という用語が出てきたら、警戒心をもって、どのような意味かを深読みしよう

参考: 僕が「ホワイトリスト」を採用しなかった訳


【HASHコンサルティング広告】
HASHコンサルティング株式会社は、ウェブアプリケーションのセキュリティに関心のあるセキュリティエンジニアを募集しています。
興味のある方は、twitterfacebookのメッセージ、あるいは問い合わせページからお問い合わせください。

2016年3月14日月曜日

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

先日以下の記事が公開されました。決済代行会社を使っていたのにカード情報が漏洩したというものです。
同社は、薬局への医薬品の卸売りのほか、運営するショッピングサイト「eキレイネット」でコラーゲンやヒアルロン酸などの美容関連製品を販売している。流出した疑いがあるのは、平成26年10月8日~27年11月5日、サイトでカードを使って商品を購入した顧客の氏名や住所、クレジットカードなどの情報だった。この間、1955人が利用していた。
 名の売れた大企業ではない。従業員わずか10人の小さな会社がサイバー攻撃の標的になったのだ。
 問題が発覚したのは昨年11月。決済代行会社からカード情報が流出した疑いがあると指摘があった。
従業員10人なのに「標的」に サイバー攻撃、中小企業が狙われる理由より引用
これに対して、以下のブックマークコメントがつきました。
そもそも、決済代行会社を使っているのになぜカード情報が漏れる?普通は自分たちの側には一切カード情報を残さないものだが。getで送るとうっかりアクセスログに残る、とかの例はあるけど。
はてなブックマークより引用
実は2013年以降、決済代行会社を使っているのにカード情報が漏洩する事件が続いています。その場合の漏洩経路には以下の二種類が知られています。
  • 決済代行を使っているのに、カード情報を自社サイトでも保存していた
  • カード情報の入力フォームを改ざんされ、別サイトにカード情報を転送された
前者の例としては、以下の記事で紹介しました。
しかし、実際の事件を調べてみると、多いのは後者の経路です。このパターンとして最初の、そしてもっとも有名な事例は、JINSオンラインショップからのカード情報漏洩でしょう。
ジェイアイエヌでは、クレジット情報漏えいの専門調査機関であるPayment Card Forensics株式会社(PCN)に調査を依頼。4月8日に受領した報告書によれば、3月6日にサーバーにバックドアプログラムが設置され、第三者のデータベースにカード情報が転送されるようにアプリケーションのプログラムが改ざんされていたことが判明した。
JINS、不正アクセスによるカード情報流出は最大2059人、当初発表下回るより引用
セキュリティコードをはじめクレジットカード情報は、弊社では保管しておりません。
保管していない情報が流出した理由につきましては、オンラインショップの支払方法入力画面に改ざんが加えられ、入力したクレジットカード情報が不正に外部のサーバに送信されるよう改ざんされたためであります。
よくあるご質問 | JINS - 眼鏡(メガネ・めがね)より引用
入力フォームが改ざんされて、入力内容が外部に送信されるような仕掛けが組み込まれたということですね。恐らく、JavaScriptが追加されて、フォームの入力内容を外部に送信するように、Webビーコンのように動的にIMGタグを生成するとか、XMLHttpRequestオブジェクトによりフォームの内容を送信する仕組みが組み込まれたのでしょうね。

では、JINSオンラインショップの事件以降、同種の事件がどの程度発生しているかを調べてみたところ、以下の様に継続的に、フォーム改ざんが攻撃経路と思われるカード情報漏洩事件が発生しています。

サイト名漏洩期間漏洩件数セキュリティコード決済代行
JINS オンラインショップ2013/3/6~2013/3/142,059漏洩使用
光文社が運営する3サイト2013/12/29~2014/1/211,160漏洩
ホビーショップタム・タム2014/5/3~2014/6/20923漏洩使用
eキレイネット2014/10/8~2015/11/5不明漏洩使用
ONYONEベースボールギア2014/11/1~2015/2/2772漏洩使用
中村屋2014/11/8~2014/12/241,422漏洩
エアコンの森Plus2015/1/1~2015/7/1712 使用
プリマージュオンラインショップ2015/4/1~2015/7/22563漏洩使用
DiXiM Store2015/8/1~2015/9/11480漏洩使用
ブルーラグオンラインストア2015/8/1~2015/8/1645 使用
シネマイクスピアリ2015/10/17~2015/10/301,414漏洩使用
日本オッターボックス2015/5/19~2016/3/2397漏洩使用
THE KISS ONLINE SHOP2016/1/16~2016/3/2537漏洩使用
「カミチャニスタ」ウェブサイト2016/1/25~2016/3/2744漏洩使用
vivid golf2016/1/25から2016/3/11616漏洩使用
NETSEA(*1)2016/1/1から2016/4/157,386漏洩使用
軒先パーキング2015/5/8~2016/7/2738,201漏洩使用

*1 NETSEAの情報漏えいは、改ざんによるものではなくHeartBleed脆弱性によるもの(報道
※ 2016年4月12日:2件(THE KISS ONLINE SHOP、カミチャニスタ)追記
※ 2016年4月27日:1件(vivid golf)追記
※ 2016年4月28日:2件(日本オッターボックス、NETSEA)追記
※ 2016年8月27日:1件(軒先パーキング)追記

特徴としては、下記の三点です。
  • 漏洩の対象が特定期間に取引した利用者に限られ漏洩件数は比較的少ない
  • セキュリティコードが漏洩するケースが多い
  • 決済代行を利用していても漏洩に至っている(決済代行業者に落ち度はない)
ということで、決済代行を使っているからカード情報は漏れないと油断していると危険です。
対策としては、JINSオンラインショップの下記が参考になります。
画面遷移型のクレジットカード情報非保持サービスの採用
このサービスを採用することにより、オンラインショップの購入画面で決済方法としてクレジットカード決済を選択した場合に、決済代行会社が管理するウェブサイトへ画面が遷移することで、購入者のクレジットカード情報が当社サーバ等のシステムを一切通過しないこととなり、当社システムからの情報漏えいの可能性が排除されることとなります。
なお、上記再発防止策に対しては、本調査委員会より、非常に高い情報セキュリティレベルが実現可能であり、セキュアなクレジット決済を行うことが可能になる施策であると評価されております。
不正アクセス(JINSオンラインショップ)に関する調査結果(最終報告)より引用
ちょっと上記だけだとわかりにくいですが、決済代行サービスには大別して、データ伝送(API)型と画面遷移型があり、JINSオンラインショップは元々データ伝送型を採用していたが、画面遷移型に切り替えるということですね。これですと、クレジットカード情報の入力フォームは決済代行業者が提供するものになり、ECサイト事業者は関与を切り離すことができます。リスクの移転というやつですね。

また、一般的な脆弱性管理やWAFの導入などに加えて、改ざん検知システムの導入も有効です。Webページの改ざんは、中々人手では検知が難しく、上記に紹介したサイトの事例でも、大半がカード事業者か決済代行業者からの連絡で事件が発覚しています。改ざん検知システムによりページ改ざんをすばやく検知できれば、被害を小さくできることが期待されます。


【HASHコンサルティング広告】
HASHコンサルティング株式会社は、ウェブサイトの保護に関心のあるセキュリティエンジニアを募集しています。
興味のある方は、twitterfacebookのメッセージ、あるいは問い合わせページからお問い合わせください。

フォロワー