← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2026-28783 · CWE-94 · 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 locked inside the maintenance closet

CVE-2026-28783 is a Twig blocklist-bypass in Craft CMS that can turn privileged template access into PHP function execution. The affected lines are Craft 4 from 4.0.0-RC1 up to before 4.17.0, and Craft 5 from 5.0.0-RC1 up to before 5.9.0; the advisory text also references 4.17.0-beta.1 and 5.9.0-beta.1 during disclosure. The bug sits in Craft’s handling of non-Closure Twig arrow functions, where the denylist missed dangerous PHP-call paths.

The vendor/NVD-style 9.1 CRITICAL score overstates enterprise urgency because the decisive prerequisite is not network reachability, it is *already having high-value application privileges* or a compromised admin. In real fleets, PR:H here means post-initial-access, post-credential-theft, or a bad production practice like allowAdminChanges=true; that narrows the reachable population hard enough to downgrade this from internet-burner to authenticated-abuse.

"Powerful impact, narrow path: this is an RCE for someone who already has privileged Craft access."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Find a reachable Craft control plane

The attacker identifies a Craft CMS instance and maps where privileged workflows live, typically the control panel or utility endpoints. Commodity tooling like Burp Suite, Wappalyzer, or simple header fingerprinting works because many deployments still reveal X-Powered-By: Craft CMS or expose recognizable admin paths.
Conditions required:
  • Target runs Craft CMS 4.x or 5.x in an affected range
  • Attacker can reach the login or admin utility surface over the network
Where this breaks in practice:
  • Many Craft sites are public-facing, but the vulnerable action path is usually behind authenticated control-panel functionality
  • Some operators suppress product headers, move the control panel to a separate domain, or gate admin access with VPN or IP allowlists
Detection/coverage: External scanners can often fingerprint Craft, but they cannot reliably prove this CVE without version intel or authenticated testing.
STEP 02

Gain privileged app access first

This CVE is not a clean unauthenticated exploit. The attacker needs one of the vendor-stated prerequisites: allowAdminChanges enabled on production, a compromised admin account, or an account with access to the System Messages utility. In practice that means phishing an admin, stealing a session, abusing SSO drift, or starting from an already-privileged insider account.
Conditions required:
  • Compromised admin account, or
  • A role with access to the System Messages utility, or
  • allowAdminChanges enabled in production
Where this breaks in practice:
  • This is the big one: PR:H means the attacker is already deep into the app trust boundary
  • MFA, SSO conditional access, and admin network segregation all break this step before the CVE matters
  • Well-run Craft deployments explicitly set allowAdminChanges=false in production
Detection/coverage: Identity telemetry, admin login alerts, impossible-travel detections, and session anomaly detections are more likely to catch this than perimeter vuln scanners.
STEP 03

Inject malicious Twig using a permitted feature

Using the compromised privileged workflow, the attacker supplies Twig that relies on non-Closure arrow-function behavior. Tools like Burp Suite or curl are enough to place the payload through utility forms or admin-editable content paths that eventually hit Twig evaluation.
Conditions required:
  • Privileged user can modify a Twig-rendered field, template-like content, or system message path
  • The target still permits the vulnerable non-Closure arrow-function behavior
Where this breaks in practice:
  • Not every privileged role can write to every Twig-rendered surface
  • Newer projects may already have enableTwigSandbox=true, which blocks the dangerous non-Closure arrow-function route
  • Change logging and approval workflows can expose suspicious template edits
Detection/coverage: Application logs may show unexpected edits to system messages or template-bearing settings; code-repo monitoring helps if templates are managed as code instead of live-edited.
STEP 04

Bypass the blocklist and execute server-side actions

The vulnerability is the incomplete blocklist itself: unblocked PHP function paths can still be invoked from Twig. Once triggered, the attacker can move from code execution to arbitrary file reads, SSRF, SSTI-style abuse, and potentially full host compromise depending on PHP permissions and what secrets the app can access.
Conditions required:
  • The crafted Twig reaches a vulnerable interpreter path
  • The PHP runtime account has access to useful files, network targets, or execution primitives
Where this breaks in practice:
  • Containerized deployments, read-only filesystems, restricted egress, and non-privileged PHP-FPM users reduce blast radius
  • The app server may still be isolated from crown-jewel systems even after code execution
