ちょっと話題の記事

Log4jの脆弱性を緩和するAWS WAFをCloudFormationで設定してみた

AWSのマネージドルールを利用して Log4jの脆弱性(CVE-2021-44228)を緩和するAWS WAFを、CloudFormatinで作成してみました。
2021.12.11

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWSチームのすずきです。

Log4jの脆弱性を緩和するため、AWSのマネージドルールに含まれる「Log4JRCE」のみ利用するAWS WAF、 CloudFormatinで設置する機会がありましたので紹介させて頂きます。

WAF設定

WebACL

  • ELB(ALB)の保護で利用するため、「Scope: REGIONAL」としました。
  • AWSのマネージドルール「AWSManagedRulesKnownBadInputsRuleSet」の最新バージョンを利用する指定を行いました。
  • 誤検知による副作用を最小化するため、「Log4JRCE」以外のルールに該当したリクエストは受け入れる除外設定を行いました。
  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: !Sub '${AWS::StackName}-webacl'
      DefaultAction:
        Allow: {}
      Description: AWS WAFv2 WebACL with only Log4JRCE
      Scope: REGIONAL
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: !Sub '${AWS::StackName}'
        SampledRequestsEnabled: false
      Rules:
        - Name: AWS-AWSManagedRulesKnownBadInputsRuleSet
          Priority: 1
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesKnownBadInputsRuleSet
              ExcludedRules:
                - Name: Host_localhost_HEADER
                - Name: PROPFIND_METHOD
                - Name: ExploitablePaths_URIPATH
          OverrideAction:
            None: {}
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: !Sub '${AWS::StackName}-AWSManagedRulesKnownBadInputsRuleSet'
            SampledRequestsEnabled: true

LoggingConfiguration

  • AWS WAFログの出力先は、S3へ直接出力する設定としました。
  • S3費用を抑制するためフィルタリング設定を実施、「BLOCK」ログのみ保存する設定としました。
  LoggingConfiguration:
    Type: AWS::WAFv2::LoggingConfiguration
    DependsOn: S3Bucket
    Properties:
      ResourceArn: !GetAtt 'WebACL.Arn'
      LogDestinationConfigs:
        - !GetAtt 'S3Bucket.Arn'
      LoggingFilter:
        DefaultBehavior: DROP
        Filters:
          - Behavior: KEEP
            Conditions:
              - ActionCondition:
                  Action: BLOCK
            Requirement: MEETS_ALL

S3設定

  • AWS WAF出力先の要件を満たすため、「aws-waf-logs」で開始するバケット名としました。
  • AWS WAFログの書き込み許可を付与するため、バケットポリシーを設定しました。
  • ストレージコスト抑制のため、一定期間で削除を行うライフサイクル設定を行いました。
  • ログに秘匿情報が含まれる可能性に考慮し、デフォルトの暗号化とパブリック公開を抑制する指定を行いました。
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'aws-waf-logs-${AWS::StackName}-${AWS::Region}-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      LifecycleConfiguration:
        Rules:
          - Id: ExpirationInDays
            Status: Enabled
            ExpirationInDays: 45
          - Id: NoncurrentVersionExpiration
            Status: Enabled
            NoncurrentVersionExpirationInDays: 7
          - Id: ExpiredObjectDeleteMarker
            Status: Enabled
            ExpirationInDays: 7
          - Id: AbortIncompleteMultipartUpload
            Status: Enabled
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 3
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref 'S3Bucket'
      PolicyDocument:
        Id: !Sub '${AWS::StackName}-BucketPolicy'
        Statement:
          - Sid: AWSLogDeliveryWrite
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action:
              - s3:PutObject
            Resource:
              - !Sub 'arn:aws:s3:::${S3Bucket}/*'
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control
          - Sid: AWSLogDeliveryAclCheck
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action:
              - s3:GetBucketAcl
            Resource:
              - !Sub 'arn:aws:s3:::${S3Bucket}'

WAF有効化

AWSコンソールを利用、「Associated AWS resources」より、AWS WAFの保護を有効化しました。

保護対象のELB (ALB) を指定しました。

テスト

curlコマンドを利用して、AWS WAFによりブロックされる事を確認しました。

$ curl http:/waftest-00000.ap-northeast-1.elb.amazonaws.com -H 'User-Agent: ${jndi' -o /dev/null -w '%{http_code}\n' -s
403

ブロックログ確認

ログ出力先として指定したS3バケットに、以下のログがJSON形式で参照できました。

