Documentazione
/
Ricette
/

Sostituzione di template lato host

Sostituzione di template lato host

v6.10.0+

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 templatereservedStrings + 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:

\{\{[^}]+\}\}

Campo Reserved Strings nell'interfaccia dell'offuscatore con la regex inserita utilizzando barre rovesciate singole

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:

\{\{[^}]+\}\}

Campo Reserved Strings nell'interfaccia dell'offuscatore con la regex inserita utilizzando barre rovesciate singole

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 convenzione UPPER_SNAKE per 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 reservedStrings operano 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.