AWS WAFで攻撃された際にIPを遮断する対応・通知の実装

本記事について

本記事は、2024年3月に社内で実施した勉強会の内容をもとに、社外公開用テックブログ向けに加筆・再構成したものです。
記載の内容は執筆当時の情報であり、現在の仕様やベストプラクティスと異なる可能性があります。
実装にあたっては、必ず最新の公式ドキュメントをご確認いただくようお願いいたします。

はじめに

本記事では、AWS WAFを活用して攻撃と判断されるアクセスを自動ブロックし、リアルタイムで検知・通知を行う仕組みの構築方法をご紹介します。
あわせて、実際に遮断されたIPアドレスを特定・可視化する実装についても解説します。

目次

関連記事

あわせて読みたい
WEBサービスのセキュリティ対策とAWSでできる対策 この記事について 本記事は、2023年9月に社内で実施した勉強会の内容を基に、社外向けに再編集したものです。記載の内容は執筆当時の情報であり、現在の仕様やベストプ...

システム構成と構築のポイント

  • Lambda作成
    • IAMロールに以下を追加します。
      • AmazonSNSFullAccess
      • AWSWAFReadOnlyAccess
  • SNS作成
    • Lambdaとの連携が目的であるため、タイプは「スタンダード」を選択します。(FIFOは不要です。)
  • WAF作成
    • WAFのWeb ACLに新しいルールを追加します。
      Rule builderから「Rate-based rule」を選択し、特定のIPからの過剰なリクエストを制限します。
  • SESの設定
    • 通知メール送信のため、Amazon SESで送信元ドメインやメールアドレスの検証(Identityの作成)を行います。

自動検知・通知のワークフロー

今回は以下のようなフローで、検知から通知までを自動化しました。

  1. CloudWatchにて、WAFの BlockedRequestに対して設定を行い、アラームを作成
  2. アラームを検知したら、SNSへ連携
  3. SNSからLambdaを起動
  4. Lambdaからslackメッセージとメールを送信して通知

参考URL:
Qiita, @yamamoto_y, CloudFormationでCloudWatchAlermを作成するhttps://qiita.com/yamamoto_y/items/dcbcb0bf9fcf1a456c2a(2024年3月参照)

WAF Rule

{
  "Name": "IP_Blocks",
  "Priority": 0,
  "Statement": {
    "RateBasedStatement": {
      "Limit": 2000,   ## アクセス数
      "EvaluationWindowSec": 300,
      "AggregateKeyType": "IP"
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "IP_Blocks"
  }
}

IPがブロックされているか動作確認

# IPブロック確認
$ aws wafv2 get-rate-based-statement-managed-keys \\ 
    --scope REGIONAL \\
    --web-acl-name {ACL名} \\                     
    --web-acl-id {ACL ID} \\
    --rule-name {ルール名}
{
    "ManagedKeysIPV4": {
        "IPAddressVersion": "IPV4",
        "Addresses": []
    },
    "ManagedKeysIPV6": {
        "IPAddressVersion": "IPV6",
        "Addresses": []
    }
}

# 連続アクセス
$ ab -c 1 -n 5  -A basic_auth:pass <https://hogehoge.com>

Server Software:        
Server Hostname:        hogehoge.com
︙
Complete requests:      100
Failed requests:        0
︙

# IPブロック再確認
$ aws wafv2 get-rate-based-statement-managed-keys \\  
    --scope REGIONAL \\
    --web-acl-name {acl-name} \\
    --web-acl-id {acl-id} \\
    --rule-name IP_Blocks
{
    "ManagedKeysIPV4": {
        "IPAddressVersion": "IPV4",
        "Addresses": [
            "xxx.xxx.xxx.xxx/32"   ←IPが追加されている
        ]
    },
    "ManagedKeysIPV6": {
        "IPAddressVersion": "IPV6",
        "Addresses": []
    }
}

# 連続アクセス再確認
$ ab -c 1 -n 5  -A basic_auth:pass <https://hogehoge.com>

Server Hostname:        hogehoge.com
Server Port:            443
︙
Complete requests:      100
Failed requests:        0
Non-2xx responses:      100  ← ここにカウントが増えている
︙

ブロック後のアクセス画面

WAFの詳細

IPブロックに関しては、通常10分程度で自動解除されます。
誤検知の際も手動解除の手間はありませんが、永続化を実行する場合には、手動登録や、組み込みを検討する必要があります。

参考URL:
DevelopersIO, 阿部洸樹, レートベースルールで検知したIPをIP Setに登録する
https://dev.classmethod.jp/articles/add-ratebase-rule-deny-ip/(2024年3月参照)

通知機能の実装

遮断されたIPを取得して、IP含めて通知します。
Lambdaを活用してWAFから直接IPアドレスを取得し、メッセージに含めることで即座に調査を開始できるようにします。

メール送信にはSESを用いる

参考URL:
Zenn, kuryu, LambdaとSESを使ってサーバレスでEメール送信してみた
https://zenn.dev/kuryu8/articles/8af37decb19e9f(2024年3月参照)

Lambdaでの遮断IP取得

参考URL:
AWS公式ドキュメントhttps://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/wafv2/client/get_rate_based_statement_managed_keys.html(2024年3月参照)

import json
import os
import logging
import base64
import boto3

from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

REGION = "ap-northeast-1"

def send_email(source, to, subject, body):
    client = boto3.client('ses', region_name=REGION)

    response = client.send_email(
        Source=source,
        Destination={
            'ToAddresses': [
                to,
            ]
        },
        Message={
            'Subject': {
                'Data': subject,
            },
            'Body': {
                'Text': {
                    'Data': body,
                },
            }
        }
    )
    
    return response

def lambda_handler(event, context):
		# 遮断されたIP取得
    web_acl_name = os.environ.get('WEB_ACL_NAME')
    web_acl_id = os.environ.get('WEB_ACL_ID')
    rule_name = os.environ.get('RULE_NAME')
    client = boto3.client('wafv2')
    
    print(web_acl_name)
    print(web_acl_id)
    print(rule_name)
    response = client.get_rate_based_statement_managed_keys(
        Scope='REGIONAL',
        WebACLName=web_acl_name,
        WebACLId=web_acl_id,
        RuleName=rule_name
    )
    dict_ip = response["ManagedKeysIPV4"]["Addresses"]
    print(dict_ip)

    ip_list = ""
    for ip in dict_ip:
        print(ip)
        ip_list += ip + ", "

    subject = '【***】ALBでDOS攻撃を検知しました。'
    text = "遮断したIPは" + ip_list + "です。"

    print(subject)
    print(text)
    
    # slack送信
    SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
    color = "#FF0000"

    slack_message = {
        'text': "*%s*" % (subject),
        'attachments': [
            {
                'color': color,
                'text': text
            }
        ]
    }
    
    req = Request(SLACK_WEBHOOK_URL, json.dumps(slack_message).encode("utf-8"))
    try:
        response = urlopen(req)
        response.read()
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)
    
    print(slack_message)
    print("slack通知を行いました")

    # メール送信
    SENDER_MAIL = os.environ.get('SENDER_MAIL')
    RECIPIENT_MAIL = os.environ.get('RECIPIENT_MAIL')
    to_mail = RECIPIENT_MAIL.split(',')
    
    for to in to_mail:
        send_email_to = send_email(SENDER_MAIL, to, subject, text)
        print("Send ResponseMetadata:" + json.dumps(send_email_to, ensure_ascii=False))
        print(to + "へメール通知を行いました")

