← Back to Feed CACHED · 2026-06-21 09:08:26 · CACHE_KEY CVE-2026-4020
CVE-2026-4020 · CWE-200 · Disclosed 2026-03-31

Gravity SMTP ≤ 2.1.4 — Unauthenticated Sensitive Information Exposure via REST API…

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

An open filing cabinet at the front door labeled `wp-json` — and the SendGrid keys are in the top drawer

Gravity SMTP registers a REST route at /wp-json/gravitysmtp/v1/tests/mock-data whose permission_callback is hardcoded to __return_true. Any unauthenticated visitor who appends ?page=gravitysmtp-settings gets back ~365 KB of JSON: PHP version and extensions, web server, document root, DB engine/version, WordPress core version, every active plugin and theme with versions, DB table prefix, and — crucially — the API tokens and credentials configured for whatever mail transport the site uses (SendGrid, Mailgun, SES, SMTP2GO, Postmark, etc.). Affects all versions ≤ 2.1.4; fixed in 2.1.5.

Vendor's HIGH (7.5, C:H only) is technically defensible — direct CIA impact is confidentiality. In practice it understates the chain. The disclosed connector_data blob includes live transport credentials, which means this is not just recon; it's a one-shot credential leak that pivots into the victim's email infrastructure (sender reputation hijack, phishing-as-the-brand, password-reset interception). Wordfence has blocked 17M+ exploit attempts; CrowdSec flipped it to background-noise status by June 1, 2026, with 412 distinct attacker IPs and 83% of observed intent classified as infrastructure takeover. We nudge to 8.2.

"Unauth GET that hands you the System Report plus configured SMTP API keys. 17M+ exploit attempts blocked. Patch now, then rotate every key the plugin touched."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Identify Gravity SMTP installs

Mass-scan WordPress targets for the plugin's presence — either by hitting the REST namespace /wp-json/gravitysmtp/v1/ and reading the 200/404, or by fingerprinting plugin assets under /wp-content/plugins/gravitysmtp/. Trivially automated with httpx, nuclei, or any WordPress recon framework.
Conditions required:
  • Internet-reachable WordPress site
  • Gravity SMTP plugin installed and active
  • REST API not blocked by WAF rule
Where this breaks in practice:
  • Sites behind Cloudflare WAF Managed Rules may block the discovery probe
  • REST API disabled via disable-json-api or similar plugin
Detection/coverage: Wordfence and Patchstack signatures fire on the request URI; nuclei template CVE-2026-4020 available publicly
STEP 02

Hit the unauthenticated endpoint

Issue an HTTP GET to https://target/wp-json/gravitysmtp/v1/tests/mock-data?page=gravitysmtp-settings. The permission_callback returns true unconditionally, so no nonce, no cookie, no auth header is required. Response is ~365 KB of JSON.
Conditions required:
  • Endpoint reachable
  • Plugin version ≤ 2.1.4
Where this breaks in practice:
  • Cloudflare/Sucuri/Wordfence virtual patch can drop the request
  • Some hosts rate-limit /wp-json/ aggressively
Detection/coverage: Atomic Edge and pentest-tools.com published PoCs; signature is a simple URI match
STEP 03

Parse the System Report

Extract connector_data from the JSON. Each configured transport carries its credentials in cleartext — SendGrid API key, Mailgun domain+key, AWS SES access_key/secret, SMTP2GO token, raw SMTP user/pass, etc. Also harvest plugin/theme inventory for follow-on CVE pivoting.
Conditions required:
  • Site has at least one mail transport configured
  • Credentials were entered through the plugin UI (not constants in wp-config.php)
Where this breaks in practice:
  • Sites using only the default wp_mail() SMTP fallback have no API keys to leak
  • Rare hardened setups inject keys via env vars not stored in plugin options
Detection/coverage: No detection at this step — parsing happens off-target
STEP 04

Weaponize the stolen credentials

Use SendGrid/Mailgun/SES API keys to send phishing from the victim's verified domain, bypassing SPF/DKIM/DMARC because the mail is genuinely signed. Or use the credentials to enumerate the victim's mail provider tenant for further abuse. Reported intent in 83% of CrowdSec-observed cases is downstream infrastructure takeover.
Conditions required:
  • Stolen key still valid (not rotated)
  • Provider tenant not IP-restricted
