diff --git a/deployments/kubehound/graph/kubehound-db-init.groovy b/deployments/kubehound/graph/kubehound-db-init.groovy index 7147f456d..9ff2792e3 100644 --- a/deployments/kubehound/graph/kubehound-db-init.groovy +++ b/deployments/kubehound/graph/kubehound-db-init.groovy @@ -40,8 +40,8 @@ mgmt.addConnection(hostRead, volume, node); hostTraverse = mgmt.makeEdgeLabel('EXPLOIT_HOST_TRAVERSE').multiplicity(MULTI).make(); mgmt.addConnection(hostTraverse, volume, volume); -sharedPs = mgmt.makeEdgeLabel('SHARE_PS_NAMESPACE').multiplicity(MULTI).make(); -mgmt.addConnection(sharedPs, container, container); +sharedPsNamespace = mgmt.makeEdgeLabel('SHARE_PS_NAMESPACE').multiplicity(MULTI).make(); +mgmt.addConnection(sharedPsNamespace, container, container); containerAttach = mgmt.makeEdgeLabel('CONTAINER_ATTACH').multiplicity(ONE2MANY).make(); mgmt.addConnection(containerAttach, pod, container); @@ -149,6 +149,9 @@ protocol = mgmt.makePropertyKey('protocol').dataType(String.class).cardinality(C role = mgmt.makePropertyKey('role').dataType(String.class).cardinality(Cardinality.SINGLE).make(); roleBinding = mgmt.makePropertyKey('roleBinding').dataType(String.class).cardinality(Cardinality.SINGLE).make(); +// All edge properties +attckTechniqueID = mgmt.makePropertyKey('attckTechniqueID').dataType(String.class).cardinality(Cardinality.SINGLE).make(); +attckTacticID = mgmt.makePropertyKey('attckTacticID').dataType(String.class).cardinality(Cardinality.SINGLE).make(); // Define properties for each vertex mgmt.addProperties(container, cls, cluster, runID, storeID, app, team, service, isNamespaced, namespace, name, image, privileged, privesc, hostPid, hostIpc, hostNetwork, runAsUser, podName, nodeName, compromised, command, args, capabilities, ports); @@ -159,6 +162,32 @@ mgmt.addProperties(permissionSet, cls, cluster, runID, storeID, app, team, servi mgmt.addProperties(volume, cls, cluster, runID, storeID, app, team, service, name, isNamespaced, namespace, type, sourcePath, mountPath, readonly); mgmt.addProperties(endpoint, cls, cluster, runID, storeID, app, team, service, name, isNamespaced, namespace, serviceEndpoint, serviceDns, addressType, addresses, port, portName, protocol, exposure, compromised); +// Define properties for each edge +mgmt.addProperties(permissionDiscover, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(volumeDiscover, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(volumeAccess, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(hostWrite, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(hostRead, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(hostTraverse, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(sharedPsNamespace, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(containerAttach, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(idAssume, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(idImpersonate, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(roleBind, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(podAttach, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(podCreate, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(podPatch, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(podExec, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(tokenSteal, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(tokenBruteforce, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(tokenList, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(nsenter, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(moduleLoad, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(umhCorePattern, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(privMount, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(sysPtrace, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(varLogSymLink, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(endpointExploit, runID, attckTechniqueID, attckTacticID); // Create the indexes on vertex properties // NOTE: labels cannot be indexed so we create the class property to mirror the vertex label and allow indexing @@ -190,6 +219,11 @@ mgmt.buildIndex('byImageAndRunIDComposite', Vertex.class).addKey(image).addKey(r mgmt.buildIndex('byAppAndRunIDComposite', Vertex.class).addKey(app).addKey(runID).buildCompositeIndex(); mgmt.buildIndex('byNamespaceAndRunIDComposite', Vertex.class).addKey(namespace).addKey(runID).buildCompositeIndex(); +// Create the indexes on edge properties +mgmt.buildIndex('edgesByAttckTechniqueID', Edge.class).addKey(attckTechniqueID).buildCompositeIndex(); +mgmt.buildIndex('edgesByAttckTacticID', Edge.class).addKey(attckTacticID).buildCompositeIndex(); +mgmt.buildIndex('edgesByRunID', Edge.class).addKey(runID).buildCompositeIndex(); + mgmt.commit(); // Wait for indexes to become available @@ -220,6 +254,10 @@ ManagementSystem.awaitGraphIndexStatus(graph, 'byImageAndRunIDComposite').status ManagementSystem.awaitGraphIndexStatus(graph, 'byAppAndRunIDComposite').status(SchemaStatus.ENABLED).call(); ManagementSystem.awaitGraphIndexStatus(graph, 'byNamespaceAndRunIDComposite').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'edgesByAttckTechniqueID').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'edgesByAttckTacticID').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'edgesByRunID').status(SchemaStatus.ENABLED).call(); + System.out.println("[KUBEHOUND] graph schema and indexes ready"); mgmt.close(); diff --git a/docs/reference/attacks/CE_MODULE_LOAD.md b/docs/reference/attacks/CE_MODULE_LOAD.md index f4a26c395..6747f59a0 100644 --- a/docs/reference/attacks/CE_MODULE_LOAD.md +++ b/docs/reference/attacks/CE_MODULE_LOAD.md @@ -11,7 +11,7 @@ mitreAttackTactic: TA0004 - Privilege escalation # CE_MODULE_LOAD -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | diff --git a/docs/reference/attacks/CE_NSENTER.md b/docs/reference/attacks/CE_NSENTER.md index b9ba8dfc6..082d41b86 100644 --- a/docs/reference/attacks/CE_NSENTER.md +++ b/docs/reference/attacks/CE_NSENTER.md @@ -11,7 +11,7 @@ mitreAttackTactic: TA0004 - Privilege escalation # CE_NSENTER -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | diff --git a/docs/reference/attacks/CE_PRIV_MOUNT.md b/docs/reference/attacks/CE_PRIV_MOUNT.md index deb414112..82827d70d 100644 --- a/docs/reference/attacks/CE_PRIV_MOUNT.md +++ b/docs/reference/attacks/CE_PRIV_MOUNT.md @@ -11,7 +11,7 @@ mitreAttackTactic: TA0004 - Privilege escalation # CE_PRIV_MOUNT -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | diff --git a/docs/reference/attacks/CE_SYS_PTRACE.md b/docs/reference/attacks/CE_SYS_PTRACE.md index 9da79dc4c..92e283f77 100644 --- a/docs/reference/attacks/CE_SYS_PTRACE.md +++ b/docs/reference/attacks/CE_SYS_PTRACE.md @@ -11,7 +11,7 @@ mitreAttackTactic: TA0004 - Privilege escalation # CE_SYS_PTRACE -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | diff --git a/docs/reference/attacks/CE_UMH_CORE_PATTERN.md b/docs/reference/attacks/CE_UMH_CORE_PATTERN.md index 0ecb2c1fc..fc6a9838b 100644 --- a/docs/reference/attacks/CE_UMH_CORE_PATTERN.md +++ b/docs/reference/attacks/CE_UMH_CORE_PATTERN.md @@ -9,7 +9,7 @@ mitreAttackTactic: TA0004 - Privilege escalation Container escape via the `core_pattern` `usermode_helper` in the case of an exposed `/proc` mount. -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | diff --git a/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md b/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md index 4b5558686..82c3239f3 100644 --- a/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md +++ b/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md @@ -5,13 +5,13 @@ title: CE_VAR_LOG_SYMLINK # CE_VAR_LOG_SYMLINK -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | diff --git a/docs/reference/attacks/CONTAINER_ATTACH.md b/docs/reference/attacks/CONTAINER_ATTACH.md index 042fc8a3b..1ab1acc6a 100644 --- a/docs/reference/attacks/CONTAINER_ATTACH.md +++ b/docs/reference/attacks/CONTAINER_ATTACH.md @@ -5,15 +5,15 @@ title: CONTAINER_ATTACH # CONTAINER_ATTACH -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------- | ------------------------------------- | -------------------------------------------------------------------- | -| [Pod](../entities/pod.md) | [Container](../entities/container.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| [Pod](../entities/pod.md) | [Container](../entities/container.md) | [Container Administration Command, T1609](https://attack.mitre.org/techniques/T1609/) | Attach to a container running within a pod given access to the pod. diff --git a/docs/reference/attacks/ENDPOINT_EXPLOIT.md b/docs/reference/attacks/ENDPOINT_EXPLOIT.md index 2e64134ac..be0d8d441 100644 --- a/docs/reference/attacks/ENDPOINT_EXPLOIT.md +++ b/docs/reference/attacks/ENDPOINT_EXPLOIT.md @@ -12,7 +12,7 @@ mitreAttackTactic: TA0008 - Lateral Movement Represents a network endpoint exposed by a container that could be exploited by an attacker (via means known or unknown). This can correspond to a Kubernetes service, node service, node port, or container port. -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ----------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------ | | [Endpoint](../entities/endpoint.md) | [Container](../entities/container.md) | [Exploitation of Remote Services, T1210](https://attack.mitre.org/techniques/T1210/) | diff --git a/docs/reference/attacks/EXPLOIT_CONTAINERD_SOCK.md b/docs/reference/attacks/EXPLOIT_CONTAINERD_SOCK.md index c60f6cb45..8d083fe21 100644 --- a/docs/reference/attacks/EXPLOIT_CONTAINERD_SOCK.md +++ b/docs/reference/attacks/EXPLOIT_CONTAINERD_SOCK.md @@ -5,18 +5,23 @@ title: EXPLOIT_CONTAINERD_SOCK # EXPLOIT_CONTAINERD_SOCK -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------------------- | ------------------------------------- | -------------------------------------------------------------------- | -| [Container](../entities/container.md) | [Container](../entities/container.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| [Container](../entities/container.md) | [Container](../entities/container.md) | [Deploy Container, T1610](https://attack.mitre.org/techniques/T1610/) | Container escape via the `containerd.sock` file that allows executing a binary into another container. +!!! warning + + This attack detection is currently __NOT IMPLEMENTED__. + ## Details When the `containerd.sock` (or other equivalent - see the list below) is mounted inside a container, it allows the container to interact with container runtime. Therefore an attacker can execute any command in any container present in the cluster. This allows an attacker to do some lateral movement across the cluster. diff --git a/docs/reference/attacks/EXPLOIT_HOST_READ.md b/docs/reference/attacks/EXPLOIT_HOST_READ.md index 12b3025b6..6386835b8 100644 --- a/docs/reference/attacks/EXPLOIT_HOST_READ.md +++ b/docs/reference/attacks/EXPLOIT_HOST_READ.md @@ -11,7 +11,7 @@ mitreAttackTactic: TA0004 - Privilege escalation # EXPLOIT_HOST_READ -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | diff --git a/docs/reference/attacks/EXPLOIT_HOST_TRAVERSE.md b/docs/reference/attacks/EXPLOIT_HOST_TRAVERSE.md index c132b02fc..eef30e108 100644 --- a/docs/reference/attacks/EXPLOIT_HOST_TRAVERSE.md +++ b/docs/reference/attacks/EXPLOIT_HOST_TRAVERSE.md @@ -11,7 +11,7 @@ mitreAttackTactic: TA0006 - Credential Access # EXPLOIT_HOST_TRAVERSE -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------------- | ------------------------------- | -------------------------------------------------------------------------- | | [Volume](../entities/volume.md) | [Volume](../entities/volume.md) | [Unsecured Credentials, T1552](https://attack.mitre.org/techniques/T1552/) | diff --git a/docs/reference/attacks/EXPLOIT_HOST_WRITE.md b/docs/reference/attacks/EXPLOIT_HOST_WRITE.md index 13d91b84a..514b55f6c 100644 --- a/docs/reference/attacks/EXPLOIT_HOST_WRITE.md +++ b/docs/reference/attacks/EXPLOIT_HOST_WRITE.md @@ -11,7 +11,7 @@ mitreAttackTactic: TA0004 - Privilege escalation # EXPLOIT_HOST_WRITE -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | diff --git a/docs/reference/attacks/IDENTITY_ASSUME.md b/docs/reference/attacks/IDENTITY_ASSUME.md index aa069178c..6b0923384 100644 --- a/docs/reference/attacks/IDENTITY_ASSUME.md +++ b/docs/reference/attacks/IDENTITY_ASSUME.md @@ -11,7 +11,7 @@ mitreAttackTactic: TA0004 - Privilege escalation # IDENTITY_ASSUME -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ------------------------------------------------------------------ | ----------------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md), [Node](../entities/node.md) | [Identity](../entities/identity.md) | [Valid Accounts, T1078](https://attack.mitre.org/techniques/T1078/) | diff --git a/docs/reference/attacks/IDENTITY_IMPERSONATE.md b/docs/reference/attacks/IDENTITY_IMPERSONATE.md index 272091ce5..26c0ad622 100644 --- a/docs/reference/attacks/IDENTITY_IMPERSONATE.md +++ b/docs/reference/attacks/IDENTITY_IMPERSONATE.md @@ -3,16 +3,21 @@ id: IDENTITY_IMPERSONATE name: "Impersonate user/group" mitreAttackTechnique: T1078 - Valid Accounts mitreAttackTactic: TA0004 - Privilege escalation +coverage: None --> # IDENTITY_IMPERSONATE With a [user impersonation privilege](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation) an attacker can impersonate a more privileged account. -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | --------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------- | | [PermissionSet](../entities/permissionset.md) | [Identity](../entities/identity.md) | [Valid Accounts, T1078](https://attack.mitre.org/techniques/T1078/) | +!!! warning + + This attack detection is currently __NOT IMPLEMENTED__. + ## Details Obtaining the `impersonate users/groups` permission will allow an attacker to execute K8s API actions on behalf of another user, including those with `cluster-admin` rights, and other highly privileged users. @@ -37,7 +42,7 @@ kubectl auth can-i impersonate groups Execute any action in the K8s API impersonating a privileged group (e.g `system:masters`) or user using the syntax: ```bash -$ kubectl –as=null –as-group=system:masters -o json | jq +$ kubectl -as=null -as-group=system:masters -o json | jq ``` ## Defences diff --git a/docs/reference/attacks/PERMISSION_DISCOVER.md b/docs/reference/attacks/PERMISSION_DISCOVER.md index 19a6f82d9..ca38f43ee 100644 --- a/docs/reference/attacks/PERMISSION_DISCOVER.md +++ b/docs/reference/attacks/PERMISSION_DISCOVER.md @@ -13,7 +13,7 @@ mitreAttackTactic: TA0007 - Discovery Represents the permissions granted to an identity that can be discovered by an attacker. -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | ----------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------- | | [Identity](../entities/identity.md) | [PermissionSet](../entities/permissionset.md) | [Permission Groups Discovery, T1069](https://attack.mitre.org/techniques/T1078/) | diff --git a/docs/reference/attacks/POD_ATTACH.md b/docs/reference/attacks/POD_ATTACH.md index e8772c79e..85c8c9fe5 100644 --- a/docs/reference/attacks/POD_ATTACH.md +++ b/docs/reference/attacks/POD_ATTACH.md @@ -5,15 +5,15 @@ title: POD_ATTACH # POD_ATTACH -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | --------------------------- | ------------------------- | -------------------------------------------------------------------- | -| [Node](../entities/node.md) | [Pod](../entities/pod.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| [Node](../entities/node.md) | [Pod](../entities/pod.md) | [Container Administration Command, T1609](https://attack.mitre.org/tactics/T1609/) | Attach to a running K8s pod from a K8s node. diff --git a/docs/reference/attacks/POD_CREATE.md b/docs/reference/attacks/POD_CREATE.md index 1b5411da2..98e31b0dc 100644 --- a/docs/reference/attacks/POD_CREATE.md +++ b/docs/reference/attacks/POD_CREATE.md @@ -5,17 +5,17 @@ title: POD_CREATE # POD_CREATE Create a pod with significant privilege (`CAP_SYSADMIN`, `hostPath=/`, etc) and schedule on a target node via setting the `nodeName` selector. -| Source | Destination | MITRE | +| Source | Destination | MITRE ATT&CK | | --------------------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------- | -| [PermissionSet](../entities/permissionset.md) | [Node](../entities/node.md) | [Container Orchestration Job, T1053.007](https://attack.mitre.org/techniques/T1053/007/) | +| [PermissionSet](../entities/permissionset.md) | [Node](../entities/node.md) | [Deploy Container, T1610](https://attack.mitre.org/techniques/T1610/) | ## Details diff --git a/docs/reference/attacks/POD_EXEC.md b/docs/reference/attacks/POD_EXEC.md index f4be389d7..b67ea72c2 100644 --- a/docs/reference/attacks/POD_EXEC.md +++ b/docs/reference/attacks/POD_EXEC.md @@ -5,17 +5,17 @@ title: POD_EXEC # POD_EXEC With the correct privileges an attacker can use the Kubernetes API to obtain a shell on a running pod. -| Source | Destination | MITRE | -| --------------------------------------------- | ------------------------- | -------------------------------------------------------------------- | -| [PermissionSet](../entities/permissionset.md) | [Pod](../entities/pod.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------- | +| [PermissionSet](../entities/permissionset.md) | [Pod](../entities/pod.md) | [Container Administration Command, T1609](https://attack.mitre.org/techniques/T1609/) | ## Details diff --git a/docs/reference/attacks/POD_PATCH.md b/docs/reference/attacks/POD_PATCH.md index 6642c246d..995c8f647 100644 --- a/docs/reference/attacks/POD_PATCH.md +++ b/docs/reference/attacks/POD_PATCH.md @@ -5,17 +5,17 @@ title: POD_PATCH # POD_PATCH With the correct privileges an attacker can use the Kubernetes API to modify certain properties of an existing pod and achieve code execution within the pod -| Source | Destination | MITRE | -| --------------------------------------------- | ------------------------- | -------------------------------------------------------------------- | -| [PermissionSet](../entities/permissionset.md) | [Pod](../entities/pod.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------- | +| [PermissionSet](../entities/permissionset.md) | [Pod](../entities/pod.md) | [Container Administration Command, T1609](https://attack.mitre.org/techniques/T1609/) | ## Details @@ -24,7 +24,7 @@ The `kubectl patch` command enables updating specific fields of a resource, incl + `spec.initContainers[*].image` + `spec.activeDeadlineSeconds` + `spec.tolerations` (only additions to existing tolerations) - + `spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative) ++ `spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative) However, this is still just enough to allow an attacker to achieve execution in a pod by modifying the container image of a running pod to a backdoored container image in an accessible container registry. diff --git a/docs/reference/attacks/ROLE_BIND.md b/docs/reference/attacks/ROLE_BIND.md index e78e32cbe..b7ca50e36 100644 --- a/docs/reference/attacks/ROLE_BIND.md +++ b/docs/reference/attacks/ROLE_BIND.md @@ -7,16 +7,20 @@ id: ROLE_BIND name: "Create role binding" mitreAttackTechnique: T1078 - Valid Accounts mitreAttackTactic: TA0004 - Privilege Escalation +coverage: Partial --> - # ROLE_BIND A role that grants permission to create or modify `(Cluster)RoleBindings` can allow an attacker to escalate privileges on a compromised user. -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| -| [PermissionSet](../entities/permissionset.md) | [PermissionSet](../entities/permissionset.md) | [Valid Accounts, T1078](https://attack.mitre.org/techniques/T1078/) | +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------- | +| [PermissionSet](../entities/permissionset.md) | [PermissionSet](../entities/permissionset.md) | [Valid Accounts, T1078](https://attack.mitre.org/techniques/T1078/) | + +!!! warning + + This attack has __LIMITATIONS__ in the current implementation. Consult the [RBAC](#rbac) section for more details. ## Details @@ -62,12 +66,12 @@ But, the PermissionSet object is created only if a role is linked by a rolebindi So some of the usecases are not fully covered: -| Usecase #| Coverage | Limitation description| -|------|-------|---------| -| 1 | Full | N/A | -| 2 | Limited | All the PermissionSet that are not namespaced are linked to a single specific namespace. Yet, this attack allow to bind a role to any namespace. Therefore, we would need to create additional PermissionSet for every namespace if we want to fully cover the attack| -| 3 | Full | N/A | -| 4 | None | To cover this usecase, we need duplicate a non-namespaced PermissionSet to a namespace one. | +| Usecase # | Coverage | Limitation description | +| --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | Full | N/A | +| 2 | Limited | All the PermissionSet that are not namespaced are linked to a single specific namespace. Yet, this attack allow to bind a role to any namespace. Therefore, we would need to create additional PermissionSet for every namespace if we want to fully cover the attack | +| 3 | Full | N/A | +| 4 | None | To cover this usecase, we need duplicate a non-namespaced PermissionSet to a namespace one. | ### Limitation of the can-i Kubernetes API diff --git a/docs/reference/attacks/SHARE_PS_NAMESPACE.md b/docs/reference/attacks/SHARE_PS_NAMESPACE.md index 4e27e89a3..ef109663f 100644 --- a/docs/reference/attacks/SHARE_PS_NAMESPACE.md +++ b/docs/reference/attacks/SHARE_PS_NAMESPACE.md @@ -5,15 +5,15 @@ title: SHARE_PS_NAMESPACE # SHARE_PS_NAMESPACE -| Source | Destination | MITRE | -| --------------------------- | ------------------------------------- |----------------------------------| -| [Container](../entities/container.md) | [Container](../entities/container.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------- | +| [Container](../entities/container.md) | [Container](../entities/container.md) | [Taint Shared Content, T1080](https://attack.mitre.org/techniques/T1080/) | Represents a relationship between containers within the same pod that share a process namespace. diff --git a/docs/reference/attacks/TOKEN_BRUTEFORCE.md b/docs/reference/attacks/TOKEN_BRUTEFORCE.md index f4c6b4020..5d235ab39 100644 --- a/docs/reference/attacks/TOKEN_BRUTEFORCE.md +++ b/docs/reference/attacks/TOKEN_BRUTEFORCE.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0006 - Credential Access # TOKEN_BRUTEFORCE -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------- | | [PermissionSet](../entities/permissionset.md) | [Identity](../entities/identity.md) | [Steal Application Access Token, T1528](https://attack.mitre.org/techniques/T1528/) | An identity with a role that allows *get* on secrets (vs list) can potentially view all the serviceaccount tokens in a specific namespace or in the whole cluster (with ClusterRole). diff --git a/docs/reference/attacks/TOKEN_LIST.md b/docs/reference/attacks/TOKEN_LIST.md index eb89d8d39..1ef3b4a7a 100644 --- a/docs/reference/attacks/TOKEN_LIST.md +++ b/docs/reference/attacks/TOKEN_LIST.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0006 - Credential Access # TOKEN_LIST -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------- | | [PermissionSet](../entities/permissionset.md) | [Identity](../entities/identity.md) | [Steal Application Access Token, T1528](https://attack.mitre.org/techniques/T1528/) | An identity with a role that allows listing secrets can potentially view all the secrets in a specific namespace or in the whole cluster (with ClusterRole). @@ -62,4 +62,4 @@ Listing secrets is a very powerful privilege and should not be required by the m + [Official Kubernetes documentation: Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#working-with-secrets) + [Securing Kubernetes Clusters by Eliminating Risky Permissions](https://www.cyberark.com/resources/threat-research-blog/securing-kubernetes-clusters-by-eliminating-risky-permissions) -+ [Official Kubernetes documentation: List Secret Risks](https://kubernetes.io/docs/concepts/security/rbac-good-practices/#listing-secrets) \ No newline at end of file ++ [Official Kubernetes documentation: List Secret Risks](https://kubernetes.io/docs/concepts/security/rbac-good-practices/#listing-secrets) diff --git a/docs/reference/attacks/TOKEN_STEAL.md b/docs/reference/attacks/TOKEN_STEAL.md index 576fcc2c6..61b016e0e 100644 --- a/docs/reference/attacks/TOKEN_STEAL.md +++ b/docs/reference/attacks/TOKEN_STEAL.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0006 - Credential Access # TOKEN_STEAL -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------- | ----------------------------------- | -------------------------------------------------------------------------- | | [Volume](../entities/volume.md) | [Identity](../entities/identity.md) | [Unsecured Credentials, T1552](https://attack.mitre.org/techniques/T1552/) | This attack represents the ability to steal a K8s API token from an accessible volume. @@ -112,4 +112,3 @@ automountServiceAccountToken: false + [The Path Less Traveled: Abusing Kubernetes Defaults (Video)](https://www.youtube.com/watch?v=HmoVSmTIOxM) + [The Path Less Traveled: Abusing Kubernetes Defaults (GitHub)](https://github.com/mauilion/blackhat-2019) + [Securing Kubernetes Clusters by Eliminating Risky Permissions](https://www.cyberark.com/resources/threat-research-blog/securing-kubernetes-clusters-by-eliminating-risky-permissions) - diff --git a/docs/reference/attacks/VOLUME_ACCESS.md b/docs/reference/attacks/VOLUME_ACCESS.md index faa039fb2..474b31f88 100644 --- a/docs/reference/attacks/VOLUME_ACCESS.md +++ b/docs/reference/attacks/VOLUME_ACCESS.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0007 - Discovery # VOLUME_ACCESS -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| -------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------- | | [Node](../entities/container.md) | [Volume](../entities/volume.md) | [Container and Resource Discovery, T1613](https://attack.mitre.org/techniques/T1613/) | Represents an attacker with access to a node filesystem gaining access to any volumes mounted inside a container (by definition). diff --git a/docs/reference/attacks/VOLUME_DISCOVER.md b/docs/reference/attacks/VOLUME_DISCOVER.md index 921ca76bf..1a2abe223 100644 --- a/docs/reference/attacks/VOLUME_DISCOVER.md +++ b/docs/reference/attacks/VOLUME_DISCOVER.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0007 - Discovery # VOLUME_DISCOVER -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------- | | [Container](../entities/container.md) | [Volume](../entities/volume.md) | [Container and Resource Discovery, T1613](https://attack.mitre.org/techniques/T1613/) | Represents an attacker within a container discovering a mounted volume. @@ -102,4 +102,3 @@ None ## References: + [Official Kubernetes documentation: Volumes ](https://kubernetes.io/docs/concepts/storage/volumes/) - diff --git a/docs/reference/attacks/gen-index.py b/docs/reference/attacks/gen-index.py index c7fbc73d2..5fee738e4 100644 --- a/docs/reference/attacks/gen-index.py +++ b/docs/reference/attacks/gen-index.py @@ -12,8 +12,14 @@ # Attack Reference -| ID | Name | MITRE ATT&CK Technique | MITRE ATT&CK Tactic | -| :----: | :--: | :-----------------: | :--------------------: | +All edges in the KubeHound graph represent attacks with a net "improvement" in an attacker's position or a lateral movement opportunity. + +!!! note + + For instance, an assume role or ([IDENTITY_ASSUME](./IDENTITY_ASSUME.md)) is considered as an attack. + +| ID | Name | MITRE ATT&CK Technique | MITRE ATT&CK Tactic | Coverage | +| :----: | :--: | :-----------------: | :--------------------: | :------: | """ for file in sorted(glob.glob('*.md')): @@ -26,9 +32,20 @@ if startIndex >= 0: print("Parsing", file) docsConfig = yaml.safe_load(contents[startIndex+len(COMMENT_PREFIX):contents.find(COMMENT_SUFFIX)]) - attackTacticId, attackTacticName = docsConfig["mitreAttackTactic"].split(' - ') - attackTechniqueId, attackTechniqueName = docsConfig["mitreAttackTechnique"].split(' - ') - table += f'| [{docsConfig["id"]}](./{file}) | {docsConfig["name"]} | {attackTechniqueName} | { attackTacticName} | \n' + # Extract and format MITRE ATT&CK Tactic + attackTacticName = "N/A" + if docsConfig["mitreAttackTactic"] != "N/A": + attackTacticId, attackTacticName = docsConfig["mitreAttackTactic"].split(' - ') + # Extract and format MITRE ATT&CK Technique + attackTechniqueName = "N/A" + if docsConfig["mitreAttackTechnique"] != "N/A": + attackTechniqueId, attackTechniqueName = docsConfig["mitreAttackTechnique"].split(' - ') + # Extract coverage + coverage = "Full" + if "coverage" in docsConfig: + coverage = docsConfig["coverage"] + # Generate table row + table += f'| [{docsConfig["id"]}](./{file}) | {docsConfig["name"]} | {attackTechniqueName} | { attackTacticName} | {coverage} |\n' else: print(f"WARNING: {file} does not have a docs config") diff --git a/docs/reference/attacks/index.md b/docs/reference/attacks/index.md index 628f249d6..fea2afa15 100644 --- a/docs/reference/attacks/index.md +++ b/docs/reference/attacks/index.md @@ -11,31 +11,31 @@ All edges in the KubeHound graph represent attacks with a net "improvement" in a For instance, an assume role or ([IDENTITY_ASSUME](./IDENTITY_ASSUME.md)) is considered as an attack. -| ID | Name | MITRE ATT&CK Technique | MITRE ATT&CK Tactic | Coverage | -| :-----------------------------------------------------: | :--------------------------------------------------------: | :---------------------------------------------: | :------------------: | :------: | -| [CE_MODULE_LOAD](./CE_MODULE_LOAD.md) | Container escape: Load kernel module | Escape to host | Privilege escalation | Full | -| [CE_NSENTER](./CE_NSENTER.md) | Container escape: nsenter | Escape to host | Privilege escalation | Full | -| [CE_PRIV_MOUNT](./CE_PRIV_MOUNT.md) | Container escape: Mount host filesystem | Escape to host | Privilege escalation | Full | -| [CE_SYS_PTRACE](./CE_SYS_PTRACE.md) | Container escape: Attach to host process via SYS_PTRACE | Escape to host | Privilege escalation | Full | -| [CE_UMH_CORE_PATTERN](./CE_UMH_CORE_PATTERN.md) | Container escape: through core_pattern usermode_helper | Escape to host | Privilege escalation | None | -| [CE_VAR_LOG_SYMLINK](./CE_VAR_LOG_SYMLINK.md) | Read file from sensitive host mount | Escape to host | Privilege escalation | Full | -| [CONTAINER_ATTACH](./CONTAINER_ATTACH.md) | Attach to running container | N/A | Lateral Movement | Full | -| [ENDPOINT_EXPLOIT](./ENDPOINT_EXPLOIT.md) | Exploit exposed endpoint | Exploitation of Remote Services | Lateral Movement | Full | -| [EXPLOIT_CONTAINERD_SOCK](./EXPLOIT_CONTAINERD_SOCK.md) | Container escape: Through mounted container runtime socket | N/A | Lateral Movement | None | -| [EXPLOIT_HOST_READ](./EXPLOIT_HOST_READ.md) | Read file from sensitive host mount | Escape to host | Privilege escalation | Full | -| [EXPLOIT_HOST_TRAVERSE](./EXPLOIT_HOST_TRAVERSE.md) | Steal service account token through kubelet host mount | Unsecured Credentials | Credential Access | Full | -| [EXPLOIT_HOST_WRITE](./EXPLOIT_HOST_WRITE.md) | Container escape: Write to sensitive host mount | Escape to host | Privilege escalation | Full | -| [IDENTITY_ASSUME](./IDENTITY_ASSUME.md) | Act as identity | Valid Accounts | Privilege escalation | Full | -| [IDENTITY_IMPERSONATE](./IDENTITY_IMPERSONATE.md) | Impersonate user/group | Valid Accounts | Privilege escalation | Full | -| [PERMISSION_DISCOVER](./PERMISSION_DISCOVER.md) | Enumerate permissions | Permission Groups Discovery | Discovery | Full | -| [POD_ATTACH](./POD_ATTACH.md) | Attach to running pod | N/A | Lateral Movement | Full | -| [POD_CREATE](./POD_CREATE.md) | Create privileged pod | Scheduled Task/Job: Container Orchestration Job | Privilege escalation | Full | -| [POD_EXEC](./POD_EXEC.md) | Exec into running pod | N/A | Lateral Movement | Full | -| [POD_PATCH](./POD_PATCH.md) | Patch running pod | N/A | Lateral Movement | Full | -| [ROLE_BIND](./ROLE_BIND.md) | Create role binding | Valid Accounts | Privilege Escalation | Partial | -| [SHARE_PS_NAMESPACE](./SHARE_PS_NAMESPACE.md) | Access container in shared process namespace | N/A | Lateral Movement | Full | -| [TOKEN_BRUTEFORCE](./TOKEN_BRUTEFORCE.md) | Brute-force secret name of service account token | Steal Application Access Token | Credential Access | Full | -| [TOKEN_LIST](./TOKEN_LIST.md) | Access service account token secrets | Steal Application Access Token | Credential Access | Full | -| [TOKEN_STEAL](./TOKEN_STEAL.md) | Steal service account token from volume | Unsecured Credentials | Credential Access | Full | -| [VOLUME_ACCESS](./VOLUME_ACCESS.md) | Access host volume | Container and Resource Discovery | Discovery | Full | -| [VOLUME_DISCOVER](./VOLUME_DISCOVER.md) | Enumerate mounted volumes | Container and Resource Discovery | Discovery | Full | +| ID | Name | MITRE ATT&CK Technique | MITRE ATT&CK Tactic | Coverage | +| :-----------------------------------------------------: | :--------------------------------------------------------: | :------------------------------: | :------------------: | :------: | +| [CE_MODULE_LOAD](./CE_MODULE_LOAD.md) | Container escape: Load kernel module | Escape to host | Privilege escalation | Full | +| [CE_NSENTER](./CE_NSENTER.md) | Container escape: nsenter | Escape to host | Privilege escalation | Full | +| [CE_PRIV_MOUNT](./CE_PRIV_MOUNT.md) | Container escape: Mount host filesystem | Escape to host | Privilege escalation | Full | +| [CE_SYS_PTRACE](./CE_SYS_PTRACE.md) | Container escape: Attach to host process via SYS_PTRACE | Escape to host | Privilege escalation | Full | +| [CE_UMH_CORE_PATTERN](./CE_UMH_CORE_PATTERN.md) | Container escape: through core_pattern usermode_helper | Escape to host | Privilege escalation | Full | +| [CE_VAR_LOG_SYMLINK](./CE_VAR_LOG_SYMLINK.md) | Arbitrary file reads on the host | Escape to host | Privilege escalation | Full | +| [CONTAINER_ATTACH](./CONTAINER_ATTACH.md) | Attach to running container | Container Administration Command | Execution | Full | +| [ENDPOINT_EXPLOIT](./ENDPOINT_EXPLOIT.md) | Exploit exposed endpoint | Exploitation of Remote Services | Lateral Movement | Full | +| [EXPLOIT_CONTAINERD_SOCK](./EXPLOIT_CONTAINERD_SOCK.md) | Container escape: Through mounted container runtime socket | Deploy Container | Execution | None | +| [EXPLOIT_HOST_READ](./EXPLOIT_HOST_READ.md) | Read file from sensitive host mount | Escape to host | Privilege escalation | Full | +| [EXPLOIT_HOST_TRAVERSE](./EXPLOIT_HOST_TRAVERSE.md) | Steal service account token through kubelet host mount | Unsecured Credentials | Credential Access | Full | +| [EXPLOIT_HOST_WRITE](./EXPLOIT_HOST_WRITE.md) | Container escape: Write to sensitive host mount | Escape to host | Privilege escalation | Full | +| [IDENTITY_ASSUME](./IDENTITY_ASSUME.md) | Act as identity | Valid Accounts | Privilege escalation | Full | +| [IDENTITY_IMPERSONATE](./IDENTITY_IMPERSONATE.md) | Impersonate user/group | Valid Accounts | Privilege escalation | None | +| [PERMISSION_DISCOVER](./PERMISSION_DISCOVER.md) | Enumerate permissions | Permission Groups Discovery | Discovery | Full | +| [POD_ATTACH](./POD_ATTACH.md) | Attach to running pod | Container Administration Command | Execution | Full | +| [POD_CREATE](./POD_CREATE.md) | Create privileged pod | Deploy Container | Execution | Full | +| [POD_EXEC](./POD_EXEC.md) | Exec into running pod | Container Administration Command | Execution | Full | +| [POD_PATCH](./POD_PATCH.md) | Patch running pod | Container Administration Command | Execution | Full | +| [ROLE_BIND](./ROLE_BIND.md) | Create role binding | Valid Accounts | Privilege Escalation | Partial | +| [SHARE_PS_NAMESPACE](./SHARE_PS_NAMESPACE.md) | Access container in shared process namespace | Taint Shared Content | Lateral Movement | Full | +| [TOKEN_BRUTEFORCE](./TOKEN_BRUTEFORCE.md) | Brute-force secret name of service account token | Steal Application Access Token | Credential Access | Full | +| [TOKEN_LIST](./TOKEN_LIST.md) | Access service account token secrets | Steal Application Access Token | Credential Access | Full | +| [TOKEN_STEAL](./TOKEN_STEAL.md) | Steal service account token from volume | Unsecured Credentials | Credential Access | Full | +| [VOLUME_ACCESS](./VOLUME_ACCESS.md) | Access host volume | Container and Resource Discovery | Discovery | Full | +| [VOLUME_DISCOVER](./VOLUME_DISCOVER.md) | Enumerate mounted volumes | Container and Resource Discovery | Discovery | Full | diff --git a/docs/reference/graph/graph.schema.json b/docs/reference/graph/graph.schema.json index 5e1262efa..b3b32bb34 100644 --- a/docs/reference/graph/graph.schema.json +++ b/docs/reference/graph/graph.schema.json @@ -232,8 +232,8 @@ "type": { "type": "string", "enum": [ - "ATTCK", - "URL" + "ATTCK Technique", + "ATTCK Tactic" ] }, "id": { diff --git a/docs/reference/graph/graph.yaml b/docs/reference/graph/graph.yaml index a49cf61a8..4d3abda3e 100644 --- a/docs/reference/graph/graph.yaml +++ b/docs/reference/graph/graph.yaml @@ -413,179 +413,257 @@ spec: - label: CE_MODULE_LOAD description: A container can load a kernel module on the node. references: - - type: ATTCK + - type: ATTCK Technique id: T1611 label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation - label: CE_NSENTER description: >- Container escape via the nsenter built-in linux program that allows executing a binary into another namespace. - references: - - type: ATTCK + references: + - type: ATTCK Technique id: T1611 label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation - label: CE_PRIV_MOUNT description: >- Mount the host disk and gain access to the host via arbitrary filesystem - write + write. references: - - type: ATTCK + - type: ATTCK Technique id: T1611 label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation - label: CE_SYS_TRACE description: >- Given the requisite capabilities, abuse the legitimate OS debugging mechanisms to escape the container via attaching to a node process. references: - - type: ATTCK + - type: ATTCK Technique id: T1611 label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation - label: CE_UMH_CORE_PATTERN description: >- Abuse the User Mode Helper (UMH) mechanism to execute arbitrary code in the host. references: - - type: ATTCK + - type: ATTCK Technique id: T1611 label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation - label: CE_VAR_LOG_SYMLINK description: Abuse the /var/log symlink to gain access to the host filesystem. references: - - type: ATTCK - id: T1611 - label: Escape to Host - - label: EXPLOIT_HOST_READ - description: Read sensitive files on the host. - references: - - type: ATTCK - id: T1611 - label: Escape to Host - - label: EXPLOIT_HOST_WRITE - description: Write sensitive files on the host. - references: - - type: ATTCK + - type: ATTCK Technique id: T1611 label: Escape to Host - - label: EXPLOIT_CONTAINERD_SOCK - description: Exploit the containerd socket to gain access to the host. - references: - - type: ATTCK - id: TA0008 - label: Lateral Movement - - label: IDENTITY_ASSUME - description: >- - Represents the capacity to act as an Identity via ownership of a service - account token, user PKI certificate, etc. - references: - - type: ATTCK - id: T1078 - label: Valid Accounts + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation - label: CONTAINER_ATTACH description: >- Attach to a running container to execute commands or inspect the container. references: - - type: ATTCK - id: TA0008 - label: Lateral Movement + - type: ATTCK Technique + id: T1609 + label: Container Administration Command + - type: ATTCK Tactic + id: TA0002 + label: Execution - label: ENDPOINT_EXPLOIT description: >- Represents a network endpoint exposed by a container that could be exploited by an attacker (via means known or unknown). This can correspond to a Kubernetes service, node service, node port, or container port. references: - - type: ATTCK + - type: ATTCK Technique id: T1210 label: Exploitation of Remote Services - - label: PERMISSION_DISCOVER - description: Discover permissions granted to an identity. + - type: ATTCK Tactic + id: TA0008 + label: Lateral Movement + - label: EXPLOIT_CONTAINERD_SOCK + description: Exploit the containerd socket to gain access to the host. references: - - type: ATTCK - id: T1069 - label: Permission Groups Discovery + - type: ATTCK Technique + id: T1610 + label: Deplay Container + - type: ATTCK Tactic + id: TA0002 + label: Execution + - label: EXPLOIT_HOST_READ + description: Read sensitive files on the host. + references: + - type: ATTCK Technique + id: T1611 + label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation - label: EXPLOIT_HOST_TRAVERSE description: >- This attack represents the ability to steal a K8s API token from a container via access to a mounted parent volume of the /var/lib/kubelet/pods directory. references: - - type: ATTCK + - type: ATTCK Technique id: T1552 label: Unsecured Credentials - - label: TOKEN_STEAL - description: >- - This attack represents the ability to steal a K8s API token from an - accessible volume. + - type: ATTCK Tactic + id: TA0006 + label: Credentials Access + - label: EXPLOIT_HOST_WRITE + description: Write sensitive files on the host. references: - - type: ATTCK - id: T1552 - label: Unsecured Credentials - - label: ROLE_BIND - description: Bind a role to an identity. + - type: ATTCK Technique + id: T1611 + label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: IDENTITY_ASSUME + description: >- + Represents the capacity to act as an Identity via ownership of a service + account token, user PKI certificate, etc. references: - - type: ATTCK + - type: ATTCK Technique id: T1078 label: Valid Accounts + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation - label: IDENTITY_IMPERSONATE description: Impersonate an identity. references: - - type: ATTCK + - type: ATTCK Technique id: T1078 label: Valid Accounts + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: PERMISSION_DISCOVER + description: Discover permissions granted to an identity. + references: + - type: ATTCK Technique + id: T1069 + label: Permission Groups Discovery + - type: ATTCK Tactic + id: TA0007 + label: Discovery - label: POD_ATTACH description: Attach to a running pod to execute commands or inspect the pod. references: - - type: ATTCK - id: TA0008 - label: Lateral Movement + - type: ATTCK Technique + id: T1609 + label: Container Administration Command + - type: ATTCK Tactic + id: TA0002 + label: Execution - label: POD_CREATE description: Create a pod on a node. references: - - type: ATTCK - id: TA0008 - label: Lateral Movement + - type: ATTCK Technique + id: T1610 + label: Deploy Container + - type: ATTCK Tactic + id: TA0002 + label: Execution - label: POD_EXEC description: Execute a command in a pod. references: - - type: ATTCK - id: TA0008 - label: Lateral Movement + - type: ATTCK Technique + id: T1609 + label: Container Administration Command + - type: ATTCK Tactic + id: TA0002 + label: Execution - label: POD_PATCH description: Patch a pod on a node. references: - - type: ATTCK - id: TA0008 - label: Lateral Movement + - type: ATTCK Technique + id: T1609 + label: Container Administration Command + - type: ATTCK Tactic + id: TA0002 + label: Execution + - label: ROLE_BIND + description: Bind a role to an identity. + references: + - type: ATTCK Technique + id: T1078 + label: Valid Accounts + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation - label: SHARE_PS_NAMESPACE description: All containers in a pod share the same process namespace. references: - - type: ATTCK + - type: ATTCK Technique + id: T1080 + label: Taint Shared Content + - type: ATTCK Tactic id: TA0008 label: Lateral Movement - label: TOKEN_BRUTEFORCE description: Bruteforce a token. references: - - type: ATTCK + - type: ATTCK Technique id: T1528 label: Steal Application Access Token + - type: ATTCK Tactic + id: TA0006 + label: Credentials Access - label: TOKEN_LIST description: List tokens. references: - - type: ATTCK + - type: ATTCK Technique id: T1528 label: Steal Application Access Token + - type: ATTCK Tactic + id: TA0006 + label: Credentials Access + - label: TOKEN_STEAL + description: >- + This attack represents the ability to steal a K8s API token from an + accessible volume. + references: + - type: ATTCK Technique + id: T1552 + label: Unsecured Credentials + - type: ATTCK Tactic + id: TA0006 + label: Credentials Access - label: VOLUME_ACCESS description: Access a volume mounted in a container. references: - - type: ATTCK + - type: ATTCK Technique id: T1613 label: Container and Resource Discovery + - type: ATTCK Tactic + id: TA0007 + label: Discovery - label: VOLUME_DISCOVER description: Discover volumes mounted in a container. references: - - type: ATTCK + - type: ATTCK Technique id: T1613 label: Container and Resource Discovery + - type: ATTCK Tactic + id: TA0007 + label: Discovery # Define the properties for each edge in the graph. edgeProperties: [] diff --git a/pkg/kubehound/graph/adapter/gremlin.go b/pkg/kubehound/graph/adapter/gremlin.go index 6cbd386d1..e0e8fa618 100644 --- a/pkg/kubehound/graph/adapter/gremlin.go +++ b/pkg/kubehound/graph/adapter/gremlin.go @@ -48,7 +48,7 @@ func structToMap(in any) (map[string]any, error) { // GremlinEdgeProcessor transforms the inputs into a map suitable for bulk edge insert using the MergeE API. func GremlinEdgeProcessor(ctx context.Context, oic *converter.ObjectIDConverter, label string, - out primitive.ObjectID, in primitive.ObjectID) (map[any]any, error) { + out primitive.ObjectID, in primitive.ObjectID, attributes map[string]any) (map[any]any, error) { vidIn, err := oic.GraphID(ctx, in.Hex()) if err != nil { @@ -66,6 +66,18 @@ func GremlinEdgeProcessor(ctx context.Context, oic *converter.ObjectIDConverter, gremlin.Direction.Out: vidOut, } + // Add any additional attributes to the edge. + for k, v := range attributes { + switch k { + case string(gremlin.T.Label), string(gremlin.T.Id), string(gremlin.Direction.In), string(gremlin.Direction.Out): + // Skip reserved keys. + continue + } + + // Add the attribute to the edge. + processed[k] = v + } + return processed, nil } diff --git a/pkg/kubehound/graph/edge/attck.go b/pkg/kubehound/graph/edge/attck.go new file mode 100644 index 000000000..721b33cbd --- /dev/null +++ b/pkg/kubehound/graph/edge/attck.go @@ -0,0 +1,51 @@ +package edge + +// AttckTacticID is the interface for the ATT&CK tactic ID. +type AttckTacticID string + +var ( + // AttckTacticUndefined is the undefined ATT&CK tactic. + AttckTacticUndefined AttckTacticID + // AttckTacticInitialAccess is the ATT&CK tactic for initial access (TA0001). + AttckTacticInitialAccess AttckTacticID = "TA0001" + // AttckTacticExecution is the ATT&CK tactic for execution (TA0002). + AttckTacticExecution AttckTacticID = "TA0002" + // AttckTacticPersistence is the ATT&CK tactic for persistence (TA0003). + AttckTacticPersistence AttckTacticID = "TA0003" + // AttckTacticPrivilegeEscalation is the ATT&CK tactic for privilege escalation (TA0004). + AttckTacticPrivilegeEscalation AttckTacticID = "TA0004" + // AttckTacticCredentialAccess is the ATT&CK tactic for credential access (TA0006). + AttckTacticCredentialAccess AttckTacticID = "TA0006" + // AttckTacticDiscovery is the ATT&CK tactic for discovery (TA0007). + AttckTacticDiscovery AttckTacticID = "TA0007" + // AttckTacticLateralMovement is the ATT&CK tactic for lateral movement (TA0008). + AttckTacticLateralMovement AttckTacticID = "TA0008" +) + +// AttckTechniqueID is the interface for the ATT&CK technique ID. +type AttckTechniqueID string + +var ( + // AttckTechniqueUndefined is the undefined ATT&CK technique. + AttckTechniqueUndefined AttckTechniqueID + // AttckTechniquePermissionGroupsDiscovery is the ATT&CK technique for permission groups discovery (T1069). + AttckTechniquePermissionGroupsDiscovery AttckTechniqueID = "T1069" + // AttckTechniqueValidAccounts is the ATT&CK technique for valid accounts (T1078). + AttckTechniqueValidAccounts AttckTechniqueID = "T1078" + // AttckTechniqueTaintedSharedContent is the ATT&CK technique for tainted shared content (T1080). + AttckTechniqueTaintedSharedContent AttckTechniqueID = "T1080" + // AttckTechniqueExploitationOfRemoteServices is the ATT&CK technique for exploitation of remote services (T1210). + AttckTechniqueExploitationOfRemoteServices AttckTechniqueID = "T1210" + // AttckTechniqueStealApplicationAccessTokens is the ATT&CK technique for stealing application access tokens (T1528). + AttckTechniqueStealApplicationAccessTokens AttckTechniqueID = "T1528" + // AttckTechniqueUnsecuredCredentials is the ATT&CK technique for unsecured credentials (T1552). + AttckTechniqueUnsecuredCredentials AttckTechniqueID = "T1552" + // AttckTechniqueContainerAdministrationCommand is the ATT&CK technique for container administration command (T1609). + AttckTechniqueContainerAdministrationCommand AttckTechniqueID = "T1609" + // AttckTechniqueDeployContainer is the ATT&CK technique for deploying a container (T1610). + AttckTechniqueDeployContainer AttckTechniqueID = "T1610" + // AttckTechniqueEscapeToHost is the ATT&CK technique for escaping to the host (T1611). + AttckTechniqueEscapeToHost AttckTechniqueID = "T1611" + // AttckTechniqueContainerAndResourceDiscovery is the ATT&CK technique for container and resource discovery (T1613). + AttckTechniqueContainerAndResourceDiscovery AttckTechniqueID = "T1613" +) diff --git a/pkg/kubehound/graph/edge/base_container_escape.go b/pkg/kubehound/graph/edge/base_container_escape.go index c2f931148..efab4881d 100644 --- a/pkg/kubehound/graph/edge/base_container_escape.go +++ b/pkg/kubehound/graph/edge/base_container_escape.go @@ -19,13 +19,13 @@ type containerEscapeGroup struct { Container primitive.ObjectID `bson:"_id" json:"container"` } -func containerEscapeProcessor(ctx context.Context, oic *converter.ObjectIDConverter, edgeLabel string, entry any) (any, error) { +func containerEscapeProcessor(ctx context.Context, oic *converter.ObjectIDConverter, edgeLabel string, entry any, attributes map[string]any) (any, error) { typed, ok := entry.(*containerEscapeGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, edgeLabel, typed.Container, typed.Node) + return adapter.GremlinEdgeProcessor(ctx, oic, edgeLabel, typed.Container, typed.Node, attributes) } func (e *BaseContainerEscape) Traversal() types.EdgeTraversal { return adapter.DefaultEdgeTraversal() diff --git a/pkg/kubehound/graph/edge/builder.go b/pkg/kubehound/graph/edge/builder.go index c8dedd1ba..e55ec6d8c 100644 --- a/pkg/kubehound/graph/edge/builder.go +++ b/pkg/kubehound/graph/edge/builder.go @@ -28,6 +28,12 @@ type Builder interface { // Label returns the label for the edge (convention is all uppercase i.e EDGE_NAME). Label() string + // AttckTechniqueID returns the ATT&CK technique ID for the edge. + AttckTechniqueID() AttckTechniqueID + + // AttckTacticID returns the ATT&CK tactic ID for the edge. + AttckTacticID() AttckTacticID + // BatchSize returns the batch size of bulk inserts (and threshold for triggering a flush). BatchSize() int diff --git a/pkg/kubehound/graph/edge/container_attach.go b/pkg/kubehound/graph/edge/container_attach.go index dbce309cf..10c0685b4 100644 --- a/pkg/kubehound/graph/edge/container_attach.go +++ b/pkg/kubehound/graph/edge/container_attach.go @@ -36,13 +36,24 @@ func (e *ContainerAttach) Name() string { return "ContainerAttach" } +func (e *ContainerAttach) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *ContainerAttach) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *ContainerAttach) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*containerAttachGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Pod, typed.Container) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Pod, typed.Container, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *ContainerAttach) Traversal() types.EdgeTraversal { diff --git a/pkg/kubehound/graph/edge/endpoint_exploit_external.go b/pkg/kubehound/graph/edge/endpoint_exploit_external.go index 3726fdd00..5560c0185 100644 --- a/pkg/kubehound/graph/edge/endpoint_exploit_external.go +++ b/pkg/kubehound/graph/edge/endpoint_exploit_external.go @@ -35,13 +35,24 @@ func (e *EndpointExploitExternal) Name() string { return "EndpointExploitExternal" } +func (e *EndpointExploitExternal) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueExploitationOfRemoteServices +} + +func (e *EndpointExploitExternal) AttckTacticID() AttckTacticID { + return AttckTacticLateralMovement +} + func (e *EndpointExploitExternal) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*sliceEndpointGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Endpoint, typed.Container) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Endpoint, typed.Container, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EndpointExploitExternal) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/endpoint_exploit_internal.go b/pkg/kubehound/graph/edge/endpoint_exploit_internal.go index 9b4e4ad5e..eca0c98d2 100644 --- a/pkg/kubehound/graph/edge/endpoint_exploit_internal.go +++ b/pkg/kubehound/graph/edge/endpoint_exploit_internal.go @@ -36,13 +36,24 @@ func (e *EndpointExploitInternal) Name() string { return "EndpointExploitInternal" } +func (e *EndpointExploitInternal) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueExploitationOfRemoteServices +} + +func (e *EndpointExploitInternal) AttckTacticID() AttckTacticID { + return AttckTacticLateralMovement +} + func (e *EndpointExploitInternal) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*containerEndpointGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Endpoint, typed.Container) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Endpoint, typed.Container, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EndpointExploitInternal) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/escape_module_load.go b/pkg/kubehound/graph/edge/escape_module_load.go index 0b8a7192a..9c4b4abb1 100644 --- a/pkg/kubehound/graph/edge/escape_module_load.go +++ b/pkg/kubehound/graph/edge/escape_module_load.go @@ -29,9 +29,20 @@ func (e *EscapeModuleLoad) Name() string { return "ContainerEscapeModuleLoad" } +func (e *EscapeModuleLoad) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *EscapeModuleLoad) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + // Processor delegates the processing tasks to the generic containerEscapeProcessor. func (e *EscapeModuleLoad) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { - return containerEscapeProcessor(ctx, oic, e.Label(), entry) + return containerEscapeProcessor(ctx, oic, e.Label(), entry, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EscapeModuleLoad) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/escape_nsenter.go b/pkg/kubehound/graph/edge/escape_nsenter.go index 4fd90820c..27bc7ce16 100644 --- a/pkg/kubehound/graph/edge/escape_nsenter.go +++ b/pkg/kubehound/graph/edge/escape_nsenter.go @@ -29,9 +29,20 @@ func (e *EscapeNsenter) Name() string { return "ContainerEscapeNsenter" } +func (e *EscapeNsenter) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *EscapeNsenter) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + // Processor delegates the processing tasks to the generic containerEscapeProcessor. func (e *EscapeNsenter) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { - return containerEscapeProcessor(ctx, oic, e.Label(), entry) + return containerEscapeProcessor(ctx, oic, e.Label(), entry, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EscapeNsenter) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/escape_priv_mount.go b/pkg/kubehound/graph/edge/escape_priv_mount.go index 3f9ac9179..0fa4f62b0 100644 --- a/pkg/kubehound/graph/edge/escape_priv_mount.go +++ b/pkg/kubehound/graph/edge/escape_priv_mount.go @@ -29,9 +29,20 @@ func (e *EscapePrivMount) Name() string { return "ContainerEscapePrivilegedMount" } +func (e *EscapePrivMount) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *EscapePrivMount) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + // Processor delegates the processing tasks to the generic containerEscapeProcessor. func (e *EscapePrivMount) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { - return containerEscapeProcessor(ctx, oic, e.Label(), entry) + return containerEscapeProcessor(ctx, oic, e.Label(), entry, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EscapePrivMount) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/escape_sys_ptrace.go b/pkg/kubehound/graph/edge/escape_sys_ptrace.go index cbbe58dc1..97cd6734b 100644 --- a/pkg/kubehound/graph/edge/escape_sys_ptrace.go +++ b/pkg/kubehound/graph/edge/escape_sys_ptrace.go @@ -29,9 +29,20 @@ func (e *EscapeSysPtrace) Name() string { return "ContainerEscapeSysPtrace" } +func (e *EscapeSysPtrace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *EscapeSysPtrace) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + // Processor delegates the processing tasks to the generic containerEscapeProcessor. func (e *EscapeSysPtrace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { - return containerEscapeProcessor(ctx, oic, e.Label(), entry) + return containerEscapeProcessor(ctx, oic, e.Label(), entry, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EscapeSysPtrace) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/escape_umh_core_pattern.go b/pkg/kubehound/graph/edge/escape_umh_core_pattern.go index 40ee89093..0cdfb61e1 100644 --- a/pkg/kubehound/graph/edge/escape_umh_core_pattern.go +++ b/pkg/kubehound/graph/edge/escape_umh_core_pattern.go @@ -36,8 +36,19 @@ func (e *EscapeCorePattern) Name() string { return "ContainerEscapeCorePattern" } +func (e *EscapeCorePattern) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *EscapeCorePattern) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *EscapeCorePattern) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { - return containerEscapeProcessor(ctx, oic, e.Label(), entry) + return containerEscapeProcessor(ctx, oic, e.Label(), entry, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EscapeCorePattern) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/escape_var_log_symlink.go b/pkg/kubehound/graph/edge/escape_var_log_symlink.go index da2744c1b..576ac0907 100644 --- a/pkg/kubehound/graph/edge/escape_var_log_symlink.go +++ b/pkg/kubehound/graph/edge/escape_var_log_symlink.go @@ -42,6 +42,14 @@ func (e *EscapeVarLogSymlink) Name() string { return "ContainerEscapeVarLogSymlink" } +func (e *EscapeVarLogSymlink) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueUnsecuredCredentials +} + +func (e *EscapeVarLogSymlink) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *EscapeVarLogSymlink) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*permissionSetIDEscapeGroup) if !ok { @@ -76,6 +84,8 @@ func (e *EscapeVarLogSymlink) Traversal() types.EdgeTraversal { InE("VOLUME_ACCESS").OutV(). Has("class", "Node").As("n"). AddE("CE_VAR_LOG_SYMLINK").From("c").To("n"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) return g @@ -83,8 +93,8 @@ func (e *EscapeVarLogSymlink) Traversal() types.EdgeTraversal { } func (e *EscapeVarLogSymlink) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, - callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - + callback types.ProcessEntryCallback, complete types.CompleteQueryCallback, +) error { permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := []bson.M{ { diff --git a/pkg/kubehound/graph/edge/exploit_host_read.go b/pkg/kubehound/graph/edge/exploit_host_read.go index 2ea9f56e4..3950998d5 100644 --- a/pkg/kubehound/graph/edge/exploit_host_read.go +++ b/pkg/kubehound/graph/edge/exploit_host_read.go @@ -49,13 +49,24 @@ func (e *ExploitHostRead) Name() string { return "ExploitHostRead" } +func (e *ExploitHostRead) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *ExploitHostRead) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *ExploitHostRead) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*exploitHostReadGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Node) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Node, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *ExploitHostRead) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/exploit_host_traverse_token.go b/pkg/kubehound/graph/edge/exploit_host_traverse_token.go index 726fd8cc4..f5c39ee4f 100644 --- a/pkg/kubehound/graph/edge/exploit_host_traverse_token.go +++ b/pkg/kubehound/graph/edge/exploit_host_traverse_token.go @@ -48,13 +48,24 @@ func (e *ExploitHostTraverse) Name() string { return "ExploitHostTraverseToken" } +func (e *ExploitHostTraverse) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueUnsecuredCredentials +} + +func (e *ExploitHostTraverse) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *ExploitHostTraverse) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*exploitTraverseTokenGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Parent, typed.Child) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Parent, typed.Child, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *ExploitHostTraverse) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/exploit_host_write.go b/pkg/kubehound/graph/edge/exploit_host_write.go index 4e66d2f62..369be327b 100644 --- a/pkg/kubehound/graph/edge/exploit_host_write.go +++ b/pkg/kubehound/graph/edge/exploit_host_write.go @@ -61,13 +61,24 @@ func (e *ExploitHostWrite) Name() string { return "ExploitHostWrite" } +func (e *ExploitHostWrite) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *ExploitHostWrite) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *ExploitHostWrite) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*exploitHostWriteGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Node) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Node, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *ExploitHostWrite) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/identity_assume_container.go b/pkg/kubehound/graph/edge/identity_assume_container.go index 0aabf7104..f058ed98d 100644 --- a/pkg/kubehound/graph/edge/identity_assume_container.go +++ b/pkg/kubehound/graph/edge/identity_assume_container.go @@ -36,13 +36,24 @@ func (e *IdentityAssumeContainer) Name() string { return "IdentityAssumeContainer" } +func (e *IdentityAssumeContainer) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueValidAccounts +} + +func (e *IdentityAssumeContainer) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *IdentityAssumeContainer) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*containerIdentityGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Container, typed.Identity) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Container, typed.Identity, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *IdentityAssumeContainer) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/identity_assume_node.go b/pkg/kubehound/graph/edge/identity_assume_node.go index e6ecf96e4..bdf696b4f 100644 --- a/pkg/kubehound/graph/edge/identity_assume_node.go +++ b/pkg/kubehound/graph/edge/identity_assume_node.go @@ -36,13 +36,24 @@ func (e *IdentityAssumeNode) Name() string { return "IdentityAssumeNode" } +func (e *IdentityAssumeNode) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueValidAccounts +} + +func (e *IdentityAssumeNode) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *IdentityAssumeNode) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*nodeIdentityGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Identity) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Identity, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *IdentityAssumeNode) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/permission_discover.go b/pkg/kubehound/graph/edge/permission_discover.go index adf6b3e91..5d209dfe7 100644 --- a/pkg/kubehound/graph/edge/permission_discover.go +++ b/pkg/kubehound/graph/edge/permission_discover.go @@ -35,13 +35,24 @@ func (e *PermissionDiscover) Name() string { return "PermissionDiscover" } +func (e *PermissionDiscover) AttckTechniqueID() AttckTechniqueID { + return AttckTechniquePermissionGroupsDiscovery +} + +func (e *PermissionDiscover) AttckTacticID() AttckTacticID { + return AttckTacticDiscovery +} + func (e *PermissionDiscover) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*permissionDiscoverGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Identity, typed.PermissionSet) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Identity, typed.PermissionSet, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *PermissionDiscover) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/pod_attach.go b/pkg/kubehound/graph/edge/pod_attach.go index 1b73e5273..1e990b84f 100644 --- a/pkg/kubehound/graph/edge/pod_attach.go +++ b/pkg/kubehound/graph/edge/pod_attach.go @@ -36,13 +36,24 @@ func (e *PodAttach) Name() string { return "PodAttach" } +func (e *PodAttach) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *PodAttach) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodAttach) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*podAttachGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Pod) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Pod, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *PodAttach) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/pod_create.go b/pkg/kubehound/graph/edge/pod_create.go index 2bbf6ded5..b6a103d3a 100644 --- a/pkg/kubehound/graph/edge/pod_create.go +++ b/pkg/kubehound/graph/edge/pod_create.go @@ -36,6 +36,14 @@ func (e *PodCreate) Name() string { return "PodCreate" } +func (e *PodCreate) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueDeployContainer +} + +func (e *PodCreate) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodCreate) BatchSize() int { if e.cfg.LargeClusterOptimizations { // Under optimization this becomes a very cheap operation @@ -82,6 +90,8 @@ func (e *PodCreate) Traversal() types.EdgeTraversal { "critical": true, }). AddE(e.Label()). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack @@ -94,6 +104,8 @@ func (e *PodCreate) Traversal() types.EdgeTraversal { Has("critical", false). AddE(e.Label()). To("n"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } diff --git a/pkg/kubehound/graph/edge/pod_exec.go b/pkg/kubehound/graph/edge/pod_exec.go index e200b9227..4fd3ade8b 100644 --- a/pkg/kubehound/graph/edge/pod_exec.go +++ b/pkg/kubehound/graph/edge/pod_exec.go @@ -36,6 +36,14 @@ func (e *PodExec) Name() string { return "PodExec" } +func (e *PodExec) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *PodExec) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodExec) BatchSize() int { if e.cfg.LargeClusterOptimizations { // Under optimization this becomes a very cheap operation @@ -82,6 +90,8 @@ func (e *PodExec) Traversal() types.EdgeTraversal { "critical": true, }). AddE(e.Label()). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack @@ -94,6 +104,8 @@ func (e *PodExec) Traversal() types.EdgeTraversal { Has("critical", false). AddE(e.Label()). To("p"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } diff --git a/pkg/kubehound/graph/edge/pod_exec_namespace.go b/pkg/kubehound/graph/edge/pod_exec_namespace.go index f0210cf6b..7e9a2edeb 100644 --- a/pkg/kubehound/graph/edge/pod_exec_namespace.go +++ b/pkg/kubehound/graph/edge/pod_exec_namespace.go @@ -35,13 +35,24 @@ func (e *PodExecNamespace) Name() string { return "PodExecNamespace" } +func (e *PodExecNamespace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *PodExecNamespace) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodExecNamespace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*podExecNSGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Pod) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Pod, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } // Stream finds all roles that are namespaced and have pod/exec or equivalent wildcard permissions and matching pods. diff --git a/pkg/kubehound/graph/edge/pod_patch.go b/pkg/kubehound/graph/edge/pod_patch.go index 6689c4fe5..0aa1f57b3 100644 --- a/pkg/kubehound/graph/edge/pod_patch.go +++ b/pkg/kubehound/graph/edge/pod_patch.go @@ -36,6 +36,14 @@ func (e *PodPatch) Name() string { return "PodPatch" } +func (e *PodPatch) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *PodPatch) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodPatch) BatchSize() int { if e.cfg.LargeClusterOptimizations { // Under optimization this becomes a very cheap operation @@ -82,6 +90,8 @@ func (e *PodPatch) Traversal() types.EdgeTraversal { "critical": true, }). AddE(e.Label()). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack @@ -94,6 +104,8 @@ func (e *PodPatch) Traversal() types.EdgeTraversal { Has("critical", false). AddE(e.Label()). To("p"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } diff --git a/pkg/kubehound/graph/edge/pod_patch_namespace.go b/pkg/kubehound/graph/edge/pod_patch_namespace.go index 85d4aec32..41a3a72ad 100644 --- a/pkg/kubehound/graph/edge/pod_patch_namespace.go +++ b/pkg/kubehound/graph/edge/pod_patch_namespace.go @@ -35,13 +35,24 @@ func (e *PodPatchNamespace) Name() string { return "PodPatchNamespace" } +func (e *PodPatchNamespace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *PodPatchNamespace) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodPatchNamespace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*podPatchNSGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Pod) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Pod, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } // Stream finds all roles that are namespaced and have pod/exec or equivalent wildcard permissions and matching pods. diff --git a/pkg/kubehound/graph/edge/role_bind_crb_cr_cr.go b/pkg/kubehound/graph/edge/role_bind_crb_cr_cr.go index c0c2c149c..deb2c42dc 100644 --- a/pkg/kubehound/graph/edge/role_bind_crb_cr_cr.go +++ b/pkg/kubehound/graph/edge/role_bind_crb_cr_cr.go @@ -34,6 +34,14 @@ func (e *RoleBindCrbCrCr) Name() string { return ClusterRoleBindName } +func (e *RoleBindCrbCrCr) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueValidAccounts +} + +func (e *RoleBindCrbCrCr) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *RoleBindCrbCrCr) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*roleBindGroup) if !ok { @@ -67,6 +75,8 @@ func (e *RoleBindCrbCrCr) Traversal() types.EdgeTraversal { Has("critical", false). AddE(e.Label()). To("r"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack @@ -80,6 +90,8 @@ func (e *RoleBindCrbCrCr) Traversal() types.EdgeTraversal { Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } diff --git a/pkg/kubehound/graph/edge/role_bind_crb_cr_r.go b/pkg/kubehound/graph/edge/role_bind_crb_cr_r.go index 3f2ea6702..921049f2b 100644 --- a/pkg/kubehound/graph/edge/role_bind_crb_cr_r.go +++ b/pkg/kubehound/graph/edge/role_bind_crb_cr_r.go @@ -34,6 +34,14 @@ func (e *RoleBindCrbCrR) Name() string { return RoleBindCrbCrRName } +func (e *RoleBindCrbCrR) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueValidAccounts +} + +func (e *RoleBindCrbCrR) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *RoleBindCrbCrR) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*roleBindGroup) if !ok { @@ -67,6 +75,8 @@ func (e *RoleBindCrbCrR) Traversal() types.EdgeTraversal { Has("critical", false). AddE(e.Label()). To("r"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack @@ -80,6 +90,8 @@ func (e *RoleBindCrbCrR) Traversal() types.EdgeTraversal { Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } diff --git a/pkg/kubehound/graph/edge/role_bind_rb_rb_r.go b/pkg/kubehound/graph/edge/role_bind_rb_rb_r.go index 318ea94a8..f3389c46d 100644 --- a/pkg/kubehound/graph/edge/role_bind_rb_rb_r.go +++ b/pkg/kubehound/graph/edge/role_bind_rb_rb_r.go @@ -39,13 +39,24 @@ func (e *RoleBindRbRbR) Name() string { return RoleBindspaceName } +func (e *RoleBindRbRbR) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueValidAccounts +} + +func (e *RoleBindRbRbR) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *RoleBindRbRbR) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*roleBindNameSpaceGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.FromPerm, typed.ToPerm) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.FromPerm, typed.ToPerm, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *RoleBindRbRbR) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/share_ps_namespace.go b/pkg/kubehound/graph/edge/share_ps_namespace.go index 6aa7098fe..bda6d9eec 100644 --- a/pkg/kubehound/graph/edge/share_ps_namespace.go +++ b/pkg/kubehound/graph/edge/share_ps_namespace.go @@ -38,6 +38,14 @@ func (e *SharePSNamespace) Name() string { return "SharePSNamespace" } +func (e *SharePSNamespace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueTaintedSharedContent +} + +func (e *SharePSNamespace) AttckTacticID() AttckTacticID { + return AttckTacticLateralMovement +} + // Processor delegates the processing tasks to the generic containerEscapeProcessor. func (e *SharePSNamespace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*sharedPsNamespaceGroupPair) @@ -45,7 +53,10 @@ func (e *SharePSNamespace) Processor(ctx context.Context, oic *converter.ObjectI return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.ContainerA, typed.ContainerB) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.ContainerA, typed.ContainerB, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *SharePSNamespace) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/token_bruteforce.go b/pkg/kubehound/graph/edge/token_bruteforce.go index 2f9b2a300..be1a6c4a4 100644 --- a/pkg/kubehound/graph/edge/token_bruteforce.go +++ b/pkg/kubehound/graph/edge/token_bruteforce.go @@ -35,6 +35,14 @@ func (e *TokenBruteforce) Name() string { return "TokenBruteforceCluster" } +func (e *TokenBruteforce) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueStealApplicationAccessTokens +} + +func (e *TokenBruteforce) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *TokenBruteforce) BatchSize() int { if e.cfg.LargeClusterOptimizations { // Under optimization this becomes a very cheap operation @@ -73,6 +81,8 @@ func (e *TokenBruteforce) Traversal() types.EdgeTraversal { Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack @@ -85,6 +95,8 @@ func (e *TokenBruteforce) Traversal() types.EdgeTraversal { Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } diff --git a/pkg/kubehound/graph/edge/token_bruteforce_namespace.go b/pkg/kubehound/graph/edge/token_bruteforce_namespace.go index f376161ab..2c31ec2fe 100644 --- a/pkg/kubehound/graph/edge/token_bruteforce_namespace.go +++ b/pkg/kubehound/graph/edge/token_bruteforce_namespace.go @@ -35,13 +35,24 @@ func (e *TokenBruteforceNamespace) Name() string { return "TokenBruteforceNamespace" } +func (e *TokenBruteforceNamespace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueStealApplicationAccessTokens +} + +func (e *TokenBruteforceNamespace) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *TokenBruteforceNamespace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*tokenBruteforceNSGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Identity) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Identity, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } // Stream finds all roles that are namespaced and have secrets/get or equivalent wildcard permissions and matching identities. diff --git a/pkg/kubehound/graph/edge/token_list.go b/pkg/kubehound/graph/edge/token_list.go index e3b97b9fb..0d5688917 100644 --- a/pkg/kubehound/graph/edge/token_list.go +++ b/pkg/kubehound/graph/edge/token_list.go @@ -35,6 +35,14 @@ func (e *TokenList) Name() string { return "TokenListCluster" } +func (e *TokenList) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueStealApplicationAccessTokens +} + +func (e *TokenList) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *TokenList) BatchSize() int { if e.cfg.LargeClusterOptimizations { // Under optimization this becomes a very cheap operation @@ -73,6 +81,8 @@ func (e *TokenList) Traversal() types.EdgeTraversal { Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack @@ -85,6 +95,8 @@ func (e *TokenList) Traversal() types.EdgeTraversal { Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } diff --git a/pkg/kubehound/graph/edge/token_list_namespace.go b/pkg/kubehound/graph/edge/token_list_namespace.go index 8060f4b9d..956403603 100644 --- a/pkg/kubehound/graph/edge/token_list_namespace.go +++ b/pkg/kubehound/graph/edge/token_list_namespace.go @@ -35,13 +35,24 @@ func (e *TokenListNamespace) Name() string { return "TokenListNamespace" } +func (e *TokenListNamespace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueStealApplicationAccessTokens +} + +func (e *TokenListNamespace) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *TokenListNamespace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*tokenListNSGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Identity) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Identity, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } // Stream finds all roles that are namespaced and have secrets/list or equivalent wildcard permissions and matching identities. diff --git a/pkg/kubehound/graph/edge/token_steal.go b/pkg/kubehound/graph/edge/token_steal.go index 2dd1300c1..8f1f53a0b 100644 --- a/pkg/kubehound/graph/edge/token_steal.go +++ b/pkg/kubehound/graph/edge/token_steal.go @@ -37,13 +37,24 @@ func (e *TokenSteal) Name() string { return "TokenSteal" } +func (e *TokenSteal) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueUnsecuredCredentials +} + +func (e *TokenSteal) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *TokenSteal) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*tokenStealGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Identity) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Identity, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *TokenSteal) Stream(ctx context.Context, sdb storedb.Provider, c cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/volume_access.go b/pkg/kubehound/graph/edge/volume_access.go index b38e1a8e5..671ea8fe3 100644 --- a/pkg/kubehound/graph/edge/volume_access.go +++ b/pkg/kubehound/graph/edge/volume_access.go @@ -36,13 +36,24 @@ func (e *VolumeAccess) Name() string { return "VolumeAccess" } +func (e *VolumeAccess) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAndResourceDiscovery +} + +func (e *VolumeAccess) AttckTacticID() AttckTacticID { + return AttckTacticDiscovery +} + func (e *VolumeAccess) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*volumeAccessGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Volume) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Volume, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *VolumeAccess) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, diff --git a/pkg/kubehound/graph/edge/volume_discover.go b/pkg/kubehound/graph/edge/volume_discover.go index 59be4edce..a6a4bd31c 100644 --- a/pkg/kubehound/graph/edge/volume_discover.go +++ b/pkg/kubehound/graph/edge/volume_discover.go @@ -36,13 +36,24 @@ func (e *VolumeDiscover) Name() string { return "VolumeDiscover" } +func (e *VolumeDiscover) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAndResourceDiscovery +} + +func (e *VolumeDiscover) AttckTacticID() AttckTacticID { + return AttckTacticDiscovery +} + func (e *VolumeDiscover) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*volumeMountGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Container, typed.Volume) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Container, typed.Volume, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *VolumeDiscover) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader,