Skip to content

Commit

Permalink
Merge pull request #24 from WithSecureLabs/create-secret
Browse files Browse the repository at this point in the history
initial version for creating secrets
  • Loading branch information
Skybound1 committed Apr 29, 2024
2 parents 5a08913 + ff6dc87 commit cc76027
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 8 deletions.
44 changes: 44 additions & 0 deletions docs/CREATE_SECRET_WITH_TOKEN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# CREATE_SECRET_WITH_TOKEN

### Overview

This attack aims to locate subjects which can gain access to a service account token by creating a secret which would be populated by the control plane with the relevant secret. The subject would also need a method to read the newly created secret.

### Description

Secrets can be configured with the type `kubernetes.io/service-account-token` and annotated with `kubernetes.io/service-account.name` which will result in the secret being automatically populated with a token for said service account.

An attacker could leverage this by creating a secret for a service account and use it to request a token for a service account they wish to gain access to. Once created, an attacker would need to read the secret. This could be done in a few different ways such as reading the secret directly or mounting it into a pod and using that to exfiltrate the secret.

### Defense

RBAC permissions regarding creating secrets should be reviewed. Access should be restricted where not needed.

### Cypher Deep-Dive

#### Listing secrets

```cypher
MATCH (src)-[:GRANTS_SECRETS_CREATE]->(ns:Namespace)<-[:WITHIN_NAMESPACE]-(dest:ServiceAccount) WHERE (src)-[:GRANTS_SECRETS_LIST]->(ns)
```

This query identifies subjects (`src`) that can create secrets within a namespace. It also checks whether the same subject can list secrets within the same namespace. This would allow them to read the secret upon creation.

#### Workload creation

```cypher
MATCH (src)-[:GRANTS_SECRETS_CREATE]->(ns:Namespace)<-[:WITHIN_NAMESPACE]-(dest:ServiceAccount) WHERE (src)-[:GRANTS_PODS_CREATE|GRANTS_REPLICATIONCONTROLLERS_CREATE|GRANTS_DAEMONSETS_CREATE|GRANTS_DEPLOYMENTS_CREATE|GRANTS_REPLICASETS_CREATE|GRANTS_STATEFULSETS_CREATE|GRANTS_CRONJOBS_CREATE|GRANTS_JOBS_CREATE]->(ns)
```

This query identifies subjects (`src`) that can create secrets within a namespace. It also checks the subject can create a workload within the same namespace which could be configured to mount the newly created secret for exfiltration purposes.

Workload creation includes the following:
- `pods`
- `replicationcontrollers`
- `daemonsets`
- `deployments`
- `replicasets`
- `statefulsets`
- `cornjobs`
- `jobs`
13 changes: 13 additions & 0 deletions icekube/attack_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ def workload_query(
Relationship.ACCESS_SECRET: "MATCH (src)-[:GRANTS_GET|GRANTS_LIST|GRANTS_WATCH]->(dest:Secret)",
# Generate service account token
Relationship.GENERATE_TOKEN: "MATCH (src)-[:GRANTS_TOKEN_CREATE]->(dest:ServiceAccount)",
# Create a long-lived secret for a service account
Relationship.CREATE_SECRET_WITH_TOKEN: [
# Uses a workload to read the generated secret
f"""
MATCH (src)-[:GRANTS_SECRETS_CREATE]->(ns:Namespace)<-[:WITHIN_NAMESPACE]-(dest:ServiceAccount)
WHERE (src)-[:GRANTS_PODS_CREATE|{create_workload_query()}]->(ns)
""",
# Uses secret list to read the generated secret
"""
MATCH (src)-[:GRANTS_SECRETS_CREATE]->(ns:Namespace)<-[:WITHIN_NAMESPACE]-(dest:ServiceAccount)
WHERE (src)-[:GRANTS_SECRETS_LIST]->(ns)
""",
],
# RBAC escalate verb to change a role to be more permissive
Relationship.RBAC_ESCALATE_TO: [
# RoleBindings
Expand Down
21 changes: 13 additions & 8 deletions icekube/models/policyrule.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,24 +83,29 @@ def affected_resource_query(
for verb in self.verbs:
valid_verbs.update(fnfilter(api_resource.verbs, verb.lower()))

if "create" in valid_verbs and sub_resource is None:
verbs_for_namespace = set("create list".split()).intersection(valid_verbs)

if verbs_for_namespace and sub_resource is None:
if namespace:
query_filter: Dict[str, Union[str, List[str]]] = {
"kind": "Namespace",
"name": namespace,
}
else:
query_filter = {"apiVersion": "N/A", "kind": "Cluster"}
for verb in verbs_for_namespace:
yield (
Relationship.generate_grant(verb.upper(), resource),
generate_query(query_filter),
)
query_filter = {"kind": "Namespace"}
for verb in verbs_for_namespace:
yield (
Relationship.generate_grant("CREATE", resource),
Relationship.generate_grant(verb.upper(), resource),
generate_query(query_filter),
)
query_filter = {"kind": "Namespace"}
yield (
Relationship.generate_grant("CREATE", resource),
generate_query(query_filter),
)
valid_verbs.remove("create")
if "create" in verbs_for_namespace:
valid_verbs.remove("create")

if not valid_verbs:
continue
Expand Down
1 change: 1 addition & 0 deletions icekube/relationships.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Relationship:

AUTHENTICATION_TOKEN_FOR: ClassVar[str] = "AUTHENTICATION_TOKEN_FOR"
GET_AUTHENTICATION_TOKEN_FOR: ClassVar[str] = "GET_AUTHENTICATION_TOKEN_FOR"
CREATE_SECRET_WITH_TOKEN: ClassVar[str] = "CREATE_SECRET_WITH_TOKEN"

WITHIN_NAMESPACE: ClassVar[str] = "WITHIN_NAMESPACE"

Expand Down

0 comments on commit cc76027

Please sign in to comment.