Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

著名PHPアプリの脆弱性に学ぶセキュアコーディングの原則

37,211 views

Published on

PHPカンファレンス2017における徳丸浩の講演「著名PHPアプリの脆弱性に学ぶ セキュアコーディングの原則」です

Published in: Technology
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

著名PHPアプリの脆弱性に学ぶセキュアコーディングの原則

  1. 1. 著名PHPアプリの脆弱性に学ぶ セキュアコーディングの原則 EG セキュアソリューションズ株式会社 徳丸 浩
  2. 2. 徳丸浩の自己紹介 • 経歴 – 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. 3. 脆弱性の分類と"入力値"の関係 3
  4. 4. 【参考】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 ディレクトリトラバーサル 4 参考: https://www.ipa.go.jp/security/vuln/CVE.html https://www.ipa.go.jp/security/vuln/CWE.html ※ CAN: CVE Numbering Authority, CVE 採番機関
  5. 5. CWEの脆弱性タイプの階層構造図 5 https://www.ipa.go.jp/security/vuln/CWE.html より引用
  6. 6. CWE-20 不適切な入力確認 6
  7. 7. 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 7https://www.jpcert.or.jp/java-rules/
  8. 8. IDS00-J. 信頼境界を越えて渡される信頼できないデータは無害化する 多くのプログラムは、認証済みでないユーザやネットワーク接続等、信頼 できない情報源からデータを受け取り、それを(改変したり、あるいはそ のまま)信頼境界(trust boundary)を越えて、信頼される側に渡す。多く の場合、データは、一定のシンタックスを持つ文字列であり、プログラム 内部のサブシステムによって解析される。不正な形式の入力データには対 応できないかもしれないし、インジェクション攻撃が含まれているかもし れないため、そのような入力データは無害化(sanitize)しなくてはならな い。 特にコマンドインタプリタやパーサに渡される文字列データはすべて、解 析される文脈で無害な状態(innocuous)にしなければならない。 コマンドインタプリタやパーサの多くは、独自の無害化メカニズムや検 査機構を備えている。可能であれば、それらの無害化メカニズムを使用す るほうが、独自に無害化メカニズムを実装するよりも好ましい。独自に実 装した無害化メカニズムでは、特殊なケースやパーサの複雑な内部構造に 配慮しない実装を行ってしまう可能性がある。それだけでなく、コマンド インタプリタやパーサに新しい機能が追加されたとき、無害化メカニズム が適切にメンテナンスされない恐れもある。 8 アーカイブ http://web.archive.org/web/20150515043831/ https://www.jpcert.or.jp/java-rules/ids00-j.html 旧版です
  9. 9. 信頼境界の中で閉じた処理の例(固定のSQL文実行) 9 信頼境界(Trust Boundary) SQL実行 プログラムソース SQL文 SQL文 SQLクエリ SQL DB SELECT * FROM employee 潜在的な脆弱性があっても、 外部から攻撃されることはない
  10. 10. 信頼できない値によりSQLインジェクションになる例 10 信頼境界(Trust Boundary) SQL実行 プログラムソース SQL文 SQL文 SQLクエリ SQL DB ブラウザ SELECT * FROM employee WHERE id='$id' 従業員コード SQLインジェクション
  11. 11. ディレクトリトラバーサルの典型例 11 信頼境界(Trust Boundary) 画面1 file= template.html ファイルシステム hiddenパラメータ fopen 設定ファイル fopen('../../../etc/passwd', … ディレクトリトラバーサル 値の改 変
  12. 12. 認可制御不備 12 信頼境界(Trust Boundary) メニュー画面1 id=takagi ユーザーDB Hidden パラメータ 個人情報表示 セッション変数 ブラウザ takagiでログイン satoの個人情報 認可制御不備 値の改変
  13. 13. セカンドオーダーSQLインジェクション 13 信頼境界(Trust Boundary) SQL実行1 (INSERT) SQL DB ブラウザ 従業員コード 氏名 SQL実行2 (SELECT) SQL実行1 (UPDATE) SQL DB 氏名 氏名 氏名 脆弱なSQLクエリ UPDATE employee SET name='$name' … 安全なSQLクエリ INSERT INTO employee VALUES(:id, :name, …) SQLインジェクション ';DELETE FROM employee -- ';DELETE FROM employee -- ';DELETE FROM employee --
  14. 14. では、SQLインジェクション対策、 どうすればいいか? 14
  15. 15. こうですか、わかりません (>_<)/ 15 無害化 フィルタ SQL 呼び出し 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 無害なデータ信頼できないデータ
  16. 16. こうですか、わかりません (>_<)/ 16 無害化 フィルタ SQL 呼び出し 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 無害なデータ信頼できないデータ
  17. 17. IDS00-J. 信頼境界を越えて渡される信頼できないデータは無害化する 多くのプログラムは、認証済みでないユーザやネットワーク接続等、信頼 できない情報源からデータを受け取り、それを(改変したり、あるいはそ のまま)信頼境界(trust boundary)を越えて、信頼される側に渡す。多く の場合、データは、一定のシンタックスを持つ文字列であり、プログラム 内部のサブシステムによって解析される。不正な形式の入力データには対 応できないかもしれないし、インジェクション攻撃が含まれているかもし れないため、そのような入力データは無害化(sanitize)しなくてはならな い。 特にコマンドインタプリタやパーサに渡される文字列データはすべて、解 析される文脈で無害な状態(innocuous)にしなければならない。 コマンドインタプリタやパーサの多くは、独自の無害化メカニズムや検 査機構を備えている。可能であれば、それらの無害化メカニズムを使用す るほうが、独自に無害化メカニズムを実装するよりも好ましい。独自に実 装した無害化メカニズムでは、特殊なケースやパーサの複雑な内部構造に 配慮しない実装を行ってしまう可能性がある。それだけでなく、コマンド インタプリタやパーサに新しい機能が追加されたとき、無害化メカニズム が適切にメンテナンスされない恐れもある。 17 アーカイブ http://web.archive.org/web/20150515043831/ https://www.jpcert.or.jp/java-rules/ids00-j.html
  18. 18. 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
  19. 19. 信頼できない値を安全に処理する例(プレースホルダ) 19 信頼境界(Trust Boundary) SQL実行 プログラムソース SQL文 SQL文 SQLクエリ SQL DB ブラウザ 従業員コード SELECT * FROM employee WHERE id=:id
  20. 20. 簡単にすると、こうだった 20 安全な形でSQL 呼び出し 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 信頼できないデータ プレースホルダ
  21. 21. そもそも信頼境界関係なくね? 21
  22. 22. そう、故に見出しが改定された 22
  23. 23. 23 https://www.jpcert.or.jp/java-rules/ids00-j.html まさかの「SQLインジェクションを防ぐ」 信頼境界も、信頼できないも、無害化も タイトルから消えた
  24. 24. では、信頼境界は無意味? 24
  25. 25. そうでもない 25
  26. 26. こういうのはダメ 26 SQL呼び出し hiddenパラメータでSQL文を渡している 信頼境界 hiddenパラメータで SQL文を渡している 安全に呼び出す方法がない
  27. 27. phpMyAdminの場合 27 SQL呼び出し 信頼境界 ソースコード セッション変数 データベース 設定ファイル … 信頼できる情報源 SQL文 SQL文 SQL文 DB管理者 認証・認可
  28. 28. ディレクトリトラバーサルの典型例 28 信頼境界(Trust Boundary) 画面1 file= template.html ファイルシステム Hidden パラメータ fopen 設定ファイル fopen('../../../etc/passwd', …
  29. 29. ディレクトリトラバーサルの対策(1) 29 信頼境界(Trust Boundary) 画面1 file= template.html ファイルシステム セッション変数 file=template.html 設定ファイル fopen('template.html', … file=template.html ファイルシステムfopen
  30. 30. ディレクトリトラバーサルの対策(2) 30 信頼境界(Trust Boundary) 画面1 file= template.html ファイルシステム Hidden パラメータ basename 設定ファイル fopen('passwd', … fopen file=passwd
  31. 31. 認可制御不備 31 信頼境界(Trust Boundary) メニュー画面1 id=takagi ユーザーDB Hidden パラメータ 個人情報表示 セッション変数 ブラウザ takagiでログイン satoの個人情報
  32. 32. 認可制御不備の対策(1) 32 信頼境界(Trust Boundary) id=takagi ユーザーDB 個人情報表 示 セッション変数 ブラウザ takagiでログイン takagiの個人情報
  33. 33. 認可制御不備の対策(2) 33 信頼境界(Trust Boundary) メニュー画 面1 id=takagi Hidden パラメータ 認可制御 セッション変数 takagiでログイン ブラウザ 個人情報表 示
  34. 34. セカンドオーダーSQLインジェクション 34 信頼境界(Trust Boundary) SQL実行1 (INSERT) SQL DB ブラウザ 従業員コード 氏名 SQL実行2 (SELECT) SQL実行1 (UPDATE) 氏名 氏名 脆弱なSQLクエリ UPDATE employee SET name=''; DELETE FROM employee --' … 安全なSQLクエリ INSERT INTO employee VALUES(:id, :name, …) '; DELETE FROM employee -- '; DELETE FROM employee -- SQL DB
  35. 35. セカンドオーダーSQLインジェクションの対策 35 信頼境界(Trust Boundary) SQL実行1 (INSERT) SQL DB ブラウザ 従業員コード 氏名 SQL実行2 (SELECT) SQL実行1 (UPDATE) SQL DB 氏名 氏名 氏名 安全なSQLクエリ UPDATE employee SET name=:name … 安全なSQLクエリ INSERT INTO employee VALUES(:id, :name, …) '; DELETE FROM employee -- '; DELETE FROM employee --
  36. 36. なんか、ややこしいですね もっと簡単にできませんか? 36
  37. 37. やりましょう! 37
  38. 38. 「値」には2種類ある • どんな値でも安全に使える方法があるもの – SQL中のリテラル(数値、文字列) プレースホルダ – HTMLの要素内容、属性値 エスケープ – … • 「値」を信頼するしかないもの – SQL文そのもの – プログラムコード – ログインユーザ名 – … 38
  39. 39. 再掲: そもそも信頼境界関係なくね? 39 安全な形でSQL 呼び出し 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 信頼できないデータ プレースホルダ
  40. 40. どんな値でも安全に使える方法がある場合は淡々をそれをやる • 脆弱性対処が局所化できる場合はそれがベスト • とりあえず、他に余計なことを考える必要はない • 「脆弱性がないこと」がひと目で分かるのが理想 • 例 – SQLクエリに文字列連結を用いず、プレースホルダを用い て行う – HTML表示の際にもれなくエスケープ処理を行う – ヘッダインジェクションが発生しないライブラリを用い る – OSコマンド呼び出しにシェル実行を伴わないAPIを用いる ※ PHPでは難しい 40
  41. 41. 「信頼できる値」が要求される場合は… • いったん信頼境界の外に出た値は信頼で きない • 信頼が必要な値は信頼境界から外に出さ ないで用いる 41 信頼できない値を いくらバリデーションしても、 信頼できる値にはならない
  42. 42. 「信頼されたデータ」が要求される例 • 信頼境界の中のデータ – プログラムコード – SQL文 – evalの入力 – 設定ファイル名に記載されたファイル名 – 正規表現 – オブジェクト 42
  43. 43. 認証・認可により信頼を与えることは可能 • 認証・認可により「信頼できることを確認」 – ログイン済みユーザ名(認証) – 管理者が入力するSQL文(認可)例: phpMyAdminで入力 するSQL文 – CMSに入力するHTML(認可) 例: WordPressで管理者 が入力するHTML • フィルタリングにより「真正でないかもしれないが 無害」にする例 – 制限されたHTML(フィルタリング) – 外部からのファイル名(basename) 43
  44. 44. 再掲: ディレクトリトラバーサルの対策(1) 44 信頼境界(Trust Boundary) 画面1 file= template.html ファイルシステム セッション変数 file=template.html 設定ファイル fopen('template.html', … file=template.html ファイルシステムfopen 値を信頼境界の外に 出さないことで対策
  45. 45. 再掲: ディレクトリトラバーサルの対策(2) 45 信頼境界(Trust Boundary) 画面1 file= template.html ファイルシステム Hidden パラメータ basename 設定ファイル fopen('passwd', … fopen file=passwd値を無害な形に フィルタリング
  46. 46. いったんまとめ • 局所的に「絶対安全」な方法で実装すれば脆弱性は 混入しない • 信頼境界の中だけを通ってきた値を用いる際には、 局所的な脆弱性はあっても、攻撃には至らない…が 確認を間違う場合がある • 信頼できない値を安全に使う方法 – 認証・認可 – フィルタリング – …これらの実装にバグがあると脆弱性になる ※ 局所的な安全を積み重ねることが、脆弱性を作り 込まない早道 46
  47. 47. ケーススタディ 47
  48. 48. Welcart 1.9.3 のオブジェクトインジェク ション脆弱性 48
  49. 49. Welcart 1.9.4 をリリースしました【脆弱性の修正】 Welcart 1.9.4 をリリースしました。オブジェクトインジェクション 脆弱性の修正などを行いました。詳細は以下の通りです。 アップグレードを行う場合は、Welcartを停止してからアップグレー ドを行ってください。 【変更点】 • オブジェクトインジェクション脆弱性の修正 フロントにて、オブジェクトインジェクションと思われる脆弱性 が認められました。 過去のすべてのバージョンが対象となります。1.9.4にアップグ レードしてください。 放置しますと、サイトに任意のファイルの埋め込まれる可能性が あります。 脆弱性に関する修正の差分はこちら 49https://www.welcart.com/community/archives/83947より引用
  50. 50. Welcartフォーラムよりインシデント報告 50 https://www.welcart.com/community/forums/forum/バグ報告 より引用
  51. 51. Welcart 1.9.3 と 1.9.4の差分 51 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= より引用 典型的な オブジェクトインジェクション脆弱性
  52. 52. オブジェクトインジェクションとは 52 • クッキー等からシリアライズデータを送り込み、任意のオブ ジェクトをメモリ内に生成 • オブジェクトが破棄されるタイミングでデストラクタが実行 される • オブジェクトを巧妙に組み合わせることにより、攻撃を実行
  53. 53. オブジェクトインジェクションに学ぶ… • unserialize関数に信頼できない値(信頼境界を超え て来た値)を渡してはいけない • やってはいけないことを知らずにやってしまったこ とが根本原因 • 以下のものも同様 – eval – system – call_user_func – call_user_func_array • 外部入力を処理する場合は、JSONを用いましょう 53
  54. 54. WordPress REST API のコンテンツインジェク ション脆弱性
  55. 55. 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サイトのコンテンツやページを改ざんできてしまう可能性が指摘されて いる。 Sucuriでは、脆弱性情報が公開されてから48時間足らずの間に悪用コードがWeb上に掲載され、共有され ていることを確認した。その情報が簡単に入手できることから、脆弱性のあるサイトを探して攻撃を試す動 きはインターネット全体に広がったという。 55 脆弱性を悪用した攻撃のイメージ(出典:IPA) http://www.itmedia.co.jp/enterprise/articles/1702/09/news064.html より引用
  56. 56. 権限チェックのupdate_item_permissions_checkメソッド 497: public function update_item_permissions_check( $request ) { 498: $post = get_post( $request['id'] ); 499: $post_type = get_post_type_object( $this->post_type ); 500: if ( $post && ! $this->check_update_permission( $post ) ) { 501: return new WP_Error( ‘rest_cannot_edit’, __(Sorry, you are not allowed ... 502: } 503: if ( ! empty( $request[‘author’] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 504: return new WP_Error( 'rest_cannot_edit_others', __( 'Sorry, you are not ... 505: } 506: if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 507: return new WP_Error( 'rest_cannot_assign_sticky', __( 'Sorry, you are not ... 508: } 509: if ( ! $this->check_assign_terms_permission( $request ) ) { 510: return new WP_Error( 'rest_cannot_assign_term', __( 'Sorry, you are not ... 511: } 512: return true; 513: } 56 wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php (Ver 4.7.1)
  57. 57. update_item_permissions_check() の返り値 57 コンテンツの性質 返り値 存在しないコンテンツ true 存在し権限のあるコンテンツ true 存在し権限のないコンテンツ false
  58. 58. update_item()メソッド 523: public function update_item( $request ) { 524: $id = (int) $request['id']; 525: $post = get_post( $id ); 526: if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) { 527: return new WP_Error( ‘rest_post_invalid_id’, __( 'Invalid post ID.' ), array( 'status' => 404 ) ); 528: } 529: $post = $this->prepare_item_for_database( $request ); 530: if ( is_wp_error( $post ) ) { 531: return $post; 532: } 58
  59. 59. WordPress 4.7.1は何がいけなかったか? • 原因: – 権限チェックの際に、存在しない id に対して、権限あり を返していた – 権限チェックの際は id キャストなし、データ更新の際は id を整数にキャストしていた • 直接の対策 – 存在しない id に対しては「権限なし」を返す – 権限チェックと更新の際には同じ id を用いる • 原則論として – 正規化(この場合は整数へのキャスト)は早期に実施す る – バリデーションしていれば防げましたね 59
  60. 60. Joomla2.5.2の権限昇格脆弱性 60
  61. 61. Joomla2.5.2の権限昇格脆弱性 攻撃の流れ 1. 会員登録時にパスワードを不整合にしておく 2. ユーザ登録時に jforms[groups][]=7 をPOSTパラ メータに追加 3. バリデーションでエラー発生 4. 再入力に備えてリクエストのパラメータをすべて セッションに保存(コントローラ) 5. モデル側で、セッションの中味をすべて取り込み 6. 2.で追加したgroupsが取り込まれる 61
  62. 62. 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 バリデーションエラーの場合、リクエストデータを まるごとセッション変数に放り込んでいる 権限の情報も含まれている
  63. 63. 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; // セッションのデータをモデルに放り込んでいる } 【中略】 $this->data->groups = isset($this->data->groups) ? array_unique($this->da ta->groups) : array(); // $this->data->groups = array(); 2.5.3でこのように修正 63 セッション汚染、Trust Boundary Violation と呼ばれる問題
  64. 64. Joomla2.5.2は何がいけなかったか? • 原因: – 入力フォームの値を、セッション変数経由で、モデルの オブジェクトに丸ごと放り込んでいた – セッション変数やオブジェクトに放り込む際にプロパ ティの名前を確認していない(!) • 対策 – まずは、放り込みをやめる – せめてプロパティ名(キー名)を確認する – セッション変数等の濫用をやめる – 結果として、信頼できる値とそうでない値を混ぜない 64
  65. 65. phpMyAdminの正規表現イン ジェクション CVE-2013-3238 65
  66. 66. phpMyAdmin: CVE-2013-3238 66 case 'replace_prefix_tbl': $current = $selected[$i]; $newtablename = preg_replace("/^" . $from_prefix . "/", $to_prefix, $current); preg_replace("/^/e¥0/", "phpinfo();", "test"); preg_replace("/^/e", "phpinfo();", "test"); $from_pref = "/e¥0“; $to_prefix = "phpinfo();”; PHP5.4.3以前では、¥0以降は無視される
  67. 67. 脆弱性が混入した要因 • preg_replaceに渡す正規表現をエスケープしていな かった – 最低限、/ をエスケープする必要がある • えーっと、preg_quoteってマルチバイト対応? – Shift_JIS以外では問題ない? • そもそも、正規表現を外部から(信頼境界を超え て)渡す実装は避けるべき(実際にもその方向で改 修された) 67 preg_replace(“/^” . $from_prefix . “/”, … ↓ reg_replace("/^" . preg_quote($from_prefix, '/') . "/", …
  68. 68. 正規表現インジェクションの対策の考え方 • PHPには、正規表現をエスケープする関数 preg_quote が用意されているが… • そもそも、正規表現を外部から指定できる状況は、 極力避けるべき • 【原則】外部由来の値は、正規表現として利用しな い • phpMyAdminの正規表現インジェクション脆弱性も、 正規表現を避ける形で実装された 68
  69. 69. Drupageddon(CVE-2014-3704) 69
  70. 70. Drupalのログイン処理のSQL文を調べる 70 name=admin&pass=xxxxxxxx&form_build_id=form-xQZ7X78LULvs6SyB9Mvuf bZh5KXjQYRHS05Jl2uD9Kc&form_id=user_login_block&op=Log+in SELECT * FROM users WHERE name = 'admin' AND status = 1 name[]=user1&name[]=user2&pass=xxxxxxxx&form_build_id=form-xQZ7X7 8LULvs6SyB9MvufbZh5KXjQYRHS05Jl2uD9Kc&form_id=user_login_block&op =Log+in SELECT * FROM users WHERE name = 'user1', 'user2' AND status = 1 通常時の要求 通常時のSQL文 nameを配列で指定 nameを配列にした場合のSQL文 文字列リテラルが複数生成される
  71. 71. IN句生成の便利な呼び出し方だが… 71 <?php db_query("SELECT * FROM {users} where name IN (:name)", array(':name'=>array('user1','user2'))); ?> SELECT * from users where name IN (:name_0, :name_1) array(':name_0'=>'user1', ':name_1'=>'user2')) db_queryにてIN句のバインド値を配列にすると… IN句の値がプレースホルダのリストに展開される バインド値の配列は以下の様に変形される
  72. 72. キー名をつけると 72 name[id1]=user1&name[id2]=user2 SELECT * FROM {users} WHERE name = :name_id1, :name_id2 AND statu s = 1 キー名をつけてみる(id1, id2) プレースホルダにキー名がつく
  73. 73. 空白付きのキー 73 array(2) { [":name_1 xxxxx"] => "user1" ← :name_1 ではない [":name_2"] => "user2" } SELECT * FROM {users} WHERE name = :name_1 xxxxx, :name_2 AND sta tus = 1 キー名に空白をつけてみる プレースホルダに空白が含まれる ちぎれたプレースホルダはSQL文 の一部として認識される プレースホルダには、キー :name_1がないので上記のSQL文呼び出しはエラーになる name[1 xxxxx]=user1&name[2]=user2
  74. 74. バインド値のつじつまを合わせる 74 array(2) { [":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の文法違反となる name[2 xxxxx]=&name[2]=user2
  75. 75. SQLインジェクションを試す 75 SELECT * FROM users WHERE name = 'user2' ;SELECT sleep(10) -- , ' user2' AND status = 1 キー名に追加のSQL文を書く 実際に呼び出されるSQL文 name[2 ;SELECT sleep(10) -- ]=&name[2]=user2 SELECT * FROM {users} WHERE name = :name_2 ;SELECT sleep(10) -- , :name_2 AND status = 1 プレースホルダの後ろに追加のSQL文が現れる
  76. 76. 脆弱なソース // 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
  77. 77. 対策版(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
  78. 78. Drupageddonは何が問題だったか? • Drupalは、DBアクセスにPDOのプレースホルダを 用いている • プレースホルダを用いる側は問題ないはず… • プレースホルダ入りのSQL文組み立てに問題があっ た – 配列の添字(整数を想定)経由で、SQL文に外部由来の値 が混入した – 配列の添字に文字列が入るのは想定外だったわけだが… • 対策版(最新版も同じ)では、array_values()関数に より、配列の値だけを取り出すことに • アドホックのように見えて、「局所安全」の考え方 には従っている 78 お前とは旨い酒が飲めそうだw
  79. 79. 安全なアプリケーションの作り方 79
  80. 80. 安全なウェブアプリケーションのための原則 • 局所的に脆弱性を解消する – 局所的に安全な方法がとれるなら、「値の安全性」は気にしない – プレースフォルダによるSQLインジェクション対策が代表例 • コード、命令に対して、外部からの値を持ち込まない – プログラム(JavaScript含む)、SQL文、シェルコマンド、ファイル名、 正規表現 – HTMLとJSONは適当な方法がないのでエスケープで… • 「ややこしいことが起きがちな」機能を避ける – eval、system、call_user_func … – 複雑な正規表現も避けた方が良い – デストラクタ等に複雑な処理を書かない • 防御的プログラミングを実践する • 単体テストを徹底する 80
  81. 81. 81 PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 https://speakerdeck.com/twada/php-conference-2016 より引用 PHPカンファレンス2016 和田卓人さんの講演から
  82. 82. 82 PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 https://speakerdeck.com/twada/php-conference-2016 より引用 PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 https://speakerdeck.com/twada/php-conference-2016 より引用 PHPカンファレンス2016 和田卓人さんの講演から
  83. 83. Drupageddonを例外処理と表明で対処しようとする foreach ($data as $i => $value) { if (! is_int($i)) { throw new Exception('添字は整数である必要があります'); } $new_keys[$key . '_' . $i] = $value; } 83 foreach ($data as $i => $value) { assert(is_int($i)); $new_keys[$key . '_' . $i] = $value; } foreach (array_values($data) as $i => $value) { // キーを削除 $new_keys[$key . '_' . $i] = $value; } Drupal 7.32 例外処理 表明
  84. 84. 「防御的」か「契約」か • 表明に基づく「契約による設計」は、表明(assign)が実行時 に無効にされることが問題 • デバッグ時にassignに引っかかれば、問題に気づき修正でき るが、脆弱性に対する攻撃では、それは期待できない • 「発生が予想されるケース」を悲観的に(防御的に)判断し て、実行時チェックを残しておく • 実行時チェックを「しないですむ」方法の一つとして、 foreachをやめてfor文を使う方法もある • バグが減れば(バグの一種である)脆弱性も減るので、表明 も適材適所で活用するとよい 84 for ($i = 0; $i < count($data); $i++) { $new_keys[$key . '_' . $i] = $data[$i]; }
  85. 85. バリデーションはしましょうね • バリデーション(フォームバリデーション)で脆弱 性対処ができるとは限らないが… • アプリケーションの前提条件を確認するという意味 でバリデーションは重要 85 脆弱性 バリデーションで防げた Welcart 1.9.3 のオブジェクトインジェクション × Joomla2.5.2の権限昇格脆弱性 × WordPress REST API CVE-2017-1001000 ○ phpMyAdminの正規表現インジェクション ○ Drupageddon(CVE-2014-3704) ○ *1 Drupageddonは、ユーザ名が文字列型(配列でない)ことのチェック を入れれば防げたが、この改修は Drupal 7.36で追加された (*1)
  86. 86. まとめ • 局所的に「絶対安全」な方法で実装すれば、脆弱性は混入し ない • 信頼境界の中だけを通ってきた値を用いる際には、局所的な 脆弱性はあっても、攻撃には至らない …が確認を間違う場合がある • 信頼できない値を安全に使う方法 – 認証・認可 – フィルタリング – …これらの実装にバグがあると脆弱性になる • 局所的な安全を積み重ねることが脆弱性を作り込まない早道 • 「信頼できるはずの値」でも、可能なら局所安全な方法で実 装することで、多重防御となる • バリデーションは必ず行うが、あてにしないこと 86

×