Nginxのlimit_req_moduleを使って自作のDoS攻撃対策をしてみた

「SAVACAN」担当のMKです。

今回の記事では、NginxでDoS攻撃対策をする設定方法と動作確認結果を紹介します。
NginxのWEBサーバーとしての基本的な設定方法を理解している方向けの記事となっております。

初めにDoS攻撃とNginxの特徴について簡単に解説します。どうぞ。

目次

DoS攻撃とは

DoS攻撃(Denial of Service Attack)は日本語で「ドス攻撃」といいます。
攻撃対象となるWEBサーバーへ大量のデータを送ったり、アクセスを集中させることで過剰な負荷をかけ、処理遅延やサービスダウンを発生させる不正アクセス攻撃のひとつです。
データベースを利用しているシステムへDoS攻撃を行う事で、WEBサーバーだけでなくバックエンドのデータベースサーバーをダウンさせる場合もあります。

(犯罪です)

似たような攻撃にDDoS攻撃(Distributed Denial of Service Attack)という手法があります。
こちらは日本語で「ディードス攻撃」といい、複数の端末から一斉に攻撃を行う分散型サービス拒否攻撃となります。この際、マルウェアなどに感染させて不正に乗っ取った端末を利用して攻撃する事例が多いようです。

もちろん、いずれも犯罪です

Nginxの特徴

NginxはOSS(オープンソース)のWEBサーバーで、大量の同時接続を捌くことに適した設計となっております。大きな特徴として下記が挙げられます。

  • C10K問題(サーバ性能に問題が無くても、同時接続数が多い場合に応答が遅延したり失敗する問題)を解決するために開発された。Cはクライアント、10Kは10,000台の同時接続(=大量の同時接続)を表現しています。
  • シングルスレッドのプロセス内でリクエストを並列処理しているため、メモリ消費量を抑えることができる。(Apacheはマルチプロセスでリクエスト毎にプロセスを割り当てて処理するため、リクエスト数に比例してメモリ消費量が増えます)
  • 静的コンテンツ(軽量な処理)を高速に同時処理することが得意。

Nginxを設定してみる

DoS攻撃は単一拠点(単一IPアドレス)からの攻撃となりますので、特定のIPアドレスからの秒間アクセス数を制限して対応します。今回ではNginxの「limit_req_module」を利用しています。
設定環境のOSはAlmaLinux 8.9となります。

Nginxインストール

dnf -y install nginx

firewall設定
今回はテスト環境ですのでhttpのみ解放します。

firewall-cmd --add-service=http --zone=public --permanent
firewall-cmd --reload

Nginxの「limit_req_module」を利用して同一IPアドレスからの秒間アクセス数を制限します。
ここでは以下のように設定しました。

設定ファイルは /etc/nginx/nginx.conf です。
httpブロックに以下の1行を追記します。

vi /etc/nginx/nginx.conf

limit_req_zone $binary_remote_addr zone=dos:1m rate=1r/s;

・設定値
$binary_remote_addr 対象物の指定、今回は接続元IPアドレス
zone=dos:1m ゾーンと保持IPアドレス容量の指定、「dos」と「1MB」を指定(1MBは約7万IPアドレスに相当)
rate=1r/s 制限するレートの指定、秒間1リクエストを指定

ここでは動作が確認しやすいように秒間1リクエストとしていますが、運用環境では実際のアクセス頻度から適した値を割り出す必要があります。

serverブロックにリミットとステータスコードの2行を追記します。

vi /etc/nginx/nginx.conf

# 1以上を指定
limit_req zone=dos burst=1;
# 429以外のステータスコードも設定できる
limit_req_status 429;

・設定値
burst レートを超えた場合に保持するリクエスト数の指定、保持したリクエストはrate指定分遅延処理させる(今回の場合は秒間1リクエスト)、burstの設定値を遅延処理させる必要がないときはburst=1 の後に nodelay を付ける
limit_req_status 拒否したリクエストへ返すステータスコードの指定、429 (Too Many Requests)を返す(デフォルト設定は503)

シンタックスチェックにて問題が無い事を確認し、起動します。

nginx -t
systemctl start nginx

動作確認をしてみます

動作確認用にドキュメントルート下に85kBの画像ファイルを設置しました。

DoS対策の設定の動作確認をする際は自分のWEBサイトで実施してください。
他社サイトでDoS攻撃を試すことは犯罪ですので絶対にやってはいけません!

動作確認はNginxサーバーとは別サーバーから以下の内容のシェルを作成して実行しました。

#!/bin/bash
 for i in {1..20}; do
  # nginxサーバーに設置した85Kの画像ファイルを20回ダウンロード
  wget http://XXX.XXX.XXX.XXX/testimg.jpg
 done

