Rails4セキュリティ リローデッド(仮)

7月 14, 2013 Development
(Last Updated On: 2018年8月8日)

前回公開したRails4セキュリティのスライドは誤解をされる可能性があるのでは?と指摘される方も居たので「Rails4セキュリティ リローデッド」として追加情報を加えたスライドを作ろうかと思っていました。確かに、今見直したら肝心な所で追加し忘れてる部分があって誤解しやすい、というか言いたい事が分からないかも知れません。

入力パラメータをバリデーションするとは、バリデーションするメソッドを作ってバリデーションする、ことです。必須・許可設定だけだとモデルに渡すデータがどれかしか分かりません。許可設定と同時にバリデーションする事が良い、という事です。少なくともここだけは直した方が良さそうです。

あまり時間が取れなかったのでスライドを作るために作ったメモを取り敢えず公開します。スライドにして欲しいという方が多ければスライドにするかも知れません。

メモを読む前に「モデルで処理に利用すべきパラメータが指定してある場合、モデルで指定外のパラメータをバリデーションすべきか?」を考えてから読むと解りやすいかも知れません。

関連エントリ

 http://blog.ohgaki.net/rails4-security

 

Rails4とそれ以前

Rails3以前は何もかもモデルでバリデーション。

Rails4コントローラで 許可するパラメータを指定するので、モデル以外でバリデーションが必要に。

ActiveRecordのモデルを普通に作るとモデルに関係ないパラメータはバリデーションしない。→モデルに関係ない(使用しないフラグが立っている)パラメータまでバリデーションするモデルは良いモデルなのか?

従って、コントローラでは少なくともActiveModelモデル以外用のパラメータもバリデーションすると良い。全部バリデーションするとなお良い。

「なんでもモデルで」から「入力パラメータバリデーションはモデルから分離へ」へ。実際、入力パラメータバリデーションはコントローラの最初の処理として実行する事が自然。 

 

Strong Parameters 

Strong Parametersは仕組みとしてコントローラで入力パラメータのバリデーションを推奨。

params.require(:user).permit(:name, :email)
my_params_validate(params) 

または

paramsオブジェクトからバリデーションメソッドを呼ぶ様に拡張

params.require(:user).permit(:name, :email).validate(self)
コントローラのバリデーションメソッドをparamsをパラメータとして呼び出す。

後者の方法が最もRails風(?)こういうパラメータが必要でバリデーションOKなら処理続行という意味も分り易い。

入力パラメータのバリデーションはモデル開発を分業している大規模なアプリなどでは特に有用。

モデルにはpermit設定があるが、モデル以外(ビュー用)のAPI・設定が無い。← 改善の余地ありだが無くても可

permitしていない場合、ActiveRecordで利用すると例外が発生する。← マスアサイメント対策。余計なパラメータは許可しないよう注意。コントローラのバリデーションではpermitの有無に関わらずバリデーションすると良い。

 

 

コントローラでのバリデーションを推奨する背景

 特定のリクエストに対する入力がどのような物か、全て定義できるのはMVCのうちコントローラ。(実装方法としてはバリデーション専用モデルを作る方法もある。← 最初にバリデーションを実行すれば良い) 

 

Railsアプリのソースコード検査を実施すると

モデルでのバリデーション、エスケープ不十分、APIの使い方が不適切な場合が多い

SQLインジェクションに脆弱なコードがある事が多い ← 何故Railsで?と思うかもしれないが意外に多い

そもそもモデルでの入力パラメータバリデーションが無いことも多い←特にDOAにマッチしない物

ビューへの出力に気を使っていないコードが多い ← 必要なサニタイズ・エスケープメソッドを利用しない(sanitize_css, escape_javascriptなど)、不用意なタグ付き出力など

データモデルに関係ないパラメータまでデータモデルでバリデーション? 不自然。

 

独自実装と現実のアプリケーション

参照整合性に代表させるデータベースアクセス仕様

 メリット:migrationで色々なバージョンのスキーマを使える ← プロトタイピングには便利

 デメリット: 

