Deployment guide

Bricksforge CVE-2026-85097: update and audit your WordPress fleet

Deploy on a VPS Cloud →

Tutorial

Bricksforge CVE-2026-85097: update and audit your WordPress fleet

Security & Monitoring10 min read12 steps

An unauthenticated upload flaw in the Bricksforge plugin, an add-on for the Bricks builder, was already being exploited before it went public. A fix exists, but updating is not enough: a patched site may already hold a PHP file dropped before the update. For an agency the difficulty is twofold: knowing which clients run the plugin, then proving none of them was visited. This guide gives the full procedure, from the wp-cli inventory to secret rotation, with Bricksforge as the worked example and a template to replay for the next flaw.

Contents· The flaw in one paragraph, the rest is procedure1/13
  1. 01The flaw in one paragraph, the rest is procedure
  2. 02Reference table: where to look, what to look for
  3. 03Why updating is not enough
  4. 04Inventory the fleet with wp-cli
  5. 05Prioritise and set the look-back window
  6. 06Update, or neutralise in the meantime
  7. 07Hunt for dropped files and access traces
  8. 08What the logs do not show
  9. 09A file found: the site can no longer be considered healthy
  10. 10Secrets and accounts to renew after a compromise
  11. 11The dated proof to hand to the client
  12. 12Replay the procedure for the next plugin flaw
  13. 13Official documentation

The flaw in one paragraph, the rest is procedure

CVE-2026-85097 affects Bricksforge 3.1.8.9 and earlier; the fix is version 3.1.8.10. The Pro Forms module checks a file's MIME type on the first upload, but when the form is submitted it trusts the client-supplied url field in the temporaryFileUploads parameter. A GIF/PHP polyglot file passes the check, then is referenced with a PHP destination URL: the server writes it there and runs it on the first request.

On severity, sources differ: critical, with a CVSS score of 9.8 according to SecurityWeek and 10.0 according to the Patchstack advisory. We do not pick one, as the response is the same. Patchstack saw the first exploitation on October 7, 2026, before disclosure (advisory of October 8), and SecurityWeek, in its October 12 article, cites about 6,000 sites running the plugin. Attacker IP lists are short-lived: they are in the Patchstack advisory, no need to copy them.

Reference table: where to look, what to look for

Scroll the table

Where to lookWhat to look forWhat it means
Plugin versionBricksforge 3.1.8.9 or earlierVulnerable: move to 3.1.8.10 or later
Plugin absent or deactivatedNo line in the wp-cli inventoryNo exposure through this plugin, record it anyway
`wp-content/uploads/bricksforge/tmp/`Any PHP fileProbable drop: treat the site as compromised
`wp-content/uploads/YYYY/MM/``login_admin_*.php` filesTarget named by Patchstack: treat the site as compromised
Access logsPOST to `/wp-json/bricksforge/v1/form_submit`Attempt: cross-check with the state of the disk
Access logs`admin-ajax.php` with `bricksforge_form_submit` or `bricksforge_regenerate_nonce`Attempt: the nonce is requested before the upload
Access logsRequest to a PHP file under `uploads/`Probable execution: rotate secrets
WordPress accountsRecent, unknown administratorPossible persistence: consider a restore

Why updating is not enough

Exploitation came before the fix. A site moved to 3.1.8.10 on October 20 may have received a file on the 8th, and that file survives the update: the patch closes the door, it does not remove what is already inside. This is the objection to settle before telling a client "everything is fixed". The right order is: patch or neutralise, then look for compromise (files, logs, accounts, secrets), then hand over dated proof.

There is a second, more mundane obstacle: without a central inventory, nobody knows which clients run the plugin. A Bricks site delivered two years ago and hosted by another provider appears in no alert. The inventory therefore comes first, and it serves every future plugin flaw.

