
Analisi e codice per CVE-2025-11492, CVE-2025-11493 - RCE in ConnctWise Automate RMM tramite Adversary-in-the-Middle
Nel corso di un test di penetrazione, ho scoperto diverse vulnerabilità nell'agente ConnectWise Automate Remote Monitoring and Management (RMM). ConnectWise è utilizzato da molti Managed Service Provider (MSP) per gestire e monitorare i dispositivi dei clienti. Queste vulnerabilità consentivano l'esecuzione remota di codice se un attaccante poteva stabilire un Adversary-in-the-Middle di rete, oppure potevano essere utilizzate come escalation dei privilegi locali e persistenza occulta se un attaccante otteneva l'esecuzione di codice o l'accesso fisico a un dispositivo che eseguiva l'agente ConnectWise Automate.
Le vulnerabilità sono state segnalate a ConnectWise il 20 agosto 2025. ConnectWise ha assegnato gli ID CVE e ha rilasciato una patch nella versione 2025.9 il 16 ottobre 2025.
Bollettino ConnectWise:
ID CVE:
2025.9 e pubblica il bollettino di sicurezza e i CVE.Ho apprezzato le risposte rapide di ConnectWise, il loro approccio collaborativo alla correzione e la disponibilità a discutere su come affrontare al meglio la classificazione e la correzione.
Classificare queste vulnerabilità è stata una sfida interessante. Sebbene il passaggio a HTTPS risolva praticamente tutti gli scenari descritti in questo report, era evidente che si trattava originariamente di una scelta progettuale (supportare HTTP) per migliorare l'affidabilità della comunicazione agente-server. Lo schema di crittografia sembrava riconoscere parzialmente / tentare di mitigare il rischio di AiTM, ma non era applicato in modo coerente. Approfondendo, si trattava di capire se la debolezza fosse http stesso o la mancanza di crittografia sopra HTTP, oltre a prevenzione del replay, validazione dei plugin, ecc., fossero tutte vulnerabilità a sé stanti. A un certo punto ConnectWise stava prendendo in considerazione 5+ CVE separati per diversi aspetti delle vulnerabilità.
Inoltre, il perimetro e il vettore di attacco cambiavano a seconda che la vulnerabilità fosse considerata da una prospettiva di AiTM (ad esempio Wi-Fi di un bar) o di LPE/accesso fisico. Un approccio alternativo sarebbe considerare ogni scenario come una vulnerabilità separata, ad esempio AiTM RCE, LPE, persistenza takeover, ecc.
Un ultimo insegnamento è che anche nel 2025 facciamo ancora fatica a condividere file in modo efficace :D (il filtro email non gradiva che inviassi file .dll o .zip che li contenevano).
Questo report viene pubblicato dopo il rilascio della patch da parte di ConnectWise e la divulgazione dei CVE, e con il loro accordo che tale divulgazione non danneggia i loro utenti. Inoltre, credo che la divulgazione pubblica di queste vulnerabilità e delle relative mitigazioni aiuterà altri vendor e professionisti della sicurezza a comprendere e mitigare meglio i rischi sia in ConnectWise Automate sia in altri sistemi RMM.
Il contenuto è destinato esclusivamente a finalità lecite e autorizzate di ricerca sulla sicurezza e scopi educativi. L'uso non autorizzato di queste informazioni per compromettere sistemi, reti o dati è illegale e non etico. Il contenuto è fornito "così com'è" e senza garanzie di alcun tipo. Gli autori declinano ogni responsabilità per eventuali danni derivanti dall'uso o dall'abuso di queste informazioni.
Se si utilizza questo codice o queste informazioni per ulteriori ricerche, praticare una divulgazione responsabile segnalando qualsiasi vulnerabilità scoperta ai vendor interessati.
Oltre al report qui sotto, questo repository contiene il codice PoC per dimostrare le vulnerabilità. Vedere automate_server/README.md per i dettagli sull'implementazione del server fittizio e sulle istruzioni d'uso.
Questo codice può essere utilizzato anche per effettuare ulteriori ricerche (etiche) sulla sicurezza di ConnectWise Automate.
Il report seguente (o una versione vicina ad esso) e il codice Python del PoC presente in questo repository sono stati forniti a ConnectWise, insieme alle mitigazioni raccomandate.
La sezione sulle mitigazioni rimossa entra più nel dettaglio sulle modifiche che potrebbero essere apportate all'agente Automate per indurirlo in diversi modi contro queste vulnerabilità.
Poiché alcune di queste modifiche sono ancora in fase di valutazione da parte di ConnectWise, tale sezione è stata rimossa da questa divulgazione pubblica.
L'agente ConnectWise Automate Remote Monitoring and Management (RMM) (testato sull'ultima versione ad agosto 2025, stringa di versione 250.252) è vulnerabile all'esecuzione remota di codice basata sulla rete in determinate configurazioni. Se l'agente è configurato per utilizzare un trasporto HTTP non crittografato (sia come primario sia come fallback) per il suo Server Address e un attaccante può eseguire un attacco Adversary-in-the-Middle (AiTM), allora può eseguire codice in remoto come SYSTEM. Questa configurazione è stata osservata in ambienti reali in più Managed Service Provider (MSP).
Lo sfruttamento è possibile anche se l'attaccante ottiene accesso fisico al dispositivo come non amministratore o può altrimenti collegare il dispositivo a una rete controllata dall'attaccante (cioè la vulnerabilità può essere utilizzata come escalation dei privilegi locali). Sebbene Automate utilizzi un sistema di crittografia per crittografare e validare la maggior parte dei comandi RMM, il suo sistema di plugin manca di una protezione adeguata e rimane suscettibile all'esecuzione remota di codice.
Implementando un server personalizzato che imita il server di controllo di Automate, l'agente Automate può essere indotto a scaricare ed eseguire un plugin malevolo.
L'agente compromesso può anche rappresentare una forma interessante di persistenza. Utilizzando la RCE per estrarre le chiavi di cifratura simmetrica dell'agente, il server personalizzato può inviare comandi arbitrari all'agente usando il canale RMM standard. Nello scenario AiTM, ciò consente all'attaccante di eseguire comandi RMM arbitrari, inclusi estrazione di file, dump delle credenziali, esecuzione di comandi e modifica della configurazione. L'attaccante potrebbe anche modificare il Server Address del RMM impostandolo sul proprio server, ottenendo una persistenza silenziosa anche dopo l'AiTM. In alternativa, la RCE può essere sfruttata per eseguire direttamente comandi a livello di sistema.
Server Address che include un endpoint http://. Configurazioni vulnerabili sono state osservate da due MSP distinti (domini e IP MSP reali sostituiti):
https://automate.msp-one.com|http://automate.msp-one.comhttps://msp.msp-two.com|http://msp.msp-two.com|http://12.346.6.78http:// viene usato come fallback se la connessione https:// fallisce, ma un attaccante può simulare questa condizione bloccando https.L'agente Automate può essere configurato per utilizzare un URL http:// come indirizzo del server. Questa configurazione è probabilmente impostata dallo script/pacchetto di installazione, ma può essere verificata controllando la chiave di registro HKLM\SOFTWARE\LabTech\Service\Server Address.

