[アップデート] Amazon GuardDuty の Custom Detection Rules を試してみた

[アップデート] Amazon GuardDuty の Custom Detection Rules を試してみた

GuardDuty の Custom Detection Rules をドライランで有効化し、CloudWatch メトリクスへの出力とライブモードへの切り替えを検証してみました。操作の仕方を少し変えるだけで検知を回避できてしまう場合があることがわかりました。
2026.10.29

はじめに

2026 年 9 月、Amazon GuardDuty に Custom Detection Rules が追加されました。

これは、AMI の外部共有や VPC フローログの削除など、環境によっては日常的でも別の環境では侵害の兆候になるような操作を検知するルールのプリセットです。

https://aws.amazon.com/about-aws/whats-new/2026/09/guardduty-optional-detection-rules/

有効化したルールは、Finding を作成する「ライブモード」か、CloudWatch メトリクスだけを出力する「ドライランモード」のどちらかで動かします。ドライランモードは、ライブで有効化する前に「このアカウントでどれくらい一致するか」を測るためのモードです[1]。

この記事では、CloudTrail 管理イベントを対象にした 3 つのルールをドライランで有効化し、実際に操作してメトリクスに出るかを確かめます。

東京リージョンで使えるルール

ListCustomDetectionRules で一覧を取得したところ、東京リージョンのルールは 88 個で、CloudTrail 管理イベントが対象のものが 33 個、Runtime Monitoring のイベントが対象のものが 55 個でした。

us-east-1 には、東京リージョンにない organization-leave-attempt と organization-policy-disabled があります。AWS Organizations などを対象にしたルールは、そのサービスが動作するリージョンでのみ提供されるためだと思われます[2]。

各ルールの検知条件は、GetCustomDetectionRule の Definition.Expression に SQL で記載されています。今回試すルールは次の 3 つです。

ルール ID 重大度 検知条件の要点
vpc-flow-logs-deleted HIGH DeleteFlowLogs が成功した
ec2-user-data-modified HIGH ModifyInstanceAttribute が成功し、requestParameters['userData'] がある
ec2-security-group-public-ssh MEDIUM AuthorizeSecurityGroupIngress が成功し、toPort が 22 で送信元が /0
ec2-security-group-public-ssh の検知条件の全文
eventSource = 'ec2.amazonaws.com'
AND eventName = 'AuthorizeSecurityGroupIngress'
AND errorCode IS NULL
AND userIdentity.type != 'AWSService'
AND NOT contains(sourceIPAddress, 'amazonaws.com')
AND (
  (
    requestParameters['toPort'] = '22'
    AND (
      requestParameters['cidrIp'] LIKE '%/0'
      OR requestParameters['cidrIpv6'] LIKE '%/0'
    )
  )
  OR (
    requestParameters['ipPermissions'] LIKE '%"toPort":22%'
    AND (
      requestParameters['ipPermissions'] LIKE '%"cidrIp":"%/0"%'
      OR requestParameters['ipPermissions'] LIKE '%"cidrIpv6":"%/0"%'
    )
  )
)

検証環境を作る

検証用のリソースは CloudFormation で作り、操作には AWS CLI を使います。Custom Detection Rules のコマンドは AWS CLI v2.36.37 以降で使えます[3]。

CloudFormation テンプレート(cdr-dryrun.yaml)
AWSTemplateFormatVersion: "2010-09-09"
Description: GuardDuty Custom Detection Rules dry run verification

Parameters:
  AmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64

Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.99.0.0/24
      Tags:
        - Key: Name
          Value: cdr-dryrun

  Subnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc
      CidrBlock: 10.99.0.0/26
      Tags:
        - Key: Name
          Value: cdr-dryrun

  # SSH ルールの追加を試すためのセキュリティグループ
  TestSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: cdr-dryrun-test
      GroupDescription: cdr-dryrun test
      VpcId: !Ref Vpc

  # ユーザーデータの変更を試すためのインスタンス
  Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref AmiId
      InstanceType: t4g.nano
      SubnetId: !Ref Subnet
      SecurityGroupIds:
        - !Ref TestSecurityGroup
      Tags:
        - Key: Name
          Value: cdr-dryrun

  FlowLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: /cdr-dryrun/flowlogs
      RetentionInDays: 1

  FlowLogRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: cdr-dryrun-flowlogs
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: vpc-flow-logs.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: flowlogs
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - logs:DescribeLogGroups
                  - logs:DescribeLogStreams
                Resource: "*"

Outputs:
  VpcId:
    Value: !Ref Vpc
  SecurityGroupId:
    Value: !Ref TestSecurityGroup
  InstanceId:
    Value: !Ref Instance
  FlowLogGroupName:
    Value: !Ref FlowLogGroup
  FlowLogRoleArn:
    Value: !GetAtt FlowLogRole.Arn

