Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2025-48040

CVE-2025-48040: Erlang OTP SSH DoS Vulnerability

CVE-2025-48040 is a denial-of-service vulnerability in Erlang OTP SSH that enables resource exhaustion through excessive allocation and flooding attacks. This article covers technical details, affected versions, and mitigation.

Published:

CVE-2025-48040 Overview

CVE-2025-48040 is an Uncontrolled Resource Consumption vulnerability [CWE-400] in the Erlang OTP SSH implementation, specifically within the ssh_sftp modules. The flaw resides in lib/ssh/src/ssh_sftpd.erl and enables remote attackers to trigger excessive memory allocation and flooding conditions against affected SSH servers. An unauthenticated attacker can reach the vulnerable code path over the network without user interaction.

The issue affects Erlang/OTP from OTP 17.0 before OTP 28.0.3, OTP 27.3.4.3, and OTP 26.2.5.15, corresponding to the ssh application from version 3.0.1 before 5.3.3, 5.2.11.3, and 5.1.4.12.

Critical Impact

Remote unauthenticated attackers can exhaust server resources on Erlang OTP SSH endpoints, degrading availability of SFTP services and any application hosted on the affected Erlang node.

Affected Products

  • Erlang/OTP releases from OTP 17.0 up to (but not including) OTP 28.0.3
  • Erlang/OTP 27.x branch prior to 27.3.4.3
  • Erlang/OTP 26.x branch prior to 26.2.5.15 (ssh application versions 3.0.1 through 5.3.2, 5.2.11.2, and 5.1.4.11)

Discovery Timeline

  • 2025-09-11 - CVE-2025-48040 published to NVD
  • 2026-07-24 - Last updated in NVD database

Technical Details for CVE-2025-48040

Vulnerability Analysis

The vulnerability lives inside the SSH File Transfer Protocol (SFTP) daemon shipped with Erlang/OTP. The ssh_sftpd.erl module handles incoming SFTP protocol messages on behalf of connected clients. When processing certain client-supplied inputs, the daemon allocates resources without applying sufficient bounds, allowing an attacker to force disproportionate memory or process consumption on the server.

Because the attack path is reachable before or during SFTP session negotiation, exploitation does not require valid credentials in all deployments. Repeatedly sending crafted requests amplifies allocation pressure and can starve the BEAM virtual machine of memory, degrading co-located services running on the same Erlang node.

Root Cause

The root cause is missing or inadequate size and rate limiting on attacker-controlled inputs handled by ssh_sftpd.erl. Fixes in adjacent SSH modules also tighten log formatting bounds using the new ssh_lib:max_log_len/1 helper and add reason trimming through ssh_lib:trim_reason/1, reducing the amount of unbounded data that can be materialized during error handling.

Attack Vector

An unauthenticated remote attacker sends crafted SSH or SFTP protocol messages to a vulnerable Erlang OTP SSH endpoint. Repeated messages trigger excessive allocation inside the daemon, resulting in a flooding-style denial of service.

text
// Security patch in lib/ssh/src/ssh_connection.erl - key exchange robustness improvements
     %% respond by disconnecting, preferably with a proper disconnect message
     %% sent to ease troubleshooting.
     MsgFun = fun(M) ->
-                     MaxLogItemLen = ?GET_OPT(max_log_item_len, Ssh#ssh.opts),
                      io_lib:format("Connection terminated. Unexpected message for unauthenticated user."
                                    " Message:  ~w", [M],
-                                   [{chars_limit, MaxLogItemLen}])
+                                   [{chars_limit, ssh_lib:max_log_len(Ssh)}])
              end,
     ?LOG_DEBUG(MsgFun, [Msg]),
     {disconnect, {?SSH_DISCONNECT_PROTOCOL_ERROR, "Connection refused"}, handle_stop(Connection)};

Source: GitHub Commit 548f129

The patch replaces the direct max_log_item_len option lookup with the centralized ssh_lib:max_log_len/1 helper, enforcing consistent bounds on data materialized during error handling.

text
// Security patch in lib/ssh/src/ssh_connection_handler.erl - decoder error path hardening
                                       {next_event, internal, Msg}
 				    ]}
 	    catch
