Upgrade to Pro — share decks privately, control downloads, hide ads and more …

コードで理解する eBPF セキュリティモニタリング

mrtc0
February 17, 2024

コードで理解する eBPF セキュリティモニタリング

mrtc0

February 17, 2024
Tweet

More Decks by mrtc0

Other Decks in Technology

Transcript

  1. 森田 浩平 / Kohei Morita 2018年にGMOペパボ株式会社に新卒入社, 2022年より株式会社グラファーにてプロダクトセキュリティに従事。 OWASP Fukuoka Chapter

    Leader,セキュリティ・キャンプ講師, 著書に「基礎から学ぶコンテナセキュリティ」など。 出身は愛媛県松山市、現在は福岡で妻+猫1匹と生活中
  2. コードで理解する eBPF セキュリティモニタリング 題材として “curl のときに example.com への通信をブロックする” 小さいプログラムを作る 。

    1. 通信を発生させる関数に attach してプロセスコンテキストを取得・表示する 2. eBPF Map に設定を書き込んでユーザーランドで表示結果をフィルタする 3. curl のとき example.com への接続の場合に通信をブロックする コードは https://github.com/mrtc0/ebpf-demo にあります。
  3. アタッチする関数の決定 $ 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
  4. #include "vmlinux.h" #include <asm/unistd.h> #include <bpf/bpf_core_read.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h>

    #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
  5. #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
  6. 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
  7. 実行結果 $ 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
  8. 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
  9. 実行結果 $ 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/
  10. bpf_override_return() と bpf_send_signal() • 関数の戻り値を変更し、プロセスにシグナルを送る ◦ 戻り値を変更できるのは一部の関数のみ • 悪意あるプロセスがシグナルをハンドリングしている場合、シグナルを送っても無効化されるので、 bpf_override_return()

    と組み合わせるべし BPF LSM を使う • 素朴に BPF プログラム内で return -EPERM とかすれば良い • Ubuntu 22.04 LTS では、起動時のパタメータを変更する必要があって、シュッとは使えない
  11. bpf_override_return() が使える条件 • Tracepoint では使えないので Kprobe を使う • また、Kprobe の中でも許可されている関数しか使えない

    ◦ cat /proc/kallsyms | grep _eil_addr ◦ 対象のほとんどがシステムコール • 今は x86 でしか使えないはず (man 7 bpf-helpers 調べ) ◦ ( ちなみに LSM BPF も ARM64 は still in development だったと思う...
  12. アタッチする場所を変更じゃ 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 では使えない...
  13. 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
  14. 実行結果 $ 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ʼ
  15. eBPF でセキュリティモニタリングがどう変わる? • 従来までプロセスのコンテキストを深く追うことは Kernel Module を作るしかなかった ◦ それが非常に簡単に、かつ、安全に実現できるようになった •

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