📚

AWS Network Firewallはアウトバウンド通信制御のセキュリティ対策にはならない

2022/02/07に公開

AWS Network FirewallによってSquid等を用いたプロキシサーバを廃止できる。
そんなふうに考えていた時期が私にもありました。
もし同じように考えている方がいれば注意が必要です。
本記事執筆時点において、AWS Network Firewallではプロキシサーバとは異なり、悪意のあるアウトバウンド通信を宛先ドメイン名ベースで制限することはできません。

AWS Network Firewallとは

公式ドキュメントより

AWS Network Firewall のステートフルファイアウォールは、接続の追跡やプロトコルの識別などのトラフィックフローからコンテキストを組み込んで、VPC が不正なプロトコルを使用してドメインにアクセスするのを防ぐなどのポリシーを適用できます。AWS Network Firewall の侵入防止システム (IPS) は、アクティブなトラフィックフロー検査を提供するため、シグネチャベースの検出を使用して脆弱性の悪用を特定してブロックできます。また、AWS Network Firewall は、既知の不正な URL へのトラフィックを停止し、完全修飾ドメイン名を監視できるウェブフィルタリングも提供します。
https://aws.amazon.com/jp/network-firewall/

プロキシの代替としての利用も期待できる機能説明です。そうしたケースも含めて、以下のページでの解説がわかりやすいでしょう。
https://dev.classmethod.jp/articles/aws-network-firewall/
https://fu3ak1.hatenablog.com/entry/2020/11/18/162149
https://blog.serverworks.co.jp/all-about-aws-network-firewall

Network Firewallのドメイン名によるフィルタリングは何を検査しているか

Network Firewallのドメインリストルールでは、HTTPとHTTPSの2つのプロトコルに対応しています。ドキュメントに記載されているドメインリスト利用時に解釈されるSuricataルールから、それぞれ検査している内容は以下の通りと理解できます。

  • HTTP: HTTPリクエストのHostヘッダ
  • HTTPS: TLSハンドシェイク時のSNI拡張におけるserver_name

https://docs.aws.amazon.com/ja_jp/network-firewall/latest/developerguide/suricata-examples.html
これらはどちらも通信の内容を検査しているのであって、実際の通信の宛先を検査しているわけでも制御しているわけでもありません。そして、リクエスト生成元において、実際の宛先とこれらの情報を別にしたリクエストを生成することは容易です。

なお、SNIによるフィルタについては、以下のページでわかりやすく説明されております。
https://milestone-of-se.nesuke.com/sv-advanced/server-software/https-url-filter/

Network Firewallでは制御できないことを確認する

検証のために下図の構成を構築し、実際に試してみます。

検証環境は下記のCloudFormationテンプレートで構築できます。Network Firewallは作成に時間がかかるため、Stackの作成完了まで7分程度かかりました。

