Log4jの脆弱性を緩和するAWS WAFをCloudFormationで設定してみた
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'