Sostituzione di template lato host
Iniettare valori generati dal server (ad esempio espressioni Go html/template `{{ .Field }}`) nel JavaScript offuscato con VM senza compromettere la pipeline di offuscamento.
Il problema
Il proprio backend (Go, Rails, Django, PHP, …) distribuisce un file JavaScript il cui contenuto deve essere in parte
generato a ogni richiesta: un endpoint API, un elenco di feature flag, un blob di stato iniziale, un id di build, un nonce.
Il JS viene offuscato con vmObfuscation: true, ma occorre anche che il motore di template sostituisca i segnaposto
nell'output offuscato dopo il termine dell'offuscamento. Se i segnaposto vengono assorbiti nel bytecode VM (come avviene
per impostazione predefinita per stringhe e identificatori), il motore di template non trova nulla da sostituire.
Quale opzione scegliere?
| Il valore fornito dal server è… | Utilizzare |
|---|---|
| Un'espressione JS grezza (letterale array, oggetto, numero, booleano, chiamata di funzione) | reservedNames + segnaposto sotto forma di identificatore |
| Una stringa, con pieno controllo sui delimitatori del template | reservedStrings + segnaposto sotto forma di template literal |
Una stringa, con delimitatori imposti dal motore di template (ad esempio Go {{ .Field }}) | reservedStrings + segnaposto sotto forma di stringa o template literal |
Pattern 1 - reservedNames con un segnaposto sotto forma di identificatore
Indicato per: iniettare espressioni JS grezze (array, oggetti, numeri, …) senza alcun problema di escaping delle virgolette.
Occorre scegliere un identificatore distintivo che non possa mai entrare in conflitto con il codice reale, farvi
riferimento direttamente ed elencare in reservedNames una regex che lo intercetti. Sotto offuscamento VM l'identificatore
viene instradato attraverso l'array delle espressioni riservate e compare testualmente nell'output.
Sorgente
(function () {
var initialState = __INITIAL_STATE__;
bootstrap(initialState);
})();
Opzioni dell'offuscatore
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedNames: ['^__INITIAL_STATE__$']
});
Dopo l'offuscamento
In un punto qualsiasi dell'output si troverà un array di espressioni riservate simile al seguente:
let _r = [__INITIAL_STATE__, /* …other entries… */];
Il token __INITIAL_STATE__ sopravvive alla rinomina degli identificatori e non viene assorbito nel bytecode VM.
Sostituzione lato host (esempio in Go)
tpl := template.Must(template.New("obf.js").Parse(obfuscated))
tpl.Execute(w, map[string]any{
"InitialState": map[string]any{"user": "alice", "theme": "dark"},
})
…dove il template Go sostituisce il token con un'espressione JS grezza:
{{ `__INITIAL_STATE__` }} → {{ .InitialState | toJSON }}
Risultato:
let _r = [{"user":"alice","theme":"dark"}, /* … */];
Nessuna virgoletta, nessun escaping: il valore iniettato viene interpretato dal motore JS come una normale espressione.
Pattern 2 - reservedStrings con un segnaposto sotto forma di template literal
Indicato per: motori di template in stile Go o Jinja che richiedono delimitatori come {{ .Field }}, i quali risultano
testo JS valido se racchiusi fra backtick.
Il segnaposto risiede all'interno di un template literal a quasi singolo e privo di interpolazioni. Sotto offuscamento VM viene instradato attraverso l'array delle espressioni riservate, preservando nell'output la forma grezza con i backtick.
Sorgente
(function () {
var featureFlags = JSON.parse(`{{.Config.FeatureFlags}}`);
applyFlags(featureFlags);
})();
Opzioni dell'offuscatore
UI
Per riservare il segnaposto {{.Config.FeatureFlags}} del sorgente precedente, aggiungere questa regex al campo
Reserved Strings, utilizzando barre rovesciate singole. L'interfaccia memorizza il valore testualmente, per cui, a
differenza di quanto avviene nel codice JS, le barre rovesciate non vanno raddoppiate:
\{\{[^}]+\}\}