Where this breaks in practice:
  • SendGrid/Mailgun anomaly detection may rate-limit a new sending pattern
  • AWS SES sandbox accounts cap sending to verified addresses
Detection/coverage: Provider-side anomaly alerts; outbound spam reports; DMARC RUF reports if monitored
STEP 05

Pivot via the leaked plugin inventory

The leaked plugin/theme/version list lets the attacker query a CVE database for known-vulnerable companions on the same host. Common follow-on: chain into an RCE in an outdated form/builder/SEO plugin, or a known auth bypass, for full WordPress compromise.
Conditions required:
  • Site runs at least one other vulnerable plugin
  • No virtual patching layer
Where this breaks in practice:
  • Sites on managed WordPress hosts (WP Engine, Kinsta, Pressable) get automatic virtual patches
Detection/coverage: Depends on the follow-on CVE; not detectable from the disclosure itself
03 · Compensating Control

1
Update Gravity SMTP to 2.1.5 across every WordPress estate host within 3 days — This is the only true fix. The patch adds a proper capability check to the REST permission_callback. Per noisgate mitigation SLA (HIGH = 30 days) we would normally allow 30 days, but active mass exploitation overrides — patch within 3 days.
2
Rotate every credential the plugin ever stored — Assume disclosure. Rotate SendGrid/Mailgun/SES/Postmark/SMTP2GO API keys, raw SMTP passwords, and any reply-to webhook tokens. Do this *before* upgrading on any host that has been internet-reachable since 2026-03-31. Patching does not invalidate already-stolen keys.
3
Drop the route at the edge / WAF until upgraded — Add a deny rule on the URI pattern ^/wp-json/gravitysmtp/v1/tests/mock-data at Cloudflare, AWS WAF, F5, or NGINX. Effective within minutes across the fleet and covers slow-to-patch sites.
4
Review mail-provider tenant logs from 2026-03-31 onward — In SendGrid/Mailgun/SES, look for sends from unexpected source IPs or unusual send volumes. If found, treat the tenant as compromised, not just the WordPress site.
5
Block known attacker source IPs at the edge — Pull CrowdSec community blocklist or GreyNoise tags for CVE-2026-4020 scanners; the population is small (~400 IPs) and concentrated in cloud ASNs FR/NL/US.
What doesn't work
  • Disabling xmlrpc.php — this CVE is in the WP REST API, not XML-RPC.
  • Forcing wp-admin behind HTTP basic auth — the vulnerable endpoint is under /wp-json/, not /wp-admin/.
  • Relying on DISALLOW_FILE_EDIT or admin lockdown plugins — the bug requires no admin session at all.
  • Rotating WordPress admin passwords alone — the leak is provider API keys, not WP credentials.
04 · Intelligence Metadata

The supporting signals.

In-the-wild statusActive mass exploitation since 2026-05-27 per CrowdSec; 17M+ attempts blocked by Wordfence since early May 2026
Attacker telemetry412 distinct source IPs May 27–Jun 1, 2026 (FR/NL/US cloud), classified as Background Noise by Jun 1 — fully commoditized
Public PoCAtomic Edge PoC, pentest-tools.com module, and public nuclei template — single-request curl-grade exploit
EPSS0.0298 (~~3rd percentile~~) — *score is stale*; real-world exploitation already saturating, expect EPSS to spike
KEV statusNot yet KEV-listed as of 2026-06-21, but meets the mass-exploitation criterion; expect addition
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N — confidentiality-only, but C:H is fair given API key leak
Affected versionsGravity SMTP ≤ 2.1.4 (all releases up to and including)
Fixed version2.1.5 — adds capability check to permission_callback
Install base~100,000 active installs of Gravity SMTP per WordPress.org plugin directory; victim mix 55% commerce / 39% SOHO (CrowdSec)
Disclosed2026-03-31 (Wordfence Threat Intelligence advisory; GHSA-jxfc-8wcq-xxcg)
DiscovererReported via the Wordfence Bug Bounty program; vendor Gravity Forms LLC

Sources.

  1. GitHub Security Advisory GHSA-jxfc-8wcq-xxcg
  2. CrowdSec — CVE-2026-4020 Vulntracking Report
  3. The Hacker News — Gravity SMTP exploit coverage
  4. Atomic Edge — PoC walkthrough
  5. pentest-tools.com — exploit module
  6. SentinelOne Vulnerability DB
  7. Managed-WP advisory (2026-03-31)
  8. The Next Web — 100k WordPress sites at risk
