Skip to content

CVE-2026-54849: Verified Repro With Script Download

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.

REPRO-2026-00251 SQLi Variant found Jul 6, 2026 CVE entry ↗ .txt
Severity
CRITICAL
CVSS
9.3
Confidence
HIGH
Reproduced in
27m 26s
Tool calls
177
Spend
$3.23
01 · Overview

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).

02 · Severity & CVSS

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 threat level
9.3 / 10 CVSS base
Weakness CWE-89 SQL Injection — Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

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
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00251/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-54849

Information disclosure — reproduced
  • 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
Trigger

premmerce_wishlist cookie (JSON array of wishlist keys) containing an embedded SQL injection payload (e.g. x") UNION SELECT ...)

Attack chain
  1. Unauthenticated HTTP GET to /?rest_route=/premmerce/wishlist/add/popup (permission_callback __return_true) with crafted premmerce_wishlist cookie
  2. RestApi::wishlistAddPopupRest
  3. getWishlistsAll()
  4. WishlistModel::getWishlistsByKeys(WishlistStorage::cookieGet())
  5. SQL built by string concatenation of cookie keys into IN ("...")
Variants tested

Variant/bypass search for CVE-2026-54849 (Premmerce Wishlist unauthenticated SQLi via premmerce_wishlist cookie). Four materially-distinct unauthenticated entry points to the same cookie-derived concatenation sink were tested (REST /add/popup GET, wc-ajax premmerce_wishlist_popup GET, REST /add POST reaching getDefaul…

How the agent worked 418 events · 177 tool calls · 27 min
27 minDuration
177Tool calls
112Reasoning steps
418Events
1Dead-ends
Agent activity over 27 min
Support
48
Repro
232
Judge
23
Variant
111
0:0027:26

Root Cause and Exploit Chain for CVE-2026-54849

Versions: <= 1.1.11. Patched in 1.1.12

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 in 1.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_login from wp_users via a UNION 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:
    1. 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).
    2. UNION-based data extraction — a UNION SELECT payload leaked the WordPress admin user_login (sqliadmin) into the unauthenticated REST response body on the vulnerable build (1 occurrence in the response), with no leak on the fixed build.
  • Parity: full. The claimed unauthenticated SQL injection (data extraction) was reproduced end-to-end against the real product on the claimed api_remote surface.
  • 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

  1. Script: bundle/repro/reproduction_steps.sh (self-contained; run twice, both pass with exit 0).
  2. What it does:
    • Deploys a real stack in Docker: mariadb:11.4 + wordpress:6.7.2-php8.2-apache, installs WordPress (admin sqliadmin), 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/popup with the crafted premmerce_wishlist cookie. 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.json and exits 0 only if all four criteria hold (vuln sleeps + leaks; fixed does neither).
  3. Expected evidence of reproduction:
    • Vulnerable SLEEP response times ≈ 5.2–6.1s; fixed ≈ 0.07–0.13s.
    • Vulnerable UNION response bodies contain sqliadmin; fixed bodies do not.
    • bundle/repro/runtime_manifest.json with confirmation_status: "confirmed".

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 in WishlistModel.php were 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_wishlist cookie.
  • Detection: review access logs for premmerce_wishlist cookie values containing UNION, 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.

Variant Analysis & Alternative Triggers for CVE-2026-54849

Versions: versions (as tested): <= 1.1.11 vulnerable; 1.1.12 fixed;

No bypass or distinct unauthenticated variant was found. The 1.1.12 fix for CVE-2026-54849 is defense-in-depth and complete: it validates every premmerce_wishlist cookie key with a strict allow-list regex (^[a-zA-Z0-9]{1,13}$) at the single choke point WishlistStorage::cookieGet(), and it parameterizes every dynamic SQL sink in WishlistModel with $wpdb->prepare(). This variant analysis searched for materially-distinct alternate entry points that reach the same cookie-derived concatenation sink and tested them on the vulnerable (1.1.11), fixed (1.1.12), and latest (1.1.13) builds. Four unauthenticated alternate triggers were confirmed to exist on the vulnerable build (they all fire SLEEP(5), ~5–6 s) — proving the search was real, not fabricated — but none fire on the fixed or latest build (all < 0.13 s). The fix covers every alternate path. A fifth, authenticated alternate trigger (wp_loginsetUserToWishlistsByKeys UPDATE) was identified by source inspection and is likewise covered by the same two-layer fix.

Fix Coverage / Assumptions

Invariant the fix relies on: all cookie→SQL data flows through WishlistStorage::cookieGet(), which is the only place the premmerce_wishlist cookie is decoded. The regex there is the outer guard; the parameterized sinks are the inner guard.

Code paths explicitly covered:

  • cookieGet() — rejects any key containing quotes, spaces, parentheses, comments, or other SQL metacharacters (regex ^[a-zA-Z0-9]{1,13}$); drops non-array / non-string JSON.
  • WishlistModel::getWishlistsByKeys(), getDefaultWishlistByKeys(), setUserToWishlistsByKeys(), deleteWishlists(), getWishlistsByProductId(), and getWishlists() (where_in + orderby) — all converted to $wpdb->prepare() with matched %s/%d placeholders; getWishlists() where_in additionally restricts columns to an allowed_columns allow-list (ID, user_id, wishlist_key).

What the fix does NOT cover: nothing unauthenticated. There are no remaining concatenation sinks in the fixed WishlistModel (verified by grep — the only implode() calls left build the %s,%s,... placeholder string, never interpolate data). Request-parameter inputs on the unauthenticated REST routes are all passed as prepare() arguments, and the URL-param routes enforce [a-zA-Z0-9]{13} / \d+ regex constraints at the router level. The admin list-table surfaces are authenticated (manage_options) and are not injectable in the fixed build (and are out of scope for this unauthenticated CVE).

Variant / Alternate Trigger

All candidates share the same root cause (untrusted premmerce_wishlist cookie → cookieGet() → a string-concatenation IN (...) sink) and differ only in the HTTP entry point that triggers it. They are alternate triggers of the same bug, not a new bug.

ID Entry point (unauthenticated) Code path → sink Vuln 1.1.11 SLEEP Fixed 1.1.12 Latest 1.1.13
C0 GET /?rest_route=/premmerce/wishlist/add/popup RestApi::wishlistAddPopupRestwishlistAddPopupgetWishlistsAll()WishlistModel::getWishlistsByKeys(cookieGet()) 5.20 s 0.11 s 0.12 s
C1 GET /?wc-ajax=premmerce_wishlist_popup RestApi::wishlistAddPopupAjaxwishlistAddPopupgetWishlistsAll()getWishlistsByKeys(cookieGet()) 6.07 s 0.04 s 0.04 s
C2 POST /?rest_route=/premmerce/wishlist/add RestApi::wishlistAddaddProductToWishlist()getDefaultWishlistByKeys(cookieGet()) (+ popup getWishlistsAll()) 5.14 s 0.08 s 0.08 s
C3 GET /?page_id=<wishlist_page> Frontend::wishlistPage (shortcode [wishlist_page]) → getWishlistsByKeys(cookieGet()) 5.13 s 0.10 s 0.10 s
C4* wp_login action Frontend::assignUserWishlistFromCookiesetUserToWishlistsByKeys($user->ID, cookieGet()) (UPDATE, single-quote IN ('...')) (source only) covered covered

* C4 is an authenticated trigger (fires on any login). It reaches a different sink (setUserToWishlistsByKeys, an UPDATE) with a different quote breakout (single-quote) from the parent SELECT/double-quote. It is covered by the same fix (cookieGet() regex + $wpdb->prepare() of setUserToWishlistsByKeys). It was validated by source inspection rather than runtime because the time-based oracle on an UPDATE ... WHERE is unreliable on an empty wishlist table (the WHERE predicate is not evaluated when zero rows are scanned).

Why these are not a bypass: every candidate funnels through the patched cookieGet() and the now-parameterized sinks. Empirically, all four unauthenticated candidates return in < 0.13 s on 1.1.12 and 1.1.13 (no SLEEP), vs 5.1–6.1 s on 1.1.11.

  • Package/component affected: premmerce-woocommerce-wishlist (Premmerce Wishlist for WooCommerce). Vulnerable component: src/WishlistStorage.php::cookieGet() + src/Models/WishlistModel.php concatenation sinks; entry points in src/RestApi/RestApi.php, src/Frontend/Frontend.php.
  • Affected versions (as tested): <= 1.1.11 vulnerable; 1.1.12 fixed; 1.1.13 latest (security-relevant code byte-identical to 1.1.12).
  • Risk level: Critical (Patchstack CVSS 9.3) for the parent vulnerability on <= 1.1.11. No residual risk on the fixed/latest builds — no bypass.
  • Consequences of the parent bug: unauthenticated remote SQL injection (data extraction / time-based blind inference against the WordPress DB). The variant run did not introduce any new impact.

Impact Parity

  • Disclosed/claimed maximum impact (parent): unauthenticated SQL injection with data extraction (CVSS 9.3, C:H).
  • Reproduced impact from this variant run: the alternate entry points reproduce the same time-based blind SQLi on the vulnerable build (SLEEP ~5–6 s on all four unauthenticated candidates). On the fixed and latest builds, no injection is reproduced on any candidate.
  • Parity (parent vs. variant on the vulnerable build): full — the alternate triggers reach the same sink with the same payload and the same observable time-based oracle.
  • Parity (variant as a bypass of the fix): none — no candidate injects on the fixed or latest build.
  • Not demonstrated: this run uses only the SLEEP time oracle to compare entry points across builds; it does not re-demonstrate the UNION-based data extraction (already proven in the parent repro on the baseline entry point). No DB write or RCE is demonstrated (out of the disclosed impact).

Root Cause

The same underlying bug — untrusted cookie data interpolated into SQL by string concatenation — is reachable from multiple unauthenticated entry points because they all call WishlistStorage::cookieGet() (no validation) and then a WishlistModel concatenation sink (getWishlistsByKeys, getDefaultWishlistByKeys, etc.). The 1.1.12 fix removes both halves of the bug: (a) cookieGet() now validates each key with ^[a-zA-Z0-9]{1,13}$, and (b) every sink is rebuilt with $wpdb->prepare(). Because both layers were fixed, even an entry point that bypassed one layer would be stopped by the other. No fix commit SHA is available (the plugin is distributed via WordPress.org as release zips, not a public git repo); the fix is identified by the 1.1.12 release (premmerce-woocommerce-wishlist.1.1.12.zip, sha256 103cbe3086ce0df927af7488e62f5b655d54094442b6385aeb6969d9068d822d), changelog "Security: SQL injection fix in wishlist cookie handling".

Reproduction Steps

  1. Script: bundle/vuln_variant/reproduction_steps.sh (self-contained, idempotent; run twice, both exit 1 = no bypass).
  2. What it does:
    • Deploys a real stack in Docker: mariadb:11.4 + wordpress:6.7.2-php8.2-apache, installs WordPress (admin sqliadmin), activates WooCommerce 8.9.3, enables pretty permalinks.
    • For each build — vulnerable 1.1.11, fixed 1.1.12, latest 1.1.13 — installs+activates Premmerce Wishlist from the local release zip and resolves the wishlist page id created on activation.
    • Sends an unauthenticated HTTP request (from a separate client container on the Docker network, hitting the WordPress service by container name) carrying the crafted premmerce_wishlist SLEEP cookie (identical payload to the parent repro: ["x\") UNION SELECT SLEEP(5),2,3,4,5,6,7,8-- "]) to four candidate entry points: REST /add/popup GET (C0 baseline), wc-ajax=premmerce_wishlist_popup GET (C1), REST /add POST (C2), and the frontend wishlist page GET (C3). Measures each response time.
    • Evaluates: a bypass = any candidate SLEEPs (≥ 4 s) on the fixed (1.1.12) or latest (1.1.13) build. Writes runtime_manifest.json and validation_verdict.json.
  3. Expected evidence:
    • Vulnerable 1.1.11: all four candidates ≈ 5.1–6.1 s (SLEEP fired) → real alternate triggers.
    • Fixed 1.1.12 & latest 1.1.13: all four candidates < 0.13 s (no SLEEP) → fix covers them.
    • validation_verdict.json with variant_outcome: "no_bypass"; script exit 1.

Evidence

  • Main log: bundle/logs/vuln_variant_reproduction.log (full output of all runs, including per-candidate http code + time_total).
  • Runtime manifest: bundle/vuln_variant/runtime_manifest.json (confirmation_status: "no_bypass"; per-candidate timings on all three builds).
  • Validation verdict: bundle/vuln_variant/validation_verdict.json (variant_outcome: "no_bypass", claim_block_reason: "fix_covers_all_alternate_entry_points").
  • Response bodies: bundle/vuln_variant/artifacts/c{0..3}_{vuln,fixed,latest}.body — confirm the endpoints were genuinely reached (C0/C1 render the wishlist popup HTML; C3 renders the full wishlist page HTML; C2 bodies are empty because /add POST issues a redirect, but the SLEEP fired pre-redirect as shown by the 5.14 s timing on the vulnerable build).
  • Source identity: bundle/vuln_variant/source_identity.json (release zips
    • sha256 + runtime-reported versions for 1.1.11 / 1.1.12 / 1.1.13).
  • Root-cause equivalence: bundle/vuln_variant/root_cause_equivalence.json.
  • Patch analysis: bundle/vuln_variant/patch_analysis.md.

Key excerpt — per-candidate SLEEP divergence (from runtime_manifest.json):

candidate_labels: [c0_baseline_add_popup_GET, c1_wc_ajax_popup_GET, c2_rest_add_POST, c3_page_render_GET]
vulnerable_1111_sleep_seconds: [5.198, 6.068, 5.138, 5.125]   # all four fire SLEEP
fixed_1112_sleep_seconds:      [0.113, 0.035, 0.078, 0.100]   # none fire (fix covers)
latest_1113_sleep_seconds:     [0.113, 0.041, 0.079, 0.096]   # none fire (latest covers)
vulnerable_sleep_count: 4/4   fixed_sleep_count: 0/4   latest_sleep_count: 0/4
bypass_on_fixed_or_latest: false

Environment: Docker mariadb:11.4 + wordpress:6.7.2-php8.2-apache (PHP 8.2), WooCommerce 8.9.3, Premmerce Wishlist 1.1.11 / 1.1.12 / 1.1.13, WordPress table prefix wp_, admin sqliadmin. Unauthenticated HTTP requests issued by a separate container on the Docker bridge network to the WordPress service by container name (http://premmerce-wp/...); response bodies 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

  • No fix extension is required for CVE-2026-54849. The 1.1.12 fix already covers every materially-distinct unauthenticated entry point (verified empirically: C0–C3) and the authenticated wp_login UPDATE path (C4, by source inspection). The two-layer design (input validation + universal parameterization) is robust.
  • Generic hardening (low priority):
    • Add automated regression tests that feed malicious premmerce_wishlist cookie values (quotes, UNION, SLEEP, --, ), single-quote variants) to every registered REST route and the wc-ajax / page-render entry points and assert no SQL injection / no error / no timing anomaly.
    • Treat all $_COOKIE / $_REQUEST as untrusted throughout the plugin; validate structure and types before use (the cookieGet() regex is a good template).
    • Consider a WAF rule alerting on premmerce_wishlist cookie values containing UNION, SLEEP, --, ", ', or ) as exploitation indicators (these are now blocked by the plugin, but the signal is useful for detection).

Additional Notes

  • Idempotency: the script tears down and rebuilds the whole Docker stack on every run, so it can be executed repeatedly with deterministic results. Verified by multiple consecutive runs (all exit 1, all no_bypass, both runtime_manifest.json and validation_verdict.json written).
  • Negative result is the correct outcome. The candidate set was bounded by the actual code: every unauthenticated path to a concatenation sink goes through cookieGet(), and there are no concatenation sinks left in the fixed WishlistModel. Inventing further "variants" beyond C0–C4 would just relabel the same cookie→cookieGet()→sink flow through sibling routes (e.g. the /page, /delete, /page/rename routes use request-parameter inputs that are already parameterized and router-regex-constrained), which the anti- fabrication rules explicitly exclude.
  • C4 limitation: the wp_loginsetUserToWishlistsByKeys UPDATE path was not runtime-tested because a time-based SLEEP in an UPDATE ... WHERE predicate does not fire on an empty table (no rows scanned). It is covered by source inspection: cookieGet() regex + $wpdb->prepare() of setUserToWishlistsByKeys. A reliable runtime oracle for this path would require pre-seeding a wishlist row (so the UPDATE scans a row) — left as a optional follow-up since the fix demonstrably covers it.
  • Threat model / SECURITY.md: the plugin ships no SECURITY.md or threat- model document. The vendor themselves classified the cookie SQLi as a security defect and shipped a fix (1.1.12 changelog), so unauthenticated cookie-driven SQL injection is unambiguously in scope; no documented limitation is being reframed here.
  • Scope/impact of this run: this run proves the fix is complete (no bypass) and that the alternate triggers are real (fire on the vulnerable build). It does not demonstrate DB writes or RCE, which are outside the disclosed impact.

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.

Event 1/40
0:003:38
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-54849 · REPRO-20
0:02
0:04
web search
0:06
web search
0:08
0:15
0:17
web search
0:20
0:22
web search
0:24
web search
0:26
1:27
1:28
web search
1:31
1:48
1:49
2:00
2:02
2:04
2:20
2:23
2:27
2:47
2:51
2:54
2:56
3:13
3:14
3:15
3:38

Artifacts and Evidence for CVE-2026-54849

Scripts, logs, diffs, and output captured during the reproduction.

bundle/artifact_promotion_manifest.json16.1 KB
bundle/logs/vuln_variant_reproduction.log10.1 KB
bundle/vuln_variant/source_identity.json3.0 KB
bundle/vuln_variant/root_cause_equivalence.json3.4 KB
bundle/repro/reproduction_steps.sh13.6 KB
bundle/repro/rca_report.md11.2 KB
bundle/repro/validation_verdict.json1.0 KB
bundle/repro/runtime_manifest.json1.3 KB
bundle/repro/artifacts/vuln_union_1.body4.6 KB
bundle/repro/artifacts/fixed_union_1.body4.1 KB
bundle/repro/artifacts/vuln_sleep_1.body4.6 KB
bundle/repro/artifacts/fixed_sleep_1.body4.1 KB
bundle/logs/reproduction_steps.log6.2 KB
bundle/repro/artifacts/fixed_sleep_2.body4.1 KB
bundle/repro/artifacts/fixed_union_2.body4.1 KB
bundle/repro/artifacts/vuln_sleep_2.body4.6 KB
bundle/repro/artifacts/vuln_union_2.body4.6 KB
bundle/vuln_variant/reproduction_steps.sh19.0 KB
bundle/vuln_variant/runtime_manifest.json2.3 KB
bundle/vuln_variant/validation_verdict.json2.0 KB
bundle/vuln_variant/artifacts/c1_vuln.body4.3 KB
bundle/vuln_variant/artifacts/c1_fixed.body3.8 KB
bundle/vuln_variant/artifacts/c3_vuln.body51.3 KB
bundle/vuln_variant/artifacts/c3_fixed.body49.7 KB
bundle/vuln_variant/rca_report.md14.4 KB
bundle/vuln_variant/patch_analysis.md9.3 KB
bundle/vuln_variant/variant_manifest.json6.6 KB
bundle/vuln_variant/artifacts/c0_fixed.body4.1 KB
bundle/vuln_variant/artifacts/c0_latest.body4.1 KB
bundle/vuln_variant/artifacts/c0_vuln.body4.6 KB
bundle/vuln_variant/artifacts/c1_latest.body3.8 KB
bundle/vuln_variant/artifacts/c2_fixed.body0.0 KB
bundle/vuln_variant/artifacts/c2_latest.body0.0 KB
bundle/vuln_variant/artifacts/c2_vuln.body0.0 KB
bundle/vuln_variant/artifacts/c3_latest.body49.7 KB
08 · How to Fix

How to Fix CVE-2026-54849

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-54849 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-54849

How does the CVE-2026-54849 SQL injection attack work?

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. An attacker sets a crafted premmerce_wishlist cookie whose keys break out of the double-quoted SQL literal, allowing arbitrary SQL to run — for example, extracting the admin user_login.

How severe is CVE-2026-54849?

It is rated critical severity.

How can I reproduce CVE-2026-54849?

Download the verified script from this page and run it in an isolated environment against a WordPress install with Premmerce Wishlist for WooCommerce <= 1.1.11. It sends an unauthenticated request with a crafted premmerce_wishlist cookie containing a SQL injection payload and observes database data extraction.

Does exploiting CVE-2026-54849 require authentication?

No. The vulnerable REST endpoint is registered with permission_callback => __return_true, so any unauthenticated remote visitor can trigger the injection.
11 · References

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.