API
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedStrings: ['\\{\\{[^}]+\\}\\}']
});
Dopo l'offuscamento
let _r = [`{{.Config.FeatureFlags}}`, /* … */];
Il segnaposto viene preservato byte per byte, backtick circostanti compresi.
Sostituzione lato host
Sostituire {{.Config.FeatureFlags}} con una stringa JSON valida: a runtime si troverà racchiusa fra backtick, che non
richiedono l'escaping delle virgolette interne:
flagsJSON, _ := json.Marshal(config.FeatureFlags) // e.g. {"newCheckout":true,"darkMode":false}
out := strings.ReplaceAll(obfuscated, "{{.Config.FeatureFlags}}", string(flagsJSON))
A runtime: JSON.parse(`{"newCheckout":true,"darkMode":false}`) — funziona.
Pattern 3 - reservedStrings con un segnaposto sotto forma di stringa fra virgolette
Indicato per: codice sorgente che deve rimanere in ES5 (senza template literal) oppure per i casi in cui l'API circostante si aspetta un normale letterale stringa.
Il segnaposto è un letterale stringa racchiuso fra virgolette singole o doppie. Sotto offuscamento VM viene instradato
attraverso l'array delle stringhe riservate, che viene emesso come array JS serializzato con JSON.stringify e quindi
sempre racchiuso fra virgolette doppie, a prescindere dallo stile di virgolette utilizzato in input.
Sorgente
(function () {
var tags = JSON.parse("{{.Page.Tags}}");
renderTags(tags);
})();
Opzioni dell'offuscatore
UI
Per riservare il segnaposto {{.Page.Tags}} del sorgente precedente, aggiungere questa regex al campo Reserved
Strings, utilizzando barre rovesciate singole. L'interfaccia memorizza il valore testualmente, per cui, a differenza di
quanto avviene nel codice JS, le barre rovesciate non vanno raddoppiate:
\{\{[^}]+\}\}

API
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedStrings: ['\\{\\{[^}]+\\}\\}']
});
Dopo l'offuscamento
var _rs = ["{{.Page.Tags}}", /* … */];
Sostituzione lato host
Poiché il segnaposto si trova all'interno di una stringa JS racchiusa fra virgolette doppie, il payload JSON iniettato deve
avere le proprie " interne sottoposte a escaping con \":
raw, _ := json.Marshal(page.Tags) // e.g. ["news","tech","release"]
// Escape " for embedding inside a JS double-quoted string.
escaped := strings.ReplaceAll(string(raw), `"`, `\"`)
out := strings.ReplaceAll(obfuscated, "{{.Page.Tags}}", escaped)
Output risultante:
var _rs = ["[\"news\",\"tech\",\"release\"]", /* … */];
A runtime: JSON.parse("[\"news\",\"tech\",\"release\"]") → ["news","tech","release"].
Se si dimentica l'escaping, il browser rileverà virgolette non bilanciate e genererà un SyntaxError. Il Pattern 2 evita
completamente il problema ricorrendo ai backtick.
Più segnaposto in uno stesso programma
Tutti e tre i pattern sono componibili. Una sola regex in reservedStrings con un'alternanza può intercettare ogni forma di
segnaposto prodotta dal proprio motore di template:
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedNames: ['^__INITIAL_STATE__$'],
reservedStrings: ['\\{\\{[^}]+\\}\\}']
});
È possibile combinare nello stesso sorgente segnaposto sotto forma di identificatore (per i valori JS grezzi) e segnaposto sotto forma di stringa (per il JSON generato): la scelta va effettuata segnaposto per segnaposto, in base a ciò che il server inietterà effettivamente.
Note di compatibilità
- Non riutilizzare gli stessi segnaposto per identificatori e stringhe. Un nome riservato come
__TOKEN__e una stringa riservata che intercetta__TOKEN__descrivono due percorsi di codice diversi (array delle espressioni riservate rispetto ad array delle stringhe riservate). Conviene utilizzare forme testuali distinte per ciascun caso, ad esempio una convenzioneUPPER_SNAKEper i segnaposto sotto forma di identificatore e una forma racchiusa fra delimitatori ({{ ... }},%{...},<<<...>>>) per quelli sotto forma di stringa. In questo modo un errore in una delle due regex non può intercettare silenziosamente l'altra. - Le regex di
reservedStringsoperano sui valori grezzi delle stringhe. La regex viene confrontata con il valore a runtime della stringa, non con il testo del sorgente.\{\{[^}]+\}\}intercetta le stringhe che contengono{{.something}}(o qualsiasi altra forma{{...}}). Se il segnaposto può risultare racchiuso in contenuto aggiuntivo ("prefix-{{.Field}}-suffix"), la regex continua a intercettarlo, ma viene preservata l'intera stringa: occorre pianificare la sostituzione di conseguenza. - Funziona con qualsiasi motore di template. Sebbene gli esempi utilizzino la sintassi Go
text/template, nulla nell'integrazione con javascript-obfuscator è specifico di Go. Funziona qualsiasi strumento in grado di effettuare una sostituzione testuale sull'output dell'offuscatore: ERB di Rails, Django, gli short tag di PHP, sed in una pipeline di CI e così via. Conviene scegliere delimitatori prodotti naturalmente dal proprio motore e che non entrino in conflitto con la sintassi JS reale.
