ELK ではじめる Cisco Secure Firewall のログ分析

## はじめに

この記事は Cisco Systems Japan の有志による [Cisco Systems Japan Advent Calendar 2022](https://qiita.com/advent-calendar/2022/cisco
) として投稿しています。

## やったこと 
Cisco Secure Firewall のログを簡単に分析できる環境を作るために以下の2つを実施しました。
- [docker-elk](https://github.com/deviantony/docker-elk) をたてる
- Logstash の grok パターンでパースする

## モチベーション
大量のログから必要な情報だけを抜き出したりそれを使って分析したいとき、コマンドラインのツールだけでは（自分の知識では）限界があると感じたため GUI で操作ができるものを調査したところ、ELK が便利そうかつ無料でしたので構築してみました。

今回はたまたま Cisco Secure Firewall のログを題材にパースしましたが、異なる構造を持ったログにも応用が効くプリミティブな内容になっています。

## 構成図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437531/8e3bfddb-dedf-3671-6941-ed6f543340a3.png)


## Cisco Secure Firewall とは 
Cisco 社の次世代ファイアウォール＆IPS です。Sourcefire 社から買収した製品をベースに作られており旧称 Firepower です。詳しくは[こちら](https://www.cisco.com/site/jp/ja/products/security/firewalls/index.html)。

## ELK とは 
[ELK](https://www.elastic.co/jp/what-is/elk-stack) とは、Elasticsearch、Logstash、Kibana という3つのオープンソースプロジェクトの頭文字です。Elasticsearch は検索・分析エンジン、Logstash はサーバサイドのデータ処理エンジン、Kibana はユーザインターフェイスです。

最近では Elastic Agent や Beats など新機能を使うことでファイルの取り込みが容易になっているらしい[^1] ですが今回は昔ながらの ELK を使います。

## ELK を使うメリット 
簡単かつ柔軟に全文検索、可視化、分析ができる環境が手に入ることです。

:::note info
実は ELK を構築しなくても Cisco Secure Firewall を管理する FMC ([Cisco Firepower Management Center](https://www.cisco.com/c/ja_jp/products/security/firepower-management-center/index.html) )で軽快にログ検索ができます。
特に version 7.0 から追加された新機能 [Unified Event Viewer](https://youtu.be/U8k3Ed5D0WA?t=1135) はコリレーション情報も含めたイベント情報を直感的に一行で表示してくれて便利です。
:::

## ELK 環境の構築
```
git clone  https://github.com/deviantony/docker-elk.git
```
:::note warn
構築には Docker、Docker Compose、Git が必要です。
Docker、Docker Compose のインストール方法は公式ガイドもしくは[こちら](https://duo.com/docs/dng#install-docker)をご参照ください。
:::

インストール後、デフォルトパスワードを変更してサービス再起動します。この記事では割愛します。
参照：https://qiita.com/Toru_Kubota/items/5a9462ef1e625167f257

## ログファイルの取り込み設定 

今回扱うログは Connection Event のログとし、 `docker-elk` 配下の `mylog` ディレクトリに置きました。Logstash は syslog をリアルタイムに受け取ることもできますが、今回は静的にファイルを参照する方式とします。

設定箇所は以下２箇所です。 
１. `docker-compose.yml` におけるボリュームマウントの指定 
```/docker-elk/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   # <= これを追記
(中略)
```
２. `logstash.conf` における input の指定 
```/docker-elk/logstash/pipeline/logstash.conf
input {
  file {
    path=>"/usr/share/logstash/mylog/*/conn/*.log"   # <= ログファイルのパスを指定
    start_position=>"beginning"
    sincedb_path => "/dev/null"
    tags => "conn"
  }
}
```
正規表現が使えます。

## パースの設定 

[grok](https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html) と呼ばれるフィルタを使ってログを構造や意味ごとに分割（パース）していくことができます。中身は正規表現です。

### 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` というフィールドに投入したログの情報がまるっと入ります。構造化されていないですが、この状態でもログに含まれる情報は検索することができ便利です。一方、フィールドを抜き出して分析にしたりグラフにする場合は扱いづらいと言えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437531/793e7580-06a2-d1e6-a87a-db5bd6b199b2.png)


### 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


:::note info
定義済みの正規表現のパターンは[こちら](https://www.alibabacloud.com/help/ja/log-service/latest/grok-patterns)で参照できます。（公式の github.com のリンクが死んでいます）
:::

途中経過がこちら。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437531/745919cb-077c-ec5c-ec6c-155b68dca434.png)
time や device といった新しいフィールドが `Key: Value` 形式で取り出せました。

上記の Grok の パターンはこのような内容です。
` %{SYSLOGTIMESTAMP:time}%{SPACE}%{HOSTNAME:device}%{SPACE}%{WORD:messageid}%{GREEDYDATA:msg1}`

### さらに grok フィルタの作成   
分析するために欲しい情報としては通信が許可されたかどうか、送信元や宛先 IP や Port などですので、そこを中心にコツコツパターンを書いていくとこんな形になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437531/314397c5-82e7-3bfd-2830-813406f5dfb5.png)

```json
{
  "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 に適用します。
```/docker-elk/logstash/pipeline/logstash.conf
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`)とこんな形で新しいフィールドが見えてきます。読み込みに数分かかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437531/36a388fa-60ad-941a-dd43-0fa44ad1bea6.png)

パースしたフィールドで検索した例がこちらです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437531/297104c3-6dd1-5710-881d-95b5156a8a84.png)
クエリで AND や OR、NOT 検索することでより効率的に検索ができます。
以上で IP や Port、通信の方向などを構造化された情報として扱うことができるようになり、高度な分析をするための準備が整いました！

## 最後に
長くなってしまいましたが最後までお読みいただきありがとうございました。
本記事では Cisco Secure Firewall の Connection Event ログを ELK で取り込み grok でパースしてフィールドごとに分析できる状態にしました。

今後の展望として、この環境を使った脅威ハンティングの調査例の紹介、Cisco Secure Firewall が生成する各種ログの完璧な grok パターンの生成、Kibana で便利なグラフの作成、別のセキュリティ製品のログの統合、Elastic Agent や Beats の活用などがありそうです。
本記事についてのアドバイス、ご意見、ご質問などがありましたらお待ちしております！

よい年末をお過ごしください～ :christmas_tree:

## 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

[^1]: 詳しくは[こちら](https://qiita.com/yukshimizu/items/eef4dffa435802567a44)や[こちら](https://qiita.com/yukshimizu/items/abec12ac46749db46b40)で私も勉強します。