正規表現でのメールアドレスチェックは見直すべき – ReDoS

(更新日: 2016/10/19)

前のエントリでStackExchangeがReDoSで攻撃されサイトがダウンした問題を紹介しました。少しだけ掘り下げて見たところ、正規表現だけでメールアドレスをチェックしている場合、壊滅的なReDoS(十分短い文字列で指数関数的に実行時間が増加する)が可能なことが判りました。

結論を書くと、正規表現でのメールアドレスチェックは見直すべき、です。(特にRubyユーザー)

追記:影響範囲はメールアドレスチェックに限らないので、正規表現チェックは全体的に見直さないと、どこが脆弱なのか判りません。見直してチェックしたとしても、それが完全であったと保証することは困難です。ネット検索して直ぐに見つかった検索パターンは非常に脆弱であったこと、メールアドレスのマッチパターンは脆弱になりやすい繰り返しの繰り返しが含まれること、これらがあったのでタイトルが「正規表現でのメールアドレスチェックは見直すべき 」になっています。

ReDoSとは?

ReDoSとは正規表現のアルゴリズムを利用してDoS攻撃を行う攻撃です。ReDoSは新しい脆弱性ではありません。10年以上前から知られており、一部のパターンには対策が行われています。しかし、正規表現の仕組み上、完全に対応することはかなり難しいと思われます。実際、StackExchangeは攻撃されました。

PCRE(PHPだとpreg関数)にはバックトラック制限/スタック制限などの対策が取り入れられていますが、前のエントリで紹介したReDoSのように完全に対応するまでには至っていません。

壊滅的ReDoSが可能な正規表現の例

送信されたメールアドレスが妥当な物であるか、チェックするために正規表現を利用している場合は多いです。以下の正規表現はOniguruma(PHPの場合、mb_ereg関数。Rubyなどでも利用されている)の場合に壊滅的なReDoSが可能なパターンです。

(注意:PHPの場合、^、$で文字列の先頭、末尾にマッチします。処理系によっては\A、\zを利用しなければなりません)

テストスクリプト

Rubyスクリプトですが、PHPでもmb_ereg関数はOnigurumaを使っているので同じです1 RubyはFedora 24 x86_64のRuby 2.3.1を利用しました。CPUはCore i7 4770Sなので爆速ではありませんが、遅いCPUでもありません。

これを実行するとメールアドレスっぽい文字列の長さ(ペイロード)と正規表現の実行時間を出力します。

ペイロード(攻撃文字列)と実行時間の変化

スクリプトの実行結果は以下の通りです。

PHPのOnigurumaはこのパターンでは脆弱ではありませんでした。Rubyのバージョンによっては、ReDoSに脆弱だったり、脆弱でなかったりするかも知れません。

 

まとめ

100文字以内でとんでもない実行時間が必要となり、80文字で421秒(7分)、6文字ペイロードが増えると実行時間が約5倍になっています。実行時間が指数関数的に激増していることがわかりました。RFC2821通りにするなら”64バイト@255バイト”の文字列まで受け入れなければなりません。300文字で攻撃したら1日経ってもこの正規表現マッチは終わらないでしょう。

結論としては、メールアドレスチェックは正規表現ではなく、去年の9月のReDoSの回避に書いた通り、文字列を分解してチェックするなどの対策を行うべき、になります。

ReDoSを回避する正規表現を考えることも可能ですが、回避したと思っていても、また別の攻撃パターンが見つかるかも知れません。正規表現エンジンのバージョンが変ると攻撃パターンが変るかも知れません。あれこれ考えるよりCERTトップ10セキュアコーディング習慣の第4位の「簡易にする」(この場合、文字列を分解して簡単な処理にする)を採用する方が良いでしょう。

PHPの場合、PCREも使えます。PCREの場合、バックトラック制限/スタック制限があり、Onigurumaのmb_ereg関数よりReDoSに対して強いです。しかし、StackExchangeの攻撃に使われた文字列の場合は効果がありませんでした。PCREを利用している場合も、文字列の繰り返しマッチを繰り返す(これに限らない方が良いですが。。)ような場合は、文字列を分割してからバリデーションするといった処理にした方が安全です。正規表現でなくても済むなら使わない、も対策になります。

攻撃はメールアドレスにマッチする正規表現に限りません。複雑な正規表現より、文字列を分割し、単純な正規表現を使うように心掛けないとリスクが残ります。解った上でリスクを取る(増やす)のもセキュリティ対応(セキュリティ対策)の1つなので、絶対にしてはならない、とまでは言いません。

 

追記:

無精せずにPHPでも実験してみました。

実行結果

PCREがFALSEを返すのは予測通り2ですが、PHPの古いOnigurumaはこの攻撃に脆弱ではありませんでした!Onigurumaをいつバージョンアップするか分らないですし、PCRE含め3、どのバージョンがどのパターンに脆弱か判らないということになります。転ばぬ先の杖(=セキュアプログラミング/セキュアコーディング/防御的プログラミング)が役に立ちます。


  1. 同じかと思っていましたが古いOnigurumaの方が脆弱でなかったです。どのパターン、どの正規表現エンジンで攻撃できるか、判別するのは結構難しいようです。たまたま連続して攻撃可能なパターンと環境を見つけていたようです。 
  2.  preg_matchはマッチした数を返すので、マッチしない場合は0を返す。 
  3. 末尾直前の複数文字にマッチするパターンの場合、PHP 5.6のPCREはクラッシュしてしまいました。PHP 7.1-devのPCREはクラッシュせず実行時間が長くなりました。PCREもバージョンによって動作が異ることが結構あります。 

Comments

comments

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です