CVE-2026-57149 Elastic Security · Elastic

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

Elastic Security (Elastic)
eql
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") ]
critical severity high confidence

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

Network/Proxy LogsEndpoint Process Telemetry

Required Tables

logs-endpoint.events.process-*logs-endpoint.events.network-*

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.

  1. 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__'.

  2. 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.

  3. Test 3String TALES expression injection with subprocess marker

    Expected signal: Web log entry: POST to @@manage-portlets with body containing 'subprocess' and '__import__'.


Response Playbook

Triage

  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. 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.
  2. 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.
  3. 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

  1. 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.
  2. 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.
  3. 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.

Hunting — KQL
kql
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
Hunting — SPL
spl
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

Test 1 Simulate TALES python: injection via portlet edit request
linux

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

bash
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

bash
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.

Test 2 Zope worker spawns shell child (post-exploitation simulation)
linux

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
bash -c 'cp /bin/bash /tmp/python3 && /tmp/python3 -c "import os; os.system(\"id > /tmp/tales_rce.txt\")"'

Cleanup

bash
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.

Test 3 String TALES expression injection with subprocess marker
linux

Sends a portlet footer field containing a subprocess-based expression to validate detection of alternate TALES injection payloads (lab only).

Command

bash
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

bash
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.

Related Detections