Skip to main content
Blog

Two Web Shells, Two Detections: Instrumenting PTC Windchill Against Clop

  • August 19, 2026
  • 0 replies
  • 9 views
Aaron Beardslee
Forum|alt.badge.img

Where CVE-2026-12569 code actually executes, and what telemetry sees it


The Clop campaign against PTC Windchill produced two structurally different implants. Most coverage treats them as one thing. They are not, they do not generate the same telemetry, and a detection built for one will not fire on the other. This writeup covers the exploit chain, the Windchill process model that determines where your parent-process anchor belongs, the split between the two shells, and how to instrument both platforms with Sysmon.


The exploit chain

CVE-2026-12569 is a deserialization of untrusted data flaw in the Windchill login servlet, affecting PTC Windchill PDMLink and FlexPLM prior to 11.0 M030. NVD scores it 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H); ReliaQuest and SecurityWeek cite 9.3. Same defect, different scoring sources.

It is not exploited alone. Operators chain a pre-authentication information disclosure in the FlexPLM WSDL endpoint (CVSS 7.5) to enumerate the target first. That reconnaissance step has a distinctive fingerprint:

GET /Windchill/rfa/jsp/login/*.jsp?wsdl     response_bytes = 4045

A 4045-byte response to that path is the pre-exploitation probe. It is cheap to hunt retroactively and it predates the actual compromise, which makes it the earliest signal available in the entire chain.

Patches shipped June 17, 2026. Ransom-ISAC assesses exploitation began as a zero-day in early June. Scope any hunt to June 1 at the latest.

CVE-2026-12569 ending in two types of web shells

Where the code executes

This determines your parent anchor, and getting it wrong produces a rule that never fires.

Since Windchill 10.0, Tomcat runs embedded inside the Method Server process. Each Method Server carries its own Tomcat instance, and Windchill always installs the Embedded Servlet Engine regardless of which front-end web server is deployed. Apache sits in front as a reverse proxy over AJP and never executes attacker code.

systemd / services.exe

├─ java (Server Manager) wt.server.ServerManager
│ └─ java (Method Server) wt.method.MethodServerMain
│ └─ [embedded Tomcat] ← JSP web shell compiles and runs HERE
│ └─ child process ← anomaly, if one appears at all

└─ httpd (Apache) ──AJP──> Method Server

Two consequences. First, monitoring httpd for anomalous children watches the wrong process. Second, on Linux the executing account is not www-data or apache, it is the Windchill installation account (commonly wcadmin or a site-specific name). That account already owns the file vaults and holds the database and keystore credentials, which is why this kill chain contains no privilege escalation stage.

Identifying the Method Server on the command line requires more than Image matching, since java alone is meaningless in an enterprise:

Anchor Value
Main class wt.method.MethodServerMain
System properties -Dwt.home=, -Dwt.codebase=
Tomcat instance path catalina.base=.../Windchill/tomcat/instances/instance-<port>
Classpath contains /Windchill/codebase
Binary bundled JRE under the install root, e.g. /opt/ptc/Windchill_XX/Java/bin/java

The Server Manager (wt.server.ServerManager) is the parent of every Method Server, so any rule keyed on "java spawns java" will fire continuously. Exclude it explicitly. Background Method Servers are Method Server variants and remain valid landing zones, so they stay in scope.


The two web shells

  Custom implant Generic command shell
Filename pattern /Windchill/login/[0-9a-f]{16}.jsp /Windchill/login/[0-9a-f]{6}.jsp
Also observed dpr_<8 hex>.jsp (family unconfirmed)  
Control channel X-windchill-req HTTP header URL query parameter
Command format 8 chars: 1 command + 7 fixed ?cmd=whoami, ?cmd=id
Response encoding GZIP plaintext
Execution model in-process, Windchill classes Runtime.exec()
Spawns children no yes
Endpoint telemetry one file write, then silence full process tree
Web log visibility header only, usually unlogged request line, logged by default
Attribution weight high, Clop-specific low, anyone

The custom implant imports MethodContext, WTConnection, and WTKeyStoreUtil, and queries the vault tables ApplicationData, FVITEM, FVMOUNT, and MasteredOnReplicaItem, referencing internal identifiers such as IDA3A4. Its command set:

Cmd Function
S Read ieStructProperties.txt, decrypt LDAP manager password and all keystore properties via WTKeyStoreUtil.decryptProperty()
L Enumerate file vault, write results to flst.txt
D Enumerate directories, partial file read
G Read file
R Delete file
J Load and execute Base64 ZIP of Java bytecode in memory
O Return operating system name
E Echo X-windchill-prm header contents

Every one of those executes inside the JVM. No fork(), no /bin/sh, no curl. Database queries run through the application's own connection, so database telemetry attributes them to the normal service identity.

The generic shell is the opposite in every respect. It is a stock JSP command shell driven by a query parameter, and it forks children on the operator's first interaction.

One Java behavior matters for the child selector. Runtime.getRuntime().exec(String) does not invoke a shell, it tokenizes and execs the binary directly:

java (Method Server) → whoami                       # direct exec
java (Method Server) → sh -c "whoami" → whoami # array form with shell
java.exe → cmd.exe /c whoami # Windows

Do not require an intermediate shell. A rule written as "Windchill JVM spawns /bin/sh" misses the direct-exec variant entirely.

The whoami and id pair is a landing check, run to confirm execution and establish context. id does not exist on Windows while whoami exists on both, so an operator trying both is fingerprinting by trial. The custom implant's O command does the same job for the same reason: Windchill runs on Windows Server and Linux, and the operators did not know which they had landed on.


Layer 1: the web tier

Apache's default combined log format does not log arbitrary request headers, which means the highest-fidelity artifact in this campaign is invisible on a stock install. Fix that first:

LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" \
\"%{X-windchill-req}i\" \"%{X-windchill-prm}i\"" windchill_hunt

CustomLog logs/windchill_access.log windchill_hunt

The generic shell needs no config change, because its command rides the query string that stock logging already captures. That makes this hunt free against existing retention:

grep -E '/Windchill/login/[0-9a-f]{6,16}\.jsp' access_log
grep -E '\.jsp\?cmd=' access_log
grep -E '/Windchill/rfa/jsp/login/.*\?wsdl' access_log | awk '$10==4045'

Full coverage of the custom implant requires three things together: header logging, response decompression, and TLS inspection. Any two of the three leaves a gap.


Layer 2: Sysmon for Linux

The implant touches disk exactly once, when the JSP is written into the Windchill codebase. That single event is the highest-value endpoint detection in the entire chain, and Sysmon for Linux EVID 11 captures it.

Install per the Sysinternals guide, then load a hunting config:

sysmon -i /opt/ft-sysmonconfig-linux-export.xml

The config I maintain for this is here:

https://github.com/fluffybunnies-h4x/FT-Linux-Sysmon-Config

Relevant event coverage for this campaign:

EVID Event What it catches
11 File create JSP written to Windchill codebase, flst.txt creation
1 Process create Generic shell children, second-stage tooling
3 Network connect Egress from the Method Server JVM
23 File delete Operator cleanup via the R command

A scoped file-create rule for Windchill hosts:

<RuleGroup name="Windchill JSP drop" groupRelation="or">
<FileCreate onmatch="include">
<TargetFilename condition="contains all">/Windchill/codebase/;.jsp</TargetFilename>
</FileCreate>
</RuleGroup>

Adjust the path to your install root. Legitimate JSP writes into that tree happen during patching and deployment only, so the baseline is close to zero outside change windows.

For process creation, anchor on the JVM and cast the child net wider than shells alone:

Category Binaries
Discovery whoami, id, uname, hostname, pwd, env, ps, w, ip, ss
Shells bash, sh, dash, ksh, zsh
Interpreters python, python3, perl, ruby, php
Network curl, wget, nc, ncat, socat
Staging tar, zip, gzip, 7z

Rank confidence within that set. whoami, id, uname, and hostname from a Method Server parent are close to zero false positive. Be more cautious with ls, cat, ps, and env, since Windchill customization code and publishing adapters can plausibly touch them.

Staging utilities deserve more attention than they usually get. This is a data theft campaign, and moving vault contents out means somebody eventually archives them. A Method Server forking tar against a vault path is a better signal for the actor's actual objective than a reverse shell would be.

Legitimate Method Server children do exist, primarily publishing and Creo View Adapter invocation, plus site-specific customization code. Baseline before deploying and filter on your own install paths, the same way you would exclude a daemon's known helper scripts.


Layer 3: Sysmon on Windows

Windchill on Windows Server runs the same architecture with java.exe as the Method Server, typically launched as a service, so the grandparent chain runs back to services.exe.

sysmon.exe -accepteula -i ft-sysmonconfig-export.xml    # install
sysmon.exe -c ft-sysmonconfig-export.xml # update existing

The config I maintain, forked from SwiftOnSecurity and tuned against live malware TTPs:

https://github.com/fluffybunnies-h4x/FT-Sysmon-Config

That config already includes asp and aspx in its EVENT ID 11 file-create rules, which is exactly the right instinct: web shell drops are caught at file creation, by extension, in web-accessible directories. This campaign is the argument for extending the same coverage to .jsp. If you run Windchill, WebLogic, Tomcat, JBoss, or any other Java application server on Windows, add the extension and scope it to your application codebase paths. Contributions welcome if you build a good scoped ruleset for it.

Relevant Windows event coverage:

EVID Event What it catches
11 File create JSP drop into the Windchill codebase
1 Process create java.exe forking cmd.exe, whoami.exe, discovery binaries
3 Network connect JVM egress
26 File delete detected Operator cleanup
7 Image load Second-stage DLL side-loading, if the J loader delivers it

Hunt artifacts

On-host:

# Suspicious JSPs in the codebase, written since the exploitation window opened
find /opt/ptc -path '*Windchill/codebase*' -name '*.jsp' -newermt '2026-06-01'

# Content markers unique to the custom implant
grep -rlE 'X-windchill-req|WTKeyStoreUtil|MethodContext|WTConnection' \
/opt/ptc/*/Windchill/codebase/