・DoS対策前
dstatコマンドにて、シェル実行時の「net/total」のsend値(Nginxサーバーからのデータ送信)を確認します。シェル実行時のsend値を確認すると1709kBとなっており、1秒以内に85kBの画像ファイルを20回(1700kB)配信したことが確認できます。

                                        ↓ここの値をチェック
----total-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai stl| read  writ| recv  send|  in   out | int   csw 
  1   1  99   0   0|   0     0 |3803B 1085B|   0     0 | 102   395 
  0   0 100   0   0|   0     0 |4981B  372B|   0     0 |  95   274 
  2   1  97   0   0|   0     0 |  23k 1709k|   0     0 | 650   563  ←ここでシェルを実行
  0   0  99   0   0|   0     0 |4922B  444B|   0     0 |  75   139 
  0   0 100   0   0|   0     0 |4928B  444B|   0     0 |  72   137

・DoS対策後
dstatコマンドにて、シェル実行中の「net/total」のsend値(Nginxサーバーからのデータ送信)を確認すると、毎秒約85kBのデータが送信されており、「rate=1r/s、burst=1」の通り秒間1回のアクセスに制限されてリクエストが遅延処理されていることが確認できます。

アクセスイメージ
1回目のwget:アクセスOK
2回目のwget:rateの秒数後にアクセス
3回目のwget:2回目のアクセスからrateの秒数後にアクセス
4回目のwget:3回目のアクセスからrateの秒数後にアクセス
・・・20回目まで繰り返し

DoS対策前はアクセス数の制限がないため一瞬で20リクエストの画像ファイルを配信しましたが、制限後は毎秒85kBの画像配信になりますので短時間で大量のアクセスを受けても急激なネットワーク転送量の増加はありません。
(制限前に比べて瞬間転送負荷が1623kB軽減されました。)

----total-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai stl| read  writ| recv  send|  in   out | int   csw 
  0   0  99   0   0|   0    16k|  11k   86k|   0     0 | 130   176  ←ここでシェルを実行
  0   0 100   0   0|   0     0 |8201B   86k|   0     0 | 125   172 
  0   1  99   0   0|   0     0 |8891B   86k|   0     0 | 106   149 
  1   0  99   0   0|   0     0 |7129B   86k|   0     0 |  88   121 
  0   0 100   0   0|   0     0 |  10k   86k|   0     0 |  99   136 
  1   0  99   0   0|   0     0 |  11k   86k|   0     0 | 112   365 
  0   0  99   0   0|   0     0 |9394B   86k|   0     0 | 127   190 
  0   0  99   0   0|   0  4096B|5886B   86k|   0     0 | 123   196 
  1   1  99   0   0|   0     0 |  12k   86k|   0     0 | 136   212 
  1   0  99   0   0|   0     0 |  10k   86k|   0     0 | 134   205 
  0   0 100   0   0|   0     0 |9269B   86k|   0     0 | 122   189 
  0   0  99   0   0|   0     0 |8981B   86k|   0     0 | 121   186 
  1   0  99   0   0|   0     0 |  11k   86k|   0     0 | 139   428 
  0   0  99   0   0|   0     0 |  11k   86k|   0     0 | 149   235 
  1   0  99   0   0|   0     0 |5759B   86k|   0     0 | 111   166 
  0   0 100   0   0|   0     0 |9131B   86k|   0     0 | 119   172 
  0   0  99   0   0|   0     0 |5785B   86k|   0     0 | 119   185 
  0   0  99   0   0|   0     0 |8757B   86k|   0     0 | 109   153 
  1   0  99   0   0|   0     0 |7195B   86k|   0     0 | 112   172 
  1   0 100   0   0|   0     0 |6908B   86k|   0     0 | 118   399
アクセスログを確認すると秒間アクセス数の制限が効いている間も1秒間隔で全てのアクセスを成功としてステータスコード200を返していることが分かります。
XXX.XXX.XXX.XXX - - [05/Mar/2024:15:32:01 +0900] "GET /testimg.jpg HTTP/1.0" 200 86139 "-" "Wget/1.12 (linux-gnu)" "-"
XXX.XXX.XXX.XXX - - [05/Mar/2024:15:32:02 +0900] "GET /testimg.jpg HTTP/1.0" 200 86139 "-" "Wget/1.12 (linux-gnu)" "-"
XXX.XXX.XXX.XXX - - [05/Mar/2024:15:32:03 +0900] "GET /testimg.jpg HTTP/1.0" 200 86139 "-" "Wget/1.12 (linux-gnu)" "-"
エラーログを確認すると1秒間隔でdelaying request(リクエストの遅延)と出ています。設定した動作とログの内容が一致しています。
2024/03/05 15:32:01 [warn] 1695#0: *27 delaying request, excess: 0.988, by zone "dos", client: XXX.XXX.XXX.XXX, server: XXX.XXX.XXX.XXX, request: "GET /testimg.jpg HTTP/1.0", host: "XXX.XXX.XXX.XXX"
2024/03/05 15:32:02 [warn] 1695#0: *28 delaying request, excess: 0.988, by zone "dos", client: XXX.XXX.XXX.XXX, server: XXX.XXX.XXX.XXX, request: "GET /testimg.jpg HTTP/1.0", host: "XXX.XXX.XXX.XXX"
2024/03/05 15:32:03 [warn] 1695#0: *29 delaying request, excess: 0.987, by zone "dos", client: XXX.XXX.XXX.XXX, server: XXX.XXX.XXX.XXX, request: "GET /testimg.jpg HTTP/1.0", host: "XXX.XXX.XXX.XXX"

