← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2026-28695 · CWE-1336 · Disclosed 2026-03-04

Craft is a content management system

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

This is a loaded nail gun left behind the manager’s desk, not a landmine in the parking lot

CVE-2026-28695 is a Craft CMS server-side template injection path to code execution. In affected craftcms/cms versions >=4.0.0-RC1, <4.17.0-beta.1 and >=5.8.7, <5.9.0-beta.1, the Twig create() function can instantiate attacker-chosen PHP classes; when paired with Symfony\Component\Process\Process, that becomes command execution on the server. The advisory explicitly calls out post-auth exploitation through the control panel, such as an Entry Type Title Format field, and says exploitation requires administrator permissions or access to the System Messages utility.

The vendor's HIGH label captures the technical impact, but it overshoots enterprise patch priority a bit because the decisive gate is privileged authenticated access. This is not an unauthenticated internet spray flaw, not a wormable edge bug, and not KEV-listed; it is a reliable post-auth RCE that matters most where Craft admin access is broad, shared, or externally reachable.

"Real server-side RCE, but it starts after the attacker already has privileged Craft access."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Get privileged Craft control-panel access

The attacker first needs a valid Craft session with either administrator rights or access to the System Messages utility, per the GHSA. In practice that means stolen admin credentials, SSO session theft, insider misuse, or chaining from another Craft authz bug. Weaponized tooling here is usually just a browser or Burp Suite, because the attack rides normal control-panel workflows.
Conditions required:
  • Internet or network reachability to the Craft admin/control-panel interface
  • Valid authenticated account
  • Administrator role or System Messages utility access
Where this breaks in practice:
  • This is post-initial-access by definition
  • SSO, MFA, conditional access, and IP restrictions often stop this before the vuln matters
  • Many enterprises tightly limit who can reach or use the Craft control panel
Detection/coverage: External network scanners generally cannot prove exploitability here; IAM, SSO, and admin audit logs are more useful than vuln scans at this step.
STEP 02

Reach a Twig-backed writable field

Using the control panel, the attacker places a malicious Twig payload into a field that will later be rendered as a template. The GHSA names Settings → Entry Types → Title Format as one attack path, and Craft documentation confirms System Messages bodies are also Twig-evaluated. Tooling remains low-tech: browser UI, intercepted requests in Burp, or scripted authenticated HTTP calls.
Conditions required:
  • allowAdminChanges enabled in production for the Entry Types path, or access to System Messages
  • Craft Pro if abusing customizable system messages
  • Ability to edit the relevant object or message template
Where this breaks in practice:
  • Craft explicitly recommends allowAdminChanges=false in production, which removes the easiest settings-based path
  • Not every deployment exposes these utilities to many users
  • Some organizations keep admin changes confined to non-production workflows
Detection/coverage: Content inspection is weak here; payloads are stored as normal admin content. Change-audit logs on entry types, utilities, and config changes are the best signal.
STEP 03

Instantiate Symfony Process through Twig create()

The malicious template invokes Craft's create() Twig function to build a Symfony\Component\Process\Process object with attacker-controlled constructor arguments, then triggers execution. The GitHub advisory provides a working proof-of-concept payload, so exploitation is not theoretical. This is effectively a built-in gadget chain rather than memory corruption: the app is doing exactly what the attacker asked it to do.
Conditions required:
  • Affected vulnerable version
  • Twig payload accepted and stored
  • Bundled symfony/process dependency present as expected by Craft
Where this breaks in practice:
  • Patched versions route create() through a restriction that only permits yii\base\BaseObject subclasses
  • Authenticated admin-only exploitation makes mass scanning and one-shot exploitation less likely
Detection/coverage: SCA/SBOM tools and composer-aware scanners should catch the vulnerable package version; WAF signatures are unreliable because the attack is authenticated and application-specific.
STEP 04

Trigger render and execute on the host

Once the template is rendered—such as when creating or editing an entry—the process gadget executes OS commands as the web server user. The GHSA notes this can become full server compromise, and in default Docker setups may run as root, which meaningfully increases blast radius. Follow-on tooling becomes standard post-exploitation tradecraft: shells, curl/wget, credential theft, lateral movement, and persistence.
Conditions required:
  • A render event occurs for the tainted template
  • The app host allows child process execution
  • OS-level controls do not block the spawned command
Where this breaks in practice:
  • Container hardening, read-only filesystems, seccomp, or no-new-privileges can reduce follow-on impact
  • EDR can catch unusual child processes from php-fpm, apache2, or nginx worker contexts
  • The code runs as the web app account unless the environment is misconfigured
