Like changing the lock after the thief already copied the key but forgetting to disable the old cut
CVE-2025-22386 affects Optimizely Configured Commerce before 5.2.2408. The flaw is insufficient session destruction / expiration in the B2B storefront: when a user logs out, the server can leave that session token valid, so anyone who already has the token can continue using it. Optimizely's advisory says this impacts all versions before 5.2.2408 (STS) and 5.2.2408 (LTS) and that the fix is server-side invalidation on logout.
In practice, this is less dangerous than the 7.3 vector suggests because the bug does not steal a token by itself. The attacker first needs a valid session token through some other means such as a shared device, browser compromise, proxy logs, session leakage, or a separate client-side weakness. That makes this a post-token-theft persistence flaw, not an initial-access primitive. The vendor's own advisory rated it Medium 5.9 on December 13, 2024, while the CVE/NVD record appeared on January 3-4, 2025 and shows a CISA-ADP 7.3 score; reality is closer to the lower end.
4 steps from start to impact.
Obtain a live storefront session token with Burp Suite or browser artifacts
- Attacker can access a victim's authenticated session token
- Target uses an affected Configured Commerce version before 5.2.2408
- Victim has an active storefront session
- Requires a separate token-capture opportunity first
- Modern EDR/browser protections often catch malware-based cookie theft
- Short session lifetimes and secure client handling reduce token exposure
Wait for or trigger logout, then replay with Burp Repeater or curl
curl or Burp Repeater is enough; no exploit framework is needed.- Captured token is still within any remaining session validity window
- Application relies on the vulnerable logout/session handling path
- Any upstream SSO/global logout, custom session revocation, or aggressive idle timeout can break replay
- Some deployments may rotate cookies or bind sessions to additional state
Operate as the logged-out user using normal storefront workflows
- Victim account has access to sensitive B2B pricing, order, or customer records
- Relevant storefront functions are reachable with the preserved session
- Impact is bounded by the victim account's role and tenant/customer scope
- Step-up auth on sensitive actions would cut off some abuse even if browsing stays open
Persist quietly until the session finally expires
- Session remains valid long enough to matter operationally
- Natural expiry limits dwell time
- Password changes, explicit token revocation, or re-auth policies may terminate access
- MFA alone doesn't solve this because a replayed authenticated session cookie usually bypasses the login prompt entirely.
- A generic WAF is weak here because the traffic uses normal authenticated requests and valid application paths.
- Password reset by itself may not immediately kill the stale storefront session if the app fails to invalidate server-side session state on logout.
The supporting signals.
| In-the-wild status | No confirmed active exploitation found in the public sources reviewed; not present in CISA KEV and no campaign reporting surfaced. |
|---|---|
| Proof-of-concept availability | No public PoC located in source search. Feedly also notes no evidence of public exploit code. |
| EPSS | 0.00265 (~0.265%) from user intel. Public Feedly's early FIRST snapshot showed 0.04% / 11th percentile on 2025-01-04, which is directionally consistent with low expected exploitation. |
| KEV status | Not KEV-listed in the current CISA Known Exploited Vulnerabilities Catalog. |
| CVSS vector reality check | CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N captures remote replay impact, but it overstates real-world risk because it hides the hardest prerequisite: already having the victim's session token. |
| Affected versions | All Configured Commerce versions before 5.2.2408 are affected according to Optimizely's advisory. |
| Fixed versions | Fixed in 5.2.2408 (STS) and 5.2.2408 (LTS). This is a vendor-hosted app fix; no distro backport model is relevant here. |
| Scanning / exposure data | No product-specific GreyNoise/Shodan/Censys/FOFA exposure signal was identified in public search. That fits a niche B2B commerce platform with a smaller exposed population than mass-market VPN, firewall, or CMS targets. |
| Disclosure timeline | Optimizely's advisory was updated 2025-01-06 and lists Date Published: 2024-12-13. NVD shows the CVE record was received 2025-01-03 and published 2025-01-03/04 depending on timezone display. |
| Researcher / reporter | No public researcher attribution was provided in the vendor advisory. |
Sources.
Why this verdict
- Downward adjustment: requires a valid session token first — the vulnerability preserves a stolen token; it does not obtain one.
- Downward adjustment: post-initial-access behavior — needing token theft, shared-device access, or browser compromise implies the attacker is already partway in.
- Downward adjustment: limited exposed population — Optimizely Configured Commerce is a niche B2B platform, not a mass internet edge product with broad opportunistic scanning.
- Downward adjustment: account-scoped blast radius — impact is serious for the affected user/customer context, but this is not server compromise or tenant-wide default takeover.
- No upward pressure from threat intel — no KEV listing, no public campaign reporting, and no public PoC found.
Why not higher?
This is not unauthenticated remote code execution, auth bypass, or arbitrary account takeover at internet scale. The attacker must already possess a valid session token, which is a major real-world prerequisite that sharply narrows who can use the bug and how often it will be used.
Why not lower?
Once the prerequisite is met, the attacker can continue acting as a legitimate logged-out user and access or change sensitive B2B storefront data at that account's privilege level. For organizations with valuable pricing, ordering, and customer data in the storefront, that is more than backlog hygiene even if it is not an emergency patch.
Crowdsourced verification payload.
Run this on an auditor workstation, CI job, or vuln-management box against your asset inventory, not on the target host. Invoke it as python3 check_cve_2025_22386.py --version 5.2.2407 for a single asset or python3 check_cve_2025_22386.py --csv commerce_assets.csv --column version for bulk checks; no special privileges are required.
#!/usr/bin/env python3
# check_cve_2025_22386.py
# Determine whether an Optimizely Configured Commerce version is affected by CVE-2025-22386.
# Exit codes:
# 0 = all checked assets are PATCHED
# 1 = one or more assets are VULNERABLE
# 2 = UNKNOWN / input error
import argparse
import csv
import re
import sys
from typing import Optional, Tuple
FIXED = (5, 2, 2408)
def parse_version(v: str) -> Optional[Tuple[int, int, int]]:
if v is None:
return None
s = v.strip()
# Accept values like 5.2.2408, v5.2.2408, 5.2.2408 (STS), 5.2.2408-LTS
m = re.search(r'(\d+)\.(\d+)\.(\d+)', s)
if not m:
return None
try:
return tuple(int(x) for x in m.groups())
except ValueError:
return None
def status_for_version(v: str) -> str:
parsed = parse_version(v)
if parsed is None:
return 'UNKNOWN'
if parsed < FIXED:
return 'VULNERABLE'
return 'PATCHED'
def check_single(version: str) -> int:
status = status_for_version(version)
print(status)
return 1 if status == 'VULNERABLE' else 0 if status == 'PATCHED' else 2
def check_csv(path: str, column: str) -> int:
overall = 0
seen = 0
try:
with open(path, newline='', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
if column not in (reader.fieldnames or []):
print(f'UNKNOWN: column "{column}" not found in CSV header', file=sys.stderr)
return 2
for row in reader:
seen += 1
version = row.get(column, '')
asset = row.get('asset') or row.get('hostname') or row.get('name') or f'row-{seen}'
status = status_for_version(version)
print(f'{asset}: {status} ({version})')
if status == 'VULNERABLE':
overall = 1
elif status == 'UNKNOWN' and overall == 0:
overall = 2
except FileNotFoundError:
print(f'UNKNOWN: file not found: {path}', file=sys.stderr)
return 2
except Exception as e:
print(f'UNKNOWN: failed to read CSV: {e}', file=sys.stderr)
return 2
if seen == 0:
print('UNKNOWN: CSV contained no rows', file=sys.stderr)
return 2
return overall
def main() -> int:
parser = argparse.ArgumentParser(description='Check Optimizely Configured Commerce versions for CVE-2025-22386')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--version', help='Single Configured Commerce version, e.g. 5.2.2407')
group.add_argument('--csv', help='CSV file containing asset inventory')
parser.add_argument('--column', default='version', help='CSV column name holding the version (default: version)')
args = parser.parse_args()
if args.version:
return check_single(args.version)
return check_csv(args.csv, args.column)
if __name__ == '__main__':
sys.exit(main())