AWS Network FirewallでSMTP通信を制御しようとして上手くいかなかった話

記事タイトルとURLをコピーする

エンタープライズクラウド部の山下(祐)です。

AWS Network Firewall(以下、NFW)で、特定メールアドレス宛てのSMTP通信のみ許可するような制御が可能か、調査を行いました。 色々試行錯誤しましたが上手くいかないためAWSサポートへの問い合わせも行ったところ、「NFWでは、SMTP通信のドメイン(メールアドレス)での制御には対応していない」という回答がありました。 残念ながら、2024年4月現在、NFWで特定メールアドレスに対するSMTP通信のみ許可することは出来ないようです。

…と、話としてはこれで以上なのですが、 折角色々試行錯誤したので、その過程を記録しておきたいと思います。 その中で、SuricataやSMTPの仕様等も簡単にご紹介したいと思います。

検証して分かったこと

まずはじめに、今回、検証してみて分かったことをいくつか記載します。

  • SMTPの通信が全く検知できないわけではなく、EHLOやMAILなど、一部の通信は検知できた。
  • (今回の検証では)RCPT、DATA、QUITを検知することは出来なかった。
  • MAIL・RCPT・DATAは、送信方法によっては一つのパケットに同時に含まれる。


検証内容

続いて、今回実施した検証内容について記載します。

検証環境

まずは今回の検証環境についてです。

全体構成

今回は下図の構成で調査を行いました。EC2から外部(今回はYahooメールアドレス)にSMTP:25番ポートでメールを送信しました。EC2とNAT Gatewayの間にNFWを設置し制御を行います。

EC2の設定

EC2ではPostfixを使用してメール送信を行います。シンプルな検証にしたかったので、STARTTLS等は使用していません。 なお、EC2から25番ポートでアウトバウンド通信を行うためには、AWSへ送信解除申請を行う必要がありますので、そちらの申請も行いました。 詳細は以下の公式ドキュメントをご参照ください。

repost.aws

また、外部のメールサーバーにブロックされないように、DKIM・DMARCの設定を行いました。 PostfixやDKIM・DMARCの設定については、それだけでブログが1本書けてしまいそうなので、本ブログでは割愛させていただきます。

DNSの設定

外部のメールサーバーにブロックされないように、SPFレコード・DKMレコード・DMARCレコードを設定しました。


また、NAT Gatewayを関連付けたElastic IPに、逆引きDNSレコードを設定しています。


なお、メール送信解除や逆引きレコードについては、以下の弊社ブログで大変分かりやすく解説されておりますので、興味がある方はこちらも是非ご参照ください。

blog.serverworks.co.jp

NFWの設定

ポリシーではルールの順序を「厳密な順序」、デフォルトアクションは「確立されたパケットをドロップ」にしています。ステートレスルールグループは使用していません。ステートフルルールグループではSuricata互換のルールでSMTP通信の制御を検討します。ドメインリストのルールグループもありますが、これはAWS Systems Manager(以下、SSM)のSession ManagerによるEC2ログインのためのもので、SMTPとは関係ありません。


Suricataのステートフルルールグループでも、ステートフルルールの順序は「厳密な順序」となっています。 また、ルール変数として「HOME_NET」を指定し、VPCのCIDRを設定しています。

Suricataの具体的なルールは後述します。

また、NFWのフローログ・アラートログともにS3に送付する設定にしています。 ログをAthenaで分析するためです。

Athenaの設定

NFWのログをAthenaで分析するため、以下のクエリでテーブルを作成しました。

クリックすると展開されます

CREATE EXTERNAL TABLE table_name(
  firewall_name string,
  availability_zone string,
  event_timestamp bigint,
  event struct<
    timestamp:string,
    flow_id:bigint,
    event_type:string,
    src_ip:string,
    src_port:int,
    dest_ip:string,
    dest_port:int,
    proto:string,
    alert:struct<
               action:string,
               signature:string
        >,
    tls:struct<
               sni:string
        >,
    app_proto:string,
    netflow:struct<
      pkts:int,
      bytes:int,
      start:string,
      finish:string,
      age:int,
      min_ttl:int,
      max_ttl:int
    >
  >
)
ROW FORMAT SERDE
  'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES (
  'paths'='availability_zone,event,event_timestamp,firewall_name'
)
STORED AS INPUTFORMAT
  'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://s3-bucket-name/AWSLogs/'