Detection/coverage: EDR on the web tier can catch child-process execution; SSRF and file-read only variants are harder and require web/app logs, unusual outbound traffic, or secret-access monitoring.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo public active-exploitation confirmation found in the sources reviewed, and CISA KEV does not list CVE-2026-28783 as of 2026-06-02.
Proof-of-concept availabilityA public exploit write-up/PoC is indexed by CXSecurity and describes authenticated abuse of Twig template access; the vendor PR #18208 also exposes the defensive mechanics.
EPSS0.00036 (~0.036% 30-day exploitation probability, user-supplied intel). That is very low, and it lines up with the heavy privilege requirement.
KEV statusNot KEV-listed. Exact-CVE searches on CISA returned no result for CVE-2026-28783; only older Craft CMS issues appear in KEV.
CVSS vector reality checkCVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H says network and low complexity, but the operational truth is the PR:H field: attacker position is already privileged inside the application.
Affected versionsGitHub/OSV list Craft CMS >= 4.0.0-RC1, < 4.17.0 and >= 5.0.0-RC1, < 5.9.0 as affected; disclosure pages also referenced 4.17.0-beta.1 and 5.9.0-beta.1 during rollout.
Fixed versionsVendor guidance points to Craft 4.17.0 and 5.9.0 as the resolved releases, with enableTwigSandbox called out as important for existing projects. I found no distro backport advisories in the reviewed sources.
Exposure populationCraft is a public-facing CMS, so the product family has real internet exposure. For context, a prior Censys Craft advisory observed 144,333 exposed Craft applications; that is population context, not a count of systems vulnerable to this CVE.
Disclosure and reporterNVD/OSV show publication on 2026-03-04; the GitHub advisory was published 2026-03-02 and credits mHe4am as reporter.
Hardening nuanceCraft’s own Securing Craft guidance says new projects have enableTwigSandbox enabled by default, while existing projects need to turn it on explicitly.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to MEDIUM (5.8/10)

The single biggest severity brake is the attacker position: this bug requires *already having privileged Craft access* or an unsafe production configuration, so it is not a practical first-hop internet exploit. The impact is absolutely real once reached, but the reachable population is much smaller than the 9.1 label implies.

HIGH Privilege requirement materially reduces exploitability in real enterprise deployments
MEDIUM Public PoC availability and internet-exposed Craft population context

Why this verdict

  • Downgrade for attacker position: PR:H is not cosmetic here; it means the attacker already owns a privileged app account, an admin session, or a misconfigured production environment.
  • Downgrade for exposure fraction: plenty of Craft sites are public-facing, but only a subset expose the exact authenticated utility path *and* delegate the needed permissions beyond trusted admins.
  • Downgrade for modern controls: MFA, SSO conditional access, admin VPN/IP gating, and EDR on web servers should stop the chain before or immediately after the vulnerable step.
  • Keep it above LOW because impact is severe: if the attacker clears the privilege hurdle, the blast radius can jump to server-side code execution, arbitrary file reads, SSRF, and secret theft.

Why not higher?

Because this is not pre-auth and not broadly wormable. A CVE that requires internal authenticated privilege in a CMS control plane is inherently narrower than the classic public-edge RCEs that actually melt patch queues.

Why not lower?

Because the post-condition is still dangerous: this can become full web-tier compromise, not just a mild information leak. Also, Craft is often internet-facing, so the set of organizations with reachable admin surfaces is non-trivial even if the exploit path itself is privileged.

05 · Compensating Control

What to do — in priority order.

  1. Set allowAdminChanges to false in production — This directly removes one of the vendor-stated prerequisites and shrinks the reachable attack surface. For a MEDIUM verdict there is no noisgate mitigation SLA — go straight to the 365-day remediation window, but this is cheap enough to do in the next normal change window.
  2. Enable enableTwigSandbox on existing projects — Craft’s own guidance says existing projects must explicitly enable this to get the stricter Twig behavior. For a MEDIUM verdict there is no mitigation SLA, so deploy during regular configuration management and complete before the remediation deadline.
  3. Strip System Messages utility access from non-admin roles — The advisory explicitly names access to the System Messages utility as a sufficient condition. Audit RBAC now, remove that permission from editor/power-user roles where it is not strictly needed, and treat completion as part of the 365-day remediation window if not already standard.
  4. Gate admin access behind VPN or IP allowlists — This adds meaningful friction to the prerequisite stage by converting a public authenticated surface into a managed admin enclave. There is no mitigation SLA for a MEDIUM verdict, but this is a strong standing control for all CMS admin planes.
  5. Alert on control-plane Twig and system-message changes — Most orgs have poor visibility into CMS administrative content changes; add logging and alerting for edits to system messages, admin utilities, and template-bearing settings. This will not prevent the bug, but it shortens time-to-detect during the long remediation window.
What doesn't work
  • A generic WAF does not reliably help because the dangerous payload is delivered through authenticated admin functionality and looks like application-authorized content, not commodity edge exploit traffic.
  • Unauthenticated perimeter scanning will miss the real risk because the decisive condition is privileged app access, not a banner or open port.
  • MFA alone is not enough if the attacker already has a stolen admin session cookie or is operating as a malicious insider with valid privileges.
06 · Verification

Crowdsourced verification payload.

Run this on the Craft application host from the project root, or pass the root as an argument: ./check-cve-2026-28783.sh /var/www/craft-app. It needs local read access to composer.lock, vendor/, and config/; no root is required unless file permissions are locked down.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check-cve-2026-28783.sh
# Detect likely exposure to CVE-2026-28783 in Craft CMS.
# Outputs one of: VULNERABLE / PATCHED / UNKNOWN
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN, 3=usage/runtime error

set -u

APP_ROOT="${1:-.}"

if ! command -v php >/dev/null 2>&1; then
  echo "UNKNOWN - php not found; cannot evaluate Craft version"
  exit 2
fi

