入力の種類には3種類以上の種類、名前、電話番号、住所など沢山の種類があります。しかし、見方によってはたった3種類しかありません。この区別ができる/できない、で大きな違いができてしまいます。
入力の種類
セキュリティ対策上、最も重要な区別は以下の区別です。
- 正しい入力値
- 入力ミスの入力値
- 不正な入力値
これらの入力値は”独立”しています。この三種類以外の入力値はありません。従って
全ての入力 = 正しい入力値 + 入力ミスの入力値 + 不正な入力値
になります。
不正な入力値はプログラムにとって、全く意味がなく120%有害なデータです。
意味がなく害しかない不正な入力値は拒否します。不正な入力の拒否はセキュリティ上、重要な意味を持ちます。ソフトウェアは非常に複雑であり、正しい入力だけであっても、正しく処理することは困難です。不正な入力を許容している場合、正しく処理することは非常に困難です。(困難と言うより、そもそも仕様上あり得ない”不正な入力値”を”正しく処理する”ことは”拒否する以外”、論理上不可能です)
入力バリデーションが出来ない!という誤り
ソフトウェア開発に於いて最も重要なセキュリティ対策は入力バリデーションです。しかし、時々ですが入力バリデーションをどうすれば良いのか解らない、といった質問があります。
この原因は”アプリケーション仕様”が正しく理解できていないことにあります。アプリケーション仕様が
- 理解できていない
- まだ決まっていない
場合には「入力バリデーションをどうすれば良いのか分らない」となってしまいます。
前者の「理解できていない」は理解してください、としか言えません。アプリケーションの入出力仕様は「決まっている」もしくはリリースまでに「決まる」はずです。
問題は「まだ決まっていない」場合です。この場合の対策は簡単です。「仕様が決まるまでバリデーションしない」で構いません。
- 正しい入力値
- 入力ミスの入力値
- 不正な入力値
これらは”独立”しているはず、ですが開発中のアプリケーションの場合は仕様が決まっていないため
- 入力ミスの入力値
- 不正な入力値
が明確に区別できないことがよくあります。しかし、アプリケーション開発が終了するまでには仕様が決まるはずです。もし決まっていないのであればプロジェクト管理・開発体制に何らかの問題があります。この問題を解決すれば、仕様がきまり、厳格な入力バリデーションが可能になります。
決まっていない仕様とそのリスク
Cプログラマであれば、データの大きさには十分な注意を払うと思います。データの大きさ(char*型のデータや整数値)に注意しないとバッファーオーバーフローを起こし、任意コード実行を許してしまうことになるからです。
メモリ管理を自分で行う必要がない言語の場合、データの大きさに無頓着になりがちです。特定の入力データの仕様が決まっていないことも多いです。それでも、それなりの品質のプログラムが作れてしまいます。
「動くだけ」のプログラムを作ればよいのであればデータの大きさを気にする必要はないでしょう。しかし、「確実に正しく動作する」プログラムを書く場合、そうは行きません。
かなり厳しいバリデーションロジックでもリスクがあります。特に制限をしないでどのような大きさ、形式のデータでも受け入れるコードの場合、飛躍的にリスクが増加することは直ぐに理解できると思います。
入力バリデーションに困ったら曖昧な仕様を直す
入力バリデーションコードを書いていて困るのは
- 仕様が曖昧
- 仕様自体がない
場合だけです。厳密に考えるとコンピューターは整数演算さえ正しく行えません。プログラムを正しく動作させるには
- 適切な前提条件、つまり仕様
が必要です。
入力ミスと不正入力の区別が困難なケース
”入力には3種類しかない”とは言っても、明確に区別することが難しいケースもあります。
最近ではJavaScriptによるクライアント側の入力ミスチェックがないアプリケーションは少ないと思います。しかし、JavaScriptが無効の状態でも動作しなければならないアプリケーションもあります。この場合、textのinputをどう扱うのか?は曖昧になります。
例えば、日本の電話番号の入力は11桁で十分ですが、12文字の入力から不正な入力とし、バリデーションエラーとして処理を停止するコードにはできません。しかし、電話番号の入力として1KBの大きさのデータは明らかで異常であり、不正な入力として処理すべきです。
このケースも”仕様の問題”です。”何文字までの入力”を”入力ミス”として扱うのか?仕様が定まっていないのでプログラマが頭を悩ますことになります。プログラマが仕様を決めているなら勝手に決められますが、そうでないプロジェクトは沢山あります。
正しい入力を決めるのは”仕様”ですが、不正な入力を決めるのも”仕様”です。一見、困難に見えても仕様さえ決まれば入力バリデーションで困ることはありません。
入力バリデーションを実装する時期
仕様がはっきり決まっている、少なくとも入力データの仕様がはっきり決まっている(基本的には開発段階最初に入力データ仕様は決めておく方が良い)場合は開発初期から実装する方が良いです。
決まっていないなら後で実装しても構いませんし、適当な制限にしておいても構いません。重要なことは”仕様を定め”、”仕様に応じたバリデーションを実装”、”漏れなくバリデーションする”ことです。
セキュリティ対策では”残存しているリスクの管理”が重要です。”適切に入力バリデーションしていないデータ”は”残存しているリスク”です。リスク管理さえしっかりしていれば、入力バリデーションを実装する時期はいつでも構いません。
不正な入力を受け入れて得をする人?
千行、1万行くらいの簡単なプログラムは別ですが、一般的なソフトウェアは非常に複雑です。複数の開発者が関わるプロジェクトで「バグが一切ないコードを書く自身がある!」とはDJB(qmail、djbdnsの作者。個人的に脆弱性に懸賞金を出している)でも言わないでしょう。
不正な入力を受け入れて開発者、利用者、管理者は得をしません。仕事としてソースコード検査をしていますが、入力バリデーションをしっかり実装していないコードの検査は非常に厄介です。(データベースの中に入っている文字列の値に何が入っているのか見当もつかないことが多くなる、など)
得をするのは攻撃者かブラックボックス型のセキュリティ検査をする業者、WAF(Webアプリケーションファイアーウォール)販売業者だけです。
”不正なデータ”を受け入れるソフトウェアは、そもそも”完全”(安全)な動作を保証することが難しいソフトウェア、の動作保証を更に困難にします。
攻撃者は一箇所(または一パターン)の攻撃可能な脆弱性を見つければ”勝ち”と言えます。開発者は”完全”(安全)な動作を保証したいです。”不正なデータ”を受け入れる、というオプションは有り得ません。
まとめ
- 入力データには”正しい入力”、”入力ミスの入力”、”不正な入力”の3種類しかない
- 入力データの区別ができない原因は”曖昧な仕様”または”決まってない仕様”
- 仕様が決まっていれば入力データは3種類しかない
- ”入力データの仕様が決まっていない”は仕様のバグと言える
- 入力バリデーションができないデータは基本的に存在しない
- 仕様外の”不正なデータ”を敢えて受け入れる理由は全くない
このエントリでは考慮していませんが、バリデーションを実施する際に重要なのは、氏名、住所、電話番号などの”コンテクスト”です。出力時には出力のコンテクストに応じて安全に出力します。入力処理時には入力のコンテクスト(氏名、重要、電話番号、HTTPヘッダーのホスト/エージェント/コンテンツタイプなど)でバリデーションします。
2017年度版のOWASP TOP 10から、不正な入力に対するバリデーションを行わないアプリケーションは”脆弱なアプリケーション”になります。OWASP TOP 10準拠を求められている場合、早急に対応する方が良いでしょう。1
- OWASP TOP 10 A7ではWAF”も”利用できる、としていますがWAFよりアプリの入力バリデーションの方が簡単かつ効率的により厳格なバリデーションを実装&維持できます。 ↩