2019/02/18)このブログではどのように”データ型”の概念とセキュアコーディングが関連しているのか、Webサーバーアプリを主体に説明します。セキュアコーディングの基本を理解している必要がありますが、難しくはないです。原則を知っているだけで十分です。セキュアコーディングの10原則は次の通りです。セキュアコーディング標準を採用する1番目の原則「入力をバリデーションする」がデータ型と関連します。/リストを持っています。この他にクラス/オブジェクト/構造体など複合型があります。これらは基本的には文字列/整数/浮動小数点/配列/リストを組み合わせ”ユーザーが定義するデータ型”で、文字列/整数などの基本的なデータ型とは異なります。プログラミング言語は一般に”汎用”利用されることを前提に設計されています。この為、基本データ型は汎用データ型として、そのデータ型が許すデータなら何でも保存できます。C言語の整数型に対してオーバーフローするような値を代入しても「許されている」ので、無効なデータであっても保存できます。クラス/オブジェクトをサポートする言語には、言語要素として予め定義されたクラス/オブジェクトが在る場合もあります。いった場合も基本的には”汎用”データ型として定義され、そのクラス/オブジェクトが許すデータなら何でも保存できます。アプリケーションの基本データ型とは?アプリケーションの入力データは、プログラミング言語の「基本データ型」が許可しているデータを全て受け入る変数ばかりではありません。プログラミング言語の「基本データ型」がそのまま使えるケースの方が少ないです。プログラミング言語の「基本データ型」は”汎用データ型”であり、アプリケーションのデータ型はのほとんどが”専用のデータ型”です。プログラミング言語の”汎用データ型”とは一致しません。アプリケーションにとってオーバーフローする整数値は通常”不正なデータ”です。年齢に”999”や取得するレコード数に”1億”、は無効なデータです。都道府県名に”あいうえお”といった文字列データも無効なデータです。Webアプリケーションのデータは基本的に”全てテキストデータ”として取り扱われます。HTTP/HTML/XML/JSON/SMTP/IMAP/LDAP/SQLといったインターフェースはデータをテキストとしてやり取りしています。整数データであっても”アスキーテキストの数字文字”でデータをやり取りします。HTTP/HTML/XML/JSON/SMTP/IMAP/LDAP/SQLといったテキストインターフェースを持つ処理系の場合、整数データの”はず”のデータであっても、”アスキーテキストの数字文字”だけが送られてくる、とは限りません。アスキー英数字だけの”はず”のデータであっても”アスキーテキストの英数字文字”だけが送られてくる、とは限りません。攻撃者は”整数であるはずの年齢や数量データ”に”プログラム”を当たり前に送ってきます。”英数字だけであるはずの商品コード”に”プログラム”を送ってきます。攻撃用データは”プログラム”に限りません。アプリケーションやライブラリにとって”特別な意味を持つ文字”(改行/ヌル文字などの制御文字や壊れた文字エンコーディングなど)を含めたデータを送り攻撃してきます。いった攻撃はインジェクション攻撃として知られているWebアプリケーションの最大の脅威になっています。無効なデータはどこまで行っても無効なデータのままです。誤作動の原因でしかありません。原則として、無効なデータのフィルタリング/サイニタイズは脆弱性に分類させる脆弱な仕様です。標準入力バリデーションにはフィルタリング/サニタイズは含まれません。話が少しそれましたが、アプリケーションは”専用”ソフトウェアで、それぞれの入力に”専用のデータ型”を持っている、と言えます。”RFC2822で許可された一文字以上のユーザー名文字列@有効なドメイン名”の形式を持つ文字データこのような形で”アプリケーションの入力データ”には”専用のデータ型”があると言えます。「入力データをバリデーションする」とは言い換えると「”アプリケーション専用のデータ型”として、”アプリケーションビジネスロジック”として妥当であることを保証する」です。特定目的に開発された専用ソフトウェアであるアプリケーションには”アプリケーション専用のデータ型”があります。通常、この”アプリケーション専用のデータ型”は”プログラミング言語の基本データ型”とは完全互換ではありません。一般に”プログラミング言語の基本データ型”は”アプリケーション専用のデータ型”とは完全互換ではなく上位互換であり、アプリケーションにとって”無効なデータ”でも処理してしまいます。アプリケーションが入力データをバリデーションせず”アプリケーション専用のデータ型”であることを保証していないこと、がインジェクション攻撃に脆弱になる大きな原因になっています。インジェクション攻撃は”誤作動”の一種で、その大きな原因が”無効なデータ”です。このため、知る限りではIPAの旧セキュアプログラミングガイドを除き、全ての真っ当なセキュリティガイドラインでは、無効なデータを排除する為にホワイトリスト型の入力データバリデーションを行うことを要求しています。バリデーションの役割は有効なデータであることを保証し、アプリケーションの正しい動作を保証すること、にあります。重要なので繰り返します。無効なデータはどこまで行っても無効なデータのままです。”誤作動”の原因でしかありません。「入力データをバリデーションする」をデータ型を使って言い換えると「入力データが”アプリケーション専用のデータ型”として妥当であるあることを保証する」(第一段階のバリデーション)となります。「入力データが”アプリケーション専用のデータ型”として妥当であるあることを保証する」だけではまだ不十分です。”データ形式”がアプリケーションにとって妥当であるかしか検証していません。入力データがアプリケーションにとって妥当であるには、入力データがアプリケーションにとって”論理的”に妥当でなければなりません。購入する商品のオプションが選択可能であるグループに追加するユーザーが存在するこういった情報に付与されるID情報は”アプリケーション専用のデータ型”として妥当な形式であるあるだけ、では不十分です。「入力データが”アプリケーションのビジネスロジック”に対して妥当であるあることを保証する」(第二段階のバリデーション)が必要です。「入力データが”アプリケーションのビジネスロジック”に対して妥当であるあることを保証する」には認証(Authentication)と認可(Authorization)、真正性(Authenticity)も含まれます。は”データ形式”(アプリケーション専用のデータ型)の検証では保証できないことは説明不要でしょう。どこで2種類の入力データの検証を行うか?入力データの検証には次の2種類があると理解りました。基本は上記の2種類ですが、同じアプリケーションであっても複数の箇所/異なるレベルで実施することがほとんどです。これらの入力データ検証をアプリケーションの何処で行うべきでしょうか?セキュアコーディング原則1の「入力をバリデーションする」では”全ての信頼できない入力データをバリデーションする”ことを求めています。Webアプリケーションクライアント(ブラウザ/Webサービス利用アプリなど)からの入力データは基本的に全て信頼できません。従ってブラウザなどからの入力データは全てバリデーションする必要があります。入力バリデーション実装の原則:フェイルファースト原則で実装する(フェイルファーストとは失敗するモノはできる限り早く失敗させるプログラミングの一般原則。CWE-20でも言及している通り”データの正規化”は先に行う)ゼロトラスト原則で実装する(全てを信頼しない原則。検証されたモノである、と完全に保証できないモノは信頼しない。27001:2012では検証コード/プロセスの検証も要求)セキュアコーディングのベストプラクティスでは”全てのコンポーネントが入力バリデーションを行うよう指揮すること”が求められます。言い替えると”多層防御(縦深防御)を実装する”(セキュアコーディング原則8)になります。Webアプリケーションの入力データバリデーションWebブラウザからの入力の場合、以下の様に検証します。WebブラウザのJavaScriptで入力データバリデーションを行うWebアプリ(サーバー)のプログラムで入力データバリデーションを行うWebアプリのデータストレージ(RDBMSなど)で入力データバリデーションを行う例えば、”年齢”というアプリケーションのデータ型(0以上140以下)がある場合、以下のようにバリデーションします。WebブラウザのJavaScriptで”0以上、140以下”であることを保証するWebアプリ(サーバー)のプログラムで”0以上、140以下”のデータのみ受け入れるWebアプリのデータストレージ(RDBMSなど)で”0以上、140以下”のデータのみ受け入れるこの例は、ブラウザ/Webサーバー/データベースの異なる独立したソフトウェア間での多層防御ですが、同じソフトウェア内でもモジュール間で同様の多層防御を実装するとより安全になります。(=Webブラウザからの入力は全て信頼できない)Webアプリ(サーバー)の入力データバリデーションは以下のように行います。ユーザー(上記の場合、Webブラウザ)が送ってこれるデータだけを受け入れるユーザーが送ってこれるデータに十分な制限を課せない場合、Webアプリ(サーバー)で妥当な制限を課し、その範囲内のデータだけ受け入れる1のユーザーによる入力エラーのチェックをWebブラウザで行っている場合、2のWebアプリ(サーバー)では単純にアプリケーション仕様で決まっている”アプリケーション専用のデータ型”として妥当かバリデーションを行います。フェイルファースト原則に従わないと無用なリスクを生むことに注意してください。1の「WebブラウザのJavaScriptで”0以上、140以下”であることを保証する」が実装できない/していない場合はどうするのか?と思うかも知れません。1がない状態はビジネスロジックバリデーション(第二段階のバリデーション)をクライアント側で実装していない状態です。2のWebアプリ(サーバー)で両方実装(第一と第二段階のバリデーション)する必要があります。バリデーションの前にデコード/正規化を行うのはCWE-20の要求事項)入力データが”アプリケーション仕様”として許可する適当な長さの文字列であることをバリデーションする(Webブラウザが送るはずがないUnicode制御コードや未割り当て領域コード以外の妥当なUTF-8文字列で100文字以下、など。処理の強制中止は行わない)ソフトウェアの各コンポーネント(この場合、ブラウザ/Webサーバー)がそれぞれの責任を果さないとWebサーバー側のデータバリデーション処理が複雑化します。各コンポーネントが厳格に入力データバリデーションを行うと、ソフトウェアをより単純にすることが可能(セキュアコーディング原則4)です。データ形式バリデーションとロジックバリデーションCERT、MITRE(CWE/SANS)、OWASPのセキュリティガイドラインでは入力データのバリデーションをデータ形式バリデーションとロジックバリデーションの2種類に分けて解説しています。”年齢”のように単純な”アプリケーション専用のデータ型”の場合、他のデータとの整合性をバリデーションする必要はありません。”PCを注文する場合に選択可能なオプション”などの場合、データベースを利用し選択したオプションがビジネスロジック(ビジネスルール)に適合したオプションであるか検証する必要があります。(ソフトウェアの信頼境界を越えるデータには、基本的に再検証が必須である点に注意)システム構成によって”どこで、どのようなバリデーションを行わないとならないか?”は変わります。”認証”と”認可”、”真正性”もバリデーションの一種で、ロジックバリデーションの1つです。ロジックバリデーションの多くはビジネスロジックを記述する部分、MVCの場合はモデル、に記述します。”認証”と”認可”、”真正性”のバリデーションはコントローラーで実施すべきである場合が多いです。MVCなら基本はモデルですが、これらは”基本形”であり、必ずデータ形式バリデーションはコントローラー、必ずロジックバリデーションはモデルで行う、といった原則ではありません。(原則にも例外がある場合もあります。強いデータ型を持つプログラミング言語でプログラミングしている開発者であれば、データ型が異なると正しく動作しない、異なるデータ型で互換性がある場合でもキャスト(データ型を変更)しないと動作しない、と知っていると思います。上位互換のデータ型でなければ暗黙キャストでコンパイラが警告またはエラー(セキュアプログラミング原則2)にしてくれます。強いデータ型を持つプログラミング言語では「言語がデータ型に適合しているか保証」してくれます。データ型に不整合がある場合、絶対にプログラムは正しく動作しません。強いデータ型を持つ言語では「基本データ型」であれば、ほぼ確実に「基本データ型」として整合性があることを保証してくれます。「基本データ型」に整合性があることを保証できるだけでも、プログラムの誤作動をかなり減らせます。これが強いデータ型を持つプログラミング言語がよりセキュアであるよりメンテナンスし易い大規模プロジェクトに向いていると言われる理由です。強いデータ型を持つプログラミング言語でも「基本データ型」の整合性保証だけでは不十分です。”年齢”を符号付き整数型の変数に保存するだけで”年齢”データとして完全に妥当である、と考える開発者はいないはずです。Theory)によるデータ型検証が必要です。Scalaはコバリアント(Covariant)とコントラバリアント(Contravariant)を言語としてサポートしています。Principle)を実装する場合、これらの機能が必要になります。バリアンスサポート機能がない言語でもassertionなどを使ってある程度保証できますが、多少手間が必要で、実行時チェックなので型論理として正しいこと完全に保証するのは困難です。アプリケーション専用のデータ型をクラスとして実装した場合に、そのデータ型が持つ”形式的検証”をセッターメソッドに実装すると実行効率が悪くなり現実的でなくなることもあります。100万を超えないカウンタ型のデータ型を定義した場合にセッターメソッドで毎回、カウンタ値が100万を超ていないかバリデーションした、とするとカウンタ値を増やすだけでif文によるブランチが発生します。「if文ブランチの1つくらいは大したことでない」と思うかも知れませんが、塵も積もれば山となる、で万事が万事この調子で実行時にバリデーションしているとアプリケーションの実行速度が許容不可能なレベルにまで低下することがあります。話がそれ気味でしたが、要するに強いデータ型を持つ言語の「データ型チェック機能」は非常に有用だが、それだけでは不十分であったり、現実のアプリケーションでは利用できなかったりする場合がある、という事です。契約プログラミング/契約による設計はコードが持つ契約(≒”アプリケーション”が定義するデータ型)が妥当であるか、確実にバリデーションするプログラミング/設計手法でもあります。起きてはならない場合、トランザクションはシリアライザブルPHPのOpenSSL関数を利用して暗号化する例2要素認証のTOTPとHOTP、どちらがより安全か?完全なSQLインジェクション対策JSONPは危険なので禁止インジェクション攻撃は3種類ある紹介エレクトロニック・サービス・イニシアチブのWebシステムのコンサルタントのブログです。PHPの開発、PHP4.x/5.xレガシーPHPセキュリティパッチサービスなどを行っています。ブログのサブタイトルを「書かない日記」としているのは、ブログを始めた時にあまり書かないつもりで始めたのでこのサブタイトルになっています。yohgaki@ohgaki.net私が記述したブログ中のコードは記載が無い場合はMITライセンスです。その他のコードは参考リンクなどのライセンス情報を参照ください。