AWS WAF to prevent XSS with CloudFormation

AWS WAF to prevent XSS with CloudFormation

AWS WAF allows you to create rules to protect your application from many attacks.

This page uses WAF to create rules for XSS (Cross Site Scripting).

An XSS (cross-site scripting) attack statement inspects for malicious scripts in a web request component. In an XSS attack, the attacker uses vulnerabilities in a benign website as a vehicle to inject malicious client-site scripts into other legitimate web browsers.

Cross-site scripting attack rule statement

In particular, XSS, which we will discuss in this article, is one of the most frequently used attack methods.
WAF can be used to protect applications from XSS and other attacks and improve application security.

Environment

Diagram of AWS WAF to prevent XSS with CloudFormation.

Create a REST API type API Gateway.
Create two stages for the API Gateway.

Place WAF on all sides of API Gateway.
Create rules for XSS.
Set the query string to be scanned.
Apply WAF to one API Gateway stage.

Specify a Lambda function as the API Gateway backend resource.
The runtime environment for the Lambda function is Python 3.12.

Scenarios

The following pattern is requested for the two stages

  • Requests containing normal query strings
  • Requests containing XSS-like query strings

This verification was conducted with reference to the following page.

【アップデート】AWS WAFでXSSに対...

CloudFormation template files

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

GitHub
awstut-saa/03/006 at main · awstut-an-r/awstut-saa Contribute to awstut-an-r/awstut-saa development by creating an account on GitHub.

Explanation of key points of template files

Lambda functions that handle query strings

Resources:
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Code:
        ZipFile: |
          import os
          
          ID = os.environ['ID']
          
          html = '''
          <!DOCTYPE html>
          <html lang="ja">
              <head>
                  <title>{id}</title>
                  <meta charset="utf-8"/>
              </head>
              <body>
                  <h1>{id}</h1>
                  {query}
              </body>
          </html>'''
          
          def lambda_handler(event, context):
            query = event['queryStringParameters']['query']
          
            return {
              'statusCode': 200,
              'headers': {'Content-Type': 'text/html'},
              'body': html.format(id=ID, query=query)
            }
      Environment:
        Variables:
          ID: !Ref Prefix
      FunctionName: !Sub "${Prefix}-function"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
Code language: YAML (yaml)

Create a Lambda function in the form of inline code to be executed.

For more information on how to create a Lambda function using CloudFormation, please see the following page.

あわせて読みたい
3 parterns to create Lambda with CloudFormation (S3/Inline/Container) 【Creating Lambda with CloudFormation】 When creating a Lambda with CloudFormation, there are three main patterns as follows. Uploading the code to an S3 buc...

The point is to get the query string in the code that executes it and embed it in the string for HTML.
This is intended to check the value passed in the query string.
This function sets Content-Type to “text/html” and returns the string for HTML that was created, so it will return a dynamic HTML file.

API Gateway creates two stages for testing

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
      
  Stage1:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId: !Ref Deployment
      RestApiId: !Ref RestApi
      StageName: !Sub "${Prefix}-${StageName1}"
      
  Stage2:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId: !Ref Deployment
      RestApiId: !Ref RestApi
      StageName: !Sub "${Prefix}-${StageName2}"
      
  Method:
    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
  
  ApiGatewayRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    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)

There are several types of API Gateways, but the type to which WAF can be applied is the REST API type.
So we will create an API Gateway of this type.

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

あわせて読みたい
Create REST API type API Gateway using CFN 【Create REST API type API Gateway using CloudFormation】 The following page covers the HTTP API type API Gateway. https://awstut.com/en/2021/12/11/serverles...

The key point is the stage, which is one of the resources that make up the REST API type API Gateway.
In this case, we will create the following two stages.

  • saa-03-006-stage1
  • saa-03-006-stage2

On the other hand, apply WAF including rules for XSS described below.

Be careful of capacity when creating rules for XSS

Resources:
  RuleGroup:
    Type: AWS::WAFv2::RuleGroup
    Properties:
      Capacity: 90
      Name: !Sub "${Prefix}-XssRuleGroup"
      Rules:
        - Action:
            Block: {}
          Name: !Sub "${Prefix}-XssRule"
          Priority: 0
          Statement:
            XssMatchStatement:
              FieldToMatch: 
                QueryString: {}
              TextTransformations: 
                - Priority: 0
                  Type: NONE
                - Priority: 1
                  Type: LOWERCASE
                - Priority: 2
                  Type: HTML_ENTITY_DECODE
                - Priority: 3
                  Type: COMPRESS_WHITE_SPACE
                - Priority: 4
                  Type: CMD_LINE
                - Priority: 5
                  Type: URL_DECODE
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: !Sub "${Prefix}-XssRule"
            SampledRequestsEnabled: false
      Scope: REGIONAL
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: !Sub "${Prefix}-XssRuleGroup"
        SampledRequestsEnabled: false

  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      DefaultAction:
        Allow: {}
      Name: !Sub "${Prefix}-WebACL"
      Rules:
        - Name: !Sub "${Prefix}-WebACL-Xss"
          OverrideAction:
            None: {}
          Priority: 0
          Statement:
            RuleGroupReferenceStatement:
              Arn: !GetAtt RuleGroup.Arn
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: !Sub "${Prefix}-WebACL-Xss"
            SampledRequestsEnabled: false
      Scope: REGIONAL
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: !Ref Prefix
        SampledRequestsEnabled: false
        
  WebACLAssociation:
    Type: AWS::WAFv2::WebACLAssociation
    Properties: 
      ResourceArn: !Sub "arn:aws:apigateway:${AWS::Region}::/restapis/${RestApi}/stages/${Stage2}"
      WebACLArn: !GetAtt WebACL.Arn
Code language: YAML (yaml)

Create a WAF that includes rules for XSS.

Please refer to the following page for basic information on WAFs.

あわせて読みたい
Introduction to WAF Web ACL with CNF – ALB Ver. 【Creating WAF Web ACL with CloudFormation】 AWS WAF (Web Application Firewall) is a security service provided by AWS.WAF consists of several services, but t...

Also see the following page for information on how to apply WAF to API Gateway.

あわせて読みたい
Apply WAF Web ACL to API Gateway 【Apply WAF Web ACL to API Gateway】 AWS WAF can be applied to the following four types of resources ALB CloudFront API Gateway AppSync In this article, we w...

There are two points.

The first is the XssMatchStatement property: WAF allows you to create rules for various attacks; if you want to create rules for XSS, use this property.

This time, the query string is the target of the inspection, so specify QueryString for the FieldToMatch property.

The TextTransformations property allows you to configure settings related to the text transformations to be inspected. The details are detailed in the following page, which specifies six items according to the page introduced in the Scenario section.

あわせて読みたい
Text transformation options - AWS WAF, AWS Firewall Manager, and AWS Shield Advanced Specify transformations for AWS WAF to apply to the request before inspecting them.

To summarize this setup, the query string will be inspected in the following order

  1. Inspect without converting anything.
  2. All uppercase letters are converted to lowercase and then inspected.
  3. HTML-encoded strings are inspected after conversion.
  4. Tabs, line feed codes, etc. are converted to blank strings before inspection.
  5. String related to the OS command line is inspected after conversion.
  6. URL-encoded strings are inspected after conversion.

The second point is capacity.

The capacities related to XSS rules are described as follows

WCUs – 40 WCUs, as a base cost. If you use the request component All query parameters, add 10 WCUs. If you use the request component JSON body, double the base cost WCUs. For each Text transformation that you apply, add 10 WCUs.

Cross-site scripting attack rule statement

When you create an XSS rule for a query string and define one text transformation, it consumes 40 WCUs. Then we add 5 more text transformations, so 10 * 5 = 50 WCUs are consumed. Note that the Capacity property must be greater than 90.

Create a WebACLAssociation resource and associate this WAF with the API Gateway.
Specify stage 2 (saa-03-006-stage2) at this time.
This means that for the same API Gateway, WAF will not be applied to stage 1, only stage 2.

Architecting

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

Create CloudFormations stack 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.

あわせて読みたい
CloudFormation’s nested stack 【How to build an environment with a nested CloudFormation stack】 Examine nested stacks in CloudFormation. CloudFormation allows you to nest stacks. Nested ...

Check the API Gateway from the AWS Management Console.

Detail of API Gateway 01.
Detail of API Gateway 02.

Indeed, two stages have been created on one API Gateway.
One has WAF applied, the other does not.

Check the WAF.

Detail of WAF 01.
Detail of WAF 02.
Detail of WAF 03.

The WAF is successfully created.
When I look at the rules, there are indeed rules for XSS.

Action Check

Now that we are ready, we access the two stages.

Normal query string

First, send a request containing a normal query string.

Detail of WAF 04.
Detail of WAF 05.

There was a normal response to requests to both stages.
This means that requests containing normal query strings can pass through successfully without being blocked by the WAF.

XSS-like query string

We then send a request containing an XSS-like query string to both stages. Specifically, a string containing a script tag, which outputs the contents of the cookie.

Detail of WAF 06.
Detail of WAF 07.

For stage 1, where WAF was not applied, a dialog was displayed. (Nothing specific was output because the cookie was empty, though.)
In contrast, “Forbidden” was displayed for stage 2 with WAF applied.
This means that the WAF blocked requests containing XSS-like strings.

WAF console

Finally, check the WAF console.

Detail of WAF 08.
Detail of WAF 09.

You can see that there were two requests, one of which was blocked.

The graph shows that there was an XSS attack, which was blocked by this WAF.

Summary

WAF is used to create rules for XSS (Cross Site Scripting) attacks.
WAF can be used to protect applications from XSS and other attacks and improve application security.