ReDoSは、特定の正規表現に細工した入力を与えるだけで、CPUを枯渇させてサービスを止められる脆弱性です。 対策の勘所は、破滅的なバックトラッキングを起こす正規表現を避け、入力の長さを抑えられているかどうかです。
ReDoSとは
ReDoS(Regular expression Denial of Service):計算量が爆発する正規表現に長い入力を与え、マッチング処理でCPUを消費させてサービスを停止させる攻撃です。 バックトラッキングを多用する正規表現エンジンの特性が根本原因になります。
一部の正規表現は、マッチに失敗する入力に対して指数関数的な回数の試行を行います。 攻撃者は数十文字の入力を送るだけで1リクエストの処理を長時間占有させ、サーバのCPUを枯渇させてサービス全体を応答不能にします。 単一の処理がイベントループを止める構成(Node.jsなど)では、1つの重い正規表現がプロセス全体を停止させるため影響が大きくなります。
ReDoSが起きるパターン
典型的な弱点は次のとおりです。
- 繰り返しが入れ子になった正規表現(
(a+)+のような形)で、失敗時に膨大なバックトラッキングが起きる - 選択肢や量指定子が重なり、同じ文字列を複数の経路で解釈できる曖昧なパターンになっている
- メールアドレスやURLの検証など、複雑な正規表現に外部入力を長さ制限なしで通している
- 危険な正規表現を含むライブラリを、更新せずに使っている
- 入力の検証やサニタイズ、ルーティングなど、リクエストごとに必ず通る箇所に危険なパターンがある
- ユーザーが正規表現そのものを入力でき、悪意あるパターンを直接与えられる
共通するのは「曖昧で入れ子の正規表現に、長さを制限しない外部入力を通している」という点です。
対策法
原則は「破滅的なバックトラッキングを起こさない正規表現を使い、入力の長さを制限する」ことです。
- 繰り返しの入れ子や曖昧な量指定子を避け、バックトラッキングが爆発しないパターンに書き換える。これが本命の対策になる
- 正規表現に通す前に入力の長さに上限を設け、極端に長い入力を弾く
- バックトラッキングしない線形時間のエンジンや、危険を検知するツールを使う
- 複雑な検証は正規表現に頼らず、専用のパーサや分割処理で行う
- 正規表現を含むライブラリを新しいバージョンに保つ
入力長の制限は手軽に効きますが、本命は危険なパターン自体を避けることです。 外部入力を通す正規表現を洗い出し、危険な形の書き換えと長さ制限を入れるのが費用対効果の高い進め方になります。
React(TypeScript)での事例
フォームバリデーションの正規表現が典型的な混入箇所です。
// 危険:繰り返しの入れ子。マッチしない長い入力で試行が指数的に増える
const USERNAME_RE = /^([a-zA-Z0-9]+)+$/;
function validate(input: string): boolean {
return USERNAME_RE.test(input);
}
このパターンに「英数字30文字+記号1文字」のような入力を与えると、マッチ失敗を確定するまでの試行が爆発し、処理が返ってこなくなります。 ブラウザで実行されればそのタブの UI が固まる程度で済みますが、同じバリデーションを Next.js の SSR や API Route(Node)で共有していると、イベントループが止まり全利用者へのレスポンスが停止します。 フロントとサーバでコードを共有する構成では、「ブラウザで固まるだけ」では済まない点に注意が要ります。
直し方は、入れ子をほどくことと、正規表現に通す前の長さ制限です。
const USERNAME_RE = /^[a-zA-Z0-9]+$/; // 入れ子を外しても、受け入れる文字列は変わらない
function validate(input: string): boolean {
if (input.length > 64) return false; // 先に長さで弾く
return USERNAME_RE.test(input);
}
Node 側でどうしても複雑なパターンが必要なら、バックトラッキングを行わない線形時間エンジンの RE2(node-re2)に置き換える選択肢があります。
Rails での事例
Ruby の正規表現エンジンもバックトラッキング型ですが、CRuby 3.2以降はメモ化によって多くの正規表現を線形時間で処理します。
たとえば、単純な/\A([a-z0-9]+)+\z/はRegexp.linear_time?がtrueを返すため、現行CRubyでは危険例として適切ではありません。
一方、後方参照や先読みなどを含むパターンは線形化の対象外になる場合があり、破滅的なバックトラッキングが残ります。
Rails で見落とされやすいのは、モデルのバリデーションが外部入力に正規表現を直接当てる場所だという点です。
# 危険:先読みの中に曖昧な繰り返しがあり、CRuby 3.2以降でも線形化されない
USERNAME_RE = /\A(?=([a-z0-9]+)+\z)[a-z0-9]+\z/
Regexp.linear_time?(USERNAME_RE) # => false
validates :username, format: { with: USERNAME_RE }
length のバリデーションを並べても防御にはなりません。
各バリデータは独立に実行されるため、長さ検証が失敗しても format の正規表現は同じ長い入力に対して実行されます。
この例は先読み部分を削除しても受け入れる文字列が変わらないため、パターン自体を/\A[a-z0-9]+\z/へ直すことが本命です。
そのうえで、Ruby 3.2 以降にはセーフティネットとして正規表現のタイムアウトがあります。
# config/initializers/regexp_timeout.rb
Regexp.timeout = 1.0 # 個別指定がない正規表現の、1回のマッチに対する既定値
これを設定しておけば、個別のtimeoutを指定していない正規表現は、1回のマッチが上限時間を超えるとRegexp::TimeoutErrorで打ち切られます。
正規表現ごとの指定はこの既定値を上書きでき、複数回のマッチを含むリクエスト全体の実行時間を制限するものでもありません。
危険なパターンの書き換えが本命、タイムアウトが最後の防波堤という役割分担です。
まとめ
ReDoS対策の要点を整理します。
- 根本原因は「バックトラッキングが爆発する正規表現に、長さを制限しない外部入力を通していること」。JavaScriptでは
(a+)+のような形が、マッチに失敗する入力に対して試行を指数的に増やす - 本命の対策はパターン自体の書き換え。
/^([a-zA-Z0-9]+)+$/は/^[a-zA-Z0-9]+$/へ直しても、受け入れる文字列が変わらない - 入力長の上限は手軽に効くが、Railsの
validatesでは防御にならない。各バリデータは独立に実行されるため、lengthが失敗してもformatの正規表現は同じ長い入力に対して走る - CRuby 3.2以降は多くの正規表現を線形化するが、後方参照や先読みなどを含むパターンは対象外になり得る。
Regexp.linear_time?で確認し、線形化されないパターンを書き換える - Ruby 3.2以降の
Regexp.timeoutは、個別指定がない正規表現の1回のマッチに対する既定値。リクエスト全体の制限ではないため、危険なパターンの除去と併用する - Node側で複雑なパターンが必要なら、バックトラッキングしない線形時間エンジン(RE2)に置き換える選択肢がある
フロントとサーバでバリデーションを共有している構成では、「ブラウザで固まるだけ」では済みません。 同じ正規表現がNext.jsのSSRやAPI Routeで動けば、イベントループが止まって全利用者へのレスポンスが停止します。
関連して読める記事
JavaScript/Node側で起きるという点では、プロトタイプ汚染と同じ守備範囲の問題です。 どちらも「外部入力が想定外の計算やデータ構造を引き起こす」形で、依存ライブラリの更新が対策の一部になる点も共通しています。 ReDoSは細工した入力1件でも開発環境で再現でき、負荷が高いほど影響が大きくなる点はレースコンディションと似ています。 ライブラリを古いまま放置する運用の問題としては、セキュリティミスコンフィグと地続きです。

プロトタイプ汚染とは?起きるパターンと対策法
プロトタイプ汚染の仕組みと起きるパターン、__proto__ など危険なキーの遮断を軸にした対策を整理します。自前の再帰マージが Object.prototype を書き換えてしまう危険例と、禁止キーの拒否や Object.create(null) の利用、対策済みライブラリへの移行を TypeScript のコードで示します。
2026年7月25日
レースコンディションとは?起きるパターンと対策法
レースコンディション(競合状態)の仕組みと起きるパターン、原子的更新と行ロックを軸にした対策を整理します。残高の確認と更新が分かれた check-then-act の危険例、UPDATE 一文にまとめる方法、with_lock による直列化、uniqueness バリデーションではなくDBのユニーク制約が必要な理由を具体的なコードで示します。
2026年7月25日
セキュリティミスコンフィグとは?起きるパターンと対策法
セキュリティミスコンフィグの仕組みと起きるパターン、安全な既定への統一と環境をまたいだ設定管理を軸にした対策を整理します。Sidekiq の Web UI を認証なしでマウントする危険例と Devise の authenticate で囲む方法、本番で consider_all_requests_local が true になっている場合の露出を具体的なコードで示します。
2026年7月25日