Slide 1

Slide 1 text

コードで理解する eBPF セキュリティモニタリング 2023/02/15 eBPF & コンテナ情報交換会 @ 福岡

Slide 2

Slide 2 text

森田 浩平 / Kohei Morita 2018年にGMOペパボ株式会社に新卒入社, 2022年より株式会社グラファーにてプロダクトセキュリティに従事。 OWASP Fukuoka Chapter Leader,セキュリティ・キャンプ講師, 著書に「基礎から学ぶコンテナセキュリティ」など。 出身は愛媛県松山市、現在は福岡で妻+猫1匹と生活中

Slide 3

Slide 3 text

https://gihyo.jp/book/2023/978-4-297-13635-2

Slide 4

Slide 4 text

モニタリング用途で使用される。現在はこちらが多めな印象。 より強固にしたい場面で。ただし、実装としては少ない印象。

Slide 5

Slide 5 text

何を監視するのか ● 次のようなイベントをトレースし、普段と異なる挙動を検出できる。プロセスのコンテキスト情報を手 軽に深く安全に取得できるのが eBPF の特徴 ● システムコールの呼び出し ● ネットワークトラフィック ● ファイルアクセス

Slide 6

Slide 6 text

コードで理解する eBPF セキュリティモニタリング 題材として “curl のときに example.com への通信をブロックする” 小さいプログラムを作る 。 1. 通信を発生させる関数に attach してプロセスコンテキストを取得・表示する 2. eBPF Map に設定を書き込んでユーザーランドで表示結果をフィルタする 3. curl のとき example.com への接続の場合に通信をブロックする コードは https://github.com/mrtc0/ebpf-demo にあります。

Slide 7

Slide 7 text

アタッチする関数の決定 $ dig +short example.com 93.184.216.34 $ sudo strace curl https://example.com |& grep 93.184 connect(5, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_addr("93.184.216.34")}, 16) = -1 EINPROGRESS (Operation now in progress) getpeername(5, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_addr("93.184.216.34")}, [128 => 16]) = 0

Slide 8

Slide 8 text

connect システムコールを確認する

Slide 9

Slide 9 text

#include "vmlinux.h" #include #include #include #include #define AF_INET 2 #define TASK_COMM_LEN 16 char LICENSE[] SEC("license") = "Dual BSD/GPL"; SEC("kprobe/security_socket_connect") int handle_security_socket_connect(struct pt_regs *ctx) { struct event evt; /* int security_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen); PT_REGS_PARM2 は第二引数の address を取得するマクロ */ struct sockaddr *address = (struct sockaddr *)PT_REGS_PARM2(ctx); sa_family_t fam; bpf_core_read(&fam, sizeof(fam), &address->sa_family); if (fam != AF_INET) { return 0; } } https://github.com/mrtc0/ebpf-demo/blob/master/bpf/trace_connect.c

Slide 10

Slide 10 text

#define EVENTS_RING_SIZE (4*4096) struct event { struct in_addr dst; u8 comm[TASK_COMM_LEN]; }; // events をユーザーランドと共有するための Map struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, EVENTS_RING_SIZE); } events SEC(".maps"); https://github.com/mrtc0/ebpf-demo/blob/master/bpf/trace_connect.c

Slide 11

Slide 11 text

