入力チェックは簡単でしょうか?
こんにちは。Nonです。
今回は入力チェックについてまとめてみようと思いました。
入力チェック。意外と難しいというのが僕の認識です。
数字チェックや文字チェックだけかと思いきや 実はそうじゃない ところが難しい。
というのも、あるプルリクの時
Modelでバリデーションするんやで(CakePHP使用)
Laravel使いの僕「!?!?!?」
となりました。
気を悪くしたらすみません。他意はありませんが、本当にこんな感じでした。
これが妥当なのかどうかを記事を書きながら検証していきます。
僕はセキュリティの最先端を行くエンジニアではないので、
- 入力ってなんや?
- 入力チェックってなんや?
- 入力チェックって何回すればええんや?
遠回りにはなってしまいますが、
「 あなたは何回入力チェックしているだろうか? 」
この問いに答えるために、上の順番で追ってみようと思います。
(激しい議論になりそう😂お手柔らかに・・・)
入力ってなんや?
これも経験値がある人にとっては簡単な質問かもしれません。
入力とはプログラムに送信される値であり、パッと思いつくのは2種類です。
- 妥当な入力
- 異常な入力
ここで1つ問いを投げたいと思います。
入力ミスは異常な入力であるか?
答えは否です。
中には不思議に思われる方もおられるかもしれません。
しかし、入力ミスは 妥当な入力 です。
「妥当な入力」ってなんや?
妥当な入力とはエンジニアが想定した値であり、プログラムが正常に動作する値 です。入力ミスはエンジニアにも想定可能であり、入力された時は入力エラーとして画面に表示します。
例:
あるアプリで商品の値段を登録する画面があります。
商品名は文字列で、値段は正の整数を想定しています。用意されたDBは下記の通りです。
products
id AUTO_INCREMENT name VARCHAR(100) price INT 用意されたプログラムはPHP(Cake風)です。
public function add() { // その他コード if ($this->request->is('post')) { // 登録処理 $entity = $this->products->newEntity($this->request->data); $this->save($entity); } // その他コード }
入力されたデータは
- 商品名→おにぎり
- 値段→ -100
でした。
-100
は異常な入力でしょうか?
違います。
これは妥当な入力です。
プログラムは正常に動作し、products
テーブルのprice
に-100
を登録することでしょう。
しかしこれでは仕様を満たせません。
だからこの入力は 入力ミス です。
エラー画面に遷移し、「正の整数を入力してね」って伝えるために、プログラムで入力チェックし正の整数のみ許可します。UNSIGNED使えや。
「異常な入力」ってなんや?
対する異常な入力とは プログラムが正常に動作しなくなる 入力です。
エンジニアの想定。つまり設計に反する入力です。先程の例をもう一度利用して見ましょう。
例:
あるアプリで商品の値段を登録する画面があります。
商品名は文字列で、値段は正の整数を想定しています。用意されたDBは下記の通りです。
products
id AUTO_INCREMENT name VARCHAR(100) price INT 用意されたプログラムはPHP(Cake風)です。
public function add() { // その他コード if ($this->request->is('post')) { // 登録処理 $entity = $this->products->newEntity($this->request->data); $this->save($entity); } // その他コード }
入力されたデータは
- 商品名→おにぎり
- 値段→ 百
でした。
百
は異常な入力でしょうか?
そのとおりです。
これは 異常な入力 です。
数字のフィールドに文字は入りません。
つまり、 プログラムが正常に動作しません。 type="number"
使えや。
参照系の入力
もうちょっと例を上げてみましょう。
例:
URL:https://hostname/edit/{:id}
を想定した編集ページがあったとしましょう。
products
の内容は下記の通り。
id name price 1 おにぎり 100
- 正常入力は
https://hostname/edit/1
- 入力ミスは
https://hostname/edit/2
- 異常入力は
https://hostname/edit/あ
ですね。
public function edit($id)
{
$product = $this->products->get($id);
if ($this->request->is(['post', 'put', 'patch']) {
// 更新処理
}
$this->set(compact(['product']));
}
https://hostname/edit/あ
でアクセスされた時、きっとView
側でエラー出まくりでしょう。
public function edit($id)
{
// idの数字チェック
$product = $this->products->get($id);
// productの存在チェック
if ($this->request->is(['post', 'put', 'patch']) {
// 更新処理
}
$this->set(compact(['product']));
}
コメントで追加した、id
の妥当性チェックと、product
の存在チェックが編集画面では必須ですね。
意地悪して攻撃してくんな
セキュリティという観点から見てみる
そもそも処理を行う前に入力チェックする理由は何でしょうか?
決まっていますセキュリティを上げ、攻撃の対策するためです。
初心者どころか、プログラムを知らない人でも思いつきそうな答えですね。
- SQLインジェクション
- コマンドインジェクション
- XSS
数々のコマンドやクエリの仕様を利用して悪意ある攻撃をしてくる輩もいるでしょう。
その対策としてエスケープがあります。
これは入力チェックでは無いところに注意してください。(知識つけたら記事にするかもしれません。)
入力の種類
これまでの例から、入力はいくつかの種類で分けられそうですね。
- 妥当な入力
- 正常な入力
- 入力ミス
- 異常な入力
- バグ
- 攻撃
ではこれらの種類から正常な入力のみを許可する入力チェックをしましょう。
入力チェックってなんや?
そもそも前提として入力チェックは入力値が妥当であるかをチェックします。
入力チェックはホワイトリスト?ブラックリスト?
セキュリティを専門とする業界では、妥当であるかを判断するには、ホワイトリストとブラックリストどちらがいいのかという内容で激しい議論になっているそうですが、少なくとも私はホワイトリストだと思っています。
例:
あるテキストフォームは
16文字以上
という入力チェックを行う時、
- 15文字以下をエラー
- 16文字以上を処理
これは難しいですね。どっちもあってる気がします。
では少し複雑にして
例:
あるパスワード入力フォームで
英字と数字を含む16文字以上
という入力チェックを行う時、
- ひらがな、カタカナ、漢字、記号を含まない15文字以下を不許可
- 英字と数字のみの16文字以上を許可
と考える時、わかりやすいのは②です。
「わかりやすいから」が理由?そうではありません。
ホワイトリストのデフォルト状態って何だろう?
なぜホワイトリストなのか?
ホワイトリストのデフォルトは「すべてを許可しない」だからです。最強の入力チェックです。プログラムに何も送らない状態がデフォルトですから。
対するブラックリストは「すべてを許可する」から許可しないものを指定します。これはセキュリティに穴がありそうなのが感覚的にわかりますね。
ではなにをホワイトリストに登録していくべきなんでしょうか?
ホワイトリストに登録するためにバリデーション(検証)する
ここでようやく出てきますね。
皆さんご存知のバリデーションです。
バリデーションってなんでしょうか?辞書を引いてみました。
バリデーション [3] 【validation】
〔確認,承認の意〕
①
妥当性確認。検査・分析の方法やその作業プロセスなどが適切であるか科学的に検証すること。
おお。妥当性確認ですって。
入力の種類でも挙げました。
- 妥当な入力
- 正常な入力
- 入力ミス
- 異常な入力
- バグ
- 攻撃
バリデーションにも色々種類がありそうです。
入力の種類だけ検査にも種類があるでしょう。
バリデーションの種類
結論から言いますと、3種類ありそうです。
- | チェック内容 |
---|---|
入力データバリデーション | プログラムが正常に動作するかどうか。つまり妥当な値かどうかを検証する。 |
ビジネスロジックバリデーション | 仕様を満たすかどうか。つまり正常な値かどうかを検証する。 |
出力バリデーション | 出力した結果、他者に害をなさないか検証する。 |
- 妥当な入力か異常な入力かどうかを「入力データバリデーション」で検証。
- 郵便番号に何故かひらがなが入力されている
- 正常な入力かバグかどうかを「ビジネスロジックバリデーション」で検証。
- 都道府県が「大阪」なのに、郵便番号はどう見ても「東京」の番号
という感じですね。だんだんタイトルへの回答の材料が揃ってきました。
では、出力データバリデーションとはなんでしょうか?
「セキュリティという観点から見てみる」でありましたが、XSSがこれに含まれそうですね。
自分(プログラム)には影響は無いが、利用者に迷惑を掛ける入力データがあります。
もちろんXSSだけでなくAPI利用している場合、変な情報を与えられても困りますしね。
例:
API利用の際、{ "name": "おにぎり", "price": "-100" }
こんなデータ寄越されても「どないせぇっちゅうねん」って感じですね。
なので出力バリデーションは出力した結果、他者に害をなさないか検証するバリデーションです。
入力チェックって何回すればええんや?
何回でもせーや
そもそもセキュアコーディングの定義のうちの1つとしてこういうものがあります。
Implement multiple layers of validation.
(多層のバリデーションを実装していること)
- 入力バリデーションで妥当性のある値を使用してプログラムを落とさない様に
- ロジックバリデーションで仕様上バグのでないシステムに
- 出力バリデーションで他人様に迷惑をかけないように
この多層構造がよりアプリのセキュアにします。
(より正確に言うならセキュアコーディングの要素のうちの1つなので、まだまだ脆弱性はあります。)
Modelでバリデーションするんやで(CakePHP使用)
これは不正解ではありませんが、足りないということですね。
同様にLaravel使いの場合
FormRequestでバリデーションするんやで(Laravel使用)
も足りないということです。
プログラムを動作させる時は
- 入力バリデーション
- ビジネスロジックバリデーション
- データ処理
- 出力バリデーション
- レンダリング
この流れが基本ということになりますね。
最後に
バリデーション。調べてみると奥が深くて脱帽しました。
調べ始めはいろんなブログ記事で バリデーション(の実装)とは??(小並感) みたいな感じでしたが、セキュリティに詳しい方のページに言ったらとんでもない議論の嵐になっていてとても勉強になりました。
7ペイなどの事件もあり、このタイミングでセキュアなことを知れてよかったと思います。
もっともっと、知見のある方いらっしゃるかも知れません。この記事をきっかけにセキュリティ意識が高めれば幸いです。
参考
書かない日記(https://blog.ohgaki.net/)
バリデーション以外にもとても勉強になりました。ありがとうございます。