Skip to content

Extraction-before-verification, Origin-derived reset links, and client-supplied proofs (Sept 17 21:31Z / Sept 18 00:31Z waves)

Source: hourly offensive-security scan of GitHub Security Advisories (reviewed + unreviewed), 2026-09-17T21:31Z and 2026-09-18T00:31Z waves.

The durable axis across these records is a trusted pipeline step that fires before — or instead of — its own verification gate: Grafana extracts plugin archives before signature verification, SOGo builds password-reset URLs from a request-supplied header, and two WordPress plugins accept a client-supplied string as proof of payment or ownership. In each case the control exists, is even correct in isolation, and simply never gates the operation that matters.

Advisory table

Product GHSA CVE Sev. Boundary
Grafana OSS / Enterprise GHSA-xp8x-3q7w-cpqr CVE-2026-15815 high Plugin archives are extracted before their signature is verified. A crafted archive chains relative symlink entries to escape the plugin install directory and write arbitrary files — including an executable backend binary — outside it. The dropped executable then runs with the privileges of the Grafana server process (RCE). A valid signature does not prevent the write; the "legitimate-looking plugin" path is as exposed as the grafana-cli / GF_INSTALL_PLUGINS / preinstall-config paths. Enterprise inherits it via the shared OSS extraction code.
SOGo GHSA-gqgv-6fx9-g8mx CVE-2026-93453 high Password-reset links are constructed using the client-supplied Origin header as the URL authority. An unauthenticated attacker submits a recovery request for a victim address with a malicious Origin, and the valid reset token is mailed inside a link pointing at attacker infrastructure → account takeover via the victim's own recovery mailbox.
Amelia (WordPress, Premium) GHSA-g24p-55f9-p54c CVE-2026-14311 medium /users/customers/<id> has no ownership verification: a wpamelia-provider role user can view and modify arbitrary customers, including password reset. WordPress accounts up to Editor are takeover-able if they ever made an Amelia booking — the plugin's customer record becomes a shadow credential store for the core account.
Motors – Car Dealership & Classified Listings (WordPress) GHSA-v742-3q56-7wc3 CVE-2026-16750 medium mvl_ajax_dealer_load_cars() has no authorization check: unauthenticated callers retrieve draft, pending, private, and future listings belonging to arbitrary users. Status-state (draft/pending/private/future) is a pure disclosure axis that listing fuzzers miss when they only request publish.

Folded to an existing page: SOGo also lands on the May 19 Apostrophe Host-header password-reset page (same class, different header); the Amelia/Motors items fold into the July 28 WordPress boundary page. Tracked without publication: GHSA-jh5x-pggf-m4cr / CVE-2026-93436 (vLLM prefill/decode-disaggregated decode-worker memory exhaustion via max_tokens=0 on rejected requests — DoS-only, no persistence), GHSA-cxv8-vf43-xcw5 / CVE-2026-93394 (libmongoc SCRAM sends the client proof after a detected nonce mismatch → MITM offline cracking, TLS-mitigated), GHSA-4vx9-xvv3-fqhj / CVE-2026-93454 (Aureus ERP payment-term note raw-HTML stored XSS, generic create-permission→all-user-execution), Chrome 153 / Microsoft Patch-Tuesday GHSA sync waves (one-line CWE descriptions, no technical detail to derive operator workflow from), O-RAN-SC SMO OAM, HCL DevOps Deploy non-ASCII redaction miss, snappy-java/go-openapi/swag memory-safety/stack-exhaustion pairs, HAPI FHIR SHC DEFLATE infinite-loop pair (GHSA-gq9c-wmrm-5hvr/GHSA-3w98-rrpr-fprr), Apache Karaf ThreadLocal classloader-pinning leak, MongoDB EF Core encryption-silent-loss trio, mayfly-go pair, Grafana Geomap MapLibre style-config stored XSS (GHSA-35gc-393c-vjxf) — revisit if it gains an offline-hostile-server angle beyond the already-published external-resource trust rule.

Why this is worth an operator page

  • "Verified" is not "verified-before-use". The Grafana record is the cleanest instance of an ordering bug the wiki keeps meeting (Nuclei mtime-cache Sept 16, plugin signature paths throughout): the signature check is real, but it runs after the dangerous side effect (extraction), so the archive has already written outside the sandbox before anyone looks at it. Audit rule for every install/import/load pipeline: enumerate the order of (fetch → extract/write → verify → activate), and test whether the verify step can be made irrelevant by a payload that survives extraction. For supply-chain shape: an operator installing an apparently legitimate plugin is affected — so on authorized engagements, the question "does this Grafana ingest third-party archives?" is itself a finding on hosts with plugin-management access.
  • Origin is the sibling of Host in token-link construction. Every reset/invite/verification-link generator that formats ${scheme}://${authority_from_request} is a token-exfiltration primitive. SOGo shows it via Origin; the Apostrophe page shows it via Host; the Nezha page shows it via redirect_uri. The probe generalizes: fire the recovery request against a lab target with a hostile Origin and a hostile Host and inspect the generated link (or the outbound email through an owned recovery mailbox) before touching real users.
  • Client-supplied strings accepted as payment/ownership proof are always a BOLA/ATO ladder. Amelia's customer endpoint lets a low-privilege plugin role reset passwords of any customer — and because WordPress users who ever booked become Amelia customers, the plugin boundary reaches back into core-account authority. Recon heuristic for any SaaS-y WordPress plugin: map plugin-internal object collections (/users/customers/<id> style) and test cross-object reads/writes from the lowest authenticated plugin role, checking whether any plugin-side mutation (password reset, email change) affects the core WordPress session.
  • Post/listing status states are unauthenticated disclosure targets. Motors' handler leaks draft/pending/private/future content anonymously. When inventorying wp-admin/admin-ajax.php actions, enumerate the handler's status parameters (post_status, status, state) and request non-public values explicitly — publish-only fuzzing misses this class entirely.

Validation workflow (authorized scope only)

Lab installs, owned mailboxes, disposable objects

The Grafana fixture requires a dedicated lab instance you are authorized to compromise — the validation is a real file write and real code execution. Never run it against a production dashboard, and never use real plugin-signing keys.

  1. Grafana ordering proof (lab only): on a disposable Grafana, install a crafted plugin archive containing chained relative symlinks that land a benign marker executable outside the plugins directory. Evidence: resolved-path table (archive entry → symlink chain → final outside path), marker file ownership/mode, and the single fact that extraction completed while the signature error was still reported. Stop at "arbitrary write + binary dropped"; do not escalate the Grafana process or persist.
  2. Grafana surface triage (no exploit): version fingerprint (login page / /api/health), whether third-party plugin installation is possible (grafana-cli, GF_INSTALL_PLUGINS, preinstall config presence), and whether signature enforcement is configured at all. Presence-only facts for the report.
  3. Reset-link header probe (lab only): on an authorized SOGo (<5.12.11), submit a recovery request for a lab address you control with Origin: https://<owned-callback>; record the emailed link's authority. Repeat with hostile Host and with both headers absent. Decision table: header → emitted link authority → token present? Never target real mailboxes; redact tokens from all evidence.
  4. Amelia-class BOLA: two disposable lab users — one wpamelia-provider, one customer-holder. From the provider token, read and modify the other customer's record and attempt the password-reset action against a lab core account, recording whether a core WordPress session/credential changes. Marker-only mutations; snapshot and restore rows.
  5. Motors-class status disclosure: on an authorized site, call mvl_ajax_dealer_load_cars() (admin-ajax, no cookie) and compare returned listings against a logged-in control, restricted to a seeded lab listing in draft status. Presence-only proof (post ID + status), never bulk-extract real listings.

September 22 follow-up: the origin leg can also be a request parameter

  • Gladys Assistant <5.1.0 reset-link poisoning via origin parameter (CVE-2026-93340, GHSA-47ff-wp9w-g26c, 6.8): the forgot_password endpoint accepts a client-supplied origin parameter with no server-side validation and embeds it in the emailed reset link — an unauthenticated attacker requests recovery for any account (including administrators) with their own origin and receives the victim's valid token. Third spelling of the same primitive: SOGo used the Origin header, Apostrophe the Host header, Nezha the redirect_uri, and Gladys a plain body/query parameter. Sweep generalizes accordingly: for every recovery/invite/verification flow, test hostile authority in Origin, Host, and every plausible body/query key named origin, redirect, host, base_url, referer — and the parameter-absent control.

Reporting checklist

  • Exact product/plugin version, transport (grafana-cli vs API vs env var; admin-ajax action name; REST route), and auth state per leg.
  • For ordering bugs: the pipeline sequence table (fetch/extract/verify/activate) with the observed side effect placed at each step — the finding is the position of the write, not just the write.
  • For header-derived links: which header controlled which URL component, and the token-bearing-vs-token-less variant of the generated link (redacted).
  • For plugin BOLA: the minimum authenticated role, the object-collection route, and whether the plugin-side mutation reaches core-account authentication.
  • Bounded impact language: extraction-with-write-outside-install-dir is "arbitrary file write → code execution as the service account" only when the dropped binary execution path is shown; an unauthenticated draft-listing read is disclosure, not ATO.

