AWS WAF String Inspection – Headers/QueryString/Body/Cookies

AWS WAF String Inspection - Headers/QueryString/Body/Cookies

AWS WAF String Inspection – Headers/QueryString/Body/Cookies

One of the features provided by AWS WAF is string match rule statements.

A string match statement indicates the string that you want AWS WAF to search for in a request, where in the request to search, and how. For example, you can look for a specific string at the start of any query string in the request or as an exact match for the request’s User-agent header.

String match rule statement

There are several objects that can be inspected by this rule, but in this case the following are covered

  • header
  • query string
  • body
  • cookie

Create a rule to block any incoming requests to the WAF that contain a specific string of characters in the above components.

Envorinment

Diagram of AWS WAF String Inspection - Headers/QueryString/Body/Cookies

Create a WAF Web ACL.
The Web ACL consists of rules defined by string matching statements.
Apply the created Web ACL to API Gateway.

Create an API Gateway and deploy Lambda on the backend.
When an HTTP request is received from a user, the API Gateway acts as an endpoint, calling a Lambda function instead and returning the result of the function’s execution to the user.
API Gateway is a REST API type.

The runtime environment for the Lambda function is Python 3.8.
The behavior of the function is simple, returning the contents of the event object when executed.

CloudFormation template files

The above configuration is built with CloudFormation.
The CloudFormation templates are placed at the following URL

https://github.com/awstut-an-r/awstut-soa/tree/main/05/003

Explanation of key points of template files

WAF Web ACL Rule Group

Resources:
  RuleGroup:
    Type: AWS::WAFv2::RuleGroup
    Properties:
      Capacity: 100
      Name: !Sub "${Prefix}-ComponentInspectionRuleGroup"
      Rules:
        - Action:
            Block: {}
          Name: !Sub "${Prefix}-HeadersInspectionRule"
          Priority: 0
          Statement:
            ByteMatchStatement:
              FieldToMatch:
                Headers:
                  MatchPattern:
                    All: {}
                  MatchScope: ALL
                  OversizeHandling: CONTINUE # default
              PositionalConstraint: CONTAINS
              SearchString: "awstut1"
              TextTransformations:
                - Priority: 0
                  Type: NONE
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: !Sub "${Prefix}-HeadersInspectionRule"
            SampledRequestsEnabled: true
        - Action:
            Block: {}
          Name: !Sub "${Prefix}-QueryStringInspectionRule"
          Priority: 10
          Statement:
            ByteMatchStatement:
              FieldToMatch:
                QueryString: {}
              PositionalConstraint: CONTAINS
              SearchString: "awstut2"
              TextTransformations:
                - Priority: 0
                  Type: NONE
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: !Sub "${Prefix}-QueryStringInspectionRule"
            SampledRequestsEnabled: true
        - Action:
            Block: {}
          Name: !Sub "${Prefix}-BodyInspectionRule"
          Priority: 20
          Statement:
            ByteMatchStatement:
              FieldToMatch:
                Body:
                  OversizeHandling: CONTINUE # default
              PositionalConstraint: CONTAINS
              SearchString: "awstut3"
              TextTransformations:
                - Priority: 0
                  Type: NONE
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: !Sub "${Prefix}-BodyInspectionRule"
            SampledRequestsEnabled: true
        - Action:
            Block: {}
          Name: !Sub "${Prefix}-CookieInspectionRule"
          Priority: 30
          Statement:
            ByteMatchStatement:
              FieldToMatch:
                Cookies:
                  MatchPattern:
                    All: {}
                  MatchScope: ALL
                  OversizeHandling: CONTINUE # default
              PositionalConstraint: CONTAINS
              SearchString: "awstut4"
              TextTransformations:
                - Priority: 0
                  Type: NONE
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: !Sub "${Prefix}-CookieInspectionRule"
            SampledRequestsEnabled: true
      Scope: REGIONAL
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: !Sub "${Prefix}-ComponentInspectionRuleGroup"
        SampledRequestsEnabled: true
Code language: YAML (yaml)

Define four rules for string checking in the rule group.

For basic information on WAF Web ACLs, please refer to the following pages.

https://awstut.com/en/2022/05/06/introduction-to-waf-web-acl-with-cloudformation-en

For information on how to apply WAF Web ACLs to API Gateway, please refer to the following page.

https://awstut.com/en/2022/06/04/apply-waf-web-acl-to-api-gateway-en

String Inspection Rules for Headers

The first rule is a string inspection rule for headers.

The ByteMatchStatement property allows you to define a string match rule statement.

Specify the component to be inspected with the FieldToMatch property.
Since this rule targets headers, set the property with the same name.
All headers can be included in the inspection by enabling the All property in the MatchPattern property.
Specifying “ALL” in the MatchScope property makes the keys and values in the header subject to inspection.
The OversizeHandling property sets the action for headers that exceed the amount WAF can inspect.

