LoFP LoFP / this probe string is specific to the ptc-published windchill exploitation indicators. false positives should be rare. validate whether any internal testing, incident response activity, or vendor diagnostics intentionally generated `gw_ready_ok` requests.

Techniques

Sample rules

PTC Windchill GW READY OK Probe

Description

This analytic detects Windchill MethodServer log4j events that contain the CVE-2026-4681 exploitation probe run?c=echo%20GW_READY_OK. PTC identifies GW_READY_OK and related run?c= activity as log indicators associated with Windchill and FlexPLM exploitation. This behavior is significant because attackers use the probe to confirm that a staged gateway component is reachable before sending operating system commands through the same c= parameter.

Detection logic

`windchill_log4j`
("GW_READY_OK" OR "run?c=echo%20GW_READY_OK" OR "c=echo%20GW_READY_OK")

| rex field=_raw "^(?:[^:\r\n]+:)?(?<log_ts>\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2},\d{3})\s+(?<log_level>\w+)\s+\[(?<thread>[^\]]+)\]\s+(?<logger>\S+)\s+-\s+(?<payload>.*)$"

| search logger IN ("wt.servlet.ServletRequestMonitor.request", "wt.method.MethodContextMonitor.contexts.servletRequest")

| rex field=payload "^(?<event_ts>\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d{3})\s+(?<event_tz>[+\-]\d{4}),\s+(?<rest>.*)$"

| eval parts=split(rest,", ")

| eval event_type=case(logger="wt.servlet.ServletRequestMonitor.request","servlet_request",logger="wt.method.MethodContextMonitor.contexts.servletRequest","method_context_servlet_request",true(),"other")

| eval src_ip=case(event_type="servlet_request",mvindex(parts,2),event_type="method_context_servlet_request",mvindex(parts,5))

| eval uri_path=case(event_type="servlet_request",mvindex(parts,3),event_type="method_context_servlet_request",mvindex(parts,8))

| eval query_string=if(event_type="servlet_request",mvindex(parts,4),null())

| eval http_method=if(event_type="servlet_request",mvindex(parts,5),null())

| eval status=if(event_type="servlet_request",tonumber(mvindex(parts,6)),null())

| rex field=uri_path "^(?<uri_only>[^\?]+)(?:\?(?<uri_query>.*))?$"

| eval query_string=if(query_string="-",null(),query_string)

| eval query_string=coalesce(query_string,uri_query)

| rex field=query_string "(?i)(?:^
|&)(?<query_param>[cp])=(?<query_value>[^&]*)"

| eval query_param=lower(query_param), query_value=urldecode(replace(query_value,"\+","%20"))

| where query_param="c" AND match(query_value,"(?i)^echo(\s
|20)+GW_READY_OK$")

| eval src=src_ip, activity="gw_ready_ok_probe"


| stats count min(_time) as firstTime
              max(_time) as lastTime
              values(log_level) as log_level
              values(logger) as logger
              values(http_method) as http_method
              values(status) as status
              values(uri_only) as uri_path
              values(query_string) as query_string
  by src activity query_param query_value


| `security_content_ctime(firstTime)`

| `security_content_ctime(lastTime)`

| `ptc_windchill_gw_ready_ok_probe_filter`