Documentation
/
Recettes
/

Substitution de gabarits côté serveur

Substitution de gabarits côté serveur

Pro
v6.10.0+

Injectez des valeurs rendues côté serveur (par exemple des marqueurs `{{ .Field }}` dans le style des gabarits Go) dans du JavaScript obfusqué par VM sans casser la chaîne d'obfuscation.

Le problème

Votre backend (Go, Rails, Django, PHP, …) sert un fichier JavaScript dont le contenu doit être partiellement rendu à chaque requête - un point de terminaison d'API, une liste de feature flags, un bloc d'état initial, un identifiant de build, un nonce. Vous obfusquez le JS avec vmObfuscation: true, mais vous avez aussi besoin que le moteur de gabarits remplace des marqueurs dans la sortie obfusquée après la fin de l'obfuscation. reservedNames et reservedStrings gardent un marqueur visible dans la sortie afin qu'il puisse être remplacé. Sans elles, un marqueur de chaîne est absorbé dans le bytecode de la VM et un marqueur d'identifiant est émis à plusieurs endroits réécrits : le moteur de gabarits ne trouve alors rien à remplacer, ou casse la sortie.

Envisagez plutôt de charger les valeurs comme des données

Si vous le pouvez, évitez complètement de modifier la sortie protégée : chargez la configuration d'exécution comme des données avant que le bundle protégé ne s'exécute. Placez-la dans un script séparé ou récupérez-la depuis un point de terminaison authentifié. La sortie protégée reste ainsi intacte, et vmSelfDefending peut rester activé. Dans tous les cas, les données livrées à un navigateur sont visibles par ce navigateur.

JavaScript

Utilisez les modèles ci-dessous lorsque les valeurs doivent réellement être substituées dans le fichier obfusqué.

Une remarque sur les exemples Go : ils effectuent un simple remplacement de chaîne (strings.ReplaceAll / strings.NewReplacer) sur la sortie obfusquée et gèrent eux-mêmes l'échappement, montré pour chaque modèle. Ils ne passent pas par le paquet html/template de Go - celui-ci appliquerait par-dessus son propre échappement JavaScript contextuel, ce qui échapperait deux fois la charge utile. Si vous effectuez le rendu via html/template, supprimez l'échappement manuel et laissez le moteur échapper une seule fois.

Quelle option choisir ?

La valeur du serveur est…Utilisez
Une valeur JS brute (tableau, objet, nombre, booléen - toute valeur JSON)Modèle 1 - reservedNames + marqueur de type identifiant
Une chaîne (le cas courant - JSON rendu, identifiant de build, nonce)Modèle 2 - reservedStrings + marqueur dans un littéral de gabarit
Une chaîne, lorsque le code doit rester en ES5 ou que l'API environnante exige un littéral de chaîne entre guillemetsModèle 3 - reservedStrings + marqueur dans une chaîne entre guillemets

La substitution après obfuscation est incompatible avec vmSelfDefending: true. Voir Notes de compatibilité à la fin de cette recette.

Modèle 1 - reservedNames avec un marqueur de type identifiant

Idéal pour : injecter des expressions JS brutes (tableaux, objets, nombres, …) sans aucun souci d'échappement des guillemets.

Choisissez un identifiant distinctif qui n'entrera jamais en collision avec du vrai code, référencez-le directement et ajoutez une regex qui lui correspond dans reservedNames. Avec l'obfuscation VM, l'identifiant passe par le tableau des expressions réservées et apparaît tel quel dans la sortie, par exemple :

JavaScript

Remplacez l'identifiant par une expression complète sérialisée en JSON. Ne collez pas la valeur entre guillemets ou entre backticks - l'identifiant ne se trouve pas dans un littéral de chaîne, donc la valeur injectée est analysée par le moteur JS comme une expression ordinaire. Utilisez un sérialiseur de confiance, échappez < lorsque le script est intégré dans du HTML, et testez des valeurs contenant des guillemets, des barres obliques inverses, des sauts de ligne, des backticks et ${...}.

JavaScript

Modèle 2 - reservedStrings avec un marqueur dans un littéral de gabarit

Idéal pour : les moteurs de gabarits de type Go / Jinja qui imposent des délimiteurs comme {{ .Field }}, lesquels se trouvent être du texte JS valide une fois entourés de backticks.

Le marqueur se trouve dans un littéral de gabarit à quasi unique, sans interpolation. Avec l'obfuscation VM, il passe par le tableau des expressions réservées, ce qui préserve la forme brute entre backticks dans la sortie.

Source

JavaScript

Options de l'obfuscateur

Interface

Pour réserver le marqueur {{.Config.FeatureFlags}} du source ci-dessus, ajoutez cette regex au champ Reserved Strings

  • avec des barres obliques inverses simples. L'interface stocke la valeur telle quelle : contrairement au code JS, les barres obliques inverses ne sont pas doublées :

Texte

Champ Reserved Strings dans l'interface de l'obfuscateur, avec la regex saisie à l'aide de barres obliques inverses simples

API

JavaScript

Après l'obfuscation

JavaScript

Le marqueur est conservé octet pour octet, backticks environnants compris.

Substitution côté serveur

