WooCommerce published a security release for WooCommerce Subscriptions on 5 August 2026 with unusually plain language: the most serious issue could allow "an unauthorized user to assume control of a site."
CVE-2026-18391 is a CVSS 9.8 unauthenticated PHP object injection leading to remote code execution in WooCommerce Subscriptions versions below 9.1.0, exploitable on stores running High-Performance Order Storage. If you sell recurring subscriptions on WooCommerce, this one is holding your customers' card-adjacent data, and it deserves an hour of your day.
What it is
| Field | Value |
|---|---|
| CVE | CVE-2026-18391 |
| CVSS | 9.8 Critical |
| Class | PHP object injection (CWE-502 / CWE-94 depending on source) → remote code execution |
| Authentication | None |
| Precondition | High-Performance Order Storage (HPOS) enabled |
| Affected | WooCommerce Subscriptions < 9.1.0 |
| Fixed in | 9.1.0, released 5 August 2026 |
| Discovery | Internal security review by WooCommerce |
| Published | 12 August 2026 |
| Record | NVD · WPScan |
The HPOS precondition matters
High-Performance Order Storage is WooCommerce's newer way of storing orders. Instead of packing orders into the generic wp_posts and wp_postmeta tables that WordPress uses for everything, HPOS gives orders dedicated tables. It is faster, it is the recommended configuration for stores at any scale, and for new installations it has been the default.
So the precondition is not obscure. If your store is recent, or you followed WooCommerce's own performance advice, you very probably have HPOS on. You can check under WooCommerce → Settings → Advanced → Features.
There is a bitter irony worth naming: the sites most exposed here are the ones that took the platform's performance guidance. "We're on an older configuration" is, for once, the safer position.
How the bug works
The plugin passes user-supplied input to PHP's unserialize() without validating it first, on the HPOS code path.
unserialize() is not a parser for data. It is a constructor for objects of whatever class the input names. Rebuilding an object can execute code, because PHP fires magic methods automatically: __wakeup() when the object is reconstructed, __destruct() when it is disposed of, __toString() when it is used as a string. None of that requires the application to do anything meaningful with the object.
From there the attacker needs a gadget chain: classes already loaded in the process whose magic methods, given attacker-chosen properties, can be strung together into something useful. In this case the chain runs through gadgets in the plugin's bundled dependencies (third-party libraries shipped alongside the plugin).
unauthenticated request ──► serialized payload
│
▼
unserialize() ── no validation of the input
│
▼
object of an attacker-named class is constructed
│
▼
__wakeup() / __destruct() fires
│
▼
gadget chain through bundled dependencies
│
▼
remote code execution
The dependency detail is the part with the widest lesson. The vulnerable code and the exploit's building blocks were in different codebases. WooCommerce Subscriptions supplied the unsafe unserialize(); the machinery that turns it into code execution came from libraries the plugin bundles. Auditing the plugin's own source and finding no dangerous function calls would not have found this.
Why this one is worse than a brochure-site bug
The compromise is the same technically. The consequences are not.
A subscriptions store holds a recurring-billing relationship with every customer: names, addresses, emails, order history, and payment tokens or gateway references that authorise future charges. Code execution on that host means:
- Customer personal data at rest in the orders tables
- Payment tokens or gateway credentials, depending on your gateway configuration
- The ability to modify checkout to skim card details going forward, which is the classic Magecart pattern and can run undetected for months
- Under PCI DSS, a compromise of the environment handling cardholder data, with the notification and forensic obligations that follow
- Under PDPL, GDPR and equivalents, a personal-data breach with statutory notification clocks
WooCommerce stated they had "no evidence that any store was compromised or that any customer data was accessed." That is a meaningful and welcome statement about their visibility. It is not a statement about your store, and it is not a substitute for checking your own logs. Whether an incident is notifiable is a question about your evidence, not the vendor's, which is exactly the kind of determination our governance and compliance work exists to make defensible.
What to do today
- Update WooCommerce Subscriptions to 9.1.0 or later. WooCommerce's own advice is to verify the version manually even if automatic updates are enabled. Do not assume.
- Check whether HPOS is on (WooCommerce → Settings → Advanced → Features). If it is, treat any pre-9.1.0 exposure window as a period requiring investigation, not just patching.
- Audit for the standard post-compromise markers before you close this out: administrator accounts you did not create, modified files, unexpected scheduled tasks, and outbound connections from the web host.
- Rotate secrets if you find anything at all: database credentials, payment gateway API keys, administrator and application passwords.
How to detect exploitation attempts
Serialized PHP has an unmistakable shape. Look for object markers in request data hitting your store:
grep -aE 'O:[0-9]+:"' access.log | grep -iE 'wc-ajax|admin-ajax|subscriptions'
Then look for what a successful chain usually leaves behind: a file:
find wp-content -type f -name '*.php' -newermt '2026-07-01' \
-not -path '*/plugins/*' -not -path '*/themes/*' -printf '%TY-%Tm-%Td %p\n' | sort
And check the checkout path specifically for injected JavaScript, because that is where the money is. Compare your active theme's checkout templates and any custom scripts against a known-good copy.
The wider point about bundled dependencies
This bug's exploitability came from libraries the plugin ships. Most WordPress plugin audits stop at the plugin's own code, and this is precisely why that is not enough. Your effective attack surface is every class loaded into the PHP process, from core, from every active plugin, and from every vendored dependency inside them.
That is not a WordPress-specific problem; it is the standard software supply-chain question wearing a different hat. Knowing what your store actually loads, and having a way to be told when any of it becomes dangerous, is the work. If you would like that established properly (or a penetration test of the store before someone else performs one), talk to us.