データベースシステムで参照整合性を管理しないのでアプリで管理しないと不整合が起きる 。より効率的に実行することも可能だがRailsの公式ガイドに掲載されている削除しの参照整合性維持方法(親レコードIDを見つけ、子テーブルのレコードをループで削除)はとんでもなく非効率。データベースシステムの参照整合性を利用していればそもそも不要。→ DRYでないし遅い。

 本格的なシステムを実際に運用する場合、参照整合性がアプリまかせだと怖い&非効率なので、参照整合性やその他のデータベースシステムに備わっている機能を独自に使う。(普通にやってますよね?)→ 参照整合性を正しく設定したデータベースシステムの利用方法なら必要ない参照整合性維持のコードを独自にモデルに書かなければならない。

データベース接続を切り替える機能が無いので、読み取り専用、読み書き用のDB接続を切り替えれない。← 緩和策として独自実装に読み取り専用のテーブル(モデル)にすること (insert_or_updateを改造)は可能。← 更新不可能なVIEW(データベースのVIEW)を使うことも可能だがこれも独自実装となる。

業務システムなどでは削除時に物理的に削除するのではなく、削除フラグのみを設定することがよくあるが、このような仕様は独自実装となる。← PostgreSQLの場合、RULEシステムを使うとこのような仕様をサポートすることができるが、これも独自実装となる。(トリガでも可能だがRULEの方が効率よく実行できる)

PostgreSQLのGINと配列を組み合わせて使うと、タグ付けしたデータの検索を大幅に高速化できるが、このような有用な機能を利用するには独自実装が必要。

これらの実際のアプリケーションでは必要な機能を独自実装するとマイグレーションは機能しない。← これは仕方ないこと。

 

このように実際のシステムではRailsの流儀ではない、実際の要求仕様に基づく独自実装が欠かせないことも多い。→ 特に本格的なアプリ

 

アーキテクチャ 

 DOA重視のRails → DOA(CRUD)にマッチするアプリ開発は安全かつ簡単に行える。→ 開発者が8割のアプリ開発を楽にすることを目的としているのでこれでOK。

 DOAにマッチしない場合は?←データベースを使わない機能など

 ActiveModel 

 include ActiveModel::Validations ← 基本的には使うべきだが、使わない(知らない人)も多い?バリデーション仕様がプロパティアクセス時なので使いづらいから?

 

モデルのみバリデーションのメリットとデメリット

 同じモデルを使い回せる場合、一箇所だけバリデーションできる→多層防御?

 モデルは複雑になりがち→ 検査しづらい、間違いを起こしやすい、DBの参照整合性を設定しれいれば不必要なバリデーションコードが必要

 そもそもモデルを信用するのか?外部ライブラリの利用?

 モデルに問題があった場合は?

 全てのアタックパスの防御として不十分

 簡単なセキュリティ問題の影響評価を行う場合でも、より多くのコードを読む必要が出てくる

 実際に脆弱性があるコードが存在する ← 参照整合性制約を書かなければならない仕様も影響?

 

コントローラによるバリデーションのメリットとデメリッ 

 セキュリティ対策は多層防御

 漏れがない

 コントローラはモデルに比べ、比較的シンプル→間違いを起こしづらい、発見しやすい

 分業に向いている→モデル、ビューの作成が他人の場合によい←いちいちコードを確認するのでは無く、取り敢えずは入力パラメータを出来る限り厳格にバリデーションした後に信用して使う。←ベストプラクティスとは言えないが普通の開発現場の実情

 モデルやモデルが利用するライブラリ、出力コード(モデル、ビュー両方)に脆弱性があっても致命的な問題を防ぐ。バリデーション方法によってはモデルの実装に関わらず、脆弱な出力がセキュリティホールになるかどうか、判断できる場合もある。→ 致命的な脆弱性を防ぐには出力先の入力仕様(エスケープ仕様)を知っている必要がある。→ 知らなくても厳格にバリデーションしていれば運良く防いでいる事もよくある。

 コントローラによる入力バリデーションの方が、モデルによるバリデーションに比べ、よりセキュアなアーキテクチャ→多層防御、よりシンプル、アタックパスの削減

  独自実装とも言えるが本格的なアプリケーションに独自実装や改造は必須。

 

