🔖

rack-attack だと十分でなく、Web アプリケーションファイアウォール(WAF)を使う理由

2024/05/16に公開

Cloudflare に出来て rack-attack に出来ないことって何でしょうか。これについて何かご存じですか? また、rack-attack を Cloudflare の WAF と同じように動作させるよう改良できる可能性はあるのでしょうか? -Sammy

とても良い質問ですね。「分散型サービス妨害(Distributed Denial of Service:DDoS)」とは何かを理解し、リトルの法則をほんの少し用いて考えると、この質問にかなりうまく答えられるのではないかと思います。

まず、DDoS の本質を理解しましょう。DDoS とは、簡単に言えば、アプリに対する分散された多数の IP アドレスからの大量のトラフィックです。この「大量のトラフィック」という点が、サイトをダウンさせる要因です。膨大な量のリクエストに圧倒されると、インフラストラクチャの何かが壊れ(通常はデータベース、または単に十分な Web サーバーの容量を確保できないなど)、サイトは事実上ダウンしてアクセスできなくなってしまいます。そして、「分散された」という点が、この脅威を防ぐのを困難にしている要因です。トラフィック元の IP アドレスが膨大だと、正規のトラフィックと攻撃者のトラフィックの区別は難しくなります。トラフィック元の IP アドレスが 1 つだけなら、その IP アドレスからの接続を禁止すればよいだけです。しかし、DDoS 攻撃では膨大な数の IP アドレスがトラフィック元となります。そうすると、IP アドレス単位で接続を禁止する戦略は、あまり効果的ではありません。

私たちに今、Ruby プロセス 10 個分のキャパシティがあるとしましょう。アプリケーションは 1 リクエストあたりの処理に 100ms かかるとします。

他のトラフィックがないと仮定するなら、リトルの法則を用いて、DDoS によるトラフィックをどの程度処理できるかが計算できます。リトルの法則に基づくと、100 リクエスト/秒 * 0.1 秒/リクエスト = 10 で、これが利用可能なプロセス数と同じであることから、100 リクエスト/秒で利用可能なキャパシティをすべて使い切るとわかります。使えないくらいアプリが遅くなるのは、その少し手前、80 ~ 90 リクエスト/秒くらいからでしょうか。いずれにしろ、この見積もりは目安として有効です。

DDoS 攻撃は基本的に「攻撃者が使用する以上のキャパシティを提供しているか(すなわち、正当なユーザーを拒否してしまっていないか)」という点に帰結します。

例えば、DDoS 攻撃を阻止するのに rack-attack を使いたいとしましょう。加えて、これ以上キャパシティは増やせず、10 個の Ruby プロセスでやりくりしなければならないとします。

最初の問題は、DDoS トラフィックを禁止するルールをどのように記述するか、ということです。

トラフィックが特定の URL を要求している、あるいは特定のユーザーエージェントヘッダーが送られてくるなど、トラフィックに何らかの特定しやすいパターンがなかったとします。そうすると、できるのは 1 秒あたりのリクエスト数に基づいてユーザーエージェントを禁止するくらいです。

rack-attack でそれを行うと、次のようになります。

Rack::Attack.throttle("requests by ip", limit: 5, period: 2) do |request|
  request.ip
end

このコードでは、 2 分間に 5 リクエストまで同一 IP からのアクセスを許容しています。それは、まあいいでしょう。しかし、DDoS 攻撃者が攻撃トラフィックを送信するための IP アドレスを 12,000 個持っているとしたらどうでしょうか。12,000 個の IP があるなら、各 IP は 2 分間に 1 回しかあなたのアプリケーションにリクエストを送らないかもしれません。そうすると、あなたが設定した rack-attack のルールは作動しないことになってしまいます。

基本的に、rack-attack は DDoS の保護には適していません。なぜなら、Rack 環境では、攻撃者からのリクエストか正当なリクエストかを識別するのに使用できる情報は非常に限られている、すなわち、攻撃をブロックする方法が非常に限られているからです。

仮に throttle で指定したルールが機能したとしましょう(攻撃者が 5 つの IP しか持っていなかった場合など。現実にはそれは起こらないでしょうが)。それでも、また別の問題に頭を悩ますことになります。それは、トラフィックを実際に禁止するには、Ruby を実行して Redis への呼び出しを行わなければならないという問題です。

Rack ミドルウェアスタックをロードし、rack-attack を実行し、スロットリングデータを含む Redis の結果を取得し、攻撃者/接続禁止対象のケースに対してレスポンスを返すのに 10 ミリ秒かかるとしましょう。これは、DDoS 攻撃者が 10 個のプロセスの代わりに 1 個のプロセス(0.01 秒/リクエスト * 100 リクエスト/秒)を占有していることを意味します。これは問題になりませんか? まあ、直ちにはならないかもしれません。ですが、それも攻撃者が送信するトラフィック量を増やすまでの話です。そうすれば、問題はまた振り出しに戻ります。

WAF は、DDoS 攻撃に対して、rack-attack では出来ない 2 つのことを非常にうまくやってくれます。

  1. WAF は、あなたのアプリケーションのトラフィックだけでなく、インターネット上の大量のトラフィックにアクセスできます。そのため、あなたのアプリケーションや他のアプリケーションに対する過去の攻撃から疑わしい行動を特定したり、ボットを特定したりできます。rack-attack は、禁止判断を行うのに Rack 環境(IP、リクエストヘッダーなど)しか使えませんが、WAF はそれに加えて、その IP アドレスに関する長年の履歴を使用できます。
  2. WAF は、DDoS トラフィックを自分たちのサーバー上で迅速かつ効率的にブロックします。これにより、DDoS 攻撃者は、Cloudflare 全体を圧倒するのに十分なトラフィックを送信しなければなりません。これは、10 個の Ruby プロセスを圧倒するのに十分なトラフィックを送信するのに比べて、はるかに困難です。

これは rack-attack の設計上の問題ではありません。これらの問題を解決したより良いバージョンの rack-attack を書くことはできません。なぜなら、これは rack-attack がスタックのどこで動くか(情報とリソースが限られたサーバー上のアプリの上)ということの副産物だからです。

ですから、前にも言ったように、保護する価値のあるものがあるなら、それは WAF の後ろに置いてください。以下に、この分野のベンダーの短いリストを示しておきます。

  1. Cloudflare
  2. 主要なクラウドプロバイダーはそれぞれ独自の WAF を持っています。例えばAmazon WAFなど
  3. Akamai Kona

Discussion