2019年12月9日月曜日

SSRF対策としてAmazonから発表されたIMDSv2の効果と限界

サマリ

Capital OneからのSSRF攻撃による大規模な情報漏えい等をうけて、Amazonはインスタンスメタデータに対する保護策としてInstance Metadata Service (IMDSv2) を発表した。本稿では、IMDSv2が生まれた背景、使い方、効果、限界を説明した上で、SSRF対策におけるIMDSv2の位置づけについて説明する。

SSRFとは

SSRFは、下図のように「外部から直接アクセスできないエンドポイント」に対して、公開サーバーなどを踏み台としてアクセスする攻撃方法です。SSRF(Server Side Request Forgery)の詳細については過去記事「SSRF(Server Side Request Forgery)徹底入門」を参照ください。
最終的な攻撃目標は多様ですが、近年問題になっているのが、クラウドサービスのインスタンス・メタデータを取得するAPIのエンドポイントです。有名なものがAmazon EC2の169.254.169.254(IMDS)ですが、類似の機能をクラウドサービス各社が提供しています。
先の記事でも紹介したSSRF脆弱なサンプルを以下に示します。これは、はてなブックマークのようなソーシャルブックマークアプリの「プレビュー機能」を想定しています。
<?php
  require_once('./htmlpurifier/library/HTMLPurifier.includes.php');
  $purifier = new HTMLPurifier();

  $ch = curl_init();
  $url = $_GET['url'];
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  $html = curl_exec($ch);
  echo $purifier->purify($html);
このスクリプトをAmazon EC2上においてSSRF攻撃すると、下図のようにIAMのクレデンシャルが表示されます。


 SSRF攻撃が一般の方にも話題になったのはCapital Oneからの1億人超の個人情報流出事件で、詳しくは以下の記事にまとめられています。
 また、SSRF攻撃の標準的な対策は、ネットワーク的な対策で、EC2の場合は以下のようなiptablesによる防御が従来から推奨されていました。
sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP

Amazon ECS コンテナインスタンスの IAM ロール - Amazon Elastic Container Service より引用
ここまでが長い前置き(前提知識の確認)です。

EC2インスタンスメタデータサービスv2(IMDSv2)とは

この状況に対して、Amazonが批判されたり、Amazonの責任ではないという反論があったりしていましたが、Amazonは今年の11月20日オフィシャルブログにて、Instance Metadata Service v2(IMDSv2) を発表しました。以下は、クラスメソッドの臼田さんのブログ記事から要点の引用です。
  • v2へのアクセスには事前に取得したTokenを必須とする
    • TokenはPUTで取得する必要がある
    • Tokenリクエスト時に有効期限(秒)を設定できる
    • Tokenはヘッダに入れてリクエストする必要がある
  • v1を無効化できる(デフォルトでは併用可能)
  • メタデータサービス自体を無効化できる
IMDSv2によるメタデータ取得は以下のようになります。まずは、トークンの取得
$ curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60"
AQAAAKunSEcqfWQgz1E-ryJ3fdWDoOkbn8Nn4h2C6qN6nP56npog8Q==$
赤字のBASE64っぽいものがトークンです。TTLを60秒としているので、この値は既に無効です。PUTメソッドとX-aws-ec2-metadata-token-ttl-secondsヘッダを要求することで、攻撃難易度を上げています。
続いて、トークンを利用したメタデータの取得です。X-aws-ec2-metadata-tokenヘッダにより、先程取得したトークンを指定します。
$ curl -H "X-aws-ec2-metadata-token: AQAAAKunSEcqfWQgz1E-ryJ3fdWDoOkbn8Nn4h2C6qN6nP56npog8Q==" http://169.254.169.254/latest/meta-data/iam/info/
{
  "Code" : "Success",
  "LastUpdated" : "2019-12-08T02:32:19Z",
  "InstanceProfileArn" : "arn:aws:iam::999999999999:instance-profile/test-role",
  "InstanceProfileId" : "ZZZZZZZZZZZZZZZZZZZZZ"
}$
これだけだと、IMDSv1が有効になっているのでSSRF攻撃は緩和されません。IMDSv1を無効化するには、AWSCLIから以下のように --http-tokens を required に設定します。
$ aws ec2 modify-instance-metadata-options --instance-id i-FFFFFFFFFFFFFFF --http-tokens required --http-endpoint enabled
{
    "InstanceId": "i-FFFFFFFFFFFFFFFFF",
    "InstanceMetadataOptions": {
        "State": "pending",
        "HttpTokens": "required",
        "HttpPutResponseHopLimit": 1,
        "HttpEndpoint": "enabled"
    }
}
$
この状態で先の攻撃をすると、以下のように攻撃は防御されます。


SSRF攻撃の文脈でPUTメソッドやカスタムHTTPリクエストヘッダを指定することは難しそうなので、「これだけでSSRF対策は十分ではないか」と思う人もいそうですが、実は攻撃は可能です。

Gopherプロトコルとは

以前からSSRF界隈ではGopherプロトコルの活用が話題となっていて、はせがわようすけさんが分かりやすいスライドで紹介されています。
このスライドの12ページからがGopherを用いた攻撃手法についての説明です…が、このスライドは今年の9月18日の講演のものですので、当時存在しなかったIMDSv2についての言及はありません。このため、はせがわさんのスライドを引き継ぐ形で、GopherによるIMDSv2への攻撃を紹介します。

まず、Gopher自体の紹介はこちらの記事などを参照していただくとして、ここではcurlとnetcatによりgopherプロトコルを簡単に試してみます。

まずはcurlコマンドにより以下のURLをアクセスしてみます。
$ curl gopher://localhost:8888/_Hello%0d%0aHiroshi%20Tokumaru%0d%0a
curlコマンド実行前にnetcatで8888ポートを待ち受けていると、以下のような表示になります。
$ nc -l 8888
Hello
Hiroshi Tokumaru

Response           ← この行はResponse 改行 Ctrl-d を手入力したもの
$
この際の呼び出し側は下記となります。
$ curl gopher://localhost:8888/_Hello%0d%0aHiroshi%20Tokumaru%0d%0a
Response
$
このように、Gopherプロトコルを使うと、任意リクエストをURLで指定でき、そのレスポンスを受け取れることから、HTTPやSMTPその他のプロトコルをエミュレートできることになります。

Gopherプロトコルを用いたIMDSv2に対する攻撃

先程のサンプルプログラムをEC2のIMDSv1を無効化した環境に設置した状態で、Gopherプロトコルを用いて攻撃してみましょう。まずはPUTメソッドによるトークン取り出しです。表示が見やすいようにHTMLソースの形で表示しています。アドレスバーには gopher://169.254.169.254:80/_PUT というURLがちらっと見えていますね。


このトークン(有効時間は60秒…もっと長くすることも可能)を用いて、メタデータを表示させた結果が下記です。

このように、IMDSv1を無効化してIMDSv2のみ有効としても、Gopherプロトコルを用いてSSRF攻撃ができました。

リダイレクトを許可している場合