05 · The Call

Final Verdict
= UNCHANGED to HIGH (8.2/10)

Why this verdict

  • Zero friction on the chain: unauthenticated, single GET, no nonce, no UA filtering, no rate-limit by default. The vendor's 7.5 already assumes AV:N/PR:N/UI:N — this is the realistic case, not the worst case.
  • The blob contains credentials, not just fingerprints. SendGrid/Mailgun/SES tokens give the attacker *signed* outbound email from the victim's domain, which bypasses SPF/DKIM/DMARC and is worth more on the criminal market than the WordPress host itself.
  • Mass exploitation is here, not theoretical. 17M+ blocked attempts (Wordfence), 412 attacker IPs in 5 days (CrowdSec), public nuclei template. EPSS of 0.0298 is stale and will spike.
  • Role multiplier: Gravity SMTP's role is mail-transport credential storage. ≥10% of installs occupy this 'secret-bearing' role *by definition of installing the plugin*. The high-value-role outcome (provider-tenant takeover, brand-spoofed phishing) is the typical outcome, not the edge case — that floors the verdict at HIGH.
  • Why not CRITICAL: the primitive does not produce code execution on the WordPress host itself, and impact is confined to outbound email provider and stale recon data. Pivot to RCE requires a separate vulnerable plugin on the same host.

Why not higher?

Direct impact is confidentiality only — no code execution, no integrity write, no service denial on the WordPress instance itself. CRITICAL would imply fleet- or domain-scale takeover of the WordPress host, which requires a chained second CVE. The leaked credentials are damaging but bounded to whichever mail-provider tenant they unlock.

Why not lower?

MEDIUM would imply low real-world exploitability or non-sensitive disclosure. Neither applies: the chain is a single unauthenticated GET, the disclosed data includes live third-party API keys, and there are 17M+ logged exploit attempts in the wild. Downgrading would mis-rank this against genuinely low-noise info-disclosure bugs.

06 · Verification

Crowdsourced verification payload.

Run this from an auditor workstation (not the target) against each WordPress host you own. Invoke as ./check-cve-2026-4020.sh https://site.example.com. No credentials needed — the test exercises the same unauthenticated path an attacker would.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# noisgate verification — CVE-2026-4020 (Gravity SMTP <= 2.1.4)
# Usage: ./check-cve-2026-4020.sh https://target.example.com
# Exits: 0 PATCHED | 1 VULNERABLE | 2 UNKNOWN
set -u

TARGET="${1:-}"
if [[ -z "$TARGET" ]]; then
  echo "Usage: $0 https://target" >&2; exit 2
fi

UA="noisgate-check/1.0"
URL="${TARGET%/}/wp-json/gravitysmtp/v1/tests/mock-data?page=gravitysmtp-settings"

# 1. Probe the vulnerable endpoint.
RESP=$(curl -sk -A "$UA" -o /tmp/gsmtp_$$.json -w '%{http_code} %{size_download}' --max-time 20 "$URL") || {
  echo "UNKNOWN ($TARGET) — network error"; rm -f /tmp/gsmtp_$$.json; exit 2;
}
CODE=$(echo "$RESP" | awk '{print $1}')
SIZE=$(echo "$RESP" | awk '{print $2}')

# 2. Decide.
if [[ "$CODE" == "200" && "$SIZE" -gt 50000 ]]; then
  if grep -qE 'connector_data|system_report|gravitysmtp' /tmp/gsmtp_$$.json; then
    echo "VULNERABLE ($TARGET) — endpoint returned ${SIZE}B of System Report JSON"
    rm -f /tmp/gsmtp_$$.json; exit 1
  fi
fi

if [[ "$CODE" == "401" || "$CODE" == "403" ]]; then
  echo "PATCHED ($TARGET) — endpoint returned $CODE"
  rm -f /tmp/gsmtp_$$.json; exit 0
fi

if [[ "$CODE" == "404" ]]; then
  echo "PATCHED ($TARGET) — plugin not installed or REST route absent"
  rm -f /tmp/gsmtp_$$.json; exit 0
fi

echo "UNKNOWN ($TARGET) — HTTP $CODE, ${SIZE}B (inspect /tmp/gsmtp_$$.json manually)"
exit 2