DRY

 コントローラで、モデルで二重→ DRY的にはNG。しかしセキュリティ対策はトレードオフ

 Railsで非DRYの筆頭はデータ整合性制約のアプリ実装

  バリデーションの目安:コントローラでは形式の妥当性をチェック(形式、長さ、数値の大小、etc)、モデルでは論理的な妥当性のチェック(データとの整合性、保存データ形式が決まっていて出力先で形式のバリデーションがない場合は形式のバリデーションも行う)→ 目安なので適宜実情に合わせた調整が必要。参照整合性やデータ形式チェックをデータベースシステムに任せる場合も。

 データの論理的整合性を維持するにはデータベースシステムの機能をできるだ利用する。(確実に整合性を維持するにはデータベースシステムで強制しかない)

  例:参照整合性。RESTRICT、CASCADE、SET NULL、SET DEFAULT
  例:NUMERIC型を使用した任意精度の数値型。
  例:CHECK制約。
  例:データベースでデータ型を作成。
  例:データベーストリガーによるチェック。
  例:PostgreSQLの場合、RULEによる制約。
  例:PostgreSQLのJSON型は不正なJSON形式でエラーとなる。

 データベースシステムによるデータ制約はアプリによるデータ制約より安全性が高い。→ データベースは1つのアプリから操作されるとは限らない。アプリによる制約はソフトな制約、データベースシステムによる制約はハードな制約。→ とは言ってもアプリの開発効率を考えるとフレームワークに従って開発する方法もアリ。(特にプロトタイピング)

完全主義でシステム全体を作る事は事実上ほぼ不可能だがデータベースシステムによる制約は実現可能。完全主義の方は当然上記のデータベースによる制約を必ず利用している? 

Railsのマニュアルに従うだけではデータベースシステムを駆使した本格的なアプリケーションは作れない 。→独自実装必須

 

入力パラメータのバリデーションとセキュリティ

 モデルやビューで問題があった場合でも致命的欠陥とならないようにする為の緩和策  → モデル(処理)のみでなく、ビュー(出力)のセキュリティ対策でもある

 モデル・ビュー(+その配下のライブラリ、内外のWebサービスなど)に欠陥があった場合の緩和策なのに、バリデーションはモデル?

 入力パラメータは出来る限り早い段階で確認する方が良い。→ 入力バリデーションは何かを処理・出力した際に起きる問題の緩和策 → 何かをする前に入力パラメータはバリデーションすべき。→ ただし、例外あり。入力がエンコードされていたり、変換してから利用しなければならない場合(Unicode正規化など)は変換してから確認。数々のセキュリティホールの実績あり。

 

 

入力ミスと入力バリデーションエラー

入力ミスと入力バリデーションエラーは明確に区別する必要がある。

  例:メールアドレスの場合、許可する文字以外(制御文字など)の文字が含まれていたらバリデーションエラー。それ以外は、入力ミスとしてモデルでエラー処理する。

 どのように区別するかはアプリケーション仕様によって異なる

 例:メールアドレスの形式をJavascriptでチェックし、Javascriptが必ず使える事が前提であるアプリなら、文字のみでなく不正な形式でバリデーションエラー。

 

コントローラとモデルの役割

 コントローラ:入力受付とモデル・ビューの呼び出し

 モデル:ビジネスロジック

 コントローラにビジネスロジックはNG。MVCモデルの基本。コントローラには入力受付の処理とモデル・ビューの呼び出し。入力受付の処理には入力パラメータのバリデーションも含める。

 ActiveRecord(データベース)を利用しないモデルはどうすべきか?

 例:StackOverflowのLDAP認証(バリデーションなし(パラメータ渡しなので当然とも言えるが)、バリデーション無しなのでActiveModelでなくActiveRecordを継承?)→ LDAPサーバへの出力をエスケープ。(TODO: net-ldapのコードを確認。filterはescapeメソッドでエスケープが必要(?)なハズ。bind_asは要らないか?他のFilterも。OKなら他の脆弱性がありそうなサンプルを探す)← セキュリティ対策の基本が「エスケープ > API > バリデーション」の例でもある

http://stackoverflow.com/questions/3539501/rails-ldap-login-using-net-ldap