# Enumeration artifact
find / -name 'flst.txt' 2>/dev/null

flst.txt is worth more than an IOC hit. It contains vault stream IDs, filenames, storage paths, and file sizes, which makes it a direct record of what the operator inventoried and a far better scoping input than anything in an extortion email.

Network and file indicators:

Type Value
SHA-256 321e1fb01eb3462b48ff6ccdef132acc1182e3f7456548439f0d4ead12fd98bf (ReliaQuest, custom shell)
SHA-256 55a1eb4c2d3da04376df39d7ba832569c6af1a37a0cf2b95f754ac898023a30c (Ransom-ISAC)
C2, priority 5.180.41.35, 79.141.160.78
C2 216.152.148.54, 216.152.151.204, 104.243.35.63, 78.128.113.10, 104.194.9.14, 185.227.83.236, 209.222.98.44
Range 104.243.35.0/24

PTC advisory CS473270 carries the full set, including 44 MD5s and 2 SHA-1s. Validate network indicators against your environment before blocking, since some correspond to shared hosting.

Observed shell filenames: 46b158b8607a4c00.jsp, 4b57d0652345d383.jsp, 56c9be44a436c4a2.jsp, 64652883d9de3299.jsp, 7c0a0a34c9d8d53b.jsp, ec6ba805a076e709.jsp. Hunt the pattern, not the list, since new shells appear under new names.


