Verbessern des Datenverständnisses
Rocco Gagliardi
Container mit Syscalls-Filters unter Kontrolle
Während unserer Projekte werden wir mit verschiedenen Aspekten der Sicherheit konfrontiert. Im Grossen und Ganzen lässt sich das von uns verwendete Modell wie folgt zusammenfassen:
In jedem Bereich analysieren wir den Stand der Sicherheit und schlagen Verbesserungen vor.
Speziell im Hinblick auf containerisierte Anwendungen haben wir uns angesehen, wie man die Auslieferung des Containers sichert und wie man einen speziellen Fall entschärft, in dem der auszuführende Code nicht vertrauenswürdig ist, indem man eine Technik zwischen Containerisierung und Virtualisierung verwendet.
In diesem Artikel befassen wir uns speziell mit den Sicherheitsrichtlinien, die auf Kernel-Ebene einer Anwendung auferlegt werden können, die in einer Cloud-Umgebung mit seccomp-bpf
ausgeführt wird.
Wenn Sie Erfahrung als Programmierer haben, haben Sie in den letzten Jahren zwanghaft eine einzige goldene Regel wiederholt: “Don’t Repeat Yourself!”, oder DRY. In der Welt der Sicherheit ist DRY zugunsten von !DRY verbannt. Der Einsatz von mehreren Schichten redundanter Sicherheitsmassnahmen ist ein gängiges Muster und Teil einer grösseren Strategie namens Defense in Depth. Das kann mitunter kompliziert sein, macht aber Sinn: Wenden Sie redundante Kontrollen an verschiedenen Stellen eines Systems an.
Wir möchten noch einmal betonen, dass Container nicht dazu gedacht sind, etwas zu enthalten! Alle Container teilen sich denselben (Host-)Kernel. Wenn der Kernel eine Schwachstelle aufweist, könnten alle Container diese Schwachstelle ausnutzen und möglicherweise auf nicht autorisierte Daten auf dem Host und praktisch in einem anderen Container zugreifen.
Grundsätzlich ist ein Kernel für die Ausführung von Low-Level-Operationen verantwortlich, die von High-Level-Programmen aufgerufen werden, er verwaltet also den privilegierten Zugriff für unprivilegierte Benutzer. Diese Operationen sind über Systemaufrufe (syscalls) zugänglich. In einem modernen System gibt es ca. 400 verschiedene Syscalls: Funktionalitäten für einen Programmierer, Angriffsvektoren für einen Angreifer.
Die Idee ist einfach: Wenn wir nicht benötigte Syscalls blockieren könnten, kann der Container/die Anwendung die Schwachstelle nicht ausnutzen, selbst wenn der Code der Syscalls angreifbar ist.
Dies ist der seccomp (SECure COMPuting) Modus, der 2005 mit Kernel 2.6.12 eingeführt wurde. Mit der Einführung von seccomp-bpf
im Jahr 2012 mit Kernel 3.5, einem menschlichen Mechanismus zur Erstellung von Syscall-Filtern, war es möglich, andere Syscalls als den ursprünglichen magic-four zu verwalten: exit()
, sigreturn()
, read()
, write()
.
Im letzten Artikel haben wir eine Methode gesehen, um Syscalls auf einfache Weise zu begrenzen. Sandboxing des Containers anstelle des Laufzeit-Kernels. Dieser Ansatz adressiert eine spezifische Bedrohung (die Ausführung von nicht vertrauenswürdigem Code), indem der Host-Kernel durch einen dedizierten Kernel ersetzt wird. Die verringerte Anzahl von Syscalls ist ein Nebeneffekt der Implementierung. Im neuen Kernel sind nur 260 Syscalls implementiert, so dass die Gefährdung um etwa ein Drittel reduziert ist.
Für einen allgemeinen Anwendungsfall, ohne Swapping oder Virtualisierung des Kernels, wollen wir prüfen, auf welche privilegierten Funktionen des Hosts ein unprivilegierter Container zugreifen kann. Die vom Kernel zur Verfügung gestellten privilegierten Funktionen werden über Syscalls bezogen und wir wollen einem Container nur die unbedingt notwendigen Syscalls zur Verfügung stellen.
Die Herausforderung besteht darin, herauszufinden, welche Syscalls für die Ausführung eines Containers notwendig sind. Und das ist keine triviale Aufgabe.
Die für die Ausführung einer Software erforderlichen Systemaufrufe hängen von vielen Faktoren ab, darunter sind CPU-Architektur, Programmiersprache, Compiler die wichtigsten. Nehmen wir das Beispiel von Hello World; wir haben das gleiche Programm in C, Go und Python:
bash$ cat hello.c #include <stdio.h> int main() { printf("Hello World\n"); return 0; } bash$ cat hello.go package main import "fmt" func main() { fmt.Println("Hello World") } bash$ cat hello.py #!/usr/bin/env python3 print("Hello World")
Ausgehend von der Aufgabe könnten wir ähnliche Ergebnisse erwarten, da die gleiche Funktionalität vom Kernel angefordert wird (print string “Hello World”), aber wenn wir uns die von jedem Programm verwendeten Syscalls ansehen, stellen wir einige Unterschiede fest:
bash$ strace -c ./run.hello.c Hello World % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 41.37 0.000163 40 4 mprotect 17.51 0.000069 69 1 write 17.26 0.000068 8 8 mmap 8.38 0.000033 33 1 munmap 4.82 0.000019 6 3 newfstatat 2.79 0.000011 3 3 brk 2.28 0.000009 9 1 getrandom 1.78 0.000007 7 1 prlimit64 1.27 0.000005 2 2 close 1.02 0.000004 4 1 set_tid_address 0.76 0.000003 1 2 1 arch_prctl 0.76 0.000003 3 1 set_robust_list 0.00 0.000000 0 1 read 0.00 0.000000 0 4 pread64 0.00 0.000000 0 1 1 access 0.00 0.000000 0 1 execve 0.00 0.000000 0 2 openat ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000394 10 37 2 total bash$ strace -c ./run.hello.go Hello World % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ------------------ 81.19 0.003276 28 113 rt_sigaction 11.15 0.000450 150 3 clone 2.16 0.000087 10 8 rt_sigprocmask 2.16 0.000087 29 3 fcntl 2.16 0.000087 29 3 1 futex 1.09 0.000044 44 1 write 0.10 0.000004 4 1 rt_sigreturn 0.00 0.000000 0 1 read 0.00 0.000000 0 1 close 0.00 0.000000 0 18 mmap 0.00 0.000000 0 1 execve 0.00 0.000000 0 2 sigaltstack 0.00 0.000000 0 1 arch_prctl 0.00 0.000000 0 1 gettid 0.00 0.000000 0 1 sched_getaffinity 0.00 0.000000 0 1 openat ------ ----------- ----------- --------- --------- ------------------ 100.00 0.004035 25 159 1 total bash$ strace -c python3 hello.py Hello World % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 29.96 0.009716 41 232 31 newfstatat 9.33 0.003025 45 66 rt_sigaction 8.22 0.002665 37 71 read 8.21 0.002661 38 69 3 lseek 7.90 0.002562 111 23 mmap 7.23 0.002345 45 52 3 openat 6.86 0.002224 42 52 close 5.60 0.001815 42 43 36 ioctl 3.66 0.001188 198 6 mprotect 2.98 0.000967 48 20 getdents64 2.51 0.000814 90 9 brk 1.60 0.000520 260 2 1 arch_prctl 0.90 0.000292 146 2 munmap 0.89 0.000290 72 4 3 readlink 0.82 0.000267 133 2 getrandom 0.81 0.000264 264 1 set_tid_address 0.59 0.000190 95 2 getcwd 0.56 0.000182 182 1 prlimit64 0.49 0.000160 160 1 set_robust_list 0.47 0.000154 51 3 dup 0.14 0.000047 47 1 fcntl 0.12 0.000039 39 1 write 0.08 0.000026 26 1 futex 0.04 0.000013 13 1 sysinfo 0.00 0.000000 0 4 pread64 0.00 0.000000 0 1 1 access 0.00 0.000000 0 1 execve 0.00 0.000000 0 1 getuid 0.00 0.000000 0 1 getgid 0.00 0.000000 0 1 geteuid 0.00 0.000000 0 1 getegid ------ ----------- ----------- --------- --------- ---------------- 100.00 0.032426 48 675 78 total
Wenn eine interpretierte Sprache erwartungsgemäss mehr Syscalls benötigt als eine kompilierte Sprache, ist der Unterschied zwischen den beiden kompilierten Sprachen C und Go weniger verständlich.
Ausserdem fügt die Verfolgung desselben Codes, der in einem Container läuft, eine Menge “Rauschen” hinzu:
bash$ strace -c podman run --rm --name labs_20220203 labs20220203 /tmp/run.hello.go Hello World % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ------------------ 66.48 0.120977 822 147 4 futex 5.29 0.009625 53 180 39 newfstatat 3.95 0.007187 52 136 read 3.18 0.005796 36 160 mmap 3.06 0.005569 51 109 27 openat 2.58 0.004692 43 107 close 2.40 0.004370 58 75 rt_sigprocmask 2.24 0.004070 47 86 34 epoll_ctl 0.79 0.001431 44 32 epoll_wait 0.76 0.001384 12 115 rt_sigaction 0.73 0.001327 45 29 fcntl 0.66 0.001194 56 21 socket 0.64 0.001156 25 46 mprotect 0.59 0.001080 90 12 munmap 0.56 0.001020 56 18 6 connect 0.56 0.001012 1012 1 execve 0.44 0.000802 61 13 geteuid 0.42 0.000772 55 14 fstat 0.41 0.000751 35 21 4 rt_sigreturn 0.34 0.000626 52 12 getdents64 0.32 0.000585 83 7 sched_yield 0.26 0.000477 47 10 sendto 0.25 0.000464 51 9 statfs 0.25 0.000458 26 17 pread64 0.23 0.000427 42 10 fstatfs 0.19 0.000346 43 8 recvfrom 0.18 0.000322 80 4 clone3 0.18 0.000321 80 4 brk 0.18 0.000321 45 7 getsockname 0.15 0.000275 34 8 7 setrlimit 0.15 0.000268 44 6 4 prctl 0.13 0.000245 35 7 getpid 0.12 0.000215 35 6 recvmsg 0.10 0.000186 62 3 timerfd_create 0.10 0.000174 87 2 setns 0.08 0.000154 154 1 chdir 0.08 0.000150 30 5 getuid 0.08 0.000141 28 5 lseek 0.08 0.000140 46 3 bind 0.08 0.000138 46 3 3 mkdirat 0.07 0.000132 44 3 epoll_pwait 0.07 0.000120 40 3 epoll_create1 0.06 0.000118 29 4 3 access 0.05 0.000096 32 3 fchmodat 0.05 0.000094 94 1 prlimit64 0.05 0.000082 27 3 gettid 0.04 0.000076 76 1 setresgid 0.04 0.000072 36 2 getrandom 0.04 0.000071 35 2 getrlimit 0.04 0.000069 69 1 setresuid 0.04 0.000066 66 1 pipe2 0.03 0.000063 63 1 getegid 0.03 0.000058 29 2 timerfd_settime 0.03 0.000057 57 1 capget 0.03 0.000051 51 1 getcwd 0.02 0.000044 44 1 sysinfo 0.02 0.000036 18 2 1 arch_prctl 0.01 0.000027 27 1 readlinkat 0.00 0.000008 8 1 sched_getaffinity 0.00 0.000000 0 1 uname 0.00 0.000000 0 1 umask 0.00 0.000000 0 2 sigaltstack 0.00 0.000000 0 1 set_tid_address 0.00 0.000000 0 1 set_robust_list ------ ----------- ----------- --------- --------- ------------------ 100.00 0.181988 121 1499 132 total
Ziemlich kompliziert zu verstehen, welcher Syscall vom Code und welcher von der Container-Laufzeit ausgeführt wurde. Aber da wir einen einzelnen Container in einer Testumgebung laufen lassen, haben wir es immer noch mit dem einfachen Teil des Problems zu tun. Wir müssen die Syscalls eines bestimmten Containers in einem Cluster mit vielen Containern nachverfolgen, also müssen wir feststellen, welcher Syscall zu welchem Container gehört.
Um dieses Problem zu lösen, wurden einige Tools entwickelt. Wir werden uns das grundlegende Verfahren zum Verfolgen eines Containers, Erstellen und Anwenden von Richtlinien ansehen. Im zweiten Teil werden wir dasselbe Verfahren auf einen Kubernetes-Cluster in einem automatisierten Modus (CI/CD) anwenden.
Wir werden versuchen, OCI-konform zu bleiben und daher podman anstelle von Docker verwenden, um Container zu verwalten und auszuführen. In unserem Fall läuft podman auf einer Fedora35 VMWare Workstation unter Windows 10, aber das Gleiche gilt für eine generische Container-Umgebung. Das Ziel ist es, zu erkennen, wann ein bestimmter Container anfängt, Syscalls zu tätigen und diese bis zum Beenden des Containers zu verfolgen.
Hier ist seccomp-bpf
hilfreich. Als Superuser können wir einen Filter erstellen, der in den Kernel injiziert wird, ihn an einen bestimmten Syscall hängen (sys_enter
), den Prozess markieren (--annotation
) und die ausgeführten Syscalls sammeln. Wir müssen nur einige OCI-Tools zu unserem System hinzufügen (unter Fedora installieren Sie einfach das Paket oci-seccomp-bpf-hook
oder kompilieren Sie Ihr eigenes) und einen Hook konfigurieren:
bash$ cat /usr/share/containers/oci/hooks.d/oci-seccomp-bpf-hook.json { "version": "1.0.0", "hook": { "path": "/usr/libexec/oci/hooks.d/oci-seccomp-bpf-hook", "args": [ "oci-seccomp-bpf-hook", "-s" ] }, "when": { "annotations": { "^io\\.containers\\.trace-syscall$": ".*" } }, "stages": [ "prestart" ] }
Nachdem die Tools installiert und konfiguriert sind, können wir mit der Erstellung der Richtlinie fortfahren.
Nachforschungen haben ergeben, dass die Mehrheit der Container nur etwa 80-120 Syscalls verwendet. Durch die Anwendung von Filtern wird die Belastung des Kernels von ~400 auf ~100 Syscalls reduziert. Beachten Sie, dass Docker eine Standardrichtlinie seccomp-bpf
hat, die ~50 Syscalls ausschliesst.
Wir können podman verwenden, um eine seccomp-bpf
-Richtlinie zu erstellen. Im folgenden Beispiel erstellen wir einen sehr einfachen dedizierten Container auf der Basis von fedora
, kopieren unsere Software in das Verzeichnis /tmp
und starten den Container mit der erforderlichen io.containers.trace-syscall=
-Anmerkung. Der seccomp-bpf
-Hook löst die Syscall-Protokolle aus, wenn sie mit der Annotation übereinstimmen, und erstellt die gebrauchsfertige Richtliniendatei /tmp/run.hello.c.json
.
# podman build -t labs20220203 . STEP 1/2: FROM fedora STEP 2/2: COPY run.hello* /tmp COMMIT labs20220203 --> b3717541ab7 Successfully tagged localhost/labs20220203:latest b3717541ab7ad6fe59b34dde61afdbb0f1cefcece5b9d28d426443805cb0ab94 # podman run --rm --name labs_20220203 --annotation io.containers.trace-syscall=of:/tmp/run.hello.c.json labs20220203 /tmp/run.hello.c Hello World # cat /tmp/run.hello.c.json | jq { "architectures" : [ "SCMP_ARCH_X86_64" ], "defaultAction" : "SCMP_ACT_ERRNO", "syscalls" : [ { "action" : "SCMP_ACT_ALLOW", "args" : [], "comment" : "", "excludes" : {}, "includes" : {}, "names" : [ "access", "arch_prctl", "brk", "capset", "chdir", "close", "close_range", "dup2", "execve", "exit_group", "fchdir", "fchown", "fstatfs", "getegid", "geteuid", "getgid", "getrandom", "getuid", "ioctl", "lseek", "mmap", "mount", "mprotect", "munmap", "newfstatat", "openat", "openat2", "pivot_root", "prctl", "pread64", "prlimit64", "pselect6", "read", "rt_sigaction", "rt_sigprocmask", "seccomp", "set_robust_list", "set_tid_address", "sethostname", "setresgid", "setresuid", "setsid", "statx", "umask", "umount2", "write" ] } ] }
Gar nicht so schlecht. Mit recht einfachen Befehlen haben wir eine brauchbare seccomp-bpf
-Richtlinie erstellt, die wir mit unserer containerisierten Anwendung mitschicken und in der Kubernetes POD-Definition (spec/securityContext/seccompProfile
) verwenden können.
Zu einfach, oder? Stimmt.
Nehmen Sie als Beispiel ein sehr verbreitetes Dienstprogramm ls
und versuchen Sie, ein Profil für dieses zu erstellen.
# podman run --rm --name labs_20220203 --annotation io.containers.trace-syscall=of:/tmp/fedora_ls_no-opt.json fedora ls / ... # cat /tmp/fedora_ls_no-opt.json | jq { "defaultAction": "SCMP_ACT_ERRNO", "architectures": [ "SCMP_ARCH_X86_64" ], "syscalls": [ { "names": [ "access", "arch_prctl", "brk", "capset", "chdir", "close", "close_range", "dup2", "execve", "exit_group", "fchdir", "fchown", "fstatfs", "getdents64", "getegid", "geteuid", "getgid", "getrandom", "getuid", "ioctl", "lseek", "mmap", "mount", "mprotect", "munmap", "newfstatat", "openat", "openat2", "pivot_root", "prctl", "pread64", "prlimit64", "pselect6", "read", "rt_sigaction", "rt_sigprocmask", "seccomp", "set_robust_list", "set_tid_address", "sethostname", "setresgid", "setresuid", "setsid", "statfs", "statx", "umask", "umount2", "write" ], "action": "SCMP_ACT_ALLOW", "args": [], "comment": "", "includes": {}, "excludes": {} } ] } # cat /tmp/fedora_ls_no-opt.json | jq '.syscalls[].names | length' 48
Versuchen Sie nun ls
mit einigen Optionen, zum Beispiel -lisa
:
# podman run --rm --name labs_20220203 --annotation io.containers.trace-syscall=of:/tmp/fedora_ls_no-opt.json fedora ls -lisa / ... # cat /tmp/fedora_ls_lisa.json | jq { "defaultAction": "SCMP_ACT_ERRNO", "architectures": [ "SCMP_ARCH_X86_64" ], "syscalls": [ { "names": [ "access", "arch_prctl", "brk", "capset", "chdir", "close", "close_range", "connect", "dup2", "execve", "exit_group", "fchdir", "fchown", "fstatfs", "futex", "getdents64", "getegid", "geteuid", "getgid", "getrandom", "getuid", "getxattr", "ioctl", "lgetxattr", "lseek", "mmap", "mount", "mprotect", "munmap", "newfstatat", "openat", "openat2", "pivot_root", "prctl", "pread64", "prlimit64", "pselect6", "read", "readlink", "rt_sigaction", "rt_sigprocmask", "seccomp", "set_robust_list", "set_tid_address", "sethostname", "setresgid", "setresuid", "setsid", "socket", "statfs", "statx", "umask", "umount2", "write" ], "action": "SCMP_ACT_ALLOW", "args": [], "comment": "", "includes": {}, "excludes": {} } ] } # cat /tmp/fedora_ls_lisa.json | jq '.syscalls[].names | length' 54
Also derselbe Befehl, aber wir haben sechs Syscalls mehr, wenn wir die -lisa
Optionen verwenden!
Um eine vollständige und stabile seccomp-bpf
-Richtlinie zu haben, müssen wir ls
mit allen möglichen Optionskombinationen ausführen, sonst könnte eine nicht getestete Kombination unsere Anwendung zum Absturz bringen. Dies ist bereits bei einer relativ einfachen Anwendung wie ls
kompliziert. Denselben Prozess für eine komplexe verteilte Anwendung zu durchlaufen, welche in einem schnellen Bereitstellungszyklus auf Netzwerke und Volumen zugreift, ist eine Herausforderung.
Sobald die Richtlinie fertig ist, können Sie sie einfach auf den Container anwenden. Beachten Sie, dass es nicht möglich ist, ein seccomp-bpf
Profil auf einen Container anzuwenden, der mit privileged: True im securityContext
des Containers eingestellt ist. Privilegierte Container laufen immer als Unconfined
.
Wenn Sie das Dienstprogramm ls
mit dem erstellten Profil ausführen, werden Syscalls, die nicht ausdrücklich erlaubt sind, blockiert. Wenn wir versuchen, ls -lisa mit der Richtlinie von ls
auszuführen, erhalten wir Fehler:
bash$ sudo podman run --security-opt seccomp=./fedora_ls_no-opt.json fedora ls / bin boot ... bash$ sudo podman run --security-opt seccomp=./fedora_ls_no-opt.json fedora ls -lisa / ls: /: Operation not permitted ls: /bin: Operation not permitted ls: Cannot read symbolic link '/bin': Operation not permitted ... total 16 441735 0 dr-xr-xr-x 1 root root 6 Jan 20 15:15 . 441735 0 dr-xr-xr-x 1 root root 6 Jan 20 15:15 .. 303907 4 lrwxrwxrwx 1 root root 7 Jul 21 2021 bin ... (ERROR)-(Exit Code 1)-(General error)
Wir können die Richtlinie so abstimmen, dass eine Baseline zugelassen wird und andere Syscalls im audit.log protokolliert werden.
{ "defaultAction": "SCMP_ACT_ERRNO", "architectures": [ "SCMP_ARCH_X86_64" ], "syscalls": [ { "names": [ "access", ... "write" ], "action": "SCMP_ACT_ALLOW", "args": [], "comment": "", "includes": {}, "excludes": {} }, { "names": [ "connect", "futex", "getxattr", "lgetxattr", "readlink", "socket" ], "action": "SCMP_ACT_LOG", "args": [], "comment": "", "includes": {}, "excludes": {} } ] } bash$ sudo podman run --security-opt seccomp=./fedora_ls_no-opt_log-delta.json fedora ls -lisa / total 16 441781 0 dr-xr-xr-x. 1 root root 6 Jan 20 15:29 . 441781 0 dr-xr-xr-x. 1 root root 6 Jan 20 15:29 .. 303907 4 lrwxrwxrwx. 1 root root 7 Jul 21 2021 bin -> usr/bin 303903 0 dr-xr-xr-x. 1 root root 0 Jul 21 2021 boot 1 0 drwxr-xr-x. 5 root root 340 Jan 20 15:29 dev 296107 0 drwxr-xr-x. 1 root root 1682 Nov 25 06:49 etc bash$ cat /var/log/audit.log ... type=SECCOMP msg=audit(1642692596.061:447): Auid=1000 uid=0 gid=0 ses=3 subj=system_u:systeer:container_t:s0:c390,c427 pid=17505 comm="ls" exe="/usr/bin/ls" sig=0 arch=c000003e syscall=202 compat=0 ip=0x7f323c139d40 code=0x7ffc0000AUID="rcc" UID="root" GID="root" ARCH=x86_64 SYSCALL=futex type=SECCOMP msg=audit(1642692596.061:454): Auid=1000 uid=0 gid=0 ses=3 subj=system_u:systeer:container_t:s0:c390,c427 pid=17505 comm="ls" exe="/usr/bin/ls" sig=0 arch=c000003e syscall=41 compat=0 ip=0x7f323c1ba55b code=0x7ffc0000AUID="rcc" UID="root" GID="root" ARCH=x86_64 SYSCALL=socket type=SECCOMP msg=audit(1642692596.061:455): Auid=1000 uid=0 gid=0 ses=3 subj=system_u:systeer:container_t:s0:c390,c427 pid=17505 comm="ls" exe="/usr/bin/ls" sig=0 arch=c000003e syscall=42 compat=0 ip=0x7f323c1b9f27 code=0x7ffc0000AUID="rcc" UID="root" GID="root" ARCH=x86_64 SYSCALL=connect type=SECCOMP msg=audit(1642692596.062:499): Auid=1000 uid=0 gid=0 ses=3 subj=system_u:systeer:container_t:s0:c390,c427 pid=17505 comm="ls" exe="/usr/bin/ls" sig=0 arch=c000003e syscall=191 compat=0 ip=0x7f323c1b50be code=0x7ffc0000AUID="rcc" UID="root" GID="root" ARCH=x86_64 SYSCALL=getxattr type=SECCOMP msg=audit(1642692596.062:500): Auid=1000 uid=0 gid=0 ses=3 subj=system_u:systeer:container_t:s0:c390,c427 pid=17505 comm="ls" exe="/usr/bin/ls" sig=0 arch=c000003e syscall=192 compat=0 ip=0x7f323c1b511e code=0x7ffc0000AUID="rcc" UID="root" GID="root" ARCH=x86_64 SYSCALL=lgetxattr type=SECCOMP msg=audit(1642692596.062:501): Auid=1000 uid=0 gid=0 ses=3 subj=system_u:systeer:container_t:s0:c390,c427 pid=17505 comm="ls" exe="/usr/bin/ls" sig=0 arch=c000003e syscall=191 compat=0 ip=0x7f323c1b50be code=0x7ffc0000AUID="rcc" UID="root" GID="root" ARCH=x86_64 SYSCALL=getxattr type=SECCOMP msg=audit(1642692596.062:504): Auid=1000 uid=0 gid=0 ses=3 subj=system_u:systeer:container_t:s0:c390,c427 pid=17505 comm="ls" exe="/usr/bin/ls" sig=0 arch=c000003e syscall=89 compat=0 ip=0x7f323c1aa05b code=0x7ffc0000AUID="rcc" UID="root" GID="root" ARCH=x86_64 SYSCALL=readlink ...
Mit dieser Methode könnten wir auch einen SOC Usecase erstellen, der bei “unerwarteten” Syscall-Ereignissen Alarm schlägt.
Wir haben gesehen, wie man steuern kann, welche privilegierten Funktionen ein Kernel einem bestimmten unprivilegierten Prozess anbieten soll. Warum sehen wir also nicht für jede Anwendung eine eigene Richtlinie? Weil die eigentliche Herausforderung darin besteht, eine Richtlinie zu erstellen, die die Anwendung sicher macht, aber den Betrieb nicht “stört”.
Wie wir gesehen haben, befinden sich die Filter im Kernel und verhindern unliebsame Syscall-Aufrufe. Aber die Liste der Syscalls, die von einer Anwendung verwendet werden, ist normalerweise nicht verfügbar. Sie hängt von einer beträchtlichen Anzahl von Faktoren ab, darunter Architektur und Compiler.
Die Bedeutung von Microservices-Umgebungen wird in Zukunft zunehmen und damit auch die Anzahl der Anwendungen und die Sicherheitskontrollen werden immer komplexer. Letztendlich müssen wir tiefer in das System eindringen und Kontrollen auch im “vertrauenswürdigen” Teil des Systems installieren, dem Kernel.
seccomp-bpf
gibt uns die Möglichkeit, Richtlinien auf der Kernel-Ebene durchzusetzen (einmal durchgesetzt, gibt es keinen anderen Weg zurück als einen Neustart), aber die Abstimmung der Richtlinien, insbesondere in einer dynamischen Umgebung, ist nicht trivial. Nichtsdestotrotz müssen wir uns mit ihnen beschäftigen, jetzt und in Zukunft noch mehr.
Wie eine Richtlinie automatisch erstellt wird, ist ein Thema des nächsten Labs, in dem wir Beispiele für die beiden wichtigsten Strategien sehen werden. Statische und dynamische Analyse, automatisiert in einer CD/CI-Kette.
Unsere Spezialisten kontaktieren Sie gern!
Rocco Gagliardi
Rocco Gagliardi
Rocco Gagliardi
Rocco Gagliardi
Unsere Spezialisten kontaktieren Sie gern!