Inventory the fleet with wp-cli

  1. Test one site at a time

    cd /home/client/public_html
    wp plugin is-installed bricksforge && wp plugin get bricksforge --field=version
    wp plugin list --fields=name,version,update
    The first command answers through its exit code, the second prints the version, the third lists every plugin with its update status. The path is an example: adapt it to your host.

  2. Find every site on a server

    find /home -name wp-config.php -not -path '*/wp-content/*'
    Each wp-config.php points to an installation. The filter drops copies stored inside themes or backups; read the list through, a forgotten test subfolder counts too.

  3. Loop over all installations

    find /home -name wp-config.php -not -path '*/wp-content/*' -print0 | while IFS= read -r -d '' f; do d=$(dirname "$f"); echo "$d $(sudo -u "$(stat -c %U "$d")" wp --path="$d" --skip-plugins --skip-themes plugin get bricksforge --field=version 2>/dev/null)"; done
    Each line prints the folder then the version, or nothing if the plugin is absent. The --skip-plugins --skip-themes options avoid loading potentially booby-trapped code during the audit.

  4. Choose the user carefully

    Run wp-cli as the site's owner user, as in the loop above. --allow-root works, but it creates root-owned files in the site and runs the site's code with full rights: keep it for cases where nothing else is possible.

  5. Capture the result in a table

    One row per installation: client, host, path, version found, date and time of the check. Add a "visited since October 7" column to fill in at the next stage. This table becomes the centrepiece of the file you hand to clients.

  6. Cover sites outside your server

    For a site hosted elsewhere, ask for temporary access or an export of wp plugin list --fields=name,version,update. A site you cannot query stays in the table marked "not verified", not missing.

Prioritise and set the look-back window

Handle first what is both exposed and exploited: a site with Bricksforge at 3.1.8.9 or lower, public Pro Forms forms, in production. Next come staging sites and working copies, then those where the plugin is installed but inactive. A dormant plugin deserves a decision: removing it is safer than letting it age.

The look-back window starts on October 7, 2026 at 21:47 UTC, the first signal published by Patchstack. Convert that time to the timezone of your logs before filtering. The key point: if your logs do not reach back to that date, the absence of a trace proves nothing. Note the date of the oldest line available, as it limits what you can state.

Update, or neutralise in the meantime

When updating is possible, wp plugin update bricksforge is enough for an installation that receives releases normally; depending on the licence, the site may require going through its dashboard or the vendor's archive. Then check with wp plugin get bricksforge --field=version: the result must be 3.1.8.10 or later.

When it is not yet possible, three temporary measures exist, none replacing the fix. Deactivating the plugin with wp plugin deactivate bricksforge cuts the route, at the cost of the site's forms: warn the client. Forbidding PHP execution in uploads/ removes the value of a dropped file: on nginx, location ~* ^/wp-content/uploads/.*\.(php|php[0-9]|phtml|pht|phps)$ { deny all; } placed before the general PHP block; on Apache, an .htaccess in uploads/ with <FilesMatch "\.(php|php[0-9]|phtml|pht|phps)$"> Require all denied </FilesMatch>. Finally, rate limiting on /wp-json/bricksforge/v1/form_submit slows bursts, and Patchstack's RapidMitigate rule blocks the attack traffic for those who use that service. These measures reduce risk, they do not fix the flaw.