IP遮断

5分間で2000アクセスで遮断される形で設定をし、実際に遮断されることを確認しました。
ただし、実測値では2000アクセスに到達した後、少々時間が経ってから(3000アクセス程度まで到達してから)遮断されているように見えます。
これはWAFの集計タイミングや反映までのラグに起因するものと考えられます。

SES

SESの管理画面で「設定」から「メール着信」を開き、送信元メールアドレスの検証(Verify)を実行します。

テストメールが送信されます。

注意点

IPが取得できないケースについて

原因は特定できていませんが、遮断されたIPアドレスを取得できないケースが確認されました。

本構成ではCloudWatchアラームの発報をトリガーとして通知を行っているため、アラーム発報までのラグにより、その間にIPブロックが解除されている可能性があります。

通知が行われないケースについて

チェック間隔を1分に設定していても、CloudWatchでは最短で5分間アラーム状態が継続する仕様のため、その間は新たな通知が送信されない場合があります。

参考URL:
DevelopersIO, M.Shimizu, CloudWatch Alarm で、アラーム状態になったまましばらくOK状態に戻りません。原因と対処法を教えてください
https://dev.classmethod.jp/articles/tsnote-cloudwatch-alarm-does-not-transition-to-ok(2024年3月参照)

参考

●WAF→IPブロック→通知
Qiita, @syogun, AWS WAFのRate LimitがブロックしたIPアドレスをSlackに通知する
https://qiita.com/syogun/items/fca50498c79ac477273c(2024年3月参照)

●SNS→Lambda
DevelopersIO, 木田亮介, SNS経由でLambdaを動かしてCloudWatchLogsにメッセージを書き込むhttps://dev.classmethod.jp/articles/run-lambda-via-sns-and-write-log-to-cwl(2024年3月参照)

●WAFのIPブロック設定
DevelopersIO, Funa, AWS WAF でアクセス数が一定回数を超えた IP アドレスを自動的にブラックリストに追加させる方法
https://dev.classmethod.jp/articles/tsnote-aws-waf-autoblock(2024年3月参照)

備考

エンジニア女子の自習室, 【AWS初学者向け・図解】AWS WAFとAWS Shieldの違いとは?現役エンジニアがわかりやすく解説 – DDoS攻撃
https://o2mamiblog.com/aws-waf-shield-beginner/#toc2(2024年3月参照)

AWS公式ドキュメント – CloudWatch 異常検出の使用https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/CloudWatch_Anomaly_Detection.html(2024年3月参照)

ナレコムAWSレシピ, Amazon Cloudwatch Anomaly Detectionとは
https://recipe.kc-cloud.jp/archives/16898(2024年3月参照)

役に立ったらシェアしていただけると嬉しいです
  • URLをコピーしました!
  • URLをコピーしました!
目次