Detect plone.app.portlets TALES Injection Remote Code Execution (CVE-2026-57149) in Elastic Security
Detects exploitation attempts against CVE-2026-57149, a critical (CVSS 9.9) remote code execution vulnerability in plone.app.portlets caused by unsafe evaluation of user-controllable TALES (Template Attribute Language Expression Syntax) expressions (CWE-95, Improper Neutralization of Directives in Dynamically Evaluated Code / 'Eval Injection'). Affected versions: 5.0.0-5.0.7, 6.0.0-6.0.3, and 7.0.0-7.0.1. An authenticated (or, depending on portlet configuration, low-privilege) actor can inject TALES/Python expressions such as python:__import__('os').system(...) or string: expressions containing path traversals into portlet assignment or portlet header/footer fields, which Zope/Plone evaluates server-side, yielding arbitrary code execution as the Zope process user. This detection surfaces suspicious portlet edit/assignment requests carrying TALES expression markers, Zope error/traceback telemetry indicating expression evaluation, and child processes spawned by the Zope/Plone (zope, zserver, python, instance) process tree. Remediate by upgrading to plone.app.portlets 5.0.8, 6.0.4, or 7.0.2.
MITRE ATT&CK
- Tactic
- Initial Access Execution
Elastic Detection Query
sequence by host.id with maxspan=1m
[ network where url.path : ("*manage-portlets*", "*contextportlets*", "*add-portlet*", "*edit-portlet*") and http.request.method in ("POST", "PUT") ]
[ process where event.type == "start" and
process.parent.name in ("zope", "zserver", "runwsgi", "instance", "python", "python3") and
process.name in ("sh", "bash", "dash", "cmd.exe", "powershell.exe", "curl", "wget", "nc", "ncat", "perl") ] Elastic EQL sequence correlating an inbound POST/PUT to a Plone portlet management endpoint with a shell/download child process spawned by the Zope/Plone process tree within one minute — a strong signal of successful TALES injection RCE (CVE-2026-57149).
Data Sources
Required Tables
False Positives & Tuning
- Plone add-ons or scheduled jobs that legitimately spawn subprocesses from the Zope worker (e.g., PDF rendering, image conversion via ImageMagick).
- Administrative maintenance scripts run under the same service account during a portlet edit window.
- Monitoring agents that periodically execute helper binaries as children of the python interpreter.
Other platforms for CVE-2026-57149
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Simulate TALES python: injection via portlet edit request
Expected signal: Web/reverse-proxy log entry: POST to a portlet endpoint with a URL-decoded body containing 'python:' and '__import__'.
- Test 2Zope worker spawns shell child (post-exploitation simulation)
Expected signal: Process-creation event: a child process (sh/bash/id) with a parent image named python3, matching the Zope process-tree pattern.
- Test 3String TALES expression injection with subprocess marker
Expected signal: Web log entry: POST to @@manage-portlets with body containing 'subprocess' and '__import__'.
References (9)
- https://github.com/plone/plone.app.portlets/security/advisories/GHSA-rr49-f9g6-c9r5
- https://nvd.nist.gov/vuln/detail/CVE-2026-57149
- https://github.com/plone/plone.app.portlets/commit/1d9cacacfad9ed08b890dadc6e75741e295dc151
- https://github.com/plone/plone.app.portlets/commit/8a0641dc4054a2b13834bba00c67cd9a2fd189e1
- https://github.com/plone/plone.app.portlets/commit/fb979f01b57dd2fc06c90ee6577eb5eb285da8f1
- https://github.com/plone/plone.app.portlets/releases/tag/5.0.8
- https://github.com/plone/plone.app.portlets/releases/tag/6.0.4
- https://github.com/plone/plone.app.portlets/releases/tag/7.0.2
- https://github.com/advisories/GHSA-rr49-f9g6-c9r5
Response Playbook
Triage
- Confirm the targeted host runs Plone with plone.app.portlets and identify the installed version; versions 5.0.0-5.0.7, 6.0.0-6.0.3, and 7.0.0-7.0.1 are vulnerable to CVE-2026-57149.
- URL-decode and inspect the captured request body/query for TALES markers (python:, __import__, string:${...}, os.system, subprocess) and determine which portlet field (header, footer, assignment expression) carried the payload.
- Correlate the web request timestamp with the Zope/Plone process tree on the host and check for any child processes (sh, bash, curl, wget, powershell) spawned within one minute of the request.
- Identify the authenticated account (if any) used to reach the portlet manager and assess whether it is an expected administrator or a compromised/low-privilege user.
Containment
- Block the source IP at the WAF/reverse proxy and rate-limit or restrict access to @@manage-portlets and ++contextportlets++ endpoints to trusted admin networks.
- Isolate the affected Plone host from the network if child-process execution from the Zope tree is confirmed, and rotate any credentials or secrets accessible to the Zope service account.
- Upgrade plone.app.portlets to the fixed release for the branch in use (5.0.8, 6.0.4, or 7.0.2) and restart the Zope/WSGI instance.
Evidence Collection
- Preserve web/reverse-proxy access logs including full request bodies, the Zope event.log and error traceback logs, and any Products.PythonScripts execution traces around the incident window.
- Capture the Plone portlet configuration (portlet assignments and their expression fields) from the ZODB / Data.fs to recover the injected TALES payload, and snapshot running-process and network-connection state on the host.
- Collect a forensic image or memory capture of the Zope worker process if a live reverse shell or in-memory implant is suspected.
Escalation Criteria
- !Escalate to incident response immediately if a non-scanner source triggered a child-process execution from the Zope/Plone process tree, indicating successful RCE.
- !Escalate if the injected expression referenced credential stores, cloud metadata endpoints, outbound C2, or data-staging paths, indicating attempted data exfiltration or lateral movement.
- !Escalate if multiple distinct source IPs or accounts targeted the portlet endpoints, suggesting a coordinated campaign against the CVE.
Investigation Guide
Related Techniques
Forensic Artifacts
- >
Zope event.log / error traceback entries showing evaluation of user-supplied TALES expressions. - >
Reverse-proxy or IIS/Apache access logs recording POST/PUT requests to portlet management endpoints with expression payloads. - >
ZODB (Data.fs) portlet assignment objects containing the injected expression string. - >
Child-process creation records (auditd execve / Sysmon EID 1 / EDR ProcessRollup2) with a zope/zserver/python parent.
Tuning Guidance
Baseline the accounts and source networks that legitimately administer portlets; most Plone deployments restrict @@manage-portlets to a handful of admins, so any expression-bearing request from outside that set is high signal. Ensure the reverse proxy or WAF logs request bodies, since portlet expressions arrive as form fields rather than in the URL. Suppress known-good automation (theming/migration tooling) by source and user, and pair the web-log detections with the endpoint process-lineage rules (Elastic/CrowdStrike) to prioritize alerts where a Zope child process actually spawned.
Hunting Queries
Hunt across historical web logs for portlet-management requests carrying TALES/Python expression markers to identify prior CVE-2026-57149 exploitation attempts.
W3CIISLog | extend q = url_decode(strcat(csUriQuery, csUriStem)) | where q has_any ("python:", "__import__", "os.system", "subprocess") and q has "portlet" | project TimeGenerated, cIP, csUriStem, q, csUserName | sort by TimeGenerated desc index=web (uri_path=*portlet* OR uri_query=*portlet*) | eval d=urldecode(_raw) | regex d="(?i)(python:|__import__|os\.system|subprocess)" | stats count values(uri_path) by src_ip user Atomic Red Team Tests
Sends a crafted POST to the Plone portlet management endpoint containing a python: TALES expression, simulating CVE-2026-57149 exploitation traffic for detection validation (lab only).
Command
curl -sk -X POST 'https://plone.lab.local/++contextportlets++plone.leftcolumn/@@add-portlet' --data-urlencode "form.header=python:__import__('os').system('id')" --data-urlencode 'form.actions.save=Save' -H 'Cookie: __ac=TESTSESSION' Cleanup
curl -sk -X POST 'https://plone.lab.local/++contextportlets++plone.leftcolumn/@@manage-portlets' --data-urlencode 'form.actions.delete=Delete' -H 'Cookie: __ac=TESTSESSION' || true Expected Telemetry
Web/reverse-proxy log entry: POST to a portlet endpoint with a URL-decoded body containing 'python:' and '__import__'.
Expected Detection
The kql, spl, qradar_aql, sumo_logic, and chronicle_yaral rules fire on the expression markers in the portlet request.
Simulates the process-lineage signature of successful TALES RCE by spawning a shell command from a process named like the Zope/Plone worker.
Command
bash -c 'cp /bin/bash /tmp/python3 && /tmp/python3 -c "import os; os.system(\"id > /tmp/tales_rce.txt\")"' Cleanup
rm -f /tmp/python3 /tmp/tales_rce.txt Expected Telemetry
Process-creation event: a child process (sh/bash/id) with a parent image named python3, matching the Zope process-tree pattern.
Expected Detection
The elastic_eql and crowdstrike_cql rules fire on the shell/utility child spawned from a python/zope parent.
Sends a portlet footer field containing a subprocess-based expression to validate detection of alternate TALES injection payloads (lab only).
Command
curl -sk -X POST 'https://plone.lab.local/@@manage-portlets' --data-urlencode "form.footer=python:__import__('subprocess').check_output(['whoami'])" --data-urlencode 'form.actions.save=Save' -H 'Cookie: __ac=TESTSESSION' Cleanup
echo 'no persistent artifact created' Expected Telemetry
Web log entry: POST to @@manage-portlets with body containing 'subprocess' and '__import__'.
Expected Detection
The kql, spl, qradar_aql, and sumo_logic rules fire on the subprocess/__import__ markers in the portlet request.