ウェブサイトのセキュリティ:入力バリデーション、正規表現
安全なウェブサイトを作るためにネット上の情報を漁っていると、「入力バリデーション」と「パスワード」についてクリティカルなものがあったりする。
今回はこのあたりについて自分の考えをまとめておく。
入力バリデーション
入力のバリデーションはSQLインジェクションの根本的な対策にはならない。SQLインジェクションへの根本的な対策は、プリペアードステートメントを利用すること。
クライアントサイドの入力バリデーションは、ウェブサイトの利用者へ「入力値の条件が満たされていない理由」を親切丁寧に伝えるために過ぎない。攻撃者はその気になればHTTPリクエストのすべての内容を生成することができるということを理解しておかなければならない。
最強のバリデーション
selectタグのoption項目のように、クライアントからの入力値として取りうる値が限定的であれば、
- 入力値を文字列へ強制的に変換した後に、
- 予め適切に設定した最大文字列長以下の長さであることを確認し、
- 「if,caseで型を含めて等しいかを判定して分岐」するか、
- または、「あらかじめ用意した連想配列(辞書)のキーとして存在するかをチェック」する
という方法が最強と思われる。
現実的な方法
お使いのフレームワークが用意しているバリデーション機構を利用してください。
その正規表現、大丈夫?
一つの正規表現で無理やり入力値のバリデーションを実現することは避けた方が良い。
そもそも本来の正規表現・正規言語は有限オートマトンでしかないので、可読性、保守性、ReDoS対策の観点から、できるだけシンプルな造りになるように処理をステップごとに分割し、一つ一つのコードを単純化することを検討しよう。
メールアドレスのバリデーションを自前の一行正規表現で済ませてしまうのはやめよう。
ちょっとまって、その正規表現、本当に本当に大丈夫?
入力文字列の長さを調べたいのならば、例えばPHPであればstrlenで判定できる。
半角英数字以外の文字が含まれていないかチェックしたいのであれば preg_match("/[^0-9a-zA-Z]/",$input)
でチェックしよう。
新しく設定しようとしているパスワードが条件を満たしているか判定したいだって? 以下の処理をシーケンシャルに実行して、条件に反していたらその時点でリジェクトすればオッケーだと思うよ。
- 「受け入れ可能なcharacter以外が入っていないかを判定」
- 「文字列の長さは条件の範囲内かを判定」
- 「必要な文字種類がすべて入っているかを分割して判定」
- 「文字の種類数が条件を満たしているかを判定("AAAAAaaaaa11111"などの弱いPWへの対策)」
- 「クラックされやすいパスワードリストに載っていないかを判定」
しつこくてごめんね。その正規表現、本当にほんと~~に大丈夫?
正規言語の限界、イプシロン遷移、バックトラック、DFAとNFAについて考えたことがない、「そんな単語聞いたことも無い」という状態であれば、頑張って複雑な一行の正規表現で済ませてしまうのは絶対にやめよう。
「0回以上繰り返し」の量指定子は地雷です。*
と{0,N}
が該当しますね。
例えばこんな短い正規表現と入力でReDoSが成立します: https://twitter.com/adokoy0001/status/824580874704293889
バックトラックが何なのか分かっていないのであれば、「極力正規表現を使わない方法」を模索しましょう。
JSONのバリデーション、カッコの開き・閉じの対応チェック、回文(逆から読んでも同じ文章)の判定、任意の1以上の整数n
に対する a^n b^n
の判定、などなど、有限オートマトンであるところの正規表現ではスタックがないので不可能です。
そういうのはプッシュダウンオートマトンであるところの文脈自由言語とかならできます。
正規表現を積極的に使うべきシーンは、文字列の置換やパターンマッチした部分を抽出する処理などでしょう。
それでも正規表現を使いたい場合
どうしても入力バリデーションを正規表現一発でやりたいのであれば、以下のURLにある正規表現のやばい挙動を完全に理解した上でやりましょう。
やばい挙動: https://regex101.com/r/enRb1V/1/debugger
やばい挙動その2: https://regex101.com/r/GM8BLm/2/debugger
最後の量指定子{1,3}
を、{1,50}
にしてみよう。
やばい挙動その3: https://regex101.com/r/GM8BLm/3/debugger
おわかりいただけただろうか。さらに50から70とかに増やすとタイムアウトになったりする。
ここで重要なのは以下である。
- 数字を増やすと指数関数的にステップ数が増える
- 短い入力でもバックトラック地獄は起こりうる
- 短くシンプルな正規表現でも危ないものは危ない
- 量指定子
*
を使わなくても、イプシロン遷移があれば危ない
この投稿が何かのお役に立てれば幸いである。