L'utilizzo di entrambi gli endpoint HTTPS e HTTP, separati da una pipe |, garantisce che, in teoria, se la connessione HTTPS incontra un problema, l'agente Automate ripiega sulla connessione HTTP.
Se un attaccante ottiene accesso Adversary-in-the-Middle (AiTM) al traffico di rete tra l'agente Automate e il server, può interrompere intenzionalmente la connessione HTTPS, facendola ripiegare su HTTP. Ciò consente all'attaccante di intercettare, monitorare e alterare il traffico scambiato tra agente e server. Un attaccante può quindi fare da reverse proxy al traffico verso https://automate.msp-one.com, ottenendo un "funzionamento standard" dell'agente, ma con l'attaccante in grado di intercettare i dati. Inoltre, può iniettare o modificare le risposte o persino impostare un server Automate completamente fraudolento che risponde alle richieste dell'agente Automate.
Ciò è stato ottenuto configurando un access point Wi-Fi "rogue" (hostapd, dnsmasq, inoltro IP + NAT) e utilizzando regole iptables per la ridirezione del traffico e uno script mitmproxy per il reverse proxy.

Altri dati più sensibili, tra cui programmi in esecuzione, configurazione di rete completa e percorsi di documenti, sono stati talvolta osservati nelle risposte dell'agente Automate.
iptables -t nat -A PREROUTING -i wlx90916440139a -p tcp --dport 80 -j REDIRECT --to-port 8080 iptables -A FORWARD -i wlx90916440139a -p tcp --dport 443 -j DROP
#### script mitmproxy:```python
# run with AiTMweb --mode transparent@8080 -s automate_AiTM.py
def request(flow: http.HTTPFlow):
if flow.request.pretty_host == 'automate.msp-one.com':
flow.request.url = 'https://automate.msp-one.com' + flow.request.path
Le soluzioni ZTNA possono complicare leggermente questa intercettazione, ma queste sono state aggirate in modo affidabile da ulteriori script mitmproxy che rilevano e bloccano condizionalmente la ZTNA, determinando il fallback a HTTP non tunnelizzato.
L'agente Automate utilizza un protocollo personalizzato sopra HTTP per comunicare con il server.
L'agente RMM sul dispositivo invia periodicamente richieste HTTP(S) all'endpoint /LabTech/agent.aspx del server. I tipi di richiesta rilevanti sono:
Si noti che la capitalizzazione incoerente dei percorsi non è un refuso; è così che l'agente Automate invia queste richieste. Tutti i percorsi degli endpoint sono racchiusi tra apici inversi per chiarezza.
/LabTech/Agent.aspx?DEPS e riceve una risposta XML contenente un elenco di file di dipendenza, i loro numeri di versione e i loro checksum./LabTech/Agent.Aspx?DepCheck=1&DependancyId=<id> e riceve un file binario offuscato contenente la dipendenza mascherata da file immagine (presumibilmente l'offuscamento ha lo scopo di impedire ai filtri dei contenuti di bloccare il download)./LabTech/agent.aspx?<id>?c<CMD id>&<arg count> e riceve una risposta impacchettata in modo personalizzato contenente dati specifici del comando.InitialCommandRetrieve), restituire i risultati dei comandi e per la chat.I "comandi remoti" sensibili sono spesso crittografati utilizzando uno schema di crittografia basato su DES3 con derivazione personalizzata della chiave da una system password e una computer password predefinite. Senza le password corrette, un attaccante non può visualizzare/iniettare/modificare i comandi legittimi inviati all'agente; tuttavia, le risposte ai comandi non sono crittografate e si è osservato che includono spesso dati sensibili in chiaro, tra cui file aperti, percorsi, nomi utente, programmi installati, ecc.
Il controllo delle dipendenze (/LabTech/Agent.aspx?DEPS) e i download (/LabTech/Agent.Aspx?DepCheck=1&DependancyId=<id>) non sono crittografati né firmati, il che significa che un attaccante può modificare la risposta per includere dipendenze dannose che verranno scaricate e caricate dall'agente RMM. Se il controllo delle dipendenze viene alterato per includere un checksum diverso, l'agente RMM eseguirà un nuovo download per il file di dipendenza non corrispondente e lo caricherà (a condizione che il file appena scaricato corrisponda al checksum alterato). I plugin sono assembly .NET che implementano un'interfaccia specifica e l'agente Automate caricherà qualsiasi assembly .NET che corrisponde all'interfaccia del plugin prevista e poi chiamerà l'interfaccia del plugin, determinando l'esecuzione di codice.
Esiste un secondo livello di validazione per le dipendenze dei plugin scaricate, in cui l'agente invia un comando cmdGetPlugins e valida anche che il checksum corrisponda in questa fase, ma questo comando non utilizza lo schema di crittografia.
Usando dnSpy, è stato possibile iniettare codice dannoso nella legittima ScreenConnectRemotePlugin.dll usata dall'agente. È stato creato un server Automate personalizzato con le funzionalità dei comandi DEPS, DepCheck e cmdGetPlugins. Il server finto replicava i plugin e le dipendenze attesi, ma modificava l'hash di ScreenConnectRemotePlugin.dll in un hash calcolato per il plugin modificato in modo dannoso. Il server finto forniva inoltre la ScreenConnectRemotePlugin.dll modificata in modo dannoso in risposta alla richiesta DepCheck (nel formato offuscato di falsa immagine) e implementava cmdGetPlugins anch'esso con l'hash manipolato. Infine, lo script mitmproxy è stato aggiornato per indirizzare l'agente Automate al server finto invece che al server legittimo (HTTPS).
Alla connessione, il plugin dannoso viene scaricato e caricato dall'agente. Il codice iniettato esfiltra le password "system" e "computer" verso il server finto. Le password system e computer sono memorizzate nel registro ma vengono decodificate da una libreria C# e nativa fortemente offuscata. Invece di estrarre i valori del registro, il plugin modificato usa la reflection per ottenere i valori decodificati, risparmiando lo sforzo di fare reverse engineering dell'offuscamento. In alternativa, l'intero contenuto della chiave di registro HKLM\SOFTWARE\LabTech\Service potrebbe essere esfiltrato e usato con una copia dell'agente RMM in un ambiente controllato per estrarre le password a runtime usando un debugger .NET.


In alternativa, il plugin può essere usato per eseguire direttamente comandi a livello di sistema; tuttavia, l'estrazione dei segreti ha permesso di utilizzare l'agente RMM stesso come command-and-control e ha fatto sì che il canale persistente non fosse rilevato dall'EDR. La persistenza può essere ottenuta aggiornando l'Server Address a un server controllato dall'attaccante, che può facoltativamente inoltrare i comandi al server legittimo per evitare che il dispositivo risulti mancante. Vedi 3. RMM Command‑and‑Control Takeover per maggiori dettagli.
È prevedibile che, anche se la configurazione predefinita non avesse plugin installati, un plugin vuoto (che corrisponda alle interfacce C# richieste per i plugin) avrebbe potuto essere compilato e inserito nelle risposte DEPS e cmdGetPlugins per ottenere lo stesso effetto.
È stato inoltre osservato che un "comando remoto" chiamato UpdatePlugins può essere restituito dal server, inducendo l'agente ad avviare un aggiornamento dei plugin. Poiché i comandi non includono la protezione dal replay, se un attaccante può osservare il comando inviato da un server legittimo, può riprodurlo verso qualsiasi altro agente per attivare l'aggiornamento. Ciò consente di sfruttare la vulnerabilità RCE in scenari aggiuntivi, aumentando l'impatto di questa vulnerabilità.
È stato inoltre osservato che l'auto-aggiornamento non è crittografato né firmato. Non è stata eseguita una dimostrazione di esecuzione di codice remoto tramite auto-aggiornamento, ma si ritiene che sia vulnerabile in modo simile. La modifica dannosa dell'auto-aggiornamento sarebbe più difficile da eseguire in modo furtivo e comporta un rischio maggiore di rompere l'agente Automate; tuttavia, è probabile che un approccio simile, basato sull'iniezione di codice .NET negli eseguibili/DLL, funzionerebbe. Il processo di auto-aggiornamento non è stato ulteriormente esplorato poiché la RCE tramite plugin era sufficiente e ritenuta più affidabile.
È anche possibile che lo sfruttamento del processo di auto-aggiornamento possa essere una soluzione praticabile al limite dello sfruttamento solo al riavvio (ad esempio, se una risposta potesse essere iniettata o riprodotta per attivare un auto-aggiornamento).
Usando l'approccio del punto 2.1 per estrarre la system password e la computer password, è stato possibile creare payload e risposte arbitrari di "comandi remoti". Invece di fare completamente reverse engineering e reimplementare lo schema di derivazione della chiave, è stato possibile importare semplicemente LabTechCommonBase.dll e usare Utilities.LabTechHash.ComputeHash per generare la chiave DES3 dalla "computer password". Usando la chiave DES calcolata e l'IV hardcoded estratto dalla DLL, è stato quindi possibile rispondere con comandi arbitrari all'agente RMM.```python
# IV extracted from decompiled LabTechSecurity.cs:
# this._initializationVector = new byte[] { 240, 3, 45, 29, 0, 76, 173, 59 };
iv = [240, 3, 45, 29, 0, 76, 173, 59]
# Helper functions for pythonnet, clr_loader, clr to load LabTechCommonBase.dll into python
labtech_net.setup_paths_and_references(str((Path(__file__).parent.parent / 'LTSvc').resolve()))
from LabTechCommonBase import Utilities
labtech_hash = Utilities.LabTechHash()
labtech_hash.ComputeHash(computer_password.encode('ascii')) # Use the exfiltrated computer password
byts = bytes(labtech_hash.GetDigestBytes())
cipher = DES3.new(byts, DES3.MODE_CBC, bytes(iv))
padded_data = pad(data.encode('utf-8'), DES3.block_size)
encrypted_data = cipher.encrypt(padded_data)
return base64.b64encode(encrypted_data).decode('utf-8')
Handler per una varietà di tipi di "Agent Command" sono stati implementati nel server Automate fittizio, consentendogli di inviare "Remote Commands" di ritorno all'agente. Questi comandi possono essere utilizzati per eseguire codice arbitrario sul dispositivo, come scaricare ed eseguire un payload, o stabilire tunnel di rete verso il dispositivo (ad esempio, configurando un proxy SOCKS che potrebbe essere utilizzato per fare pivot attraverso l'accesso ZTNA del dispositivo).
Per una pulizia immediata dal punto 2.1, il server può inviare il comando `UpdatePlugins` crittografato e servire il plugin legittimo originale per sovrascrivere il plugin modificato in modo malevolo. L'attaccante può ancora interagire con l'agente (supponendo che le password siano state estratte) e può mantenere la persistenza aggiornando il `Server Address` verso un server controllato dall'attaccante.

È stata inoltre dimostrata l'esecuzione arbitraria di comandi utilizzando il comando `Execute` per eseguire un ping, che è stato eseguito con successo sul dispositivo in un contesto di amministratore. Questo può essere servito come comando `InitialCommandRetrieve` o restituito in risposta ad altri "comandi agente"/check-in.```python
# Extract the RemoteCommandIDs enum from LabTechCommonBase.Constants using pythonnet
REMOTE_CMD_IDS = get_RemoteCommandIDs()
REMOTE_CMD_IDS_r = {str(v): k for k, v in REMOTE_CMD_IDS.items()}
# `register_cmd_handler` registers a command handler for the `/LabTech/agent.aspx?c<id>` endpoint.
# The string 'InitialCommandRetrieve' is mapped back to ID `36`.
# The full set of "Agent Command" and their IDs can be extracted from `LabTechCommonBase.Constants.modEnums.AgentCommandIDs`
@register_cmd_handler('InitialCommandRetrieve')
async def initial_command_retrieve(_req: dict) -> str:
cmd = '*!*'.join([
'202706506',
str(REMOTE_CMD_IDS_r['Execute']),
'!!!'.join([
'CMD.exe',
'/c ping -t -l 1337 192.168.20.2'
])
])
encrypted_cmd = encrypt(cmd)
return '|||'.join([
len_b64_gzip( # Simple helper to return '{len(data)}-{base64(gzip(data))}'
encrypted_cmd
),
# Generate common [latest-version, FILETIME(), queued command(s), MD5 hashes] encoding string expected in "agent command" responses
make_p2(),
])

L'uso di -l 1337 indica a ping di utilizzare una dimensione del payload di 1337 byte, che viene poi osservata nel secondo screenshot (1337 byte di payload + 42 byte di header = 1379 byte totali) come un "canary".
Sezione rimossa dalla divulgazione pubblica.