ReDoSとは何か?正規表現によるサービス拒否攻撃の基本概要

目次
- 1 ReDoSとは何か?正規表現によるサービス拒否攻撃の基本概要
- 2 ReDoSの発生原因: 正規表現マッチ処理に潜む性能問題と脆弱性要因
- 3 正規表現マッチの仕組みと実装方法: DFA型とVM型エンジンの違い
- 4 実際のReDoS攻撃事例と被害: Stack OverflowやCloudflareでの障害例
- 5 脆弱な正規表現パターンの具体例: ReDoSを招く危険な書き方
- 6 ReDoS脆弱性の検出・診断方法: 静的解析ツールや専門ツールの活用
- 7 ReDoSへの基本的・有効な対策: タイムアウト設定から安全なエンジン活用まで
- 8 入力文字列やデータ長に関する制限の重要性: ReDoSリスク低減の基本策
- 9 開発者が注意すべきポイントと落とし穴: ReDoS防止の心得
- 10 最新の脅威動向と今後の展望: ReDoS脆弱性の現状と未来への対策
ReDoSとは何か?正規表現によるサービス拒否攻撃の基本概要
まずはReDoS(正規表現によるサービス拒否攻撃)とは何か、その基本概念と特徴について整理します。
正規表現によるサービス拒否攻撃(ReDoS)の定義と基本概要
ReDoS(Regular Expression Denial of Service)は、正規表現のマッチ処理において意図的に極端な遅延を引き起こすことでサーバーの資源を枯渇させ、サービスを拒否状態に陥らせる攻撃です。正規表現は文字列パターンを効率よく表現・照合する手段ですが、その実装によっては特定の入力で処理時間が爆発的に増大する場合があります。ReDoS攻撃では攻撃者がその弱点を突き、通常では考えられないほど長時間かかるパターンマッチを引き起こすことで、対象システムの応答を停止させます。
ReDoSという用語の由来と歴史
「ReDoS」という用語はOWASPによって2012年頃に定義され、正規表現の性能劣化を悪用したDoS攻撃を指すものとして使われ始めました。2010年代半ば以降、この脆弱性は実際のサービス障害事例(前述のStack OverflowやCloudflareなど)によって広く知られるようになりました。また近年、オープンソースソフトウェアの脆弱性データベースでもReDoSに関連する報告が増加しており、セキュリティコミュニティにおける注目度が高まっています。
他のDoS攻撃との違い: アルゴリズム悪用型の特徴
ReDoSは、膨大なトラフィックを送りつける典型的なDoS攻撃とは性質が異なります。通常のDoS/DDoS攻撃では大量のリクエストやデータを送り込む必要がありますが、ReDoS攻撃ではわずか1件の悪意あるリクエストでもシステムを過負荷状態に追い込むことが可能です。これは、攻撃者が入力文字列を工夫することで正規表現エンジンを最悪の実行パスに導き、少ない入力で不釣り合いに大きな計算コストを発生させられるためです。言い換えれば、ReDoSはアルゴリズムの複雑性を突いた攻撃であり、リソース消費効率が非常に高い非対称型のDoSである点が特徴です。
システムへの影響: サーバー負荷とサービス停止
ReDoS攻撃が成功すると、対象のシステム資源(CPUやメモリ)が正規表現処理に占有されてしまい、他の処理に割く余裕がなくなります。その結果、システムは正常なリクエストに応答できなくなり、サービスが著しく遅延したり完全に停止したりします。特にWebアプリケーションでは、正規表現の処理がブロッキングで実行される場合、単一の悪意ある入力が他の全ユーザーへの応答を一斉に滞らせる可能性があります。実際にサービスダウンが発生すれば、直接的なビジネス損失やユーザからの信頼失墜、ブランドイメージの毀損といった深刻な影響が生じます。
ReDoSの基本原理: 正規表現処理の落とし穴
なぜ一つの正規表現がこれほど深刻な問題を引き起こすのでしょうか。その背景には、正規表現エンジンの実装上の落とし穴があります。多くの正規表現ライブラリはバックトラッキング方式でパターンマッチを行っていますが、この方式では複雑なパターンに対して試行回数が爆発的に増える可能性があります。つまり、正規表現の書き方次第では、非常に些細な入力に対してエンジンがほぼ無限に近い組み合わせを検証してしまうのです。ReDoS脆弱性の根幹には、この正規表現処理の特性が横たわっており、次節では詳しくその原因を見ていきます。
ReDoSの発生原因: 正規表現マッチ処理に潜む性能問題と脆弱性要因
このセクションでは、ReDoSがなぜ発生するのか、その技術的な原因を解説します。正規表現エンジンの仕組みと、特定のパターン構造が引き起こす計算量の爆発について見ていきましょう。
バックトラッキングによる計算量爆発とReDoS発生の仕組み
多くの正規表現エンジンはNFAベースのバックトラッキングアルゴリズムで動作しており、これがReDoSの根本原因です。バックトラッキングでは、パターン中の分岐や繰り返しに対し、あらゆる可能性を試す探索が行われます。計算量の爆発は、この探索空間が指数関数的に膨れ上がった場合に発生します。つまり、ある正規表現でマッチングの組み合わせ試行回数が入力の長さに対して爆発的に増大するとき、そのパターンはReDoSを引き起こす脆弱性を抱えていることになります。
指数時間パターンと二乗時間パターン: 2種類の性能劣化ケース
バックトラッキングの計算量増加には主に2種類のパターンがあります。一つは入力長に対して指数関数的(例:2N)の時間がかかるケースで、量指定子のネストなどによって生じます。この場合、ごく短い入力でも劇的な遅延が発生し得ます。もう一つは多項式時間(典型的にはN2)のケースで、一見指数ほどではありませんが、入力が大きくなると深刻な性能劣化を招きます。実際、先述したStack OverflowとCloudflareの障害を引き起こしたパターンは後者の二乗オーダーの例でした。指数オーダーほど短い入力で異常な挙動を示すわけではないので、開発中のテストでは見つけることが難しいという厄介さもあります。
最悪ケース入力が引き起こす過剰なバックトラック
ReDoS攻撃では、ターゲットとなる正規表現に対して最悪ケースの入力を見つけ出すことが鍵となります。典型的には、「ほとんどマッチするが最終的に失敗する」ような文字列がエンジンに最大の負荷をかけます。例えば、^(a+)+$
というパターンに対して「aaaaaaaaab」(末尾に不一致の文字“b”を付けた入力)を与えると、マッチしないと判断するまでに膨大なバックトラックが発生します。このように、正規表現の弱点を突いた入力を送り込むことで、エンジンを意図的に最も非効率な経路に誘導し、極端な処理遅延を引き起こすのです。
正規表現パターンの曖昧性が生む脆弱性
正規表現の曖昧な構造がReDoS脆弱性を生む直接の要因です。曖昧さとは、一つの入力に対して複数のマッチ方法が存在し得ることを指します。例えば、^(a|b|ab)*$
では、文字列”ab“の処理において「a
」+
「b
」の組み合わせ方が複数通りあり、それが計算量爆発を招く原因となっています。同様に、繰り返しが冗長に重ねられたパターンや、選択肢同士が部分的に重複するパターンでは、エンジンがどの経路をとるか非常に不明確で、その非決定性がバックトラッキングを際限なく増大させてしまいます。
開発時に見落とされる理由と判別の難しさ
開発者がReDoS脆弱性を見落としがちなのは、その発見が容易でないためです。多くの場合、脆弱な正規表現も通常の想定入力では問題なく動作してしまうため、単体テストやレビューでは性能問題に気付けないことがあります。さらに、正規表現の構文だけを目視でチェックしても、前述のように曖昧な構造は一見判別しにくいのが現状です。よく言われる「量指定子のネストを避ける」といった指針も万能ではなく、ネストがなくとも脆弱なケースがあります。開発者にとっては、ReDoSは静かに潜む落とし穴であり、意識的な対策やツールの助けなしには検知が難しい脆弱性と言えます。
正規表現マッチの仕組みと実装方法: DFA型とVM型エンジンの違い
ここでは、正規表現エンジンの内部的な仕組みと実装方式について説明します。DFA型とNFA型(バックトラッキング型)の違いを理解することで、ReDoS脆弱性がどのようにエンジン性能に関係するかが見えてきます。
正規表現エンジンの二種類: DFA型とNFA(バックトラッキング)型
正規表現エンジンの実装方式には、大別してDFA型(Deterministic Finite Automaton型)とNFA型(Non-deterministic FA型、バックトラッキング型)の2種類があります。DFA型エンジンは正規表現パターンを決定性有限オートマトンに変換してマッチングを行う方式で、入力長に対して厳密に線形時間で処理が完了します。一方、NFA型エンジン(バックトラッキング実装)は、パターンを非決定性オートマトンとして解釈し、仮想マシン上でパターンの分岐や繰り返しを試行錯誤しながらマッチを行います。RubyやPerl、Python、JavaScript、Javaなど多くの言語の正規表現実装はこちらの方式を採用しており、柔軟で高速な場合が多い反面、最悪ケースで非常に非効率になる可能性を孕んでいます。ReDoS脆弱性は主にこのNFA型(バックトラッキング型)のエンジンで問題となります。
DFA型エンジンの特徴: 線形時間で安全なマッチ処理
DFA型の正規表現エンジンは、線形時間でパターンマッチが完了する点が最大の特徴です。入力文字列を1文字ずつ読み進め、各状態遷移が決定的に定まるため、どんな入力でも処理時間は文字列長に比例する上限になります。したがってDFA型では原理的にReDoSは発生しません。Googleが提供する正規表現ライブラリRE2や、Go言語およびRust言語の標準正規表現実装がこの方式を採用しており、安全性が高いとされています。ただし、DFA型は後方参照など一部の高度な正規表現機能をサポートできない制約があり、また複雑なパターンではオートマトンの状態数が膨大になるためメモリ消費や構築時間の面で課題がある場合もあります。それでも、可能な限りDFA型エンジンを使用することはReDoS対策として非常に有効です。
NFA(バックトラッキング)型エンジンの特徴: 柔軟だが性能リスク
NFA(バックトラッキング)型のエンジンは、正規表現の柔軟性と表現力の高さが特徴です。後方参照や先読みなど高度な機能にも対応し、実装も比較的簡潔であるため、Perl・Ruby・Python・JavaScript・Javaなど多くの環境で採用されています。通常の入力に対しては高速に動作しますが、そのアルゴリズム上、最悪の場合に指数的な時間を要するパターンが存在し得ます。バックトラッキング型では、文字列のある位置でマッチに失敗すると前の分岐点まで戻って別のパスを試行するため、パターンに曖昧さがあると爆発的な組み合わせ探索に陥る恐れがあります。このようにNFA型エンジンは利便性と引き換えにパフォーマンス上のリスクを内包しており、ReDoS脆弱性はまさにこの点を突くものです。
バックトラッキングの動作原理: 分岐と繰り返しによる探索
バックトラッキングエンジンの動作原理をもう少し詳しく見てみましょう。マッチ処理中、正規表現に選択肢(|)が現れると、エンジンはまず一つ目のパターンを試し、途中までマッチしてから失敗した場合に直前の分岐点まで戻って別の選択肢を試します。同様に、繰り返し(量指定子)では可能な限りマッチさせてから、行き詰まると繰り返し回数を減らす方向にバックトラックします。例えば、(a|b)*c
というパターンを入力“aaaaab”に対して評価すると、最後のc
でマッチ失敗した時点で、それまでマッチさせていたa|b
の部分を遡って別の分割を試す、といった処理が行われます。こうした全探索に近い試行はパターンが複雑になると指数的に増大する可能性があり、ReDoSの温床となるのです。
Catastrophic Backtracking(バックトラック爆発)のメカニズム
Catastrophic Backtracking(バックトラックの爆発)とは、バックトラッキング処理が事実上無限ループに近い状態に陥る現象を指します。脆弱なパターンでは、エンジンが何十万、何億というパスを試行し続け、実行時間が秒単位から分・時間単位にまで延びてしまう場合があります。例えば、(x+)+y
のようなパターンに対して攻撃的な入力を与えると、エンジンはy
を見つけようと大量のバックトラックを繰り返し、計算量爆発が発生します。最新のエンジン実装では極端な場合に対して内部でキャッシュを使った最適化を行うものもありますが、それでもReDoSを完全には防ぎきれないことが知られています。Catastrophic BacktrackingはReDoSの中核にある現象であり、正規表現エンジンの構造上避けがたい落とし穴です。
実際のReDoS攻撃事例と被害: Stack OverflowやCloudflareでの障害例
ここでは、実際に報告されたReDoS攻撃の事例とその被害について紹介します。大規模サービスがどのように影響を受けたかを学び、ReDoSの脅威を具体的にイメージしましょう。
Stack Overflowのダウン事例(2016年): 原因となった単純な正規表現
2016年6月、世界的なQ&AサイトであるStack OverflowがReDoS攻撃により約34分間ダウンする事故が発生しました。原因究明の報告によれば、問題となったのはユーザー入力文字列の末尾にある空白を取り除くために使用されていたごく単純な正規表現でした。攻撃者(もしくは偶然悪意のないユーザー)がこの正規表現に対し、最悪ケースの入力を与えた結果、サーバーの処理が極端に遅延し、負荷監視のヘルスチェックに失敗してサービス停止に至ったとされています。運営チームは対応として、その機能を正規表現ではなく通常の文字列操作に置き換えることで問題を解決し、再発を防止しました。
Cloudflare障害(2019年): WAFの脆弱な正規表現による影響
2019年7月には、著名なCDNプロバイダのCloudflareがReDoS攻撃により約27分間のサービス停止に陥りました。この原因は、CloudflareのWebアプリケーションファイアウォール(WAF)に設定されていた正規表現ルールに脆弱なパターンが含まれていたことです。攻撃者が巧妙な入力を送り込んだ結果、WAFの正規表現マッチ処理が暴走し、全体のサービスに障害が発生しました。Cloudflare社は、このインシデントを受けてWAFで使用している正規表現エンジンを従来のバックトラッキング型からDFA型エンジン(例:RE2)に置き換えることで再発防止を図りました。この例は、セキュリティ対策そのものに使われる正規表現においてもReDoSのリスクが潜んでいることを示しています。
その他の報告例: OSSライブラリにおけるReDoS脆弱性の発見
近年ではオープンソースソフトウェアの正規表現に起因する脆弱性報告も増加しています。例えば2024年11月には、Ruby言語の標準XML解析ライブラリであるREXMLにReDoS脆弱性(CVE-2024-49761)が発見され、特定のXMLを解析した際にシステムが停止するおそれがあるとしてアップデートが公開されました。このように、Webアプリケーションだけでなくライブラリ内部の正規表現実装にもReDoSのリスクが潜んでおり、幅広い範囲で注意が必要です。実際、脆弱性データベース(例えばCVEやJVN)には、様々なソフトウェアにおけるReDoS関連の報告が蓄積されています。
被害の範囲: ReDoSによるサービス停止の深刻さ
Stack OverflowやCloudflareの例では数十分の停止に留まりましたが、ReDoS攻撃は検知と対処が遅れればより長時間のサービス停止を引き起こしかねません。特に、自動リトライなどが行われる環境では負荷が雪だるま式に増大し、システム全体がダウンするリスクがあります。サービスの種類によっては、数分の停止でも利用者に大きな影響を与え、ビジネスに深刻な損害をもたらす可能性があります。また、一部のケースでは、正規表現によるCPU消費が他の重要なプロセスを妨げ、データ処理の遅延や障害連鎖を引き起こすことも考えられます。このようにReDoSによる被害は技術的な問題に留まらず、サービス提供者の信用や経済的損失に直結する重大なものです。
教訓: 事例から学ぶ正規表現運用上の注意点
これらの事例から得られる教訓は、正規表現を用いる際にはその最悪性能を常に念頭に置く必要があるということです。Stack Overflowのケースではごく単純な空白除去用のパターンが問題を起こし、Cloudflareのケースではセキュリティ対策用の正規表現が裏目に出ました。つまり、どんな場面の正規表現であってもReDoSのリスクを考慮すべきなのです。開発者は、正規表現を実装する際に性能面で疑わしい構造(曖昧な繰り返しや複雑な分岐)がないか注意深くレビューし、必要であれば前もって安全な代替手段(後述する対策)を検討することが重要です。また、異常な入力に対するテストを行い、想定外の遅延が発生しないことを確認するなど、防御的プログラミングの姿勢が求められます。
脆弱な正規表現パターンの具体例: ReDoSを招く危険な書き方
では、どのようなパターンがReDoSを引き起こしやすいのでしょうか。典型的な脆弱な正規表現パターンの例をいくつか挙げます。
ネストした繰り返し(量指定子)のパターン
ネストした繰り返しとは、量指定子(+
, *
など)が入れ子構造で使用されているパターンを指します。例えば、(\d+)+
や(.+)+
のようにプラス記号を重ねたものが該当します。これらのパターンでは、一度のマッチの中で同じ文字列部分を繰り返し部分が複数の方法で消費できるため、マッチ失敗時に試行すべき組み合わせの数が指数的に増加します。結果としてバックトラッキングが爆発的に発生し、極端な遅延を招く危険があります。この種のネスト構造はCatastrophic Backtrackingの典型例であり、正規表現の書き方として避けるべきパターンです。
冗長な繰り返し(重複する量指定子)のパターン
一見ネストには見えなくても、同じ繰り返しを連続させたパターンも危険です。例えば、^aa$
のように同じ文字に対するが二つ並ぶパターンでは、冗長な繰り返し部分があるためにバックトラッキングが過剰に発生します。このケースでは計算量は主に二乗時間で増加し、大きな入力で著しい遅延を招きます。Stack OverflowやCloudflareで問題となった正規表現も、実はこうした冗長な繰り返しを含む構造でした(空白文字のトリムに
\s\s*
が使われていた等)。開発者は、一見無害に見える重複した量指定子にも注意を払う必要があります。
部分一致が重複するオルタネーション(曖昧な分岐)のパターン
正規表現のオルタネーション(選択肢)において、各選択肢のパターンが部分的に重複している場合も危険です。典型例は(a|ab)*
のようなケースで、この例では「ab
」という文字列をマッチさせるのに「a
」+「b
」の二通りの解釈が可能であるため、エンジンがどちらを取るかでバックトラッキングが大量発生します。同様に、選択肢の一方が他方の接頭辞になっているようなパターンは曖昧さを含み、計算量の爆発を招きやすいと言えます。正規表現を書く際には、可能な限りこのような曖昧な分岐構造を避け、選択肢は互いに明確に排他的になるよう心がけるべきです。
無意味な選択肢を含む冗長な正規表現
冗長な正規表現、つまり意味的に同じケースを複数表現してしまっているパターンも性能上問題を引き起こします。例えば、(a|a)
のように同一の選択肢を繰り返している場合、理論上は単なるa
と等価ですが、バックトラッキングエンジンはこれを別々の選択肢として扱うため無駄な探索が発生します。結果として処理時間は通常よりも大幅に増大し得ます。このような冗長表現はコードの可読性を下げるだけでなく、パフォーマンス面でもリスクとなるため避けるのが望ましいでしょう。なお、現代的なエンジンの一部はこの種の単純な冗長性を内部で最適化してくれる場合もありますが、過信は禁物です。
一見無害な正規表現に潜む落とし穴
OWASPでは、ReDoSを引き起こし得る正規表現パターンをEvil Regex(悪意のある正規表現)と呼び、その典型的な構成要素として「繰り返しの中にさらに繰り返しを含むグループ」「大きな入力に対して非常に多くのバックトラックを誘発する構造」などを挙げています。しかし実際には、これに限らず一見無害なパターンが思わぬ組み合わせ爆発を起こすこともあります。例えば、長い入力を扱う単純な検証用正規表現でも、偶然の組み合わせでReDoS脆弱性を露呈するケースがあります。開発者は、「自分の書いた正規表現は大丈夫」と思い込まず、どんなパターンにも潜在的な落とし穴がないか注意を払う必要があります。
ReDoS脆弱性の検出・診断方法: 静的解析ツールや専門ツールの活用
ReDoS脆弱性を早期に発見するためには、ツールを活用した検出・診断が有効です。ここでは、開発段階で使える静的解析や専門ツール、テスト方法について紹介します。
静的解析ツールによる検出: SonarQubeやESLintプラグインの活用
正規表現のReDoS脆弱性は、コード解析ツールの静的解析によって検出できる場合があります。近年、主要な静的解析ツールには危険な正規表現パターンを検知するルールが組み込まれつつあります。例えば、JavaScript/TypeScriptの開発ではESLintにReDoS検出用のプラグイン(eslint-plugin-redos)が利用可能で、コード中の正規表現リテラルを解析して脆弱なパターンがないかチェックできます。また、品質検査プラットフォームのSonarQubeにも類似の検出ルールがあり、正規表現の潜在的な性能問題を警告する仕組みがあります。これらのツールをCIに組み込むことで、開発段階でReDoSリスクの高い正規表現を自動的に検出し、修正を促すことが可能です。
専門ツール「recheck」などによる自動解析
静的解析の分野では、正規表現専用のReDoS検出ツールも登場しています。藤浪氏が公開しているrecheckは、そのようなツールの一例で、正規表現パターンを入力すると、それが指数オーダーや多項式オーダーのバックトラッキングを誘発し得るかどうかを高速に解析してくれます。このツールは内部でオートマトンの構造を解析することで曖昧な部分を検出し、パターンが安全か危険かを判定します。recheckを組み込んだESLintプラグインやWeb上で試せるプレイグラウンドも提供されており、開発者は自分の正規表現にReDoS脆弱性がないか手軽にチェックできます。ただし、これらの解析結果が完全に正確とは限らず、理論上検出が難しいケースもあるため、ツールの結果はあくまで参考として人間の判断と併用することが重要です。
リソースモニタリング: 実行時間計測による検知
もう一つのアプローチとして、正規表現の実行時間やCPU使用率をモニタリングする方法があります。アプリケーション実行中に正規表現マッチの処理時間を計測し、異常に時間がかかっているケースを検知する仕組みを導入することで、潜在的なReDoSの兆候を察知できます。例えば、ある入力処理に通常は数ミリ秒しかかからないのに秒以上かかっている場合、自動的にアラートを出すといった対策です。また、負荷テストの一環で意図的に長大な入力や特殊なパターンの入力を与え、システムの応答を観察することで、事前にボトルネックとなる正規表現を洗い出すことも有効です。これらのリソースモニタリングや動的テストにより、実運用でのReDoS脆弱性の発現を早期に検知・対処することが期待できます。
テストケースの設計: 最悪ケース入力での性能テスト
開発・テスト段階では、正規表現の性能問題を炙り出すために工夫したテストケースを設計することが重要です。特に、各正規表現について最悪ケースの入力(攻撃者が使用しそうなパターンの文字列)を考え、それを実際にマッチさせてみることで、処理時間の異常を確認できます。例えば、「数字のみを許容する」正規表現であれば、何万桁にも及ぶ数字列+不適切な文字を末尾に付加した入力を試す、などです。もしこのようなテストで著しい遅延が観察される場合、その正規表現はReDoS脆弱性を含む可能性が高いと言えます。こうした性能テストを自動化し、CI/CDパイプラインに組み込んでおけば、開発中に問題を発見して修正することができ、本番環境での被害を未然に防ぐことにつながります。
CI/CDでのセキュリティスキャン: 開発フローでのReDoS検知
近年では、ソフトウェア開発のCI/CDプロセスにセキュリティスキャンを組み込むのが一般的になっています。ReDoS対策としても、上述の静的解析ツールや専門ツールをCI上で定期的に実行し、コードに脆弱な正規表現が追加されていないか継続的にチェックすることが有効です。Pull Request時に自動でReDoSの可能性を検出して警告を出す仕組みを導入しておけば、開発者が意図せず危険な正規表現を導入してしまうリスクを大幅に低減できます。また、セキュリティ部門やコードレビューの段階で正規表現のパフォーマンスに注目してチェックする文化を醸成することも重要でしょう。開発フロー全体にReDoS検知の目を組み込むことで、脆弱性を早期に摘出し、安全なリリースを実現できます。
ReDoSへの基本的・有効な対策: タイムアウト設定から安全なエンジン活用まで
次に、開発者が取るべきReDoSへの対策について解説します。被害を未然に防ぐための基本的な対処法から有効なテクニックまで、複数の観点で対策を確認しましょう。
セッションタイムアウトの設定: サービス全体でのDoS防御
最も基本的で有効な対策の一つに、タイムアウトの導入があります。特に、Webアプリケーション全体で各リクエストの処理時間に上限を設けるセッション単位のタイムアウトは、一般的なDoS対策としても有効であり、ReDoSによる無限ループ的な遅延からサービスを守る切り札となります。たとえば、Webサーバやフレームワークの設定でリクエストごとの最大実行時間を定め、超過した場合には処理を中断してリソースを開放する仕組みです。こうすることで、たとえ正規表現の処理が暴走してもサービス全体のダウンは回避できます。しかし、その場合でも該当リクエストの処理は途中で打ち切られるため、根本的な解決には至りません。この点を補完するため、マッチ単位のタイムアウトも検討できます。例えば、.NETの正規表現APIでは個々のマッチ処理にタイムアウトを設定でき、Rubyもバージョン3.2から同様の機能を提供予定です。タイムアウト値の調整は難しいこともありますが、セッション単位・マッチ単位両面で適切なタイムアウトを導入することはReDoS被害の軽減に効果的です。
安全な正規表現エンジン(DFA型)の採用: RE2やRust regex等
根本的にReDoSのリスクをなくすには、安全な正規表現エンジンを使用する方法があります。具体的には、バックトラッキングを行わないDFA型エンジンに置き換えることです。Googleが提供するRE2ライブラリはその代表例で、多くの言語向けにバインディングが用意されているため既存システムにも組み込みやすくなっています。また、Go言語やRust言語の標準正規表現ライブラリは初めからDFA型(あるいはそれに準じる実装)で作られており、ReDoSが起こりにくい設計です。Cloudflareの事例では、WAFのルールエンジンをRE2ベースに切り替えることで問題の再発を防ぎました。ただし、DFA型では前述のように一部の高度なパターン(例えば後方参照)を利用できない場合があるため、必要に応じて機能と安全性のトレードオフを検討する必要があります。それでも、可能な限り安全なエンジンを使うことはReDoS対策として非常に効果的です。
正規表現パターンの見直し: ネスト回避と単純化
開発段階での根本対策は、脆弱になりうる正規表現パターンそのものを見直すことです。具体的には、前述のような曖昧な繰り返し構造や冗長な表現を避け、正規表現をシンプルかつ明確な形に書き換えることが有効です。例えば、ネストした量指定子を用いずに済むようにパターンを工夫したり、必要以上に包括的な .*
を使わないよう限定したパターンにする、といった対応が考えられます。また、バックトラッキングの挙動を制御する先読みや原子グループ(Atomic Grouping)等の機能が利用できる場合、それらを活用して不要なバックトラックを抑制する手法もあります。重要なのは、正規表現を書く際に常に最悪ケースを意識し、少しでも曖昧さや冗長さを排除するよう心掛けることです。
正規表現以外の手段の検討: 標準関数やコードで代替
場合によっては、正規表現自体を使わないという選択も検討すべきです。特に、簡単な文字列操作や検証であれば、言語が提供する標準ライブラリの関数(例えば空白除去にはtrim、文字種の判定にはisNumericなど)や自前のコードで十分実現可能なことが多いです。Stack Overflowの事例でも、空白除去の処理を正規表現から標準の文字列操作に切り替えることで問題が解決されました。正規表現は強力ですが、不要な場面で多用すると思わぬリスクを抱え込むことになります。単純な処理は正規表現以外の手段で実装し、正規表現を使う場合でも処理対象の入力サイズを限定するといった工夫で、ReDoSリスクを下げることができます。
依存ライブラリの監視と更新: 脆弱性情報への対応
また、開発者は自分のコードだけでなく、依存するライブラリに潜むReDoSリスクにも注意を払う必要があります。オープンソースのライブラリやフレームワークが内部で脆弱な正規表現を使用している場合、開発者の手元では気付きにくいためです。定期的にプロジェクトの依存関係をチェックし、ReDoS脆弱性に関するセキュリティアドバイザリやCVE情報が公開されていないか監視しましょう。実際、RubyのREXMLのように標準ライブラリであっても後になって脆弱性が判明するケースがあります。そうした情報をキャッチしたら速やかにライブラリのアップデートを適用し、脆弱性を修正することが大切です。また、依存ライブラリをアップデートできない場合には、一時的な緩和策(該当部分に対する入力長の制限やフィルタリングなど)を講じて被害を防ぐ工夫も必要になります。
入力文字列やデータ長に関する制限の重要性: ReDoSリスク低減の基本策
ここでは、入力として与えられる文字列のサイズやデータ長を制限することによってReDoSリスクを下げる手法について説明します。この対策はシンプルですが効果的であり、他の対策と組み合わせることでより安全なシステム運用につながります。
過度な入力を制限するメリット: ReDoSのリスク軽減
入力長の制限は、ReDoSリスクを下げる基本的な対策の一つです。正規表現の性能は概ね入力の長さに依存するため、極端に長い文字列を処理させないようにすれば、たとえ脆弱なパターンであっても実行時間の悪化に一定の歯止めをかけることができます。例えば、通常ユーザが入力しないような何百万文字ものデータを受け付けないよう、事前に上限サイズを設けて検証することで、エンジンが無限に近い時間を費やす状況を防ぐことができます。特にWebフォームやAPIでユーザ入力を扱う場合、各フィールドに妥当な長さ制限を設定することはセキュリティ上の基本的な心得です。
実装例: 入力長をバリデーションでチェック
入力長の制限は、具体的にはバリデーションの一環として実装します。例えば、Webアプリケーションであれば、フォームのサーバサイドバリデーションやAPIの入力検証で、文字列フィールドに対して「最大N文字まで」という制約を設けます。実装レベルでは、正規表現にかける前に if (input.length > N) { エラー }
とするだけでも効果は絶大です。また、クライアント側でもHTMLのmaxlength
属性やスクリプトで入力を制限しておけば、過剰なデータが送信されるのを防げます。たとえば、メールアドレスの入力で1000文字を超えるような値は明らかに異常なので、システム上は数十~数百文字程度の上限を設定しておくのが望ましいでしょう。このように、二重のチェック(フロントとバックエンド双方)で入力長を制御することで、ReDoS攻撃の余地を狭めることができます。
現実的な閾値設定: 適切な長さ上限を決めるポイント
制限を設ける際は、その閾値の設定が肝心です。あまり厳しすぎる上限にすると、正当な利用まで妨げてしまう恐れがあり、逆に緩すぎると効果が薄れます。適切な上限値を決めるために、まずそのフィールドに現実的に入力される最大サイズを見積もります。例えば、ユーザ名ならせいぜい数十文字、住所なら数百文字もあれば十分でしょう。ログや記事の本文のように長大になりうるテキストであっても、内部で正規表現処理を行う必要がある場合には、一度に処理するサイズを数万文字程度に区切るなど対策が考えられます。また、指数オーダーで爆発するパターンについては100文字以下程度の文字列でも数秒のマッチ時間になるので注意が必要です。その場合は更に低い閾値や別対策も検討します。システム要件とセキュリティのバランスを考慮しつつ、実用的かつ安全な長さ制限を策定することが重要です。
例外と注意点: 正当な長入力を拒否しないために
とはいえ、常に入力長を短く制限できるとは限りません。正当な長大入力があり得るシナリオでは、単純に長さで拒否するとユーザビリティに影響が出ます。この場合、いくつかの工夫が考えられます。第一に、本当に大きなデータに対しては、そもそも正規表現で処理しないよう設計を見直すことです。例えば、巨大なログファイルや文章全文を解析するような場合、一括で正規表現にかけるのではなく、データを適切なサイズに分割して順次処理するといった対策が有効でしょう。また、入力長の上限を緩和する場合でも、異常に長い入力に対してはログを記録して管理者に通知するなど、セーフティネットを設けておくと安心です。重要なのは、ユーザの正当な利用を阻害しない範囲で、可能な限りReDoSリスクを軽減するバランスを取ることです。
入力長制限だけでは不十分な場合: 他対策との組み合わせ
なお、入力長の制限だけでは不十分なケースもあります。特に、指数オーダーで性能劣化するような悪質な正規表現では、数十文字程度の入力でも攻撃が成立してしまうことがあります。そのため、入力長制限はあくまで被害の上限を減らす効果と位置づけ、他の対策(正規表現パターン自体の改善、安全なエンジンへの移行、タイムアウト設定など)と組み合わせて用いることが重要です。総合的な防御を施すことで、万一一つの対策が突破された場合でも、他の層でリスクを緩和できる多層防御(defense in depth)の姿勢が求められます。
開発者が注意すべきポイントと落とし穴: ReDoS防止の心得
最後に、開発者が日々の開発においてReDoS脆弱性に注意するためのポイントや陥りやすい誤解について述べます。これらを踏まえて、普段からセキュリティ意識を持った正規表現の利用を心がけましょう。
開発時に見逃しやすい正規表現の性能問題
正規表現の性能問題は、開発時に非常に見落とされやすいポイントです。開発者は正規表現の「動作が正しいか」には注意を払いますが、「性能上安全か」まで考慮が及ばないことが少なくありません。特に、小さなテスト入力では問題なく動くため、潜在的なボトルネックが潜んでいても表面化せず、レビューでも指摘されないまま本番環境に導入されてしまうケースがあります。開発段階では、機能要件の達成だけでなく、最悪ケースでのパフォーマンスを意識的にチェックする姿勢が求められます。
コードレビューで正規表現をチェックすべきポイント
コードレビューの際には、正規表現について以下のポイントに留意してチェックすると良いでしょう。まず、新規に追加・変更された正規表現がある場合、それが曖昧な構造を含んでいないか(繰り返しのネストや重複するオルタネーションがないか)目視で確認します。必要なら簡単なケースで実行してみて、想定外に時間がかからないかをテストするのも有効です。また、プロジェクトのコーディング規約として危険な正規表現パターンを禁止するルールを設けておくのも手です(例えば、+
や直後の
+
やの使用はレビューで要注意、といった指針)。さらに、レビュー段階で判断が難しい場合は、前述のツールを使って疑わしいパターンを解析し、問題の有無を確認することも検討してください。コードレビューにおいて正規表現の性能に目を光らせることは、ReDoS脆弱性を仕込まないための重要な防衛線となります。
ユーザー入力を直接正規表現に渡す際の危険性
ユーザー入力をそのまま正規表現に使用するようなケースは、極めて危険です。これは、ユーザーに正規表現パターンの一部または全部を指定させ、それをプログラム内でコンパイル・実行するような場面を指します。攻撃者はここに悪意のある正規表現を送り込むことで、意図的にReDoSを引き起こすことができます。例えば、検索機能でユーザー提供のパターンを用いる場合や、フォームのバリデーションルールを外部から取り込むような場合が該当します。基本的に、信頼できない入力を正規表現エンジンに直接渡すべきではありません。やむを得ずそのような機能を提供する際は、入力パターンを事前にサニタイズしたり複雑な構文を禁止したりする、あるいは安全なサンドボックス環境で実行するなど、厳重な対策が必要となります。
複雑な正規表現のテスト不足によるリスク
複雑な正規表現ほど、その性能面のテストが不十分だと危険です。例えば、何十文字にも及ぶような長い正規表現や高度なパターン(ネストしたグループや多数の選択肢を含むもの)は、実装者自身も全ての挙動を把握しきれないことがあります。こうした正規表現は、意図せぬ入力で思わぬバックトラックを引き起こす可能性が高いため、通常の動作確認だけでなく、異常系のストレステストを入念に行う必要があります。もしテスト資源が限られるなら、むしろ正規表現をシンプルに分割して複数回に分けて処理するなど、構造自体を見直すことも検討すべきでしょう。複雑さはそのままリスクに直結するため、「うまく動いたからOK」ではなく「最悪の場合でも大丈夫か?」まで検証する姿勢が重要です。
ReDoS対策で陥りがちな誤解と過信
最後に、ReDoS対策における誤解や過信にも注意が必要です。一つは、「ループのネストを避けたから安全だ」といった単純化しすぎた安心感です。前述のように、ネストがなくとも曖昧な構造であればReDoSは起こりえます。また、「我々のシステムは小規模だから大丈夫」「これまで問題が起きていないから平気だ」といった過信も危険です。脆弱性は発見されるまで表面化しないことが多く、偶然のトリガーで初めて露呈することもあります。さらに、「タイムアウトを入れているから安心」「有名なライブラリの正規表現だから大丈夫」といった思い込みも禁物です。タイムアウトは万能ではなく、ライブラリのパターンでも脆弱性が報告されることがあります。常に最新の情報にアンテナを張り、複数の対策を層状に講じているかを見直し、慢心することなくセキュリティ意識を持ち続けることが、ReDoS防止には欠かせません。
最新の脅威動向と今後の展望: ReDoS脆弱性の現状と未来への対策
最後に、ReDoSを取り巻く最新の脅威動向と今後の展望について述べます。報告事例の増加やコミュニティの取り組み、技術的な進歩を踏まえ、将来の対策の方向性を考察します。
報告件数の増加: 近年のReDoS脆弱性の動向
ここ数年、ReDoS脆弱性の報告件数は増加傾向にあります。これは、一面ではセキュリティ研究者や開発コミュニティが正規表現のリスクに注目し、多くのソフトウェアを精査した結果とも言えます。実際、脆弱性が大きな問題となる前に発見・報告されるケースが増えていることは喜ばしいことです。しかしその反面、開発者にとっては依存ライブラリのアップデート対応やパッチ適用などの負担が大きくなっているのも事実です。例えば、NPMやRubyGemsといったパッケージエコシステムでは、ReDoS関連の脆弱性アラートが定期的に発行されており、メンテナは迅速な対応を迫られます。今後もしばらくは、このような報告増加のトレンドが続くと予想され、開発コミュニティ全体での対策が求められるでしょう。
セキュリティコミュニティの注目: 研究とツール開発の進展
セキュリティコミュニティでも、ReDoSは近年ますます注目度が高まっている脅威です。複数の学術研究やカンファレンスで、正規表現の性能問題に関する発表が相次いでおり、攻撃手法の体系的な分析や検出・修正アルゴリズムの提案など、研究が活発に行われています。また、それら研究成果を基にした実用的なツールも次々と登場しています。先に触れたrecheckを始め、ブラウザ上で正規表現の挙動を可視化できるサービスや、IDEプラグインで開発者にリアルタイムに警告を出す仕組みなど、開発現場に役立つソリューションが増えてきました。さらに、OWASP Top 10などセキュリティ指針の中でReDoSへの言及がなされるようになるなど、広く啓発も進んでいます。コミュニティ全体のこうした取り組みにより、今後はReDoS脆弱性への早期検出と対策がますます容易になることが期待されます。
自動修正技術の台頭: 正規表現を安全に書き換える試み
特筆すべき動きとして、正規表現の自動修正技術の研究開発があります。2023年、NTTと早稲田大学の研究チームは、脆弱な正規表現を書き換えてReDoS脆弱性を除去するアルゴリズムを考案し、世界で初めてその技術を実現しました。この技術では、正規表現パターンを解析して潜む曖昧性を特定し、性能上安全な形に自動変換することが可能となります。これにより、専門知識を持たない開発者でも正規表現の脆弱性修正が可能になり、安全なサービスの実現が期待できます。まだ研究段階の技術ではありますが、将来的にはIDEやコードエディタに組み込まれて、自動で危険な正規表現を修正提案してくれるようなツールが登場するかもしれません。正規表現の安全性向上に向けたこうした自動化の試みは、今後の大きな展望として注目されます。
各プラットフォームの対応: 言語やフレームワークでの対策強化
プラットフォーム側でも、ReDoS対策の強化が進みつつあります。例えば、Rubyではバージョン3.2から正規表現マッチにタイムアウトを設定できる機能が追加されました。また、JavaやPythonでもライブラリレベルで正規表現の実行時間制御や安全なエンジンの選択肢が提供され始めています。実際、PythonにはGoogle RE2エンジンを利用できるモジュールが存在し、大規模プロジェクトで採用するケースもあります。Webフレームワークの中には、ユーザ入力に対するバリデーションでReDoSを検知して警告する仕組みを備えるものも出てきました。さらには、npmパッケージの審査や言語公式のパッケージリポジトリにおいて、脆弱な正規表現が含まれていないかチェックする取り組みも検討されています。各言語・プラットフォームが提供するこうした組み込みの対策を活用することで、個々の開発者がすべてを手作業で注意しなくても、一定の安全性が担保される未来が期待できます。
今後の展望: ReDoSリスクの低減に向けた取り組みの方向性
今後の展望としては、開発者とセキュリティ研究者が協力し、ReDoSリスクを体系的に低減していく方向に進むでしょう。一つは、正規表現エンジン自体の改良です。バックトラッキングのキャッシュ戦略を選択的に適用することで理論上は計算量を線形にできることが知られており、将来的により安全なエンジン実装が普及する可能性があります。ただし、そのような改良にはメモリ使用量など他のトレードオフも伴うため、実用化には課題もあります。もう一つは、教育と意識向上です。正規表現の性能に関する知識がより広まり、開発者が設計段階からReDoSを念頭に置くようになれば、そもそも脆弱なパターンを書かなくなるでしょう。最終的には、ツールと知識の両面からReDoSを防ぐエコシステムが整い、新規にこの脆弱性が混入するケースは大幅に減っていくと期待されます。完全になくすことは難しくとも、対策が標準化・自動化されることで、ReDoSは「怖いけれど対処可能な問題」へと位置づけが変わっていくでしょう。