2018/08/13)Rubyデフォルトの正規表現では^は行の先頭、$は改行を含む行末にマッチします。PHPのPCREとmbregexでは^はデータの先頭、$は改行を含む行末にマッチします。この仕様の違いはデータのバリデーションに大きく影響します。続きの解説になります。こちらのエントリもどうぞ。関連なぜ^と$が行の先頭と行の末尾にマッチするのか?そもそも正規表現はテキスト検索を行うステートマシーンとして設計されました。通常テキストには改行があります。特定の行に一致するかどうかテストするように設計するのが自然です。この為、正規表現の^と$は行の先頭と末尾にマッチするように設計されたと考えられます。正規表現をバリデーションに利用することは可能です。そもそもは正規表現に一致する「テキスト」を検索する為に作られた物なので、バリデーションに利用するには正規表現の仕様を理解する必要があります。Bar\n12345\n<script>alert('XSS')</script>\nのような入力を数値のみと判断してしまいます。^、$を使うPHPの正規表現はほぼ安全なぜ安全なのかは明らかです。PHPの正規表現(注:POSIX正規表現を除く)^はデータの先頭にマッチします。$は改行を含むデータの終端にマッチします。Bar”になり数値としてマッチしません。<script>alert('XSS')</script>\n012312345\n<script>alert('XSS')</script>\nがデータとして渡された場合、終端となる行にPHP<script>alert('XSS')</script>\n012<script>alert('XSS')</script>\nが含まれているので数値としてマッチしません。^\d+$を利用した場合、マッチする文字列はPHP数字012数字またはPHP数字\n012数字\nになります。(他の種類の改行文字は省略)つまり、PHPの場合はPHP正規表現にマッチするデータ012正規表現にマッチするデータまたはPHP正規表現にマッチするデータ\n012正規表現にマッチするデータ\nにマッチします。PHPの場合はほぼ安全である事が分かります。任意のデータ01234任意のデータ\n正規表現にマッチするデータ\n任意のデータにマッチします。「任意のデータ」はJavaScriptインジェクション、SQLインジェクションなど、全てのインジェクション攻撃に利用可能である可能性があります。攻撃者にとっては非常に便利な状況です。PHPとRuby「PHPで^$を利用しても”ほぼ”安全」と書いている理由の解説になります。誤って終端に改行が含まれてしまった場合、セキュリティ上のリスクはゼロではありません。HTTPヘッダーインジェクション(HTTPレスポンススプリッティング)やメールヘッダーインジェクションは不正な改行文字の挿入により攻撃が行われます。PHPの場合、HTTPヘッダーは直接出力できず必ずheader関数を利用して出力しなければなりません。header関数にはHTTPヘッダーインジェクション対策が行われているので安全に出力されます。mail/mb_send_mail関数も同様にメールヘッダーインジェクション対策が行われています。メール送信にパイプなどを利用していない限り、メールヘッダーインジェクションからも保護されています。Rubyの場合、HTTP/メールヘッダーが保護されるかどうかはフレームワーク/プログラマ任せになります。この他に出力データの形式が改行で壊される問題が発生するケースも考えられます。出力先の入力形式に合った出力を行わなかった場合、セキュリティ問題ではなくてもバグと言えます。PHPとRubyの違いPHPの場合、^$をバリデーション用の正規表現に使ってしまう間違いは「単純なバグ」と言えまっす。Rubyの場合は「致命的なセキュリティバグ」です。実は古いmbregex(忘れるくらい古い4.xの時代の話です)はRubyと全く同じ動作をしていました。この動作はセキュリティ上の問題であるとして、現在の^は\A(データの先頭)と同じ、$は\Z(改行を含む終端にマッチ)に変更されました。昔の話なので議論の内容を記憶違いしているかも知れませんが、\z(改行を含まない終端にマッチ)を利用しなかった理由は以下だったと思います。PCREの非マルチラインモードと同じ動作である(シングルラインモードがデフォルト)終端の改行のセキュリティリスクは低いPCREの非マルチラインモードと同じ動作であることは、mbregexをPCREの代わりに利用するためには重要です。here}}のようなコード中のpreg_match関数をmb_ereg関数で置き換えようとした場合、行末の改行を含む終端にマッチしないと、単純にmb_eregに置き換えれない問題が生まれます。この為、mb_ereg/eregi関数の$は\zではなく\Zにマッチする仕様になっています。まとめRubyでは^、$を使ったバリデーションは致命的なセキュリティバグPHPでは^、$を使ったバリデーションは単純なバグバリデーションしたデータを例外的な使い方をしない限り、PHPではセキュリティ問題になりません。バリデーション後のデータに改行が含まれてはならない場合、バグであることには変わりません。他のプラットフォームで同じ正規表現を使う可能性なども考えると^、$ではなく\A、\zを利用した方が良いでしょう。正規表現の^、$、\A、\z、\Zの仕様はどちらかというと常識的な知識かと思っていました。正規表現の解説文などで説明されていないことが多いことが原因で、あまり広くは知られていないようでした。出力先の入力(エスケープ)仕様と同様に、セキュリティに関連する機能の仕様は良く理解しておかないとセキュリティ上の問題になる、という良い例かも知れません。最後に実際の動作例を書いておきます。起きてはならない場合、トランザクションはシリアライザブルマグネット式のスマホホルダーは大丈夫/十分なのか?PHPのOpenSSL関数を利用して暗号化する例2要素認証のTOTPとHOTP、どちらがより安全か?完全なSQLインジェクション対策JSONPは危険なので禁止インジェクション攻撃は3種類ある紹介エレクトロニック・サービス・イニシアチブのWebシステムのコンサルタントのブログです。PHPの開発、PHP4.x/5.xレガシーPHPセキュリティパッチサービスなどを行っています。ブログのサブタイトルを「書かない日記」としているのは、ブログを始めた時にあまり書かないつもりで始めたのでこのサブタイトルになっています。yohgaki@ohgaki.net私が記述したブログ中のコードは記載が無い場合はMITライセンスです。その他のコードは参考リンクなどのライセンス情報を参照ください。