-		C:E:ST  ->
-                    MaxLogItemLen = ?GET_OPT(max_log_item_len,SshParams#ssh.opts),
+		Class:Reason0:Stacktrace  ->
+                    Reason = ssh_lib:trim_reason(Reason0),
+                    MsgFun =
+                        fun(debug) ->
+                                io_lib:format("Bad packet: Decrypted, but can't decode~n~p:~p~n~p",
+                                              [Class,Reason,Stacktrace],
+                                              [{chars_limit, ssh_lib:max_log_len(SshParams)}]);
+                           (_) ->
+                                io_lib:format("Bad packet: Decrypted, but can't decode ~p:~p",
+                                              [Class, Reason],
+                                              [{chars_limit, ssh_lib:max_log_len(SshParams)}])
+                        end,
                     {Shutdown, D} =
                         ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
-                                         io_lib:format("Bad packet: Decrypted, but can't decode~n~p:~p~n~p",
-                                                       [C,E,ST], [{chars_limit, MaxLogItemLen}]),
+                                         ?SELECT_MSG(MsgFun),
                                          StateName, D1),
                     {stop, Shutdown, D}
 	    end;

Source: GitHub Commit 7cd7abb

The fix introduces ssh_lib:trim_reason/1 to bound the size of error reasons and splits log formatting between debug and non-debug paths, preventing unbounded stacktraces from being emitted on the error path.

Detection Methods for CVE-2025-48040

Indicators of Compromise

  • Sustained spikes in memory or process count on the Erlang BEAM VM hosting the SSH/SFTP service.
  • Repeated SFTP connection attempts from a single or small set of source IPs immediately followed by resource exhaustion errors.
  • Unusual volumes of ?SSH_DISCONNECT_PROTOCOL_ERROR disconnects with "Bad packet: Decrypted, but can't decode" or "Connection refused" messages in SSH logs.

Detection Strategies

  • Inventory all Erlang/OTP deployments and compare installed versions against the vulnerable ranges published in the GitHub Security Advisory GHSA-h7rg-6rjg-4cph.
  • Correlate SSH/SFTP connection rates with BEAM memory metrics to identify allocation floods reaching the ssh_sftpd process.
  • Alert on repeated protocol decode failures logged by ssh_connection_handler, which often precede resource exhaustion attempts.

Monitoring Recommendations

  • Emit BEAM VM telemetry (memory, process count, message queue lengths) to a centralized logging platform for anomaly detection.
  • Track per-source-IP SFTP request rates and enforce alerting thresholds tied to baseline traffic.
  • Monitor availability of dependent services co-located on the affected Erlang node to detect cascading impact.

How to Mitigate CVE-2025-48040

Immediate Actions Required

  • Upgrade to Erlang/OTP 28.0.3, 27.3.4.3, or 26.2.5.15 or later, matching the maintenance branch currently deployed.
  • Restrict network exposure of SSH and SFTP services to trusted management networks until patching is complete.
  • Review authentication and rate-limiting configuration for any Erlang-based SSH listener reachable from untrusted networks.

Patch Information

The upstream fixes are tracked in GitHub Pull Request #10162 and delivered through commits 548f1295 and 7cd7abb7. Full advisory details are available in the CNA advisory and mirrored in OSV entry EEF-CVE-2025-48040.

Workarounds

  • Place the SSH/SFTP listener behind a network firewall or bastion host that enforces per-source connection and rate limits.
  • Disable the SFTP subsystem in Erlang SSH configurations where it is not required for operations.
  • Deploy connection throttling at the load balancer or reverse proxy layer to blunt flooding attempts against the vulnerable daemon.
bash
# Example: verify installed Erlang/OTP version and target patched release
erl -eval 'io:format("~s~n", [erlang:system_info(otp_release)]), halt().' -noshell
# Upgrade paths (choose the branch matching your deployment):
#   OTP 28.x -> 28.0.3 or later
#   OTP 27.x -> 27.3.4.3 or later
#   OTP 26.x -> 26.2.5.15 or later

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.