# [CRIT] Krampus delivers an end-of-year Struts vulnerability

**Source:** Snyk
**Published:** 2024-01-02
**Article:** https://snyk.io/blog/struts-path-traversal-vulnerability/

## Threat Profile

Snyk Blog In this article
Written by Micah Silverman 
January 2, 2024
0 mins read Editor's Note: January 16, 2024 A code example in this blog has been updated to increase the security of the fix.
On December 20, 2023, NIST updated a CVE to reflect a new path traversal vulnerability in struts-core . This is CVE-2023-50164 , also listed on the Snyk Vulnerability database , with 9.8 critical severity CVSS. If you’ve been doing cybersecurity long enough, you remember the 2017 Equifax breach , which …

## Indicators of Compromise (high-fidelity only)

- **CVE:** `CVE-2023-50164`

## MITRE ATT&CK Techniques

- **T1190** — Exploit Public-Facing Application
- **T1505.003** — Server Software Component: Web Shell
- **T1059.003** — Command and Scripting Interpreter: Windows Command Shell
- **T1059.001** — Command and Scripting Interpreter: PowerShell
- **T1059.004** — Command and Scripting Interpreter: Unix Shell

## Kill chain phases observed

_(none detected from narrative keywords)_

## Recommended hunts

### Struts CVE-2023-50164 path-traversal upload — HTTP exploit attempt

`UC_1300_1` · phase: **exploit** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=true count min(_time) as firstTime max(_time) as lastTime values(Web.url) as url values(Web.src) as src values(Web.http_user_agent) as ua values(Web.status) as status from datamodel=Web.Web where Web.url="*.action*" (Web.url="*uploadFileName=*..*" OR Web.url="*fileFileName=*..*" OR Web.http_content="*uploadFileName=*..*" OR Web.http_content="*fileFileName=*..*" OR Web.form_data="*uploadFileName=*..*" OR Web.form_data="*fileFileName=*..*") by Web.dest Web.src Web.url Web.http_method | `drop_dm_object_name(Web)` | rename firstTime as firstTime lastTime as lastTime | convert ctime(firstTime) ctime(lastTime)
```

**Defender KQL:**
```kql
// Defender lacks raw web-server logs; this is a best-effort surface via outbound URL telemetry when a reverse-proxy fronts Struts.
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where RemoteUrl has_any (".action")
| where RemoteUrl has_any ("uploadFileName=..", "fileFileName=..", "uploadFileName%3D..", "fileFileName%3D..")
| project Timestamp, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, RemoteIP, RemoteUrl, LocalPort, RemotePort
| order by Timestamp desc
```

### Tomcat/Java process writes .jsp/.jspx webshell into webapp directory

`UC_1300_2` · phase: **install** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=true count min(_time) as firstTime max(_time) as lastTime values(Filesystem.file_path) as file_path values(Filesystem.process_name) as proc values(Filesystem.user) as user from datamodel=Endpoint.Filesystem where (Filesystem.process_name="java.exe" OR Filesystem.process_name="tomcat*.exe" OR Filesystem.process_name="javaw.exe" OR Filesystem.process_name="java") (Filesystem.file_name="*.jsp" OR Filesystem.file_name="*.jspx" OR Filesystem.file_name="*.war") (Filesystem.file_path="*\\webapps\\*" OR Filesystem.file_path="*\\WEB-INF\\*" OR Filesystem.file_path="*\\ROOT\\*" OR Filesystem.file_path="*/webapps/*" OR Filesystem.file_path="*/WEB-INF/*" OR Filesystem.file_path="*src/main/webapp/*") NOT Filesystem.file_path="*\\work\\Catalina\\*" NOT Filesystem.file_path="*/work/Catalina/*" by Filesystem.dest Filesystem.file_path Filesystem.file_name Filesystem.process_name Filesystem.user | `drop_dm_object_name(Filesystem)` | convert ctime(firstTime) ctime(lastTime)
```

**Defender KQL:**
```kql
DeviceFileEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ ("java.exe","javaw.exe","tomcat.exe","tomcat9.exe","tomcat10.exe","prunsrv.exe")
| where FileName endswith ".jsp" or FileName endswith ".jspx" or FileName endswith ".war"
| where FolderPath has_any (@"\webapps\", @"\WEB-INF\", @"\ROOT\", "/webapps/", "/WEB-INF/", "/ROOT/", "src/main/webapp")
| where not(FolderPath has_any (@"\work\Catalina\", "/work/Catalina/", @"\jasper\", "/jasper/"))
| project Timestamp, DeviceName, FileName, FolderPath, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessAccountName, InitiatingProcessParentFileName, SHA256
| order by Timestamp desc
```

### Tomcat/Java process spawns OS shell or LOLBin (post-webshell RCE)

`UC_1300_3` · phase: **actions** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=true count min(_time) as firstTime max(_time) as lastTime values(Processes.process) as cmd values(Processes.process_name) as child values(Processes.parent_process_name) as parent values(Processes.user) as user from datamodel=Endpoint.Processes where (Processes.parent_process_name="java.exe" OR Processes.parent_process_name="javaw.exe" OR Processes.parent_process_name="tomcat*.exe" OR Processes.parent_process_name="prunsrv.exe" OR Processes.parent_process_name="java") (Processes.process_name="cmd.exe" OR Processes.process_name="powershell.exe" OR Processes.process_name="pwsh.exe" OR Processes.process_name="sh" OR Processes.process_name="bash" OR Processes.process_name="dash" OR Processes.process_name="nc" OR Processes.process_name="ncat" OR Processes.process_name="curl" OR Processes.process_name="wget" OR Processes.process_name="certutil.exe" OR Processes.process_name="bitsadmin.exe" OR Processes.process_name="whoami.exe" OR Processes.process_name="whoami" OR Processes.process_name="net.exe" OR Processes.process_name="net1.exe") by Processes.dest Processes.user Processes.parent_process_name Processes.process_name Processes.process Processes.parent_process | `drop_dm_object_name(Processes)` | convert ctime(firstTime) ctime(lastTime)
```

**Defender KQL:**
```kql
DeviceProcessEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ ("java.exe","javaw.exe","tomcat.exe","tomcat9.exe","tomcat10.exe","prunsrv.exe","java")
| where FileName in~ ("cmd.exe","powershell.exe","pwsh.exe","sh","bash","dash","zsh","nc","ncat","curl.exe","curl","wget.exe","wget","certutil.exe","bitsadmin.exe","whoami.exe","whoami","net.exe","net1.exe","hostname.exe","ipconfig.exe","ifconfig","id")
| where AccountName !endswith "$"
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessCommandLine, FileName, FolderPath, ProcessCommandLine, SHA256
| order by Timestamp desc
```

### IOC-driven hunts (use shared templates)

These are standard IOC-substitution hunts — the canonical SPL and KQL live once in [`_TEMPLATES.md`](../_TEMPLATES.md), so we don't repeat the same boilerplate on every CVE / hash / network-IOC briefing.

- **Asset exposure — vulnerability matches article CVE(s)** ([template](../_TEMPLATES.md#asset-exposure)) — phase: **recon**, confidence: **High**
  - CVE(s): `CVE-2023-50164`


## Why this matters

Severity classified as **CRIT** based on: CVE present, 4 use case(s) fired, 5 technique(s) inferred. Read the full article for actor attribution, tooling details, and any defanged IOCs in the body that aren't visible in the RSS summary.