CloudFormationテンプレート
AWSTemplateFormatVersion: 2010-09-09
Resources:
  VPC: 
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: "10.0.0.0/16"
  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: "10.0.1.0/24"
      VpcId: !Ref VPC
      AvailabilityZone: !Select 
        - '0'
        - !GetAZs 
          Ref: 'AWS::Region'
  FirewallSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: "10.0.2.0/24"
      VpcId: !Ref VPC
      AvailabilityZone: !Select 
        - '0'
        - !GetAZs 
          Ref: 'AWS::Region'
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: "10.0.3.0/24"
      VpcId: !Ref VPC
      AvailabilityZone: !Select 
        - '0'
        - !GetAZs 
          Ref: 'AWS::Region'

  InternetGateway:
    Type: AWS::EC2::InternetGateway
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
      
  NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      SubnetId: !Ref PublicSubnet
      AllocationId: !GetAtt NATEIP.AllocationId 
  NATEIP:
    Type: AWS::EC2::EIP

  NetworkFirewall:
    Type: AWS::NetworkFirewall::Firewall
    Properties:
      FirewallName: NetworkFirwallTestXYZ
      FirewallPolicyArn: !Ref NetworkFirewallPolicy
      VpcId: !Ref VPC
      SubnetMappings:
        - SubnetId: !Ref FirewallSubnet
  NetworkFirewallPolicy:
    Type: AWS::NetworkFirewall::FirewallPolicy
    Properties:
      FirewallPolicyName: NetworkFirewallPolicyTestXYZ
      FirewallPolicy:
        StatelessDefaultActions:
          - 'aws:forward_to_sfe' 
        StatelessFragmentDefaultActions:
          - 'aws:forward_to_sfe'
        StatefulRuleGroupReferences:
          - ResourceArn: !Ref StatefulRuleGroup
      Tags:
        - Key: Name
          Value: NetworkFirewallPolicy
  StatefulRuleGroup:
    Type: AWS::NetworkFirewall::RuleGroup
    Properties:
      RuleGroupName: StatefulRuleGroupTestXYZ
      Type: STATEFUL
      Capacity: 100
      RuleGroup:
        RuleVariables:
          IPSets:
            HOME_NET:
              Definition:
                - "10.0.0.0/16"
        RulesSource:
          RulesSourceList:
            Targets:
              - "www.yahoo.co.jp"
              - ".ap-northeast-1.amazonaws.com"
            TargetTypes:
              - "TLS_SNI"
              - "HTTP_HOST"
            GeneratedRulesType: "ALLOWLIST"
      Tags:
        - Key: Name
          Value: AllowDomainList


  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: PrivateRouteTable
  PrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: "0.0.0.0/0"
      VpcEndpointId: !Select ["1", !Split [":", !Select ["0", !GetAtt NetworkFirewall.EndpointIds]]]
  PrivateRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet

  FirewallRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: FirewallRouteTable
  FirewallRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref FirewallRouteTable
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref NATGateway
  FirewallRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref FirewallRouteTable
      SubnetId: !Ref FirewallSubnet
  
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: PublicRouteTable
  PublicRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway
  PublicRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: "10.0.1.0/24"
      VpcEndpointId: !Select ["1", !Split [":", !Select ["0", !GetAtt NetworkFirewall.EndpointIds]]]
  PublicRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet

  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Allow All"
      GroupName: "AllowAll"
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: '0'
          ToPort: '65535'
          CidrIp: '0.0.0.0/0'
      VpcId: !Ref VPC

  RoleForSSM:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal: 
              Service:
                - "ec2.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: /
      ManagedPolicyArns: 
        - 'arn:aws:iam::aws:policy/AdministratorAccess'

  InstanceProfileForSSM:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref RoleForSSM
  
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-03d79d440297083e3
      InstanceType: t2.micro
      NetworkInterfaces:
        - DeviceIndex: "0"
          SubnetId: !Ref PrivateSubnet
          GroupSet:
            - !Ref InstanceSecurityGroup
      IamInstanceProfile: !Ref InstanceProfileForSSM
      Tags:
        - Key: Name
          Value: NetworkFirewallTest
    DependsOn:
      - NetworkFirewall

Network Firewallではドメインリストルールでwww.yahoo.co.jp及び*.ap-northeast-1.amazonaws.comのみを許可しています。後者を許可している理由はSession Managerでのサーバログインを可能とするためです。

サーバから以下のコマンドを実行すると、許可したwww.yahoo.co.jpにアクセスできることが確認できます。

curl http://www.yahoo.co.jp/ -v
curl https://www.yahoo.co.jp/ -v

以下のコマンドで許可されていないwww.google.co.jpへの通信はDropされていることが確認できます。(応答待ちとなるため、Ctrl+Cで中断する必要があります)

curl http://www.google.co.jp/ -v
curl https://www.google.co.jp/ -v

これでドメインの制御がちゃんとできるいると思いがちですが、違います。
下記のコマンドを打つと、Googleのサーバから応答が得られていることが確認できます。

curl http://172.217.31.131/ -H "Host: www.yahoo.co.jp" -v
curl https://www.yahoo.co.jp/ --resolv www.yahoo.co.jp:443:172.217.31.131 -H "Host: www.google.co.jp" --insecure -v

HostヘッダやSNIのserver_nameを許可されているドメイン名に設定したリクエストにすれば、任意のIPアドレスの宛先(上記の例ではGoogleのIPアドレス)と通信することが可能なのです。

ドメインリストルール以外の場合には

この問題は、ドメインリストルールだけではなく、より細かく制御可能なSuricataルールでも対応できません。SuricataにおいてHTTP/TLSで利用可能なキーワードは以下に記載がありますが、いずれのキーワードを用いても対応できません。
https://suricata.readthedocs.io/en/suricata-6.0.0/rules/http-keywords.html
https://suricata.readthedocs.io/en/suricata-6.0.0/rules/tls-keywords.html

TLSにおいては、通信先の証明書のfingerprintで制御する方法も考えられなくはありませんが、通信相手の予期できない証明書更新への追随のための運用を考えれば適切な方法とは言えないでしょう。証明書のサブジェクトで検査する場合には証明書チェーンの検証機能も伴わなければ効果がありませんが、証明書チェーンの検証機能はありません。

Network Firewallのドメインリストでアウトバウンド通信を制御する意味

