Network Firewallで Suricata互換のIPSルールを試してみた
僕はまだNetwork Firewallの真の力を知らなかった
こんにちは、のんピ です。
最近、Network Firewallの記事を2本書きました。
しかし、Network Firewallのココが凄いポイントである、Suricata互換のIPSルールにまだノータッチでした。
また、Suricata互換のIPSルールについてまとまった日本語の資料があまりなかったので、 せっかくなので、気合を入れて今回はSuricata互換のIPSルールを深掘りしていこうと思います。
ちなみに、IPSにロマンを感じるのは私だけでしょうか。
通信を検知して、振る舞いや大量のシグネチャーと照らし合わせて、不正な通信を見つけ出してやるという働きっぷりにロマンを感じられずにはいられません。
単純にIPSのシグネチャーを見るだけで興奮します。
※ 同じロマンネットワーク機器としてはL3スイッチがあります。
いきなりまとめ
- Suricataはシグネチャー型のIPS
- 同じSuricata互換のIPSのシグネチャーを使い回すことができる
- そもそも、Network Firewallのステートフルエンジン自体がSuricata互換
- Domain Listや5-tapleでは設定できなかったシグネチャー毎に優先度をつけることができる
- ホワイトリスト運用、ブラックリスト運用のどちらも実装しやすい
- CloudWatch Logs Insightsを用いることで、いつ、どのシグネチャーが多く検知されたのか容易に把握することができる
Suricataとは
Suricataの特徴
まずは、Suricataについての説明からしていきます。
Suricataとはコミュニティが管理するIDS/IPSです。
Suricata is a high performance Network IDS, IPS and Network Security Monitoring engine. It is open source and owned by a community-run non-profit foundation, the Open Information Security Foundation (OISF). Suricata is developed by the OISF.
IDS/IPSにはシグネチャー型とアノマリ型と大きく分けて2つの種類がありますが、Suricataはシグネチャー型です。
シグネチャー型は事前に用意されたシグネチャーと照らし合わせて、シグネチャーとマッチしたらアラートをあげたり、通信をブロックする方式です。
例えば、Webサーバの.htaccess
にアクセスするために、httpのリクエストに.htaccess
という文字列が含まれていたら、通信をブロックするといったものです。
一方でアノマリ型は、通信の振る舞いを見て、不審な通信の場合はアラートをあげたり、通信をブロックする方式です。
例えば、大量のSYN
パケットが短時間に大量に送られて来たときは、SYN Flood攻撃と判断して、通信をブロックするといったものです。
AWS Network FirewallはSuricata互換と記載されている通り、同じSuricata互換のIPSで使用してたシグネチャーを使い回しすることが可能です。
そのため、既にSuricata互換のIDS/IPSを使っているのであれば、スムーズに導入できるかと思います。
※ 投稿時点では、AWS公式のシグネチャーセットのようなものはありませんでした。
なお、SuricataでSSLのデコード処理は出来ません。https通信の中身を確認した上で検知・ブロックしたいなどの要件がある場合は、AWS Marketplace上で提供されているIPSの仮想アプライアンスなど、3rdパーティの製品を利用することになります。
例) httpsのクエリーの文字列で検知したい場合など
連携例として、Suricata公式ドキュメントには、Symantec SSL Visibilityが掲載されていました。
As Suricata itself cannot decrypt SSL/TLS traffic, some organizations use a decryption product to handle this. This document will offer some advice on using Suricata with the Symantec SSL Visibility appliance (formerly known as BlueCoat).
シグネチャーの書き方
Suricataのシグネチャーのフォーマットは大きく分けて以下の3つに分かれています。
- action
- header
- options
実際のシグネチャーで色分けすると以下のようになります。
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:”ET TROJAN Likely Bot Nick in IRC (USA +..)”; flow:established,to_server; flowbits:isset,is_proto_irc; content:”NICK “; pcre:”/NICK .USA.[0-9]{3,}/i”; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
それぞれの役割、書き方について以降、説明していきます。
action
drop tcp $HOME_NET any -> $EXTERNAL_NET any(msg:”ET TROJAN Likely Bot Nick in IRC (USA +..)”; flow:established,to_server; flowbits:isset,is_proto_irc; content:”NICK “; pcre:”/NICK .USA.[0-9]{3,}/i”; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
action
はシグネチャーに一致したら、どのような処理を行うのかを指定する部分です。
Suricataで定義されているactionは以下の通りです。
値 | 説明 |
---|---|
alert | アラートを出す |
pass | パケットの検査を中止し、通信を許可する |
drop | パケットをドロップしてアラートを出す |
reject | 一致したパケットの送信者にRST/ICMP unreach errorを送信する |
なお、AWS公式ドキュメントに記載がある通り、Network Firewallでは、alert
、pass
、drop
以外の値は指定出来ません。
header
header
は以下の4つのセクションに分かれています。
- protocol
- source and destination
- ports
- direction
順番に説明していきます。
protocol
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:”ET TROJAN Likely Bot Nick in IRC (USA +..)”; flow:established,to_server; flowbits:isset,is_proto_irc; content:”NICK “; pcre:”/NICK .USA.[0-9]{3,}/i”; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
protocol
は通信のプロトコルを指定する部分です。
Suricataで定義されているprotocol
は以下の通りです。httpなどアプリケーション槽のプロコトルにも対応しています。
- tcp
- udp
- icmp
- ip (「all」または「any」を表します)
- http
- ftp
- tls (sslを含む)
- smb
- dns
- dcerpc
- ssh
- smtp
- imap
- modbus (Network Firewallでは使用不可)
- dnp3 (Network Firewallでは使用不可)
- enip (Network Firewallでは使用不可)
- nfs (Network Firewallでは使用不可)
- ikev2
- krb5
- ntp
- dhcp
source and destination
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:”ET TROJAN Likely Bot Nick in IRC (USA +..)”; flow:established,to_server; flowbits:isset,is_proto_irc; content:”NICK “; pcre:”/NICK .USA.[0-9]{3,}/i”; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
source and destination
は、通信の送信元と、送信先を指定する部分です。
左側が送信元のネットワーク、右側が送信先のネットワークです。
IPアドレス(IPv4とIPv6の両方がサポート)とIPアドレスの範囲を割り当てることができます。また、以下のように演算子を使うことが出来ます。
演算子 | 説明 |
---|---|
../.. | IPアドレスの範囲 (CIDR表記) |
! | 否定/例外 |
[.., ..] | グループ |
入力例は以下の通りです。
例 | 意味 |
---|---|
! 1.1.1.1 | 1.1.1.1以外の全てのIPアドレス |
![1.1.1.1, 1.1.1.2] | 1.1.1.1と1.1.1.2以外の全てのIPアドレス |
[$EXTERNAL_NET, !$HOME_NET] | EXTERNAL_NETに定義したネットワークの範囲 と HOME_NETに定義したネットワークの範囲以外 |
[10.0.0.0/24, !10.0.0.5] | 10.0.0.5以外の10.0.0.0/24 |
否定演算子があるのはかなり熱いですよね。10.0.0.0/16
以外からの通信のようにセキュリティグループや、NACLでは設定出来なかったシグネチャーを作成することができます。
また、一般的には自分のネットワークの範囲を示す$HOME_NET
と自分以外のネットワークの範囲を示す$EXTERNAL_NET
という2つの変数を定義して使用することが多いです。
ports
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:”ET TROJAN Likely Bot Nick in IRC (USA +..)”; flow:established,to_server; flowbits:isset,is_proto_irc; content:”NICK “; pcre:”/NICK .USA.[0-9]{3,}/i”; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
ports
は通信のポートを指定する部分です。
左側が送信元のポート、右側が送信先のポートです。
source and destination
同様に以下のように演算子を使うことが出来ます。
演算子 | 説明 |
---|---|
: | ポート範囲 |
! | 否定/例外 |
[.., ..] | グループ |
入力例は以下の通りです。
例 | 意味 |
---|---|
[80, 81, 82] | ポート番号が80, 81, 82 |
[80: 82] | ポート番号が80から82 |
[1024: ] | ポート番号が1024から最大ポート番号まで |
!80 | ポート番号が80以外 |
[80:100,!99] | ポート番号が99を除く80から100 |
[1:80,![2,4]] | ポート番号が2と4を除く1から80 |
注意点としては、指定したポートは通信で使用されるプロトコルを自動で判別しないことです。
例えば、httpsの通信を検知するために、以下のようにプロトコルをhttp
、ポートは443を指定しても検知されません。
これはhttpをtcp/443で通信した場合として判別されます。
alert http $HOME_NET any -> $EXTERNAL_NET 443 (msg:”DETECTED https”; flow:to_server, established; sid:1000001; rev:1;)
httpsの通信を検知する場合は、以下のようにtls
を使って記述します。
alert tls $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED https"; flow:to_server, established; sid:1000001; rev:1;)
direction
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:”ET TROJAN Likely Bot Nick in IRC (USA +..)”; flow:established,to_server; flowbits:isset,is_proto_irc; content:”NICK “; pcre:”/NICK .USA.[0-9]{3,}/i”; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
direction
は通信の方向性を指定する部分です。種類は以下の通り2つあります。
演算子 | 例 | 意味 |
---|---|---|
-> | source -> destination | sourceからdestionationへの通信 |
source destination | sourceからdestionationへの通信 destionationからsourceへの通信 |
ちなみに``とした場合は、リクエストとレスポンスの通信、それぞれで検知されることになります。
そのため、ほとんどのシグネチャーでは->
が使われます。
options
options
は数が多いので、以下の通り、よく使われるものを抜粋して説明します。
- sid
- rev
- msg
- priority
- metadata
- content
- nocase
- depth / offset
- isdataat
- startswith / endswith
- dotprefix
- flow
- http
- ssl/tls
sid
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:”ET TROJAN Likely Bot Nick in IRC (USA +..)”; flow:established,to_server; flowbits:isset,is_proto_irc; content:”NICK “; pcre:”/NICK .USA.[0-9]{3,}/i”; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
sid
はoptionsで唯一必須の項目です。sidはシグネチャーに独自のIDを付与します。そのため、他のシグネチャーと重複することはできません。
sidは一意にする必要があるため、以下のようにユーザーが作成したシグネチャーに割り当てる範囲は決められています。
sidの範囲 | 割り当て対象 |
---|---|
1000000-1999999 | ユーザーが作成したシグネチャー |
2000000-2099999 | Emerging Threatsのオープンシグネチャーセット |
2100000-2103999 | フォークされたEmerging Threatsのシグネチャー |
2200000-2200999 | Suricataデコーダのイベント |
2210000-2210999 | Suricataストリームのイベント |
2220000-2299999 | Suricataの予約領域 |
2800000-2899999 | Emerging Threats Pro Full Coverage Ruleset |
参考: Emerging Threats Threats SID Allocation
また、sid
はoptions
の最後または、rev
が存在する場合は最後から2番目に記述することがほとんどです。
rev
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:”ET TROJAN Likely Bot Nick in IRC (USA +..)”; flow:established,to_server; flowbits:isset,is_proto_irc; content:”NICK “; pcre:”/NICK .USA.[0-9]{3,}/i”; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
rev
はシグネチャーのバージョンを表します。シグネチャーを変更した場合は、rev
の値をインクリメントします。
msg
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:”ET TROJAN Likely Bot Nick in IRC (USA +..)”; flow:established,to_server; flowbits:isset,is_proto_irc; content:”NICK “; pcre:”/NICK .USA.[0-9]{3,}/i”; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
msg
はシグネチャーがマッチした際に、出力する文字です。
priority
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED dev.classmethod.jp"; http.host; content:"dev.classmethod.jp"; startswith; endswith; priority:1; flow:to_server, established; sid:1000001; rev:1;)
priority
はシグネチャーの優先度を表します。1〜255
の範囲の値で指定しますが、1〜4
の数字が最もよく使用されます。優先度の高いシグネチャーが最初に検査されます。最も高い優先度は1
です。
優先度を上手く使用すれば、どのルールにも当てはまらなかったものは、drop
/pass
といったホワイトリスト/ブラックリスト運用が可能になります。
metadata
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED dev.classmethod.jp"; http.host; content:"dev.classmethod.jp"; startswith; endswith; flow:to_server, established; sid:1000001; rev:1; metadata:created_at 2021_05_05, updated_at 2021_05_05;)
metadata
はシグネチャーに任意の情報を付与します。Suricataのアラートにも表示されるため、ログを集計するときのキーとして使用することもできます。
content
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED dev.classmethod.jp"; http.host; content:"dev.classmethod.jp"; startswith; endswith; flow:to_server, established; sid:1000001; rev:1;)
content
はパケットまたはストリームのペイロードの内容の検査文字列です。普通の文字はもちろん、改行や:
、"
などの特殊文字、メタ文字は16進表記で表現することができます。
16進表記は大文字で書くのが慣例です。以下は16進表記で入力した例です。
|61| = a
|61 61| = aa
|41| = A
|21| = !
|0D 0A| = Windowsの改行コード
|22| = "
|3B| = ;
|3A| = :
|7C| = |
検索範囲としては、デフォルトではペイロードのすべてが対象になり、部分一致するものを見つけようとします。
また、検索の際は大文字と小文字が区別されます。
否定を意味する!
も使用することできます。以下のシグネチャーでは、Firefoxの使用バージョンが3.6.13でない場合にアラートを出します。
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"Outdated Firefox on Windows"; content:"User-Agent|3A| Mozilla/5.0 |28|Windows|3B| "; content:"Firefox/3."; distance:0; content:!"Firefox/3.6.13"; distance:-10; sid:9000000; rev:1;)
nocase
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:\".htpasswd access attempt\"; flow:to_server,established; content:\".htpasswd\"; nocase; sid:210503; rev:1;)"
nocase
は大文字と小文字の区別を無視する際に使用するオプションです。上のようにcontent
の後ろに配置します。
depth / offset
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED dev.classmethod.jp"; http.host; content:"dev.classmethod.jp"; depth:18; offset:0; flow:to_server, established; sid:1000001; rev:1;)
depth
はペイロードの先頭から何バイトまでが検索対象かを指定します。
offset
はペイロードのどのバイトからチェックされるかを指定します。
上の例だと、先頭の0バイトから18バイトまでをチェック対象となります。
depth
やoffset
を使用することで、ペイロードの一部分のみが検索対象となり、処理負荷軽減につながります。
isdataat
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED .jp"; http.host; content:"jp"; isdatat:!1,relative; flow:to_server, established; sid:1000001; rev:1;)
isdataat
はペイロードの特定の部分にまだデータがあるかどうかを確認することができます。relative
というオプションを使用して、最後に一致した文字列の後にまだデータがあるかどうかを確認します。
否定を意味する!
も使用することできます。
上の例では、HTTP Hostヘッダーの末尾が.jp
である場合に検知されます。
startswith / endswith
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED dev.classmethod.jp"; http.host; content:"dev.classmethod.jp"; startswith; endswith; flow:to_server, established; sid:1000001; rev:1;)
startswith
はcontent
で指定した文字列が先頭であることを指定します。正規表現で言うところの、^
です。
endswith
はcontent
で指定した文字列が末尾であることを指定します。正規表現で言うところの、$
です。
どちらもcontent
の後ろに記述する必要があります。
通常、content
で指定した文字列はペイロード内を部分一致検索をしますが、startswith
とendswith
を組みあせて使うことで、完全一致で検索することができます。
上の例では、HTTP Hostヘッダーがdev.classmethod.jp
である場合に検知されます。
dotprefix
alert dns any any -> any any (msg:"DETECTED .classmethod.jp dns query"; dns.query; dotprefix; content:".classmethod.jp"; sid:1000001; rev:1;)
dotprefix
は、content
に含まれる.
をワイルドカードとして扱います。
上の例では、DNSクエリがdev.classmethod.jp
であっても検知されます。
flow
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:”ET TROJAN Likely Bot Nick in IRC (USA +..)”; flow:established,to_server; flowbits:isset,is_proto_irc; content:”NICK “; pcre:”/NICK .USA.[0-9]{3,}/i”; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
flow
は、通信の方向を指定することができます。以下のようにflow:
で、サーバー/クライアントのどちらからの通信か、3ウェイ・ハンドシェイクの後なのかを指定することができます。
オプション | 説明 |
---|---|
to_client | サーバーからクライアントへのパケットを照合する |
to_server | クライアントからサーバーへのパケットを照合する |
from_client | クライアントからサーバーへのパケットを照合する(to_serverと同じ)。 |
from_server | サーバーからクライアントへのパケットを照合する(to_clientと同じ)。 |
established | 確立された接続に対して照合する |
not_established | 確立されていない接続のパケットに対して照合する。 |
stateless | 確立された接続の一部であるパケットとそうでないパケットを照合する |
only_stream | 再構築されたパケットまたは確立された通信中のパケットを照合する |
no_stream | 再構築されたパケットまたは確立された通信中のパケット以外と照合する |
only_frag | フラグメントから再構成されたパケットを照合する |
no_frag | フラグメントから再構成されていないパケットを照合する |
http
今までcontent
で指定した文字列がペイロードに含まれるかと言う説明をしていましたが、ペイロードのどこなのかの指定をしていませんでした。
http
を使うと、HTTPトラフィックの特定の部分のみをチェックするよう指定することができます。例えば、リクエストURI、Cookie、HTTPリクエストまたはレスポンスの本文などが指定できます。
http
は以下に記載するとおり、多くの指定方法があります。
値 | 説明 | 通信方向 |
---|---|---|
http.uri | リクエストURIを検索対象とします。 ペイロードが、「GET/index.html HTTP/1.0\n」のときに、「index.html」があるかどうか検索したい場合は、「http.uri; content:/index.html;」 とします。 |
リクエスト |
http.uri.raw | リクエストURIを検索対象とします。http.uri との違いは16進数表記で書かれた文字を変換せずそのまま解釈するところです。|%20|という文字があった時、http.uriではスペースと認識しますが、http.uri.rawでは、そのまま|%20|という文字として認識します。 |
リクエスト |
http.method | HTTPのメソッド部分(GET、POST、PUT、HEAD、 DELETE、TRACE、OPTIONS、CONNECT、およびPATCH)を検索対象とします。 GETメソッドを検知したいのに、「content:"GET"」としてしまうと、htmlファイル名に"GET"が含まれたときに意図しない検知をしてしまいます。 |
リクエスト |
http.request_line | HTTPリクエスト全てを検索対象とします。 | リクエスト |
http.request_body | HTTPリクエストボディを検索対象とします。 | リクエスト |
http.header | HTTPヘッダーを検索対象とします。 | 両方向 |
http.header.raw | HTTPヘッダーを検索対象とします。http.header は末尾の空白とタブ文字はすべて削除しますが、http.header.raw はそのまま取得できます。 |
両方向 |
http.cookie | cookieを検索対象とします。 | 両方向 |
http.user_agent | HTTPリクエストヘッダーのUser-Agentヘッダーを検索対象とします。 | リクエスト |
http.host | HTTP hostヘッダーを検索対象とします。 | リクエスト |
http.host.raw | HTTP hostヘッダーを検索対象とします。http.host はすべて小文字であるように正規化されますが、http.host.raw は大文字、小文字を区別します。 |
リクエスト |
http.accept | HTTP Acceptヘッダーを検索対象とします。 | リクエスト |
http.accept_lang | HTTP Accept-Encodingヘッダーを検索対象とします。 | リクエスト |
http.accept_enc | HTTP Accept-Languageヘッダーを検索対象とします。 | リクエスト |
http.referer | HTTP Refererヘッダーを検索対象とします。 | リクエスト |
http.connection | HTTP Connection ヘッダーを検索対象とします。 | リクエスト |
http.content_type | HTTP Content-Typeヘッダーを検索対象とします。 | 両方向 |
http.content_len | HTTP Content-Lengthヘッダーを検索対象とします。 | 両方向 |
http.start | HTTPリクエストまたはレスポンスの開始を検索対象とします。 | 両方向 |
http.protocol | HTTPリクエストまたはレスポンスのプロトコルフィールドを検索対象とします。 | 両方向 |
http.header_names | HTTPヘッダーの名前を検索対象とします。 ヘッダーが存在しないことを確認したり、ヘッダーが特定の順序に並んでいるか確認したりする場合に便利です。 |
両方向 |
http.stat_msg | HTTPステータスメッセージを検索対象とします。 | レスポンス |
http.stat_code | HTTPステータスコードを検索対象とします。 | レスポンス |
http.response_line | HTTPレスポンス全てを検索対象とします。 | レスポンス |
http.header | HTTPヘッダーを検索対象とします。 なお、cookieなど一部の独自の値については他の値で取得する必要があります。 |
両方向 |
http.header.raw | HTTPヘッダーを検索対象とします。http.header はすべて小文字であるように正規化されますが、http.header.raw は大文字、小文字を区別します。 |
両方向 |
http.cookie | cookieを検索対象とします。 | 両方向 |
http.server | HTTP Serverヘッダーを検索対象とします。 | レスポンス |
http.location | HTTP locationヘッダーを検索対象とします。 | レスポンス |
file_data | HTTPレスポンスボディを検索対象とします。 | レスポンス |
ssl/tls
ssl/tls
はSSL/TLSのハンドシェイクのさまざまなプロパティを検索対象とします。よく利用されるものを抜粋して以下に記載します。
値 | 説明 |
---|---|
tls.cert_subject | TLS/SSL証明書のSubjectフィールドを検索対象とします。 |
tls.sni | TLS/SSL通信をしているServer Name Indication(SNI)フィールドを検索対象とします。 |
tls_cert_expired | 証明書の有効期限が切れている場合、trueを返します。 |
tls_cert_valid | 証明書の有効期限が切れていない場合、trueを返します。 |
tls.version | 使用されているTLSのバージョンを返却します。 TLS 1.2が使われているか確認する際は、「tls.version:1.2;」とします。 |
ssl_version | 使用されているSSL/TLSのバージョンを返却します。 TLS 1.2が使われているか確認する際は、「ssl_version:tls1.2;」とします。 |
Network Firewall上での評価フロー
Network Firewallはステートレスエンジンと、ステートフルエンジンとの2つの検査エンジンがあります。そして以下の通り、ステートフルエンジン自体がSuricata互換です。
そのため、ステートフルエンジンで動作する、Domain Listや、5-tupleで設定するルールについても、Suricataのシグネチャーに置き換えて書くことができます。
The stateful engine takes rules that are compatible with Suricata, an open source intrusion prevention system (IPS). Suricata provides a standard rule-based language for stateful network traffic inspection.
そもそも、ステートレスエンジンとステートフルエンジンの関係性は? という方は以下の図をご覧ください。
抜粋: Network Firewall stateless and stateful rules engines
評価のフローとしては以下の通りです。
- ステートレスエンジン内で設定した優先度順に評価する
- 設定したどのルールにも合致しなかった場合は、デフォルトアクションで設定した処理に進む
- ステートレスエンジンで設定できる処理は、以下の通り
- alert
- pass
- drop
- Forward to stateful rule groups
- ステートレスエンジンから転送された処理をステートフルエンジンで順番に評価する
- ステートフルエンジンで設定できる処理は、以下の通りで、上から優先度が高い
- pass
- drop
- alert
- ステートフルエンジンで設定できる処理は、以下の通りで、上から優先度が高い
また、Network Firewallが互換性のあるSuricataのバージョンは、以下の通り、バージョン5.0.2です。
A stateful rule group is a rule group that uses Suricata compatible intrusion prevention system (IPS) specifications. Suricata is an open source network IPS that includes a standard rule-based language for stateful network traffic inspection. AWS Network Firewall supports Suricata version 5.0.2.
上述したSuricataの説明で参考にしたドキュメントも、Suricata 5.0.2の公式ドキュメントになります。
やってみた
キャパシティの計算
まずは、キャパシティの計算をする必要があります。Network Firewallには設定できるルール数にキャパシティという制限(クォーター)があります。
キャパシティはファイアウォールポリシー1つにあたり以下のようになっています。
- ルール数の上限
- ステートレスルールグループ:
10,000
ルール - ステートフルルールグループ:
30,000
ルール
- ステートレスルールグループ:
キャパシティの変更は作成後はできず、再作成するしかないので、今後増えるであろうルールを予測して設定する必要があります。
今回のSuricata互換のIPSルールは、1行あたり1つキャパシティを消費します。
そのため、文字数の上限(上限1,000,000
バイト)もありますが、最大30,000行
のルールを設定することができます。
仮に、キャパシティの上限を超えてしまうと、更新しようとしたタイミングで、StatefulRules capacity exceeded
と表示されます。
今回は、以下の通り、httpもしくはhttpsでdev.classmethod.jp
に通信したときに、アラートを出すというを設定しようと思います。
設定ルールが2行あるので、必要なキャパシティは2
になります。ただ、今回は少し余裕を持って、10
で設定しておきます。
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED http://dev.classmethod.jp"; http.host; content:"dev.classmethod.jp"; startswith; endswith; flow:to_server, established; sid:1000001; rev:1;) alert tls $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED https://dev.classmethod.jp"; tls.sni; content:"dev.classmethod.jp"; startswith; endswith; flow:to_server, established; sid:1000002; rev:1;)
CDKでデプロイ
いつものごとくCDKでやっていきます。 構成は前回と同じで以下の通りです。
CDK内部では、Suricataのシグネチャーを記載したファイルを読み込むようにしました。
// Read IPS rules const ipsRules = fs.readFileSync("./ips.rules", "utf8"); // Create Network Firewall rule group const networkfirewallRuleGroupIps = new networkfirewall.CfnRuleGroup( this, "NetworkfirewallRuleGroupIps", { capacity: 10, ruleGroupName: "IpsRuleGroup", type: "STATEFUL", ruleGroup: { rulesSource: { rulesString: ipsRules, }, ruleVariables: { ipSets: { HOME_NET: { definition: [vpc.vpcCidrBlock], }, EXTERNAL_NET: { definition: ["any"], }, }, }, }, } );
デプロイ後、セッションマネージャーでEC2インスタンスにログインして、curl
で`http://dev.classmethod.jp`と、`https://dev.classmethod.jp`にアクセスしました。
ログは以下の通りで、通信は正常にできています。
sh-4.2$ curl -I http://dev.classmethod.jp HTTP/1.1 301 Moved Permanently Server: awselb/2.0 Date: Fri, 07 May 2021 07:29:52 GMT Content-Type: text/html Content-Length: 134 Connection: keep-alive Location: https://dev.classmethod.jp:443/ sh-4.2$ curl -I https://dev.classmethod.jp HTTP/2 200 date: Fri, 07 May 2021 07:30:07 GMT content-type: text/html; charset=utf-8 content-length: 169005 set-cookie: AWSALB=szpO81N0hPEcO6Z4YcGajE9EEO1mrqvcHEBE48ojUcFPilgJ68oLHLf9fGWuENoogoUUncxwyoPjiYxAaloHoqqSe1Ow7Pg0V9RUGXBvLzcnlbm1cnIi63xgXTIN; Expires=Fri, 14 May 2021 07:30:07 GMT; Path=/ set-cookie: AWSALBCORS=szpO81N0hPEcO6Z4YcGajE9EEO1mrqvcHEBE48ojUcFPilgJ68oLHLf9fGWuENoogoUUncxwyoPjiYxAaloHoqqSe1Ow7Pg0V9RUGXBvLzcnlbm1cnIi63xgXTIN; Expires=Fri, 14 May 2021 07:30:07 GMT; Path=/; SameSite=None; Secure server: nginx/1.18.0 vary: Accept-Encoding x-amzn-requestid: 6c343df9-51a9-49aa-aed7-210817682b69 x-amzn-remapped-content-length: 169005 x-amz-apigw-id: e8nXYEvDNjMFo4Q= cache-control: max-age=300 etag: "2942d-baiRZkF+/OR2k69LHWf0LUyDoHg" x-powered-by: Express x-amzn-trace-id: Root=1-6094ec2f-3ee1cbf2743b46aa0e4d8a48;Sampled=0 via: 1.1 0c2ca767ecc2f5a180d1781f16f1e2f3.cloudfront.net (CloudFront), 1.1 fd95d915cb5f672e4b8b3613a0dde9ea.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C4 vary: Accept-Encoding x-cache: Hit from cloudfront x-amz-cf-pop: NRT12-C2 x-amz-cf-id: TNOymqDo6NUGNUgdtKb_vZkMlf_JdHhD70HGslAAflcj_6gutdCGbQ== age: 33 expires: Fri, 07 May 2021 07:35:07 GMT strict-transport-security: max-age=31536000; includeSubDomains accept-ranges: bytes
CloudWatch Logsに出力しているAlertを確認してみると、以下の通り2つログが出力されていました。
{ "firewall_name": "NetworkFirewall", "availability_zone": "us-east-1a", "event_timestamp": "1620372592", "event": { "timestamp": "2021-05-07T07:29:52.667151+0000", "flow_id": 429088379510758, "event_type": "alert", "src_ip": "10.0.1.150", "src_port": 3481, "dest_ip": "75.2.71.201", "dest_port": 80, "proto": "TCP", "tx_id": 0, "alert": { "action": "allowed", "signature_id": 1000001, "rev": 1, "signature": "DETECTED http://dev.classmethod.jp", "category": "", "severity": 3 }, "http": { "hostname": "dev.classmethod.jp", "url": "/", "http_user_agent": "curl/7.61.1", "http_method": "HEAD", "protocol": "HTTP/1.1", "length": 0 }, "app_proto": "http" } }, { "firewall_name": "NetworkFirewall", "availability_zone": "us-east-1a", "event_timestamp": "1620372606", "event": { "timestamp": "2021-05-07T07:30:06.627396+0000", "flow_id": 980871418901001, "event_type": "alert", "src_ip": "10.0.1.150", "src_port": 9696, "dest_ip": "99.83.185.173", "dest_port": 443, "proto": "TCP", "tx_id": 0, "alert": { "action": "allowed", "signature_id": 1000002, "rev": 1, "signature": "DETECTED https://dev.classmethod.jp", "category": "", "severity": 3 }, "tls": { "sni": "dev.classmethod.jp", "version": "UNDETERMINED", "ja3": {}, "ja3s": {} }, "app_proto": "tls" } }
「VPCエンドポイントは不要なのか検証してみた」のリベンジ
Suricata互換のIPSルールを使えば、自分で細かい設定をする必要があるとは言え、Domain Listも使えるし、優先度も設定できるしということで、最高ですね!!
ということで、前回企画倒れになってしまった「Network FirewallとNAT Gatewayを使えば、VPCエンドポイントは不要なのか検証してみた」にリベンジしてみようと思います。
最早、私を突き動かしているのはロマンです。
早速、作成した環境からSSMエンドポイントを削除し、IPSルールを以下のように変更してから、CDKで再度デプロイしました。
pass tls $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED https://ssm.us-east-1.amazonaws.com"; tls.sni; content:"ssm.us-east-1.amazonaws.com"; startswith; endswith; flow:to_server, established; priority:1; sid:1100001; rev:1;) pass tls $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED https://ssmmessages.us-east-1.amazonaws.com"; tls.sni; content:"ssmmessages.us-east-1.amazonaws.com"; startswith; endswith; flow:to_server, established; priority:1; sid:1100002; rev:1;) pass tls $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED https://ec2messages.us-east-1.amazonaws.com"; tls.sni; content:"ec2messages.us-east-1.amazonaws.com"; startswith; endswith; flow:to_server, established; priority:1; sid:1100003; rev:1;) drop tls $HOME_NET any <> $EXTERNAL_NET any (msg:"not matching any allowlisted FQDNs"; flow:to_server, established, only_stream; priority:254; sid:1200001; rev:1;) drop ip $HOME_NET any <> $EXTERNAL_NET any (msg:"not matching any allowlisted FQDNs"; flow:to_server, established; priority:255; sid:1200002; rev:1;)
すると、以下ご覧の通り、セッションマネージャーで接続できて、pingや許可したドメイン以外のhttp/httpsの通信は出来ないことが確認できました!!
リベンジ成功です!!!
それでは設定したルールの説明をします。 大枠としては、SSMに必要な通信だけは許可して、それ以外の通信はドロップするように指定しています。
まず、1行目から3行目SSMに必要なFQDNを許可します。ポイントは3行目と4行目にあるflow:to_server
です。
4行目のflow:to_server, established, only_stream;
はSSMの通信で再構成前のパケットを許可するためです。仮に、flow:to_server, established;
としてしまうと、再構成前のパケットも検知対象となってしまい、マネージドインスタンスとして登録されません。
5行目は許可していない通信を明示的にドロップするように指定します。
提供されているシグネチャーの適用
参考までに、ネット上に公開されているSuricata互換のシグネチャーを適用してみます。適用するシグネチャーはProofpointが提供しているEmerging Threatsです。
今回は、https://rules.emergingthreats.net/open/suricata/emerging-all.rules の中の以下シグネチャーを適用してみました。
alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET WEB_CLIENT Mozilla Firefox nsTreeSelection Element invalidateSelection Remote Code Execution Attempt"; flow:established,to_client; content:"document.getElementById(|27|treeset|27|)"; nocase; content:"view.selection"; nocase; distance:0; content:"invalidateRange"; nocase; distance:0; reference:bid,41853; reference:cve,2010-2753; classtype:attempted-user; sid:2013144; rev:2; metadata:affected_product Web_Browsers, affected_product Web_Browser_Plugins, attack_target Client_Endpoint, created_at 2011_06_30, deployment Perimeter, signature_severity Major, tag Web_Client_Attacks, updated_at 2016_07_01;) alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET WEB_CLIENT Download of PDF With Compressed Flash Content"; flowbits:noalert; flow:established,to_client; content:"stream"; content:"|0A|CWS"; within:5; fast_pattern; pcre:"/stream(\x0D\x0A|\x0A)CWS/"; flowbits:set,ET.flash.pdf; reference:url,www.symantec.com/connect/blogs/analysis-zero-day-exploit-adobe-flash-and-reader; reference:url,blog.zynamics.com/2010/06/09/analyzing-the-currently-exploited-0-day-for-adobe-reader-and-adobe-flash/; classtype:misc-activity; sid:2012907; rev:3; metadata:affected_product Web_Browsers, affected_product Web_Browser_Plugins, attack_target Client_Endpoint, created_at 2011_05_31, deployment Perimeter, signature_severity Major, tag Web_Client_Attacks, updated_at 2016_07_01;) alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET WEB_CLIENT Possible Adobe Multimedia Doc.media.newPlayer Memory Corruption Attempt"; flow:to_client,established; content:"PDF-"; depth:300; content:"this.media.newPlayer|28|null"; nocase; distance:0; content:"util.printd"; nocase; within:150; reference:url,www.metasploit.com/redmine/projects/framework/repository/revisions/7881/entry/modules/exploits/windows/fileformat/adobe_media_newplayer.rb; reference:url,vrt-sourcefire.blogspot.com/2009/12/adobe-reader-medianewplayer-analysis.html; reference:bid,37331; reference:cve,2009-4324; classtype:attempted-user; sid:2010495; rev:13; metadata:affected_product Web_Browsers, affected_product Web_Browser_Plugins, attack_target Client_Endpoint, created_at 2010_07_30, deployment Perimeter, signature_severity Major, tag Web_Client_Attacks, updated_at 2016_07_01;)
cdk diff
をすると、以下のように正しくシグネチャーの差分情報が出力されました。
> npx cdk diff Stack AppStack Resources [~] AWS::NetworkFirewall::RuleGroup NetworkfirewallRuleGroupIps NetworkfirewallRuleGroupIps └─ [~] RuleGroup └─ [~] .RulesSource: └─ [~] .RulesString: ├─ [-] pass tls $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED https://ssm.us-east-1.amazonaws.com"; tls.sni; content:"ssm.us-east-1.amazonaws.com"; startswith; endswith; flow:to_server, established; priority:1; sid:1100001; rev:1;) pass tls $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED https://ssmmessages.us-east-1.amazonaws.com"; tls.sni; content:"ssmmessages.us-east-1.amazonaws.com"; startswith; endswith; flow:to_server, established; priority:1; sid:1100002; rev:1;) pass tls $HOME_NET any -> $EXTERNAL_NET any (msg:"DETECTED https://ec2messages.us-east-1.amazonaws.com"; tls.sni; content:"ec2messages.us-east-1.amazonaws.com"; startswith; endswith; flow:to_server, established; priority:1; sid:1100003; rev:1;) drop tls $HOME_NET any <> $EXTERNAL_NET any (msg:"not matching any allowlisted FQDNs"; flow:to_server, established, only_stream; priority:254; sid:1200001; rev:1;) drop ip $HOME_NET any <> $EXTERNAL_NET any (msg:"not matching any allowlisted FQDNs"; flow:to_server; priority:255; sid:1200002; rev:1;) └─ [+] alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET WEB_CLIENT Mozilla Firefox nsTreeSelection Element invalidateSelection Remote Code Execution Attempt"; flow:established,to_client; content:"document.getElementById(|27|treeset|27|)"; nocase; content:"view.selection"; nocase; distance:0; content:"invalidateRange"; nocase; distance:0; reference:bid,41853; reference:cve,2010-2753; classtype:attempted-user; sid:2013144; rev:2; metadata:affected_product Web_Browsers, affected_product Web_Browser_Plugins, attack_target Client_Endpoint, created_at 2011_06_30, deployment Perimeter, signature_severity Major, tag Web_Client_Attacks, updated_at 2016_07_01;) alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET WEB_CLIENT Download of PDF With Compressed Flash Content"; flowbits:noalert; flow:established,to_client; content:"stream"; content:"|0A|CWS"; within:5; fast_pattern; pcre:"/stream(\x0D\x0A|\x0A)CWS/"; flowbits:set,ET.flash.pdf; reference:url,www.symantec.com/connect/blogs/analysis-zero-day-exploit-adobe-flash-and-reader; reference:url,blog.zynamics.com/2010/06/09/analyzing-the-currently-exploited-0-day-for-adobe-reader-and-adobe-flash/; classtype:misc-activity; sid:2012907; rev:3; metadata:affected_product Web_Browsers, affected_product Web_Browser_Plugins, attack_target Client_Endpoint, created_at 2011_05_31, deployment Perimeter, signature_severity Major, tag Web_Client_Attacks, updated_at 2016_07_01;) alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET WEB_CLIENT Possible Adobe Multimedia Doc.media.newPlayer Memory Corruption Attempt"; flow:to_client,established; content:"PDF-"; depth:300; content:"this.media.newPlayer|28|null"; nocase; distance:0; content:"util.printd"; nocase; within:150; reference:url,www.metasploit.com/redmine/projects/framework/repository/revisions/7881/entry/modules/exploits/windows/fileformat/adobe_media_newplayer.rb; reference:url,vrt-sourcefire.blogspot.com/2009/12/adobe-reader-medianewplayer-analysis.html; reference:bid,37331; reference:cve,2009-4324; classtype:attempted-user; sid:2010495; rev:13; metadata:affected_product Web_Browsers, affected_product Web_Browser_Plugins, attack_target Client_Endpoint, created_at 2010_07_30, deployment Perimeter, signature_severity Major, tag Web_Client_Attacks, updated_at 2016_07_01;)
デプロイ後、マネージメントコンソールからルールグループを確認すると、以下のようになっていました。分量が多くなると、マネージメントコンソールで管理をするのはちょっと大変そうですね。
検知したシグネチャーの集計
IPSで様々な通信を許可/拒否できることを確認しましたが、どういった通信が検知されたのか気になりますよね。 Network FirewallのアラートログをCloudWatch Logsに出力していたので、今回はCloudWatch Logs Insightsで集計してみます。
CloudWatch Logs Insightsのクエリは以下記事を参考にしました。
Network Firewallのアラートを出力していたCloudWatch Logsを選択し、以下クエリを実行します。
fields @timestamp, @message | sort @timestamp desc | limit 20
直近20件のログが抽出されています。
次はどのシグネチャー毎の検知数を確認してみます。
以下のクエリで、シグネチャーのメッセージであるevent.alert.signature
をカウントします。
fields @message | stats count(event.alert.signature) by event.alert.signature
以下の通り、シグネチャー毎の検知数が確認できました。運用担当の方もこんなに簡単に集計できると嬉しいのではないでしょうか。
Network FirewallはSuricataを使いこなすと楽しくなる
Suricata互換のルールを使うことによって、一気にできる幅が広がったような感覚です。
IPS機能実装するのがめんどくさい...
といった方も、Emerging Threatsなど公開されているSuricataのルールから試してみてはいかがでしょうか。
私自身はこのGWはNetwork Firewallと格闘して、ようやく友達になれたような気がします。これからもNetwork Firewallを上手く活用していこうと思います!!
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!