DEV Community

SorrelThorncroft
SorrelThorncroft

Posted on

Two Accepts and One Reject: How Bricksforge CVE-2026-85097 Turned a Form Upload into a Webshell

Two Accepts and One Reject: How Bricksforge CVE-2026-85097 Turned a Form Upload into a Webshell

A CVSS 10.0 unauthenticated file upload leading to remote code execution invites a simple reading: an attacker uploads a PHP file and the site executes it. CVE-2026-85097 in the Bricksforge WordPress plugin is more specific than that, and the specificity is what makes it worth studying. The upload does not have to end in a .php extension, because the plugin writes it to a .php path later.

The two-stage mismatch

The plugin exposes a form submission endpoint, reachable both through /wp-json/bricksforge/v1/form_submit and through admin-ajax.php with the bricksforge_form_submit action. A submission can carry a temporaryFileUploads entry, which is a URL the plugin fetches and then writes into its uploads directory.

The validation split is the bug. Stage one checks the upload against an allowed MIME type, and a GIF or PNG header in front of PHP code passes. Stage two trusts the path metadata in the submission itself, so the destination filename can carry a .php extension no matter what the MIME check approved.

A second endpoint removes the last obstacle. The AJAX action bricksforge_regenerate_nonce hands out a valid nonce without requiring an authenticated session, so an unauthenticated attacker can obtain the token the form handler expects.

Put together: fetch a remote file the attacker hosts, land it at a path ending in .php, request that path. Affected versions are 3.1.8.9 and earlier. The fix is 3.1.8.10, released 8 October 2026, plus additional hardening in the Pro Forms component in 4.0.1.

The exploitation timeline, and the discrepancy in the numbers

Patchstack recorded exploitation attempts starting 7 October 2026 at 21:47 UTC, roughly three weeks before the fixes landed. Their published severity is 10.0. At least one Chinese-language daily brief lists the same CVE at 9.8. That difference deserves flagging rather than silent resolution; the practical assessment is unchanged either way, because unauthenticated remote code execution is not a survivable risk for a public WordPress site.

A public toolkit exists at tc4dy/CVE-2026-85097-Toolkit. Where a working exploit is public, the population that gets hit is no longer limited to targeted actors.

What the telemetry shows

The interesting material in the disclosure is not the bug but the attempt distribution. The reporters describe 63 source IPs, of which 6 accounted for more than 46% of requests. One cluster of 44 addresses sent 56 identical requests within seconds of each other, and three addresses tried 25 different suffix and encoding variants against a single temporary file.

Those variants are the signature to hunt for: .php5, .php7, .phtml, case mixing, and double URL encoding such as .%70hp, %2ephp, and .%2570%2568%2570. Reported IP addresses include 177.75.57[.]20, 23.97.62[.]146, 84.247.60[.]125, 38.154.185[.]97, 150.109.16[.]166 and 153.75.90[.]146. They are useful for triaging past attempts, not for blocking future ones.

Hunting and verifying

The plugin writes temporary files under its own directory, so the hunt starts there: look inside wp-content/uploads/bricksforge/tmp/ for anything with an executable extension, and compare modification times against your form submission logs.

For version verification, read the plugin version on disk rather than trusting the dashboard, since a partially applied update leaves the old version string in place. Sites running the Pro Forms component also need 4.0.1; the 3.1.8.10 release does not cover it.

Patchstack shipped a RapidMitigate rule, which handles the WAF side for organisations that have it. For everyone else the sequence is upgrade, search the tmp directory, then check whether anything in it has been requested over HTTP.

Why this pattern will repeat

WordPress plugin bugs that escalate from upload to execution usually share this shape: one component validates the bytes, another decides the path, and the two never compare notes. The MIME check was doing its job. The path assignment was doing what the form handler asked. The vulnerability lived in the space between them.

If you maintain a plugin with any file handling, the review question is not whether the MIME check is correct. It is whether the filename can be influenced by the same request that supplied the bytes.

References

Top comments (0)