8K Views
October 08, 17
スライド概要
PHPカンファレンス2017における徳丸浩の講演「著名PHPアプリの脆弱性に学ぶセキュアコーディングの原則」です
著名PHPアプリの脆弱性に学ぶ セキュアコーディングの原則 EG セキュアソリューションズ株式会社 徳丸 浩
徳丸浩の自己紹介 • 経歴 – 1985年 京セラ株式会社入社 – 1995年 京セラコミュニケーションシステム株式会社(KCCS)に出向・転籍 – 2008年 KCCS退職、HASHコンサルティング株式会社(現社名:EGセキュアソリューショ ンズ株式会社)設立 • 経験したこと – 京セラ入社当時はCAD、計算幾何学、数値シミュレーションなどを担当 – その後、企業向けパッケージソフトの企画・開発・事業化を担当 – 1999年から、携帯電話向けインフラ、プラットフォームの企画・開発を担当 Webアプリケーションのセキュリティ問題に直面、研究、社内展開、寄稿などを開始 – 2004年にKCCS社内ベンチャーとしてWebアプリケーションセキュリティ事業を立上げ • 現在 – EGセキュアソリューションズ株式会社 代表 https://www.eg-secure.co.jp/ – 独立行政法人情報処理推進機構 非常勤研究員 https://www.ipa.go.jp/security/ – 著書「体系的に学ぶ 安全なWebアプリケーションの作り方」(2011年3月) 「徳丸浩のWebセキュリティ教室 」(2015年10月) – 技術士(情報工学部門) 2
脆弱性の分類と"入力値"の関係 3
【参考】CVEとCWE • 脆弱性の国際的な分類として CVE と CWE がある • CVE(Common Vulnerabilities and Exposures) – 個別ソフトウェアの具体的な脆弱性を識別する番号 – 米国政府の支援を受けた非営利団体のMITRE社他のCNAが採番 – CVEの例 • CVE-2017-1001000 WordPress Rest APIの脆弱性 • CVE-2017-5638 Apache Struts2の脆弱性S2-045 • CWE(Common Weakness Enumeration) – 脆弱性の種類を識別する番号 – MITRE社が中心となって策定 – CWEの例 • CWE-89 SQLインジェクション • CWE-22 ディレクトリトラバーサル ※ CAN: CVE Numbering Authority, CVE 採番機関 参考: https://www.ipa.go.jp/security/vuln/CVE.html https://www.ipa.go.jp/security/vuln/CWE.html 4
CWEの脆弱性タイプの階層構造図 https://www.ipa.go.jp/security/vuln/CWE.html より引用 5
CWE-20 不適切な入力確認 6
Java セキュアコーディングスタンダード CERT/Oracle 版 • はじめに • 00. 入力値検査とデータの 無害化 (IDS) • 01. 宣言と初期化 (DCL) • 02. 式 (EXP) • 03. 数値型とその操作 (NUM) • 04. オブジェクト指向 (OBJ) • 05. メソッド (MET) • 06. 例外時の動作 (ERR) • 07. 可視性とアトミック性 (VNA) • 08. ロック (LCK) • 09. スレッド API (THI) • 10. スレッドプール (TPS) • 11. スレッドの安全性に関 する雑則 (TSM) • 12. 入出力 (FIO) • 13. シリアライズ (SER) • 14. プラットフォームのセ キュリティ (SEC) • 15. 実行環境 (ENV) • 49. 雑則 (MSC) • AA. 参考情報 • BB. Glossary https://www.jpcert.or.jp/java-rules/ 7
IDS00-J. 信頼境界を越えて渡される信頼できないデータは無害化する 多くのプログラムは、認証済みでないユーザやネットワーク接続等、信頼 できない情報源からデータを受け取り、それを(改変したり、あるいはそ のまま)信頼境界(trust boundary)を越えて、信頼される側に渡す。多く の場合、データは、一定のシンタックスを持つ文字列であり、プログラム 内部のサブシステムによって解析される。不正な形式の入力データには対 応できないかもしれないし、インジェクション攻撃が含まれているかもし れないため、そのような入力データは無害化(sanitize)しなくてはならな い。 特にコマンドインタプリタやパーサに渡される文字列データはすべて、解 析される文脈で無害な状態(innocuous)にしなければならない。 コマンドインタプリタやパーサの多くは、独自の無害化メカニズムや検 査機構を備えている。可能であれば、それらの無害化メカニズムを使用す るほうが、独自に無害化メカニズムを実装するよりも好ましい。独自に実 装した無害化メカニズムでは、特殊なケースやパーサの複雑な内部構造に 配慮しない実装を行ってしまう可能性がある。それだけでなく、コマンド インタプリタやパーサに新しい機能が追加されたとき、無害化メカニズム が適切にメンテナンスされない恐れもある。 旧版です アーカイブ http://web.archive.org/web/20150515043831/ https://www.jpcert.or.jp/java-rules/ids00-j.html 8
信頼境界の中で閉じた処理の例(固定のSQL文実行) 信頼境界(Trust Boundary) SQL実行 SQL文 SQLクエリ SELECT * FROM employee プログラムソース SQL文 SQL DB 潜在的な脆弱性があっても、 外部から攻撃されることはない 9
信頼できない値によりSQLインジェクションになる例 信頼境界(Trust Boundary) SQLインジェクション ブラウザ 従業員コード SQL実行 SELECT * FROM employee WHERE id='$id' SQL文 プログラムソース SQL文 SQLクエリ SQL DB 10
ディレクトリトラバーサルの典型例 信頼境界(Trust Boundary) 画面1 hiddenパラメータ 設定ファイル file= template.html ディレクトリトラバーサル 値の改 変 fopen ファイルシステム fopen('../../../etc/passwd', … 11
認可制御不備 信頼境界(Trust Boundary) takagiでログイン Hidden パラメータ メニュー画面1 セッション変数 id=takagi 値の改変 satoの個人情報 個人情報表示 ユーザーDB ブラウザ 認可制御不備 12
セカンドオーダーSQLインジェクション 信頼境界(Trust Boundary) ブラウザ 従業員コード 氏名 ';DELETE FROM employee -- SQL実行1 (INSERT) 安全なSQLクエリ INSERT INTO employee VALUES(:id, :name, …) SQL DB SQL実行2 (SELECT) 氏名 SQL実行1 (UPDATE) 氏名 ';DELETE FROM employee -- 脆弱なSQLクエリ UPDATE employee SET name='$name' … 氏名 SQL DB ';DELETE FROM employee -SQLインジェクション 13
では、SQLインジェクション対策、 どうすればいいか? 14
こうですか、わかりません (>_<)/ 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 信頼できないデータ 無害化 フィルタ 無害なデータ SQL 呼び出し 15
こうですか、わかりません (>_<)/ 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 信頼できないデータ 無害化 フィルタ 無害なデータ SQL 呼び出し 16
IDS00-J. 信頼境界を越えて渡される信頼できないデータは無害化する 多くのプログラムは、認証済みでないユーザやネットワーク接続等、信頼 できない情報源からデータを受け取り、それを(改変したり、あるいはそ のまま)信頼境界(trust boundary)を越えて、信頼される側に渡す。多く の場合、データは、一定のシンタックスを持つ文字列であり、プログラム 内部のサブシステムによって解析される。不正な形式の入力データには対 応できないかもしれないし、インジェクション攻撃が含まれているかもし れないため、そのような入力データは無害化(sanitize)しなくてはならな い。 特にコマンドインタプリタやパーサに渡される文字列データはすべて、解 析される文脈で無害な状態(innocuous)にしなければならない。 コマンドインタプリタやパーサの多くは、独自の無害化メカニズムや検 査機構を備えている。可能であれば、それらの無害化メカニズムを使用す るほうが、独自に無害化メカニズムを実装するよりも好ましい。独自に実 装した無害化メカニズムでは、特殊なケースやパーサの複雑な内部構造に 配慮しない実装を行ってしまう可能性がある。それだけでなく、コマンド インタプリタやパーサに新しい機能が追加されたとき、無害化メカニズム が適切にメンテナンスされない恐れもある。 アーカイブ http://web.archive.org/web/20150515043831/ https://www.jpcert.or.jp/java-rules/ids00-j.html 17
IDS00-J. 信頼境界を越えて渡される信頼できないデータは無害化する 違反コード 以下の違反コード例は、ユーザ認証を行うJDBCのコードを示している。パ スワードはchar型配列として渡され、データベースへの接続が作成され、 パスワードがハッシュ化されている。 残念ながらこのコードはSQLインジェクション攻撃を許してしまう。SQL文 sqlString は無害化されていない入力値を受け付けており、前述の攻撃シナ リオが成立してしまうだろう。 適合コード (PreparedStatement) 幸いJDBCライブラリはSQLコマンドを組み立てるAPIを提供しており、信頼 できないデータを無害化してくれる。java.sql.PreparedStatementクラスは 入力文字列を適切にエスケープ処理するため、適切に利用すればSQLイン ジェクション攻撃を防ぐことができる。これはコンポーネントベースで行 う無害化の一例である。 この適合コードでは java.sql.Statement の代わりに PreparedStatementを使 用するように doPrivilegedAction() メソッドを変更している。また、引数 username の長さを検証しており、攻撃者が任意に長いユーザ名を送り込む ことを防止している。 18 カイブ http://web.archive.org/web/20150515043831/ https://www.jpcert.or.jp/java-rules/ids00-j.html
信頼できない値を安全に処理する例(プレースホルダ) 信頼境界(Trust Boundary) ブラウザ 従業員コード SQL実行 SELECT * FROM employee WHERE id=:id SQL文 プログラムソース SQL文 SQLクエリ SQL DB 19
簡単にすると、こうだった 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 プレースホルダ 信頼できないデータ 安全な形でSQL 呼び出し 20
そもそも信頼境界関係なくね? 21
そう、故に見出しが改定された 22
まさかの「SQLインジェクションを防ぐ」 信頼境界も、信頼できないも、無害化も タイトルから消えた https://www.jpcert.or.jp/java-rules/ids00-j.html 23
では、信頼境界は無意味? 24
そうでもない 25
こういうのはダメ hiddenパラメータでSQL文を渡している 信頼境界 安全に呼び出す方法がない SQL呼び出し hiddenパラメータで SQL文を渡している 26
phpMyAdminの場合 DB管理者 信頼境界 認証・認可 SQL呼び出し SQL文 信頼できる情報源 SQL文 SQL文 ソースコード セッション変数 データベース 設定ファイル … 27
ディレクトリトラバーサルの典型例 信頼境界(Trust Boundary) 画面1 設定ファイル file= template.html Hidden パラメータ fopen ファイルシステム fopen('../../../etc/passwd', … 28
ディレクトリトラバーサルの対策(1) 信頼境界(Trust Boundary) file=template.html 画面1 設定ファイル file= template.html セッション変数 file=template.html fopen ファイルシステム fopen('template.html', … 29
ディレクトリトラバーサルの対策(2) 信頼境界(Trust Boundary) 画面1 設定ファイル file= template.html Hidden パラメータ basename fopen ファイルシステム file=passwd fopen('passwd', … 30
認可制御不備 信頼境界(Trust Boundary) takagiでログイン Hidden パラメータ メニュー画面1 セッション変数 id=takagi satoの個人情報 個人情報表示 ユーザーDB ブラウザ 31
認可制御不備の対策(1) 信頼境界(Trust Boundary) takagiでログイン セッション変数 id=takagi ブラウザ 個人情報表 示 takagiの個人情報 ユーザーDB 32
認可制御不備の対策(2) 信頼境界(Trust Boundary) takagiでログイン Hidden パラメータ メニュー画 面1 認可制御 ブラウザ セッション変数 id=takagi 個人情報表 示 33
セカンドオーダーSQLインジェクション 信頼境界(Trust Boundary) ブラウザ 従業員コード 氏名 SQL実行1 (INSERT) 安全なSQLクエリ INSERT INTO employee VALUES(:id, :name, …) SQL DB '; DELETE FROM employee -- SQL実行2 (SELECT) 氏名 '; DELETE FROM employee -- 氏名 SQL実行1 (UPDATE) 脆弱なSQLクエリ UPDATE employee SET name=''; DELETE FROM employee --' … SQL DB 34
セカンドオーダーSQLインジェクションの対策 信頼境界(Trust Boundary) ブラウザ 従業員コード 氏名 SQL実行1 (INSERT) 安全なSQLクエリ INSERT INTO employee VALUES(:id, :name, …) SQL DB '; DELETE FROM employee -- SQL実行2 (SELECT) 氏名 '; DELETE FROM employee -- 氏名 SQL実行1 (UPDATE) 安全なSQLクエリ UPDATE employee SET name=:name … 氏名 SQL DB 35
なんか、ややこしいですね もっと簡単にできませんか? 36
やりましょう! 37
「値」には2種類ある • どんな値でも安全に使える方法があるもの – SQL中のリテラル(数値、文字列) プレースホルダ – HTMLの要素内容、属性値 エスケープ – … • 「値」を信頼するしかないもの – SQL文そのもの – プログラムコード – ログインユーザ名 – … 38
再掲: そもそも信頼境界関係なくね? 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 プレースホルダ 信頼できないデータ 安全な形でSQL 呼び出し 39
どんな値でも安全に使える方法がある場合は淡々をそれをやる • • • • 脆弱性対処が局所化できる場合はそれがベスト とりあえず、他に余計なことを考える必要はない 「脆弱性がないこと」がひと目で分かるのが理想 例 – SQLクエリに文字列連結を用いず、プレースホルダを用い て行う – HTML表示の際にもれなくエスケープ処理を行う – ヘッダインジェクションが発生しないライブラリを用い る – OSコマンド呼び出しにシェル実行を伴わないAPIを用いる ※ PHPでは難しい 40
「信頼できる値」が要求される場合は… • いったん信頼境界の外に出た値は信頼で きない • 信頼が必要な値は信頼境界から外に出さ ないで用いる 信頼できない値を いくらバリデーションしても、 信頼できる値にはならない 41
「信頼されたデータ」が要求される例 • 信頼境界の中のデータ – – – – – – プログラムコード SQL文 evalの入力 設定ファイル名に記載されたファイル名 正規表現 オブジェクト 42
認証・認可により信頼を与えることは可能 • 認証・認可により「信頼できることを確認」 – ログイン済みユーザ名(認証) – 管理者が入力するSQL文(認可)例: phpMyAdminで入力 するSQL文 – CMSに入力するHTML(認可) 例: WordPressで管理者 が入力するHTML • フィルタリングにより「真正でないかもしれないが 無害」にする例 – 制限されたHTML(フィルタリング) – 外部からのファイル名(basename) 43
再掲: ディレクトリトラバーサルの対策(1) 信頼境界(Trust Boundary) file=template.html セッション変数 file=template.html 画面1 設定ファイル file= template.html 値を信頼境界の外に 出さないことで対策 fopen ファイルシステム fopen('template.html', … 44
再掲: ディレクトリトラバーサルの対策(2) 信頼境界(Trust Boundary) 画面1 設定ファイル file= template.html Hidden パラメータ basename 値を無害な形に フィルタリング fopen ファイルシステム file=passwd fopen('passwd', … 45
いったんまとめ • 局所的に「絶対安全」な方法で実装すれば脆弱性は 混入しない • 信頼境界の中だけを通ってきた値を用いる際には、 局所的な脆弱性はあっても、攻撃には至らない…が 確認を間違う場合がある • 信頼できない値を安全に使う方法 – 認証・認可 – フィルタリング – …これらの実装にバグがあると脆弱性になる ※ 局所的な安全を積み重ねることが、脆弱性を作り 込まない早道 46
ケーススタディ 47
Welcart 1.9.3 のオブジェクトインジェク ション脆弱性 48
Welcart 1.9.4 をリリースしました【脆弱性の修正】 Welcart 1.9.4 をリリースしました。オブジェクトインジェクション 脆弱性の修正などを行いました。詳細は以下の通りです。 アップグレードを行う場合は、Welcartを停止してからアップグレー ドを行ってください。 【変更点】 • オブジェクトインジェクション脆弱性の修正 フロントにて、オブジェクトインジェクションと思われる脆弱性 が認められました。 過去のすべてのバージョンが対象となります。1.9.4にアップグ レードしてください。 放置しますと、サイトに任意のファイルの埋め込まれる可能性が あります。 脆弱性に関する修正の差分はこちら https://www.welcart.com/community/archives/83947より引用 49
Welcartフォーラムよりインシデント報告 https://www.welcart.com/community/forums/forum/バグ報告 より引用 50
Welcart 1.9.3 と 1.9.4の差分 典型的な オブジェクトインジェクション脆弱性 https://plugins.trac.wordpress.org/changeset?sfp_email=&sfph_mail=&reponame=&new=1728429 40usc-e-shop&old=1728428%40usc-e-shop&sfp_email=&sfph_mail= より引用 51
オブジェクトインジェクションとは • クッキー等からシリアライズデータを送り込み、任意のオブ ジェクトをメモリ内に生成 • オブジェクトが破棄されるタイミングでデストラクタが実行 される • オブジェクトを巧妙に組み合わせることにより、攻撃を実行 52
オブジェクトインジェクションに学ぶ… • unserialize関数に信頼できない値(信頼境界を超え て来た値)を渡してはいけない • やってはいけないことを知らずにやってしまったこ とが根本原因 • 以下のものも同様 – – – – eval system call_user_func call_user_func_array • 外部入力を処理する場合は、JSONを用いましょう 53
WordPress REST API のコンテンツインジェク ション脆弱性
WordPressの脆弱性突く攻撃が激増、6万以上のWebサイトで改ざん被害 脆弱性情報が公開されてから48時間足らずの間に悪用コードが投稿され、脆弱性のあるサイトを探して攻撃 を試す動きはインターネット全体に広がった。ハッキングされたWebサイトの数は6万6000以上にのぼり、 現在も増え続けている。 1月下旬のパッチで修正された、WordPressの深刻な脆弱性を突く攻撃が、わずか2週間足らずの間に激増 し、多数のWebサイトが改ざんなどの被害に遭っていることが分かった。この問題を発見したセキュリティ 企業のSucuriが2月6日のブログで伝えた。 WordPressは1月26日に公開した更新版の4.7.2で複数の脆弱性を修正した。このうち特に深刻なWordPress REST APIの脆弱性については、2月1日まで待ってから情報を公開していた。この問題を悪用された場合、認 証を受けないユーザーがWordPressサイトのコンテンツやページを改ざんできてしまう可能性が指摘されて いる。 脆弱性を悪用した攻撃のイメージ(出典:IPA) Sucuriでは、脆弱性情報が公開されてから48時間足らずの間に悪用コードがWeb上に掲載され、共有され ていることを確認した。その情報が簡単に入手できることから、脆弱性のあるサイトを探して攻撃を試す動 きはインターネット全体に広がったという。 http://www.itmedia.co.jp/enterprise/articles/1702/09/news064.html より引用 55
権限チェックのupdate_item_permissions_checkメソッド
497:
498:
499:
500:
501:
502:
503:
504:
505:
506:
507:
508:
509:
510:
511:
512:
513:
public function update_item_permissions_check( $request ) {
$post = get_post( $request['id'] );
$post_type = get_post_type_object( $this->post_type );
if ( $post && ! $this->check_update_permission( $post ) ) {
return new WP_Error( ‘rest_cannot_edit’, __(Sorry, you are not allowed ...
}
if ( ! empty( $request[‘author’] ) && get_current_user_id() !==
$request['author'] &&
! current_user_can( $post_type->cap->edit_others_posts ) ) {
return new WP_Error( 'rest_cannot_edit_others', __( 'Sorry, you are not ...
}
if ( ! empty( $request['sticky'] ) &&
! current_user_can( $post_type->cap->edit_others_posts ) ) {
return new WP_Error( 'rest_cannot_assign_sticky', __( 'Sorry, you are not ...
}
if ( ! $this->check_assign_terms_permission( $request ) ) {
return new WP_Error( 'rest_cannot_assign_term', __( 'Sorry, you are not ...
}
return true;
}
wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php (Ver 4.7.1)
56
update_item_permissions_check() の返り値 コンテンツの性質 存在しないコンテンツ 存在し権限のあるコンテンツ 存在し権限のないコンテンツ 返り値 true true false 57
update_item()メソッド
523:
524:
525:
526:
527:
528:
529:
530:
531:
532:
public function update_item( $request ) {
$id
= (int) $request['id'];
$post = get_post( $id );
if ( empty( $id ) || empty( $post->ID )
|| $this->post_type !== $post->post_type ) {
return new WP_Error( ‘rest_post_invalid_id’,
__( 'Invalid post ID.' ), array( 'status' => 404 ) );
}
$post = $this->prepare_item_for_database( $request );
if ( is_wp_error( $post ) ) {
return $post;
}
58
WordPress 4.7.1は何がいけなかったか? • 原因: – 権限チェックの際に、存在しない id に対して、権限あり を返していた – 権限チェックの際は id キャストなし、データ更新の際は id を整数にキャストしていた • 直接の対策 – 存在しない id に対しては「権限なし」を返す – 権限チェックと更新の際には同じ id を用いる • 原則論として – 正規化(この場合は整数へのキャスト)は早期に実施す る – バリデーションしていれば防げましたね 59
Joomla2.5.2の権限昇格脆弱性 60
Joomla2.5.2の権限昇格脆弱性 攻撃の流れ 1. 会員登録時にパスワードを不整合にしておく 2. ユーザ登録時に jforms[groups][]=7 をPOSTパラ メータに追加 3. バリデーションでエラー発生 4. 再入力に備えてリクエストのパラメータをすべて セッションに保存(コントローラ) 5. モデル側で、セッションの中味をすべて取り込み 6. 2.で追加したgroupsが取り込まれる 61
Joomla2.5.2の権限昇格脆弱性
components/com_users/controllers/registration.php register()関数
$data = $model->validate($form, $requestData);
// Check for validation errors.
バリデーションエラーの場合、リクエストデータを
まるごとセッション変数に放り込んでいる
if ($data === false) {
権限の情報も含まれている
// Save the data in the session.
$app->setUserState('com_users.registration.data', $requestData);
// Redirect back to the registration screen.
$this->setRedirect(JRoute::_('index.php?option=com_users&view=registrat
ion', false));
return false;
// 中略
// バリデーションが正常の場合
// Flush the data from the session.
$app->setUserState('com_users.registration.data', null);
62
components/com_users/models/registration.php getData() 関数内
$temp = (array)$app->getUserState('com_users.registration.data', array());
foreach ($temp as $k => $v) {
$this->data->$k = $v; // セッションのデータをモデルに放り込んでいる
}
【中略】
セッション汚染、Trust Boundary Violation
と呼ばれる問題
$this->data->groups = isset($this->data->groups) ? array_unique($this->da
ta->groups) : array();
// $this->data->groups = array(); 2.5.3でこのように修正
63
Joomla2.5.2は何がいけなかったか? • 原因: – 入力フォームの値を、セッション変数経由で、モデルの オブジェクトに丸ごと放り込んでいた – セッション変数やオブジェクトに放り込む際にプロパ ティの名前を確認していない(!) • 対策 – – – – まずは、放り込みをやめる せめてプロパティ名(キー名)を確認する セッション変数等の濫用をやめる 結果として、信頼できる値とそうでない値を混ぜない 64
phpMyAdminの正規表現イン ジェクション CVE-2013-3238 65
phpMyAdmin: CVE-2013-3238
case 'replace_prefix_tbl':
$current = $selected[$i];
$newtablename = preg_replace("/^" . $from_prefix . "/",
$to_prefix, $current);
$from_pref = "/e¥0“;
$to_prefix = "phpinfo();”;
preg_replace("/^/e¥0/", "phpinfo();", "test");
PHP5.4.3以前では、¥0以降は無視される
preg_replace("/^/e", "phpinfo();", "test");
66
脆弱性が混入した要因 • preg_replaceに渡す正規表現をエスケープしていな かった – 最低限、/ をエスケープする必要がある preg_replace(“/^” . $from_prefix . “/”, … ↓ reg_replace("/^" . preg_quote($from_prefix, '/') . "/", … • えーっと、preg_quoteってマルチバイト対応? – Shift_JIS以外では問題ない? • そもそも、正規表現を外部から(信頼境界を超え て)渡す実装は避けるべき(実際にもその方向で改 修された) 67
正規表現インジェクションの対策の考え方 • PHPには、正規表現をエスケープする関数 preg_quote が用意されているが… • そもそも、正規表現を外部から指定できる状況は、 極力避けるべき • 【原則】外部由来の値は、正規表現として利用しな い • phpMyAdminの正規表現インジェクション脆弱性も、 正規表現を避ける形で実装された 68
Drupageddon(CVE-2014-3704) 69
Drupalのログイン処理のSQL文を調べる 通常時の要求 name=admin&pass=xxxxxxxx&form_build_id=form-xQZ7X78LULvs6SyB9Mvuf bZh5KXjQYRHS05Jl2uD9Kc&form_id=user_login_block&op=Log+in 通常時のSQL文 SELECT * FROM users WHERE name = 'admin' AND status = 1 nameを配列で指定 name[]=user1&name[]=user2&pass=xxxxxxxx&form_build_id=form-xQZ7X7 8LULvs6SyB9MvufbZh5KXjQYRHS05Jl2uD9Kc&form_id=user_login_block&op =Log+in nameを配列にした場合のSQL文 SELECT * FROM users WHERE name = 'user1', 'user2' AND status = 1 文字列リテラルが複数生成される 70
IN句生成の便利な呼び出し方だが…
db_queryにてIN句のバインド値を配列にすると…
<?php
db_query("SELECT * FROM {users} where name IN (:name)",
array(':name'=>array('user1','user2')));
?>
IN句の値がプレースホルダのリストに展開される
SELECT * from users where name IN (:name_0, :name_1)
バインド値の配列は以下の様に変形される
array(':name_0'=>'user1', ':name_1'=>'user2'))
71
キー名をつけると キー名をつけてみる(id1, id2) name[id1]=user1&name[id2]=user2 プレースホルダにキー名がつく SELECT * FROM {users} WHERE name = :name_id1, :name_id2 AND statu s = 1 72
空白付きのキー キー名に空白をつけてみる name[1 xxxxx]=user1&name[2]=user2 プレースホルダに空白が含まれる SELECT * FROM {users} WHERE name = :name_1 xxxxx, :name_2 AND sta tus = 1 ちぎれたプレースホルダはSQL文 の一部として認識される プレースホルダには、キー :name_1がないので上記のSQL文呼び出しはエラーになる array(2) { [":name_1 xxxxx"] => "user1" [":name_2"] => "user2" } ← :name_1 ではない 73
バインド値のつじつまを合わせる キー名に空白をつけてみる name[2 xxxxx]=&name[2]=user2 プレースホルダに空白が含まれる SELECT * FROM {users} WHERE name = :name_2 xxxxx, :name_2 AND sta tus = 1 プレースホルダ :name_2 が 2箇所現れる プレースホルダ配列は上記SQL文の要求を満たすのでSQL文は呼び出される… が、xxxxxの箇所でSQLの文法違反となる array(2) { [":name_2 xxxxx"] => "" [":name_2"] => "user2" } 74
SQLインジェクションを試す
キー名に追加のSQL文を書く
name[2 ;SELECT sleep(10) -- ]=&name[2]=user2
プレースホルダの後ろに追加のSQL文が現れる
SELECT * FROM {users} WHERE name = :name_2 ;SELECT sleep(10) -- ,
:name_2 AND status = 1
実際に呼び出されるSQL文
SELECT * FROM users WHERE name = 'user2' ;SELECT sleep(10) -- , '
user2' AND status = 1
75
脆弱なソース
// includes/database/database.inc
protected function expandArguments(&$query, &$args) {
$modified = FALSE;
// $argsの要素から配列のみ処理対象として foreach
foreach (array_filter($args, 'is_array') as $key => $data) {
$new_keys = array();
// $dataは配列であるはずなので、foreach 可能。 $i(キー)に注目
foreach ($data as $i => $value) {
$new_keys[$key . '_' . $i] = $value;
}
// $queryを改変 $new_keysのキーをarray_keysでSQL文に混ぜている
$query = preg_replace('#' . $key . '¥b#',
implode(', ', array_keys($new_keys)), $query);
unset($args[$key]);
$args += $new_keys;
$modified = TRUE;
}
return $modified;
}
76
対策版(7.32)
// includes/database/database.inc
protected function expandArguments(&$query, &$args) {
$modified = FALSE;
// $argsの要素から配列のみ処理対象として foreach
foreach (array_filter($args, 'is_array') as $key => $data) {
$new_keys = array();
// $dataは配列であるはずなので、foreach 可能。 $i(キー)に注目
//foreach ($data as $i => $value) {
foreach (array_values($data) as $i => $value) { // キーを削除
$new_keys[$key . '_' . $i] = $value;
}
// $queryを改変 $new_keysのキーをarray_keysでSQL文に混ぜている
$query = preg_replace('#' . $key . '¥b#',
implode(', ', array_keys($new_keys)), $query);
unset($args[$key]);
$args += $new_keys;
$modified = TRUE;
}
return $modified;
}
77
Drupageddonは何が問題だったか? • Drupalは、DBアクセスにPDOのプレースホルダを 用いている • プレースホルダを用いる側は問題ないはず… • プレースホルダ入りのSQL文組み立てに問題があっ た – 配列の添字(整数を想定)経由で、SQL文に外部由来の値 が混入した – 配列の添字に文字列が入るのは想定外だったわけだが… • 対策版(最新版も同じ)では、array_values()関数に より、配列の値だけを取り出すことに • アドホックのように見えて、「局所安全」の考え方 には従っている お前とは旨い酒が飲めそうだw 78
安全なアプリケーションの作り方 79
安全なウェブアプリケーションのための原則 • 局所的に脆弱性を解消する – 局所的に安全な方法がとれるなら、「値の安全性」は気にしない – プレースフォルダによるSQLインジェクション対策が代表例 • コード、命令に対して、外部からの値を持ち込まない – プログラム(JavaScript含む)、SQL文、シェルコマンド、ファイル名、 正規表現 – HTMLとJSONは適当な方法がないのでエスケープで… • 「ややこしいことが起きがちな」機能を避ける – eval、system、call_user_func … – 複雑な正規表現も避けた方が良い – デストラクタ等に複雑な処理を書かない • 防御的プログラミングを実践する • 単体テストを徹底する 80
PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 https://speakerdeck.com/twada/php-conference-2016 より引用 PHPカンファレンス2016 和田卓人さんの講演から 81
PHPカンファレンス2016 和田卓人さんの講演から PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 https://speakerdeck.com/twada/php-conference-2016 より引用 https://speakerdeck.com/twada/php-conference-2016 より引用 82
Drupageddonを例外処理と表明で対処しようとする
Drupal 7.32
foreach (array_values($data) as $i => $value) { // キーを削除
$new_keys[$key . '_' . $i] = $value;
}
例外処理
foreach ($data as $i => $value) {
if (! is_int($i)) {
throw new Exception('添字は整数である必要があります');
}
$new_keys[$key . '_' . $i] = $value;
}
表明
foreach ($data as $i => $value) {
assert(is_int($i));
$new_keys[$key . '_' . $i] = $value;
}
83
「防御的」か「契約」か • 表明に基づく「契約による設計」は、表明(assign)が実行時 に無効にされることが問題 • デバッグ時にassignに引っかかれば、問題に気づき修正でき るが、脆弱性に対する攻撃では、それは期待できない • 「発生が予想されるケース」を悲観的に(防御的に)判断し て、実行時チェックを残しておく • 実行時チェックを「しないですむ」方法の一つとして、 foreachをやめてfor文を使う方法もある for ($i = 0; $i < count($data); $i++) { $new_keys[$key . '_' . $i] = $data[$i]; } • バグが減れば(バグの一種である)脆弱性も減るので、表明 も適材適所で活用するとよい 84
バリデーションはしましょうね • バリデーション(フォームバリデーション)で脆弱 性対処ができるとは限らないが… • アプリケーションの前提条件を確認するという意味 でバリデーションは重要 脆弱性 バリデーションで防げた Welcart 1.9.3 のオブジェクトインジェクション × Joomla2.5.2の権限昇格脆弱性 × WordPress REST API CVE-2017-1001000 phpMyAdminの正規表現インジェクション ○ Drupageddon(CVE-2014-3704) ○ (*1) ○ *1 Drupageddonは、ユーザ名が文字列型(配列でない)ことのチェック を入れれば防げたが、この改修は Drupal 7.36で追加された 85
まとめ • 局所的に「絶対安全」な方法で実装すれば、脆弱性は混入し ない • 信頼境界の中だけを通ってきた値を用いる際には、局所的な 脆弱性はあっても、攻撃には至らない …が確認を間違う場合がある • 信頼できない値を安全に使う方法 – 認証・認可 – フィルタリング – …これらの実装にバグがあると脆弱性になる • 局所的な安全を積み重ねることが脆弱性を作り込まない早道 • 「信頼できるはずの値」でも、可能なら局所安全な方法で実 装することで、多重防御となる • バリデーションは必ず行うが、あてにしないこと 86