Detection priority

Rank by what each layer actually sees.

The web-tier path rule catches the generic shell and any copycat, works on default logging, and supports a free retroactive hunt. Deploy first. The header rule catches the custom implant at every operator interaction, but requires the LogFormat change to be in place, so it protects you going forward rather than backward. Sysmon EVID 11 catches both families at the single moment either touches disk, and is the only endpoint detection that fires against the custom implant at all. Sysmon EVID 1 with a JVM parent anchor catches the generic shell immediately and the custom implant only if the operator uses the J class loader to deliver tooling that shells out.

That last point is the one to sit with. Against the custom implant, process-creation telemetry is silent by design. If your Windchill hosts are quiet, that is not evidence of anything. Go look at the codebase directory.


References

Primary threat intelligence

Vendor advisories and vulnerability data

Windchill architecture

Tooling and configuration

MITRE ATT&CK

Technique ID
Exploit Public-Facing Application T1190
Server Software Component: Web Shell T1505/003
Credentials from Password Stores T1555
Unsecured Credentials: Credentials in Files T1552/001
System Owner/User Discovery T1033
System Information Discovery T1082
File and Directory Discovery T1083
Archive Collected Data T1560
Exfiltration Over Web Service T1567
Reflective Code Loading T1620
Indicator Removal: File Deletion T1070/004

Sysmon configs referenced above are maintained at FT-Sysmon-Config and FT-Linux-Sysmon-Config.