今までの「脆弱なスクリプト」は、与えられたURLに対してスキーム(プロトコル)もホストのIPアドレスもチェックしていなかったので、これらのチェックを追加してみましょう。これだけだと防御できて当然なので、cURLのオプションとしてCURLOPT_FOLLOWLOCATIONをtrueにします。これは、リダイレクトをcURL内部で自動的に追跡するという意味です。
<?php
  require_once('./htmlpurifier/library/HTMLPurifier.includes.php');
  $purifier = new HTMLPurifier();

  $ch = curl_init();
  $url = $_GET['url'];
  $urlinfo = parse_url($url);  // URLのパース
  $scheme = $urlinfo['scheme'];
  $host = $urlinfo['host'];
  $ip = gethostbyname($host);
  if ($ip === "169.254.169.254") {  // IPアドレスのチェック
    die("Invalid host");
  } elseif ($scheme !== 'http' && $scheme !== 'https') { // スキームのチェック
    die("Invalid scheme");
  }
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);  // リダイレクトを自動追跡
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

  $html = curl_exec($ch);
  echo $purifier->purify($html);
このスクリプトに対して、リダイレクトを用いた攻撃をします。具体的には下記のスクリプト(リダイレクタ)のURLをサンプルスクリプトに指定します。
<?php
  header('Location: gopher://169.254.169.254:80/_PUT%20/latest/api/token...以下悪用防止のため略
実行結果は以下となり、トークンを取得できていることがわかります。


同様に、このトークンを使用してEC2インスタンスのメタデータを取得することができます。

※ はせがわさんのスライドでは、この攻撃にはスクリプト側で明示的に任意プロトコルへのリダイレクトが許可されている必要があるように読めます(P23)が、私が実験により確認した範囲では、任意プロトコルの明示的な許可は必要ないようです。

対策

上記攻撃には以下の対策候補が考えられます。
  • curlで扱うプロトコルをHTTPおよびHTTPSに限定(常に指定を推奨)
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
  • リダイレクトの追跡を禁止する(curlのデフォルトに戻す、あるいは以下を設定)
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);  // リダイレクト追跡しない
  • CURLINFO_PRIMARY_IPにより「実際にアクセスしたIPアドレス」を求め、169.254.169.254(等ブラックリストのIPアドレス)であれば表示をやめる
$primary_ip = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
  • URLからホスト名に対応するIPアドレスとスキームを確認する(先のスクリプトでは実施済み)

結局どうすればよいか

IMDSv2はそもそもAmazonからもインスタンスメタデータに対する defense in depth (多層防御)と紹介されており、根本的な解決策ではありません。なので、他の根本的な解決策を実施した上で、予防的な対策(緩和策)として用いるべきです。
では、根本的な解決策はなにかというと、インスタンスメタデータの保護という点では、先に紹介したiptables等を用いたネットワーク的な対策が確実です。あるいは、以下により、HTTPによるインスタンスメタデータ参照そのものを禁止することも有効です。
$aws ec2 modify-instance-metadata-options --instance-id i-FFFFFFFFFFFFFFFF --http-endpoint disabled
{
    "InstanceId": "i-FFFFFFFFFFFFFFFFF",
    "InstanceMetadataOptions": {
        "State": "pending",
        "HttpTokens": "required",
        "HttpPutResponseHopLimit": 1,
        "HttpEndpoint": "disabled"
    }
}
ただし、上記はインスタンスメタデータに対する保護であり、SSRF攻撃全般を防御できるわけではないため、他に守るべきエンドポイントがある場合には他の対策を併用する必要があります。

まとめ

Instance Metadata Service v2 (IMDSv2) について紹介しました。IMDSv2を用いることにより、SSRF攻撃をかなり緩和されることが期待できるものの、根本的な解決策ではなく緩和策の一つとして用いるべきと考えます。これは、Amazon自体がIMDSv2をdefense in depth(多層防御)とうたっていることからも伺えます。
また、SSRF攻撃の対策は難易度が高いため、可能であればSSRF攻撃の影響を受けない仕様(例えば外部から受け取ったURLにアクセスしない)の検討を推奨します。

2019年12月5日木曜日

シェルを経由しないOSコマンド呼び出しがPHP7.4で実装された

この記事はPHP Advent Calendar 2019の5日目の記事です。

はじめに

私は6年前に、PHP Advent Calendar 2013として「PHPだってシェル経由でないコマンド呼び出し機能が欲しい」という記事を書きました。その中で、OSコマンドインジェクション対策の根本的かつ安全な対策は「シェルを経由しないコマンド呼び出し」であることを指摘した上で、末尾に以下のように書きました。
PHPコミッタのみなさま、PHP5.6の新機能として、シェルを経由しないコマンド呼び出しの機能を追加できませんか?
現実には当時からPCNTL関数にてシェルを経由しないコマンド呼び出しはできたのですが、当関数の使用が難しいことと、CLI版あるいはCGI版(FastCGIは可)のPHPでないとサポートされていないなどの制限があり、popenやproc_openなど使いやすいコマンド呼び出し関数において、シェル呼び出しのないコマンド実行機能が欲しいところでした。

この「私の願い」はPHP 5.6では実現しませんでしたが、PHP 7.4においてproc_open関数の拡張として実現しました。実に6年越しの実現ということになります。

proc_openの従来の問題点

proc_openに限りませんが、PHPの従来のコマンド実行機能(PCNTL関数は例外)の問題として、「常にシェル経由でコマンドを呼び出す」ことがあります。これを確認するための簡単なサンプルを示します。以下は、ps -fコマンドをproc_open関数で呼び出しています。
<?php
$cmd = "ps -f";
$process = proc_open($cmd, [], $pipes);
if (is_resource($process)) {
    $return_value = proc_close($process);
    echo "command returned $return_value\n";
}
呼び出し例は下記となります。赤字で示しているように、シェル(/bin/sh)経由でpsコマンドが実行されています。
UID        PID  PPID  C STIME TTY          TIME CMD
ockeghem 16921 16920  0 16:31 pts/0    00:00:00 -bash
ockeghem 18858 16921  0 17:18 pts/0    00:00:00 php-7.4.0 proc_open4.php
ockeghem 18859 18858  0 17:18 pts/0    00:00:00 sh -c ps -f
ockeghem 18860 18859  0 17:18 pts/0    00:00:00 ps -f
command returned 0
このため、コマンドラインにセミコロンなどにより追加のコマンドを実行できる可能性があり、OSコマンドインジェクション脆弱性の原因になっていました。ここで、psのオプションとして、-fの代わりに、「-f; echo Hello」を指定してみましょう。
<?php
$cmd = "ps -f; echo Hello";
$process = proc_open($cmd, [], $pipes);
// 以下省略
呼び出し例は下記となります。sh -c のパラメータとして; echo Helloが追加されていることと、echoコマンドの実行結果としてHelloが表示されていることがわかります。これがOSコマンドインジェクションの原理です。
UID        PID  PPID  C STIME TTY          TIME CMD
ockeghem 16921 16920  0 16:31 pts/0    00:00:00 -bash
ockeghem 18932 16921  0 17:30 pts/0    00:00:00 php-7.4.0 proc_open4.php
ockeghem 18933 18932  0 17:30 pts/0    00:00:00 sh -c ps -f; echo Hello
ockeghem 18934 18933  0 17:30 pts/0    00:00:00 ps -f
Hello
command returned 0
この対策として、コマンドラインのパラメータをエスケープ処理する方法もありますが、エスケープ処理自体が複雑になる可能性があり、実際にPHPのescapeshellcmd関数には脆弱性(こちらを参照)があるため使用を避けるべき状態でした。

