====== 正規表現に用いられる入力データにメタ文字がないことを確認する ====== 正規表現やその一部に外部入力を用いると、正規表現を用いたDoSや情報漏洩の原因となる場合がある。正規表現を用いた検索を行う場合、入力データにメタ文字が無いことを確認する。 ===== 正規表現インジェクションの例 ===== システムのイベントを記録したログがあり、自身のユーザーIDのログは参照できるとする。 14:14:15 bob[14] Login from IP 10.10.10.123 14:14:30 tom[14] Login from IP 10.10.20.234 14:15:27 bob[34] Update password 14:15:27 tom[38] Update email from tom@example.com to tom@example.org 14:18:12 bob[15] Logout 14:19:43 bob[15] Logout システムにbobがログインしている場合に、自身のログのみを検索する正規表現は以下のような物となる。 (.*? +bob\[\d+\] +.*.*) はユーザーが入力する検索条件となる。この場合、攻撃者が .*)|(.* を入力した場合、検索用の正規表現は次のようになり (.*? +bob\[\d+\] +.*.*)|(.*.*) bob以外のログエントリ全てを取得可能となる。 ===== 非適合コード例 ===== 非適合なコードは信頼できないユーザーに対して正規表現となる文字列を許可し正規表現検索を実行する。 **非適合コード** function search_log($user, $pattern) { if (!strlen($pattern)) { return FALSE; } $regex = '(.*? +'. $user .'\[\d+\] +.*'. $pattern .'.*'; $log = file_get_contents('/path/to/logfile'); preg_match_all($regex, $log, $matches); return $matches; } $log = search_log($_SESSION['username'], $_POST['pattern']); var_dump($log); このコードは攻撃者に正規表現インジェクションを許す。 ===== 適合コード例(Whitelisting) ===== 検索パターンにアルファベットのみを許可する。 **適合コード** function search_log($user, $pattern) { if (!strlen($pattern)) { return FALSE; } $regex = '#(.*? +'. $user .'\[\d+\] +.*'. $pattern .'.*#i'; $log = file_get_contents('/path/to/logfile'); preg_match_all($regex, $log, $matches); return $matches; } // Length check if (mb_strlen($_POST['pattern'], 'UTF-8') < 1) { throw new Exception('Validation error: Too short UTF-8 string'); } if (mb_strlen($_POST['pattern'], 'UTF-8') > 30) { throw new Exception('Validation error: Too long UTF-8 string'); } // $_POST['pattern'] is validated by JavaScript at client side and // must not contain other than alphabet chars. if (strlen($_POST['pattern']) !== strspn($_POST['pattern'], 'abcdefghijklmnopqrstuvwxyz')) { throw new Exception('Validation error: pattern may contain alphabet chars only.'); } $log = search_log($_SESSION['username'], $_POST['pattern']); var_dump($log); ===== 適合コード例(Detect Meta Character) ===== preg_quote()はPCRE正規表現のメタ文字をエスケープする。エスケープされた場合、元の文字列より長くなることを利用してバリデーションを行う。 **適合コード** function search_log($user, $pattern) { if (!strlen($pattern)) { return FALSE; } $regex = '#(.*? +'. $user .'\[\d+\] +.*'. $pattern .'.*#i'; $log = file_get_contents('/path/to/logfile'); preg_match_all($regex, $log, $matches); return $matches; } // Length check if (mb_strlen($_POST['pattern'], 'UTF-8') < 1) { throw new Exception('Validation error: Too short UTF-8 string'); } if (mb_strlen($_POST['pattern'], 'UTF-8') > 30) { throw new Exception('Validation error: Too long UTF-8 string'); } // Char encoding check if (mb_check_encoding($_POST['pattern'], 'UTF-8')) { throw new Exception('Validation error: Invalid UTF-8 encoding'); } // Allow only following char patterns if (!mb_ereg('\A[\x20-\x7e\x80-\xFF]+\z', $_POST['pattern'])) { throw new Exception('Invalid char', $_POST['pattern']); } // $_POST['pattern'] is validated by JavaScript at client side and // must not contain meta chars. if (strlen($_POST['pattern']) !== strlen(preg_quote($_POST['pattern'], '#')) { throw new Exception('Validation error: pattern may contain regex meta chars.'); } $log = search_log($_SESSION['username'], $_POST['pattern']); var_dump($log); ===== 適合コード例の解説 ===== 例示した適合コードは非常に単純であるため、出力時(preg_match_all()の呼び出し)のバリデーションを省略している。コードが複雑で入力となる箇所が多い場合、入力バリデーション漏れが発生する可能性が高くなる。ある程度複雑なプログラムの場合は入力と出力の両方でバリデーションを行う。 ログフォーマットが変更された場合に意図せず秘密にすべきログが取得できてしまう可能性がある点にも注意が必要である。 ユーザー定義正規表現を許可する場合、ReDoSに対してかなり脆弱になる。 ===== 例外 ===== * 正規表現を利用するユーザーが権限を持つシステム管理者のみ限定されている場合、などそもそも検索対象全てにアクセス権限を持っている時。 * 検索対象のデータが機密情報でない場合。 ===== リスク評価 ===== 正規表現の記述をユーザーに許すと、正規表現のアルゴリズムを使ったReDoSを許したり、意図した文字列以外にもマッチさせ不正に情報を取得可能となる場合がある。 ^ Rule ^ Severity ^ Likelihood ^ Remediation Cost ^ Priority ^ Level ^ | | medium | unlikely | medium | P4 | L3 | ===== 関連ガイドライン ===== * ===== 参考文献 ===== * http://blog.ohgaki.net/avoiding-redos * https://www.securecoding.cert.org/confluence/display/java/IDS08-J.+Sanitize+untrusted+data+included+in+a+regular+expression *