ログの検索時には以下のクエリを実行します。 event.timestampの日付は検証実施時刻に合わせて適宜変更します。

クリックすると展開されます

SELECT event.timestamp,
       event.event_type,
       event.src_ip,
       event.src_port,
       event.dest_ip,
       event.dest_port,
       event.proto,
       event.alert.action,
       event.alert.signature
FROM table_name
WHERE (event.src_port = 25 or event.dest_port = 25)
AND event.timestamp like '2024-04-29%' 
GROUP BY event.timestamp,
         event.event_type,
         event.src_ip,
         event.src_port,
         event.dest_ip,
         event.dest_port,
         event.proto,
         event.alert.action,
         event.alert.signature,
ORDER BY event.timestamp DESC
LIMIT 10

(前提知識)SMTP通信の内容

まず、最もシンプルな形式でのSMTP通信について図示します。

詳細な説明は割愛しますが、今回の検証でポイントとなるのは以下の2点です。
・1通のメールを送信するために、複数のSMTPメッセージのやり取りが必要。
・宛先メールアドレスが記載されるメッセージは、その内の一部のみ。

つまり、単純に宛先のメールアドレスやドメインだけで許可しようとしても、 EHLOやMAIL FROMのメッセージが許可されないため、メールが送れないということです。

Suricataルール案

そこで、以下のようなSuricataルールで制御が出来ないかと考えました。

・RCPT以外のメッセージは基本的にPass
・RCPTについてのみ、アドレスが一致するものはPass、それ以外はdrop

上記の実現のためには、各SMTPメッセージを適切に検知する必要があるため、まずは各SMTPメッセージをalertルールで検知できるか試すことにしました。

検証結果

実際にメール送信を行い、NFWのログを確認した結果を記載します。

SMTPメッセージが検知できるか試してみる

SuricataでSMTP通信が想定通り検知できるか確認するために、以下のAlertルールを設定してみました。 各SMTPメッセージを検知できるようalertルールを設定しつつ、保険として、最後に全てのSMTPメッセージを検知するalertルールを入れました。

alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP EHLO"; content:"EHLO"; nocase; sid:1000000; rev:1;)
alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP MAIL FROM"; content:"MAIL"; nocase; sid:1000001; rev:1;)
alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP RCPT TO"; content:"RCPT"; nocase; sid:1000002; rev:1;)
alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP DATA"; content:"DATA"; nocase; sid:1000003; rev:1;)
alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP QUIT"; content:"QUIT"; nocase; endswith; sid:1000004; rev:1;)
alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP any"; sid:1000007; rev:1;)
pass smtp $HOME_NET any -> any 25 (msg:"pass SMTP any"; sid:1000008; rev:1;)

そのうえで、EC2から sendmailコマンドでメールを送信しました。

[ssm-user@ip-10-0-0-150 ~]$ sendmail xxxxx@yahoo.co.jp
From:sendmail@yamashita-kensho.net
To:xxxxx@yahoo.co.jp
Subject:test_20240429

This is Test.
Today is April 29th.

.
[ssm-user@ip-10-0-0-150 ~]$

メールは問題なく届きました。

Athenaでクエリを実施し、アラートとフローの検知状況を見てみます。

なんか少ないですね。。SMTPメッセージが3つしか検知できていません。そのうえ、一つは最後のルールで検知されたようです。

パケットキャプチャで実際のSMTP通信の中身を見てみる

なぜSMTPメッセージが3つしか検知できていないのか確認するために、EC2の通信をキャプチャしてみます。 EC2上で tcpdumpコマンドをバックグラウンドで実行しながら、再度同じメールを送信してみます。

[ssm-user@ip-10-0-0-150 ~]$ sudo tcpdump -w 20240429_1_tcpdump -i any -X port 25 &
[1] 2929
[ssm-user@ip-10-0-0-150 ~]$ tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes

[ssm-user@ip-10-0-0-150 ~]$ sendmail xxxxx@yahoo.co.jp
From:sendmail@yamashita-kensho.net
To:xxxxx@yahoo.co.jp
Subject:test_20240429

This is Test.
Today is April 29th.

.
[ssm-user@ip-10-0-0-150 ~]$

tcpdumpの結果が書き込まれたファイルをWiresharkで開いたところ、以下のような出力結果になりました。

黄色で塗りつぶした箇所が、クライアント側のSMTPメッセージです。確かに、SMTPメッセージは3つしかありません。その中の1つを確認すると、MAIL FROM、RCPT TO、DATAの3メッセージをまとめて送信していました。これは、sendmailコマンドでFROM、TO、DATAをまとめて送っているためだと気付きました。

[ssm-user@ip-10-0-0-150 ~]$ sendmail xxxxx@yahoo.co.jp
From:sendmail@yamashita-kensho.net  #MAIL FROMの部分
To:xxxxx@yahoo.co.jp  #RCPT TOの部分
Subject:test_20240429  #DATAの開始部分

This is Test.
Today is April 29th.

. 
[ssm-user@ip-10-0-0-150 ~]$

また、QUITについては、最後のメッセージに含まれていました。

パケットを見たことで、アラート検知が3つしかなかったことと、RCPT・DATAが検知できなかった理由は分かりました。 ただ、QUITが正しく検知できなかった理由は分かりませんでした。

Suricataルールを変更してやり直してみる

RCPTとQUITを検知するために、Suricataのルールをいくつか変更してやり直してみます。 以下のように修正してみました。

alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP EHLO"; content:"EHLO"; nocase; sid:1000000; rev:1;)
alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP RCPT TO"; content:"RCPT"; offset:50; depth:10; nocase; sid:1000002; rev:1;)
alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP MAIL FROM"; content:"MAIL"; nocase; sid:1000001; rev:1;)
alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP DATA"; content:"DATA"; nocase; sid:1000003; rev:1;)
alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP QUIT"; content:"|51 55 49 54 0D 0A|"; nocase; endswith; sid:1000004; rev:1;)
alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP any"; sid:1000007; rev:1;)
pass smtp $HOME_NET any -> any 25 (msg:"pass SMTP any"; sid:1000008; rev:1;)

変更したのはRCPTとQUITです。変更内容について説明します。

RCPTの変更点

ルールの順序を変更

まず、ルールの順番を、MAIL FROMのルールより前にしました。NFWでステートフルルールの順序を「厳密な順序」にしている場合、上から順にルールが評価されるためです。


ちなみに、Suricataの仕組みとしてルールに優先順位を付ける「prioirity」というものがありますが、これはNFWの「厳密な順序」と併用して利用することができません。


offset、depthで検査範囲を指定

次に、ペイロードのどの部分を検査するのかを、offset、depthを利用して、より厳密に指定してみました。
offsetは、ペイロードの何バイト目から検査を開始するかを指定するキーワードです。例えば、offset:3; とした場合、ペイロードの4バイト目から検査を開始します。
depthは、ペイロードの何バイト目まで検査するかを指定するキーワードです。
offsetとdepthは組み合わせが可能です。例えば、offset:3; depth:3; とした場合、ペイロードの4バイト目から6バイト目までが検査対象となります。

詳細は以下のページを参照してください。

https://docs.suricata.io/en/latest/rules/payload-keywords.html#depth https://docs.suricata.io/en/latest/rules/payload-keywords.html#offset

上記を踏まえて、改めてRCPTが入っているパケットを見てみると、RCPTはペイロードの53バイト目に入っていました。 今回は少し余裕を持たせて、51バイト目から10バイト分を検査することにしました。


そのため、Suricataのルールは以下としました。

alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP RCPT TO"; content:"RCPT"; offset:50; depth:10; nocase; sid:1000002; rev:1;)


QUITの変更点

文字を16進数で指定し、ペイロードの末尾であることも明示