proc_openのPHP 7.4での新しい呼び出し方

この状況に対して、PHP 7.4では、proc_openの第1引数を配列として指定することにより、コマンドとパラメータを明確に分離するとともに、シェルを経由しないコマンド実行ができるようになりました(パチパチパチ)。
先のスクリプトをこの形式で書き換えてみましょう。
<?php
$cmd = ["ps", "-f"];
$process = proc_open($cmd, [], $pipes);
if (is_resource($process)) {
    $return_value = proc_close($process);
    echo "command returned $return_value\n";
}
実行例は以下となります。シェルを経由せずに直接コマンドが実行されていることがわかります。
UID        PID  PPID  C STIME TTY          TIME CMD
ockeghem 16921 16920  0 16:31 pts/0    00:00:00 -bash
ockeghem 18895 16921  0 17:27 pts/0    00:00:00 php-7.4.0 proc_open4.php
ockeghem 18896 18895  0 17:27 pts/0    00:00:00 ps -f
command returned 0
続いて、先程同様に、-f オプションの代わりに -f; echo Hello を指定してみましょう。
<?php
$cmd = ["ps", "-f; echo Hello"];
$process = proc_open($cmd, [], $pipes);
// 以下略
実行結果は以下のとおりです。psコマンドにオプションとして「-f; echo Hello」が渡されたため、「unsupported SysV option」というエラーになっていますが、OSコマンドインジェクションにはならないことがわかります。
error: unsupported SysV option

Usage:
 ps [options]

 Try 'ps --help <simple all="" list="" misc="" output="" threads="">'
  or 'ps --help <s a="" l="" m="" o="" t="">'
 for additional help text.

For more details see ps(1).
command returned 1
この呼出方法(proc_openの第一引数を配列で指定)の場合、シェルを経由しないでコマンドを呼び出すことから、原理的にOSコマンドインジェクション脆弱性を避けることができます。今後PHP 7.4以降にて外部コマンドを呼び出す場合は常に、proc_open関数にて第1引数を配列で指定し、かつ配列の先頭要素(コマンド名)は固定とすることで、OSコマンドインジェクションを避けつつ簡便かつ安全な実装が可能になります。

まとめ

PHP 7.4にて新たに追加されたproc_openの新しい呼び出し方を紹介しました。私個人としても、6年越しの要望がかなえられた結果となり、よいクリスマスを迎えられそうです。

2019年7月29日月曜日

PHPサーバーサイドプログラミングパーフェクトマスターのCSRF対策に脆弱性

サマリ

PHPサーバーサイドプログラミングパーフェクトマスターには、PHP入門書としては珍しくクロスサイト・リクエストフォージェリ(CSRF)対策についての説明があるが、その方法には問題がある。アルゴリズムとして問題があることに加えて、実装上の問題があり、そのままコピペして用いると脆弱性となる。

はじめに

古庄親方の以下のツイートを見て驚きました。

CSRFトークンの生成に、password_hash関数を使うですと?
親方に書籍名を教えていただき、購入したのが、この記事で紹介する「PHPサーバーサイドプログラミングパーフェクトマスター」です。同書では、CSRF対策にpassword_hashを2種類の方法で使っています(!)が、本稿では、セクション10.3 (P726~) にて説明されている方法を取り上げます。

当該コード

当該コードを示します。同書では、認証・入力フォーム・登録の3機能が1つのPHPファイルにまとめられていますが、そこから抜き出す形で、以下は入力フォームです。
<?php
function getToken($rand='') {
    $_SESSION['rand'] = $rand;
    $token = password_hash($rand, PASSWORD_DEFAULT);
    return $token;
}
// セッションスタート
session_start();
$rand = mt_rand();
$token = getToken($rand);
print <<<EOD
<form action="" method="post">
<input type="hidden" name="request" value="reg" />
<input type="hidden" name="token" value="$token" />
<input type="submit" value="登録">
</form>
EOD;
トークンの「素(もと)」としてmt_rand()関数が呼ばれ、その値をセッション変数$_SESSION['rand']に保存しています。その乱数値をpassword_hash関数で処理することで、トークンを生成しています。入力フォームの生成例を以下に示します。
<form action="" method="post">
<input type="hidden" name="request" value="reg" />
<input type="hidden" name="token"
  value="$2y$10$alqDLwZSizuBYpwmBR6pj.WzxI7UeW9YutuxHjb.r2qw9QQOBqbw6" />
<input type="submit" value="登録">
</form>
赤字に示した部分($2y$...)がトークンですが、これを見ると、「なんでパスワードのハッシュ値をCSRFトークンにしているわけ?」と勘違いする人が続出しそうですね。
そして、このトークンを受け取り、処理するプログラムが下記の部分です。
<?php
function check ($token = '') {
    return (password_verify($_SESSION['rand'], $token));
}
session_start();
if (isset($_POST['request']) === true && $_POST['request'] === 'reg' && isUser() == true) {
    if(isset($_POST['token']) === false || check($_POST['token']) === false) {
        print '不正なアクセスが行われました。';
    } else {
        print '処理が完了しました!';
        unset($_SESSION['rand']);
        $_SESSION = array();
    }
}
$_POST['token']がNULLでないことを確認した上で、check関数で、セッション変数$_SESSION['rand']をパスワードに見立て、パスワードのハッシュ値に相当する$_POST['token']にてpassword_verify関数で照合する形で、トークンを検証しています。

何が問題か

このプログラムには以下の問題があります。
  1. 設計上の問題
  2. 実装上の問題
以下、順に説明します。

設計上の問題

そもそもpassword_hash関数は、パスワードを安全に保存するための関数なので、CSRFトークンの処理のことはまったく考慮されておらず、一言で言えば「使う場所を間違えている」ことになります。通常トークンの生成には、暗号論的に安全な疑似乱数生成器(CSPRNG)を用います。PHP 7以降ではrandom_bytes関数があるので(*1)、それを素直に使うだけです。さらにハッシュ関数を通すようなことは必要ありません。余計なことをやればやるほど、バグの原因になり、ひいては脆弱性の原因になります。

(追記 2019/7/29 20:36)
*1 当書籍はXAMPP上のPHP5.5を前提としているので、random_bytes関数(PHP 7.0以降)は使えませんが、代わりにopenssl_random_pseudo_bytes関数(PHP 5.3以降)が使えます。当関数はopensslの導入が前提ですが、XAMPPはデフォルトでopenssl関数が使用できます。
(追記終わり)