{
  "timestamp": 1639205931267,
  "formatVersion": 1,
  "webaclId": "arn:aws:wafv2:ap-northeast-1:000000000000:regional/webacl/waf-webacl/0000-0000-0000-0000",
  "terminatingRuleId": "AWS-AWSManagedRulesKnownBadInputsRuleSet",
  "terminatingRuleType": "MANAGED_RULE_GROUP",
  "action": "BLOCK",
  "terminatingRuleMatchDetails": [],
  "httpSourceName": "ALB",
  "httpSourceId": "000000000000-app/waftest/000000000000",
  "ruleGroupList": [
    {
      "ruleGroupId": "AWS#AWSManagedRulesKnownBadInputsRuleSet",
      "terminatingRule": {
        "ruleId": "Log4JRCE",
        "action": "BLOCK",
        "ruleMatchDetails": null
      },
      "nonTerminatingMatchingRules": [],
      "excludedRules": null
    }
  ],
  "rateBasedRuleList": [],
  "nonTerminatingMatchingRules": [],
  "requestHeadersInserted": null,
  "responseCodeSent": null,
  "httpRequest": {
    "clientIp": "0.0.0.0",
    "country": "JP",
    "headers": [
      {
        "name": "Host",
        "value": "waftest-00000.ap-northeast-1.elb.amazonaws.com"
      },
      {
        "name": "Accept",
        "value": "*/*"
      },
      {
        "name": "User-Agent",
        "value": "${jndi"
      }
    ],
    "uri": "/",
    "args": "",
    "httpVersion": "HTTP/1.1",
    "httpMethod": "GET",
    "requestId": "1-0000-0000"
  },
  "labels": [
    {
      "name": "awswaf:managed:aws:known-bad-inputs:Log4JRCE"
    }
  ]
}

まとめ

Log4jの脆弱性対策としてAWS WAFの利用を検討されている場合、今回のテンプレートをお試しください。

AWS WAFルールの誤判定が発生、副作用回避が必要となった場合には、S3に記録されたブロックログより詳細を確認を行い、IPアドレスなどを利用した除外設定をお試しください。

複数のマネージドルール利用、副作用の除外設定

サンプルテンプレート全文(YAML)

AWSTemplateFormatVersion: '2010-09-09'
Description: AWS WAFv2 configuration for Log4JRCE blocking and S3 logging
Resources:
  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: !Sub '${AWS::StackName}-webacl'
      DefaultAction:
        Allow: {}
      Description: AWS WAFv2 WebACL with only Log4JRCE
      Scope: REGIONAL
      Tags:
        - Key: StackName
          Value: !Sub '${AWS::StackName}'
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: !Sub '${AWS::StackName}'
        SampledRequestsEnabled: false
      Rules:
        - Name: AWS-AWSManagedRulesKnownBadInputsRuleSet
          Priority: 1
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesKnownBadInputsRuleSet
              ExcludedRules:
                - Name: Host_localhost_HEADER
                - Name: PROPFIND_METHOD
                - Name: ExploitablePaths_URIPATH
          OverrideAction:
            None: {}
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: !Sub '${AWS::StackName}-AWSManagedRulesKnownBadInputsRuleSet'
            SampledRequestsEnabled: true
  LoggingConfiguration:
    Type: AWS::WAFv2::LoggingConfiguration
    DependsOn: S3Bucket
    Properties:
      ResourceArn: !GetAtt 'WebACL.Arn'
      LogDestinationConfigs:
        - !GetAtt 'S3Bucket.Arn'
      LoggingFilter:
        DefaultBehavior: DROP
        Filters:
          - Behavior: KEEP
            Conditions:
              - ActionCondition:
                  Action: BLOCK
            Requirement: MEETS_ALL
          #- Behavior: KEEP
          #  Conditions:
          #    - ActionCondition:
          #        Action: COUNT
          #  Requirement: MEETS_ANY

# --------------------------------------------- #
# S3 bucket configuration for WAF log output
# --------------------------------------------- #
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'aws-waf-logs-${AWS::StackName}-${AWS::Region}-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      LifecycleConfiguration:
        Rules:
          - Id: ExpirationInDays
            Status: Enabled
            ExpirationInDays: 45
          - Id: NoncurrentVersionExpiration
            Status: Enabled
            NoncurrentVersionExpirationInDays: 7
          - Id: ExpiredObjectDeleteMarker
            Status: Enabled
            ExpirationInDays: 7
          - Id: AbortIncompleteMultipartUpload
            Status: Enabled
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 3
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      Tags:
        - Key: StackId
          Value: !Sub '${AWS::StackId}'
      VersioningConfiguration:
        Status: Enabled
  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref 'S3Bucket'
      PolicyDocument:
        Id: !Sub '${AWS::StackName}-BucketPolicy'
        Statement:
          - Sid: AWSLogDeliveryWrite
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action:
              - s3:PutObject
            Resource:
              - !Sub 'arn:aws:s3:::${S3Bucket}/*'
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control
          - Sid: AWSLogDeliveryAclCheck
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action:
              - s3:GetBucketAcl
            Resource:
              - !Sub 'arn:aws:s3:::${S3Bucket}'

Outputs:
  WafAclArn:
    Value: !GetAtt 'WebACL.Arn'
  WafAclId:
    Value: !GetAtt 'WebACL.Id'