Une seule requête POST vers un formulaire, et le serveur exécute votre code
Une seule requête POST vers un formulaire. Voilà ce qui sépare un visiteur anonyme de l’exécution de code sur votre serveur. La requête ci-dessous résume toute la faille, et elle est déjà envoyée à des sites en production. CVE-2026-85097 est une vulnérabilité de téléversement arbitraire de fichiers sans authentification dans Bricksforge, une extension populaire du constructeur de sites Bricks. Toutes les versions jusqu’à la 3.1.8.9 incluse sont concernées ; le correctif est disponible à partir de la 3.1.8.10. [1][2] Wordfence, qui a attribué l’identifiant CVE, lui donne un score de 9,8. Patchstack, qui a observé son exploitation, lui attribue un score de 10,0. [1][2]
Bricksforge a été averti le 3 septembre et les détails ont été rendus publics le 7 octobre. [2] Patchstack a observé la première tentative d’exploitation le jour même, à 21 h 47 UTC, depuis une infrastructure automatisée manifestement prête à agir. [1] Je n’ai pas reproduit cette attaque sur une installation en production. L’analyse qui suit s’appuie sur l’article de Patchstack et le code qu’il publie, l’avis de Wordfence, le journal des modifications de l’éditeur et la documentation de WordPress, consultés le 12 octobre 2026. [1][2][3][4] Le plus intéressant n’est pas qu’un téléversement ait mené à une exécution de code. C’est que l’extension vérifiait soigneusement le fichier et pouvait tout de même être compromise.
POST /wp-json/bricksforge/v1/form_submit HTTP/1.1
Host: victim.example
Content-Type: application/json
{
"temporaryFileUploads": [
{
"field": "form-field-file",
"file": {
"file": "/var/www/.../uploads/bricksforge/tmp/img.gif", // real GIF, passed the MIME check
"url": "https://victim.example/wp-content/uploads/shell.php", // attacker names the destination
"type": "image/gif"
}
}
]
}Le serveur vérifiait les octets. L’attaquant choisissait le nom
Voici la logique sur laquelle reposait Bricksforge. Lorsqu’un visiteur joint un fichier, l’extension le place dans un dossier temporaire et y vérifie correctement son type MIME avant toute autre opération. Un GIF doit réellement être un GIF, et ce contrôle fonctionne. Les développeurs ont ensuite fait un choix en apparence anodin : lors de l’envoi final du formulaire, ils ont laissé le navigateur indiquer au serveur quel était le fichier concerné et où il devait être stocké.
C’est là toute l’erreur. Dire simplement que « le champ url n’est pas validé » ne rend pas compte du problème. L’extension validait une propriété du fichier, son contenu, à partir d’un champ, puis déterminait son nom définitif et son extension à partir d’un autre champ, contrôlé par l’attaquant et jamais revérifié. Ce qui était inspecté ne correspondait pas à ce qui guidait l’action. Les octets d’un GIF ne disent rien du fait que le fichier sur le point d’être écrit s’appellera shell.php. Sur un serveur Apache avec mod_php, c’est l’extension .php, et non l’en-tête de l’image, qui détermine si le serveur exécutera le fichier.
Une image valide, copiée dans un fichier exécutable
Suivons les deux étapes. D’abord, le téléversement. L’attaquant demande un nonce à un point d’accès sans authentification, puis envoie un fichier polyglotte : un véritable GIF qui contient également une ligne de PHP. Comme le fichier est bien une image, mime_content_type renvoie image/gif, et celui-ci arrive intact dans le répertoire temporaire. [1] Jusqu’ici, l’extension a tout fait correctement. Le fichier présent sur le disque est bel et bien une image.
Le formulaire est ensuite envoyé et les métadonnées entrent en jeu. Le JSON transmis contient un objet file doté de deux champs essentiels : file, le chemin sur le serveur vers le fichier validé, et url, sa destination. Le code ci-dessous condense le chemin vulnérable publié par Patchstack. [1] Le nom de destination est extrait du champ url, passé à basename, puis utilisé pour copier les octets qui ont été validés.
Regardez ce que fait copy(). La fonction prend le véritable GIF du répertoire temporaire et écrit ses octets à un emplacement choisi par l’attaquant, avec un nom se terminant par .php, dans la racine web. L’en-tête de l’image permet au fichier de rester un GIF valide ; le PHP qu’il contient sera exécuté par le serveur à la première requête vers cette URL. [1] La vérification MIME ne pouvait rien y faire, car elle répondait à une autre question. Elle prouvait que le fichier était une image, pas qu’il serait enregistré ou servi comme tel.
// Bricksforge Pro Forms (condensed from the vulnerable path)
$file_path = $file_data['file']; // server path of the file that was MIME-checked
$file_url = $file_data['url']; // client-supplied, never re-validated
$attachment['guid'] = $file_url; // destination comes from the URL
$desired_filename = basename($attachment['guid']); // ...so from the attacker
$destination_path = $upload_path . '/' . $desired_filename;
copy($file_path, $destination_path); // GIF bytes written to an attacker-named .phpPourquoi « nos formulaires n’ont pas de champ de fichier » ne vous protège pas
C’est ici que le problème devient préoccupant, même si vous pensez ne pas être exposé. On pourrait croire que seuls les formulaires acceptant des pièces jointes sont concernés. Le journal des modifications de Bricksforge affirme pourtant le contraire : la faille touche tout site où Pro Forms est actif, même si ses formulaires ne comportent aucun champ de téléversement. [3] Le code explique pourquoi. La liste des fichiers à traiter ne provient pas d’un véritable envoi multipart, mais de la valeur temporaryFileUploads présente dans le corps JSON, que l’attaquant peut composer de toutes pièces. [1] Un simple formulaire de contact textuel suffit.
Et le nonce n’est pas le verrou qu’il semble être. Bricksforge expose un point d’accès, bricksforge_regenerate_nonce, qui fournit un nouveau nonce à n’importe quel visiteur anonyme, ce qui suffit déjà à le rendre inutile comme barrière de sécurité. Même sans cela, WordPress génère le même nonce pour tous les visiteurs non connectés, puisqu’ils partagent l’identifiant utilisateur 0. [4] Un nonce ne prouve pas que l’auteur de la requête est une personne de confiance. La documentation WordPress le précise : il ne faut jamais se reposer sur les nonces pour l’authentification, l’autorisation ou le contrôle d’accès. [4] C’est pourtant exactement le rôle que ce gestionnaire leur faisait jouer.
// The file list is read from the JSON body, not from a real multipart upload:
$file_uploads = isset($form_data['temporaryFileUploads'])
? json_decode($form_data['temporaryFileUploads'], true) // attacker-supplied
: [];
$merged_files = array_merge($file_uploads, $request->get_file_params());
// The gate in front of it:
// GET bricksforge_regenerate_nonce -> hands any anonymous visitor a nonce
// WordPress gives every logged-out user the SAME nonce (user ID 0)
// A nonce is CSRF hygiene. It was never authentication.Le correctif redonne au serveur le contrôle du nom de fichier
Le correctif rétablit une règle élémentaire : c’est au serveur, et non au client, de décider où un fichier téléversé sera stocké et sous quel nom. Patchstack et l’avis de Wordfence désignent la 3.1.8.10 comme la version corrigée. [1][2] Le journal des modifications de Bricksforge mentionne également la correction du téléversement de fichiers dans la 4.0.1, publiée le 8 octobre, et précise que des détails supplémentaires suivront la divulgation. [3] Installez la version corrigée correspondant à votre branche et à votre licence ; sur la branche 3.x, la 3.1.8.10 constitue le minimum.
Le principe dépasse largement cette extension. Choisir le nom d’un fichier enregistré est une décision de sécurité : elle doit rester du ressort du serveur. Générez un nom aléatoire, limitez les extensions à une liste d’autorisation et stockez les fichiers hors de la racine web ou dans un répertoire où l’exécution de PHP est désactivée. Le bloc ci-dessous illustre ce principe ; ce n’est pas le diff exact de l’éditeur. Bricksforge présente aussi un piège propre à sa distribution : c’est une extension commerciale, absente de WordPress.org, dont les mises à jour dépendent d’une licence. [3] Une licence expirée peut vous laisser discrètement sur une version vulnérable, alors même que le tableau de bord semble normal. « Nous l’avons acheté une fois » ne signifie pas « nous avons appliqué le correctif ».
// The fix, as a principle: the SERVER owns the name and extension, never the client.
$ext = 'bin'; // or an allowlisted image extension
$stored = wp_generate_uuid4() . '.' . $ext; // random name the attacker can't choose
$destination = $upload_path . '/' . $stored;
copy($validated_tmp_file, $destination);
// And decide execution by WRITE location, not by MIME: store outside the web root,
// or in a directory where the server is configured never to run PHP.Comment je rechercherais cette faille dans du code sans rapport avec Bricksforge
Cette vulnérabilité appartient à une famille d’erreurs plus ancienne que WordPress : valider une propriété, puis agir selon une autre. Dès qu’un gestionnaire de téléversement vérifie le contenu d’un fichier, mais laisse les données de la requête choisir son nom, son extension ou son répertoire, on retrouve la même faille, qu’il s’agisse de PHP, de Node ou de Java. Lorsque j’audite un mécanisme de téléversement, je ne commence pas par le contrôle MIME dont tout le monde est fier. Je commence par l’écriture sur disque.
Je repère tous les points où des octets sont écrits sur le disque, puis je remonte le flux pour déterminer l’origine du nom de destination. La recherche grep ci-dessous permet d’identifier les candidats ; le véritable travail consiste à examiner chacun d’eux. À chaque opération d’écriture, je pose une seule question, celle qui aurait permis de détecter CVE-2026-85097 en revue de code : la propriété que vous avez validée détermine-t-elle réellement celle qui sera exécutée ? Si les octets sont contrôlés, mais qu’un champ distinct sous le contrôle du client nomme le fichier, cette validation n’est qu’une façade.
# Smell to hunt in any upload handler:
# the stored filename, extension or directory derived from request input.
rg -n "basename\(|get_param|\$_POST|\$_REQUEST|json_decode" --glob '*.php'
# Then read each write sink by hand: move_uploaded_file, copy, file_put_contents, rename.
# The one question that confirms the bug:
# does the property you VALIDATED (the bytes, the MIME) decide the property
# that gets EXECUTED (the extension, the directory)?
# If a separate, client-controlled field names the sink, the check is decoration.Est-ce votre urgence ? Seulement si vous utilisez Bricksforge, mais alors oui
Il faut garder le sens des proportions. Selon Patchstack, Bricksforge compte environ 6 000 installations actives : ce n’est donc pas une crise à l’échelle de tout Internet. [1] Si vous n’utilisez pas Bricksforge, vous pouvez fermer cet onglet. Si vous l’utilisez, le risque n’est pas théorique. Patchstack a relevé des tentatives d’exploitation provenant de 63 adresses IP sources distinctes, quelques heures à peine après la divulgation. Dans 95 % des requêtes malveillantes, une charge PHP était déguisée en GIF ou en PNG ; certains groupes ont essayé des dizaines de variantes d’extensions pour contourner des filtres rudimentaires. [1]
Ces techniques de contournement méritent qu’on s’y attarde, car elles indiquent quoi rechercher dans les journaux. Les attaquants ont essayé d’autres extensions PHP, comme .php7 et .phtml, des variantes de casse comme .PHp et des formes encodées dans les URL comme %2ephp, toutes destinées au même copy(). [1] Les fichiers qu’ils tentaient de déposer suivaient des modèles tels que login_admin_*.php dans le répertoire des téléversements, y compris le dossier temporaire de Bricksforge. [1] Au 12 octobre, cette CVE ne figurait pas encore dans le catalogue KEV de la CISA et son score EPSS était inférieur à un pour cent. Cela ne signifie pourtant pas qu’il ne se passait rien. [2] Cela signifie qu’il était encore tôt. Le catalogue KEV est un indicateur retardé ; les honeypots donnent des signaux plus précoces, et ils enregistraient déjà des attaques.
Trois conditions suffisent pour que cela devienne votre urgence : vous utilisez Bricksforge, vous exécutez une version antérieure à la 3.1.8.10 et votre site est accessible depuis Internet. Si les trois sont réunies, l’application du correctif n’est que la première étape : un fichier PHP qui pouvait être écrit l’a peut-être déjà été et exécuté. Pour un administrateur, la question n’est pas seulement « avons-nous effectué la mise à jour ? », mais aussi « quelqu’un a-t-il atteint le point d’accès form_submit avec une charge de fichier avant nous ? ». La réponse se trouve dans vos journaux et votre répertoire des fichiers téléversés, pas dans l’écran des extensions.
Que faire avant de fermer cet onglet
Voici l’ordre que je suivrais. Appliquez d’abord le correctif : la fenêtre d’exploitation se compte en heures, pas en semaines. [1] Mais si une version vulnérable a été exposée à Internet, partez du principe qu’une tentative a pu avoir lieu et enquêtez avant de considérer l’incident comme clos. Le correctif empêche le prochain dépôt malveillant ; il ne supprime pas un shell déjà installé la semaine précédente.
La liste ci-dessous est opérationnelle. Dans les journaux, cherchez la signature que l’attaque peut difficilement éviter : une requête vers le point d’accès de soumission du formulaire contenant temporaryFileUploads, dont le champ file pointe vers une image tandis que le champ url se termine par une extension PHP. Cette incohérence est l’empreinte de la vulnérabilité et elle apparaît rarement par hasard.
Patch and verify (in this order):
[ ] Find every site running Bricksforge; confirm the version actually LOADED.
[ ] Update to 3.1.8.10+ (3.x branch) or 4.0.1+. Bricksforge is license-gated and
not on WordPress.org, so a lapsed license can silently block the update.
[ ] Cannot update now? Block at the WAF / server: POST to
/wp-json/bricksforge/v1/form_submit and the admin-ajax action
bricksforge_form_submit carrying a temporaryFileUploads body.
If a vulnerable version faced the internet, assume compromise and hunt:
[ ] Grep uploads for unexpected .php, especially
/wp-content/uploads/bricksforge/tmp/ and names like login_admin_*.php.
[ ] Audit for hidden admin accounts by querying the database directly,
and review the must-use (mu-plugins) directory.
[ ] In access logs, look for form_submit requests whose file path is an image
but whose url field ends in a PHP extension.
[ ] Rotate secrets reachable from PHP: database, API keys, SMTP.Ce que « nous validons les fichiers téléversés » ne garantit jamais
CVE-2026-85097 illustre pourquoi « nous validons les fichiers téléversés » ne répond pas à la question « le téléversement est-il sécurisé ? ». L’extension contrôlait ce qui était facile à contrôler et faisait confiance à ce qui comptait réellement. C’est dans cet écart entre la propriété vérifiée et celle qui détermine l’exécution que se cachent de nombreuses vulnérabilités de téléversement. Ni un numéro de version ni une coche verte dans un scanner ne permettent de le voir : il faut suivre le chemin complet, de la requête à l’écriture sur disque.
Suivre ce chemin, depuis une requête anonyme, à travers le framework, jusqu’à la ligne précise qui nomme le fichier écrit sur le disque, est au cœur d’un audit de sécurité d’application web. Et ce schéma consistant à « valider une chose et agir selon une autre », je le recherche aussi dans des projets qui n’ont rien à voir avec WordPress. Si votre application utilise des formulaires, des téléversements ou tout autre traitement transformant des entrées externes en fichiers sur votre serveur, c’est cette frontière de confiance que j’examinerais. Dites-moi ce que vous développez et nous définirons le périmètre.
Sources
Besoin de vérifier ce point dans votre propre produit ?
Test d’intrusion web et APIVous voulez apprendre à trouver vous-même ce type de vulnérabilité ?
Formation individuelle au test d’intrusion web