Aller au contenu

Protocole WebSocket

Stentor utilise des connexions WebSocket pour la communication bidirectionnelle en temps réel entre les composants du système. Deux hubs WebSocket indépendants gèrent des préoccupations différentes :

  • CockpitHub diffuse les événements vers les clients de l'interface utilisateur de l'opérateur (mises à jour des beacons, sortie des tâches, journaux de la console)
  • RelayHub gère les connexions des agents relais (commandes, pulsations, événements C2)

Les deux hubs utilisent le cadrage de messages JSON sur des connexions WebSocket standard (RFC 6455). Cette page documente les procédures de connexion, les formats de message, les types d'événements et les mécanismes de récupération pour chaque hub.


CockpitHub (Opérateur WebSocket)

CockpitHub est le flux d'événements en temps réel entre le serveur Stentor et les clients du navigateur de l'opérateur. Chaque session de l'interface utilisateur de l'opérateur ouvre une connexion WebSocket pour recevoir les mises à jour des beacons, la progression des tâches, les sorties de la console et les événements système au fur et à mesure qu'ils se produisent.

Connexion

Point de terminaison : GET /api/v1/cockpit/ws

CockpitHub prend en charge deux méthodes d'authentification. L'authentification basée sur les tickets est l'approche recommandée : elle évite d'exposer des JWT de longue durée dans les URL WebSocket (qui peuvent apparaître dans les journaux du serveur et les caches proxy).

Tout d'abord, obtenez un ticket de courte durée en appelant l'API REST avec votre jeton d'accès :

# Step 1: Get a WebSocket ticket (valid for 30 seconds, single-use)
TICKET=$(curl -s -X POST https://stentor.app/api/v1/auth/ws-ticket \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq -r '.ticket')

# Step 2: Connect with the ticket
wscat -c "wss://stentor.app/api/v1/cockpit/ws?ticket=$TICKET"
// JavaScript: Ticket-based WebSocket connection
async function connectCockpit(apiUrl, accessToken) {
  // 1. Fetch a single-use ticket
  const res = await fetch(`${apiUrl}/v1/auth/ws-ticket`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${accessToken}` },
  });
  const { ticket } = await res.json();

  // 2. Connect using the ticket (not the JWT)
  const wsUrl = apiUrl.replace(/^http/, 'ws');
  const ws = new WebSocket(`${wsUrl}/v1/cockpit/ws?ticket=${ticket}`);

  ws.onopen = () => console.log('Connected to CockpitHub');
  ws.onmessage = (e) => {
    const event = JSON.parse(e.data);
    console.log(`[seq=${event.seq}] ${event.type}:`, event.payload);
  };

  return ws;
}

Propriétés du billet

  • A usage unique : Chaque ticket ne peut être utilisé qu'une seule fois (consommé à la validation)
  • ** TTL de 30 secondes : ** les tickets expirent 30 secondes après la génération
  • Opaque : Les tickets sont des chaînes hexadécimales de 64 caractères (32 octets aléatoires)
  • Le nettoyage en arrière-plan s'exécute toutes les 60 secondes pour purger les tickets expirés

Transmettez le jeton d'accès directement en tant que paramètre de requête :

wscat -c "wss://stentor.app/api/v1/cockpit/ws?token=$ACCESS_TOKEN"

Avis de dépréciation

Le paramètre ?token= est obsolète. Les JWT dans les URL peuvent fuir via les journaux d'accès au serveur, l'historique du navigateur et les en-têtes de référent HTTP. Migrez vers l’authentification basée sur les tickets.

Réponses aux erreurs

Si l'authentification échoue, le serveur répond avec une erreur JSON avant la mise à niveau de WebSocket :

Coder Erreur Description
TICKET_EXPIRED ticket expired TTL du ticket dépassé (30 s)
TICKET_NOT_FOUND ticket not found Billet déjà utilisé ou n'a jamais existé
TICKET_INVALID invalid ticket Erreur de validation du billet
TOKEN_EXPIRED token expired Le jeton d'accès JWT a expiré
TOKEN_INVALID_SIGNATURE invalid token signature La vérification de la signature JWT a échoué
TOKEN_WRONG_TYPE invalid token type Le jeton n'est pas un jeton d'accès
TOKEN_INVALID invalid token Échec général de la validation JWT
AUTH_MISSING ticket or token required Aucune authentification fournie

Format des messages

Tous les messages de CockpitHub sont des SequencedEvents : chaque événement est entouré d'un numéro de séquence croissant de manière monotone :

{
  "seq": 42,
  "type": "beacon_update",
  "payload": {
    "beacon_id": "a1b2c3d4-...",
    "hostname": "WORKSTATION-01",
    "username": "CORP\\jsmith",
    "status": "active"
  }
}
Champ Type Description
seq uint64 Numéro de séquence croissant de manière monotone (commence à 1)
type string Identifiant du type d'événement (voir Types d'événements ci-dessous)
payload object Payload spécifique au type (varie selon le type d'événement)

Le champ seq permet aux clients de détecter les messages supprimés. Si votre client reçoit seq=40 suivi de seq=43, les séquences 41 et 42 ont été abandonnées (voir Drop Recovery).


Types d'événements

CockpitHub diffuse les types d'événements suivants à tous les clients opérateurs connectés.

beacon_update

L'état d'un beacon a changé (nouvelle beacon enregistrée, enregistrement ou changement de statut).

{
  "seq": 1,
  "type": "beacon_update",
  "payload": {
    "beacon_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "machine_ip": "10.0.0.20",
    "hostname": "WORKSTATION-01",
    "username": "CORP\\jsmith",
    "pid": 4832,
    "last_seen": "2026-02-19T10:00:00Z",
    "status": "active"
  }
}
Schéma de payload
Champ Type Description
beacon_id string UUID du beacon
machine_ip string Adresse IP de la machine cible
hostname string Nom d'hôte cible
username string Contexte utilisateur (DOMAIN\format utilisateur)
pid int ID de processus du beacon
last_seen string Horodatage ISO 8601 du dernier enregistrement
status string "active" ou "lost"

console_log

Une entrée de journal pour la console opérateur (entrée/sortie de commande, messages d'information, erreurs).

{
  "seq": 2,
  "type": "console_log",
  "payload": {
    "tab_id": "beacon-a1b2c3d4",
    "timestamp": "2026-02-19T10:00:05Z",
    "type": "output",
    "text": "Directory listing of C:\\Users\\jsmith\\Desktop..."
  }
}
Schéma de payload
Champ Type Description
tab_id string Onglet de la console cible : "event-log" pour le journal global, "beacon-{id}" pour les beacons spécifiques
timestamp string Horodatage ISO 8601
type string Type d'entrée : "input", "output", "info", "success", "error"
text string Contenu du message du journal

task_started

Une tâche a été envoyée à un beacon pour exécution.

{
  "seq": 3,
  "type": "task_started",
  "payload": {
    "task_id": "f1e2d3c4-...",
    "beacon_id": "a1b2c3d4-...",
    "technique": "shell",
    "status": "started"
  }
}
Schéma de payload
Champ Type Description
task_id string UUID de la tâche
beacon_id string UUID du beacon cible
technique string Nom de la commande/technique (par exemple, "shell", "ls", "hashdump")
status string Toujours "started" pour cet événement

task_completed

Une tâche a terminé son exécution sur un beacon.

{
  "seq": 4,
  "type": "task_completed",
  "payload": {
    "task_id": "f1e2d3c4-...",
    "beacon_id": "a1b2c3d4-...",
    "technique": "shell",
    "status": "completed",
    "output": "Volume in drive C has no label..."
  }
}
Schéma de payload
Champ Type Description
task_id string UUID de la tâche
beacon_id string UUID du beacon cible
technique string Nom de la commande/technique
status string "completed" ou "failed"
output string Sortie de commande (vide si aucune sortie)

Un lien de mouvement latéral a été établi entre deux machines (représenté sous forme d'arêtes dans le graphe pivot).

{
  "seq": 5,
  "type": "pivot_link",
  "payload": {
    "from": "machine-001",
    "to": "machine-002",
    "type": "pivot",
    "method": "smb"
  }
}
Schéma de payload
Champ Type Description
from string ID du nœud source
to string ID du nœud cible
type string Type de lien : "initial" (lien C2) ou "pivot" (mouvement latéral)
method string Méthode de déplacement : "smb", "wmi", "winrm", "dcom" (vide pour les liens initiaux)

lateral_movement_completed

Une opération de mouvement latéral a commencé, s'est terminée ou a échoué (déclenche des animations de bord dans la vue topologique).

{
  "seq": 6,
  "type": "lateral_movement_completed",
  "payload": {
    "task_id": "f1e2d3c4-...",
    "source_beacon_id": "a1b2c3d4-...",
    "source_machine_id": "machine-001",
    "target_ip": "10.10.10.50",
    "method": "psexec",
    "status": "completed"
  }
}
Schéma de payload
Champ Type Description
task_id string UUID de la tâche
source_beacon_id string Beacon initiant le mouvement
source_machine_id string Identifiant de la machine source
target_ip string IP de la machine cible
method string Méthode de mouvement latéral (par exemple, "psexec", "wmi", "winrm", "dcom")
status string "started", "completed" ou "failed"

shell_output

Sortie en temps réel d'une session shell interactive (routes vers l'onglet de terminal xterm.js correct).

{
  "seq": 7,
  "type": "shell_output",
  "payload": {
    "beacon_id": "a1b2c3d4-...",
    "output": "C:\\Users\\jsmith> ",
    "is_error": false,
    "timestamp": "2026-02-19T10:01:00Z"
  }
}
Schéma de payload
Champ Type Description
beacon_id string Beacon UUID (achemine la sortie vers l'onglet correct du terminal)
output string Texte de sortie du shell
is_error bool S'il s'agit d'une sortie stderr
timestamp string Horodatage ISO 8601

file_transfer_progress

Mise à jour de la progression pour une opération de téléchargement ou de téléchargement de fichier en cours.

{
  "seq": 8,
  "type": "file_transfer_progress",
  "payload": {
    "transfer_id": "t1r2a3n4-...",
    "beacon_id": "a1b2c3d4-...",
    "direction": "download",
    "filename": "secrets.docx",
    "progress": 0.65,
    "bytes_done": 6553600,
    "bytes_total": 10085376,
    "status": "in_progress",
    "sha256": ""
  }
}
Schéma de payload
Champ Type Description
transfer_id string Identifiant de transfert unique (corrèle les événements de progression)
beacon_id string UUID du beacon
direction string "download" (beacon vers serveur) ou "upload" (serveur vers beacon)
filename string Nom de fichier d'origine
progress float Taux d'achèvement (0,0 à 1,0)
bytes_done int64 Octets transférés jusqu'à présent
bytes_total int64 Taille totale du fichier en octets
status string "in_progress", "completed" ou "failed"
sha256 string Hachage du fichier (rempli à la fin)

beacon_highlight_update

Un opérateur a modifié la couleur de surbrillance d'un beacon (repère visuel de collaboration).

{
  "seq": 9,
  "type": "beacon_highlight_update",
  "payload": {
    "beacon_id": "a1b2c3d4-...",
    "color": "#ef4444"
  }
}
Schéma de payload
Champ Type Description
beacon_id string UUID du beacon
color string Valeur de couleur CSS (hexadécimale) ou chaîne vide lorsque la surbrillance est supprimée

dialog_request

Un script CNA demande l'entrée de l'opérateur via une invite de dialogue (prompt_text, prompt_confirm, prompt_file, dialog_show).

{
  "seq": 10,
  "type": "dialog_request",
  "payload": {
    "dialog_type": "prompt_text",
    "title": "Enter target username",
    "message": "Provide the username for lateral movement:",
    "default_value": "administrator"
  }
}

Un script CNA demande la navigation vers une page spécifique (openBeaconConsole, openPayloadDialog, etc.).

{
  "seq": 11,
  "type": "navigate",
  "payload": {
    "target": "beacon_console",
    "beacon_id": "a1b2c3d4-..."
  }
}

cna_tab

Un script CNA crée un onglet de visualisation personnalisé (addTab, showVisualization).

{
  "seq": 12,
  "type": "cna_tab",
  "payload": {
    "tab_id": "custom-viz-1",
    "title": "Attack Graph",
    "content_type": "html",
    "content": "<div>...</div>"
  }
}

siem_alert

Une alerte SIEM transmise aux clients opérateurs pour une sensibilisation en temps réel.

{
  "seq": 13,
  "type": "siem_alert",
  "payload": {
    "rule": "Suspicious PowerShell execution",
    "severity": "high",
    "host": "WORKSTATION-01",
    "timestamp": "2026-02-19T10:02:00Z"
  }
}

dropped_messages

Envoyé lorsque le serveur détecte que des messages ont été supprimés pour ce client (voir Drop Recovery).

{
  "seq": 50,
  "type": "dropped_messages",
  "payload": {
    "last_sent_seq": 42,
    "current_seq": 50,
    "dropped_count": 8
  }
}
Schéma de payload
Champ Type Description
last_sent_seq uint64 Dernier numéro de séquence envoyé avec succès à ce client
current_seq uint64 Numéro de séquence du serveur actuel
dropped_count uint64 Nombre de messages supprimés

Récupération de chute

CockpitHub utilise un tampon d'envoi limité (1024 messages par client). Lorsque le tampon d'un client est plein (par exemple, une connexion lente ou un volume d'événements important), le serveur ne peut pas fournir de nouveaux événements. Au lieu de supprimer silencieusement les messages, le serveur :

  1. Envoie un événement dropped_messages avec la plage d'intervalle (last_sent_seq à current_seq)
  2. Si même la notification de suppression ne peut pas être envoyée, le client est déconnecté

Lorsque votre client reçoit un événement dropped_messages, la stratégie de récupération recommandée est la suivante :

ws.onmessage = (e) => {
  const event = JSON.parse(e.data);

  if (event.type === 'dropped_messages') {
    console.warn(
      `Dropped ${event.payload.dropped_count} messages ` +
      `(seq ${event.payload.last_sent_seq + 1} - ${event.payload.current_seq})`
    );

    // Full data refresh -- re-fetch all mutable state from REST API
    refreshBeacons();
    refreshTasks();
    refreshCredentials();
    return;
  }

  // Normal event processing
  handleEvent(event);
};

Suivi du numéro de séquence

Suivez la dernière valeur seq reçue. A la reconnexion, comparez-la avec la séquence actuelle du serveur (disponible via l'API REST) ​​pour déterminer si une actualisation est nécessaire.


Messages client à serveur

CockpitHub accepte également les messages des clients connectés. Le ReadPump traite les messages JSON entrants et les achemine par type.

tunnel_local_response

Trames de tunnel renvoyées depuis les navigateurs de l'opérateur pour rportfwd_local (transfert de port inversé via le réseau de l'opérateur).

{
  "type": "tunnel_local_response",
  "beacon_id": "a1b2c3d4-...",
  "bind_port": 8080,
  "frames": [
    {
      "conn_id": 1,
      "type": 2,
      "data": "<base64>"
    }
  ]
}
Champ Type Description
type string Toujours "tunnel_local_response"
beacon_id string UUID de beacon qui possède le port inverse avant
bind_port uint16 Port de liaison local sur la machine de l'opérateur
frames array Trames de tunnel (données de connexion revenant au beacon)

RelayHub (Relais WebSocket)

RelayHub gère les connexions WebSocket persistantes entre le serveur Stentor et les agents de relais. Les relais sont des processus Linux (généralement exécutés sur Kali) qui hébergent les listeners C2, traitent les enregistrements de beacons et exécutent la génération de payload.

Connexion

Point de terminaison : GET /ws/relay

Les relais s'authentifient à l'aide d'un secret partagé et de leur UUID enregistré. Les deux peuvent être fournis via des en-têtes (de préférence) ou des paramètres de requête.

wscat -c "wss://stentor.app/ws/relay" \
  -H "X-Relay-ID: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" \
  -H "X-Relay-Secret: your-shared-secret"
wscat -c "wss://stentor.app/ws/relay?relay_id=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee&secret=your-shared-secret"

Conditions préalables

  1. Le relais doit exister dans la base de données -- Le serveur valide l'UUID du relais par rapport à la table relays. Si le relais n'est pas enregistré, la connexion renvoie HTTP 404.
  2. Le secret partagé doit correspondre -- L'en-tête X-Relay-Secret (ou le paramètre de requête secret) doit correspondre à la variable d'environnement RELAY_SECRET configurée sur le serveur.

Réponses aux erreurs

Statut Erreur Description
400 relay_id required Aucun identifiant de relais fourni
400 invalid relay_id format L'ID de relais n'est pas un UUID valide
401 invalid relay secret Le secret ne correspond pas à la configuration du serveur
404 relay not found UUID du relais non enregistré dans la base de données
500 internal error Échec de la requête de base de données

Inscription à la base de données requise

Après une reconstruction de la base de données, les enregistrements de relais sont perdus. Vous devez réinsérer le relais dans la table relays avant que le relais puisse se reconnecter :

INSERT INTO relays (id, name, description, ip_address, status)
VALUES ('aaaaaaaa-...', 'Kali Relay', 'Relay agent', '10.0.0.50', 'online')
ON CONFLICT (id) DO NOTHING;

Format des messages

Tous les messages RelayHub utilisent l'enveloppe RelayMessage :

{
  "type": "command",
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "correlation_id": null,
  "timestamp": "2026-02-19T10:00:00Z",
  "payload": { ... }
}
Champ Type Description
type string Type de message : "command", "response", "heartbeat", "event", "ack"
id uuid Identifiant unique du message
correlation_id uuid? Pour les réponses : le id de la commande d'origine (permet la correspondance requête/réponse)
timestamp string Horodatage de création ISO 8601
payload object Payload spécifique au type (voir les sections ci-dessous)

Types de messages

command (backend vers relais)

Les commandes sont envoyées du serveur à un relais pour lancer des actions (démarrer les listeners, mettre les tâches en file d'attente, générer des payloads).

{
  "type": "command",
  "id": "cmd-uuid",
  "timestamp": "2026-02-19T10:00:00Z",
  "payload": {
    "command": "start_listener",
    "args": {
      "listener_id": "6ea88162-...",
      "name": "HTTPS Relay",
      "type": "https",
      "bind_address": "0.0.0.0",
      "port": 8443
    }
  }
}

Schéma de payload (RelayCommandPayload) :

Champ Type Description
command string Nom de la commande (voir Référence de la commande)
args object Arguments spécifiques à la commande (objet JSON)

response (relais vers backend)

Les réponses portent le résultat d'une commande reçue précédemment. Le correlation_id relie la réponse au id de la commande d'origine.

{
  "type": "response",
  "id": "resp-uuid",
  "correlation_id": "cmd-uuid",
  "timestamp": "2026-02-19T10:00:01Z",
  "payload": {
    "success": true,
    "error": "",
    "data": { "listener_id": "6ea88162-...", "status": "running" }
  }
}

Schéma de payload (RelayResponsePayload) :

Champ Type Description
success bool Si la commande a réussi
error string Message d'erreur (vide en cas de succès)
data object? Données de réponse (spécifiques à la commande, présentes en cas de succès)

heartbeat (bidirectionnel)

Messages de surveillance de l'état envoyés dans les deux sens pour maintenir la connexion et signaler l'état du relais.

{
  "type": "heartbeat",
  "id": "hb-uuid",
  "timestamp": "2026-02-19T10:00:30Z",
  "payload": {
    "relay_id": "aaaaaaaa-...",
    "status": "idle"
  }
}

Schéma de payload (RelayHeartbeatPayload) :

Champ Type Description
relay_id string UUID de l'instance de relais
status string État actuel du relais : "idle", "busy" ou "error"

event (relais vers backend)

Événements asynchrones du relais vers le serveur (enregistrements de beacons, réalisations de tâches, activité de phishing).

{
  "type": "event",
  "id": "evt-uuid",
  "timestamp": "2026-02-19T10:01:00Z",
  "payload": {
    "event": "beacon_new",
    "data": {
      "beacon_id": "a1b2c3d4-...",
      "hostname": "WORKSTATION-01",
      "username": "jsmith",
      "ip": "10.0.0.20",
      "os": "Windows 10",
      "arch": "x64",
      "pid": 4832
    }
  }
}

Schéma de payload (RelayEventPayload) :

Champ Type Description
event string Nom de l'événement (voir Référence de l'événement)
data object Données spécifiques à l'événement

ack (relais vers backend)

Accusé de réception envoyé par le relais immédiatement à la réception d'une commande, avant le début de son exécution. Cela confirme la réception tandis que le résultat réel arrive plus tard sous la forme d'un response.

{
  "type": "ack",
  "id": "ack-uuid",
  "timestamp": "2026-02-19T10:00:00Z",
  "payload": {
    "command_id": "cmd-uuid",
    "received": "2026-02-19T10:00:00Z"
  }
}

Schéma de payload (RelayAckPayload) :

Champ Type Description
command_id uuid ID de la commande en cours d'accusé de réception
received string Horodatage ISO 8601 lors de la réception de la commande

Référence des commandes

Commandes envoyées depuis le serveur Stentor aux agents relais. Chaque commande est enveloppée dans un RelayCommandPayload avec le nom de la commande et les arguments.

Gestion des listeners

Commande Description Arguments clés
start_listener Démarrer un listener C2 sur le relais listener_id, name, type, bind_address, port, profile_name, tls_cert, tls_key, dns_domain, smb_pipe_name
stop_listener Arrêter un listener en cours d'exécution listener_id
host_file Héberger un fichier sur un URI personnalisé sur un listener listener_id, uri, data, content_type, filename
unhost_file Supprimer un fichier hébergé d'un listener listener_id, uri
Arguments start_listener (StartListenerArgs)
Champ Type Description
listener_id string UUID du listener
name string Nom d'affichage
type string Protocole : "http", "https", "dns", "smb_pipe"
bind_address string Adresse d'écoute (par exemple, "0.0.0.0")
port int Port d'écoute
profile_name string? Nom du profil malléable C2
profile_config object? Configuration du profil JSON
tls_cert string? Certificat TLS PEM (pour HTTPS)
tls_key string? Clé privée TLS PEM (pour HTTPS)
dns_domain string? Domaine DNS (pour les listeners DNS)
dns_ttl int? TTL de l'enregistrement DNS
dns_resolver string? Résolveur DNS personnalisé
smb_pipe_name string? Nom du canal nommé (pour les listeners SMB)
beacon_port int? Remplacement du port pour le rappel du beacon
guardrails object? Règles de garde-fou d’exécution
wg_private_key string? Clé privée WireGuard (pour VPN)
wg_peer_public_key string? Clé publique homologue WireGuard
wg_tunnel_ip string? IP du tunnel WireGuard
wg_peer_tunnel_ip string? IP du tunnel homologue WireGuard
wg_c2_port int? Port WireGuard C2

Opérations de tâches

Commande Description Arguments clés
queue_task Mettre en file d'attente une tâche pour la livraison du beacon lors du prochain enregistrement beacon_id, task_type, task_data
tunnel Envoyer des trames de tunnel SOCKS à un beacon beacon_id, frames[]
covertvpn Gérer l'interface VPN TAP secrète (Arguments spécifiques au VPN)
Arguments queue_task (QueueTaskPayload)
Champ Type Description
task_id uuid? Facultatif : ID de tâche fourni par le backend pour la corrélation
beacon_id uuid UUID du beacon cible
task_type string Type de tâche (par exemple, "shell", "upload", "inject")
task_data object Arguments spécifiques à la tâche

Génération de payload

Commande Description Arguments clés
generate_payload Générer un binaire de payload (EXE, DLL, shellcode, etc.) type, c2_url, obfuscate, profile, evasion_options, architecture
generate_clr_shellcode Générer le shellcode du chargeur CLR pour PowerShell assembly, script, arch
generate_assembly_shellcode Générer du shellcode pour l'exécution de l'assembly .NET assembly, args, arch
generate_browser_pivot_shellcode Générer le shellcode du proxy WinINet pipe_name, arch
sign_payload Authenticode-signer un binaire de payload payload, cert_pfx, password
generate_payload Arguments (GeneratePayloadArgs)
Champ Type Description
type string Type de payload : "exe", "dll", "shellcode", "lnk", "docm", "xlsm", "iso", "hta", "html_smuggling", "stager", "service", "msi"
c2_url string URL de rappel C2
obfuscate bool Utilisez garble pour un hachage binaire unique par build
profile string Nom du profil malléable C2
architecture string? Arc cible : "x64", "x86"
sleep int? Intervalle de veille du beacon (secondes)
jitter int? Pourcentage de gigue (0-100)
evasion_options object? Contournement AMSI, patch ETW, basculement d'obscurcissement du sommeil
guardrail_config object? Restrictions d'exécution (IP, nom d'hôte, domaine, modèles de nom d'utilisateur)
kill_date string? Date d'expiration de le payload RFC 3339
exit_func string? Fonction de sortie : "process" ou "thread"

Configurations spécifiques au type supplémentaires : iso_config, hta_config, html_smuggling_config, stager_config, dll_config, service_config, shellcode_config, msi_config, udrl_config, drip_config, proxy_config, host_rotation_config, sleep_mask_config, udc2_config.

Opérations de phishing

Commande Description Arguments clés
send_email Envoyer un email de phishing via le relais template_id, from_email, to_email, tracking_id, tracking_base_url
preview_email Aperçu d'un modèle d'e-mail rendu template_id, target_email
host_payload Hébergez un payload sur le serveur de suivi payload_id, data, content_type

Référence d'événement

Événements envoyés par les agents relais au serveur Stentor. Chaque événement est enveloppé dans un RelayEventPayload.

Événements du cycle de vie des beacons

Événement Description Champs de données clés
beacon_new Nouvelle beacon enregistrée depuis une cible beacon_id, hostname, username, ip, os, arch, pid, listener_id
beacon_checkin Beacon existante enregistrée beacon_id, hostname, username, ip, os, arch, pid
beacon_dead Beacon a manqué les enregistrements et est marqué comme mort beacon_id
beacon_new / beacon_checkin Payload (BeaconEventPayload)
Champ Type Description
beacon_id uuid UUID du beacon
hostname string Nom d'hôte cible
username string Contexte utilisateur
ip string Adresse IP cible
os string Système d'exploitation (par exemple, "Windows 10")
arch string Architecture ("x64", "x86")
pid int ID de processus
listener_id string? UUID du listener d'origine

Événements de tâches

Événement Description Champs de données clés
task_complete Exécution de la tâche terminée sur un beacon task_id, beacon_id, success, output, error, artifacts[]

??? remarque « payload task_complete (TaskCompletePayload) »

| Champ | Type | Description |
|-------|------|-------------|
| `task_id` | `uuid` | UUID de la tâche |
| `beacon_id` | `uuid` | Beacon qui a exécuté la tâche |
| `task_type` | `string?` | Type de tâche (facultatif pour la compatibilité ascendante) |
| `success` | `bool` | Si l'exécution a réussi |
| `output` | `string` | Sortie de commande |
| `error` | `string?` | Message d'erreur (en cas d'échec) |
| `artifacts` | `array?` | Artefacts médico-légaux produits (fichiers, clés de registre) |

Événements du cycle de vie des listeners

Événement Description Champs de données clés
listener_started L'listener a démarré avec succès listener_id, status, port
listener_stopped L'listener s'est arrêté listener_id, status
listener_error L'listener a rencontré une erreur listener_id, status, error

??? remarque « Payload du statut d'écoute (ListenerStatusPayload) »

| Champ | Type | Description |
|-------|------|-------------|
| `listener_id` | `string` | UUID du listener |
| `status` | `string` | `"running"`, `"stopped"` ou `"error"` |
| `error` | `string?` | Description de l'erreur (pour `listener_error`) |
| `port` | `int?` | Port lié réel (pour `listener_started`) |

Événements dans les tunnels

Événement Description Champs de données clés
tunnel_response Cadres de tunnel SOCKS à partir d'un beacon beacon_id, frames[]

??? remarque « Payload d'événement de tunnel (TunnelEventPayload) »

| Champ | Type | Description |
|-------|------|-------------|
| `beacon_id` | `uuid` | UUID du beacon |
| `frames` | `array` | Tableau d'objets TunnelFrame |

**Cadre tunnel :**

| Champ | Type | Description |
|-------|------|-------------|
| `conn_id` | `uint32` | ID du multiplexeur de connexion |
| `type` | `uint8` | Type de trame : `1`=connexion, `2`=données, `3`=fermer, `4`=erreur |
| `data` | `bytes` | Payload (adresse pour la connexion, octets bruts pour les données) |

Événements de garde-corps

Événement Description Champs de données clés
guardrail_violation Une règle de garde-fou s'est déclenchée sur le relais Détails des violations spécifiques aux règles

Événements de phishing

Événement Description Champs de données clés
email_sent E-mail de phishing envoyé avec succès tracking_id, to_email, timestamp, success
email_opened Pixel de suivi chargé par cible tracking_id, timestamp, user_agent, ip
link_clicked Lien de phishing cliqué par cible tracking_id, timestamp, user_agent, ip
creds_captured Identifiants capturés à partir de la page de destination tracking_id, username, password, timestamp
payload_downloaded Fichier de payload téléchargé par cible payload_id, timestamp, user_agent, ip
keystroke_captured Frappes capturées à partir du site cloné tracking_id, keystrokes, timestamp

Événements de l’AIIC

Événement Description Champs de données clés
profiler_hit Visite du profileur système collectée external_ip, internal_ip, user_agent, platform, applications, token
web_hit Requête HTTP servie par le serveur Web method, uri, remote_addr, user_agent, response_code, response_size, handler

Commandes synchrones

Certaines opérations nécessitent d'attendre la réponse d'un relais avant de continuer (par exemple, la génération de payload doit renvoyer les données binaires). Le mécanisme SendCommandSync implémente une requête/réponse synchrone sur le canal WebSocket asynchrone.

sequenceDiagram
    participant Server as Stentor Server
    participant Relay as Relay Agent

    Server->>Server: Create command with unique ID
    Server->>Server: Register pending request (ID -> response channel)
    Server->>Relay: Send command message
    Relay->>Server: Send ack (command_id)
    Note over Relay: Execute command...
    Relay->>Server: Send response (correlation_id = command ID)
    Server->>Server: Match correlation_id to pending request
    Server->>Server: Deliver response via channel
    Note over Server: Returns data to caller

Gestion du délai d'attente : Si le relais ne répond pas dans le délai d'attente spécifié (généralement 30 à 60 secondes pour la génération de payload), la requête renvoie une erreur ErrRelayTimeout. La demande en attente est nettoyée quel que soit le résultat.

Corrélation : Le relais inclut le id de la commande d'origine comme correlation_id dans sa réponse. La fonction handleMessage du serveur vérifie les réponses entrantes par rapport à la carte pendingRequests : si une correspondance est trouvée, la réponse est transmise à la goroutine en attente et l'entrée en attente est supprimée.


Cycle de vie de la connexion

Cycle de vie du CockpitHub

sequenceDiagram
    participant Client as Operator Browser
    participant API as REST API
    participant Hub as CockpitHub

    Client->>API: POST /v1/auth/login
    API-->>Client: access_token

    Client->>API: POST /v1/auth/ws-ticket
    Note over Client: Authorization: Bearer <token>
    API-->>Client: { ticket: "a3f2..." }

    Client->>Hub: GET /v1/cockpit/ws?ticket=a3f2...
    Hub->>Hub: Validate ticket (single-use, 30s TTL)
    Hub-->>Client: 101 Switching Protocols

    Hub->>Hub: Register client, fire event_join CNA event

    loop Real-time Events
        Hub->>Client: SequencedEvent { seq, type, payload }
    end

    Hub->>Client: Ping (every 54s)
    Client->>Hub: Pong (must respond within 60s)

    alt Client Send Buffer Full
        Hub->>Client: dropped_messages event
        Note over Client: Re-fetch all data from REST API
    end

    alt Disconnection
        Hub->>Hub: Unregister client, fire event_quit CNA event
        Hub->>Hub: Clean up operator tunnel forwards
    end

Stratégie de reconnexion : Lorsque la connexion WebSocket est interrompue, les clients doivent :

  1. Attendez brièvement (intervalle exponentiel avec gigue : 1 s initiales, 30 s maximum)
  2. Demander un nouveau billet via POST /v1/auth/ws-ticket
  3. Reconnectez-vous avec le nouveau billet
  4. Comparez le dernier seq reçu avec la séquence actuelle du serveur
  5. S'il y a un écart, effectuez une actualisation complète des données

Cycle de vie du RelayHub

sequenceDiagram
    participant DB as PostgreSQL
    participant Relay as Relay Agent
    participant Hub as RelayHub

    Note over DB: Relay must be registered in relays table

    Relay->>Hub: GET /ws/relay
    Note over Relay: X-Relay-ID + X-Relay-Secret headers
    Hub->>Hub: Validate secret
    Hub->>DB: GetByID(relay_uuid)
    DB-->>Hub: Relay record (or 404)
    Hub-->>Relay: 101 Switching Protocols

    Hub->>DB: UpdateStatus(relay_id, "connected")
    Hub->>Hub: Register client (replaces existing if same ID)

    loop Heartbeat Loop
        Relay->>Hub: heartbeat { relay_id, status: "idle" }
    end

    loop Commands & Events
        Hub->>Relay: command { command: "start_listener", args: {...} }
        Relay->>Hub: ack { command_id: "..." }
        Relay->>Hub: response { correlation_id: "...", success: true }
        Relay->>Hub: event { event: "beacon_new", data: {...} }
    end

    alt Disconnection
        Hub->>Hub: Unregister client
        Hub->>DB: UpdateStatus(relay_id, "disconnected")
    end

Reconnexion du relais : Si le processus de relais redémarre ou si la connexion WebSocket est interrompue :

  1. Le relais tente de se reconnecter avec le même UUID et le même secret
  2. Si un client avec le même identifiant est déjà enregistré, l'ancienne connexion est remplacée
  3. L'état du relais est mis à jour à "connecté" dans la base de données
  4. Les listeners actifs doivent être redémarrés après la reconnexion (l'état n'est pas préservé)