This is a land mine buried behind the front desk, not a bomb in the parking lot
CVE-2026-8135 is an insecure deserialization flaw in Concrete CMS ExpressEntryList that affects 9.5.0 and earlier and is fixed in 9.5.1. A user who already has enough Concrete privileges to add blocks can abuse the REST API's JSON handling to satisfy the _fromCIF === true gate, store a malicious serialized payload in the block's filterFields column, and get code execution when an administrator later views or edits that block.
The vendor's *technical* severity is fair for impact but too hot for patch triage across a large estate. The decisive reality check is the prerequisite stack: this is PR:H, requires a Concrete role that can add blocks, depends on a specific block path, and only becomes RCE after admin interaction with the poisoned block state. That's post-initial-access abuse with a narrower exposed population than a typical internet-reachable RCE.
4 steps from start to impact.
Obtain a privileged Concrete session
- Authenticated access to the target Concrete CMS instance
- Role permissions sufficient to add blocks to at least one area
- This is not unauthenticated remote exploitation
- Many enterprises heavily restrict CMS admin access behind SSO, VPN, IP allowlists, or delegated workflows
- If only a tiny central web team holds these rights, reachable population drops fast
<=9.5.0, but they cannot confirm exploitable role assignments. IAM, SSO, and Concrete audit logs are more useful than perimeter scanning here.Poison ExpressEntryList via REST API
curl, or Burp Suite, the attacker submits block data through the REST API path so json_decode() turns the JSON string "true" into a PHP boolean and bypasses the intended _fromCIF safeguard. That lets them write attacker-controlled serialized data into the filterFields database column for the ExpressEntryList block.- Target uses the vulnerable
ExpressEntryListblock controller - Attacker can reach the REST/API workflow used to save block configuration
- This is a product-specific bug path, not a generic CMS primitive
- The attacker still needs the exact block workflow and parameter shape
- Not every Concrete deployment uses Express features or this block type operationally
Wait for an administrator to touch the booby-trapped block
- An administrator later views or edits the poisoned block
- No user interaction in the CVSS sense, but there is still an operational trigger requirement
- Dormant or rarely managed blocks may never execute
- If content and platform admins are separated, the trigger window can be long
Execute gadget chain as the web server user
- A working gadget chain exists in the target runtime/application set
- PHP execution context has access to filesystem secrets, DB credentials, or deployment artifacts
- Real post-exploit blast radius depends on containerization, read-only filesystems, SELinux/AppArmor, and secret handling
- If the site is well isolated, the attacker may get app compromise without easy host breakout
filterFields associated with ExpressEntryList, for unexpected serialized payloads or anomalous admin/API edits. Use this as a hygiene and threat-hunting measure during the 365-day remediation window if you cannot patch immediately.- Generic perimeter WAF rules won't reliably stop this because the exploit lives in authenticated application logic and stored block configuration, not a simple edge signature.
- MFA alone helps prevent account takeover but does nothing against a malicious or already-authenticated privileged user.
- Version-only vulnerability scans identify potentially affected hosts but do not tell you whether the exploitable role/feature combination exists on that site.
The supporting signals.
| In-the-wild status | No public evidence of active exploitation found in the sources reviewed; CISA ADP in the OpenCVE record marks exploitation as none. |
|---|---|
| KEV status | Not KEV-listed as of the current CISA KEV catalog review. |
| EPSS | 0.00232 (~0.232%) from the user-supplied intel, which is low and consistent with a niche, privilege-heavy path rather than a broadly weaponized internet bug. |
| Proof-of-concept availability | No authoritative public PoC repository surfaced in the reviewed sources. Public writeups describe the primitive, but I did not find a primary-source exploit release from the reporter or vendor. |
| Vendor scoring | User-supplied vendor baseline is HIGH 7.2 with CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H; the vendor's published CVE/release-note text also shows a CVSS v4.0 8.9 HIGH path, reflecting the same high impact but different scoring model. |
| Affected versions | Concrete CMS 9.5.0 and earlier are listed as affected in the CVE record. |
| Fixed version | Upgrade to 9.5.1. I found no authoritative distro backport advisory in the reviewed sources, so treat upstream 9.5.1 as the fixed target unless your package maintainer says otherwise. |
| Trigger mechanics | The exploit chain is authenticated and stored: abuse REST API JSON parsing to satisfy _fromCIF === true, write a serialized payload into filterFields, then wait for an admin view/edit to trigger deserialization. |
| Scanning/exposure data | No trustworthy internet-scale, version-precise exposure count was available from reviewed sources. Inference: banner-based Shodan/Censys discovery for Concrete CMS is likely noisy, and exploitability still depends on app role assignments plus use of the ExpressEntryList path. |
| Disclosure and credit | Disclosed on 2026-05-21; credited to Nguyễn Văn Thiện via HackerOne report H1 3643372 in vendor-linked records. |
Sources.
Why this verdict
- Downgrade for
PR:H: the attacker already needs a privileged Concrete CMS account with block-creation rights, which implies prior compromise, insider abuse, or risky delegated access. - Downgrade for narrow exposure: only deployments using the vulnerable
ExpressEntryListpath and allowing that role to configure blocks are meaningfully exposed. - Downgrade for trigger friction: the stored payload still needs a later admin view/edit event, so this is not a one-shot wormable RCE.
- Keep at MEDIUM, not LOW: once triggered, the result is real server-side code execution in the web app context, which can expose secrets, data, and adjacent infrastructure.
Why not higher?
This is not an unauthenticated edge-service bug and not even a low-privileged authenticated bug. Requiring high privileges inside the CMS, plus a specific feature path, plus a later admin-trigger event compounds downward pressure on severity for enterprise patch scheduling.
Why not lower?
Calling it LOW would ignore the fact that successful exploitation crosses an important boundary: CMS admin misuse becomes server-side code execution. That materially increases blast radius beyond normal content-management abuse, especially on flat web stacks with accessible secrets and writable deployment paths.
Crowdsourced verification payload.
Run this on the target Concrete CMS host or a mounted application filesystem, not from a network scanner. Invoke it as bash verify_concrete_cve_2026_8135.sh /var/www/html (or your Concrete install root); no root is required, but the script needs read access to the app files and php in PATH if it falls back to the CLI version check.
#!/usr/bin/env bash
# verify_concrete_cve_2026_8135.sh
# Checks whether a local Concrete CMS installation appears vulnerable to CVE-2026-8135.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN, 3=usage/runtime error
set -u
TARGET="${1:-}"
if [[ -z "$TARGET" ]]; then
echo "Usage: $0 /path/to/concretecms"
exit 3
fi
if [[ ! -d "$TARGET" ]]; then
echo "UNKNOWN - target directory not found: $TARGET"
exit 2
fi
version=""
# Try composer.lock first
if [[ -f "$TARGET/composer.lock" ]]; then
version=$(php -r '
$f=$argv[1];
$j=json_decode(file_get_contents($f), true);
if (!is_array($j)) exit(1);
$pkgs=array_merge($j["packages"] ?? [], $j["packages-dev"] ?? []);
foreach ($pkgs as $p) {
if (($p["name"] ?? "") === "concretecms/core" || ($p["name"] ?? "") === "concretecms/concretecms") {
echo $p["version"] ?? "";
exit(0);
}
}
exit(1);
' "$TARGET/composer.lock" 2>/dev/null)
fi
# Fallback to Concrete CLI if present
if [[ -z "$version" && -x "$TARGET/concrete/bin/concrete5" ]]; then
version=$(php "$TARGET/concrete/bin/concrete5" --version 2>/dev/null | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
fi
# Normalize leading v and Composer-style suffixes
version=$(echo "$version" | sed -E 's/^v//; s/^([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
if [[ -z "$version" ]]; then
echo "UNKNOWN - could not determine Concrete CMS version from composer.lock or CLI"
exit 2
fi
verlte() {
# returns true if $1 <= $2
[[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" == "$1" ]]
}
verlt() {
# returns true if $1 < $2
[[ "$1" != "$2" ]] && verlte "$1" "$2"
}
if verlte "$version" "9.5.0"; then
echo "VULNERABLE - Concrete CMS version $version is <= 9.5.0"
exit 1
elif verlt "$version" "9999.9999.9999"; then
echo "PATCHED - Concrete CMS version $version is > 9.5.0"
exit 0
else
echo "UNKNOWN - unparsable version state: $version"
exit 2
fi