参考:http://www.veracode.com/security/ldap-injection

 コントローラでバリデーション済みの入力パラメータのバリデーションはモデルで行なっても良い。モデルではデータ整合性チェック(これ自体がバリデーションにもなる)やモデルから利用する外部リソースの入力・出力 チェックを確実に行う。(特にエスケープ処理できないブラウザへの出力となるデータには細心の注意を払う)

 モデルにも入力出力がある事に注意。データベース以外にもWebサービスやファイルなど信頼できないリソースのやり取りがある場合も。JSON、CSV、etc

  例:Railsで普通にメール送信をしている場合は問題無いが、メールのような単純な出力先でもメールヘッダーインジェクションに注意しなければならない。何らかの理由で他の手段でメール送信しなければならない場合は注意が必要。

 

アプリ開発

 モデル開発の分業→モデルを盲目的に信じるのはNG→開発後のメンテナンスもある

 モデル開発を分業しても入力パラメータのバリデーションは必要→分業しているからこそ入力バリデーションが必要

 個人で開発? → 全部コードを把握しているつもりでもミスはある → ビューではデフォルトで全てHTMLエスケープ → コントローラではデフォルトで全ての入力パラメータをバリデーション(例えば、未知のパラメータは英数字のみに制限)

 

 

モデルとセキュリティ

モデルのセキュリティ対策はニーズによって変化する

 例: 金融系アプリのモデルでO(英字)を0(数字)に変換

 一般的にはNGだが、この場合はOK。→ Availability。外部からの壊れたデータが原因でトランザクションが実行できない方が問題

 インタラクティブな操作の入力パラメータでOを受け入れるのはOK?→当然NG。入力エラーにするか、バリデーションエラーにしなければならない。

 基本的にモデルは「汎用的」であるべき。→ パスワードの文字種を制限?LDAPは?他のアプリでパスワード変更→ログイン不可。 アプリに特化?DRY?→ メソッドを作ってコントローラで統一チェック。

 

モデル(DOA的)アプローチによるセキュリティ維持の欠点

 基本的にはモデルのアクション(操作)を実行しないとバリデーションしない → アタックパスの許容

 モデルでは使わないパラメータも。Javascriptへのパラメータ、CSSスタイル指定など。

 元々複雑になりがちなモデルを更に複雑化。(とは言ってもモデルで必要なバリデーションは行うように)

 モデルを完全に信用してよい?現実的には信用しない方が安全。→ デフォルトHTMLエスケープと同類の議論

 

Rails4(Strong Parameters)の制限

 Strong Parameters導入で、モデルでのバリデーション対象は許可済みパラメータに実質的に限定しているが、公式APIがない。簡単にコントローラでのバリデーションを実現できるが公式APIがある方がよい。

 ビューで許可するpermitsの制御ができない→モデルには使わない。ビューに出力(Javascriptで制御された管理者用画面、CSSスタイルの変更など)

 ただし、DOAに親和性の高い簡単なブログアプリなどではpermitsのモデル以外の制御は必要ない

 複数のコントローラから同じモデルを利用する場合、入力パラメータバリデーションが重複→フレームワークのサポートが欲しいところ。クラス・メソッド化によって軽減可能。→ 論理的整合性などのビジネスロジックはモデル。

 

ホワイトリストとブラックリスト

 入力パラメータのバリデーションの基本ホワイトリスト。ブラックリストアプローチも可能だが不十分になりやすい。

 ホワイトリスト > ブラックリスト

 

 

文字エンコーディングのチェック(ケーススタディ)

 文字エンコーディングはチェックする事が当たり前に

 PHPは文字エンコーディング攻撃が認知され始めた当初からバリデーション関数あり(2006年4月)

 Ruby 1.9で例外。Ruby2.1でscrub。

 

 

ベストプラクティスと実装

 文字エンコーディングをチェックすることはベストプラクティスだが実装されていない事も多い。→ エンコーディング指定することによって意識しなくてもチェックされるという考え方もあるが、もっと明示的な操作(プログラマが明示的にチェックしていると意識できる操作)の方が良い効果をもたらすと思う。→ Ruby2.1のscrub

 HTMLエスケープのベストプラクティスは<,>,&,”,’,\をエスケープ→\をエスケープしていない実装は多い。 

 最小権限の原則はセキュリティ原則の1つだが簡単な事でも実装されていない事が多い。

  例:読み込み専用のデータベース接続、読み書きのデータベース接続の使い分け。→ 検査したソース(Rails以外含め)ではほぼ皆無。 そもそもRailsの場合、ActiveRecordがサポートしていない。ソフト制限であるリードオンリーモデルには独自実装が必要。

 エスケープが必要な出力(入力)がある場合でも、エスケープ関数が必要だが実装されていない事も多い。

  例:データベースの識別子エスケープ(Rails(ActiveRecord)には無いが、ruby-pgにはある)、CSSエスケープ(Railsはサニタイズ(空文字列にする) sanitize_cssはある)、Javascriptエスケープ(RailsにはJS文字列エスケープ用にjがある)

 ベストプラクティスが実装されていない事は当たり前(残念ながら)→ デフェンシブ(防御的)なコーディングが必要 → 入力を処理に渡す前にバリデーションする事はデフェンシブプログラミングの基本中の基本

 

 

ビューのデフォルトHTMLエスケープとデフォルトコントローラバリデーション

 明らかにデフォルトHTMLエスケープの方がより安全

 デフォルトエスケープになったけど、選択的にエスケープ?コントローラで入力パラメータをバリデーションできるようになったけど、モデルでチェック?

 デフォルトコントローラで入力バリデーション。よりリスクを軽減する効果が期待できる。

  デフォルトエスケープもフェイルセーフ。コントローラ入力バリデーションもフェイルセーフ(モデルよりカバー範囲が広い)

 

 

セキュリティ対策とは

 セキュリティ対策であるかないか、の議論に意味はない → どちらがより効果的なのか?で良し悪しを判断。→ 間接的、不完全でもリスク軽減に役立つものは全てセキュリティ対策

 例:プリペアドステートメント、パスワードによる認証 

   間接的・不完全な対策をセキュリティ対策でないとすると、多くのセキュリティ対策はセキュリティ対策でない、という定義になってしまう。

 セキュリティ対策は「効果」を議論する物。→ セキュリティ対策=緩和策

 「効果的」の意味は場合によって異なる事も。(Oと0の変換モデル)

 穴(アタックパス)をできる限り作らない。現実のコード(脆弱性)を参考に。

 

 

モデルでも入力パラメータのバリデーションは必要

 モデルからWebサービスなどの外部入力を取得・利用するモデル→入力バリデーションは必須

 データの保存先が形式チェックをしていない場合、アプリ(モデル)での形式チェックは必須 

 多層防御を実装するならモデルでも再チェック(DRYではないが、多層防御とはそういう物。モデルを書くプログラマはコントローラを書くプログラマを信用する必要はない)→ アプリ単位でセキュリティを考えるのか?アプリのコンポーネント単位でセキュリティを考えるのか?

 

コード検査と入力パラメータバリデーション

 検査しやすいコード=より安全なコード

 入力パラメータをコントローラで十分なチェックしていれば、危険な出力を行なっていても、取り敢えずは安全であると直ぐ分かる。→ モデルで入力バリデーションする場合、モデルのバリデーションを通過しているか、モデル(複雑になりがち)で正しくチェックしているか、下位のライブラリが正しく処理しているか、確認項目が多くなる。

 例:LDAP認証モデルのコードを見ただけでは、安全であるかどうか判断できない。利用しているLDAPアクセスライブラリのコードを確認する必要がある。→ 英数字のみでバリデーションしていれば安全だと直ぐ分かる

 例:RailsのTagHelper。深くネストした呼び出しの最後まで見ないと、属性名のエスケープ処理が行われていない事は確認できない。→ 属性名を英数字のみでバリデーションしていれば安全だと直ぐ分かる

 

 

コントローラ入力バリデーション Q&A

何故コントローラで入力バリデーション?→Strong Parametersが導入され、モデルでのバリデーション対象はモデルで利用するパラメータに限定。(限定した以外のパラメータチェックに意味がない)

モデルで完璧にバリデーションしていればOK → モデルやビューに欠陥があった場合の緩和策

使う入力パラメータを全部バリデーションするとpermitsが設定されてデータ操作可能? → 権限に応じて許可する入力パラメータのみ許可。バリデーションは許可設定も参考にできるが、無条件にバリデーションしても構わない。

 コントローラとモデルで二重にバリデーションするのは非効率 → セキュリティ対策とは様々な要素とのトレードオフ

コントローラで入力バリデーションすればモデルでの入力バリデーションは不必要? → いいえ。多層防御を実装しても良いし、モデルでの入力バリデーションが必須な場合もある。

