feat: Remote-Execute für Linux/Bash – OS-basierte Tool-Descriptions und remote.sh Script
Remote-Verbindungen erkennen jetzt das OS des Clients und passen die Tool-Description dynamisch an (PowerShell für Windows, Bash für Linux). Neues remote.sh Script für Linux/macOS-Clients mit embedded Python WebSocket-Client. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
1035066a39
commit
53cee67bef
11 files changed
+381
-27
No files matched your search
@@ -324,8 +324,10 @@ async function showRemoteModal(sessionId) {
|
||||
|
||||
var empty = document.createElement('div');
|
||||
empty.className = 'sp-loading';
|
||||
empty.innerHTML = '<div>Powershell-Befehl unter Windows ausführen</div>' +
|
||||
'<code>irm ' + esc(location.origin) + '/remote | iex</code>';
|
||||
empty.innerHTML = '<div>Windows (PowerShell)</div>' +
|
||||
'<code>irm ' + esc(location.origin) + '/remote.ps1 | iex</code>' +
|
||||
'<div style="margin-top:8px">Linux (Bash)</div>' +
|
||||
'<code>curl -s ' + esc(location.origin) + '/remote.sh | bash</code>';
|
||||
body.appendChild(empty);
|
||||
|
||||
if (!connections || connections.length === 0) {
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
#!/usr/bin/env bash
|
||||
# GOTT Remote Agent - Linux Bash Client
|
||||
# Verbindet sich per WebSocket zum GOTT-Server und fuehrt Befehle aus.
|
||||
#
|
||||
# Verwendung:
|
||||
# curl -s https://gott.example.com/remote.sh | bash
|
||||
# oder:
|
||||
# chmod +x remote.sh && ./remote.sh
|
||||
# oder mit eigener URL:
|
||||
# WS_URL="wss://example.com/api/remote" ./remote.sh
|
||||
|
||||
WS_URL="${WS_URL:-{{WS_URL}}}"
|
||||
|
||||
# Python finden
|
||||
PYTHON=""
|
||||
for cmd in python3 python py; do
|
||||
if command -v "$cmd" &>/dev/null; then
|
||||
PYTHON="$cmd"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$PYTHON" ]; then
|
||||
echo ""
|
||||
echo " [FEHLER] Python nicht gefunden!"
|
||||
echo " Bitte installiere Python 3: sudo apt install python3"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Python-Script inline ausfuehren
|
||||
exec "$PYTHON" - "$WS_URL" << 'PYTHON_SCRIPT'
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import signal
|
||||
import socket
|
||||
import ssl
|
||||
import hashlib
|
||||
import base64
|
||||
import struct
|
||||
import time
|
||||
from urllib.parse import urlparse
|
||||
|
||||
WS_URL = sys.argv[1] if len(sys.argv) > 1 else "ws://localhost/api/remote"
|
||||
|
||||
CYAN = "\033[96m"
|
||||
GREEN = "\033[92m"
|
||||
YELLOW = "\033[93m"
|
||||
RED = "\033[91m"
|
||||
GRAY = "\033[90m"
|
||||
WHITE = "\033[97m"
|
||||
RESET = "\033[0m"
|
||||
|
||||
def cprint(msg, color=""):
|
||||
print(f"{color}{msg}{RESET}", flush=True)
|
||||
|
||||
class WebSocketClient:
|
||||
"""Minimaler WebSocket-Client (RFC 6455) ohne externe Abhaengigkeiten."""
|
||||
|
||||
def __init__(self, url):
|
||||
self.url = url
|
||||
self.sock = None
|
||||
self.closed = False
|
||||
|
||||
def connect(self):
|
||||
parsed = urlparse(self.url)
|
||||
use_ssl = parsed.scheme in ("wss", "https")
|
||||
host = parsed.hostname
|
||||
port = parsed.port or (443 if use_ssl else 80)
|
||||
path = parsed.path or "/"
|
||||
if parsed.query:
|
||||
path += "?" + parsed.query
|
||||
|
||||
self.sock = socket.create_connection((host, port), timeout=10)
|
||||
if use_ssl:
|
||||
ctx = ssl.create_default_context()
|
||||
self.sock = ctx.wrap_socket(self.sock, server_hostname=host)
|
||||
|
||||
# WebSocket-Handshake
|
||||
key = base64.b64encode(os.urandom(16)).decode()
|
||||
handshake = (
|
||||
f"GET {path} HTTP/1.1\r\n"
|
||||
f"Host: {host}\r\n"
|
||||
f"Upgrade: websocket\r\n"
|
||||
f"Connection: Upgrade\r\n"
|
||||
f"Sec-WebSocket-Key: {key}\r\n"
|
||||
f"Sec-WebSocket-Version: 13\r\n"
|
||||
f"\r\n"
|
||||
)
|
||||
self.sock.sendall(handshake.encode())
|
||||
|
||||
# Response lesen
|
||||
response = b""
|
||||
while b"\r\n\r\n" not in response:
|
||||
chunk = self.sock.recv(4096)
|
||||
if not chunk:
|
||||
raise ConnectionError("Handshake fehlgeschlagen")
|
||||
response += chunk
|
||||
|
||||
status_line = response.split(b"\r\n")[0].decode()
|
||||
if "101" not in status_line:
|
||||
raise ConnectionError(f"WebSocket-Handshake fehlgeschlagen: {status_line}")
|
||||
|
||||
self.sock.settimeout(None)
|
||||
|
||||
def send(self, data):
|
||||
if self.closed:
|
||||
return
|
||||
payload = data.encode("utf-8")
|
||||
frame = bytearray()
|
||||
frame.append(0x81) # FIN + Text
|
||||
|
||||
length = len(payload)
|
||||
if length < 126:
|
||||
frame.append(0x80 | length) # Mask-Bit gesetzt
|
||||
elif length < 65536:
|
||||
frame.append(0x80 | 126)
|
||||
frame.extend(struct.pack(">H", length))
|
||||
else:
|
||||
frame.append(0x80 | 127)
|
||||
frame.extend(struct.pack(">Q", length))
|
||||
|
||||
# Masking Key (Client muss maskieren)
|
||||
mask = os.urandom(4)
|
||||
frame.extend(mask)
|
||||
masked = bytearray(b ^ mask[i % 4] for i, b in enumerate(payload))
|
||||
frame.extend(masked)
|
||||
|
||||
self.sock.sendall(frame)
|
||||
|
||||
def recv(self):
|
||||
if self.closed:
|
||||
return None
|
||||
|
||||
def read_exact(n):
|
||||
data = b""
|
||||
while len(data) < n:
|
||||
chunk = self.sock.recv(n - len(data))
|
||||
if not chunk:
|
||||
raise ConnectionError("Verbindung geschlossen")
|
||||
data += chunk
|
||||
return data
|
||||
|
||||
header = read_exact(2)
|
||||
opcode = header[0] & 0x0F
|
||||
is_masked = header[1] & 0x80
|
||||
length = header[1] & 0x7F
|
||||
|
||||
if opcode == 0x8: # Close
|
||||
self.closed = True
|
||||
return None
|
||||
if opcode == 0x9: # Ping
|
||||
self.send_pong()
|
||||
return self.recv()
|
||||
|
||||
if length == 126:
|
||||
length = struct.unpack(">H", read_exact(2))[0]
|
||||
elif length == 127:
|
||||
length = struct.unpack(">Q", read_exact(8))[0]
|
||||
|
||||
if is_masked:
|
||||
mask = read_exact(4)
|
||||
data = read_exact(length)
|
||||
data = bytearray(b ^ mask[i % 4] for i, b in enumerate(data))
|
||||
else:
|
||||
data = read_exact(length)
|
||||
|
||||
return data.decode("utf-8")
|
||||
|
||||
def send_pong(self):
|
||||
if self.closed:
|
||||
return
|
||||
frame = bytearray([0x8A, 0x80]) # FIN + Pong, Mask-Bit, Length 0
|
||||
frame.extend(os.urandom(4)) # Mask key
|
||||
self.sock.sendall(frame)
|
||||
|
||||
def close(self):
|
||||
if self.closed:
|
||||
return
|
||||
self.closed = True
|
||||
try:
|
||||
# Close-Frame senden
|
||||
frame = bytearray([0x88, 0x82]) # FIN + Close, Mask-Bit, Length 2
|
||||
mask = os.urandom(4)
|
||||
frame.extend(mask)
|
||||
# Status 1000 (Normal Closure) maskiert
|
||||
code = struct.pack(">H", 1000)
|
||||
frame.extend(bytearray(b ^ mask[i % 4] for i, b in enumerate(code)))
|
||||
self.sock.sendall(frame)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
print()
|
||||
cprint(" ============================", CYAN)
|
||||
cprint(" GOTT Remote Agent", CYAN)
|
||||
cprint(" ============================", CYAN)
|
||||
print()
|
||||
cprint(f" Verbinde zu: {WS_URL}", GRAY)
|
||||
print()
|
||||
|
||||
ws = WebSocketClient(WS_URL)
|
||||
running = True
|
||||
|
||||
def on_signal(sig, frame):
|
||||
nonlocal running
|
||||
running = False
|
||||
|
||||
signal.signal(signal.SIGINT, on_signal)
|
||||
signal.signal(signal.SIGTERM, on_signal)
|
||||
|
||||
try:
|
||||
ws.connect()
|
||||
except Exception as e:
|
||||
cprint(f" [FEHLER] Verbindung fehlgeschlagen: {e}", RED)
|
||||
cprint(f" URL: {WS_URL}", GRAY)
|
||||
return
|
||||
|
||||
cprint(" [OK] Verbunden!", GREEN)
|
||||
|
||||
# Hello-Message senden
|
||||
hostname = os.uname().nodename if hasattr(os, "uname") else socket.gethostname()
|
||||
username = os.environ.get("USER", os.environ.get("LOGNAME", "unknown"))
|
||||
import platform
|
||||
os_version = f"{platform.system()} {platform.release()}"
|
||||
|
||||
hello = json.dumps({
|
||||
"type": "hello",
|
||||
"hostname": hostname,
|
||||
"username": username,
|
||||
"os": os_version
|
||||
})
|
||||
ws.send(hello)
|
||||
|
||||
cprint(f" Host: {username}@{hostname} ({os_version})", GRAY)
|
||||
print()
|
||||
cprint(" Warte auf Befehle... (Strg+C zum Beenden)", YELLOW)
|
||||
print()
|
||||
|
||||
while running and not ws.closed:
|
||||
try:
|
||||
raw = ws.recv()
|
||||
except (ConnectionError, OSError):
|
||||
cprint(" Verbindung verloren.", RED)
|
||||
break
|
||||
|
||||
if raw is None:
|
||||
cprint(" Server hat Verbindung geschlossen.", YELLOW)
|
||||
break
|
||||
|
||||
try:
|
||||
msg = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
msg_type = msg.get("type", "")
|
||||
|
||||
if msg_type == "execute":
|
||||
command = msg.get("command", "")
|
||||
timestamp = time.strftime("%H:%M:%S")
|
||||
print(f" {CYAN}[{timestamp}] Ausfuehren: {WHITE}{command}{RESET}", flush=True)
|
||||
|
||||
exit_code = 0
|
||||
stdout = ""
|
||||
stderr = ""
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["bash", "-c", command],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300
|
||||
)
|
||||
stdout = result.stdout
|
||||
stderr = result.stderr
|
||||
exit_code = result.returncode
|
||||
except subprocess.TimeoutExpired:
|
||||
stderr = "Timeout nach 300 Sekunden"
|
||||
exit_code = 124
|
||||
except Exception as e:
|
||||
stderr = str(e)
|
||||
exit_code = 1
|
||||
|
||||
response = json.dumps({
|
||||
"type": "result",
|
||||
"exitCode": exit_code,
|
||||
"output": stdout or "",
|
||||
"error": stderr or ""
|
||||
})
|
||||
ws.send(response)
|
||||
|
||||
# Kurzausgabe lokal
|
||||
if stdout:
|
||||
preview = stdout[:200] + "..." if len(stdout) > 200 else stdout.rstrip()
|
||||
cprint(f" -> {preview}", GRAY)
|
||||
if stderr:
|
||||
cprint(f" [stderr] {stderr.rstrip()}", RED)
|
||||
color = GREEN if exit_code == 0 else RED
|
||||
cprint(f" [Exit: {exit_code}]", color)
|
||||
print()
|
||||
|
||||
elif msg_type == "ping":
|
||||
ws.send('{"type":"pong"}')
|
||||
|
||||
elif msg_type == "pong":
|
||||
pass
|
||||
|
||||
else:
|
||||
cprint(f" Unbekannte Nachricht: {msg_type}", YELLOW)
|
||||
|
||||
ws.close()
|
||||
print()
|
||||
cprint(" Verbindung beendet.", YELLOW)
|
||||
|
||||
|
||||
main()
|
||||
PYTHON_SCRIPT
|
||||
Reference in new issue
Block a user