加えて、mt_rand()の使用も問題です。この関数は、マニュアル(下記引用)にある通り、セキュリティ目的で使うことは適切でありません。
警告 この関数が生成する値は、暗号学的に安全ではありません。そのため、これを暗号として使ってはいけません。暗号学的に安全な値が必要な場合は、random_int() か random_bytes() あるいは openssl_random_pseudo_bytes() を使いましょう。

https://php.net/manual/ja/function.mt-rand.php より引用
また、mt_rand関数を引きなしで呼び出すと、0からmt_getrandmax()の戻り値までの値を返しますが、mt_getrandmax()は64ビット環境でも2147483647なので、31ビットの範囲になります。これはトークンに用いる乱数のエントロピーとしては不足しています。
さらに、password_hash関数はパスワードの保護を強化するためにストレッチング(ハッシュ計算を繰り返すこと)を施していますが、そのために、password_hashおよびpassword_verify関数は非常に低速です。CSRF対策のために、わざわざ処理を遅くする必要はありません。

実装上の問題

紹介したプログラムには実装上の問題もあります。使われているトークンはワンタイムのものであり、使用済みになるとunsetされます。すなわち、トークンがNULLとなる時期が存在します。そのタイミングを狙った攻撃ができるのです。
すなわち、以下の関数呼び出しがtrueになるような$tokenを渡せばよいことになりますが…
password_verify(NULL, $token)
実は、password_verifyの第一引数にNULLを渡すと、空文字列を渡したのと同じ結果になります。なので、空文字列に対するpassword_hash関数の結果を使って、攻撃が可能です。空文字列に対するハッシュ値はソルトにより無数に存在しますが、たとえば以下で攻撃ができます。
$2y$10$V3V9iX2iR6ZqaNuFAyUlPeiIvmQrGKDbrJQWdkXWVECGUodZON0Iu
検証例を示します。
<?php
var_dump(password_verify(null, '$2y$10$V3V9iX2iR6ZqaNuFAyUlPeiIvmQrGKDbrJQWdkXWVECGUodZON0Iu'));

// bool(true) が表示される
これは、前述した「余計なことをやればやるほど、バグの原因になり、ひいては脆弱性の原因にな」った例といえます。

まとめ

PHPサーバーサイドプログラミングパーフェクトマスターにおけるCSRF対策の問題を報告しました。実務上でCSRF対策をする場合は、アプリケーションフレームワークの機能を使うか、よく検証されたライブラリを使うべきですが、特別な事情があって独自実装する場合には、定石的な手法を用い、かつできるだけ簡素な、検証のしやすい実装にすべきと考えます。
また、password_hash関数をトークン生成に用いる例が、同書に限らず散見されますが、これは百害あって一利なしですので、トークン生成には単にCSPRNGを使うと覚えておきましょう。



2019年7月1日月曜日

PHPカンファレンス福岡2019のSST社ブースにてPHPクイズ出題を担当しました

PHPカンファレンス福岡2019のセキュアスカイテクノロジー(SST)社ブースにて、PHPクイズの出題を担当しました。以下は、そのパネルのイメージです。SST社は弊社EGセキュアソリューションズ株式会社のパートナー企業で、私はSST社のEラーラニングコンテンツ(PHP編)の監修を担当しています。


問題はすべて2択で、回答は下の写真のように、赤または青のシールをボードに貼り付ける形になっています。回答がわりとバラけていて、出題者的にはいい感じですね。


以下、出題と解答、解説を記載します。

問題1

以下のPHPスクリプトで、クッキーPHPSESSIDの削除として機能するのはどちら?
PHPのバージョンは7.3.6とする。

A) (正解)31人が選択=正答率 83.8%
<?php
setcookie('PHPSESSID');

B) 6人が選択
<?php
header('Set-Cookie: PHPSESSID=');

解説

setcookie関数の第2引数の省略時の値は '' (空文字列)ですので、A)は setcookie('PHPSESSID', ''); としたのと同じはずです。そして、第2引数が空文字列の場合は、実際にはクッキーの値としてdeletedが指定され、expires属性が過去日時(PHP-7.3.6の場合は、Thu, 01-Jan-1970 00:00:01 GMT)になります。すなわち、setcookie関数の第2引数を省略すると、クッキーの削除として機能します。
B) header関数の方は、クッキーの値が空文字列になりますが、クッキーそのものは削除されません。

参考記事: PHPのsetcookie関数で空文字列を設定しようとするとクッキーが削除される

ところが、出題の確認時にmodphpallで確認したところ、古いPHPでは上記の挙動にはならず、以下のレスポンスヘッダが送信されることがわかりました。この動作は、setcookie関数の第2引数を省略した場合のみで、空文字列またはnullを指定した場合はマニュアルどおりの動作となります。
Set-Cookie: PHPSESSID=
正確に言えば、PHP 4.1.0~PHP 5.6.13までが上記挙動になります。すべてのPHP 4.0と PHP 5.6.14以降、PHP 7以降では、マニュアルどおりの動作となります。この変更は、恐らく以下のバグレポートに対応したものと思われます。

Bug #67131 setcookie() conditional for empty values not met

プログラミング言語のエッジケースの出題をする場合は特に、実環境での動作確認と、処理系の想定バージョンの明記が重要だなとあらためて感じました。

問題2

以下のPHPスクリプトはどちらが表示される? PHPのバージョンは7.3.6とする。
<?php
   $mail = "a@b@example.jp";
   var_dump(filter_var($mail, FILTER_SANITIZE_EMAIL));

A) 31人が選択
bool(false)

B) (正解)6人が選択=正答率 16.2%
string(14) "a@b@example.jp"

解説

この問題は「ひっかけ」でして、実際に多くの方がひっかかりました。
マニュアルにあるように、filter_var関数に FILTER_SANITIZE_EMAIL を指定した場合は、英字、数字および !#$%&'*+-=?^_`{|}~@.[] 以外のすべての文字を取り除きます。出題の場合は、除去対象の文字がないので、入力値がそのまま出力されます。
メールアドレスの形式を検証するためには、FILTER_SANITIZE_EMAIL ではなく、FILTER_VALIDATE_EMAIL を使用します。

率直に言って、FILTER_SANITIZE_EMAILのユースケースは思いつきません。要らんでしょ、こんなもの。

問題3(PHP考古学)

PHP-5.2.17にてregister_globals=onの環境で、あらかじめ以下のようにセッション変数が設定されている。
$_SESSION['user'] = 'yamada';
クエリ文字列 user=tanaka
を指定して以下のスクリプトを実行した場合の表示はどちら?
<?php
   session_start();
   echo $user;
A)yamada  (正解) 23人が選択=正答率 62.2%
B)tanaka 14人が選択

解説

