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

5.4K Views

October 08, 17

スライド概要

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

profile-image

徳丸本の中の人 OWASP Japanアドバイザリーボード EGセキュアソリューションズ取締役CTO IPA非常勤職員 YouTubeチャンネル: 徳丸浩のウェブセキュリティ講座 https://j.mp/web-sec-study

シェア

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

著名PHPアプリの脆弱性に学ぶ セキュアコーディングの原則 EG セキュアソリューションズ株式会社 徳丸 浩

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

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 ディレクトリトラバーサル ※ CAN: CVE Numbering Authority, CVE 採番機関 参考: https://www.ipa.go.jp/security/vuln/CVE.html https://www.ipa.go.jp/security/vuln/CWE.html 4

5.

CWEの脆弱性タイプの階層構造図 https://www.ipa.go.jp/security/vuln/CWE.html より引用 5

6.

CWE-20 不適切な入力確認 6

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 https://www.jpcert.or.jp/java-rules/ 7

8.

IDS00-J. 信頼境界を越えて渡される信頼できないデータは無害化する 多くのプログラムは、認証済みでないユーザやネットワーク接続等、信頼 できない情報源からデータを受け取り、それを(改変したり、あるいはそ のまま)信頼境界(trust boundary)を越えて、信頼される側に渡す。多く の場合、データは、一定のシンタックスを持つ文字列であり、プログラム 内部のサブシステムによって解析される。不正な形式の入力データには対 応できないかもしれないし、インジェクション攻撃が含まれているかもし れないため、そのような入力データは無害化(sanitize)しなくてはならな い。 特にコマンドインタプリタやパーサに渡される文字列データはすべて、解 析される文脈で無害な状態(innocuous)にしなければならない。 コマンドインタプリタやパーサの多くは、独自の無害化メカニズムや検 査機構を備えている。可能であれば、それらの無害化メカニズムを使用す るほうが、独自に無害化メカニズムを実装するよりも好ましい。独自に実 装した無害化メカニズムでは、特殊なケースやパーサの複雑な内部構造に 配慮しない実装を行ってしまう可能性がある。それだけでなく、コマンド インタプリタやパーサに新しい機能が追加されたとき、無害化メカニズム が適切にメンテナンスされない恐れもある。 旧版です アーカイブ http://web.archive.org/web/20150515043831/ https://www.jpcert.or.jp/java-rules/ids00-j.html 8

9.

信頼境界の中で閉じた処理の例(固定のSQL文実行) 信頼境界(Trust Boundary) SQL実行 SQL文 SQLクエリ SELECT * FROM employee プログラムソース SQL文 SQL DB 潜在的な脆弱性があっても、 外部から攻撃されることはない 9

10.

信頼できない値によりSQLインジェクションになる例 信頼境界(Trust Boundary) SQLインジェクション ブラウザ 従業員コード SQL実行 SELECT * FROM employee WHERE id='$id' SQL文 プログラムソース SQL文 SQLクエリ SQL DB 10

11.