Hunt for dropped files and access traces

  1. Look for PHP files in uploads

    find wp-content/uploads -type f \( -iname '*.php' -o -iname '*.php[0-9]' -o -iname '*.phtml' -o -iname '*.pht' -o -iname '*.phps' \) -ls
    A healthy uploads/ folder holds no executable PHP. A few-byte index.php that is just a comment is common and harmless; read any other file before judging it.

  2. Aim at the targets named by Patchstack

    ls -la wp-content/uploads/bricksforge/tmp/
    find wp-content/uploads -name 'login_admin_*.php' -ls
    find wp-content/uploads -type f -newermt '2026-10-07' -ls
    The first two commands target the locations and file name from the advisory; the third lists everything written to uploads/ since October 7, to catch a variant.

  3. Sweep the nginx logs

    zgrep -h -i -E 'bricksforge/v1/form_submit|bricksforge_(form_submit|regenerate_nonce)' /var/log/nginx/access.log*
    zgrep reads compressed and plain files alike: gzip rotation is therefore covered by the wildcard. Keep each line's date and compare it with the look-back window.

  4. Sweep the Apache and cPanel logs

    zgrep -h -i -E 'bricksforge/v1/form_submit|bricksforge_(form_submit|regenerate_nonce)' /var/log/apache2/*access*.log* /var/log/httpd/*access*log* /usr/local/apache/domlogs/*
    Folders vary: /var/log/apache2/ on Debian and Ubuntu, /var/log/httpd/ on RHEL and AlmaLinux, /usr/local/apache/domlogs/ on a cPanel server. Missing paths produce a harmless error.

  5. Look for access to dropped files

    zgrep -h -i -E 'login_admin_[^ ]*\.php|/uploads/[^ ]*\.(php[0-9]?|phtml|pht)|%2e(php|phtml)' /var/log/nginx/access.log*
    A request, especially a successful GET, to a PHP file under uploads/ means a file may have run. Case and encoding variants are covered here, in the logs, not on disk.

  6. Count the sources and record

    zgrep -h 'POST /wp-admin/admin-ajax.php' /var/log/nginx/access.log* | awk '{print $1}' | sort | uniq -c | sort -rn | head
    This command ranks addresses by number of POSTs to admin-ajax.php. Record the result, even if empty, with the oldest log date read.

What the logs do not show

The AJAX action often travels in the POST body, which the access log does not record: searching for bricksforge_form_submit may find nothing even though the call happened. In that case, look for an abnormal volume of POSTs to admin-ajax.php or to the REST route, and cross-check with the state of the disk. This guide describes administration tasks; it is provided as is, with no support from us.

A file found: the site can no longer be considered healthy

A dropped file implies code execution on the server with the rights of the site's account. Deleting the file alone is not enough: it may have been used to create an account, read wp-config.php or plant a second door. Isolate the site, keep a copy of the file for analysis, then restore from a backup older than October 7, 2026 rather than cleaning by hand.

After the restore, update Bricksforge before putting the site back online, otherwise the same request starts again. If no clean backup exists, rebuild from source code and the content export, and treat any database extraction as suspect.

Secrets and accounts to renew after a compromise

  • Salts and keys in wp-config.php — wp config shuffle-salts regenerates them and invalidates open sessions, including an intruder's.
  • Database password — change it on the MySQL or MariaDB server, then in wp-config.php, without waiting.
  • API keys and tokens — payment, transactional email, mapping, third-party services declared in the site or the database.
  • SMTP credentials — a mail account readable in the configuration quickly gets used for fraudulent sending.
  • Administrator passwords — wp user reset-password <ID> for each, and a unique password per person.
  • Unknown WordPress accounts — wp user list --fields=ID,user_login,role,user_registered --orderby=user_registered --order=DESC reveals recent creations.
  • Persistence — wp cron event list, the wp-content/mu-plugins/ folder and any plugins or themes that are not yours.

The dated proof to hand to the client

A client who reads "updated" does not know what you checked. Hand over a one-page sheet per site: date and time of the audit, version before and after, results of the file searches, period covered by the logs with the date of the oldest line read, actions taken (restore, rotation, accounts removed) and what could not be checked. Including the limits protects as much as the results.

The sheet is also your protection if a compromise appears later: it dates what you knew and what you did. Keep the inventory table and the raw command outputs with the sheet.

Replay the procedure for the next plugin flaw

Five steps, valid for any plugin. Inventory: plugin, version, site, client, host. Prioritise: exposed and exploited first. Set the look-back window from the first published signal. Neutralise or update, then look for files, access traces, accounts and secrets, in that order. Hand over the dated proof. Only the inventory command and the search patterns change, and the vendor or Patchstack advisory provides them.

A fleet scattered across several hosts makes every step slow. A reseller account groups your sites under a single account, with one access point per client; SSH and wp-cli access depends on the plan, so check it first. You keep the client relationship, we run the infrastructure.

Official documentation

Patchstack advisory on CVE-2026-85097: https://patchstack.com/articles/critical-unauthenticated-arbitrary-file-upload-vulnerability-exploited-in-bricksforge-plugin/
SecurityWeek article: https://www.securityweek.com/bricksforge-wordpress-plugin-vulnerability-exploited-in-the-wild/
wp plugin commands: https://developer.wordpress.org/cli/commands/plugin/
wp config shuffle-salts command: https://developer.wordpress.org/cli/commands/config/shuffle-salts/
nginx location directive: https://nginx.org/en/docs/http/ngx_http_core_module.html#location

A fleet of client sites, one audit loop

When client sites live with several hosts, every audit costs hours. With ServOrbit.com reseller hosting, one access point per client and a single interface let you group your clients' sites under a single interface, with your rates and your brand.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab