はじめに
Web上でサービスを公開していると DDoS 攻撃など何らかの不正な攻撃の対象となるリスクは避けることが出来ません。
Fastly を導入することでレイヤー3およびレイヤー4に対する攻撃はエッジで自動的にブロックすることが出来ますが、レイヤー7に対する攻撃はブロックするためのルールを Varnish Configuration Language (VCL) を利用して作成する必要があります。
この記事では攻撃に備えて取得しておくと役に立つ変数の情報や、攻撃を受けた際に迅速に対処するために事前に準備しておくと便利な設定について紹介します。
考慮すべき変数
Fastly ではリアルタイムでログを送信することが出来るので、ログを取得しておくと万が一の攻撃の際にもどのような攻撃が行われているかをリアルタイムに確認することが出来ます。
ここでは攻撃を分析したり、実際に攻撃リクエストをブロックするために使用することが出来る代表的な変数について紹介します。
req.url
クライアントから送信されたフルリクエストパス (クエリ文字列を含む)。
req.url.qs
req.url のクエリ文字列部分
req.method
リクエストの HTTP メソッド (GET POST PUT など)
req.http.user-agent
クライアントから送信された User-Agent ヘッダー
req.http.referer
クライアントから送信された Referer ヘッダー
req.http.accept-language
クライアントから送信された Accept-Language ヘッダー
req.http.via
クライアントから送信された Via ヘッダー
req.http.content-length
クライアントから送信された Content-Length ヘッダー。
req.body_bytes_read
実際に受信したリクエストの body のサイズ
client.ip
エッジノードで確認されたクライアント IP (ACL で利用可能)
req.http.Fastly-Client-IP
エッジノードで確認されたクライアントIP(シールドノード用)
client.geo.country_code
この client.ip に対して解決された ISO 3166-1 alpha 2 国コード
client.as.number
この client.ip に対して解決されたAS(自律システム)番号
上記の変数はデフォルトの LogFormat(common log形式)に含まれているものや冗長な情報もあるのですべての情報を取得する必要はありませんが、上記のような変数をログに取得することを検討してみるとよいと思います。
上記のすべての情報とその他の代表的なキャッシュステータスなどの情報(server.datacenter, fastly_info.state, resp.response, time.to_first_byte, time.elapsed)や後述のブロックした場合のブロック理由をログに取得したい場合のポータルの Log format に指定する Format は以下のようになります。
%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i" %{req.http.host}V %{if(req.http.accept-language, req.http.accept-language, "-")}V %{if(req.http.via, req.http.via, "-")}V %{if(req.http.content-length, req.http.content-length, "-")}V %{req.body_bytes_read}V %{client.ip}V %{req.http.Fastly-Client-IP}V %{client.geo.country_code}V %{client.as.number}V %{server.datacenter}V %{fastly_info.state}V %{resp.response}V %{time.to_first_byte}V %{time.elapsed}V %{if(req.http.x-ban-reason, req.http.x-ban-reason, "-")}V
if(req.http.xxx, req.http.xxx, "-")
はリクエストに xxx
ヘッダーがある場合はその値を、ない場合は -
を記録する、という意味になります。
攻撃をブロックする VCL コード
続いて攻撃を受けた場合になんらかの攻撃トラフィックのパターンが判明した場合、そのパターンを VCL でブロックする設定を追加する方法を説明します。
設定はポータルの UI からも可能ですが、VCL Snippet または Custom VCL などを通じて直接コードを追加するほうが簡単です。
通常このようなリクエストをブロックする設定は vcl_recv(リクエストを受け付けたタイミング)の一番最初のタイミングで実施するのが望ましいです。
Snippet を使うのであれば Priority に 1 や 0 など低い数字を指定して優先的に実行されるようにしましょう。また Generated VCL のリンクから生成された VCL を確認し、このコードより上に処理の実行を邪魔する別の処理が入っていないことを確認しましょう。
ブロックする設定は例えば以下のようなコードになります。
if (req.http.user-agent~ "bad-useragent" &&
(client.as.number == 49 || client.as.number == 50)) {
error 403 "Forbidden";
}
この例だとリクエストの送信元の AS 番号が 49 か 50、 かつ user-agent に bad-useragent
という文字列を含むリクエストに 403 を返却するという設定になります。
攻撃に備えた VCL コードを予め追加する
攻撃を受けた際に上記のようなコードを設定に追記してもよいですが、攻撃を受けている際にリクエストをブロックするコードを間違いなく迅速に追加するのはなかなか難しい場合もあります。
そこで予め攻撃に備えたコードを配信設定に行っておくと緊急時に設定ミスなどにより別の問題が発生するリスクを下げることが出来ます。
ここでは一般的に考えられる攻撃に備えた VCL コードのサンプルを紹介します。ポイントとしては以下になります。
・処理は vcl_init、vcl_recv, vcl_error に分かれて記述します。
・ブロック対象を追加する際に出来るだけ Service のバージョンを更新しなくても済むように、可能なものについては Edge Dictionary を利用しています。
それでは順番にコードの説明をしていきます。
vcl_init
acl allow_acl {
"x.x.x.1";
}
acl ban_acl {
"x.x.x.100"/16; # Subnet range
"y.y.y.y"; # Single IP
"FE80:0000:0000:0000:0202:B3FF:FE1E:8329"; # ipv6 address
}
table ban_country {
"US": "true",
"JP": "true",
"DE": "true",
}
ここではブロックに利用する情報を3つの Edge Dictionary テーブルに分けて保持しています。
allow_acl: 特定の IP アドレスをブロック処理から除外するための ACL です。
ban_acl: 特定の IP アドレスをブロック対象とする場合の ACL です。
ban_country: ブロック対象の国を指定します
ここで紹介している Edge Dictionary のコードはポータルのUIか、API経由で追加することが出来ます。Snippet や Custom VCL でも上記のようなEdge Dictionary を直接追加することは可能ですが、その場合は UI で設定を変更することは出来ません。
vcl_recv
vcl_recv でリクエストをチェックして条件に合致した場合には 60x レスポンスを設定して vcl_error に処理を飛ばします。
# Apply the check only on first contact with the request
# Skip this check when client ip is in allow_acl
if (fastly.ff.visits_this_service == 0 && req.restarts == 0 && client.ip !~ allow_acl) {
# Check whether the client's IP is in the ban list
if (client.ip ~ ban_acl) {
error 601 "banned-ip";
# Check whether the client country is in the ban list
} else if (table.contains(ban_country, client.geo.country_code)){
error 602 "banned-country";
# Check whether the client user-agent matches regex to catch malicious user-agent
} else if (req.http.user-agent ~ "^malicious.*" || req.http.user-agent ~ "^curl.*"){
error 603 "banned-user-agent";
}
}
最初の if で処理が一度だけ行われるようにし、あわせてクライアントの IP が allow_acl に含まれる場合はブロックのチェック処理を行わないようにしています。
その後の if では以下の条件に一致するとブロック対象となり vcl_error に処理が移ります。vcl_error に移る前にそれぞれ異なるレスポンスコード(601, 602, 603)とエラーメッセージを設定していることに注目して下さい。
(client.ip ~ ban_acl)
ban_acl に IP が含まれる
(table.contains(ban_country, client.geo.country_code))
クライアントのアクセス元の国が ban_country で指定されている
(req.http.user-agent ~ "^malicious.*" || req.http.user-agent ~ "^curl.*")
リクエストの User-Agent が指定した正規表現(ここの例では malicious が curl で始まる User-Agent)に一致
IP アドレスのチェックおよび文字列の完全一致でチェックできる場合はブロック条件のチェックに Edge Dictionary を利用できます。
正規表現を利用してチェックしたい場合は VCL コードの if 文の中に直接条件を記述する必要があります。
vcl_error
if (http_status_matches(obj.status, "601,602,603")){
# store the reason of the ban for logging
set req.http.x-ban-reason = obj.status "-" obj.response;
set obj.status = 403;
set obj.response = "Forbidden";
set obj.http.Content-Type = "text/plain";
synthetic "Request rejected";
return (deliver);
}
最初に vcl_recv で設定されたレスポンスコードに一致するかどうかを確認し、一致した場合にエラーレスポンスを生成してクライアントに返却(vcl_deliver に処理を移動)しています。
ここでは 403 Forbidden
のレスポンスを返却していますが、もちろん 403 以外のレスポンスを返却しても問題ありません。
また、処理の最初に set req.http.x-ban-reason = obj.status "-" obj.response;
という処理が行われています。
この処理により、vcl_recv で設定された 60x などのレスポンスコードと ban の理由が req.http.x-ban-reason
というヘッダーに保存されます。
このヘッダーをログに追加することで、リクエストがブロックされた理由をログから確認することが出来ます。
以下のリンクから Fastly の VCL テストツールの Fastly Fiddle で上記のコードの動作をテストすることができます。
https://fiddle.fastlydemo.net/fiddle/cfc7067c
ここで紹介したコードではクライアントの IP と User-Agent のみをチェックしています。
設定をコピーしてコードを変更したりすることも可能ですので、必要に応じて他の要素(例えば Referer や AS)をチェックする同様のロジックを追加してテストしてみて下さい。
ブロック時にランダムなエラーを返却する
ここからは必ず必要な設定ではないのですが、リクエストをブロックした際にランダムなレスポンスを返却する設定を紹介します。
攻撃者はリクエストがブロックされたことを確認すると、リクエストのパラメータを変更してブロックされないリクエストで再度攻撃を行ってくる可能性があります。リクエストをブロックする際に返却するレスポンスをランダムにすることにより、攻撃者によるリクエストブロック条件の分析を困難にする効果が期待できます。
ランダムなレスポンスを返却したい場合も vcl_init, vcl_recv のコードは今までと同じで大丈夫です。vcl_error のコードを以下のように変更します。
まず最初にset var.Response = randomint(0, 2)
で0から2の数字をランダムに生成し、その値に応じて返却するレスポンスを制御しています。
if (http_status_matches(obj.status, "601,602,603")){
declare local var.Response INTEGER;
set var.Response = randomint(0, 2);
# store the reason of the ban for logging
set req.http.x-ban-reason = obj.status "-" obj.response;
if (var.Response == 0) {
set obj.status = 403;
set obj.response = "Forbidden";
set obj.http.Content-Type = "text/plain";
synthetic "Request rejected";
} else if (var.Response == 1) {
set obj.status = 307;
set obj.response = "Temporary redirect";
set obj.http.Location = "/login";
synthetic "";
} else if (var.Response == 2) {
set obj.status = 503;
set obj.response = "Service unavailable";
set obj.http.Content-Type = "text/plain";
synthetic "Sorry, right now our site is offline for maintenance.";
}
return (deliver);
}
上記では3種類のレスポンスですがもちろんこれを5種類、8種類など増やしたりすることも可能です。
上記のコードも以下の Fastly Fiddle ツールでテスト可能ですのでご確認下さい。
https://fiddle.fastlydemo.net/fiddle/a9b764cc
そのほかの DDoS 対策
Edge Rate Limiting
ここまで紹介した VCL コードは攻撃があった場合に防御するコードを追加する事後対策ですが、Fastly のオプション機能である Edge Rate Limitingを利用することで指定した閾値を超えるアクセスを自動的にブロックすることが可能です。
機能についてはオフィシャルドキュメントやこちらの記事をご参照下さい。
DDoS Protection and Mitigation Service
こちらも別のオプションとなりますが、Fastly のセキュリティチームとあらかじめ DDoS に備えた設定を行ったり発生した場合の対応を決めておき、実際に攻撃が発生した場合に共同で迅速に対策にあたるというサービスになります。
また、このオプションをご利用のお客様は DDoS が原因で発生したトラフィックの課金を免除するという保険のような役割ももっています。
詳細は以下のドキュメントをご参照下さい。
まとめ
この記事では Fastly で攻撃に備える設定について紹介しました。この設定はあくまでサンプルです。ブロックする条件などは自由に追加したり変更してサイトの特性にあわせた設定を作成する参考にして下さい。
また、必要に応じて Edge Rate Limiting や DDoS Protection and Mitgation Service の利用もご検討下さい。