Using ES2015 Proxy for fun and profit
Alon Niv
23 de agosto de 2016
0 minutos de leituraMuch has been written about ES2015 - with its arrow functions, scoped variable declarations and controversial classes. However, a certain feature has received little love so far: the Proxy.As JS developers, we’re not used to relying on trapping mechanisms throughout our codebase, but they have several very useful applications. To name a few:
Testing, mocking and monkeypatching
The
ObserverandVisitordesign patternsAbstractions over complicated concepts
Until now, the language hasn’t provided us with any such mechanism.I feel like Proxy solves this problem, while keeping the feel of the JS we know and love (i.e. no [Symbol.__setattr__] methods for our objects).In this post, I’ll give an example for the most common traps (get, set, has and deleteProperty), and in keeping with the Snyk spirit, it’s going to be about dependencies.
Our goal
We want to create a package that allows a developer to safely require modules, limiting their access to specific modules. After all, we wouldn’t want to trust a functional utilities module with our fs, right?
Setting the stage
The Proxy constructor accepts 2 parameters:
target: this is the Object we want to proxy aroundhandler: this is an Object containing the spec for the traps we want to handle (a trap, in this sense, is a function that is being called on certain events happening to the proxy, such as a property being accessed, set or deleted) - examples follow.
How require works
I’m only including this part because, while node’s docs do a pretty great job explaining this, we’re about to do some nifty things to the modules cache, so I want to make sure the require flow is clear:
Module
xrequires moduleyName
yis resolved to an absolute path (or not, if it’s a core module, such asfs)The resolved name is fetched from the cache, without checking for its existence
If the fetched value is defined (a Module object), the exported values are returned
Otherwise, node actually fetches the file from the FS, and compiles it into a Module object, which is put into the cache, and has its exported values returned.
This cache is a global singleton, accessible via require('module')._cache and require.cache, and is a plain JS Object.
Finally, some code
For simplicity’s sake, let’s assume we just want to provide a blacklisting interface for modules that shouldn’t be used by our required module. It’s going to look like this:
Okay, so now we have a feel for what it’s going to look like, let’s start coding:
Cool, we’re done, right?Well, not exactly. Yes, we made sure our nefarious module can’t require a blacklisted module, but there are other dirty tricks it can pull off:
Side-note about require.cache: if you want to corrupt a single module in the module cache, use require.cache.If, however, you want to switch out the entire caching mechanism, use require('module')._cache = ....Let’s fix our code to handle this situation:
And… now we’re done… right? Nope. Let’s consider this nefarious code:
Let’s just add some last touches, then:
And now we’re done. Or, at least, I think so.
Do you have an idea how to circumvent this proxy? Share them with us on Twitter @snyksec!
The repo for this POC is available here.
Primeiros passos com Capture the Flag
Saiba como resolver desafios de Capture the Flag assistindo ao nosso workshop virtual de conceitos básicos sob demanda.