AWS WAF doesn’t support inspecting very large contents for the web request components body, headers, or cookies. The underlying host service has count and size limits on what it forwards to AWS WAF for inspection.

Handling oversize web request components

Restrictions on headers are as follows

Headers – AWS WAF can inspect at most the first 8 KB (8,192 bytes) of the request headers and at most the first 200 headers.

Handling oversize web request components

This time, by specifying “CONTINUE” for this property, it is set so that inspection is performed only on data within the size limit.

The PositionalConstraint property allows you to specify how to search for strings.
In this case, we specify “CONTAINS” and the condition is satisfied if it contains the specified string, regardless of its position.

Specify the string to be examined with the SearchString property.
In this rule, the string “awstut1” is to be searched.

If the TextTransformations property is specified, it can be converted to BASE64, for example, and then inspected.
In this case, by specifying “NONE”, no transformation is performed.

String Inspection Rule for Query String

The second rule is a string inspection rule for the query string.
Set the QueryString property in the FieldToMatch property.

Specify “awstut2” as the string to be inspected.

String Inspection Rule for Body

The third rule is a rule that performs string inspection on the body.
Set the Body property in the FieldToMatch property.

The body also has an upper limit on the size that can be inspected.
Set the OversizeHandling property within this property to “CONTINUE” to inspect only data within the size limit.

Specify “awstut3” as the string to be inspected.

String Inspection Rule for Cookies

The fourth rule is a string inspection rule for cookies.
Set the Cookies property in the FieldToMatch property.

By activating the All property in the MatchPattern property, all cookies can be scanned.
Specifying “ALL” in the MatchScope property makes the keys and values in the cookie subject to inspection.
Set the OversizeHandling property to “CONTINUE” to inspect only data within the size limit.

Specify “awstut4” as the string to be inspected.

(Reference) Lambda function

Resources:
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Code:
        ZipFile: |
          import json

          def lambda_handler(event, context):

            return {
              'statusCode': 200,
              'body': json.dumps(event)
            }
      FunctionName: !Sub "${Prefix}-function"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
Code language: YAML (yaml)

The code to be executed by the Lambda function in inline format.
For more information, please see the following page.

https://awstut.com/en/2022/02/02/3-parterns-to-create-lambda-with-cloudformation-s3-inline-container

The code to execute is as simple as returning an event object.
The content of the HTTP request can be confirmed from this object.

(Reference) REST type API Gateway

Resources:
  RestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      EndpointConfiguration:
        Types:
          - EDGE
      Name: !Ref Prefix

  Deployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn:
      - Method
    Properties:
      RestApiId: !Ref RestApi

  Resource:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !GetAtt RestApi.RootResourceId
      PathPart: !Sub "${Prefix}-resource"
      RestApiId: !Ref RestApi

  Stage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId: !Ref Deployment
      RestApiId: !Ref RestApi
      StageName: !Sub "${Prefix}-stage"

  Method1:
    Type: AWS::ApiGateway::Method
    Properties:
      AuthorizationType: NONE
      HttpMethod: GET
      Integration:
        ConnectionType: INTERNET
        Credentials: !GetAtt ApiGatewayRole.Arn
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${FunctionArn}/invocations"
      ResourceId: !Ref Resource
      RestApiId: !Ref RestApi

  Method2:
    Type: AWS::ApiGateway::Method
    Properties:
      AuthorizationType: NONE
      HttpMethod: POST
      Integration:
        ConnectionType: INTERNET
        Credentials: !GetAtt ApiGatewayRole.Arn
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${FunctionArn}/invocations"
      ResourceId: !Ref Resource
      RestApiId: !Ref RestApi

  ApiGatewayRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - apigateway.amazonaws.com
      Policies:
        - PolicyName: !Sub "${Prefix}-InvokeFunctionPolicy"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - lambda:InvokeFunction
                Resource:
                  - !Ref FunctionArn
Code language: YAML (yaml)

For basic information on REST-type API Gateways, please refer to the following page.

https://awstut.com/en/2022/05/22/create-rest-api-type-api-gateway-using-cloudformation-en

The point is to define two Method resources.
One is for GET requests and the other is for POST requests.
Both are configured to invoke the aforementioned Lambda function.

Architecting

Use CloudFormation to build this environment and check its actual behavior.

Create CloudFormation stacks and check the resources in the stacks

Create CloudFormation stacks.
For information on how to create stacks and check each stack, please see the following page.

https://awstut.com/en/2021/12/11/cloudformations-nested-stack

After reviewing the resources in each stack, information on the main resources created in this case is as follows

  • WAF Web ACL: soa-05-003-WebACL
  • WAF Web ACL Rule Group: soa-05-003-ComponentInspectionRuleGroup
  • API Gateway: soa-05-003
  • API Gateway endpoint: https://5qsyw758ji.execute-api.ap-northeast-1.amazonaws.com/soa-05-003-stage/soa-05-003-resource
  • Lambda function: soa-05-003-function

