Utiliser Proxy d’ES2015 pour s’amuser et réussir
Alon Niv
23 août 2016
0 minutes de lectureBeaucoup a été écrit sur ES2015 : ses fonctions fléchées, ses déclarations de variables à portée limitée et ses classes controversées. Pourtant, une fonctionnalité n’a pas encore reçu beaucoup d’attention : Proxy.En tant que développeurs JS, nous n’avons pas l’habitude de nous appuyer sur des mécanismes d’interception dans toute notre base de code, mais ils ont plusieurs applications très utiles. En voici quelques exemples :
Tests, simulation et monkey patching
Les modèles de conception
ObserveretVisitorDes abstractions pour des concepts complexes
Jusqu’à présent, le langage ne nous offrait aucun mécanisme de ce type.J’ai l’impression que Proxy résout ce problème tout en préservant les habitudes de JS que nous connaissons et apprécions (c’est-à-dire sans méthodes [Symbol.__setattr__] pour nos objets).Dans cet article, je vais présenter un exemple des pièges les plus courants (get, set, has et deleteProperty), et, dans l’esprit de Snyk, il sera question de dépendances.
Notre objectif
Nous voulons créer un package qui permet à un développeur de require des modules en toute sécurité, en limitant leur accès à des modules spécifiques. Après tout, nous ne voudrions pas confier notre fs à un module d’utilitaires fonctionnels, n’est-ce pas ?
Posons le décor
Le constructeur Proxy accepte 2 paramètres :
target: l’objet que nous voulons placer derrière un proxyhandler: un objet contenant la spécification des pièges que nous voulons gérer (un piège, dans ce contexte, est une fonction appelée lors de certains événements affectant le proxy, par exemple l’accès à une propriété, sa modification ou sa suppression) — la suite en donne des exemples.
Fonctionnement de require
J’inclus cette partie uniquement parce que, même si la documentation de node explique très bien le sujet, nous allons manipuler le cache des modules de façon astucieuse. Je veux donc m’assurer que le fonctionnement de require est clair :
Le module
xrequirele moduleyLe nom
yest résolu en chemin absolu (ou pas, s’il s’agit d’un module natif commefs)Le nom résolu est récupéré dans le cache, sans vérifier son existence
Si la valeur récupérée est définie (un objet Module), les valeurs exportées sont renvoyées
Sinon, node récupère le fichier depuis le système de fichiers et le compile en un objet Module, qui est placé dans le cache et dont les valeurs exportées sont renvoyées.
Ce cache est un singleton global, accessible via require('module')._cache et require.cache, et c’est un simple objet JS.
Enfin, passons au code
Pour simplifier, supposons que nous voulons simplement fournir une interface de liste de blocage pour les modules que le module requis ne doit pas utiliser. Voici à quoi elle va ressembler :
Bien, nous avons maintenant une idée de ce à quoi cela va ressembler. Commençons à coder :
Super, c’est terminé, n’est-ce pas ?Pas tout à fait. Certes, nous avons veillé à ce que notre module malveillant ne puisse pas require un module inscrit sur la liste de blocage, mais il peut encore recourir à d’autres sales astuces :
Remarque sur require.cache : pour corrompre un seul module dans le cache des modules, utilisez require.cache.En revanche, pour remplacer tout le mécanisme de mise en cache, utilisez require('module')._cache = ....Modifions notre code pour gérer cette situation :
Et… maintenant, c’est terminé… non ? Eh bien non. Examinons ce code malveillant :
Ajoutons quelques dernières retouches :
Et maintenant, c’est terminé. Enfin, je crois.
Avez-vous une idée pour contourner ce proxy ? Partagez-la avec nous sur Twitter @snyksec !
Le dépôt de cette preuve de concept est disponible ici.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.


