Seccomp-bpf - Container mit Syscalls-Filters unter Kontrolle

Seccomp-bpf

Container mit Syscalls-Filters unter Kontrolle

Rocco Gagliardi
von Rocco Gagliardi
am 03. Februar 2022
Lesezeit: 16 Minuten

Keypoints

Container mit Syscalls-Filters unter Kontrolle

  • Container enthalten nichts
  • mit Microservices müssen wir die Sicherheit auf die Kernel-Ebene bringen
  • SECure COMPuting-Profile sind in der Theorie mächtig, in der Praxis schwierig
  • seccomp-bpf gibt uns die Möglichkeit, einen minimierten Kernel für jede Anwendung freizulegen

Die exponentielle Zunahme von Microservices stellt neue Herausforderungen an die Sicherheit, die selbst in Komponenten, die bis vor ein paar Jahren per Definition als sicher galten, notwendig sind.

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.

Reduzierung der Angriffsfläche

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().

Sicherheitsmechanismen in einem Container

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.

Probleme bei der Verfolgung von Syscalls

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.

Verfolgen eines Containers und Erstellen der Richtlinie

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.

Verfolgen eines Containers

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.

Erstellen Sie eine Richtlinie

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.

Richtlinie anwenden

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:system_r: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:system_r: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:system_r: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:system_r: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:system_r: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:system_r: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:system_r: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.

Zusammenfassung

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.

Über den Autor

Rocco Gagliardi

Rocco Gagliardi ist seit den 1980er Jahren im Bereich der Informationstechnologie tätig. In den 1990er Jahren hat er sich ganz der Informationssicherheit verschrieben. Die Schwerpunkte seiner Arbeit liegen im Bereich Security Frameworks, Routing, Firewalling und Log Management.

Links

Sie brauchen Unterstützung bei einem solchen Projekt?

Unsere Spezialisten kontaktieren Sie gern!

×
Übergang zu OpenSearch

Übergang zu OpenSearch

Rocco Gagliardi

Graylog v5

Graylog v5

Rocco Gagliardi

auditd

auditd

Rocco Gagliardi

Security Frameworks

Security Frameworks

Rocco Gagliardi

Sie wollen mehr?

Weitere Artikel im Archiv

Sie brauchen Unterstützung bei einem solchen Projekt?

Unsere Spezialisten kontaktieren Sie gern!

Sie wollen mehr?

Weitere Artikel im Archiv