ユーザーデータは停止中のインスタンスでしか変更できないため、スタックを作成したらインスタンスを停止しておきます。

aws cloudformation create-stack --stack-name cdr-dryrun \
  --template-body file://cdr-dryrun.yaml --capabilities CAPABILITY_NAMED_IAM
aws cloudformation wait stack-create-complete --stack-name cdr-dryrun

out() {
  aws cloudformation describe-stacks --stack-name cdr-dryrun \
    --query "Stacks[0].Outputs[?OutputKey=='$1'].OutputValue" --output text
}
VPC_ID=$(out VpcId)
SG_ID=$(out SecurityGroupId)
INSTANCE_ID=$(out InstanceId)
FLOW_LOG_ROLE_ARN=$(out FlowLogRoleArn)

aws ec2 stop-instances --instance-ids $INSTANCE_ID
aws ec2 wait instance-stopped --instance-ids $INSTANCE_ID

ドライランで挙動をチェックしてみる

ルールをアカウントに適用するための関連付けを作ります。

for RULE in vpc-flow-logs-deleted ec2-user-data-modified ec2-security-group-public-ssh; do
  aws guardduty create-custom-detection-rule-association --rule-id $RULE --mode DRY_RUN
done

応答から一部の項目を抜き出すと次のとおりで、ExpiresAt には作成からちょうど 14 日後の日時が入っていました。期限が切れると、ルールは自動で無効に戻ります。

{
    "RuleAssociation": {
        "RuleId": "vpc-flow-logs-deleted",
        "Mode": "DRY_RUN",
        "ExpiresAt": "2026-11-12T11:07:08+09:00"
    }
}

続いて、3 つのルールに引っかかりそうな操作を実行してみます。

# フローログを作成してすぐに削除する
FLOW_LOG_ID=$(aws ec2 create-flow-logs --resource-type VPC --resource-ids $VPC_ID \
  --traffic-type ALL --log-destination-type cloud-watch-logs \
  --log-group-name /cdr-dryrun/flowlogs --deliver-logs-permission-arn $FLOW_LOG_ROLE_ARN \
  --query "FlowLogIds[0]" --output text)
aws ec2 delete-flow-logs --flow-log-ids $FLOW_LOG_ID

# 停止中のインスタンスのユーザーデータを変更する
printf '#!/bin/bash\necho hello\n' > userdata.sh
aws ec2 modify-instance-attribute --instance-id $INSTANCE_ID \
  --user-data Value=$(base64 < userdata.sh | tr -d '\n')

# 0.0.0.0/0 からの SSH を許可し、すぐに取り消す
aws ec2 authorize-security-group-ingress --group-id $SG_ID --protocol tcp --port 22 --cidr 0.0.0.0/0
aws ec2 revoke-security-group-ingress --group-id $SG_ID --protocol tcp --port 22 --cidr 0.0.0.0/0

3 分後、AWS/GuardDuty 名前空間に CustomRuleDryRunSignalsMatched というメトリクスが 3 つ増えました!

aws cloudwatch list-metrics --namespace AWS/GuardDuty \
  --metric-name CustomRuleDryRunSignalsMatched

ディメンションは AccountId、Tactic、Technique、RuleName の 4 つで、単位は Count です。ルールごとに別のメトリクスになるため、ルール単位でグラフやアラームを作れます。

{
  "Namespace": "AWS/GuardDuty",
  "MetricName": "CustomRuleDryRunSignalsMatched",
  "Dimensions": [
    {"Name": "AccountId", "Value": "123456789012"},
    {"Name": "Tactic", "Value": "Defense Impairment"},
    {"Name": "Technique", "Value": "T1685.002 - Disable or Modify Tools: Disable or Modify Cloud Log"},
    {"Name": "RuleName", "Value": "VPCFlowLogsDeleted"}
  ]
}

1 分間隔の合計値をグラフにすると次のようになりました。01:01 の山は 3 つのルールの線が重なったもので、01:14 の山は後の節で対照として再実行したユーザーデータの変更です。

3 つのルールの CustomRuleDryRunSignalsMatched を 1 分間隔の合計で表示したグラフ。01:01 に 3 本とも 1 になり、01:14 に EC2UserDataModified だけが再び 1 になっている

3 つの操作は 2 つの分にまたがって実行しましたが、データ点はすべて同じ 1 分に入りました。データ点の時刻は操作の時刻ではなく、GuardDuty がまとめて評価した時刻なのだと思われます。

検知条件から外れる操作を試してみる

ドキュメントによると、同じ変更を API の別のパラメーターでも指定できる場合など、操作の仕方によってはルールを回避できてしまうと書かれています[4]。

