AWS WAFの文字列検査 – Headers/QueryString/Body/Cookies

目次

AWS WAFの文字列検査 – Headers/QueryString/Body/Cookies

AWS WAFが提供する機能の1つに、文字列一致ルールステートメントがあります。

文字列一致ステートメントは、AWS WAF がリクエストで検索する文字列、検索するリクエストの場所、および検索する方法を指示します。例えば、リクエストに含まれるクエリ文字列の先頭にある特定の文字列、またはリクエストの User-agent ヘッダと完全に一致する特定の文字列を検索できます。

文字列一致ルールステートメント

本ルールで検査できる対象はいくつかありますが、今回は以下を対象とします。

  • ヘッダ
  • クエリ文字列
  • ボディ
  • Cookie

上記のコンポーネントに特定の文字列を含むリクエストがWAFに着信した場合は、これをブロックするルールを作成します。

構築する環境

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

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に関する基本的な事項に関しては、以下のページをご確認ください。

あわせて読みたい
CFNでWAF Web ACL入門 – ALB向け 【CloudFormationを使用してWAF Web ACLを作成する】 AWS WAF(Web Application Firewall)はAWSが提供するセキュリティサービスです。WAFはいくつかのサービスで構成され...

またWAF Web ACLをAPI Gatewayに適用する方法については、以下のページをご確認ください。

あわせて読みたい
WAF Web ACLをAPI Gatewayに適用する 【WAF Web ACLをAPI Gatewayに適用する】 AWS WAFは以下の4種類のリソースに適用することができます。 ALB CloudFront API Gateway AppSync 今回は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関数で実行するコードをインライン形式で記載します。
詳細につきましては、以下のページをご確認ください。

あわせて読みたい
CloudFormationでLambdaを作成する3パータン(S3/インライン/コンテナ) 【CloudFormationでLambdaを作成する】 CloudFormationでLambdaを作成する場合、大別すると以下の3パターンあります。 S3バケットにコードをアップロードする インライ...

実行するコードの内容ですが、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に関する基本的な事項は、以下のページをご確認ください。

あわせて読みたい
CFNを使ってREST APIタイプのAPI Gatewayを構築 【CloudFormationを使ってREST APIタイプのAPI Gatewayを構築】 以下のページで、HTTP APIタイプのAPI Gatewayについて取り上げました。 https://awstut.com/2021/12/03...

ポイントはMethodリソースを2つ定義する点です。
一方はGETリクエスト用、もう一方はPOSTリクエスト用です。
どちらも先述のLambda関数を呼び出すように設定します。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【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を確認します。

Detail of WAF 1.

このWeb ACLがAPI Gatewayに関連づいていることがわかります。

Detail of WAF 2.

ACLにルールグループが設定されていることがわかります。

ルールグループを確認します。

Detail of WAF 3.

4つのルールが作成されています。

各ルールを確認します。

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

それぞれヘッダ・クエリ文字列・ボディ・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)に対する検査を確認しました。

目次