serverブロックのlimit_reqでnodelayを指定した場合
アクセスログの先頭から6件抜粋しています。
nodelayを指定した場合、遅延処理を行わないため全てのアクセスが 07/Mar/2024:15:53:18 に処理されています。
burstが1ですので3回目のアクセスからステータスコード「429」を返しています。内1件は別IPアドレスからのアクセスですが、制限中のIPアドレスとは異なる為、問題なくアクセスが可能です。

XXX.XXX.XXX.XXX - - [07/Mar/2024:15:53:18 +0900] "GET /testimg.jpg HTTP/1.0" 200 86139 "-" "Wget/1.12 (linux-gnu)" "-"
XXX.XXX.XXX.XXX - - [07/Mar/2024:15:53:18 +0900] "GET /testimg.jpg HTTP/1.0" 200 86139 "-" "Wget/1.12 (linux-gnu)" "-"
XXX.XXX.XXX.XXX - - [07/Mar/2024:15:53:18 +0900] "GET /testimg.jpg HTTP/1.0" 429 178 "-" "Wget/1.12 (linux-gnu)" "-"
XXX.XXX.XXX.XXX - - [07/Mar/2024:15:53:18 +0900] "GET /testimg.jpg HTTP/1.0" 429 178 "-" "Wget/1.12 (linux-gnu)" "-"
# IPアドレスYYY.YYY.YYY.YYYはIPアドレスでのアクセス数制限を受けていないためアクセスできます
YYY.YYY.YYY.YYY - - [07/Mar/2024:15:53:18 +0900] "GET /testimg.jpg HTTP/1.0" 200 86139 "-" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36"
XXX.XXX.XXX.XXX - - [07/Mar/2024:15:53:18 +0900] "GET /testimg.jpg HTTP/1.0" 429 178 "-" "Wget/1.12 (linux-gnu)" "-"
エラーログを確認するとlimiting requests(リクエストの制限)となっており、アクセス制限されてステータスコード「429」を返した動作と一致していることが確認できます。
2024/03/07 15:53:18 [error] 1033#0: *3 limiting requests, excess: 1.974 by zone "dos", client: XXX.XXX.XXX.XXX, server: XXX.XXX.XXX.XXX, request: "GET /testimg.jpg HTTP/1.0", host: "XXX.XXX.XXX.XXX"
2024/03/07 15:53:18 [error] 1033#0: *4 limiting requests, excess: 1.967 by zone "dos", client: XXX.XXX.XXX.XXX, server: XXX.XXX.XXX.XXX, request: "GET /testimg.jpg HTTP/1.0", host: "XXX.XXX.XXX.XXX"
2024/03/07 15:53:18 [error] 1033#0: *5 limiting requests, excess: 1.961 by zone "dos", client: XXX.XXX.XXX.XXX, server: XXX.XXX.XXX.XXX, request: "GET /testimg.jpg HTTP/1.0", host: "XXX.XXX.XXX.XXX"

DoS攻撃対策をしてみて

運用環境でも類似の対策を行いました。リクエスト判定処理が追加されるため、通常アクセスが多い時間帯に閲覧速度が落ちる事を懸念しましたが、そこは流石Nginx、体感できるような速度遅延は発生しませんでした。

Dos攻撃対策の効果

  • DoS攻撃によるサイトダウンを防ぐことが出来た
  • DoS攻撃による不要なトラフィックを抑えることができるようになった
  • 結果がログに出力されるため不審なアクセスを検出しやすくなった

実際の運用での注意点

全体に対してリミットを設けてしまうと、画像/css/font/javascriptなどのアクセスが集中しがちなファイルまで対象となってしまい、適切な設定が出来ません。
運用環境では下記の様に特定のコンテンツに絞って制限を設けると良いでしょう。

server {
 location ~* ^/[コンテンツ]/ {
  limit_req zone=lrz burst=1;
  limit_req_status 429;
 }
}

リミット値に気を付ければとても手軽にDoS攻撃対策が出来ます!
この記事が、少しでもDoS攻撃でお困りの方の参考になれば幸いです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次