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
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.
CloudFormation template files
The above configuration is built with CloudFormation.
The CloudFormation template is placed at the following URL
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.
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.
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.
Also see the following page for information on how to apply WAF to API Gateway.
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.
To summarize this setup, the query string will be inspected in the following order
- Inspect without converting anything.
- All uppercase letters are converted to lowercase and then inspected.
- HTML-encoded strings are inspected after conversion.
- Tabs, line feed codes, etc. are converted to blank strings before inspection.
- String related to the OS command line is inspected after conversion.
- 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.
Check the API Gateway from the AWS Management Console.
Indeed, two stages have been created on one API Gateway.
One has WAF applied, the other does not.
Check the WAF.
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.
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.
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.
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.