はじめに
この記事は Cisco Systems Japan の有志による Cisco Systems Japan Advent Calendar 2022 として投稿しています。
やったこと
Cisco Secure Firewall のログを簡単に分析できる環境を作るために以下の2つを実施しました。
- docker-elk をたてる
- Logstash の grok パターンでパースする
モチベーション
大量のログから必要な情報だけを抜き出したりそれを使って分析したいとき、コマンドラインのツールだけでは(自分の知識では)限界があると感じたため GUI で操作ができるものを調査したところ、ELK が便利そうかつ無料でしたので構築してみました。
今回はたまたま Cisco Secure Firewall のログを題材にパースしましたが、異なる構造を持ったログにも応用が効くプリミティブな内容になっています。
構成図
Cisco Secure Firewall とは
Cisco 社の次世代ファイアウォール&IPS です。Sourcefire 社から買収した製品をベースに作られており旧称 Firepower です。詳しくはこちら。
ELK とは
ELK とは、Elasticsearch、Logstash、Kibana という3つのオープンソースプロジェクトの頭文字です。Elasticsearch は検索・分析エンジン、Logstash はサーバサイドのデータ処理エンジン、Kibana はユーザインターフェイスです。
最近では Elastic Agent や Beats など新機能を使うことでファイルの取り込みが容易になっているらしい1 ですが今回は昔ながらの ELK を使います。
ELK を使うメリット
簡単かつ柔軟に全文検索、可視化、分析ができる環境が手に入ることです。
実は ELK を構築しなくても Cisco Secure Firewall を管理する FMC (Cisco Firepower Management Center )で軽快にログ検索ができます。
特に version 7.0 から追加された新機能 Unified Event Viewer はコリレーション情報も含めたイベント情報を直感的に一行で表示してくれて便利です。
ELK 環境の構築
git clone https://github.com/deviantony/docker-elk.git
構築には Docker、Docker Compose、Git が必要です。
Docker、Docker Compose のインストール方法は公式ガイドもしくはこちらをご参照ください。
インストール後、デフォルトパスワードを変更してサービス再起動します。この記事では割愛します。
参照:https://qiita.com/Toru_Kubota/items/5a9462ef1e625167f257
ログファイルの取り込み設定
今回扱うログは Connection Event のログとし、 docker-elk
配下の mylog
ディレクトリに置きました。Logstash は syslog をリアルタイムに受け取ることもできますが、今回は静的にファイルを参照する方式とします。
設定箇所は以下2箇所です。
1. docker-compose.yml
におけるボリュームマウントの指定
(中略)
logstash:
build:
context: logstash/
args:
ELASTIC_VERSION: ${ELASTIC_VERSION}
volumes:
- ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro,Z
- ./logstash/pipeline:/usr/share/logstash/pipeline:ro,Z
- ./mylog/:/usr/share/logstash/mylog:ro # <= これを追記
(中略)
2. logstash.conf
における input の指定
input {
file {
path=>"/usr/share/logstash/mylog/*/conn/*.log" # <= ログファイルのパスを指定
start_position=>"beginning"
sincedb_path => "/dev/null"
tags => "conn"
}
}
正規表現が使えます。
パースの設定
grok と呼ばれるフィルタを使ってログを構造や意味ごとに分割(パース)していくことができます。中身は正規表現です。
grok フィルタを利用しない場合の見え方(参考)
Cisco Secure Firewall の Connection Event ログは例えば以下のような構造になっています。ご覧の通り通信の内容によって若干フィールドが変わります。(ちなみに Intrusion Event や File Event のログも全然違います😇エグいです😇)
#ブロックされた http 通信
Nov 15 02:59:24 firepower-1 %FTD-6-430003: EventPriority: High, DeviceUUID: 5b924422-3881-11e1-a2bd-9dcb2d79f6cd, InstanceID: 4, FirstPacketSecond: 2022-11-15T02:59:24Z, ConnectionID: 5157, AccessControlRuleAction: Block, AccessControlRuleReason: Intrusion Block, SrcIP: 10.1.12.12, DstIP: 10.2.9.8, SrcPort: 53396, DstPort: 80, Protocol: tcp, IngressInterface: outside, EgressInterface: inside, IngressZone: outzone-tp, EgressZone: inzone-tp, ACPolicy: ACP-1, AccessControlRuleName: CATCH-ALL, Prefilter Policy: Default Prefilter Policy, ConnectionDuration: 0, IPSCount: 2, InitiatorPackets: 2, ResponderPackets: 1, InitiatorBytes: 140, ResponderBytes: 74, NAPPolicy: Balanced Security and Connectivity
#許可された http 通信
Nov 15 04:40:41 firepower-2 %FTD-6-430003: EventPriority: Low, DeviceUUID: dbe055ce-3881-11e1-8dfb-f19c7899208c, InstanceID: 6, FirstPacketSecond: 2022-11-15T04:40:35Z, ConnectionID: 17012, AccessControlRuleAction: Allow, SrcIP: 10.1.90.17, DstIP: 10.2.5.8, SrcPort: 34791, DstPort: 80, Protocol: tcp, IngressInterface: outside, EgressInterface: inside, IngressZone: outzone-tp, EgressZone: inzone-tp, ACPolicy: ACP-2, AccessControlRuleName: CATCH-ALL, Prefilter Policy: Default Prefilter Policy, UserAgent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393, Client: Edge, ClientVersion: 14.14393, ApplicationProtocol: HTTP, WebApplication: Web Browsing, ConnectionDuration: 6, InitiatorPackets: 4, ResponderPackets: 39, InitiatorBytes: 463, ResponderBytes: 48747, NAPPolicy: Balanced Security and Connectivity, HTTPResponse: 404, ReferencedHost: 10.2.5.8, URL: http://10.2.5.8/old
#許可された dns 通信
Nov 15 07:44:00 firepower-2 %FTD-6-430003: EventPriority: Low, DeviceUUID: dbe055ce-3881-11e1-8dfb-f19c7899208c, InstanceID: 8, FirstPacketSecond: 2022-11-15T07:44:00Z, ConnectionID: 29241, AccessControlRuleAction: Allow, SrcIP: 10.2.5.1, DstIP: 128.8.10.90, SrcPort: 24887, DstPort: 53, Protocol: udp, IngressInterface: inside, EgressInterface: outside, IngressZone: inzone-tp, EgressZone: outzone-tp, ACPolicy: ACP-2, AccessControlRuleName: CATCH-ALL, Prefilter Policy: Default Prefilter Policy, Client: DNS, ApplicationProtocol: DNS, ConnectionDuration: 0, InitiatorPackets: 1, ResponderPackets: 0, InitiatorBytes: 70, ResponderBytes: 0, NAPPolicy: Balanced Security and Connectivity
#許可された http 通信
Nov 15 08:43:54 firepower-2 %FTD-6-430003: EventPriority: Low, DeviceUUID: dbe055ce-3881-11e1-8dfb-f19c7899208c, InstanceID: 5, FirstPacketSecond: 2022-11-15T08:43:54Z, ConnectionID: 39454, AccessControlRuleAction: Allow, SrcIP: 10.1.250.32, DstIP: 10.2.5.10, SrcPort: 48241, DstPort: 443, Protocol: tcp, IngressInterface: outside, EgressInterface: inside, IngressZone: outzone-tp, EgressZone: inzone-tp, ACPolicy: ACP-2, AccessControlRuleName: CATCH-ALL, Prefilter Policy: Default Prefilter Policy, ConnectionDuration: 0, InitiatorPackets: 1, ResponderPackets: 1, InitiatorBytes: 0, ResponderBytes: 0, NAPPolicy: Balanced Security and Connectivity
この情報をパースせずに投入した場合の Kibana の Discover(検索結果)画面の様子がこちらです。message
というフィールドに投入したログの情報がまるっと入ります。構造化されていないですが、この状態でもログに含まれる情報は検索することができ便利です。一方、フィールドを抜き出して分析にしたりグラフにする場合は扱いづらいと言えます。
grok フィルタの作成
パースの設定は Logstash の設定ファイルにおいて filter の grok フィルターで指定します。Pattern と呼ばれる別ファイルで指定するか、grok の中に直接記述するか選べますが、今回は直接書きます。
どんなフィルタ条件であればパースが成功するのか、Kibana の Dev Tools
にある Grok Debugger
で事前にシミュレーションすることができます。
Grok Debugger の URL:
http://{docker-elk の IP}:5601/app/dev_tools#/grokdebugger
実際に試してみましょう。例えば FTD のログの日時表示は Syslog 形式で始まりこれは Grok のパターンとしてすでに定義済みの SYSLOGTIMESTAMP
が使えます。同じく次の文字列は HOSTNAME
が使えます。残りその他をすべてマッチさせる表現が GREEDYDATA
となります。コロンで区切った右側がそのフィールドの任意の名前です。
参照:https://qiita.com/tuneyukkie/items/75cbb4d44f901fec2188
定義済みの正規表現のパターンはこちらで参照できます。(公式の github.com のリンクが死んでいます)
途中経過がこちら。
time や device といった新しいフィールドが Key: Value
形式で取り出せました。
上記の Grok の パターンはこのような内容です。
%{SYSLOGTIMESTAMP:time}%{SPACE}%{HOSTNAME:device}%{SPACE}%{WORD:messageid}%{GREEDYDATA:msg1}
さらに grok フィルタの作成
分析するために欲しい情報としては通信が許可されたかどうか、送信元や宛先 IP や Port などですので、そこを中心にコツコツパターンを書いていくとこんな形になります。
{
"msg3": "NAPPolicy: Balanced Security and Connectivity, HTTPResponse: 404, ReferencedHost: 10.2.5.8, URL: http://10.2.5.8/old",
"srcip": "10.1.90.17",
"ptorocol": "tcp",
"msg2": "IngressZone: outzone-tp, EgressZone: inzone-tp, ACPolicy: ACP-2, AccessControlRuleName: CATCH-ALL, Prefilter Policy: Default Prefilter Policy, UserAgent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393, Client: Edge, ClientVersion: 14.14393, ApplicationProtocol: HTTP, WebApplication: Web Browsing, ConnectionDuration: 6, InitiatorPackets: 4, ResponderPackets: 39",
"msg1": "%FTD-6-430003: EventPriority: Low, DeviceUUID: dbe055ce-3881-11e1-8dfb-f19c7899208c, InstanceID: 6, FirstPacketSecond: 2022-11-15T04:40:35Z, ConnectionID: 17012",
"dstport": "80",
"respBytes": "48747",
"ingressIF": "outside",
"egressIF": "inside",
"initBytes": "463",
"action": "Allow",
"srcport": "34791",
"time": "Nov 15 04:40:41",
"dstip": "10.2.5.8",
"device": "firepower-2"
}
このパターンにより以下のような情報を抽出することができそうです。
- イベントの時間
- 送信元 IP
- 宛先 IP
- プロトコル
- 送信元ポート
- 宛先ポート
- 送信バイト
- 受信バイト
- 受信インターフェイス
- 送信インターフェイス
上記の Grok パターンはこのような内容です。
%{SYSLOGTIMESTAMP:time}%{SPACE}%{HOSTNAME:device}%{SPACE}%{GREEDYDATA:msg1},%{SPACE}AccessControlRuleAction: %{WORD:action},%{SPACE}SrcIP: %{IP:srcip},%{SPACE}DstIP: %{IP:dstip},%{SPACE}SrcPort: %{INT:srcport},%{SPACE}DstPort: %{INT:dstport},%{SPACE}Protocol: %{WORD:ptorocol},%{SPACE}IngressInterface: %{WORD:ingressIF},%{SPACE}EgressInterface: %{WORD:egressIF},%{SPACE}%{GREEDYDATA:msg2},%{SPACE}InitiatorBytes: %{NUMBER:initBytes},%{SPACE}ResponderBytes: %{NUMBER:respBytes},%{SPACE}%{GREEDYDATA:msg3}
それではこれを logstash.conf
の filter に適用します。
filter {
grok {
match=>{
"message" => "%{SYSLOGTIMESTAMP:time}%{SPACE}%{HOSTNAME:device}%{SPACE}%{GREEDYDATA:msg1},%{SPACE}AccessControlRuleAction: %{WORD:action},%{SPACE}SrcIP: %{IP:srcip},%{SPACE}DstIP: %{IP:dstip},%{SPACE}SrcPort: %{INT:srcport},%{SPACE}DstPort: %{INT:dstport},%{SPACE}Protocol: %{WORD:ptorocol},%{SPACE}IngressInterface: %{WORD:ingressIF},%{SPACE}EgressInterface: %{WORD:egressIF},%{SPACE}%{GREEDYDATA:msg2},%{SPACE}InitiatorBytes: %{NUMBER:initBytes},%{SPACE}ResponderBytes: %{NUMBER:respBytes},%{SPACE}%{GREEDYDATA:msg3}"
}
}
}
ちなみに output の設定はデフォルトのままです。
この状態で docker-compose しなおす(docker-compose up -d --build
)とこんな形で新しいフィールドが見えてきます。読み込みに数分かかります。
パースしたフィールドで検索した例がこちらです。
クエリで AND や OR、NOT 検索することでより効率的に検索ができます。
以上で IP や Port、通信の方向などを構造化された情報として扱うことができるようになり、高度な分析をするための準備が整いました!
最後に
長くなってしまいましたが最後までお読みいただきありがとうございました。
本記事では Cisco Secure Firewall の Connection Event ログを ELK で取り込み grok でパースしてフィールドごとに分析できる状態にしました。
今後の展望として、この環境を使った脅威ハンティングの調査例の紹介、Cisco Secure Firewall が生成する各種ログの完璧な grok パターンの生成、Kibana で便利なグラフの作成、別のセキュリティ製品のログの統合、Elastic Agent や Beats の活用などがありそうです。
本記事についてのアドバイス、ご意見、ご質問などがありましたらお待ちしております!
よい年末をお過ごしください~
Appendix
実際に試される方向けにメモなどを残しておきます↓
動作確認で使ったコマンド
docker ps
docker logs {container id}
curl -u elastic:{password} -XGET http://localhost:9200/_cat/indices?v
curl -u elastic:{password} -XGET http://localhost:9200/{index name}/_mappings?pretty
はまった場所
-
Grok Debugger の存在に気付かず、grok パターンを
logstash.conf
で直接試行錯誤していて時間を浪費しました。 -
何度も
docker-compose down
=>docker-compose up -d
を繰り返していて docker system や overlay のボリュームが溢れてしまい Elasticsearch が正常に起動しなくなりました。 これはdocker system prune -a
docker system prune --volumes
で一度イメージやボリュームをすべて削除することで解決しました。 -
grok パターン
WORD
でスペースを含む文字列が抜き出せなかったり、HOSTNAME
でコンマを含む文字列がうまく抜き出せなかったりするのは grok のバグだと思ってますがよくわかりませんでした(正確には、Grok Debugger ではうまくいくがlogstash.conf
では異なる挙動となる)。対処方法として、スペースを含む文字列はGREEDYDATA
としました。コンマを含むホスト名は事前にログの中身を簡易な文字列に置換しました。
参照
https://github.com/deviantony/docker-elk
https://qiita.com/ohhara_shiojiri/items/0b45fd000103b7345073
https://qiita.com/subretu/items/5857628534b53f29f5a3
https://qiita.com/mug-cup/items/ba5dd0a14838e83e69ac