エンタープライズクラウド部の山下(祐)です。
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へ送信解除申請を行う必要がありますので、そちらの申請も行いました。 詳細は以下の公式ドキュメントをご参照ください。
また、外部のメールサーバーにブロックされないように、DKIM・DMARCの設定を行いました。 PostfixやDKIM・DMARCの設定については、それだけでブログが1本書けてしまいそうなので、本ブログでは割愛させていただきます。
DNSの設定
外部のメールサーバーにブロックされないように、SPFレコード・DKMレコード・DMARCレコードを設定しました。
また、NAT Gatewayを関連付けたElastic IPに、逆引きDNSレコードを設定しています。
なお、メール送信解除や逆引きレコードについては、以下の弊社ブログで大変分かりやすく解説されておりますので、興味がある方はこちらも是非ご参照ください。
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。