例えば、最近発生したLog4Shell[1]は外部のリソースから任意のコードを読み込んで任意の処理を実行させるものでしたが、外部のリソースからコードを取得する部分のリクエスト自体を自力で組み立てることはできなかったため、このケースについては、Network Firewallによるドメイン制御でも対処はできたでしょう。また、ミス等で外部と通信してしまうことを防ぐこともできます。その点で、全く意味がないというわけではありません。
一方で、セキュリティ観点におけるアウトバウンドの通信制限とは、システム内部から任意の外部の宛先に対して情報を流出させないことを目的とすることが多いかと思います。この脅威に対する対策として、十分であるとはいえません。

Azure Firewallの場合

Azureには類似のサービスとして、Azure Firewallがあります。こちらもアウトバウンド通信の制御を1つの目的として利用することができます。HTTPではHostヘッダ、HTTPSではSNIの値に基づいて制御する点ではAWS Network Firewallと同様ですが、こちらは上述のNetwork Firewallのような問題は起こりません。
Azure Firewallのアプリケーションルールでは、送信元が指定したIPアドレスを実際の通信先として利用しません。リクエストの送信元で宛先IPアドレスに何を指定しようとも、HTTPであればHostヘッダ、HTTPSであればSNIのserver_nameの値に従ってAzure Firewallが名前解決し通信先を決定し、名前解決した宛先にDNATします。従って、AWS Network Firewallのような問題はなく、アウトバウンド通信をドメイン名ベースで制限するという目的に合致した動作をします。AWSに先んじて提供されていたAzureのサービスでは期待通りの動作をしていたことも、Network Firewallに誤った期待してしまう一因かもしれません。

Network 規則への一致がなく、プロトコルに HTTP、HTTPS または MSSQL を使用している場合は、その後で、 Application 規則により、優先度順で、パケットを評価します。

HTTP については、Azure Firewall で Application 規則への一致を調べるとき、ホスト ヘッダーの情報を使用します。 HTTPS については、Azure Firewall で Application 規則への一致を調べるとき、SNI のみを使用します。

HTTP と TLS インスペクションを有効にした HTTPS では、ファイアウォールでパケットの宛先 IP アドレスを無視し、DNS でホスト ヘッダーから解決した IP アドレスを使用します。

https://docs.microsoft.com/ja-jp/azure/firewall/rule-processing#network-rules-and-applications-rules

AWSに期待すること

Azure Firewall同様、Firewall自身が通信先を制御することで、抜け道なくアウトバウンド通信をドメイン名ベースで制御可能な機能の追加は期待したいところです。ただ、Azureの場合元々FirewallがNAT装置でありましたが、AWSの場合はGateway Load Balancerを利用してパケットを改変せずに取り扱っているので、本機能を実装することはアーキテクチャの思想に合致しない点があるかもしれません。

AWS Network FirewallはほぼAmazon Managed IPS for Suricataともいえるものです。最近ではマネージドルールの提供も開始して、IPSとしての充実度が上がってきました。Suricataとして様々なプロトコルに対応したルールを定義できる良さもあります。これはこれで有用なサービスです。
https://aws.amazon.com/jp/about-aws/whats-new/2021/12/aws-network-firewall-aws-managed-rules/

これをアウトバウンド通信を制御するプロキシの代替となる、と考えてしまうことが誤りなのです。とはいえ、そうした誤解を招きうる機能説明がされている印象はあります。セキュリティに関する内容でもあるので、この点の注意喚起はドキュメント等に記載されても良いのではないかと思います。
なお、aws-samplesにはsquid定義からの移行用コードが公開されています。
https://github.com/aws-samples/migrating-to-aws-network-firewall-from-squid-web-proxy

また、アウトバウンドの通信制御に関して必要だったものは、マネージドなSuricataではなく、マネージドなSquidです。ドメイン制御の留まらないより細かな制御を実現するためにも、TLS復号によるコンテンツフィルタ、透過型構成も選択可能な、ユーザフレンドリな設定UIを具備した、Amazon Managed Proxy for Squidの登場に期待します。

結論

Network Firewallは、プロキシサーバが担っていたドメイン名ベースでの通信先制御が、マネージドサービスで実現できることを期待させるものでした。が、Network Firewallのドメインリストルールではセキュリティ的な意味での制御には不十分です。依然として、AWS上における本目的の達成にはプロキシサーバないし十分な機能を備えたセキュリティアプライアンスの方が適切と言えます。プロキシサーバの構築/運用という作業からはまだしばらく解放されなそうです。

脚注
  1. https://amg-solution.jp/blog/27313 ↩︎

Discussion