”雑”なソフトウェアセキュリティ対策とは?

(Last Updated On: 2018年8月3日)

先日、ソフトウェアセキュリティ対策が”雑”である、という話題になり「どのようなソフトウェアセキュリティ対策が”雑”なのか?」を説明しておかないとならないと感じました。

[名]いろいろなものが入りまじっていること。区別しにくい事柄を集めたもの。「雑の部」「雑収入」
[形動]大まかで、いいかげんなさま。ていねいでないさま。粗雑。粗末。「雑な仕事」「雑に扱う」

プログラムが正しく(=脆弱性がなく)動作するには

  • 正しいコード
  • 正しいデータ

の両方が”必ず”必要です。このため、丁寧なソフトウェアセキュリティ対策には「コード」と「データ」の両方を丁寧に扱う必要があります。

今あるWebアプリの多くは「データ」の取り扱いが”雑”です。

“雑”なソフトウェアセキュリティの代表例

「SQLインジェクション対策にはプリペアードクエリ/プレイスホルダだけ使えば良い」とするのは”雑”なソフトウェアセキュリティ対策の代表例です。なぜ”雑”なのか?はプログラムがどのように動作するのかを見れば分かります。

よく見る「これでSQLインジェクション対策は完璧!」というコードは

pg_query_params(‘INSERT INTO mytable (col) VALUES ($1)’, [$_GET[‘val’]);

とプリペアードクエリ/プレイスホルダだけを使ったSQLクエリが例に挙げられます。

確かに、このクエリによってSQLインジェクションは発生しませんが、”これだけ”では様々な意味で”雑”な対策です。

  • colのデータ型を全く考慮ぜず、アプリへの入力データをDBに渡している
    • → 無効なデータ(データ型、データ範囲エラー)でSQLエラーが発生する
    • → 無効なデータ(無効であっても使えてしまうデータ)の内容によってはDoS状態になったり、他のコードに悪影響を与える
    • → JSON型やXML型などの場合、構造に配慮していないので余計なデータを挿入できる
    • → 文字列型の場合、普通は受け入れるべきでない制御文字やその他の許可すべきでない文字データまで挿入できる
    • → 文字列型の場合でデータベース文字エンコーディングがASCIIの場合、文字以外のデータまで挿入できる
    • → 文字列型の場合で形式(HEXの32バイトハッシュ値、電話番号などの形式)が決まっている場合でも不正な形式のデータまで挿入できる
    • → DB側でCHECK制約(バリデーション)を利用し、不正なデータの挿入を禁止している場合、不正なデータで挿入時にバリデーションエラーが発生する

(問題はこれだけではありません。省略しましたが”データ”のコンテクストをよく理解した上でクエリを送信しないと、データを丸ごと抜かれた、といった事態にもなります)

ざっと問題点を挙げただけでも、”プリペアードクエリ/プレイスホルダだけ”ではかなり”雑”な動作になると分かります。

※ SQLインジェクション対策としてもプリペアードクエリ/プレイスホルダ”だけ”では不完全で、完璧とするには程遠いです

プリペアードクエリで”雑”にデータを扱う問題点

プログラムが”雑”にデータを扱うとかなり”雑”な動作になってしまいます。先ほどのSQLインジェクション対策で発生するそれぞれの”雑”な動作には異なる問題点があります。しかし、話を簡単にするため、ここでは「無効なデータでSQLエラーが発生する」だけを考えます。省略しているからといって他は重要ではない、という意味ではないことに留意してください。

  • → 無効なデータ(データ型、データ範囲エラー)でSQLエラーが発生する

SQLエラーになるなら問題ないのではないか?と一瞬思うかも知れません。しかし、SQLエラーになってしまう事自体が問題の原因になってしまう事もあります。

例えば、以下のようなメール送信記録を保存する仕組みがあったとします。

  • メールサーバーにメールデータ送信が成功した場合に、生成したメールデータをデータベースに保存する

※ 解りやすいようにDBMSのトランザクションは利用しない前提としています。また単純なログ保存にトランザクションが必要となるような構造は良い構造ではない、と言えると思います。

メール送信に利用されるプログラム/ライブラリの多くは”正当な仕様”として文字エンコーディングもバリデーションしませんし、送信するデータ量もバリデーションしません。

生成されたメールデータが

  • 不正な文字エンコーディングデータを含んでいる
  • データサイズが大きすぎる

これらの場合はSQLエラーになります。これでは、データベースに記録されるべきメールデータが保存できない状態になります。

この例では「ログされるべきデータが保存できない」という比較的軽微なセキュリティ問題しか発生しませんが、問題は問題です。

※ ISO 27000/ISMSの定義では「ログされるべきデータが保存できない」はReliability/Accountabilityのセキュリティ問題になります。

問題が発生したコード、だけを修正しても問題は解決/対策できない

先の問題をどのように解決/対策すべきでしょうか?

問題が発生した箇所のコードを修正する、といったアプローチで試してみましょう。「問題が発生した箇所のコード」とは「SQLクエリーエラーが発生させたSQL実行コード」になります。

  • 保存できない不正な文字エンコーディングデータを除去するフィルタを作成する
  • 大きすぎるデータサイズの場合、最大値を超えたデータは切り捨てる

データベースにメールログを記録するには上記のようなフィルタを作り「データを改変」してから保存するしかありません。

データベースの文字エンコーディングを変更し、バイナリデータでも、より長いデータでも保存できるようにする方法もあります。しかし、多くの場合はこれは修正方法にならないでしょう。

不正なデータにより問題が発生したコードに着目した修正/対策では、大抵の場合はこのように不正なデータを改変したり、不正なデータをそのまま保存(この仕様自体も危険)するしかありません。

雑な修正/対策しかできないのが「問題が発生したコード」に着目する方法です。

丁寧な解決/対策方法

丁寧な解決/対策方法は問題を発生させるデータに着目した修正です。

そもそもプログラムは原理的に

  • 正しい(妥当な)データ
  • 正しいコード

の両方がそろわないと正しく動作できません。

コンピュータは数値さえ正確に扱えない

プログラムがおかしな動作をした場合、コードだけでなくデータにも着目します。

プログラマであればデバッグの際にコードとデータの両方に着目してデバッグすると思います。これをもう少し高い視点から見て対応すると丁寧な問題修正が行えます。

参考:正しい(妥当な)データ

入力値の種類は3種類しかない

プログラムで正しく処理不可能な「正しくない(妥当でない)データ」(=不正なデータ)は原則に従って修正します。

  • Fail Fast原則 – 失敗するモノはできる限り早く失敗させる

先ほどの「問題が発生したコード」に着目する方法が”雑”な対応策になってしまう原因はFail Fast原則に従わず、「問題が発生したコード」に近い箇所で対応しようとしたことにあります。

データが正しくないと、プログラムは正しく動作できないので

  • Fail Fast原則に従い、正しい(妥当な)データであることをできる限り早く保証する(=正しく処理できない不正なデータをできる限り早く廃除する)
  • 廃除したリクエスト情報を記録し、適切に対応する

こうすれば中途半端な処理が行われ困った状態にならず、クライアントがおかしなデータを送ってきた事実は入力データバリデーション処理で記録/対応できます。

  • バリデーション – 正しい(妥当な)データであることを保証し、それ以外はエラーにする(不正なデータのリクエストは受け付けない。不正なリクエストは記録&対応する)

不正なデータを除外/修正するフィルタリングを行い、正しく処理可能なデータに加工し、正常なデータと同じく処理する事も可能です。しかし、これはお勧めできる方法ではありません。2017年版 OWASP TOP 10ではこのような処理を行うアプリケーションは脆弱なアプリケーションであるとしています。

正しく処理できずエラーを起こすようなデータが送られてきたにも関わらず、無視する仕様では「丁寧な対策」でないどころか、去年から脆弱なアプリケーションとしてOWASPから認定されてしまいます。

バリデーションの方法

入力データのバリデーションはできる限り早く行います。通常、バリデーションには以下の3種類があります。

  1. アプリケーションの入力処理部分で入力データ形式をチェック
  2. アプリケーションのロジック処理部分で入力データの論理的整合性のチェック
  3. アプリケーションの出力処理部分で出力データが出力先のコンテクストに対して完全に無害であるかチェック

3の出力処理部分でのバリデーションはフェイルセーフ対策です。本来はここでバリデーションエラーになるようなデータ処理は行ってはなりません。もし出力処理のバリデーションでエラーになる場合、大抵の場合はどこか上流の処理で”雑”な入力バリデーションが存在します。

バリデーションには3種類のバリデーションがある 〜 セキュアなアプリケーションの構造 〜

出力対策の3原則

入力データのバリデーションを行わなければならない、と話をすると少なくない開発者が「入力データのバリデーションだけでは出力時の安全性を保証できない!」と勘違いしてしまうようです。

セキュリティ対策の仕様のみでなく、通常のアプリケーション仕様でも「これだけやっておけばOK」ではなく「これと、これと、これをセットで実装して初めてOK」となるモノが多くを占めます。

入力バリデーションは出力時点で「正しくない(妥当でない)データを出力させない」為には役立ちますが、出力処理での安全性を保証するモノではありません。

出力処理の安全性は出力対策の3原則のどれか、またはその組み合わせで保証します。丁寧にデータを取り扱う場合、入力バリデーションの有無に関わらず、出力時点で出力データが出力先に対して無害であることを保証します。

出力対策の3原則 + 1原則

Webアプリ入力の特殊性

Webアプリで作ることが当たり前になった現在で「特殊性」とするのもおかしな話ですが、現在でもその「特殊性」を無視した仕様と実装が多く見られます。簡単にどこがどう特殊なのか紹介します。

Webアプリが一般的になる前のクライアントサーバー時代の入力バリデーションは、ロジック処理だけでバリデーションすれば十分な入力仕様を持っていました。

クライアントサーバー時代ではクライアントとサーバーが通信するプロプライエタリなプロトコルでデータをやり取りしていました。一般にこの通信プロトコルはこれは数値データ、これは文字列データ、これは日付データといった感じで厳格にどれがどういうデータなのか定義されていました。

クライアント/サーバー側のプログラムが入力データを処理する前に、通信プロトコルレベルでデータ形式はほぼバリデーション済みで、クライアントからの入力は多くの場合でクライアント画面の入力と同じでした。この為、ロジック処理では論理的/仕様的なバリデーションだけ行なえば十分でした。

Webアプリが利用するHTTPプロトコルは特定のアプリケーションを実装する為のプロトコルではなく、汎用プロトコルです。やり取りするデータは開発者が(攻撃者も)好きなように利用できるようになっています。

  • HTTPヘッダーには何でも、幾らでも、好きなだけ、好きなようにデータを記載できる
  • HTTPコンテンツには何でも、幾らでも、好きなだけ、好きなようにデータを記載できる
  • URLのクエリ文字列には何でも、幾らでも、好きなだけ、好きなようにデータを記載できる

※ 勿論、プロトコルとして多少の制限はありますが基本は自由にできます。

HTTPプロトコルがテキストベースプロトコルで汎用性が高いのは便利なのですが、クライアント/サーバー側は「汎用のデータやり取り」の方法に対応しなければならくなりました。

具体的には

クライアントサーバー時代では必要なかったプロトコルレベルでのデータ形式バリデーションをアプリケーションレベルで行う必要性が生まれました。

このため、Webアプリケーションで丁寧なセキュリティ対策を行おうとすると、より多くの入力バリデーションとロジックレベルより上位のプロトコルに近い場所、つまりアプリケーションの入力データ処理でバリデーションが必要になりました。

環境が大きく変わってしまったので、アプリケーションの作り方も変えなければならなくなったのです。

しかし、残念ながら多くのWebアプリケーションは「クライアントサーバー時代」と同レベル、同じ方法のロジック部分でのみバリデーションしています。

  • クエリパラメーターの必要なモノだけバリデーション、しかも不十分
  • POSTパラメーターの必要なモノだけバリデーション、しかも不十分

※ 不十分である代表例がデータ型チェックです。HTTPプロトコルはテキストベースのプロトコルなので、自然数データである場合は0から9までの文字列であることを検証/保証しなければならないです。クライアントサーバー時代ではこれはプロトコルレベルで検証/保証されていましたが、Webアプリにはそれがありません。

HTTPヘッダーパラメーターに至ってはバリデーションさえ皆無といったWebアプリケーションさえ多いです。大企業や公官庁でよく利用されているStrutsにはContent-Length(本来は自然数データ)にプログラムが書けてしまった脆弱性があり大問題になりました。

Webアプリケーションはそのプロトコルの汎用性から攻撃者がとても攻撃し易いモノになっています。セキュアコーディング原則の1番目に従い、全ての入力をバリデーションします。

“雑”なデータの取り扱いが生む問題の例

データを雑に取り扱うと様々な問題が生まれます。既に幾つかブログに書いているので問題例はこれらを参照ください。

出力時にも丁寧なデータの取り扱いが必要です。

完全なSQLインジェクション対策

これも出力時にも丁寧なデータの取り扱いが必要な事例です。

コマンド実行時、コマンドと引数を分離すれば完璧?

SQL実行に利用する入力データも、コマンド実行に利用する入力データもバリデーションしないと思わぬ動作の原因になります。原因は”雑”なデータの取り扱いです。出力先でデータが誤っておかしな解釈/不都合な解釈をされないよう、APIを使えばOK!、と雑にデータを扱うのではなく丁寧に扱う必要があります。

最も理解り易く身近な問題は文字エンコーディングの”雑”な取り扱いです。

エンジニアなら理解る文字エンコーディングバリデーションの必要性

入力処理時の入力データバリデーション”だけ”では丁寧なデータの取り扱うことにはなりませんが、これら出力時の問題の多くが入力データバリデーションで解消/緩和されます。

“雑”なデータの取り扱いで誰が得をする?

”雑”なデータの取り扱いでWebアプリの開発者や利用者は得をしません。得をするのはWebアプリを攻撃しようとする攻撃者です。

例えば、パラメーターの数をチェックしていないWebアプリがほとんどです。あまり重要なチェックでない、と思うかも知れません。しかし、このような”雑”なデータの取り扱いは攻撃者にとって好都合です。

例えば、Webアプリの代表的問題に

  • デバッグパラメーターの無効化のし忘れ
  • 追加権限の取得パラメーターの防御不足

があります。

攻撃者デバッグ用、追加権限用のパラメーターが分かっていない場合はブルートフォース型攻撃で「怪しい/よくある名前のパラメーター + α」で利用できるモノがないか確認します。

Webアプリにパラメーター数の制限がなく無効なパラメーターを無視している場合、クエリ文字列に数百パターン、POSTパラメーターに数千パターン、HTTPヘッダーパラメーターに数千パターン、といった感じで1回のリクエストで数千、数万回分のブルートフォース攻撃が可能になります。一秒間に1回のアクセスでも一時間で100万回の試行を行う、といったことが可能になります。ゆっくりしたペースでブルートフォース攻撃を行うと、脆弱なWebアプリからは通常のアクセスにしか見えません。

パラメーター数チェック無しで発生するリスクはほんの一例でしかありません。入力データの取り扱いが”雑”なWebアプリは、攻撃者にとって様々な攻撃をおこなう為に好都合です。

セキュリティ業者にとっても好都合です。この例の通り仕事が早く行える、”雑”なデータの取り扱いだと開発者が脆弱性を作りやく見つけやすい、ブラックリスト型で信頼性が低くても高価なWAFを売りやすくなる、などメリットが大きいです。

2017年版 OWASP TOP 10では「セキュリティ検査ツール(攻撃用ツール)が送ってくるようなデータはバリデーションして検出&対応」することを求めています。ブラックボックス型のセキュリティ検査(外部からWebサイトを攻撃するタイプの検査)をしているセキュリティ業者であれば、セキュリティ検査ツール(攻撃ツール)が生成するようなリクエストはそもそも無効/不正なモノばかりで、これを防止するだけでセキュリティ問題の多くが解消すると知っていました。

入力バリデーションによる不正データの検出と対応はセキュアコーディングの基本なので態々記載がなかったと思われます。あまりに多くのWebアプリが”雑”にデータを取り扱うので、OWASPのセキュリティ専門も業を煮やした、といったところでしょうか?

まとめ

丁寧なデータの扱い方はデータを丁寧に扱うRDBMS仕様と制約を丁寧に定義した理想的なテーブル設計を参考にすると良いと思います。1

RDBMSから学ぶデータセキュリティ

データを丁寧に取り扱うアプリケーションの構造は以下の図のようになります。

参考: “契約プログラミング”(契約による設計)を行うと自然と上記のようなデータを丁寧に扱うセキュアな構造のアプリケーションになります。

ほとんどの場合、セキュリティ対策として「◯◯だけ行なえば良い」といった対策は限定的な対策です。本当に「◯◯だけ行なえば良い」を実践してしまうと、大抵の場合は”雑”な対策になってしまいます。

重要なので繰り返しになりますが、プログラムが正しく動作するには

  • 正しいデータ
  • 正しいコード

この両方が必要です。データを雑に扱ってしまうと正しく動作しなくなって当然と言えます。入力バリデーションにより正しく動作するデータにすることが可能です。

入力バリデーションはセキュアなプログラムには必須のセキュリティ対策ですが、万能ではなく、どういうバリデーションをするどういうリスクが残るのか?は知っておいた方が良いと思います。

ほぼ全てのインジェクション攻撃を無効化/防止する入力バリデーション 〜 ただし出力対策も必須です 〜

最後に、データを丁寧に扱う責任を持つのはアプリケーションです。

フレームワークやライブラリ等には必ずしもデータを丁寧に扱う責任はありませんし、データを丁寧に扱うことが不可能な(ありのままを受け入れるしかない)場合もあります。間違いの場合もありますが、敢えて丁寧に扱っていない場合も少なくありません。

まだ作りかけですがPHP用の入力バリデーションフレームワークを公開しています。よろしければどうぞ。

参考:

正しく動作するソフトウェアの作り方

データのセキュリティ対策が無いセキュリティ対策?!


  1. SQLiteは仕様としてデータを”雑”に取り扱うデータベースです。SQLiteは丁寧なデータの取り扱いの参考にはなりません。 

投稿者: yohgaki