Suricataでは、改行等の特殊文字は16進数文字で表示することが出来ます。通常の文字も16進数で表記可能なので、全ての文字を16進数文字で表示することが可能です。 QUITのパケットを改めて見ると、ペイロードの末尾が「QUIT+改行」で終わっていましたので、これをより厳密に指定してみたいと思います。

16進数文字は、パケットキャプチャに載っているので、そのまま流用可能です。16進数文字を指定する場合は、content:"|51 55 49 54 0D 0A|"; のように、文字列をパイプで囲みます。


また、endswith; を使い、contentの値がペイロードの末尾であることを明示します。

16進数文字の指定とendswithについては、以下のドキュメントを参照してください。

https://docs.suricata.io/en/latest/rules/payload-keywords.html#content https://docs.suricata.io/en/latest/rules/payload-keywords.html#endswith


上記を踏まえ、Suricataのルールは以下としました。

alert smtp $HOME_NET any -> any 25 (msg:"alert SMTP QUIT"; content:"|51 55 49 54 0D 0A|"; nocase; endswith; sid:1000004; rev:1;)


Suricataルールを変更したが、結果は変わらず

さて、Suricataルールを更新したうえで改めてメールを送信したのですが、Athenaで改めてクエリを実行した結果、何も変わりませんでした。。 検索範囲を絞ることによる改善は見受けられませんでした。

メールアドレスの一部が検知できるか、3パターンのルールで確認してみる

最後に、SMTPメッセージの種別によらず、とにかくメールアドレスの一部が検知できるか、3パターンのルールで確認してみました。EC2のメール送信コマンド等は、これまでと同様のため割愛します。

送信元アドレスと宛先アドレスの一部を指定

Suricataルールは以下です。送信元アドレスの一部「yamashita」と、宛先アドレスの一部「yahoo」を指定してみます。

alert smtp $HOME_NET any -> any 25 (msg:"alert yamashita"; content:"yamashita"; nocase; sid:1000000; rev:1;)
alert smtp $HOME_NET any -> any 25 (msg:"alert yahoo"; content:"yahoo"; nocase; sid:1000001; rev:1;)
pass smtp $HOME_NET any -> any 25 (msg:"pass SMTP any"; sid:1000008; rev:1;)

Athenaのクエリログ結果は以下です。

送信元アドレスの一部「yamashita」だけ検知できました。


宛先アドレスの一部を指定

Suricataルールは以下です。今度は宛先アドレスの一部「yahoo」だけを指定してみます。

alert smtp $HOME_NET any -> any 25 (msg:"alert yahoo"; content:"yahoo"; nocase; sid:1000001; rev:1;)
pass smtp $HOME_NET any -> any 25 (msg:"pass SMTP any"; sid:1000008; rev:1;)

Athenaのクエリログ結果は以下です。

アラートが何も出ませんでした。。フローログしか載っていませんね。


宛先アドレスの一部を指定(TCP)

Suricataルールは以下です。宛先アドレスの一部「yahoo」だけを指定するのは前回と同じですが、プロトコルをSMTPではなくTCPで指定してみます。

alert tcp $HOME_NET any -> any 25 (msg:"alert yahoo"; content:"yahoo"; nocase; sid:1000001; rev:1;)
pass tcp $HOME_NET any -> any 25 (msg:"pass SMTP any"; sid:1000008; rev:1;)

Athenaのクエリログ結果は以下です。

やはりアラートは出ず、フローログしか載りませんでした。


宛先アドレスは、どうやっても検知できなかった

というわけで、Suricataルールを色々といじって検証したのですが、どうやっても、何故か宛先アドレスの部分を検知することができませんでした。。
NFWによるSMTPの制御が出来なかったのは残念でしたが、今回の調査・検証を通じて、SMTPの仕様やメールサーバの設定、Suricataルールの書式について理解が深まったので、良しとすることにします。


本ブログの内容は以上となります。最後までお読みいただいた方、ありがとうございました。何か一つでも参考になることがあれば幸いです。

山下 祐樹(執筆記事の一覧)

2021年11月中途入社。前職では情シスとして社内ネットワークの更改や運用に携わっていました。 2023 Japan AWS All Certifications Engineers。