SEC("kprobe/security_socket_connect") int handle_security_socket_connect(struct pt_regs *ctx) { struct event evt; struct sockaddr *address = (struct sockaddr *)PT_REGS_PARM2(ctx); sa_family_t fam; __builtin_memset(&evt, 0, sizeof(evt)); bpf_core_read(&fam, sizeof(fam), &address->sa_family); if (fam != AF_INET) { return 0; } // events Map に書き込む struct sockaddr_in *addr = (struct sockaddr_in *)address; bpf_get_current_comm(&evt.comm, sizeof(evt.comm)); evt.dst = BPF_CORE_READ(addr, sin_addr); bpf_ringbuf_output(&events, &evt, sizeof(evt), 0); return 0; } https://github.com/mrtc0/ebpf-demo/blob/master/bpf/trace_connect.c

Slide 12

Slide 12 text

実行結果 $ sudo ./bbarrier tracer 2024/02/15 11:28:31 waiting for events... 2024/02/15 11:28:37 event: comm=curl addr=889192575 # 127.0.0.53 2024/02/15 11:28:37 event: comm=curl addr=584628317 # 93.184.216.34 2024/02/15 11:28:37 event: comm=curl addr=584628317 # 93.184.216.34 $ curl https://example.com/ $ dig +short example.com 93.184.216.34

Slide 13

Slide 13 text

IP アドレスが 93.184.216.34 のときだけ検知したい ● ユーザーランドでフィルタしても良いが今回は eBPF プログラム内で行ってみる

Slide 14

Slide 14 text

BPF_MAP_TYPE_LPM_TRIE

Slide 15

Slide 15 text

struct { __uint(type, BPF_MAP_TYPE_LPM_TRIE); __uint(max_entries, 256); __type(key, struct ipv4_lpm_key); __type(value, __u32); __uint(map_flags, BPF_F_NO_PREALLOC); } denied_ipaddr_map SEC(".maps"); int handle_security_socket_connect(struct pt_regs *ctx) { ... struct ipv4_lpm_key key = { .prefixlen = 32, .data = evt.dst.s_addr }; if (bpf_map_lookup_elem(&denied_ipaddr_map, &key)) { bpf_ringbuf_output(&events, &evt, sizeof(evt), 0); } ... } err = objs.DeniedIPAddrMap.Put(&socketConnectIpv4LpmKey{ Prefixlen: 32, Data: network.IPToInt(exampleComIPAddr), }, uint32(0)) https://github.com/mrtc0/ebpf-demo/blob/master/bpf/trace_connect.c https://github.com/mrtc0/ebpf-demo/blob/master/pkg/tracer/socket_connect.go

Slide 16

Slide 16 text

実行結果 $ r$ sudo ./bbarrier tracer 2024/02/15 13:57:58 waiting for events... 2024/02/15 13:58:02 event: comm=curl addr=[93 184 216 34] 2024/02/15 13:58:02 event: comm=curl addr=[93 184 216 34] $ curl https://example.com/ $ curl https://github.com/

Slide 17

Slide 17 text

Fault Injection (Error Injection) A. bpf_override_return() と bpf_send_signal() を使う ○ e.g. Tetragon B. LSM BPF を使う ○ e.g. Teleport

Slide 18

Slide 18 text

bpf_override_return() と bpf_send_signal() ● 関数の戻り値を変更し、プロセスにシグナルを送る ○ 戻り値を変更できるのは一部の関数のみ ● 悪意あるプロセスがシグナルをハンドリングしている場合、シグナルを送っても無効化されるので、 bpf_override_return() と組み合わせるべし BPF LSM を使う ● 素朴に BPF プログラム内で return -EPERM とかすれば良い ● Ubuntu 22.04 LTS では、起動時のパタメータを変更する必要があって、シュッとは使えない

Slide 19

Slide 19 text

bpf_override_return() が使える条件 ● Tracepoint では使えないので Kprobe を使う ● また、Kprobe の中でも許可されている関数しか使えない ○ cat /proc/kallsyms | grep _eil_addr ○ 対象のほとんどがシステムコール ● 今は x86 でしか使えないはず (man 7 bpf-helpers 調べ) ○ ( ちなみに LSM BPF も ARM64 は still in development だったと思う...

Slide 20

Slide 20 text

アタッチする場所を変更じゃ mrtc0@sandbox:~$ cat /proc/kallsyms | grep _eil_addr | grep security_socket_connect mrtc0@sandbox:~$ cat /proc/kallsyms | grep _eil_addr | grep connect 0000000000000000 d _eil_addr___ia32_sys_connect 0000000000000000 d _eil_addr___x64_sys_connect security_socket_connect では使えない...

Slide 21

Slide 21 text

SEC("kprobe/sys_connect") int kprobe__sys_connect(struct pt_regs *ctx) { ... struct denied_command comm; bpf_get_current_comm(&comm.comm, sizeof(evt.comm)); if (bpf_map_lookup_elem(&denied_ipaddr_map, &key) && bpf_map_lookup_elem(&denied_command_map, &comm)) { bpf_ringbuf_output(&events, &evt, sizeof(evt), 0); bpf_send_signal(9); bpf_override_return(ctx, -1); } return 0; } https://github.com/mrtc0/ebpf-demo/blob/master/bpf/enforce_connect.c

Slide 22

Slide 22 text

実行結果 $ r$ sudo ./bbarrier enforce 2024/02/15 13:57:58 waiting for events... 2024/02/15 13:58:02 event: comm=curl addr=[93 184 216 34] mrtc0@sandbox:~$ curl -sI https://example.com Killed mrtc0@sandbox:~$ curl -sI https://github.com HTTP/2 200 server: GitHub.com mrtc0@sandbox:~$ wget https://example.com --2024-02-17 06:00:06-- https://example.com/ Resolving example.com (example.com)... 93.184.216.34, 2606:2800:220:1:248:1893:25c8:1946 Connecting to example.com (example.com)|93.184.216.34|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 1256 (1.2K) [text/html] Saving to: ʻindex.htmlʼ

Slide 23

Slide 23 text

eBPF でセキュリティモニタリングがどう変わる? ● 従来までプロセスのコンテキストを深く追うことは Kernel Module を作るしかなかった ○ それが非常に簡単に、かつ、安全に実現できるようになった ● 従来の技術でも工夫次第で十分にモニタリングはできるが、コンテナ環境では限界がある ○ すでに多くの企業が eBPF を使ったモニタリングを実装・運用している ● 主要なディストリビューションで「カーネルのバージョンが新しい」「デフォルトで eBPF に関する Kernel Config が有効になっている」状態になると、もっと広がる ● プロセスのコンテキスト情報を蓄積・分析する基盤があれば、「本当に疑わしい挙動」のみにフォーカ スできるようになる (誤検知が減る) ○ フォレンジックの分野でも状況証拠が多く手に入る