October 10 follow-up: SkillHub account-merge flow returns the verification token to the requester — identity-verification proofs that never leave the attacker's session (CVE-2026-108550 / GHSA-wq7r-c6mg-rqv2, high, fixed 0.2.22)

SkillHub's AccountMergeService merge-initiate endpoint accepts an attacker-supplied target username or OAuth identity, generates the verification token whose whole purpose is to prove the victim owns that identity — and returns it in the API response instead of (or in addition to) delivering it to the victim's bound contact. The attacker calls confirm with the returned token and inherits the victim's API tokens, roles, and namespace ownership. Authenticated-low-priv → full account takeover with no victim interaction.

  • This is the extract-before-verify ordering bug wearing the identity-proof costume (this page's Grafana leg proved verify-after-extract): the token-generation step and the token-delivery step are separate code paths, and the "proof" primitive fails whenever the generate response is visible to the party that will consume the token. Fourth generalization beyond this page's reset-link legs (SOGo Origin header, Apostrophe Host, Gladys origin parameter): even when the token itself is unguessable, the initiate call leaks it if the service treats "who requested the merge" and "who must approve the merge" as the same principal.
  • Sweep rule for every product with connect/merge/link/account-recovery flows (SSO link, marketplace account merge, org invite, email change): call the initiate endpoint with your own session and a second disposable account as the target and check whether the response body, response headers, or a queryable status endpoint exposes the verification token/code that is supposed to land only in the other account's mailbox/device. Evidence = response capture (redact) + successful confirm against the disposable victim. Positive across any of these is a standing ATO path.
  • Design-fingerprint to grep in open-source merges/linking code: the initiate handler builds a token, persists it keyed by requester (not by target+requester pair), and returns or echoes it; or the confirm handler only checks token validity, never that the two identities consented from their own sessions.

October 13 follow-up: Grafana provisioning/data-source legs — status-subresource write drift, data-source-proxy confused deputy, and a plugin-trust DoS trio (4 GHSAs, grafana.com advisories published 2026-10-13T18:30Z)

Same product, new boundary families, same page (this page's Grafana leg was the extract-before-verify plugin RCE):

  • Read-only grant, writable status subresource (GHSA-pm7c-5ghp-r9fq / CVE-2026-15577): a user or service account with read-only access to Git Sync (provisioning) connections can modify the connection's configuration through its status subresource. Kubernetes-style status subresources are the classic verb-gate blind spot — RBAC/role models written for get/list/update forget that status/{subresource} is a separate write path, and here it accepted full config mutation. Sweep rule: for any API with subresources (/status, /scale, /spec, /token, /history), test each verb × each subresource from the lowest-privilege role; a read-only principal succeeding any POST/PUT/PATCH on a subresource is the finding. Feature-toggle precondition (experimental provisioning flag on 12.4) = fingerprint before claiming.
  • Data-source proxy = standing confused deputy with the server's cloud identity (GHSA-5wf9-m99g-f6xj / CVE-2026-93294): a user with query access to an Azure Monitor data source can drive its resource proxy to call ARM APIs beyond those Grafana needs, using the data source's Azure identity. The user never holds those permissions; the proxy holds them and will spend them on the user's behalf. Generalizes to every product with a proxied cloud data source (CloudWatch/Azure/GCS/BigQuery plugins, connector frameworks): enumerate the proxy's callable surface, not just the intended queries, and compare against the identity's cloud role — impact is bounded by that role, which is routinely over-provisioned. Prove on owned stacks: request a harmless-but-unintented ARM read (e.g. resource-group listing) through the proxy and record whether it succeeds; never enumerate real customer inventories.
  • Trusted-upstream amplification DoS pair (GHSA-w2wx-887f-65f8 / CVE-2026-95502: a configured Git Sync server returns oversized/highly-compressed responses to exhaust Grafana memory; GHSA-5mj5-pfmr-wxjp / CVE-2026-33374: Prometheus rule-conversion endpoints accept unbounded request bodies → OOM). Operator value is the shape, not the severity label: (1) "admin configures hostile server" is a supply-chain precondition worth noting wherever a product fetches from admin-chosen endpoints — the Git Sync config leg above plus this DoS leg together mean a compromised or attacker-suggested Git remote is a standing attack channel against the dashboard server; (2) conversion/translation endpoints (prometheus-rules convert style) are parse-everything/write-little routes that habitually skip body limits that normal APIs have — fuzz allocation behavior on every convert/import/transform route, authenticated-low is enough.