FalcoはLinux OS向けのランタイムセキュリティツールであり、システム上での異常なアクティビティや潜在的なセキュリティリスクをリアルタイムで検知し、警告するように設計されています。 Kubernetesとシームレスに統合できるため、クラウド上のWebアプリケーションのセキュリティモニタリングに非常に適しています。 前回の記事で、Falcoの概要、デプロイ方法、実際の検出例について詳しく紹介しました。 Falcoプロジェクトでは、異常検知のためのデフォルトルールセットが提供されており、開発者によってメンテナンスされています。ユーザーは必要に応じてこれらのルールを選択し、利用することが可能です。
Falcoのデフォルトルールセットは強力ですが、前回の記事で指摘した通り、未知のセキュリティリスクを検知できないケースが存在します。 攻撃者は既知の攻撃パターンに加え、常に新たな方法で脆弱性を探り、侵入を試みます。 この記事では、そうした脅威に対抗するために、ホワイトリスト型カスタムルールの活用を検討しましたので、それについて紹介したいと思います。
FalcoはLinux OS向けのランタイムセキュリティツールで、 システム上で発生する異常なアクティビティや潜在的なセキュリティリスクをリアルタイムで検知し、警告するよう設計されています。 このツールの核となるのは、カーネルレベルでのシステムコールを監視するモジュール、異常なイベントを検知するための監視ルールに基づくエージェント、そして外部に通知を行うFalcosidekickエージェントです。
Falcoの監視ルールは、開発元が提供するデフォルトセットを利用することも、組織のセキュリティポリシーに合わせてカスタマイズすることも可能です。
前回の記事では、シンプルなnginx podを起動し、以下の3つの操作を実施した際のFalcoによる検知能力を検証しました。検知結果はWeb UIで確認し、さらにjsonログファイルをダウンロードして詳細に分析しました。
Terminal shell in container
ルールにマッチし、対応するログが生成されました。Drop and execute new binary in container
ルールにマッチし、対応するログが生成されました。Disallowed SSH Connection Non Standard Port
ルールにマッチし、対応するログが生成されました。これらの結果は、Falcoが予定されたイベントを正確に検知できる能力を有していることを示しています。しかし、侵入者が工夫を凝らすことで検知を回避する可能性があることも明らかになりました。
前述の例で、Falcoが特定の異常行動を検知できることを示しましたが、侵入者がFalcoの監視を回避するために工夫を凝らす可能性があります。 ここでは、検証した3つのケースでの潜在的な回避方法と、それに対抗するためのホワイトリスト型カスタムルールの必要性について考察します。
bashコマンドを/dev/hoge
にコピーし、それを実行することで、Terminal shell in container
ルールの検知を回避することが可能です。このルールは特定のバイナリ名(ash, bash, csh, ksh, sh, tcsh, zsh, dash)を検出対象としていますが、ファイル名が変更されると検知できなくなります。
root@v130:~# kubectl exec -it nginx -- cp /bin/bash /dev/hoge
root@v130:~# kubectl exec -it nginx -- /dev/hoge
root@nginx:/#
exit
viを/dev/vi
にコピーし、実行することで、Drop and execute new binary in container
ルールの検知を回避できます。このルールはoverlayfsの最上位層に新規作成されたファイルの実行を検知しますが、/dev
ディレクトリはtmpfsがマウントされており、検知条件から外れてしまいます。
root@nginx:/# apt install vim
root@nginx:/# cp /usr/bin/vi /dev/vi
root@nginx:/# /dev/vi test
実行ファイル名を偽装したり、標準以外のポートを使用してSSH接続することで、Disallowed SSH Connection Non Standard Port
ルールを回避できます。このルールは特定のポート(80, 8080, 88, 443, 8443, 53, 4444)への接続を検知するため、条件に合致しなければ検知されません。
これらのケースは、Falcoのデフォルトルールセットでは検知しきれないシナリオを示しています。攻撃者はFalcoのルールを分析し、これらを回避する新たな手法を常に模索しています。
Falcoのデフォルトルールセットは、セキュリティ脆弱性に関する広範な情報を提供するMITRE ATT&CK データベースを基に作られており、既知のリスクを広くカバーしています。 しかし、実行バイナリファイル名、ファイルの置き場所、ポート番号などを検知条件にしたルールも多く、常に新たな攻撃パターンにさらされ、どうしても監視ルールの作成がいたちごっこになってしまいます。
そこで、すべてのアクティビティを網羅できるようなルールファイルを作成し、出力されたログを元に無害なものを逐次、検知の除外条件に加えていく、ホワイトリスト型のルールセットが作成可能か検討してみました。
ホワイトリスト型カスタムルールでは、システムで許可されるアクティビティのみを明確に定義し、それ以外の全てを潜在的な脅威として扱います。 このアプローチにより、未知の攻撃手法に対しても、もれなく対応することが可能になります。 以下では、この考え方を具体的なルール作成の例を通じて説明します。
Falcoで検出可能なイベントの種類は大まかに次の三種類に分類できます。
それぞれについて、ホワイトリスト型カスタムルールを考えてみました。
例えば、80、443ポートを除くすべてのTCPのインバウンドコネクションを検出するカスタムルールは次のようなものになります。
- macro: inbound
condition: >
(((evt.type in (accept,listen) and evt.dir=<)) or
(fd.typechar = 4 or fd.typechar = 6) and
(fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and (evt.rawres >= 0 or evt.res = EINPROGRESS))
- list: expected_tcp_ports
items: [80, 443]
- macro: expected_tcp_traffic
condition: fd.port in (expected_tcp_ports)
- rule: Inbound TCP Traffic
condition: inbound and fd.l4proto=tcp and not expected_tcp_traffic
output: ...
priority: WARNING
tags: [catch_all, inbound, container]
macro: inboundでは、インバウンドのtcpコネクションに関する条件を定義しています。
macro: expected_tcp_trafficで除外条件を定義しています。除外条件を追加する場合には、ここに追加します。
rule: Inbound TCP Trafficがルール本体で、condition:
が検出条件、output:
には検出条件がマッチした場合の出力ログをテンプレート形式で定義します。 not expected_tcp_traffic
とあるように、マクロで定義した条件の頭にnot
をつけて、除外条件としていることがポイントです。
全てのコマンド実行、プロセス生成を検出するルールは次の様になります。
- macro: spawned_process
condition: (evt.type in (execve, execveat) and evt.dir=<)
- macro: proc_trusted
condition: (...)
- rule: Process Creation
condition: spawned_process and not proc_trusted
output: ...
priority: WARNING
tags: [catch_all, process]
macro: spawned_processはプロセス生成イベントをキャッチするためのマクロです。
macro: proc_trustedに信頼するプロセス、すなわち除外条件の定義を追加していきます。
rule: Process Creationがルール本体です。信頼するプロセス以外のすべてのプロセス生成イベントに対し、output:
に定義したログを出力します。 not proc_trusted
とあるように、マクロで定義した条件の頭にnot
をつけて、除外条件としていることがポイントです。
ファイル操作の内、書き込みモードでのファイルオープンを全て検知するルールは次の様になります。
- macro: open_write
condition: (evt.type in (open,openat,openat2) and evt.is_open_write=true and fd.typechar='f' and fd.num>=0)
- macro: file_write_exempt
condition: (...)
- rule: Unexpected File Write
condition: open_write and not file_write_exempt
output: ...
priority: WARNING
tags: [catch_all, file_write]
macro: open_write は、書き込みモードでのファイルオープンをキャッチするためのマクロです。
macro: file_write_exemptには、ログファイルへの書き込みなど、通常のアクティビティで想定される条件を追加していきます。
rule: Unexpected File Writeがルール本体です。condition:
にマッチしたイベントに対してoutput:
に定義された形式でログ出力することを設定しています。 not file_write_exempt
とあるように、マクロで定義した条件の頭にnot
をつけて、除外条件としていることがポイントです。
以上で、TCPのインバウンドコネクション、プロセス生成、書き込みモードのファイルオープンを検知するルールを例に、キャッチオール形式でルールを作成し、除外条件をホワイトリストとして追加していくやり方を説明しました。
実際には、TCPのアウトバウンドコネクション、UDPインバウンド・アウトバンド通信、ファイル操作に関しては、renameやdelete、symlink、hardlinkに関する条件などにも対応する必要があり、上に掲げた例の他にいくつかルールを作成する必要があります。 しかし、おおまかな考え方については上記で説明した通りになります。
Falcoでカスタムルールを作成し適用する方法を簡潔に説明します。
Falcoをhelmでインストールする場合には、helm chartのカスタマイズファイルにカスタムルールを記述することができます。
custom-rules.yaml
customRules:
custom-rules.yaml: |-
- rule: Example rule 1
desc: ...
...
- rule: Example rule 2
...
"customRules:"
と2行目の"custom-rules.yaml: |-"
が必要で、ルール自体のインデントも二段分深くする必要があるようです。(参考)
helm chartのカスタマイズファイルは複数読み込むことができますので、カスタムルールが長くなる場合には、それぞれ専用のカスタマイズファイルに書くと良いでしょう。 Falcoのカスタムルールをさらに別のcustom-rules2.yamlファイルから読み込むには、同様のフォーマットで記述します。
custom-rules2.yaml
customRules:
custom-rules2.yaml: |-
- rule: Example rule 3
desc: ...
...
- rule: Example rule 4
...
ここでの注意点としては、"custom-rules2.yaml: |-"
や"- rule: Example rule 3"
等の行が同じファイルの別部分や他のファイルと重複しないようにする必要があります。
デフォルトルールセットに加えて、カスタムルールを利用する場合には、次のコマンドラインでhelmを実行します。
helm install falco falcosecurity/falco --namespace falco --create-namespace \
-f myvalues.yaml -f custom-rules1.yaml -f custom-rules2.yaml
デフォルトルールセットを利用せずに、カスタムルールのみを利用したい場合には、myvalues.yamlファイルの中身を次の様にし、上記のコマンドでhelmを実行します。(参考)
falcoctl:
config:
artifact:
install:
enabled: false
follow:
enabled: false
falco: rules_file: - /etc/falco/falco_rules.local.yaml - /etc/falco/rules.d
Helmにより適用されたFalcoのルールはKubernetesのConfigMapに格納されます。この設定ファイルへの不正な改ざんを試みる行為は、Falcoによって検出可能です。攻撃者がFalcoのルールファイルを書き換えるためには、コンテナ内からKubernetesのAPIを介してConfigMapにアクセスする必要があります。このようなアクセスは、Falcoの監視ルールによって検知され警告されます。
それでは実際のホワイトリスト形式カスタムルールはどのように作成すれば良いでしょうか。ここではインバウンドのTCPコネクションのケースを例に手順を説明します。
Kubernetesのコントロールプレーンが使用するポートはこのページにまとまっています。これを元に初期バージョンのカスタムルールファイルを作成します。
検知不要なイベントに対するログが出なくなるまで、ステップ2と3を繰り返します。
カスタムルールの初期バージョンとして、Kubernetesのコントロールプレーンのトラフィックを除外するルールファイルを作成しました。
custom-rules-inboud-tcp0.yaml
customRules:
custom-rules-inboud-tcp0.yaml: |-
- macro: container
condition: (container.id != host)
- macro: inbound_tcp
condition: >
((evt.type in (accept,accept4,listen) and evt.dir=<) and
(fd.typechar = 4 or fd.typechar = 6) and
(fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and
(evt.rawres >= 0 or evt.res = EINPROGRESS))
- macro: k8s_kubelet
condition: >
proc.name = "kubelet" and (
(evt.type in (accept4) and fd.sport = 10250)
)
- macro: k8s_apiserver
condition: >
k8s.ns.name = "kube-system" and
k8s.pod.name startswith kube-apiserver- and (
(evt.type = accept4 and fd.sport = 6443)
)
- macro: k8s_etcd
condition: >
k8s.ns.name = "kube-system" and
k8s.pod.name startswith etcd- and (
(evt.type = accept4 and fd.sport = 2380)
)
- macro: expected_tcp_traffic
condition: (
k8s_kubelet or
k8s_apiserver or
k8s_etcd
)
- rule: TCP Inbound
desc: Detect unexpected Inbound TCP connection
enabled: true
condition: inbound_tcp and fd.l4proto=tcp and not expected_tcp_traffic
output: >
Unexpected Inbound TCP Traffic
(evt_type=%evt.type host=%evt.hostname
connection=%fd.name fd_proto=%fd.l4proto
cIP=%fd.cip sIP=%fd.sip cPort=%fd.cport sPort=%fd.sport
process=%proc.name command=%proc.cmdline proc_exepath=%proc.exepath parent=%proc.pname
user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid terminal=%proc.tty
exe_flags=%evt.arg.flags %container.info)
priority: WARNING
tags: [catch_all, tcp_inbound, network, ktaka]
以下のコマンドでFalcoをデプロイします。
helm install falco falcosecurity/falco --namespace falco --create-namespace \
-f myvalues.yaml -f custom-rules-inboud-tcp0.yaml
Falcoのデプロイ後しばらくすると、WebUIに大量のログが記録されます。全体を見通すために、json形式のログファイルをExportし、jqを駆使してどのようなログが記録されているのか解析します。
次の例は、一件の検出イベントに対するjsonログです。
ktaka@bulls:~/Downloads$ jq .[2] falco_events.json
{
"uuid": "0effbce1-6012-4e16-84ee-1f7ec2012a1a",
"output": "04:21:50.762917673: Warning Unexpected Inbound TCP Traffic (evt_type=accept4 host=v136 connection=10.244.5.1:45754->10.244.5.226:2801 fd_proto=tcp cIP=10.244.5.1 sIP=10.244.5.226 cPort=45754 sPort=2801 process=falcosidekick command=falcosidekick proc_exepath=/app/falcosidekick parent=containerd-shim user=falcosidekick user_uid=1234 user_loginuid=-1 terminal=0 exe_flags= container_id=d38b28bb9ab9 container_image=docker.io/falcosecurity/falcosidekick container_image_tag=2.28.0 container_name=falcosidekick k8s_ns=falco k8s_pod_name=falco-falcosidekick-749d77d5c7-22jph)",
"priority": "Warning",
"rule": "TCP Inbound",
"time": "2023-12-22T04:21:50.762917673Z",
"source": "syscall",
"output_fields": {
"container.id": "d38b28bb9ab9",
"container.image.repository": "docker.io/falcosecurity/falcosidekick",
"container.image.tag": "2.28.0",
"container.name": "falcosidekick",
"evt.arg.flags": null,
"evt.hostname": "v136",
"evt.time": 1703218910762917600,
"evt.type": "accept4",
"fd.cip": "10.244.5.1",
"fd.cport": 45754,
"fd.l4proto": "tcp",
"fd.name": "10.244.5.1:45754->10.244.5.226:2801",
"fd.sip": "10.244.5.226",
"fd.sport": 2801,
"k8s.ns.name": "falco",
"k8s.pod.name": "falco-falcosidekick-749d77d5c7-22jph",
"proc.cmdline": "falcosidekick",
"proc.exepath": "/app/falcosidekick",
"proc.name": "falcosidekick",
"proc.pname": "containerd-shim",
"proc.tty": 0,
"user.loginuid": -1,
"user.name": "falcosidekick",
"user.uid": 1234
},
"hostname": "v136",
"tags": [
"catch_all",
"ktaka",
"network",
"tcp_inbound"
]
}
今回の例ではこのようなログが、1分25秒間の間に8308件と大量に記録されていました。
$ jq .[].time falco_events.json |wc -l
8308
$ jq .[0].time falco_events.json
"2023-12-22T04:21:51.067533099Z"
$ jq .[8307].time falco_events.json
"2023-12-22T04:20:26.389131383Z"
このログを、jqを使って見やすい形式に整形します。 今回の例では、元のログファイルから必要そうなフィールドのみを拾い出し、重複行を一行にまとめることで見やすい形になりました。
$ jq '.[].output_fields' falco_events.json|jq -r '[."fd.sport", ."evt.type", ."k8s.pod.name"]|@csv' | sort -u -t, -k 1
2801,"accept4","falco-falcosidekick-749d77d5c7-22jph"
2801,"accept4","falco-falcosidekick-749d77d5c7-9g2lq"
2802,"accept4","falco-falcosidekick-ui-6885c6cffd-dslgc"
2802,"accept4","falco-falcosidekick-ui-6885c6cffd-hsb4j"
6379,"accept","falco-falcosidekick-ui-redis-0"
8080,"accept4","coredns-5dd5756b68-48lkl"
8080,"accept4","coredns-5dd5756b68-759kd"
8181,"accept4","coredns-5dd5756b68-48lkl"
8181,"accept4","coredns-5dd5756b68-759kd"
8765,"accept","falco-6tcd5"
8765,"accept","falco-ch66w"
8765,"accept","falco-dbcf4"
8765,"accept","falco-dqphh"
8765,"accept","falco-wpt8n"
8765,"accept","falco-wt662"
この内容をみると、どうやらログ出力の原因となった通信は、falco関連のプロセスと、corednsに起因するもので、いずれも無害なものと判断して良さそうです。
そこで、この結果を元に以下のようなマクロを作成しました。 これらをホワイトリストとして除外条件に加ることで、1分25秒間の間に8308件出力されていたfalcoの関連プロセス間通信によるの大量のログは全く出力されなくなります。
- macro: k8s_coredns
condition: >
k8s.ns.name = "kube-system" and
k8s.pod.name startswith coredns- and (
evt.type = accept4 and fd.sport in (8080,8181)
)
- macro: falco
condition: >
k8s.ns.name = "falco" and
k8s.pod.name startswith falco- and (
(evt.type = accept and fd.sport = 8765)
)
- macro: falco_falcosidekick
condition: >
k8s.ns.name = "falco" and
k8s.pod.name startswith falco-falcosidekick- and (
(evt.type = accept4 and fd.sport = 2801)
)
- macro: falco_falcosidekick_ui
condition: >
k8s.ns.name = "falco" and
k8s.pod.name startswith falco-falcosidekick-ui- and (
(evt.type = accept4 and fd.sport = 2802)
)
- macro: falco_redis
condition: >
k8s.ns.name = "falco" and
k8s.pod.name startswith falco-falcosidekick-ui-redis- and (
(evt.type = accept and fd.sport = 6379)
)
また、しばらく運用していると違う条件で通信イベントのログが出力されますが、同じ要領で順次除外条件をホワイトリストに加えていきます。
最終的なカスタムルールファイルは次の様になりました。 このルールファイルでFalcoを起動し直すと、無害な通信に起因するログが出力されなくなりました。
custom-rules-inboud-tcp1.yaml
customRules:
custom-rules-inboud-tcp0.yaml: |-
- macro: container
condition: (container.id != host)
- macro: inbound_tcp
condition: >
((evt.type in (accept,accept4,listen) and evt.dir=<) and
(fd.typechar = 4 or fd.typechar = 6) and
(fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and
(evt.rawres >= 0 or evt.res = EINPROGRESS))
- macro: k8s_kubelet
condition: >
proc.name = "kubelet" and (
(evt.type in (accept4) and fd.sport = 10250)
)
- macro: k8s_apiserver
condition: >
k8s.ns.name = "kube-system" and
k8s.pod.name startswith kube-apiserver- and (
(evt.type = accept4 and fd.sport = 6443)
)
- macro: k8s_etcd
condition: >
k8s.ns.name = "kube-system" and
k8s.pod.name startswith etcd- and (
(evt.type = accept4 and fd.sport = 2380)
)
- macro: k8s_coredns
condition: >
k8s.ns.name = "kube-system" and
k8s.pod.name startswith coredns- and (
evt.type = accept4 and fd.sport in (8080,8181)
)
- macro: falco
condition: >
k8s.ns.name = "falco" and
k8s.pod.name startswith falco- and (
(evt.type = accept and fd.sport = 8765)
)
- macro: falco_falcosidekick
condition: >
k8s.ns.name = "falco" and
k8s.pod.name startswith falco-falcosidekick- and (
(evt.type = accept4 and fd.sport = 2801)
)
- macro: falco_falcosidekick_ui
condition: >
k8s.ns.name = "falco" and
k8s.pod.name startswith falco-falcosidekick-ui- and (
(evt.type = accept4 and fd.sport = 2802)
)
- macro: falco_redis
condition: >
k8s.ns.name = "falco" and
k8s.pod.name startswith falco-falcosidekick-ui-redis- and (
(evt.type = accept and fd.sport = 6379)
)
- macro: expected_tcp_traffic
condition: (
k8s_kubelet or
k8s_apiserver or
k8s_etcd or
k8s_coredns or
falco or
falco_falcosidekick or
falco_falcosidekick_ui or
falco_redis
)
- rule: TCP Inbound
desc: Detect unexpected Inbound TCP connection
enabled: true
condition: inbound_tcp and fd.l4proto=tcp and not expected_tcp_traffic
output: >
Unexpected Inbound TCP Traffic
(evt_type=%evt.type host=%evt.hostname
connection=%fd.name fd_proto=%fd.l4proto
cIP=%fd.cip sIP=%fd.sip cPort=%fd.cport sPort=%fd.sport
process=%proc.name command=%proc.cmdline proc_exepath=%proc.exepath parent=%proc.pname
user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid terminal=%proc.tty
exe_flags=%evt.arg.flags %container.info)
priority: WARNING
tags: [catch_all, tcp_inbound, network, ktaka]
前節でTCPのインバウンド通信を例に、ホワイトリスト形式のカスタムルールの作成方法について説明しました。
その他に、作成すべきカスタムルールとしては、以下のようなものが考えられます。
1.のネットワーク通信の残りについては、前節と同じようにホワイトリストを作成していけば問題ありません。 2.、3.に関しては、基本的に同じ手順でホワイトリストの作成ができますが、ルールの記述方法に関して、難しさがあることがわかりました。
プロセス生成のホワイトリストに関しては、ルールの記述が比較的容易なものと、どう書くべきか難しい部分が存在しています。
簡単な部分は、kubernetesのコントロールプレーンなど、目的がはっきりしているプロセス達です。以下は、大量に出力されるログから作成した、除外条件マクロの一部です。
- macro: proc_kube_proxy
condition: k8s.ns.name = kube-system and k8s.pod.name startswith kube-proxy- and (
proc.exepath = /usr/sbin/xtables-nft-multi
)
ログファイルの中に/usr/sbin/xtables-nft-multi
の起動に関するログが大量にありました。その中でkube-proxy-xxxx
という名前のポッド内で起動されるものであれば、問題ないであろうと判断し、このマクロを作成しました。 kube-proxy-xxxx
はリナックスカーネルのiptablesを操作してproxyを実現しており、/usr/sbin/xtables-nft-multi
はiptablesコマンドのバイナリ実体であるからです。
- macro: proc_falco
condition: k8s.ns.name = falco and k8s.pod.name startswith falco-falcosidekick-ui- and (
proc.exepath = /app/falcosidekick-ui
)
ログファイルの中に/app/falcosidekick-ui
の実行に関するログが大量にありました。これもfalco-falcosidekick-ui-xxxx
という名前のポッド内で起動されていたので、問題ないと判断し、このマクロを作成しました。
- macro: proc_kubelet
condition: container.id = host and proc.pname = kubelet and (
proc.exepath = /sbin/xtables-nft-multi
or proc.exepath = /usr/sbin/xtables-nft-multi
or proc.exepath = /usr/bin/mount
or proc.exepath = /usr/bin/umount
)
ログファイルの中には、他にもxtables-nft-multi
、mount
、umount
に関するものがありました。その中でも、proc.pname = kubelet
すなわち親プロセスがkubelet
であるものは問題ないであろうと判断し、このマクロを作成しました。
一方で、プロセス起動に関するログとして、cp
、mv
、rm
、cmp
、dirname
、tar
、date
、apt-compat
、dpkg
など、それだけでは一見、問題があるかどうか判断がつかないものも大量に存在していました。
その多くは、ホストのOS上で、cronやsystemdにより定期的に実行されるスクリプトの中から呼び出されるものでした。
一例としてcronジョブに関係していそうなログに対して、それを除外するためのマクロを以下に示します。
- macro: proc_cron
condition: container.id = host and (
(proc.name in (apt-compat, aptitude, dpkg, logrotate, sysstat, debian-sa1) and (proc.aname[2] = cron or proc.aname[3] = cron or proc.aname[4] = cron))
or (proc.name = sh and proc.pname = cron)
or proc.exepath = /usr/bin/run-parts
or proc.exepath = /usr/bin/cmp
or proc.exepath = /usr/bin/basename
or proc.exepath = /usr/bin/dirname
or (proc.exepath = /usr/bin/cp and proc.cmdline = "cp -p /var/lib/aptitude/pkgstates aptitude.pkgstates")
or (proc.exepath = /usr/bin/dash and proc.cmdline = "savelog /bin/savelog -c 7 aptitude.pkgstates")
or (proc.exepath = /usr/bin/rm and proc.cmdline = "rm -f -- .//aptitude.pkgstates.6 .//aptitude.pkgstates.6.gz")
or (proc.exepath = /usr/bin/gzip and proc.cmdline = "gzip -f -9 .//aptitude.pkgstates.0")
)
このマクロは、以下の条件にマッチするものです。
(apt-compat, aptitude, dpkg, logrotate, sysstat, debian-sa1)
については、祖先のいずれかがcronであるものsh
は、親プロセスがcron
であるものcp
, dash
, rm
, gzip
については、コマンドラインが完全一致するものそれぞれのログに対してアドホックに条件を書いているので指定の仕方が統一されていないことなどから、今後も検討、改善を続けることが必要だと考えています。
ファイル操作のホワイトリストに関しては、実際にやってみると、稼働中のシステムではたいへん多くのファイル読み書きのイベントが発生していることがわかり、それを整理してホワイトリストを作成するのは大変でした。
ファイル書き込み操作に関するホワイトリストマクロの一部を以下に紹介します。
- macro: file_trusted_process
condition: (
proc.exepath = /usr/sbin/runc
or proc.exepath = /usr/bin/kubelet
or proc.exepath = /usr/bin/apt-config
or proc.exepath = /usr/bin/apt-get
or proc.exepath = /usr/lib/apt/apt-helper
or proc.exepath = /usr/bin/dpkg
or proc.exepath = /usr/local/bin/containerd
or proc.exepath = /usr/local/bin/containerd-shim-runc-v2
or proc.exepath = /usr/bin/systemctl
or proc.exepath = /usr/bin/systemd-tmpfiles
or proc.exepath = /usr/lib/systemd/systemd
or proc.exepath = /usr/lib/systemd/systemd-journald
or proc.exepath = /usr/sbin/logrotate
or proc.exepath = /usr/lib/sysstat/sadc
or proc.exepath = /usr/bin/kmod
or proc.exepath = /usr/sbin/xtables-nft-multi
or proc.exepath = /usr/bin/dbus-daemon
or proc.exepath = /usr/bin/udevadm
or proc.exepath = /usr/bin/umount
or proc.exepath = /opt/cni/bin/bridge
or proc.exepath = /opt/cni/bin/flannel
or proc.exepath = /opt/cni/bin/host-local
or proc.exepath = /opt/cni/bin/loopback
or proc.exepath = /usr/bin/cmp
or proc.exepath = /usr/bin/sar.sysstat
or (k8s.ns.name = kube-system and (
(k8s.pod.name startswith etcd- and proc.exepath = /usr/local/bin/etcd)
or (k8s.pod.name startswith kube-apiserver- and proc.exepath = /usr/local/bin/kube-apiserver)
or (k8s.pod.name startswith kube-proxy- and proc.exepath = /usr/local/bin/kube-proxy)
or (k8s.pod.name startswith kube-proxy- and proc.exepath = /usr/sbin/xtables-nft-multi)
or (k8s.pod.name startswith coredns- and proc.exepath = /coredns)
))
or (k8s.ns.name = falco and (
(k8s.pod.name startswith falco- and proc.exepath = /usr/bin/falco)
or (k8s.pod.name startswith falco- and proc.exepath = /usr/bin/falcoctl)
or (k8s.pod.name startswith falco-falcosidekick- and proc.exepath = /app/falcosidekick)
or (k8s.pod.name startswith falco-falcosidekick-ui- and proc.exepath = /app/falcosidekick-ui)
or (k8s.pod.name startswith falco-falcosidekick-ui-redis- and proc.exepath = /opt/redis-stack/bin/redis-server)
))
or (k8s.ns.name = kube-flannel and (
(k8s.pod.name startswith kube-flannel and proc.exepath = /opt/bin/flanneld)
or (k8s.pod.name startswith kube-flannel and proc.exepath = /sbin/xtables-nft-multi)
))
)
- macro: file_write
condition: (
(container.id = host and (
fd.name in (/dev/null)
or (proc.exepath = /usr/bin/dash and fd.name in (/run/motd.dynamic.new, /var/lib/apt/daily_lock))
or (proc.exepath = /usr/bin/bash and fd.name in (/dev/tty, /root/.bash_history))
or (proc.exepath = /usr/bin/bash and proc.name = sa2 and fd.name pmatch (/var/log/sysstat/))
or (proc.exepath = /usr/bin/bash and proc.name = sa2 and proc.pname = systemd and fd.name = /usr/lib/sysstat/sa2 and proc.cmdline = "sa2 /usr/lib/sysstat/sa2 -A")
or (proc.exepath = /usr/sbin/sshd and fd.name in (/dev/null, /dev/ptmx, /dev/pts/0, /dev/tty, /proc/self/oom_score_adj, /var/log/lastlog, /var/log/wtmp, /var/run/utmp))
or (proc.exepath = /usr/bin/cp and proc.cmdline = "cp -p /var/lib/aptitude/pkgstates aptitude.pkgstates" and fd.name = "/var/backups/aptitude.pkgstates")
or (proc.exepath = /usr/bin/gzip and proc.cmdline = "gzip -f -9 .//aptitude.pkgstates.0" and fd.name = "/var/backups/aptitude.pkgstates.0.gz")
or (proc.cmdline = "6 init" and proc.name = 6 and proc.pname = runc and fd.name startswith /proc/self/oom_score_adj)
))
or (k8s.ns.name = falco and (
(k8s.pod.name startswith falco-falcosidekick-ui-redis- and proc.exepath = /opt/redis-stack/nodejs/bin/node and fd.name startswith /redisinsight/content/)
))
)
- macro: file_write_exempt
condition: (
file_write
or file_trusted_process
or user_apps_file_write
)
このマクロのポイントは次の通りです。
汎用的なユーティリティプログラムによるファイル書き込みに関して、安全なものを識別するためにどのような条件が適切なのか、ホワイトリスト全体としての管理のし易さなどに関して、今後の改善が必要であると考えております。
FalcoはLinux OSに特化したランタイムセキュリティツールで、 システム上で発生する異常なアクティビティや潜在的なセキュリティリスクをリアルタイムで検知し、警告します。 前回の記事では、Falcoプロジェクトで用意されているデフォルトルールセットだけでは、未知の脅威に対して必ずしも万全ではないということを指摘しました。 この課題に対処するため、本記事ではホワイトリスト型のカスタムルールを用いるアプローチを探求しました。
ホワイトリスト型カスタムルールの導入により、ネットワーク通信、プロセス生成、ファイル操作など、各種イベントに対して全インシデントを検知可能なルールセットを構築することができます。 Falcoの運用中に記録される大量のログデータを分析することで、これらのルールを継続的に調整し、検知の除外条件を追加していくことが可能です。
実際にやってみると、ネットワーク通信に関しては、比較的容易にルールファイルの作成が可能であることがわかりました。 一方で、プロセス生成やファイル操作に関するルール作成に関しては、ルールの精度を高め、誤検知を減らすためにルールの書き方の細部について改善検討を続けていく必要があることがわかりました。
Falcoのホワイトリスト型カスタムルールは、未知の脅威に対しても高いレベルの保護を提供する強力なツールです。継続的な監視、分析、およびルールの微調整を通じて、Falcoはクラウド上のWebアプリケーションのセキュリティ強化に重要な役割を果たし続けるでしょう。
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。
合わせて読みたい
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。