CloudFormationを使用して、AWS WAFを作成して、XSSを防ぐ

CloudFormationを使用して、AWS WAFを作成して、XSSを防ぐ

AWS WAFでは多くの攻撃からアプリケーションを防御するためのルールを作成することができます。

本ページではWAFを使用して、XSS(Cross Site Scripting)用のルールを作成します。

XSS (クロスサイトスクリプティング) 攻撃ステートメントは、ウェブリクエストコンポーネント内の悪意のあるスクリプトを検査します。XSS 攻撃では、攻撃者は、悪意のあるクライアントサイトスクリプトを他の正当なウェブブラウザに挿入するための手段として、悪意のないウェブサイトの脆弱性を利用します。

クロスサイトスクリプティング攻撃ルールステートメント

特に今回取り上げるXSSは頻繁に用いられる攻撃手法の1つです。
WAFを使用することで、XSS等の攻撃からアプリケーションを防御し、アプリケーションのセキュリティを向上させることができます。

構築する環境

Diagram of AWS WAF to prevent XSS with CloudFormation.

REST APIタイプのAPI Gatewayを作成します。
API Gatewayには2つのステージを作成します。

API Gatewayの全面にWAFを配置します。
XSS用ルールを作成します。
検査対象はクエリ文字列とします。
一方のAPI GatewayステージにWAFを適用します。

API GatewayのバックエンドリソースとしてLambda関数を指定します。
Lambda関数のランタイム環境はPython3.12とします。

検証のシナリオ

2つのステージに対して、以下のパターンでリクエストします。

  • 通常のクエリ文字列を含むリクエスト
  • XSS的なクエリ文字列を含むリクエスト

なお今回の検証は以下のページを参考に進めました。

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

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

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.

テンプレートファイルのポイント解説

クエリ文字列を扱うLambda関数

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)

実行するコードをインライン形式で記述する形でLambda関数を作成します。

CloudFormationを使用してLambda関数を作成する方法に関しては、以下のページをご確認ください。

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

ポイントは、実行するコード内でクエリ文字列を取得し、それをHTML用文字列に埋め込んでいる点です。
これはクエリ文字列で渡された値を確認することを意図しています。
この関数はContent-Typeに「text/html」を設定した上で、作成されたHTML用文字列を返しますから、動的なHTMLファイルを返すことになります。

API Gatewayではテスト用に2つのステージを作成する

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)

API Gatewayにはいくつか種類がありますが、WAFを適用できるタイプはREST APIタイプです。
ですから今回は同タイプのAPI Gatewayを作成します。

REST APIタイプのAPI Gatewayに関する基本的な事項については、以下のページをご確認ください。

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

ポイントはREST APIタイプのAPI Gatewayを構成するリソースの1つであるステージです。
今回は以下の2つのステージを作成します。

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

一方は後述のXSS用ルールを含むWAFを適用します。

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)

XSS用ルールを含むWAFを作成します。

WAFに関する基本的な事項は以下のページをご参照ください。

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

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

あわせて読みたい
WAF Web ACLをAPI Gatewayに適用する 【WAF Web ACLをAPI Gatewayに適用する】 AWS WAFは以下の4種類のリソースに適用することができます。 ALB CloudFront API Gateway AppSync 今回はAPI Gatewayに適用す...

ポイントは2点です。

1点目はXssMatchStatementプロパティです。WAFでは様々な攻撃用のルールを作成することができますが、XSS用のルールを作成する場合は、このプロパティを使用します。

今回はクエリ文字列を検査の対象としますので、FieldToMatchプロパティにQueryStringを指定します。

TextTransformationsプロパティで検査対象のテキスト変換に関する設定が行えます。詳細は以下のページに詳しいですが、シナリオの項目でご紹介したページに従い、6つの項目を指定しています。

あわせて読みたい
テキスト変換オプション - AWS WAF、 AWS Firewall Manager、および AWS Shield Advanced が検査する前にリクエスト AWS WAF に適用する変換を指定します。

今回の設定をまとめると、クエリ文字列を以下の順番で検査することになります。

  1. 何も変換せずに検査する。
  2. 大文字を全て小文字に変換した上で検査する。
  3. HTMLエンコードされた文字列を変換した上で検査する。
  4. タブや改行コード等を空白文字列に変換した上で検査する。
  5. OSのコマンドラインに関する文字列を変換した上で検査する。
  6. URLエンコードされた文字列を変換した上で検査する。

2点目のポイントはキャパシティです。

XSSルールに関するキャパシティは以下の通りに説明されています。

WCU- 40 WCU (基本コストとして)。All query parameters のリクエストコンポーネントを使用する場合、10 WCU を追加します。[JSON body] (JSON 本文) のリクエストコンポーネントを使用する場合、基本コストの WCU を倍増させます。適用する各テキスト変換について、10 WCU を追加します。

クロスサイトスクリプティング攻撃ルールステートメント

クエリ文字列を対象としたXSSルールを作成し、1つのテキスト変換を定義した時点でWCUを40消費します。そしてさらに5つのテキスト変換を追加していますから、10 * 5 = 50WCUも消費します。ですから合計40 + 50 = 90WCUとなります。Capacityプロパティでは、90以上を指定する必要があることに注意してください。

WebACLAssociationリソースを作成して、このWAFをAPI Gatewayに関連付けます。
この時にステージ2(saa-03-006-stage2)を指定します。
つまり同じAPI Gatewayでもステージ1の方はWAFが適用されず、ステージ2のみWAFが適用されるということになります。

環境構築

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

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

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

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

AWS Management ConsoleからAPI Gatewayを確認します。

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

確かに1つのAPI Gatewayに2つのステージが作成されています。
一方はWAFが適用されており、もう一方は適用されていません。

WAFを確認します。

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

正常にWAFが作成されています。
ルールを見ると、確かにXSSに関するルールが用意されています。

動作確認

準備が整いましたので、2つのステージに対してアクセスします。

通常のクエリ文字列

まず通常のクエリ文字列を含むリクエストを送ります。

Detail of WAF 04.
Detail of WAF 05.

両ステージへのリクエストに対して、正常にレスポンスがありました。
つまり正常なクエリ文字列を含むリクエストであれば、WAFによってブロックされることなく、正常に通過できるということです。

XSS的なクエリ文字列

続いて両ステージに対してXSS的なクエリ文字列を含むリクエストを送ります。具体的にはscriptタグを含む文字列で、cookieの内容を出力する内容です。

Detail of WAF 06.
Detail of WAF 07.

WAFが適用されていないステージ1の方ではダイアログが表示されました。(Cookieが空だったため具体的には何も出力されませんでしたが。)
対してWAFが適用されているステージ2の方では「Forbidden」が表示されました。
つまりWAFによってXSS的な文字列を含むリクエストはブロックされたということです。

WAFのコンソール画面

最後にWAFのコンソール画面を確認します。

Detail of WAF 08.
Detail of WAF 09.

リクエストが2回あり、内1回はブロックされていることがわかります。

グラフを見ると、XSSによる攻撃があり、これがこのWAFによってブロックされたことがわかりますね。

まとめ

WAFを使用して、XSS(Cross Site Scripting)攻撃用のルールを作成しました。
WAFを使用することでXSS等の攻撃からアプリケーションを防御し、アプリケーションのセキュリティを向上させることができます。