
Detector non distruttivo per RCE non autenticata in BeyondTrust Remote Support e PRA, che combina l'iniezione di argomenti e il bypass dell'escape di PostgreSQL per verificare la sfruttabilità tramite differenziale basato sul tempo.
Avvertenza Etica: Solo a scopo di ricerca, formazione e test autorizzati. Non utilizzare contro sistemi senza autorizzazione.
BeyondTrust Remote Support (RS) e Privileged Remote Access (PRA) sono appliance che fanno da intermediari per sessioni remote privilegiate verso i sistemi interni di un'organizzazione, pertanto sono spesso esposte a internet e occupano una posizione di fiducia sulla rete.
Un attaccante non autenticato che riesce a raggiungere l'endpoint WebSocket dell'appliance può eseguire comandi del sistema operativo come utente site combinando due vulnerabilità: un'iniezione di argomenti nello script thin-scc-wrapper dell'appliance (CVE-2024-12356) e un bypass dell'escape delle stringhe PostgreSQL che trasforma un byte UTF-8 non valido in un'interruzione anticipata del letterale SQL (CVE-2025-1094). Il percorso vulnerabile viene raggiunto prima di qualsiasi autenticazione.
bt_rs_rce_check.py in questo repository è un rilevatore non distruttivo per quella catena. Pilota l'esatto percorso di codice non autenticato utilizzato dal vero exploit ma si ferma alla prova di raggiungibilità: l'unica cosa che chiede mai al target di eseguire è una innocua (una differenziale basata sul tempo) o una ricerca innocua di hostname/HTTP verso un collettore out-of-band che controlli tu. Non apre mai una shell, non scrive file, non legge dati, non persiste e non si muove lateralmente.
sleeppsql PostgreSQL per UTF-8 non valido, concatenata per completare la RCEBT24-10-ONPREM1 / BT24-10-ONPREM2 per appliance self-hosted; le istanze cloud sono state patchate da BeyondTrust).CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HLa catena produce l'esecuzione remota di codice non autenticata come utente site su un'appliance il cui intero scopo è fare da intermediario per l'accesso privilegiato ai sistemi interni, quindi una singola richiesta anonima converte un host RS o PRA esposto in un punto d'appoggio e in un pivot verso ogni sistema a valle che l'appliance media.
Non richiede credenziali, alcuna interazione con l'utente e alcun accesso precedente: l'attaccante è esterno e anonimo, e l'unico prerequisito è la raggiungibilità di rete dell'endpoint WebSocket dell'appliance.
La vulnerabilità è stata divulgata dopo essere stata trovata sfruttata in natura, è stata aggiunta al catalogo delle Vulnerabilità Note Sfruttate CISA il 19 dicembre 2024, ed è stata pubblicamente collegata alla violazione di dicembre 2024 del Dipartimento del Tesoro degli Stati Uniti attribuita a un attore sponsorizzato da uno stato.
La logica vulnerabile risiede nello script shell thin-scc-wrapper dell'appliance, che legge una chiave di sessione controllata dall'attaccante (gskey) da un frame WebSocket non autenticato e la passa verso il database. La RCE completa è una catena di tre problemi distinti.
1. Iniezione di argomenti in thin-scc-wrapper (CVE-2024-12356). Il wrapper passa la gskey non attendibile a un helper senza virgolette, quindi la shell la divide in argomenti prima che venga eseguita echo:
quoted=$(export PHPRC="$BG_app_root/config/php-cli.ini"; echo $gskey | $ingrediRoot/app/dbquote)
Una chiave che inizia con -e trasforma echo in echo -e, che interpreta le sequenze di escape con barra rovesciata e consente all'attaccante di emettere byte grezzi arbitrari — soprattutto \xC0 — nei dati che fluiscono verso la fase successiva. L'attaccante controlla un flag di un comando esistente piuttosto che iniettare un nuovo comando, motivo per cui questa è un'iniezione di argomenti (CWE-88) piuttosto che una classica iniezione di comandi.
2. Bypass dell'escape PostgreSQL tramite UTF-8 non valido (CVE-2025-1094). dbquote esegue l'escape del valore con pg_escape_string() di PHP, che chiama PQescapeStringInternal() di libpq. Per un carattere multibyte si fida della lunghezza riportata da pg_encoding_mblen() e copia i byte del carattere parola per parola, senza validarli:
len = pg_encoding_mblen(encoding, source);
for (i = 0; i < len; i++) /* copy the character */
*target++ = *source++;
Un byte come 0xC0 annuncia un carattere di 2 byte, quindi l'escaping ingloba il byte successivo come sua "continuazione" — anche quando quel byte è un apice singolo (0x27). La sequenza 0xC0 0x27 passa quindi con un byte di apice singolo non escapato ancora incorporato nella stringa "escapata".
3. psql esce dal letterale ed esegue \! (CVE-2025-1094). La stringa "escapata" viene passata a psql, che non tratta 0xC0 0x27 come un unico carattere, quindi 0x27 chiude il letterale della stringa SQL anticipatamente e tutto ciò che segue viene analizzato come nuovo input. L'attaccante lo usa per raggiungere il meta-comando \! di psql, che esegue un comando shell come utente site:
<0xC0>'; \! <cmd> #
Rapid7 ha scoperto che la RCE in pratica dipende sempre da CVE-2025-1094, e che essa è sufficiente da sola: consegnare il byte grezzo 0xC0 all'interno di un frame WebSocket binario (piuttosto che un frame di testo, dove la validazione UTF-8 lo eliminerebbe) raggiunge gskey senza modifiche e innesca la rottura SQL senza alcun passaggio echo -e. Questo rilevatore utilizza quell'approccio con frame binario.
L'intera catena è raggiungibile pre-autenticazione tramite un singolo WebSocket:
/nw (un handler Tornado sull'appliance).Sec-WebSocket-Protocol: ingredi support desk customer thin, inviato codificato in URL come ingredi%20support%20desk%20customer%20thin.Host (mappato a un'azienda installata) o, come fallback, dall'header X-Ns-Company. Se nessuno dei due mappa a un'azienda installata, l'appliance chiude il socket prima che gskey venga elaborata.1, un UUID thinMint, authType 0 (autenticazione gskey), quindi la gskey dannosa (la versione 1 significa che non c'è alcuna riga locale_code):1
<thinMint UUID>
0
<0xC0>'; \! <cmd> #
BT24-10 aggiunge un gate di validazione dell'input che rifiuta qualsiasi gskey non alfanumerica prima che raggiunga dbquote/psql:
elif [[ ! "$gskey" =~ ^[a-zA-Z0-9]{32}$ ]]; then
blog "bad session key given"
...
exit 1
Qualsiasi chiave contenente 0xC0, un apice o un metacarattere shell viene rifiutata e lo script termina, quindi un'appliance patchata non produce alcuna risposta 1 failure e nessuna esecuzione di comandi. Il rilevatore trasforma quella differenza comportamentale in una differenziale indipendente dall'egresso basata sul tempo che non richiede connettività in uscita dal target:
A × 32). Sia sulle build patchate che su quelle non patchate raggiunge la ricerca nel database e restituisce ['2\n', '1 failure\n'] rapidamente.<0xC0>'; \! sleep <ritardo> #. Su un host vulnerabile psql esegue la sleep iniettata, quindi la risposta 1 failure viene ritardata di circa <ritardo> secondi. Su un host patchato la regex gskey rifiuta la chiave, lo script termina e il socket si chiude rapidamente senza risposta.Poiché il verdetto si basa sulla tempistica della risposta dell'appliance stessa, vale anche quando il target blocca DNS/HTTP in uscita — un caso in cui un solo callback OAST darebbe un falso negativo. La modalità callback opzionale è solo una corroborazione, per gli host che consentono l'egresso.
Installa l'unica dipendenza:
pip install -r requirements.txt
Modalità differenziale (consigliata, indipendente dall'egresso):
python3 bt_rs_rce_check.py --target rs.example.com --mode differential
Differenziale con corroborazione OAST:
python3 bt_rs_rce_check.py --target rs.example.com \
--oast <tuo-collettore>.oast.pro --mode both
Quando l'header Host non risolve a un tenant, fornisci uno slug esplicito, o un file di slug candidati (uno per riga) provati a turno tramite l'header X-Ns-Company:
python3 bt_rs_rce_check.py --target rs.example.com --company acme
python3 bt_rs_rce_check.py --target rs.example.com --company-list slugs.txt
Opzioni principali:
| Opzione | Significato |
|---|---|
--target <host> | Host virtuale / FQDN dell'appliance (obbligatorio). |
--company <slug> | Slug del tenant esplicito, inviato come X-Ns-Company. |
--company-list <file> | File di slug candidati, provati finché uno non risolve. |
--oast <dominio> | Dominio del collettore out-of-band per la corroborazione del callback. |
--mode {differential,callback,both} | Strategia di rilevamento (predefinita differential). |
--delay <secondi> | Durata della sleep per il test basato sul tempo (predefinita 9). |
--timeout <secondi> | Timeout del socket per sonda (predefinito 25). |
--header 'H: v' | Header di richiesta grezzo aggiuntivo; ripetibile. |
Interpretazione del risultato:
VULNERABLE — la risposta 1 failure iniettata è stata ritardata di circa --delay secondi rispetto al controllo. La sleep è stata eseguita; la catena RCE non autenticata è sfruttabile su questo host.PATCHED — il controllo ha restituito 1 failure rapidamente, ma l'iniezione è stata rifiutata senza risposta. La regex gskey BT24-10 è in atto e l'iniezione non raggiunge mai il sink psql.UNRESOLVED ("company did not resolve") — nessuna delle due sonde ha prodotto un 1 failure, quindi il controllo non ha mai raggiunto nemmeno l'handler gskey. Questo non è un risultato patchato; l'appliance ha chiuso il socket perché Host/azienda non mappava a un'azienda installata. Riprova con l'host virtuale corretto (--target) o uno slug valido (--company / --company-list).Applica BT24-10 (BT24-10-ONPREM1 / BT24-10-ONPREM2) o aggiorna oltre la 24.3.1. Poiché la catena dipende anche dalla vulnerabilità psql PostgreSQL (CVE-2025-1094), mantieni patchato anche il tooling client PostgreSQL e limita l'esposizione di rete dell'endpoint WebSocket dell'appliance dove fattibile.
psql PostgreSQL UTF-8 non valido / SQL injection, concatenata per completare la RCE.rapid7/metasploit-framework, RCE non autenticata contro BeyondTrust RS/PRA.Questo software è fornito solo per test di sicurezza autorizzati e ricerca difensiva. Eseguilo esclusivamente contro sistemi di tua proprietà o per i quali hai esplicita autorizzazione scritta al test. È un rilevatore non distruttivo che evita deliberatamente l'esecuzione di comandi weaponizzati, l'accesso ai dati e la persistenza. Sei l'unico responsabile della conformità a tutte le leggi applicabili e dell'ottenimento della corretta autorizzazione; gli autori non accettano alcuna responsabilità per un uso improprio.