How do I selectively override WAF rules for only specific URIs in CloudFormation?

0

There are some false positives in the AWS common rule sets that make it difficult to implement as-is. For example, if one of your URI endpoints accepts XML or HTML/HTML fragments, then you will very likely have problems with the CrossSiteScripting family of rules. Also, if you are publishing content, then there are some sequences that trigger LFI-family rules as well, like "../."
It sure seems sad to lose out of all of that CrossSiteScripting goodness for all of the other URIs that don't accept that type of data.

This post was very helpful and gave me hope that I could make it work for multiple URIs with a bit of work, but I wasn't able to get it to work for me for multiple URIs.

What's the most efficient way of handling this for just a few URIs?

1 Answer
0
Accepted Answer

After many hours on a call with the wonderful AWS employees in Cape Town, South Africa, and then some more trial and error, we have settled on this for our implementation:

The following is a complete example (except for actually associating the resources with WAF) with simple logging for easy debugging. Allow CrossSiteScripting_BODY and GenericLFI_BODY on /uri1, /uri2, and /uri3 only. Log WebACL traffic to CloudWatch so we don't have to worry about S3 permissions and AWS Glue and Athena to query WAF logs. Redact sensitive information. I threw in

AWSTemplateFormatVersion: '2010-09-09'
Description: WAF implementation that allows exclusions for specific URIs
Transform: AWS::Serverless-2016-10-31
Parameters:
  ProjectName:
    Description: Project Name
    Type: String

Resources:
  regionalWebAcl:
    Type: AWS::WAFv2::WebACL
    Properties:
      Scope: REGIONAL
      Name: !Sub ${ProjectName}-regional-webacl
      DefaultAction:
        Allow: {}
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: !Sub ${ProjectName}-regional-webacl-metric
        SampledRequestsEnabled: true
      Rules:
        - Name: !Sub ${ProjectName}-regional-webacl-AWSManagedRulesKnownBadInputsRuleSet
          Priority: 5
          OverrideAction:
            None: {}
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: !Sub ${ProjectName}-regional-webacl-AWSManagedRulesKnownBadInputsRuleSet-metric
            SampledRequestsEnabled: true
          Statement:
            ManagedRuleGroupStatement:
              Name: AWSManagedRulesKnownBadInputsRuleSet
              VendorName: AWS
        - Name: !Sub ${ProjectName}-regional-webacl-AWSManagedRulesCommonRuleSet
          Priority: 10
          OverrideAction:
            None: {}
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: !Sub ${ProjectName}-regional-webacl-AWSManagedRulesCommonRuleSet-metric
            SampledRequestsEnabled: true
          Statement:
            ManagedRuleGroupStatement:
              Name: AWSManagedRulesCommonRuleSet
              VendorName: AWS
              RuleActionOverrides:
                - Name: CrossSiteScripting_BODY
                  ActionToUse:
                    Count: {}
                - Name: GenericLFI_BODY
                  ActionToUse:
                    Count: {}
        - Name: !Sub ${ProjectName}-regional-webacl-reblock
          Priority: 15
          RuleLabels:
            - Name: reblock
          Statement:
            AndStatement:
              Statements:
                - OrStatement:
                    Statements:
                      - LabelMatchStatement:
                          Key: awswaf:managed:aws:core-rule-set:CrossSiteScripting_Body # Pay very careful attention to the casing, it's NOT CrossSiteScripting_BODY
                          Scope: LABEL
                      - LabelMatchStatement:
                          Key: awswaf:managed:aws:core-rule-set:GenericLFI_Body  # Pay very careful attention to the casing, it's NOT GenericLFI_BODY
                          Scope: LABEL
                - NotStatement:
                    Statement:
                      RegexMatchStatement:
                        FieldToMatch:
                          UriPath: {}
                        RegexString: ^.*\/(?:uri1|uri2|uri3)$ # Note that this regex is supposed to (according to AWS support that I received) match on the whole field, not just part of it.  Also notice that you don't specify the leading forward slash and the trailing slash with common regex flags.  If you need lowercase, then use a TextTransformation to do that.
                        TextTransformations:
                          - Priority: 0
                            Type: NONE
          Action:
            Block: {}
          VisibilityConfig:
              CloudWatchMetricsEnabled: true
              MetricName: !Sub ${ProjectName}-regional-webacl-reblock-metric
              SampledRequestsEnabled: true

  regionalWebAclLoggingConfiguration:
    Type: AWS::WAFv2::LoggingConfiguration
    Properties:
      LogDestinationConfigs:
        - !GetAtt regionalWebAclLogGroup.Arn
      ResourceArn: !GetAtt regionalWebAcl.Arn
      RedactedFields:
        - SingleHeader:
            Name: authorization
        - QueryString: {} # If you use an auth query string parameter, redact the whole query string
      LoggingFilter:
        DefaultBehavior: DROP
        Filters:
          - Behavior: KEEP
            Requirement: MEETS_ANY
            Conditions:
              - ActionCondition:
                  Action: BLOCK
          - Behavior: KEEP
            Requirement: MEETS_ANY
            Conditions:
              - ActionCondition:
                  Action: COUNT


  regionalWebAclLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Properties:
      LogGroupName: !Sub aws-waf-logs-${ProjectName}-regional-webacl
      RetentionInDays: 30

Outputs:
  regionalWebAclArn:
    Value: !GetAtt regionalWebAcl.Arn
  regionalWebAclLogGroupArn:
    Value: !GetAtt regionalWebAclLogGroup.Arn

Please note that the rule names and rule labels are identical in the characters used, but not in the casing, so you cannot simply copy and paste a rule name from this documentation

Regarding RegEx testing, they recommended this website in PCRE2 mode: https://regex101.com/

answered a year ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions