Telemetria e reazioni delle difese VM
Segnalare al proprio backend i rilevamenti delle difese VM con vmDefenseHook e regolare la reazione di ogni categoria di rilevamento con vmDefenseReaction, da una build puramente di telemetria che non interrompe nulla fino al blocco netto di un bundle sottratto.
Il problema
Le difese VM - vmSelfDefending, vmDebugProtection e vmDomainLock - agiscono localmente: quando viene rilevato un debugger, uno strumento di automazione, un ambiente manomesso o un dominio non autorizzato, il codice protetto si blocca oppure altera silenziosamente i propri risultati. L'attaccante viene così fermato, ma per impostazione predefinita non se ne viene mai a conoscenza. Non è possibile sapere con quale frequenza il proprio bundle venga sondato, quale rilevatore sia scattato né se una difesa stia bloccando un utente legittimo.
Dalla versione 7.1.0 due opzioni colmano questa lacuna. Nessuna delle due attiva alcuna difesa: si limitano a osservare e a orientare le difese già attivate:
vmDefenseHook- una callback globale che riceve un oggetto segnale ogni volta che una difesa rileva qualcosa. Si utilizza per inviare telemetria al proprio backend.vmDefenseReaction- una mappa per categoria che seleziona come una difesa attiva debba reagire: blocco, avvelenamento dei dati oppure nessuna azione locale.
Ricetta 1 - segnalare i rilevamenti al proprio backend
Passaggio 1 - registrare una funzione hook globale prima del caricamento del bundle offuscato
Il runtime della VM e le relative difese vengono eseguiti prima del programma protetto, per cui molti rilevamenti si verificano già in fase di avvio. Occorre quindi definire l'hook come una semplice variabile globale nella pagina host, prima del tag script offuscato:
<script>
// In your page, BEFORE the obfuscated script:
window.__vmDetection = function (signal) {
navigator.sendBeacon('/api/vm-defense', JSON.stringify(signal));
};
</script>
<script src="/app.obfuscated.js"></script>
Passaggio 2 - fare puntare vmDefenseHook alla funzione
L'opzione è un oggetto il cui campo name indica la funzione globale da richiamare (aliases è facoltativo, si veda più avanti):
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
// the hook alone enables nothing - a defense must be on for detectors to run:
vmSelfDefending: true,
vmDebugProtection: true,
vmDefenseHook: { name: '__vmDetection' }
});
La forma con semplice stringa (vmDefenseHook: '__vmDetection') è ancora accettata come abbreviazione di { name: '__vmDetection' }, ma è deprecata: è preferibile la forma a oggetto.
Nella dashboard il campo VM Defense Hook compare nel pannello delle opzioni VM non appena è attiva almeno una difesa (vmSelfDefending, vmDebugProtection o vmDomainLock).
Passaggio 3 - ricevere il segnale sul proprio backend
Ogni rilevamento richiama l'hook con un singolo oggetto signal:
source- il rilevatore specifico:headless,node,agent,domain,debugger,sandbox,nativeHook,timingoppureintegrity. A partire dalla v7.4.0 i precedenti rilevatorienveinspectorvengono segnalati sottosource: 'debugger'.category-automation,debugger,sandbox,domain,tamperoppureintegrity. Il sourcenodeviene segnalato sottocategory: 'debugger'(v7.4.0+).score,threshold- il punteggio del rilevamento e la soglia superata
Segue un endpoint di ricezione minimo (l'esempio utilizza Express, ma è adatto qualsiasi backend in grado di accettare una POST). Il corpo della richiesta viene normalizzato in un array, così da gestire anche la forma raggruppata inviata dal pattern con buffer illustrato più avanti:
app.post('/api/vm-defense', express.text({ type: '*/*' }), (req, res) => {
// a signal: { source: 'headless', category: 'automation', score: 7, threshold: 4 }
const signals = [].concat(JSON.parse(req.body));
for (const signal of signals) {
console.warn('vm-defense', { ...signal, ip: req.ip, ua: req.get('user-agent') });
}
res.sendStatus(204);
});
Rinominare i campi del segnale (alias) v7.4.0+
I valori predefiniti di source e category sono nomi descrittivi, per cui chiunque strumenti la callback (o legga l'output) è in grado di riconoscere la protezione e il rilevatore che è scattato. aliases rinomina i campi del segnale con token opachi a propria scelta, applicati all'interno della VM prima dell'emissione del segnale, così che quei nomi non compaiano mai nell'output né raggiungano la callback. È l'applicazione a conoscere la propria mappatura e a inoltrare i token al backend.
Gli alias si definiscono per singolo campo: ciascuno accetta una key (il nome della proprietà che la callback riceve); i campi nominali di tipo stringa source e category accettano inoltre una mappa values, mentre score e threshold sono numerici e accettano soltanto una key. Le voci non impostate mantengono i nomi predefiniti.
vmDefenseHook: {
name: '__vmDetection',
aliases: {
source: { key: 'a8Qm', values: { headless: 'xP4m9Q' } },
category: { key: 'p3Tx', values: { automation: 'bQ7s1M' } },
score: { key: 's1' },
threshold: { key: 't1' }
}
// the callback now receives e.g. { a8Qm: 'xP4m9Q', p3Tx: 'bQ7s1M', s1: <score>, t1: <threshold> }
}
Nella dashboard la sezione Signal aliases si trova sotto il campo VM Defense Hook.
Ricetta 2 - modificare le reazioni predefinite
vmDefenseReaction configura il modo in cui reagisce ciascuna categoria di rilevamento. Non attiva nulla: le difese vengono abilitate da vmSelfDefending, vmDebugProtection e vmDomainLock, mentre questa opzione si limita a selezionare la reazione di una difesa già attiva. L'unità di controllo è la categoria: ogni rilevatore appartenente a una categoria mette in atto la reazione prevista per quella categoria, mentre una reazione impostata per una categoria la cui opzione è disattivata semplicemente non ha effetto.
| Categoria | Attivata da | Reagisce quando |
|---|---|---|
automation | vmSelfDefending oppure vmDebugProtection | Il codice è pilotato da un software anziché da una persona: un browser headless o automatizzato, un framework di scraping o di test, oppure un agente di programmazione basato su IA che percorre la pagina. |
debugger | vmDebugProtection oppure vmSelfDefending | È aperto un debugger o l'inspector degli strumenti di sviluppo del browser e qualcuno sta percorrendo passo passo il codice in esecuzione per comprenderlo. |
sandbox | vmDebugProtection | Il codice non viene eseguito in un browser reale: è stato trasferito in un ambiente JavaScript emulato o pilotato da script per essere eseguito e studiato offline. |
domain | vmDomainLock | Il codice viene eseguito su un sito non autorizzato: un host non incluso nell'elenco consentito di vmDomainLock (ad esempio il proprio bundle copiato sul dominio di terzi). |
tamper | vmSelfDefending | L'ambiente JavaScript attorno alla VM è stato modificato per osservarla o dirottarla, ad esempio sostituendo le funzioni native del browser con versioni strumentate. |
integrity | vmSelfDefending | Il codice del bundle protetto è stato modificato o corretto dopo la sua generazione. |
Le chiavi sono i sei nomi di categoria appena elencati, oppure default (un valore di ripiego per le categorie non specificate). I valori disponibili sono:
break- blocco immediatodecoy- prosecuzione dell'esecuzione su uno stato avvelenato, producendo silenziosamente risultati erratinone- nessuna azione locale (sola telemetria)
Una categoria non impostata ricade sui valori predefiniti integrati:
// built-in defaults
vmDefenseReaction: {
automation: 'break',
debugger: 'decoy',
sandbox: 'decoy',
domain: 'break',
tamper: 'break',
integrity: 'break'
}
default interessa tutte le categorie, comprese quelle corrette per costruzione (integrity, tamper), per cui { default: 'none' } produce una build realmente non invasiva, dedicata alla sola telemetria:
vmDefenseReaction: { default: 'none' } // never break - pair with vmDefenseHook
vmDefenseReaction: { automation: 'none' } // tolerate automation FPs; the rest keep their defaults (a bad domain still breaks)
Nella dashboard i selettori VM Defense Reactions compaiono nel pannello delle opzioni VM non appena una difesa è attiva; ciascuna categoria è modificabile soltanto finché è attiva una difesa che ne emette i rilevatori.
Dalla telemetria all'applicazione delle difese
Non è necessario scegliere fin dal primo giorno fra visibilità e applicazione delle difese. Conviene distribuire le difese in due build successive: una che si limita a segnalare e poi, quando la telemetria risulta pulita, una che reagisce.
Passaggio 1 - distribuire una build di sola osservazione
Attivare tutte le difese che si intende utilizzare, fare puntare vmDefenseHook al proprio endpoint e disattivare tutte le reazioni. Ogni rilevatore continua a funzionare e a segnalare al backend ciascuna occorrenza, senza però mai interrompere nulla:
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
vmSelfDefending: true,
vmDebugProtection: true,
vmDomainLock: ['example.com'],
vmDefenseHook: '__vmDetection',
vmDefenseReaction: { default: 'none' } // observe only
});
Passaggio 2 - esaminare i segnali raccolti
Dopo che la build ha ricevuto traffico reale, occorre individuare i rilevamenti provocati da un utilizzo legittimo. I due casi più frequenti:
- Occorrenze di
automationdovute ai propri test end-to-end o al monitoraggio della disponibilità: conviene generare quegli artefatti senza le difese, anziché tollerare la categoria in produzione. - Occorrenze di
domaindovute a un host di staging o di anteprima dimenticato nell'elenco consentito divmDomainLock: è sufficiente aggiungere l'host.
È preferibile rimuovere la causa anziché attenuare una reazione: ogni categoria lasciata su none è un rilevatore di cui un attaccante non deve più preoccuparsi.
Passaggio 3 - attivare le reazioni
Rimuovere l'override default: 'none' affinché si applichino le reazioni predefinite per categoria: l'intero passaggio si riduce a quella singola riga. Se una categoria continua a produrre falsi positivi non eliminabili, è possibile mantenere su none soltanto quella categoria (ad esempio vmDefenseReaction: { automation: 'none' }) e applicare le difese per tutte le altre.