WAF Web ACLs are also checked from the AWS Management Console.

Detail of WAF 1.

You can see that this Web ACL is associated with API Gateway.

Detail of WAF 2.

You can see that a rule group has been set up for the ACL.

Check the rule group.

Detail of WAF 3.

Four rules have been created.

Review each rule.

Detail of WAF 4.
Detail of WAF 5.
Detail of WAF 6.
Detail of WAF 7.

These are the content rules for string checking for headers, query strings, bodies, and cookies, respectively.

Operation Check

Now that you are ready, check the operation of each rule.

String Inspection for Headers

Send HTTP requests with headers set with the curl command to the API Gateway.

Checking the rules for headers again, the content is to block traffic that contains “awstut1” in the header.

First, send a request that does not match the rule.

$ curl -H "test-header: awstut" https://5qsyw758ji.execute-api.ap-northeast-1.amazonaws.com/soa-05-003-stage/soa-05-003-resource | jq                      ...
{
  "multiValueHeaders": {
    "test-header": [
      "awstut"
    ],
    ...
  },
  ...
}
Code language: Bash (bash)

I sent a request with a header key name of “test-header” and a value of “awstut” and got a successful response.
This means that the traffic reached the API Gateway endpoint without being blocked by the WAF Web ACL and the backend Lambda function was executed.

Then send a rule matched request.

$ curl -H "test-header: awstut1" https://5qsyw758ji.execute-api.ap-northeast-1.amazonaws.com/soa-05-003-stage/soa-05-003-resource | jq
...
{
  "message": "Forbidden"
}
Code language: Bash (bash)

The response was different from the previous one.
This means that the string inspection rules for headers were applied and the request was blocked.

String Inspection for Query String

Send an HTTP request with a query string to the API Gateway using the curl command.

Checking the rules for query strings again, the content is to block traffic that contains “awstut2” in the query string.

First, send a request that does not match the rule.

$ curl https://5qsyw758ji.execute-api.ap-northeast-1.amazonaws.com/soa-05-003-stage/soa-05-003-resource?test-query=awstut | jq
...
{
  "queryStringParameters": {
    "test-query": "awstut"
  },
  ...
}
Code language: Bash (bash)

I sent a request with the query string key name “test-query” and value “awstut” and received a successful response.

Then send a request that matches the rule.

$ curl https://5qsyw758ji.execute-api.ap-northeast-1.amazonaws.com/soa-05-003-stage/soa-05-003-resource?test-query=awstut2 | jq
...
{
  "message": "Forbidden"
}
Code language: Bash (bash)

The response was different from the previous one.
This means that the string inspection rules for the query string were applied and the request was blocked.

String Inspection for Body

Send HTTP request with body set by curl command to API Gateway.

Checking the rules for the body again, the content is to block traffic that contains “awstut3” in the body.

First, send a request that does not match the rule.

$ curl -X POST -d "test-data=awstut" https://5qsyw758ji.execute-api.ap-northeast-1.amazonaws.com/soa-05-003-stage/soa-05-003-resource | jq
...
{
  "body": "test-data=awstut",
  ...
}
Code language: Bash (bash)

The body sent a request for “test-data=awstut” and received a successful response.

Then send a request that matches the rule.

$ curl -X POST -d "test-data=awstut3" https://5qsyw758ji.execute-api.ap-northeast-1.amazonaws.com/soa-05-003-stage/soa-05-003-resource | jq
...
{
  "message": "Forbidden"
}
Code language: Bash (bash)

The response was different from the previous one.
This means that the string inspection rules for the body were applied and the request was blocked.

String Inspection for Cookies

Send HTTP requests with cookies set to the API Gateway with the curl command.

Checking the rules for cookies again, the content is to block traffic that contains “awstut4” in the cookie.

First, send a request that does not match the rule.

$ curl -b "test-cookie=awstut" https://5qsyw758ji.execute-api.ap-northeast-1.amazonaws.com/soa-05-003-stage/soa-05-003-resource | jq
...
{
  "multiValueHeaders": {
    "Cookie": [
      "test-cookie=awstut"
    ],
    ...
  },
  ...
}
Code language: Bash (bash)

I sent a request with a cookie of “test-cookie=awstut” and received a successful response.

Then send a request that matches the rule.

$ curl -b "test-cookie=awstut4" https://5qsyw758ji.execute-api.ap-northeast-1.amazonaws.com/soa-05-003-stage/soa-05-003-resource | jq                     ...
{
  "message": "Forbidden"
}
Code language: Bash (bash)

The response was different from the previous one.
This means that the string inspection rules for cookies were applied and the request was blocked.

Summary

WAF Web ACL string checking rules were created and checked against four components (headers, query strings, bodies, and cookies).