AWS WAFの文字列検査 – Headers/QueryString/Body/Cookies
AWS WAFが提供する機能の1つに、文字列一致ルールステートメントがあります。
文字列一致ステートメントは、AWS WAF がリクエストで検索する文字列、検索するリクエストの場所、および検索する方法を指示します。例えば、リクエストに含まれるクエリ文字列の先頭にある特定の文字列、またはリクエストの User-agent ヘッダと完全に一致する特定の文字列を検索できます。
文字列一致ルールステートメント
本ルールで検査できる対象はいくつかありますが、今回は以下を対象とします。
- ヘッダ
- クエリ文字列
- ボディ
- Cookie
上記のコンポーネントに特定の文字列を含むリクエストがWAFに着信した場合は、これをブロックするルールを作成します。
構築する環境
WAF Web ACLを作成します。
Web ACLは文字列一致ステートメントで定義されたルールで構成されています。
作成したWeb ACLをAPI Gatewayに適用します。
API Gatewayを作成し、バックエンドにLambdaを配置します。
ユーザからHTTPリクエストを受けた場合、API Gatewayがエンドポイントとなって、代わりにLambda関数を呼び出し、同関数の実行結果をユーザに返すというシンプルな構成です。
API GatewayはREST APIタイプです。
Lambda関数のランタイム環境はPython3.8とします。
関数の動作ですが、実行するとeventオブジェクトの中身を返すシンプルな内容です。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-soa/tree/main/05/003
テンプレートファイルのポイント解説
WAF Web ACLルールグループ
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)
ルールグループ内に、文字列検査を行う4つのルールを定義します。
WAF Web ACLに関する基本的な事項に関しては、以下のページをご確認ください。
またWAF Web ACLをAPI Gatewayに適用する方法については、以下のページをご確認ください。
ヘッダに対する文字列検査ルール
1つ目のルールがヘッダを対象として文字列検査を行うルールです。
ByteMatchStatementプロパティで文字列一致ルールステートメントを定義できます。
FieldToMatchプロパティで検査する対象のコンポーネントを指定します。
本ルールでは、ヘッダを対象としますので、同名のプロパティに設定を行います。
MatchPatternプロパティ内のAllプロパティを有効化することで、全ヘッダを検査の対象とすることができます。
MatchScopeプロパティに「ALL」を指定することで、ヘッダ内のキーおよび値を検査の対象とします。
OversizeHandlingプロパティはWAFが検査可能な量を超えるヘッダに対するアクションを設定するものです。
AWS WAF は、本文、ヘッダ、または cookie リクエストコンポーネントの非常に大きなコンテンツの検査をサポートしていません。基盤となるホストサービスには、検査用に AWS WAF に転送する数とサイズの制限があります。
過剰サイズのリクエストコンポーネントの処理
ヘッダに対する制限は以下の通りです。
Headers – 検査できるのは、最大で、リクエストヘッダの最初の 8 KB (8,192 バイト) かつ、最初の 200 ヘッダです。
過剰サイズのリクエストコンポーネントの処理
今回は本プロパティに「CONTINUE」を指定することで、サイズ制限内のデータに対してのみ検査を行うように設定します。
PositionalConstraintプロパティで、文字列の検索方法を指定できます。
今回は「CONTAINS」を指定して、位置に関係なく、指定した文字列を含む場合は、条件を満たすこととします。
SearchStringプロパティで検査する文字列を指定します。
本ルールでは、「awstut1」という文字列を検索することとします。
TextTransformationsプロパティを指定すると、例えばBASE64等に変換した上で、検査を行うことができます。
今回は「NONE」を指定することで、変換を行いません。
クエリ文字列に対する文字列検査ルール
2つ目のルールがクエリ文字列を対象として文字列検査を行うルールです。
FieldToMatchプロパティ内のQueryStringプロパティを設定します。
検査する文字列に「awstut2」を指定します。
ボディに対する文字列検査ルール
3つ目のルールがボディを対象として文字列検査を行うルールです。
FieldToMatchプロパティ内のBodyプロパティを設定します。
ボディも検査できるサイズに上限があります。
本プロパティ内のOversizeHandlingプロパティに「CONTINUE」を指定することで、サイズ制限内のデータに対してのみ検査を行うように設定します。
検査する文字列に「awstut3」を指定します。
Cookiesに対する文字列検査ルール
4つ目のルールがCookieを対象として文字列検査を行うルールです。
FieldToMatchプロパティ内のCookiesプロパティを設定します。
MatchPatternプロパティ内のAllプロパティを有効化することで、全Cookieを検査の対象とすることができます。
MatchScopeプロパティに「ALL」を指定することで、Cookie内のキーおよび値を検査の対象とします。
OversizeHandlingプロパティは「CONTINUE」を指定することで、サイズ制限内のデータに対してのみ検査を行うように設定します。
検査する文字列に「awstut4」を指定します。
(参考) Lambda関数
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)
Lambda関数で実行するコードをインライン形式で記載します。
詳細につきましては、以下のページをご確認ください。
実行するコードの内容ですが、eventオブジェクトを返すというシンプルなものです。
同オブジェクトからHTTPリクエストの内容を確認することができます。
(参考) RESTタイプの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)
RESTタイプのAPI Gatewayに関する基本的な事項は、以下のページをご確認ください。
ポイントはMethodリソースを2つ定義する点です。
一方はGETリクエスト用、もう一方はPOSTリクエスト用です。
どちらも先述のLambda関数を呼び出すように設定します。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- WAF Web ACL:soa-05-003-WebACL
- WAF Web ACLルールグループ:soa-05-003-ComponentInspectionRuleGroup
- API Gateway:soa-05-003
- API Gatewayのエンドポイント:https://5qsyw758ji.execute-api.ap-northeast-1.amazonaws.com/soa-05-003-stage/soa-05-003-resource
- Lambda関数:soa-05-003-function
AWS Management ConsoleからもWAF Web ACLを確認します。
このWeb ACLがAPI Gatewayに関連づいていることがわかります。
ACLにルールグループが設定されていることがわかります。
ルールグループを確認します。
4つのルールが作成されています。
各ルールを確認します。
それぞれヘッダ・クエリ文字列・ボディ・Cookieに対して、文字列検査を行う内容のルールです。
動作確認
準備が整いましたので、各ルールの動作を確認します。
ヘッダに対する文字列検査
curlコマンドでヘッダを設定したHTTPリクエストをAPI Gatewayに送信します。
改めてヘッダ用のルールを確認すると、ヘッダに「awstut1」を含むトラフィックをブロックする内容です。
まずはルールを満たさないリクエストを送信します。
$ 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)
ヘッダのキー名が「test-header」、値が「awstut」のリクエストを送信したところ、正常にレスポンスがありました。
つまりWAF Web ACLによってブロックされずに、トラフィックがAPI Gatewayのエンドポイントに到達し、バックエンドのLambda関数が実行されたということです。
続いてルールを満たすリクエストを送信します。
$ 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)
先ほどとは異なるレスポンスがありました。
つまりヘッダに対する文字列検査ルールが適用されて、リクエストがブロックされたということです。
クエリ文字列に対する文字列検査
curlコマンドでクエリ文字列を設定したHTTPリクエストをAPI Gatewayに送信します。
改めてクエリ文字列用のルールを確認すると、クエリ文字列に「awstut2」を含むトラフィックをブロックする内容です。
まずはルールを満たさないリクエストを送信します。
$ 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)
クエリ文字列のキー名が「test-query」、値が「awstut」のリクエストを送信したところ、正常にレスポンスがありました。
続いてルールを満たすリクエストを送信します。
$ 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)
先ほどとは異なるレスポンスがありました。
つまりクエリ文字列に対する文字列検査ルールが適用されて、リクエストがブロックされたということです。
ボディに対する文字列検査
curlコマンドでボディを設定したHTTPリクエストをAPI Gatewayに送信します。
改めてボディ用のルールを確認すると、ボディに「awstut3」を含むトラフィックをブロックする内容です。
まずはルールを満たさないリクエストを送信します。
$ 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)
ボディが「test-data=awstut」のリクエストを送信したところ、正常にレスポンスがありました。
続いてルールを満たすリクエストを送信します。
$ 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)
先ほどとは異なるレスポンスがありました。
つまりボディに対する文字列検査ルールが適用されて、リクエストがブロックされたということです。
Cookieに対する文字列検査
curlコマンドでCookieを設定したHTTPリクエストをAPI Gatewayに送信します。
改めてCookie用のルールを確認すると、Cookieに「awstut4」を含むトラフィックをブロックする内容です。
まずはルールを満たさないリクエストを送信します。
$ 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)
Cookieが「test-cookie=awstut」のリクエストを送信したところ、正常にレスポンスがありました。
続いてルールを満たすリクエストを送信します。
$ 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)
先ほどとは異なるレスポンスがありました。
つまりCookieに対する文字列検査ルールが適用されて、リクエストがブロックされたということです。
まとめ
WAF Web ACLの文字列検査ルールを作成し、4つのコンポーネント(ヘッダ、クエリ文字列、ボディ、Cookie)に対する検査を確認しました。