モデルでの入力バリデーションが必要な場合、コントローラでの入力バリデーションと同じでよい? → 可能であれば別のチェックにした方が良い。コピー&ペーストは間違いもコピー。

二重にバリデーションするのはDRYじゃない → 多層防御の必要性が無いなら、バリデーションしない事も選択肢(あまりお薦めしませんが)DRYじゃない、と言ってもアプリで参照整合性制約を維持するよりはかなり簡単。

どういう物を入力バリデーションエラーにすべき? → 入力ミスとバリデーションエラーは区別して考える。メールアドレスなどのフォーマットはJavascriptで形式チェックをしていればフォーマットチェックで不正な形式をバリデーションエラーにしても構わないが、入力ミスがあり得る場合はモデルでエラーとして処理する。不正な文字や不正なフォーマットの場合、エラーページを表示して処理を終了する入力をバリデーションエラーにする。

独自実装じゃないの? → before_filter/actionなどで呼び出すよりは良いのでは?本格的なアプリにはどちらにしても独自実装は不可欠。Rails4の場合は許可パラメータを列挙する時にバリデーションを実行するのが見た目も分り易い。

Strong Parametersはモデルの事だけを考えて作ってあるからビューなどの入力パラメータのバリデーションとしては使い辛い → 制限に付き合う。パッチを作って提案。(ビューで許可するパラメータを設定できると便利。不許可パラメータで例外)

Cookieとかは? → 全ての入力、プリファレンス設定などのクッキー値も別途バリデーションすべき。クッキーの他にはHTTPヘッダや環境変数にも外部入力データがある。攻撃テストなどでは必ずチェックされる。

 

まとめ

 モデルでの入力バリデーションは、アーキテクチャ的にコントローラで入力バリデーションするものより脆弱。

 Strong Parametersの導入で実質的にモデルで利用するパラメータ以外のバリデーションはコントローラに責任が移った。バリデーションするならコントローラで全てバリデーションする方が良い。バリデーションエラーの場合、処理を中止することを忘れずに。

 入力パラメータのバリデーションを「実行する場所」と「実行の仕方」が問題なので、実際の入力パラメータバリデーションコードは何処にあってもよい。
(コード自体は適当なモデルの中でもOK。コントローラへの入力パラメータ仕様を切り出して実装する方が使いやすい)
(場所、は何か処理する前。実行の仕方、は無条件に実行することが要件。空のユーザ入力は普通、セキュリティ問題にならない。概ね無視でもOKだが空文字列や0が特別な意味を持つ場合は要注意)

 デフォルトでコントローラから全ての入力パラメータのバリデーションを行う事は、デフォルトでビューの出力パラメータを全てエスケープするセキュリティ対策に似ている。(今時、これに反論する人は居ないハズ?)

 モデルで入力パラメータをバリデーションしてもセキュリティ対策になる→しかし、より穴を作りやすい 

  よいセキュリティ対策とはより多くのアタックパスを除去する対策 → 同じバリデーションをするなら、より多くのアタックパスを除去可能な「場所」でバリデーションをする方が良い

 バリデーションするコードは自前で書く。従来からのモデルによるバリデーションもAPIサポートはあるが中身は独自実装と言える。

 多層防御が要らないならマスアサイメント対策としてだけ利用することも可能。Railsの基本仕様がマッチしているシンプルなアプリではコントローラでのバリデーションの必要性は低くなる。

 

 

 

その他のメモ

 ブラックリストでもセキュリティ対策にはなる→しかし、より穴を作りやすい

 SQLインジェクション対策とモデル

  盲目的にプレイスホルダを使え→間違いの元 (IN句、識別子、LIKE検索など)

  そもそも機能が無い場合も→例:PostgreSQLのByteaのエスケープとアンエスケープ。

 不正なデータを認める事が「セキュリティ対策」になることも。Oと0の変換。

 脆弱な仕様やアーキテクチャにこだわる意味は無い → PHPで色々体験。register_globals(PHP版マスアサイメント)、埋め込みスクリプトしかサポートしない仕様、複数アプリ利用が多いPHPでセッションアダプション脆弱性を直さない、マルチバイト文字モジュールが標準でない仕様、etc

投稿者: yohgaki