Detection/coverage: Good EDR/host telemetry should see suspicious child processes from PHP. Web-only scanners will usually miss the final impact stage.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo confirmed active exploitation found in the sources reviewed, and not listed in CISA KEV as of 2026-05-29. That sharply separates it from Craft's earlier edge-exploited bugs.
Proof-of-concept availabilityPublic PoC exists in the GitHub advisory itself, using Twig create() plus Symfony\\Component\\Process\\Process. This is low-friction for anyone who already has the required role.
EPSSUser-supplied EPSS is 0.00027. That is extremely low and fits the real-world gating factor: privileged authenticated access. FIRST documents score and percentile availability via its API, but percentile was not confirmed in the collected source set.
KEV statusNo — not present in the CISA Known Exploited Vulnerabilities Catalog during this reassessment.
CVSS vector reality checkCVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H is honest about the big caveat: PR:H. Yes, impact is full-host-class RCE; no, it is not an internet-scale unauthenticated exploit.
Affected versionsAuthoritative package ranges are >=4.0.0-RC1, <4.17.0-beta.1 and >=5.8.7, <5.9.0-beta.1. The 5.x range starts at 5.8.7 because this flaw is described as a bypass of the earlier fix for CVE-2025-57811.
Fixed versionsFixed upstream in 4.17.0-beta.1 and 5.9.0-beta.1. This is a Composer-delivered app dependency case; no distro backport source was identified in the collected material.
Exposure / scanning realityThis CVE is hard to verify from the outside because exploitation needs auth and role context. Product exposure still matters: Censys reported 144,333 exposed Craft CMS applications in a February 2025 Craft advisory, and runZero provides inventory queries for locating Craft CMS internally—useful for scoping, but neither proves this specific flaw is reachable.
DisclosurePublished by NVD on 2026-03-04; GitHub reviewed/published the advisory on 2026-03-03 / 2026-03-04 depending on source view.
Reporter / sourcePrimary CNA/source is GitHub, Inc. via the Craft CMS advisory. No separate external researcher credit was visible in the reviewed advisory data.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to MEDIUM (5.9/10)

The single biggest severity reducer is the attacker position requirement: this bug only matters after an attacker already has a privileged Craft account or equivalent utility access. That makes it a dangerous post-auth blast-radius amplifier, not a primary intrusion path on the open internet.

HIGH Exploit preconditions are clearly documented
MEDIUM Real-world exposure of admin-capable Craft users varies a lot by deployment

Why this verdict

  • Downgraded for PR:H: the attacker must already hold administrator rights or System Messages utility access, which implies prior compromise, insider misuse, or another auth chain.
  • Further downgraded for reachability friction: the named settings path depends on allowAdminChanges in production, and Craft explicitly recommends disabling that in prod.
  • Not downgraded below MEDIUM because impact is real RCE: once the preconditions are met, exploitation is straightforward, public-PoC-backed, and can end in full host compromise.

Why not higher?

This is not an unauthenticated edge bug, not KEV-listed, and not a low-privilege user-to-root scenario. Every serious attack chain starts with the attacker already crossing your authentication and authorization boundary, which is a major downward pressure on urgency.

Why not lower?

If the attacker has the required role, this is not a nuisance bug—it is a clean path from CMS privilege to server command execution. That jump from application administration to host-level execution materially increases blast radius, especially on weakly isolated containers or shared web infrastructure.

05 · Compensating Control

What to do — in priority order.

  1. Disable admin changes in production — Set allowAdminChanges=false on production Craft instances to remove the easiest control-panel path called out in the advisory. For a MEDIUM verdict there is no noisgate mitigation SLA, so treat this as hardening you should roll in with normal config governance rather than emergency change windows.
  2. Restrict control-panel access — Put the Craft admin path behind SSO + MFA, IP allowlisting, VPN, or a reverse-proxy access policy so fewer identities can ever satisfy the vuln's first prerequisite. This directly attacks the most important risk factor: privileged authenticated reachability.
  3. Tighten utility permissions — Review who can access System Messages and similar high-trust control-panel features, and remove that access from broad admin or content teams. The GHSA explicitly treats that utility as an alternate exploitation route.
  4. Alert on PHP child processes — Create EDR/SIEM detections for php-fpm, apache2, or nginx spawning shells or process-execution children. This helps catch successful exploit attempts and follow-on activity even if the malicious payload is tucked inside legitimate CMS content.
  5. Inventory Craft by package version — Use SBOM, Composer lockfile parsing, or app inventory to identify craftcms/cms in the affected ranges and queue upgrades inside the normal remediation program. This flaw is much easier to manage through software inventory than through perimeter scanning.
What doesn't work
  • A WAF alone will not reliably save you here; the exploit is authenticated, application-specific, and lives inside normal control-panel fields.
  • Hiding the admin URL is not a control. If an attacker already has a session or valid credentials, obscurity does nothing.
  • Network-only vulnerability scanning is insufficient because scanner reach rarely includes the required authenticated role and utility context.
06 · Verification

Crowdsourced verification payload.

Run this on the target Craft host from the application root that contains composer.lock or vendor/. Invoke it as bash ./check-cve-2026-28695.sh /var/www/craft-app. It needs only read access to the app files; no root is required.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check-cve-2026-28695.sh
# Determine whether a Craft CMS installation is vulnerable to CVE-2026-28695
# Output: VULNERABLE / PATCHED / UNKNOWN
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