register_globals=on の環境では、session_start()を実行時にセッション変数の値がグローバル変数として初期設定されます。したがって、$_SESSION['user'] = 'yamada'; が設定されている場合、$user の初期値は 'yamada' になります。
仮に、このセッション変数がセットされていない場合、クエリ文字列 user=tanaka の方が使われ、$user の初期値は 'tanaka' になります。セッション変数とクエリ文字列(やPOST変数など)の両方に同じパラメータ名がある場合、セッション変数の方が優先されます。その理由は、セッション変数のグローバル変数へのセットは、起動時ではなく、session_start()の実行時に行われるからです。すなわち、同名のパラメータがあった場合は、セッション変数が上書きします。
したがって、register_globals=on の使用は脆弱性の原因になりやすいことはよく知られていますが、セッション変数をグローバル変数の初期値として用いると、特に脆弱性が混入しやすくなります。

例えば、以下のログインチェックのプログラムについて
session_start();
if (! isset($_SESSION['user']) {
  die('ログインしてください');
}
$user = $_SESSION['user'];

これを register_globals=on を想定して「直訳」すると以下のようになります。
session_start();
if (! isset($user)) {
   die('ログインしてください');
}
// $user = $_SESSION['user'];  // これは不要となる
register_globals=on の場合、session_start()を実行した時点で、セッション変数 $_SESSION['user'] の値はグローバル変数 $user にセットされるため、「これは不要となる」とコメントした行は不要となります。便利ですね!
しかし、$_SESSION['user'] がない場合、$user=nullが実行されるわけではないので、クエリ文字列等にuser=tanakaがあった場合、$userには 'tanaka' がセットされます。すなわち、パスワードを知らなくても誰にでもなりすましできます。これは重大な問題ですね。
register_globals=on を用いて、上記の脆弱性を修正するには以下のように書くべきですが…
$user = null;  // register_globals=onによる初期設定を無効化する
session_start();
// $_SESSION['user'] がある場合、$userにその値がセットされる
if (! isset($user)) {
  die('ログインしてください');
}
上記は著しく直感に反するプログラムですし、それゆえに初期化漏れが盛大に発生しそうです。なので、register_globalsがPHP 5.4で削除されたのは、必然のことと言えましょう。


[PR]
徳丸が代表を務めるEGセキュアソリューションズ株式会社では、ウェブサイトを堅牢にするための各種セキュリティサービスを提供しています。

2019年5月30日木曜日

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

株式会社ヤマダ電機の運営するECサイトから、最大37,832件のクレジットカード情報が漏洩したと昨日発表されました。ヤマダ電機のように日本を代表する家電量販店のサイトからクレジットカード情報が漏洩したことに私自身驚きました。

弊社が運営する「ヤマダウエブコム・ヤマダモール」への不正アクセスによる個人情報流出に関するお詫びとお知らせ

漏洩した情報は以下のように発表されています。
  • クレジットカード番号
  • 有効期限
  • セキュリティコード
はてなブックマークやtwitterのコメントを見ていると、「セキュリティコードを保存していたのか」という意見が見えますが、おそらくセキュリティコードは保存されていなかったと推測します。
本稿では、この件を含め、本年の現時点までのウェブサイトからのクレジットカード情報漏えい事件についてまとめました。

事件の一覧

下表に本年(2019年)の現時点までに公表されたウェブサイトからのクレジットカード情報漏洩事件をまとめました。サイト名、漏洩期間、漏洩件数(最大)、セキュリティコードの漏洩有無、漏洩の手口(後述)を記載しています。

サイト名漏洩期間漏洩件数セキュリティコード漏洩手口
バニーファミリー横浜ネットショップ2018年6月28日~同年10月25日241件漏洩Type5
オンライン通販サイト(ハセ・プロ)2018年10月1日~2019年1月24日1,311件漏洩Type5
歯学書ドットコム2012年11月11日~2018年12月28日5,689件漏洩不明
本味主義2017年5月22日~2018年10月14日2,926件漏洩Type4?
子供服サーカス2018年10月1日~2019年1月18日2,200件漏洩Type4
エコレオンラインショップ2018年5月16日~2018年12月11日247件漏洩Type4
ななつ星 Gallery2013年10月5日(サイト開設日)~
2019年3月11日(サイト閉鎖日)
3,086件漏洩不明
「ショコラ ベルアメール」オンラインショップ2018年8月6日~2019年1月21日1,045件漏洩Type5
エーデルワイン オンラインショップ2015年7月8日~2018年8月5日1,140件漏洩不明
小田垣商店オンラインショップ2018年4月3日~2018年5月16日及び
2018年9月3日~2019年2月28日
2,415件漏洩不明
藤い屋オンラインショップ2018年10月15日~2019年1月28日477件Type5
エンターテインメントホビーショップ ジャングル2017年5月2日~2018年11月6日2,507件漏洩Type2
ヤマダウエブコム・ヤマダモール2019年3月18日~2019年4月26日37,832件漏洩Type4

大半の事件でセキュリティコードが漏洩している

上表からわかるように、藤い屋オンラインショップを除いたすべての事件でセキュリティコードが漏洩しています。昨年のまとめも見ていただくとわかりますが、最近のウェブサイトからのクレジットカード情報漏洩では、セキュリティコードが漏洩する方がむしろ普通です。
昨年10月のブログ記事「クレジットカード情報盗み出しの手口をまとめた」では、クレジットカード情報を窃取する手口をType1~Type5にまとめましたが、この中で、Type4とType5はセキュリティコードを容易に盗むことができます。

Type4の手口とは

ここでType4の手口について説明します。下図のように、カード情報入力画面に細工を施すことがType4の特徴です。
下図はサイト利用者がクレジットカード情報を入力している様子です。入力フォームに仕掛けられたJavaScriptにより、カード情報は攻撃者の管理するサーバーに送信されています。
ヤマダ電機のサイトからの情報漏えいがこのパターンと推測する理由は、リリースの以下の記述からです。
(1)原因
第三者によって「ヤマダウエブコム・ヤマダモール」に不正アクセスされ、ペイメントアプリケーションの改ざんが行われたため
弊社が運営する「ヤマダウエブコム・ヤマダモール」への不正アクセスによる個人情報流出に関するお詫びとお知らせより引用
そして、同じくType4と推測されるサイトとしては下記があります。
Q.子供服サーカス、および子供服ミリバールからクレジットカード情報が流出したのですか?
弊社ではカード情報を保有しておりません。今回は、注文情報入力画面 が 不正アクセスにより 改竄され、カード入力画面で入力されたお客様のカード情報が、流出したと思われます。
Q&A】クレジットカード情報流出に関するご質問と回答(株式会社サーカス)より引用
2018 年5 月16 日に攻撃者が、データベースへ不正な仕掛けをページ内に埋め込んだとみられます。
その仕掛けは、ページ内の入力フォームに入力されたカード会員情報を正規の処理とは別に外部サイトへ転送する機能を持っていた為、2018年5月16日以降当該サイトのカード利用者のカード会員情報が搾取されていたと考えられる、との事でした。
エコレ通販サイトにおける不正アクセスによるお客さま情報の流出懸念に関するお知らせ より引用

Type5の手口も依然活発

一方、昨年から使われた始めたType5も依然として活発です。下図は、バニーファミリー横浜のFAQからの引用です。


【重要】カード情報流出についての、ご質問とご回答  バニーファミリー横浜 公式オンラインショップ より引用

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


【重要】カード情報流出についての、ご質問とご回答  バニーファミリー横浜 公式オンラインショップ より引用

この遷移は、「2018年に公表されたウェブサイトからのクレジットカード情報漏えい事件まとめ」で紹介した伊織の事例と酷似しており、偽画面のドメイン名まで同じです。同一犯人、あるいは同一犯行グループではないでしょうか。

また、以下のように、ハセ・プロ、ショコラ ベルアメール、藤い屋についてもこの手口であることがリリースからわかります。

フィッシングサイトによるクレジットカード情報不正取得についてのお詫びと注意喚起のお知らせ (株式会社ハセ・プロ)より引用

「ショコラ ベルアメール」のオンラインショップへの不正アクセスに関するご質問と回答 より引用
2018年10月15日から2019年1月28日までの期間においてシステムが改ざんされた痕跡があり、 クレジットカード決済を選択されたお客様が偽の決済画面へ誘導され、そこで入力されたクレジットカード情報が流出し、 一部のお客様のクレジットカード情報が不正利用された可能性があることを確認いたしました。
弊社が運営する「藤い屋オンラインショップ」への不正アクセスによる個人情報流出に関するお詫びとお知らせより引用

なぜ入力フォームからクレジットカード情報を盗むのか

ブログ記事「ECサイトからクレジットカード情報を盗み出す新たな手口」にて紹介したように、昨年の6月1日に改正割賦販売法が施行され、クレジットカード情報を扱うECサイト事業者にもカード情報保護が求められるようになりました。そして、この保護についてのガイドラインになるのが、『クレジット取引セキュリティ協議会の「実行計画」』であり、中身はクレジットカード情報番号の非保持化が柱になっています。
具体的には、以下に示す「JavaScript(トークン)型」と「リダイレクト(リンク)型」による決済システムが推進されています。
以下は、JavaScript型の決済の様子です。入力フォームは加盟店(ECサイト)側にありますが、カード情報はECサイトのサーバーを通過せず、JavaScriptにより直接決済代行事業者にカード情報を送信する方式です。

実行計画2019 より引用

JavaScript型の場合、先に説明したType4およびType5でカード情報を盗むことができます。
一方下図はリダイレクト型決済の概念図です。カード情報を入力する際には、決済代行事業者の画面に遷移するため、ECサイト側では一切生のカード情報を扱わず、安全性が高い方法と考えられていました。

実行計画2019 より引用

しかし、前記Type5の攻撃ではリダイレクト型決済でもカード情報を盗むことができます。今年のカード情報窃取事件ではType4とType5が使い分けられていますが、これは決済方式に合わせて適した方法が採用されたものと考えられます。

カード情報非保持化で満足せず基本的な対策を

以上で説明してきたように、経産省およびクレジット取引セキュリティ協議会の進める「カード情報非保持化」では十分にカード情報を保護できる状況ではありません。基本に立ち返って、以下の対策を推奨します(2018年末の記事の再掲)。
  • ウェブアプリケーションやソフトウェアライブラリ、プラットフォームの脆弱性対策(パッチ適用など)
  • 管理者等のパスワードを強固にする(可能ならばインターネットからは管理者ログインできないよう設定する)
加えて、以下の対策を推奨します。
  • Web Application Firewall(WAF)の導入
  • ファイルパーミッションとファイルオーナーの適切な設定
  • 改ざん検知システムの導入

[PR]
徳丸が代表を務めるEGセキュアソリューションズ株式会社では、ECサイトを堅牢にするための各種セキュリティサービスを提供しています。


2019年5月7日火曜日

[書評]噂の学園一美少女な先輩がモブの俺に惚れてるって、これなんのバグですか?

瓜生聖(うりゅうせい)の近著「噂の学園一美少女な先輩がモブの俺に惚れてるって、これなんのバグですか? 」を読んだので紹介したい。本書は、JNSA(特定非営利活動法人 日本ネットワークセキュリティ協会)が主催したサイバーセキュリティ小説コンテストにて大賞を受賞した作品「目つきの悪い女が眼鏡をかけたら美少女だった件」を大幅に加筆したのち、角川スニーカー文庫から出版された。

重要事項説明

  • 著者と評者は知人関係にあり公私共に交流がある
  • 評者が読んだ書籍はご恵贈いただいたものである
  • この記事のリンクにはアフィリエイトが含まれる

はじめに

著者の瓜生聖は、twitterのプロフィールには「ITmediaで記事を書いてる兼業ライター」とあるが、本業はITエンジニアである。つまり、現役のITエンジニア兼テクニカルライターである人物が、サイバーセキュリティ小説を書いたのが本書ということになる。瓜生聖はライターとしても一流なので技術面と文章力は申し分ないはずだが、小説となるとどうだろうか。評者の興味は、まずそこにあった。

あらすじ

本書は以下の三部構成となっている。

第一話 試験問題漏洩事件

本書の準ヒロイン格で、主人公鷹野祐(たかのたすく)の幼なじみの西村理乃(にしむらりの)が、数学の試験問題漏洩事件の犯人だという噂が流れ、事件が提示される。祐は、美少女衣川マト(ころもがわまと)の力を借りて事件の真相を解明し、理乃を救う。祐は小学生時代に探偵を志していたが、とある事件がトラウマになり、探偵の夢をあきらめていたが、この事件をきっかけに、自分が進むべき道がおぼろげながら見えてくる。

第二話 夏の嵐

祐は区民プールで子供たちと遊ぶマトを見かけ、そこからの流れでマトが幼少期を過ごした施設を訪問、マトの過去を知ることになる。その後マトの自宅を訪問しドキドキの一時を過ごす。
台風の日、買い物を言いつけられた祐は自宅に帰ることができなくなり、愛帝学園を訪れる。そこで第二の事件を解決する。過程で、サイバーセキュリティに詳しい青年鴻上(こうがみ)が登場する。

第三話 最強の武器

マトが高校に来なくなってしまった。鴻上がマトをスカウトして米国に呼んだらしい。マトが遠くに行ってしまう。しかも、米国でのマトの仕事は…
マトを救うべく、祐は第三の事件を三人の少女の力を借りて解決する。そして、はたして祐とマトは結ばれるのか。

重層的な構造

本書はラブコメラノベという分類になっているが、その実複雑な構成になっている。評者の見るところ、以下の要素を併せ持っている。
  • ラブコメ
  • サイバーセキュリティ小説
  • 成長譚(さまざまな事件を少女たちの力を借りて解決する過程で主人公は成長する)
  • 自分探し(無目的に生きてきた主人公が自分のやりたいことを見出す)
  • 探偵小説(第一話は探偵小説でもある)
  • 家庭内暴力や児童ポルノ問題
まだ他にもあるかもしれない。
これだけの要素を文庫本300ページに突っ込むとごちゃごちゃしてしまいそうだが、どうか。率直なところ、児童ポルノ問題を扱い探偵小説仕立てにもなっている第一話は、話が少し重くなり、また、筋書きを追うのに苦労する箇所がある。著者の初めての小説ということで張り切ってネタをぶち込みすぎたのかもしれない。その反省からか、第二話と第三話では、材料を少し減らして、著者はすっきりとまとめてみせる。全体としては、重層的な構造にも関わらずラノベらしくすらすら楽しく読むことができる。

エピソード紹介

次に、本書の冒頭からいくつかのエピソードを引用で紹介しよう。

物語は、本書の主人公である愛帝学園の1年生の鷹野祐(たかのたすく)が、ヒロイン衣川マト(ころまがわまと)と邂逅するところから始まる。
 教室に入ろうとした瞬間、突然引き戸が勢いよく開いた。
 突然のことに足が絡まり、思わず尻餅をつく。
「いてて……す、すいません」
 見上げた先にいたのは妖精だつた。
 陽の光をまとい、細く艷やかな銀髪がふわりと舞う。シルバーアッシュの柳眉の下はぱっちりとした大きな翠眼(すいがん)で、真っ白な肌は白磁のようにつるつるだ。
 学年の違う俺でも知っている銀髪の美少女ーー衣川先輩だった。
 遠くから見るのとは全然違う美の暴力をまともに受け、俺はただただ、呆然と見つめるだけだった。目を離すこともできない。もしかしたら呼吸も忘れていたかもしれない。
この後、いったんは「氷のように冷たい目で俺を見下ろしていた」マトは、あるきっかけの後主人公を抱きしめるに至る。その後マトは顔を赤らめて走り去るが、主人公は小さなUSBメモリが落ちていることに気づく。

その後、準ヒロイン格でアイドルを目指している西村理乃(にしむらりの)や、主人公が入り浸っているコンピュータ部(主人公は部員ではない)の部員である嵯峨野亜弥(さがのあや)が次々に登場し、わずか10ページほどの間で主要人物が出揃うことになる。スピーディな展開で手際が良い。

主人公は亜弥にUSBメモリの調査を依頼する。亜弥はアーミーナイフを取り出し、慣れた手つきでガワを開くと、USBメモリのチップがPhison2307であり、ファームウェア改造ツールPsychsonにより改造が可能であると指摘する。
「ちょ、ちょっと待ってくれ。つまりその、ファームウェア? ってのを書き換えるとどうなるんだ?」
「デバイスクラスを偽装できるー」
「俺にもわかるように言ってくれ」
「USBデバイスの挙動をエミュレートできるー」
「俺にもわかるように言ってくれ」
 亜弥は話し足りなそうにしながらも、言葉を選ぶように言った。
「PCを乗っ取れるー」
「まじか」
BadUSBが早くも登場である。このやり取りでもわかるように、主人公はコンピュータには詳しくない。本書は主人公の一人称視点で書かれているので、難しい用語が交わる技術的な説明について、主人公に「わからない、もっと易しく説明してくれ」と言わせることによって、わかりやすく解説したり、詳細を省略して要点のみを説明できることになる。これは、著者の工夫だろう。

この後、なぜか主人公と連れ歩いているマトと、アイドルを目指す理乃が、主人公を巡って張り合う場面がある。そう、なんせラブコメなので主人公はモテモテなのだ。理乃は言う。
「好みは人それぞれだと思いますけどね、衣川先輩。幼なじみでずっと昔から知っていて、お互いに祐、理乃と呼び合う関係のあたしから言わせてもらえれば、今の祐はつまんないやつですよ。普通の人に分かる良さなんて、皆無ですからね」
幼なじみの理乃は、祐は昔と違ってつまらないやつになってしまったと言う。祐は、小学生の頃まではシャーロック・ホームズにあこがれ、将来を探偵になることを目指していたが、ある事件に関わったことがきっかけとなり、探偵を志すことをやめ、できるだけ目立たないように過ごしている。

あらすじで紹介したように、この物語は、愛帝学園に起こるさまざまな事件を主人公が周囲の力を解決することにより成長する、いわばロールプレイングゲーム(成長譚)の形態をとっている。主人公は、過去のトラウマから探偵になる夢を放棄してしまったが、友人のトラブルを解決すべく再び探偵として行動する。その過程で、マトからOSINT(オシント)という方法論を教えてもらい、これが自分の進むべき道だと気づく。

問題を抱えているのは主人公だけではない。マトと理乃は、家族に関するトラブルを抱えていたが、それぞれの方法で過去のトラブルを乗り越えようとしている。それが物語に厚みを加えている。

次に、本書の二大要素であるサイバーセキュリティ小説として、およびラブコメラノベとしての側面を紹介する。

サイバーセキュリティ小説として

本書の「事件」に登場するセキリティ要素を紹介しよう。

第一話: BadUSB、MaaS(Malware-as-a-Service)
第二話: ウェブサイト侵入(クレジットカード情報非保持サイト?)
第三話: 暗号通貨

上記のように、イマドキのセキュリティ要素がふんだんに盛り込まれている。しかも、現役のITエンジニアが書いただけあって、セキュリティ部分のクォリティは非常に高い。先に引用したBadUSBもそうだが、ここでは、評者が思わずニヤリとした箇所を引用しよう。以下は、第二話中に出てくる、侵入されたウェブサイトを巡る会話である。
「なるほど…じゃあコレはニセモノってことなんですか?」
 僕は入力途中になっていた通販サイトのページを示す。アドレスバーには緑色で企業名まで表示されている。これがニセモノならどうやって見分ければいいんだろうか。
「ああ、フィッシングという言い方は正確じゃないか。改ざん、と言った方が正しいのかな。通常だと通信が保護されていなかったり、URLが微妙に違ったりすることが多いけど、これは本物のサーバーに何かあったときの予備サーバだから、それ自体は本物よ。ただ、ファイルが書き換えられているだけ」
これはクレジットカード情報窃取の最新の手口である。評者が以前ブログ記事「クレジットカード情報盗み出しの手口をまとめた」に書いたタイプ4かタイプ5に相当する。おそらく「フィッシング」という言葉があることから、タイプ5、すなわち最新の手口であろう。
このように、著者は細かいクラッキングの手口にまで目配りして、最新の手法が紹介されている。まるで、評者がふだん「これをフィッシングと言うな」と言っているのを意識しているかのような会話だw
細かいようだが、セキュリティはディテールが重要であるし、あまりに荒唐無稽な手口だと(荒唐無稽さを突っ込んで楽しむなら別だが)読者は白けてしまう。その点、本書は、セキュリティに精通した読者でも、存分に楽しむことができる。
また、本書のヒロイン衣川マトは、実在の高名なバグハンターがモデルになっている。評者は予めそれを知っていたが、それを知らない読者でも、途中で実在のモデルを暗示する重要なエピソードが出てくるので、セキュリティに関心のある読者なら「ははーん」と分かるだろう。ここは引用したいところではあるが、ネタバレになるので控えておく。

ラブコメとして

ラブコメラノベなのだから、主人公が「冴えないやつ」であるにも関わらずがモテモテなのは、いくら非現実的でも大目に見てあげるのが大人のたしなみというものだ…そんなふうに考えていた時期が私にもありました。
実際冒頭のくだりでは、「おいおい、それはないだろう」と思いながら読んでいたのであるが、読み進めていくうちに、主人公のモテモテぶりが不自然ではなくなってくる。当初は、「主人公がモテモテなのは在原業平や光源氏以来の我が国の伝統だからな」と思っていたが、そうではなかった。鷹野祐は冴えないように見えて実は魅力的な少年なのだ。そのことを、周囲も、当人も、そして読者も気づいていないが、三人の少女は気づいていた。それは、先に引用した「普通の人に分かる良さなんて、皆無ですからね」という理乃の言葉からも伺える。読者は、物語の進展とともに主人公の魅力がわかってくるわけで、それで「モテモテぶりが自然に思えてくる」のだろう。
評者は、主人公の艶福ぶりをうらやんだり、時には「おいおい、昔から『据え膳食わぬは男の恥』と言ってな…なぜ据え膳を食わぬ」と主人公をけしかたり、さらには、「うーん、この調子でいくと祐はマトとくっつくのではなく、理乃とくっついてしまう、なんてどんでん返しもなくないか?」などと要らぬ心配をしたりした。つまり、評者はラブコメとしての本書も存分に堪能した。

おわりに

瓜生聖のサイバーセキュリティ小説「噂の学園一美少女な先輩がモブの俺に惚れてるって、これなんのバグですか? 」を紹介した。本書は、ラブコメ仕立てのサイバーセキュリティ小説であるのみならず、重層的な構造を持つ意欲的な内容であり、それでいて、ラノベとしてすらすらと読める一流のエンタテインメントである。
著者は「あとがき」の末尾で「本書を読んだラノベ好きの方が『サイバーセキュリティって面白そう』と思っていだたければ、サイバーセキュリティ関係者が『ラノベもなかなか面白いな』と思っていただければ著者冥利につきます」と記している。本書は評者が初めて読んだラノベだが、本当に面白かった。可能ならば、本書の続編で、祐とマトが恋人らしくいちゃついているシーンも読んでみたい。


2019年4月29日月曜日

鈴木常彦先生の「共用レンタルサーバにおけるメールの窃盗」の話を聴講した

4月23日(火)に開催された 「#ssmjp 2019/04 ~DNSの話を聞く会~」に「Outputなら任せてください枠」で参加しましたので、講演内容からとくにやばい(?)内容と思われる@tss_ontap(鈴木常彦=浸透言うな先生)の「黒塗りの DNS (萎縮編)」から、「共用レンタルサーバにおけるメールの窃盗」について紹介します。スライドは公開されています

サマリ

レンタルサーバーからメールを送信する場合、悪意の第三者に、特定のドメインに対するメールを横取りされるリスクがある

攻撃手法


  • 攻撃者は、レンタルサーバーを契約(お試しなどでも可能)して、攻撃対象のドメイン名(ここではchukyo-u.ac.jp…中京大学のドメイン名を用いる)を登録する
  • その際に、当該ドメイン名の権利を有している必要はない(権利があれば正当にメールを受信できるので攻撃の必要がない)
  • これだけ

なぜメールが横取りされるか?

一般にメール送信する場合、サーバー内に送信先ドメインがある場合、そちらを優先して配送する。レンタルサーバー内に攻撃対象のドメイン名(ここではchukyo-u.ac.jp)がメール設定されていると、メール配送の際にDNSを参照することなく、無条件に(ローカル内で)当該のメールボックスにメールを配送してしまう。このchukyo-u.ac.jpのメールボックスは、実際には正規のドメイン名登録者ではなく、悪意の第三者が登録したものなので、メールは悪意の第三者が横取りできることになる。

FAQ

Q1: レンタルサーバー契約時にドメイン名の権利は確認されないのか?
A1: 多くの場合権利確認はないそうですし、まぁ難しそうですね

Q2: レンサバ側でDNS権威サーバーやwhoisとか参照しないのか?
A2: 前述の通り、ローカル配送の際にはDNSやwhoisは参照しません

Q3: ドメイン名の権利者ができる対策はあるか?
A3: すべてのレンタルサーバーに契約すれば防げますが現実的ではありせん

Q4: メール送信者(レンタルサーバー利用者)ができる対策はあるか?
A4: メール以外の方法で「メール送ったよ、届いていないときは連絡して!」とメッセージするとか…というのは冗談で、メール送信者側にとれる対策はなさそうです

Q5: DNS関係ないじゃん
A5: はい。DNSとは無関係の問題ですが、近隣のテーマだとは思います

2019年4月17日水曜日

WordPressのプラグインVisual CSS Style Editorに権限昇格の脆弱性

最近WordPressのプラグインのアップデート状況を監視しているのですが、Visual CSS Style Editor(別名Yellow pencil visual theme customizer)が公開停止になり、しばらくたって公開が再開されていました。これは、掲題のように権限昇格の脆弱性があったので公開停止になり、修正版が出たことにより公開再開したものです。WordPressのプラグインではよく見る光景です。

注: 公開停止になったまま再開しないプラグインも珍しくありません。最近では、typesgoogle-maps-buildersimple-share-buttons-adderyuzo-related-post等の人気プラグインが公開停止になりましたが、本稿執筆時点で再開していません。

脆弱性の概要

脆弱性のあるバージョン: 7.1.9以前
脆弱性の種類: 権限昇格
脆弱性による直接の影響: 外部からWordPressの設定をログインなしに変更できる
典型的な攻撃手法: 外部から勝手に管理者ユーザーを登録することで管理者になれる
対策: 最新版(本稿執筆時点では7.2.1)にアップデートする

上記は、以前紹介したWP GDPR Complianceの脆弱性CVE-2018-19207と攻撃手法や影響は似ています。PoC(概念実証コード)は公開されていますが、影響が大きいので紹介はしません。私が逮捕されても困りますしね…

原因

複数の問題が組み合わさって攻撃が可能になっていますが、とくに、yp_remote_get_first()関数内で、特定のGETパラメータを指定すると一時的に管理者になる(厳密にはid=1のユーザになる)機能が実装されていることが原因のようです。この機能の意図はよくわかりませんが、表面上は開発者が仕込んだバックドアのように見えます。以下は7.1.9から7.2.0への差分です。当該箇所は、修正版では単に削除されています。



対策

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

宣伝

EGセキュアソリューションズ株式会社では、WordPress利用サイトのセキュリティ強化施策を支援しています。サービス案内はこちら。詳細はお問い合わせください

2019年3月4日月曜日

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

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

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

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

DNS Rebinding

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


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

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

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

試してみる

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

対策

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

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

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

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

補足

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

まとめ

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

2019年2月25日月曜日

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

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

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

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


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

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

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

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

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

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

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

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

ではどうすればよいか

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

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

まとめ

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

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

2019年1月15日火曜日

CWE-20入門

サマリ

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

CWEとは

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


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

CWE-20とは

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

CWE-20の現実の使われ方

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

まとめ

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

フォロワー

ブログ アーカイブ