Remplacez {{.Config.FeatureFlags}} par une chaîne JSON échappée pour un littéral de gabarit. Les backticks n'exigent pas d'échapper les " intérieurs, mais une barre oblique inverse, un backtick ou ${ dans les données modifierait ou terminerait tout de même le littéral : échappez donc ces trois éléments :

Code

À l'exécution : JSON.parse(`{"newCheckout":true,"darkMode":false}`) - cela fonctionne.

Pourquoi préférer les littéraux de gabarit aux guillemets simples ou doubles pour les valeurs de type chaîne ? Les backticks n'exigent pas d'échapper les " contenus dans les données JSON. C'est important, car la plupart des valeurs rendues côté serveur sont du JSON, et le JSON regorge de guillemets doubles. Avec un marqueur entre guillemets doubles, vous devriez échapper chaque " intérieur (voir le modèle 3) ; avec des backticks, seules les rares barres obliques inverses, les backticks et les ${ des données doivent être échappés.

Modèle 3 - reservedStrings avec un marqueur dans une chaîne entre guillemets

Idéal pour : le code source qui doit rester en ES5 (sans littéraux de gabarit), ou les cas où l'API environnante attend un littéral de chaîne classique.

Le marqueur est un littéral de chaîne entre guillemets simples ou doubles. Avec l'obfuscation VM, il passe par le tableau des chaînes réservées, émis sous la forme d'un tableau JS sérialisé par JSON.stringify - toujours entre guillemets doubles, quel que soit le style de guillemets en entrée.

Source

JavaScript

Options de l'obfuscateur

Interface

Pour réserver le marqueur {{.Page.Tags}} du source ci-dessus, ajoutez cette regex au champ Reserved Strings - avec des barres obliques inverses simples. L'interface stocke la valeur telle quelle : contrairement au code JS, les barres obliques inverses ne sont pas doublées :

Texte

Champ Reserved Strings dans l'interface de l'obfuscateur, avec la regex saisie à l'aide de barres obliques inverses simples

API

JavaScript

Après l'obfuscation

JavaScript

Substitution côté serveur

Comme le marqueur se trouve dans une chaîne JS entre guillemets doubles, les barres obliques inverses et les caractères " intérieurs des données JSON injectées doivent être échappés :

Code

Sortie obtenue :

JavaScript

À l'exécution : JSON.parse("[\"news\",\"tech\",\"release\"]") → ["news","tech","release"].

Si vous oubliez l'échappement, le navigateur verra des guillemets non appariés et lèvera une SyntaxError. Le modèle 2 évite complètement les guillemets doubles grâce aux backticks.

Plusieurs marqueurs dans un même programme

Les trois modèles se combinent. Une seule regex reservedStrings avec une alternative peut correspondre à toutes les formes de marqueurs que votre moteur de gabarits émet - ici, les styles {{ .Field }} et %{ .Field } :

JavaScript

Vous pouvez mélanger des marqueurs de type identifiant (pour les valeurs JS brutes) et des marqueurs dans des chaînes (pour le JSON rendu) dans le même source - choisissez marqueur par marqueur en fonction de ce que le serveur injectera réellement.

Notes de compatibilité

vmSelfDefending casse la substitution après obfuscation

VM Self Defending détecte toute modification de la sortie obfusquée après sa génération, y compris une substitution de gabarit légitime, et le code protégé refuse alors de s'exécuter.

Si vous dépendez de la substitution de gabarits côté serveur, définissez vmSelfDefending: false. Laissez aussi selfDefending désactivé : il est sans effet avec l'obfuscation VM, et sans VM il interdit lui aussi toute modification de la sortie.

Conseils sur les marqueurs

  • Ne réutilisez pas les mêmes marqueurs pour les identifiants et pour les chaînes. Un nom réservé comme __TOKEN__ et une chaîne réservée correspondant à __TOKEN__ décrivent deux chemins de code différents (tableau des expressions réservées contre tableau des chaînes réservées). Utilisez des formes textuelles distinctes pour chacun - par exemple, une convention UPPER_SNAKE pour les marqueurs de type identifiant et une forme entourée de délimiteurs ({{ ... }}, %{...}, <<<...>>>) pour les marqueurs dans des chaînes. Ainsi, une erreur dans une regex ne peut pas correspondre silencieusement à l'autre forme.
  • Les regex de reservedStrings s'appliquent aux valeurs brutes des chaînes. La regex est comparée à la valeur de la chaîne à l'exécution, et non au texte source. \{\{[^}]+\}\} correspond aux chaînes qui contiennent {{.something}} (ou toute autre forme {{...}}). Si votre marqueur peut être entouré d'un autre contenu ("prefix-{{.Field}}-suffix"), la regex correspond toujours, mais c'est la chaîne entière qui est conservée - prévoyez votre substitution en conséquence.
  • Fonctionne avec n'importe quel moteur de gabarits. Bien que les exemples utilisent la syntaxe Go, rien dans l'intégration de javascript-obfuscator n'est propre à Go. Tout outil capable d'effectuer un remplacement de texte sur la sortie de l'obfuscateur fonctionnera : Rails ERB, Django, les short tags PHP, sed dans une chaîne de CI, etc. Choisissez des délimiteurs que votre moteur émet naturellement et qui n'entrent pas en collision avec la vraie syntaxe JS.