
🔥 CyberForge CVE of the Day #071 🚨 CVE-2026-105844 — Payload CMS: prototype pollution in the Import Export plugin can lead to RCE An unauthenticated caller can supply prototype-sensitive field paths when @payloadcms/plugin-import-export is enabled. The vendor describes unintended application behaviour that can allow remote code execution. Applications that do not use this plugin are outside the advisory's stated scope. 🗓️ Evidence cutoff: 7 October 2026, 08:27 UTC. The CVE was published on 6 October; the vendor advisory dates to 22 September, and stable 3.88.0 was released on 11 August. Today's CVE publication date is not the patch date. 🎯 THE QUICK HIT Who needs to check? Owners running Payload with Import Export enabled: stable 3.0.0–3.87.x, or 4.0.0-canary.0 through canary.26. Confirm installed package versions, effective configuration and reachable routes. A lockfile entry alone does not establish plugin enablement. What fixes it? Upgrade aligned Payload packages to at least 3.88.0 on stable 3.x, or 4.0.0-canary.27 on the canary train. Latest stable at retrieval: 3.90.2. Keep each train's repair floor separate and use the owner's supported release set. What while upgrading? The vendor recommends disabling the plugin or restricting endpoint access. Verify actual custom, proxy and direct routes. Hiding a menu or protecting only admin login does not demonstrate that request handlers are unreachable. What should the SOC follow? Preserve edge/proxy, application, worker/job and runtime records. Hunt prototype-sensitive selections at confirmed plugin routes, then correlate the same asset/window with unexplained execution, writes, egress or application changes. A token, response status or error is a lead; execution needs corroboration. What can a quiet hunt mean? No matches in the examined sources and interval. Access-only records often omit fields. Missing bodies, worker logs, route mapping or runtime coverage can leave the main comparison unavailable. Record the gap instead of declaring the system clean. How urgent? Critical, unauthenticated, with potential RCE. Prioritise exposed enabled deployments and give uncertain inventory an owner. No field exploitation or standalone public exploit was verified in the primary material reviewed. KEV absence and unavailable EPSS do not lower the published impact. 🔑 KEY DETAILS • Component: @payloadcms/plugin-import-export for Payload CMS; enablement is a required scope condition. • Weakness: CWE-1321 — improperly controlled modification of object prototype attributes, or prototype pollution. • Vendor/GitHub CNA CVSS v4.0: 9.3 Critical — CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. • CVSS v3.x: no usable vendor/CNA vector was provided. A null vector paired with 0.0 in one feed is not a published zero-severity assessment. • Prerequisites: network access, low complexity, no prior privileges or victim interaction; plugin enabled and relevant code reachable. • Stable affected: >=3.0.0 and <3.88.0. First fixed stable: 3.88.0. • Canary affected: >=4.0.0-canary.0 and <4.0.0-canary.27. First fixed canary: 4.0.0-canary.27. • Vendor GHSA: GHSA-qf28-8hc6-vwrp. Reporter credited: iamnoooob. • NVD: Awaiting Analysis, with GitHub-attributed v4.0 metrics; no independent NIST score was present. • CISA KEV: no exact match in the retrieved 2026.10.04 catalogue, released 4 October. • FIRST EPSS: HTTP 200, total: 0, data: []. No score, percentile or score date available; this is not 0%. • Impact boundary: vendor-described server-side execution. Runtime permissions and deployment reach determine downstream access; no universal root, container escape or named campaign is established here. 🧠 WHY FIELD SELECTION BECOMES A TRUST PROBLEM The plugin accepts dot-separated field paths for data selection. Some JavaScript property names also touch prototype relationships and inherited behaviour. Unsafe handling can let untrusted selection influence application object state beyond its intended operation. The referenced patch guards __proto__, constructor and prototype segments, creates intermediate objects with null prototypes and checks property ownership. Its nested-value helper also validates array bounds, using source data from export preview. The vendor confirms potential RCE; the reviewed material does not supply the downstream gadget or a universal execution chain. Those remain case-specific unknowns. The commit bundles other hardening and its release label emphasises multipart handling; we inspected the relevant plugin files and stable release membership directly. Other bundled changes are not automatically this CVE. 🔎 THE SOC TRAIL — SIX CHECKS FOR THE CASE 01 — SCOPE PACKAGES, ENABLEMENT AND REACHABILITY Action: identify serving Payload deployments and workers. Record installed payload and @payloadcms/plugin-import-export versions, enabled configuration, build identity and owner. Inspect nested package copies and every execution context. Where: owner-provided package/build inventory, effective configuration and route/proxy mapping. Read configuration as authorised text; do not import payload.config.ts for inventory, because it is executable application code. Signal and correlation: affected train + enabled plugin + reachable handlers. Join asset, build, package tree and request destination. Mark whether evidence describes source, installed packages or the serving runtime. Include direct listeners and alternate proxy prefixes. Limit: an unused dependency or archived build can look affected. A fixed web tier does not verify every replica or worker. Unknown enablement/version is an owned gap; exposed enabled affected instances need urgent repair. 02 — PRESERVE THE REQUEST AND RUNTIME WINDOW Action: preserve retained edge/WAF/proxy records, application errors, import/export jobs and available runtime telemetry. Set a justified UTC interval; retain offsets, hashes, source locations, collection health and retention limits. Keep raw evidence restricted and redact the working view. Where: actual configured destinations. Payload supports configurable logging, and logging can be disabled. Optional plugin hook/debug examples do not establish that requests, fields or jobs were recorded in this deployment. Signal and correlation: relevant requests, field-path errors, unusual job transitions and runtime changes. Join request IDs where propagated; for queued work record both enqueue/execution times and worker identity. Different processes can have different logging schemas. Limit: routine exports, schema changes and integration errors create noise. “Invalid field path.” is a patched error string, not proof of full protection or prior exploitation. Access-only logs can omit fields. Avoid broad body logging that collects credentials or exported content. 03 — HUNT THE CONFIRMED FIELD-PATH SURFACE Action: map actual handlers and examine authorised captured field selections. Tag 3.88.0 registers POST /export-preview and POST /download on the default exports collection. With default API prefix/slug, examples are /api/exports/export-preview and /api/exports/download. Custom slugs, prefixes and proxies change these paths; neither example is a universal IOC or proof that each handler is independently exploitable. Signal: preview request data includes fields, which flows into getSelect. The patched helpers reject dot-separated segments exactly equal to __proto__, constructor or prototype. Match that case-sensitive vocabulary after documented parsing, preserving a private original. The Forge field_paths schema below is analyst-defined; do not infer it from a URI or response status. Correlation and limit: join confirmed route, asset, request/time and captured fields to downstream events. Tutorials, literal keys and authorised testing can match. A token, 200 or 400 is a lead, not proof of object mutation or execution. Document parser/encoding and missing field capture; do not replay suspect inputs against production. 04 — FOLLOW REQUESTS THROUGH JOBS INTO RUNTIME Action: establish queued versus synchronous execution. Reviewed plugin source registers createCollectionExport work; available job/worker identifiers are deployment-dependent. Follow request → job → runtime only where evidence supports each link. Where and signal: application/job records plus existing EDR or managed-platform telemetry. Seek attributable unexplained execution, writes, destinations or application outcomes on the Payload process/function/worker. Code can execute in an existing runtime without a child shell. Correlation and limit: align asset/build, propagated identifiers and UTC time with independent events. A shared Node name or IP is insufficient. Exports, hooks, uploads and maintenance can cause similar activity; no CVE-specific egress or command line was verified. Suspicious fields with unexplained runtime effects warrant IR review. 05 — REPAIR THE TRAIN AND CONTROL THE ROUTES Action: update aligned Payload packages to stable >=3.88.0 or canary >=4.0.0-canary.27. Registry metadata confirms the canary packages and matching peer versions. Use the owner's supported release set; editing one manifest does not update serving instances. While upgrading, the vendor recommends disabling the plugin or restricting endpoint access. Verify approved controls across custom slugs, prefixes, direct listeners and proxy routes. Removing a menu entry or protecting only admin login does not prove request handlers are blocked. Correlation and limit: record old/new builds, installed core/plugin versions, deployment time, replaced replicas/workers and control owner. Review suspect queued inputs with the incident/change owner before processing resumes. A patch repairs code; earlier compromise and credential exposure require their own evidence and response decisions. 06 — VERIFY AND HAND OVER THE COVERAGE Action: reconcile every serving replica/function and relevant worker with the approved fixed build, effective plugin state and route controls. Use ordinary authorised service/feature checks. A completed install, health page or source checkout does not establish complete deployment. Handoff: record repaired assets, examined UTC window, datasets, parsing rules and collection gaps. “No matches in the examined sources and interval” is the defensible empty-result statement. Missing field/job/runtime records remain gaps. Assign owners and escalate unresolved execution/integrity evidence to IR. The screenshot checklist has empty boxes: a workflow must be completed against an actual deployment and case, not inferred from the CVE's existence. 🧰 TOOLS AND BOUNDED CHECKS Two helpers below inspect authorised local metadata. They passed 42 synthetic offline scenarios covering stable/canary boundaries, nested package copies, unknown inputs, private-value exclusion and request-window gaps. We did not execute npm against an application, run vendor tests, load Payload configuration, test an exploit or deploy a detector. A — Package metadata from the owner's installed environment An authorised owner can produce this inventory in the correct application/container environment. npm ls lists packages; it does not install them. Preserve stderr and exit status privately: missing/invalid dependency state can give a nonzero status while still producing JSON. Do not mistake a partial inventory for an unaffected deployment. ```sh npm ls --all --json payload @payloadcms/plugin-import-export > payload-package-tree.json ``` Save this as payload_package_metadata.py, then run it against the approved export: ```python """Offline npm-ls metadata. Does not load packages or application configuration.""" import json import re import sys from pathlib import Path TARGETS = {'payload', '@payloadcms/plugin-import-export'} LIMIT = 4 * 1024 * 1024 def classify(value): if not isinstance(value, str): return None, 'unclassified' stable = re.fullmatch(r'(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z.-]+)?', value) canary = re.fullmatch(r'4\.0\.0-canary\.(0|[1-9]\d*)(?:\+[0-9A-Za-z.-]+)?', value) if stable and stable[1] == '3': return value, 'affected-range' if int(stable[2]) < 88 else 'fixed-floor-met' if canary: return value, 'affected-range' if int(canary[1]) < 27 else 'fixed-floor-met' return None, 'unclassified' def inspect(path): with Path(path).open('rb') as handle: raw = http://handle.read(LIMIT + 1) if len(raw) > LIMIT: raise ValueError('size') tree = json.loads(raw) if not isinstance(tree, dict): raise ValueError('root') stack = [tree]; records = []; problems = 0; nodes = 0 while stack: node = stack.pop(); nodes += 1 if nodes > 20000 or not isinstance(node, dict): raise ValueError('tree') diagnostic = node.get('problems', []) deps = node.get('dependencies', {}) if not isinstance(diagnostic, list) or not isinstance(deps, dict): raise ValueError('schema') problems += len(diagnostic) for name, item in deps.items(): if not isinstance(item, dict): raise ValueError('dependency') if name in TARGETS: version, status = classify(item.get('version')) records.append({'package': name, 'version': version, 'range_status': status}) stack.append(item) return {'records': records, 'missing_packages': sorted(TARGETS - {r['package'] for r in records}), 'unclassified_records': sum(r['range_status'] == 'unclassified' for r in records), 'npm_problem_occurrences': problems} if __name__ == '__main__': try: if len(sys.argv) != 2: raise ValueError('usage') print(json.dumps(inspect(sys.argv[1]))) except (OSError, ValueError, UnicodeError, RecursionError) as exc: print('Input/schema check failed: ' + type(exc).__name__, file=sys.stderr) sys.exit(3) ``` ```sh python3 payload_package_metadata.py payload-package-tree.json ``` The helper walks nested dependencies and emits only relevant package versions/range labels, missing targets, unclassified records and diagnostic counts. It omits resolved URLs, paths and diagnostic text. “Fixed-floor-met” concerns this advisory's version criterion only; enablement, deployed runtime identity, historical impact and other vulnerabilities are separate checks. Only the stated stable 3.x and numbered canary train are classified; other/unknown values remain unclassified. Missing targets describe this supplied view. Input is bounded to 4 MiB; malformed schema fails without a safety verdict. B — Metadata triage of a prepared request window Prepare payload-request-window.json as an ARRAY of Forge-normalised request objects for one recorded UTC interval. route_role='import-export' must come from an owner-confirmed route mapping. field_paths must be an already-parsed array of strings from authorised captured fields; do not invent it from a path/status or decode it repeatedly to force a match. Forge metadata: time_utc, asset_id, request_id, method, status, route_role, field_paths. These are not native Payload log columns. Preserve private raw sources and parsing notes; omit secrets and content from shared output. Save this helper as payload_field_path_triage.py: ```python """Metadata triage of a Forge-normalised local request window, never an exploit.""" import json import sys from pathlib import Path SEGMENTS = {'__proto__', 'constructor', 'prototype'} LIMIT = 4 * 1024 * 1024 def inspect(path): with Path(path).open('rb') as handle: raw = http://handle.read(LIMIT + 1) if len(raw) > LIMIT: raise ValueError('size') rows = json.loads(raw) if not isinstance(rows, list) or any(not isinstance(r, dict) for r in rows): raise ValueError('request array') pivots = []; gaps = 0; plugin_rows = 0 for row in rows: if row.get('route_role') != 'import-export': continue plugin_rows += 1 paths = row.get('field_paths') if not isinstance(paths, list) or any(not isinstance(p, str) for p in paths): gaps += 1 continue found = sorted({part for path in paths for part in path.split('.') if part in SEGMENTS}) if found: item = {k: row.get(k) for k in ['time_utc', 'asset_id', 'request_id', 'method', 'status']} item['prototype_sensitive_segments'] = found pivots.append(item) return {'rows': len(rows), 'plugin_rows': plugin_rows, 'plugin_rows_without_usable_field_paths': gaps, 'pivots': pivots} if __name__ == '__main__': try: if len(sys.argv) != 2: raise ValueError('usage') print(json.dumps(inspect(sys.argv[1]))) except (OSError, ValueError, UnicodeError, RecursionError) as exc: print('Input/schema check failed: ' + type(exc).__name__, file=sys.stderr) sys.exit(3) ``` ```sh python3 payload_field_path_triage.py payload-request-window.json ``` Output keeps selected metadata and matched vocabulary, omitting full paths/bodies. Missing field capture is a counted gap; non-plugin roles are skipped. Empty results give no clean verdict. This is local string comparison, with no evaluation, prototype mutation or target contact. Translate this relationship into the deployed SIEM schema and validate retained events with the owner. These helpers are not deployed Sigma/KQL/WAF rules or a substitute for the patched code. 📍 IOC, EXPLOITATION AND CONFIDENCE STATUS No verified CVE-specific malicious domain, IP, hash or command-line set was found in the primary material reviewed. Prototype-sensitive segment names and plugin routes are behavioural pivots; they are ordinary technical strings in other contexts. Do not turn the patch's rejected vocabulary into a universal malicious-indicator list. Standalone PoC and field exploitation were not verified here. A discovery badge is not a checked exploit artefact or incident report. Public patch material explains repair, not a deployed attack. Confirmed: vendor/CNA scope, unauthenticated potential RCE, v4.0 score/vector, train-specific remedies, stable patch membership, published canary package metadata and reviewed source behaviour. Forge analysis: the six-stage triage, correlations and acceptance criteria. Unknown: exact RCE gadget, real deployment state, usable body/job/runtime telemetry and any organisation's compromise state. 💜 CYBERFORGE VERDICT A selected field should remain data throughout the export. For an exposed affected deployment, establish plugin enablement quickly, repair the correct package train and verify every serving worker/replica. Then follow suspicious selections into attributable runtime effects. Keep repair evidence and historical investigation evidence distinct; a quiet incomplete hunt is not a clean-system certificate. 🔗 PRIMARY SOURCES Vendor advisory: https://github.com/payloadcms/payload/security/advisories/GHSA-qf28-8hc6-vwrp CVE record: https://www.cve.org/CVERecord?id=CVE-2026-105844 Referenced patch: https://github.com/payloadcms/payload/commit/a742140ab4fca3160f7f83e9e7d996552ffc3b5a First fixed stable release: https://github.com/payloadcms/payload/releases/tag/v3.88.0 NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-105844 CISA catalogue: https://www.cisa.gov/known-exploited-vulnerabilities-catalog FIRST: https://api.first.org/data/v1/epss?cve=CVE-2026-105844 Plugin docs: https://payloadcms.com/docs/plugins/import-export Patched field-path guard: https://github.com/payloadcms/payload/blob/v3.88.0/packages/plugin-import-export/src/utilities/fieldPath.ts Configured REST prefix: https://payloadcms.com/docs/rest-api/overview npm ls reference: https://docs.npmjs.com/cli/v11/commands/npm-ls #CyberForge #CVE #BlueTeam #SOC #PayloadCMS #DFIR