ディレクトリトラバーサルの典型例 信頼境界(Trust Boundary) 画面1 hiddenパラメータ 設定ファイル file= template.html ディレクトリトラバーサル 値の改 変 fopen ファイルシステム fopen('../../../etc/passwd', … 11

12.

認可制御不備 信頼境界(Trust Boundary) takagiでログイン Hidden パラメータ メニュー画面1 セッション変数 id=takagi 値の改変 satoの個人情報 個人情報表示 ユーザーDB ブラウザ 認可制御不備 12

13.

セカンドオーダー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

14.

では、SQLインジェクション対策、 どうすればいいか? 14

15.

こうですか、わかりません (>_<)/ 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 信頼できないデータ 無害化 フィルタ 無害なデータ SQL 呼び出し 15

16.

こうですか、わかりません (>_<)/ 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 信頼できないデータ 無害化 フィルタ 無害なデータ SQL 呼び出し 16

17.

IDS00-J. 信頼境界を越えて渡される信頼できないデータは無害化する 多くのプログラムは、認証済みでないユーザやネットワーク接続等、信頼 できない情報源からデータを受け取り、それを(改変したり、あるいはそ のまま)信頼境界(trust boundary)を越えて、信頼される側に渡す。多く の場合、データは、一定のシンタックスを持つ文字列であり、プログラム 内部のサブシステムによって解析される。不正な形式の入力データには対 応できないかもしれないし、インジェクション攻撃が含まれているかもし れないため、そのような入力データは無害化(sanitize)しなくてはならな い。 特にコマンドインタプリタやパーサに渡される文字列データはすべて、解 析される文脈で無害な状態(innocuous)にしなければならない。 コマンドインタプリタやパーサの多くは、独自の無害化メカニズムや検 査機構を備えている。可能であれば、それらの無害化メカニズムを使用す るほうが、独自に無害化メカニズムを実装するよりも好ましい。独自に実 装した無害化メカニズムでは、特殊なケースやパーサの複雑な内部構造に 配慮しない実装を行ってしまう可能性がある。それだけでなく、コマンド インタプリタやパーサに新しい機能が追加されたとき、無害化メカニズム が適切にメンテナンスされない恐れもある。 アーカイブ http://web.archive.org/web/20150515043831/ https://www.jpcert.or.jp/java-rules/ids00-j.html 17

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.

信頼できない値を安全に処理する例(プレースホルダ) 信頼境界(Trust Boundary) ブラウザ 従業員コード SQL実行 SELECT * FROM employee WHERE id=:id SQL文 プログラムソース SQL文 SQLクエリ SQL DB 19

20.

簡単にすると、こうだった 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 プレースホルダ 信頼できないデータ 安全な形でSQL 呼び出し 20

21.

そもそも信頼境界関係なくね? 21

22.

そう、故に見出しが改定された 22

23.

まさかの「SQLインジェクションを防ぐ」 信頼境界も、信頼できないも、無害化も タイトルから消えた https://www.jpcert.or.jp/java-rules/ids00-j.html 23

24.

では、信頼境界は無意味? 24

25.

そうでもない 25

26.

こういうのはダメ hiddenパラメータでSQL文を渡している 信頼境界 安全に呼び出す方法がない SQL呼び出し hiddenパラメータで SQL文を渡している 26

27.

phpMyAdminの場合 DB管理者 信頼境界 認証・認可 SQL呼び出し SQL文 信頼できる情報源 SQL文 SQL文 ソースコード セッション変数 データベース 設定ファイル … 27

28.

ディレクトリトラバーサルの典型例 信頼境界(Trust Boundary) 画面1 設定ファイル file= template.html Hidden パラメータ fopen ファイルシステム fopen('../../../etc/passwd', … 28

29.

ディレクトリトラバーサルの対策(1) 信頼境界(Trust Boundary) file=template.html 画面1 設定ファイル file= template.html セッション変数 file=template.html fopen ファイルシステム fopen('template.html', … 29

30.

ディレクトリトラバーサルの対策(2) 信頼境界(Trust Boundary) 画面1 設定ファイル file= template.html Hidden パラメータ basename fopen ファイルシステム file=passwd fopen('passwd', … 30

31.

認可制御不備 信頼境界(Trust Boundary) takagiでログイン Hidden パラメータ メニュー画面1 セッション変数 id=takagi satoの個人情報 個人情報表示 ユーザーDB ブラウザ 31

32.

認可制御不備の対策(1) 信頼境界(Trust Boundary) takagiでログイン セッション変数 id=takagi ブラウザ 個人情報表 示 takagiの個人情報 ユーザーDB 32

33.

認可制御不備の対策(2) 信頼境界(Trust Boundary) takagiでログイン Hidden パラメータ メニュー画 面1 認可制御 ブラウザ セッション変数 id=takagi 個人情報表 示 33

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=''; DELETE FROM employee --' … SQL DB 34

35.

セカンドオーダー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.

なんか、ややこしいですね もっと簡単にできませんか? 36

37.

やりましょう! 37

38.

「値」には2種類ある • どんな値でも安全に使える方法があるもの – SQL中のリテラル(数値、文字列) プレースホルダ – HTMLの要素内容、属性値 エスケープ – … • 「値」を信頼するしかないもの – SQL文そのもの – プログラムコード – ログインユーザ名 – … 38

39.

再掲: そもそも信頼境界関係なくね? 信頼境界を越えて渡される信頼できないデータは無害化する 信頼境界 プレースホルダ 信頼できないデータ 安全な形でSQL 呼び出し 39

40.

どんな値でも安全に使える方法がある場合は淡々をそれをやる • • • • 脆弱性対処が局所化できる場合はそれがベスト とりあえず、他に余計なことを考える必要はない 「脆弱性がないこと」がひと目で分かるのが理想 例 – SQLクエリに文字列連結を用いず、プレースホルダを用い て行う – HTML表示の際にもれなくエスケープ処理を行う – ヘッダインジェクションが発生しないライブラリを用い る – OSコマンド呼び出しにシェル実行を伴わないAPIを用いる ※ PHPでは難しい 40

41.

「信頼できる値」が要求される場合は… • いったん信頼境界の外に出た値は信頼で きない • 信頼が必要な値は信頼境界から外に出さ ないで用いる 信頼できない値を いくらバリデーションしても、 信頼できる値にはならない 41

42.

「信頼されたデータ」が要求される例 • 信頼境界の中のデータ – – – – – – プログラムコード SQL文 evalの入力 設定ファイル名に記載されたファイル名 正規表現 オブジェクト 42

43.

認証・認可により信頼を与えることは可能 • 認証・認可により「信頼できることを確認」 – ログイン済みユーザ名(認証) – 管理者が入力するSQL文(認可)例: phpMyAdminで入力 するSQL文 – CMSに入力するHTML(認可) 例: WordPressで管理者 が入力するHTML • フィルタリングにより「真正でないかもしれないが 無害」にする例 – 制限されたHTML(フィルタリング) – 外部からのファイル名(basename) 43

44.

再掲: ディレクトリトラバーサルの対策(1) 信頼境界(Trust Boundary) file=template.html セッション変数 file=template.html 画面1 設定ファイル file= template.html 値を信頼境界の外に 出さないことで対策 fopen ファイルシステム fopen('template.html', … 44

45.

再掲: ディレクトリトラバーサルの対策(2) 信頼境界(Trust Boundary) 画面1 設定ファイル file= template.html Hidden パラメータ basename 値を無害な形に フィルタリング fopen ファイルシステム file=passwd fopen('passwd', … 45

46.

いったんまとめ • 局所的に「絶対安全」な方法で実装すれば脆弱性は 混入しない • 信頼境界の中だけを通ってきた値を用いる際には、 局所的な脆弱性はあっても、攻撃には至らない…が 確認を間違う場合がある • 信頼できない値を安全に使う方法 – 認証・認可 – フィルタリング – …これらの実装にバグがあると脆弱性になる ※ 局所的な安全を積み重ねることが、脆弱性を作り 込まない早道 46

47.

ケーススタディ 47

48.

Welcart 1.9.3 のオブジェクトインジェク ション脆弱性 48

49.

Welcart 1.9.4 をリリースしました【脆弱性の修正】 Welcart 1.9.4 をリリースしました。オブジェクトインジェクション 脆弱性の修正などを行いました。詳細は以下の通りです。 アップグレードを行う場合は、Welcartを停止してからアップグレー ドを行ってください。 【変更点】 • オブジェクトインジェクション脆弱性の修正 フロントにて、オブジェクトインジェクションと思われる脆弱性 が認められました。 過去のすべてのバージョンが対象となります。1.9.4にアップグ レードしてください。 放置しますと、サイトに任意のファイルの埋め込まれる可能性が あります。 脆弱性に関する修正の差分はこちら https://www.welcart.com/community/archives/83947より引用 49

50.

Welcartフォーラムよりインシデント報告 https://www.welcart.com/community/forums/forum/バグ報告 より引用 50

51.

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.

オブジェクトインジェクションとは • クッキー等からシリアライズデータを送り込み、任意のオブ ジェクトをメモリ内に生成 • オブジェクトが破棄されるタイミングでデストラクタが実行 される • オブジェクトを巧妙に組み合わせることにより、攻撃を実行 52

53.

オブジェクトインジェクションに学ぶ… • unserialize関数に信頼できない値(信頼境界を超え て来た値)を渡してはいけない • やってはいけないことを知らずにやってしまったこ とが根本原因 • 以下のものも同様 – – – – eval system call_user_func call_user_func_array • 外部入力を処理する場合は、JSONを用いましょう 53

54.

WordPress REST API のコンテンツインジェク ション脆弱性

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サイトのコンテンツやページを改ざんできてしまう可能性が指摘されて いる。 脆弱性を悪用した攻撃のイメージ(出典:IPA) Sucuriでは、脆弱性情報が公開されてから48時間足らずの間に悪用コードがWeb上に掲載され、共有され ていることを確認した。その情報が簡単に入手できることから、脆弱性のあるサイトを探して攻撃を試す動 きはインターネット全体に広がったという。 http://www.itmedia.co.jp/enterprise/articles/1702/09/news064.html より引用 55

56.
[beta]
権限チェックの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

57.

update_item_permissions_check() の返り値 コンテンツの性質 存在しないコンテンツ 存在し権限のあるコンテンツ 存在し権限のないコンテンツ 返り値 true true false 57

58.
[beta]
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

59.

WordPress 4.7.1は何がいけなかったか? • 原因: – 権限チェックの際に、存在しない id に対して、権限あり を返していた – 権限チェックの際は id キャストなし、データ更新の際は id を整数にキャストしていた • 直接の対策 – 存在しない id に対しては「権限なし」を返す – 権限チェックと更新の際には同じ id を用いる • 原則論として – 正規化(この場合は整数へのキャスト)は早期に実施す る – バリデーションしていれば防げましたね 59

60.

Joomla2.5.2の権限昇格脆弱性 60

61.

Joomla2.5.2の権限昇格脆弱性 攻撃の流れ 1. 会員登録時にパスワードを不整合にしておく 2. ユーザ登録時に jforms[groups][]=7 をPOSTパラ メータに追加 3. バリデーションでエラー発生 4. 再入力に備えてリクエストのパラメータをすべて セッションに保存(コントローラ) 5. モデル側で、セッションの中味をすべて取り込み 6. 2.で追加したgroupsが取り込まれる 61

62.
[beta]
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.
[beta]
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

64.

Joomla2.5.2は何がいけなかったか? • 原因: – 入力フォームの値を、セッション変数経由で、モデルの オブジェクトに丸ごと放り込んでいた – セッション変数やオブジェクトに放り込む際にプロパ ティの名前を確認していない(!) • 対策 – – – – まずは、放り込みをやめる せめてプロパティ名(キー名)を確認する セッション変数等の濫用をやめる 結果として、信頼できる値とそうでない値を混ぜない 64

65.

phpMyAdminの正規表現イン ジェクション CVE-2013-3238 65

66.
[beta]
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

67.

脆弱性が混入した要因 • preg_replaceに渡す正規表現をエスケープしていな かった – 最低限、/ をエスケープする必要がある preg_replace(“/^” . $from_prefix . “/”, … ↓ reg_replace("/^" . preg_quote($from_prefix, '/') . "/", … • えーっと、preg_quoteってマルチバイト対応? – Shift_JIS以外では問題ない? • そもそも、正規表現を外部から(信頼境界を超え て)渡す実装は避けるべき(実際にもその方向で改 修された) 67

68.

正規表現インジェクションの対策の考え方 • PHPには、正規表現をエスケープする関数 preg_quote が用意されているが… • そもそも、正規表現を外部から指定できる状況は、 極力避けるべき • 【原則】外部由来の値は、正規表現として利用しな い • phpMyAdminの正規表現インジェクション脆弱性も、 正規表現を避ける形で実装された 68

69.

Drupageddon(CVE-2014-3704) 69

70.

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

71.
[beta]
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

72.

キー名をつけると キー名をつけてみる(id1, id2) name[id1]=user1&name[id2]=user2 プレースホルダにキー名がつく SELECT * FROM {users} WHERE name = :name_id1, :name_id2 AND statu s = 1 72

73.

空白付きのキー キー名に空白をつけてみる 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

74.

バインド値のつじつまを合わせる キー名に空白をつけてみる 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

75.
[beta]
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

76.
[beta]
脆弱なソース
// 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.
[beta]
対策版(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.

Drupageddonは何が問題だったか? • Drupalは、DBアクセスにPDOのプレースホルダを 用いている • プレースホルダを用いる側は問題ないはず… • プレースホルダ入りのSQL文組み立てに問題があっ た – 配列の添字(整数を想定)経由で、SQL文に外部由来の値 が混入した – 配列の添字に文字列が入るのは想定外だったわけだが… • 対策版(最新版も同じ)では、array_values()関数に より、配列の値だけを取り出すことに • アドホックのように見えて、「局所安全」の考え方 には従っている お前とは旨い酒が飲めそうだw 78

79.

安全なアプリケーションの作り方 79

80.

安全なウェブアプリケーションのための原則 • 局所的に脆弱性を解消する – 局所的に安全な方法がとれるなら、「値の安全性」は気にしない – プレースフォルダによるSQLインジェクション対策が代表例 • コード、命令に対して、外部からの値を持ち込まない – プログラム(JavaScript含む)、SQL文、シェルコマンド、ファイル名、 正規表現 – HTMLとJSONは適当な方法がないのでエスケープで… • 「ややこしいことが起きがちな」機能を避ける – eval、system、call_user_func … – 複雑な正規表現も避けた方が良い – デストラクタ等に複雑な処理を書かない • 防御的プログラミングを実践する • 単体テストを徹底する 80

81.

PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 https://speakerdeck.com/twada/php-conference-2016 より引用 PHPカンファレンス2016 和田卓人さんの講演から 81

82.

PHPカンファレンス2016 和田卓人さんの講演から PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 https://speakerdeck.com/twada/php-conference-2016 より引用 https://speakerdeck.com/twada/php-conference-2016 より引用 82

83.
[beta]
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

84.

「防御的」か「契約」か • 表明に基づく「契約による設計」は、表明(assign)が実行時 に無効にされることが問題 • デバッグ時にassignに引っかかれば、問題に気づき修正でき るが、脆弱性に対する攻撃では、それは期待できない • 「発生が予想されるケース」を悲観的に(防御的に)判断し て、実行時チェックを残しておく • 実行時チェックを「しないですむ」方法の一つとして、 foreachをやめてfor文を使う方法もある for ($i = 0; $i < count($data); $i++) { $new_keys[$key . '_' . $i] = $data[$i]; } • バグが減れば(バグの一種である)脆弱性も減るので、表明 も適材適所で活用するとよい 84

85.

バリデーションはしましょうね • バリデーション(フォームバリデーション)で脆弱 性対処ができるとは限らないが… • アプリケーションの前提条件を確認するという意味 でバリデーションは重要 脆弱性 バリデーションで防げた 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.

まとめ • 局所的に「絶対安全」な方法で実装すれば、脆弱性は混入し ない • 信頼境界の中だけを通ってきた値を用いる際には、局所的な 脆弱性はあっても、攻撃には至らない …が確認を間違う場合がある • 信頼できない値を安全に使う方法 – 認証・認可 – フィルタリング – …これらの実装にバグがあると脆弱性になる • 局所的な安全を積み重ねることが脆弱性を作り込まない早道 • 「信頼できるはずの値」でも、可能なら局所安全な方法で実 装することで、多重防御となる • バリデーションは必ず行うが、あてにしないこと 86