set -u

APP_ROOT="${1:-.}"
COMPOSER_LOCK="$APP_ROOT/composer.lock"
COMPOSER_JSON="$APP_ROOT/composer.json"

have_cmd() {
  command -v "$1" >/dev/null 2>&1
}

ver_ge() {
  [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -n1)" = "$1" ]
}

ver_lt() {
  [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]
}

extract_version_php() {
  local file="$1"
  php -r '
    $f = $argv[1];
    if (!is_file($f)) { exit(2); }
    $j = json_decode(file_get_contents($f), true);
    if (!is_array($j)) { exit(3); }
    $pkgs = [];
    if (isset($j["packages"]) && is_array($j["packages"])) $pkgs = array_merge($pkgs, $j["packages"]);
    if (isset($j["packages-dev"]) && is_array($j["packages-dev"])) $pkgs = array_merge($pkgs, $j["packages-dev"]);
    foreach ($pkgs as $p) {
      if (($p["name"] ?? "") === "craftcms/cms") {
        echo $p["version"] ?? "";
        exit(0);
      }
    }
    exit(4);
  ' "$file" 2>/dev/null
}

normalize() {
  local v="$1"
  v="${v#v}"
  printf '%s' "$v"
}

if ! have_cmd php; then
  echo "UNKNOWN: php CLI not found"
  exit 2
fi

VERSION=""
SOURCE=""

if [ -f "$COMPOSER_LOCK" ]; then
  VERSION="$(extract_version_php "$COMPOSER_LOCK")"
  if [ -n "$VERSION" ]; then
    SOURCE="$COMPOSER_LOCK"
  fi
fi

if [ -z "$VERSION" ] && [ -f "$COMPOSER_JSON" ]; then
  # Fallback: infer declared constraint, not installed version
  VERSION="$(php -r '
    $f = $argv[1];
    $j = json_decode(file_get_contents($f), true);
    if (!is_array($j)) exit(1);
    $req = $j["require"]["craftcms/cms"] ?? "";
    echo is_string($req) ? $req : "";
  ' "$COMPOSER_JSON" 2>/dev/null)"
  if [ -n "$VERSION" ]; then
    echo "UNKNOWN: only composer constraint found in $COMPOSER_JSON -> $VERSION"
    exit 2
  fi
fi

if [ -z "$VERSION" ]; then
  echo "UNKNOWN: could not determine installed craftcms/cms version from $APP_ROOT"
  exit 2
fi

RAW_VERSION="$VERSION"
VERSION="$(normalize "$VERSION")"

# Exact vulnerable ranges from advisory:
# 4.0.0-RC1 <= v < 4.17.0-beta.1
# 5.8.7 <= v < 5.9.0-beta.1

if ver_ge "$VERSION" "4.0.0-RC1" && ver_lt "$VERSION" "4.17.0-beta.1"; then
  echo "VULNERABLE: craftcms/cms $RAW_VERSION detected from $SOURCE (matches 4.x vulnerable range for CVE-2026-28695)"
  exit 1
fi

if ver_ge "$VERSION" "5.8.7" && ver_lt "$VERSION" "5.9.0-beta.1"; then
  echo "VULNERABLE: craftcms/cms $RAW_VERSION detected from $SOURCE (matches 5.x vulnerable range for CVE-2026-28695)"
  exit 1
fi

if ver_ge "$VERSION" "5.9.0-beta.1" || ver_ge "$VERSION" "4.17.0-beta.1"; then
  echo "PATCHED: craftcms/cms $RAW_VERSION detected from $SOURCE"
  exit 0
fi

echo "PATCHED: craftcms/cms $RAW_VERSION detected from $SOURCE (outside known vulnerable ranges for CVE-2026-28695)"
exit 0
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning, treat this as a post-auth CMS-to-host escalation problem: inventory every Craft instance, identify who can reach the control panel, and check whether allowAdminChanges is still enabled in production. Because the reassessed verdict is MEDIUM, there is no noisgate mitigation SLA — go straight to the 365-day remediation window; use routine change control to harden admin access and remove risky utility permissions, then complete the actual Craft upgrade within the noisgate remediation SLA of ≤365 days. If a particular site has internet-exposed admin access, shared admin accounts, or weak MFA, move that instance up locally even though the portfolio-wide rating stays MEDIUM.

Sources

  1. NVD CVE record
  2. GitHub Advisory Database JSON for GHSA-94rc-cqvm-m4pw
  3. Craft CMS fix commit e31e508
  4. Craft docs: allowAdminChanges
  5. Craft docs: System Messages are Twig-evaluated
  6. Craft supported versions / lifecycle
  7. CISA Known Exploited Vulnerabilities Catalog
  8. Censys advisory noting exposed Craft CMS population
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.