if [ ! -d "$APP_ROOT" ]; then
  echo "UNKNOWN - app root not found: $APP_ROOT"
  exit 3
fi

get_version_from_composer_lock() {
  local lock="$1/composer.lock"
  [ -f "$lock" ] || return 1
  php -r '
    $f = $argv[1];
    $j = json_decode(file_get_contents($f), true);
    if (!$j) exit(1);
    $pkgs = array_merge($j["packages"] ?? [], $j["packages-dev"] ?? []);
    foreach ($pkgs as $p) {
      if (($p["name"] ?? "") === "craftcms/cms") {
        $v = $p["version"] ?? "";
        $v = ltrim($v, "vV");
        echo $v;
        exit(0);
      }
    }
    exit(1);
  ' "$lock"
}

get_version_from_vendor() {
  local cj="$1/vendor/craftcms/cms/composer.json"
  [ -f "$cj" ] || return 1
  php -r '
    $f = $argv[1];
    $j = json_decode(file_get_contents($f), true);
    if (!$j) exit(1);
    $v = $j["version"] ?? "";
    if (!$v) exit(1);
    echo ltrim($v, "vV");
  ' "$cj"
}

get_version_from_cli() {
  if [ -f "$1/craft" ]; then
    (cd "$1" && php craft --version 2>/dev/null) | awk '{print $NF}' | sed 's/^v//'
    return 0
  fi
  return 1
}

version_compare_php() {
  # args: v1 op v2
  php -r 'exit(version_compare($argv[1], $argv[3], $argv[2]) ? 0 : 1);' "$1" "$2" "$3"
}

is_affected() {
  local v="$1"
  if version_compare_php "$v" ">=" "4.0.0-RC1" && version_compare_php "$v" "<" "4.17.0"; then
    return 0
  fi
  if version_compare_php "$v" ">=" "5.0.0-RC1" && version_compare_php "$v" "<" "5.9.0"; then
    return 0
  fi
  return 1
}

is_in_fixed_major() {
  local v="$1"
  if version_compare_php "$v" ">=" "4.17.0" && version_compare_php "$v" "<" "5.0.0"; then
    return 0
  fi
  if version_compare_php "$v" ">=" "5.9.0"; then
    return 0
  fi
  return 1
}

sandbox_enabled() {
  # Best-effort check only. Existing projects may need this explicitly enabled.
  local root="$1"
  local files=""
  [ -f "$root/config/general.php" ] && files="$files $root/config/general.php"
  if [ -d "$root/config" ]; then
    # shellcheck disable=SC2046
    files="$files $(find "$root/config" -maxdepth 2 -type f \( -name '*.php' -o -name '*.php.dist' \) 2>/dev/null)"
  fi
  [ -n "$files" ] || return 1
  grep -Eqs "enableTwigSandbox[[:space:]]*=>[[:space:]]*true|['\"]enableTwigSandbox['\"][[:space:]]*=>[[:space:]]*true" $files
}

VERSION=""
VERSION="$(get_version_from_composer_lock "$APP_ROOT" 2>/dev/null)" || true
if [ -z "$VERSION" ]; then
  VERSION="$(get_version_from_vendor "$APP_ROOT" 2>/dev/null)" || true
fi
if [ -z "$VERSION" ]; then
  VERSION="$(get_version_from_cli "$APP_ROOT" 2>/dev/null)" || true
fi

if [ -z "$VERSION" ]; then
  echo "UNKNOWN - unable to determine Craft CMS version from composer.lock, vendor metadata, or CLI"
  exit 2
fi

if is_affected "$VERSION"; then
  echo "VULNERABLE - Craft CMS version $VERSION is in the affected range for CVE-2026-28783"
  exit 1
fi

if is_in_fixed_major "$VERSION"; then
  if sandbox_enabled "$APP_ROOT"; then
    echo "PATCHED - Craft CMS version $VERSION is fixed and enableTwigSandbox appears enabled"
    exit 0
  else
    echo "UNKNOWN - Craft CMS version $VERSION is fixed, but enableTwigSandbox was not confirmed from local config"
    exit 2
  fi
fi

echo "PATCHED - Craft CMS version $VERSION is outside the affected major/version ranges for CVE-2026-28783"
exit 0
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: do not let the 9.1 stamp hijack your queue. Reclassify this as a privileged-abuse Craft issue, quickly identify any internet-facing Craft 4/5 estates, and use normal change control to harden allowAdminChanges, enableTwigSandbox, and System Messages permissions; for a MEDIUM verdict there is no noisgate mitigation SLA — go straight to the 365-day remediation window. Your noisgate remediation SLA is ≤365 days for the actual upgrade to 4.17.0 or 5.9.0, but public-facing CMS nodes with broad admin access deserve the front of that annual window, not the back.

Sources

  1. NVD CVE-2026-28783
  2. GitHub Security Advisory GHSA-5fvc-7894-ghp4
  3. Craft CMS pull request #18208
  4. OSV entry for CVE-2026-28783
  5. Craft knowledge base: Securing Craft
  6. CISA Known Exploited Vulnerabilities Catalog
  7. FIRST EPSS overview
  8. Censys advisory for prior Craft CMS exposure context
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.