ルールが検知しようとしている変更を、検知条件から外れる方法で 2 つ試してみます。

--attribute と --value でユーザーデータを変更する

modify-instance-attribute では、ユーザーデータを --user-data で渡す以外に、--attribute userData --value でも渡せます。試しに以下で実行してみました。

aws ec2 modify-instance-attribute --instance-id $INSTANCE_ID \
  --attribute userData --value $(base64 < userdata.sh | tr -d '\n')

しかし、いくら待っても EC2UserDataModified のデータ点は増えませんでした。--user-data で変更し直すとデータ点が増えたので、後者の渡し方だけが一致しなかったことになります。

CloudTrail の requestParameters を比べると、--attribute と --value で渡した場合には userData キーがありませんでした。

渡し方 requestParameters
--user-data {"instanceId": "i-0123456789abcdef0", "userData": "<sensitiveDataRemoved>"}
--attribute userData --value {"instanceId": "i-0123456789abcdef0", "attribute": "userData", "value": "<sensitiveDataRemoved>"}

ルールの条件は requestParameters['userData'] IS NOT NULL なので、キーの名前の違いで一致しなかったのだと考えられます。

全ポートを開放するルールを追加する

ec2-security-group-public-ssh の条件は toPort が 22 であることなので、景気良く 0〜65535 番のポートを 0.0.0.0/0 に開放してみました。

aws ec2 authorize-security-group-ingress --group-id $SG_ID --protocol tcp --port 0-65535 --cidr 0.0.0.0/0
aws ec2 revoke-security-group-ingress --group-id $SG_ID --protocol tcp --port 0-65535 --cidr 0.0.0.0/0

SSH も外部から到達できる設定ですが、CloudTrail には "toPort": 65535 と記録され、メトリクスは増えませんでした。このルールが検知するのは 22 番ポートを指定した開放操作で、SSH が外部から到達できる状態ではないということですね。

状態そのものを評価したい場合は、AWS Config や Security Hub CSPM のコントロールなど、構成を評価するサービスと組み合わせることになりそうです。

ライブモードに切り替える

最後に、vpc-flow-logs-deleted の関連付けをライブモードに更新してみます。

ASSOCIATION_ID=$(aws guardduty list-custom-detection-rule-associations \
  --rule-id vpc-flow-logs-deleted --query "RuleAssociations[0].AssociationId" --output text)
aws guardduty update-custom-detection-rule-association \
  --rule-id vpc-flow-logs-deleted --association-id $ASSOCIATION_ID --mode LIVE
aws guardduty get-custom-detection-rule-association \
  --rule-id vpc-flow-logs-deleted --association-id $ASSOCIATION_ID

更新後の関連付けは ID と CreatedAt がそのままで、ExpiresAt がなくなっていました。ドライランの関連付けを更新すれば、14 日の期限を気にせずそのままライブに移れます。

切り替えた後にフローログの作成と削除を 2 回繰り返すと、3 分ほどで次の Finding が 1 件作成されました。

項目 値
Type DefenseImpairment:EC2/DisableOrModifyTools
Severity 8.0
FeatureName CustomDetectionRule - CloudTrailManagementEvent
Count 2

2 回の削除が 1 件にまとめられ、Count が 2 になっています。ドキュメントのとおり、同じプリンシパルによる同じ戦術・サービス・手法のシグナルは 1 件に集約されるようです[5]。

発表時点で、35 個のルールに対して Finding タイプは 26 種類しかなく、Type だけではどのルールに一致したかを特定できない場合があります。Service.Detection.Sequence.Signals にはルール名の VPCFlowLogsDeleted が入っていたので、通知にはこの値も含めると良さそうです。

なお、ライブモード中の削除は CustomRuleDryRunSignalsMatched に現れず、ドライラン中に一致した操作からも Finding は作成されていませんでした。

おわりに

Custom Detection Rules のドライランでは、一致した件数だけがルールごとに CloudWatch メトリクスとして出力されます。どの操作が一致したかが知りたければ CloudTrail で調べることになります。

ユーザーデータの書き換えで試してみた通り、CloudTrail の特定のキーや値を条件にするルールは、その気になれば回避できてしまう可能性があります。ドライランは誤検知の量を測るだけでなく、検知してほしい操作が本当に検知されるかを確かめる場としても使えそうですね。

脚注
  1. Managing Custom Detection Rules - Amazon GuardDuty ↩︎

  2. Available Custom Detection Rules - Amazon GuardDuty ↩︎

  3. aws-cli/CHANGELOG.rst at 2.36.37 · aws/aws-cli ↩︎

  4. How Custom Detection Rules work - Amazon GuardDuty ↩︎

  5. How Custom Detection Rules work - Amazon GuardDuty ↩︎

この記事をシェアする

関連記事