├── README.md
├── burp_pollute
├── extension
│ └── BurpExtender.java
└── server
│ ├── package.json
│ ├── pollute.js
│ └── pollute.php
├── match_rules
└── match-rules.tab
└── prototype_checker
└── prototype_checker.js
/README.md:
--------------------------------------------------------------------------------
1 | # Client-Side Prototype Pollution Tools
2 |
3 | ## Match rules for Burp Software Version Reporter extension
4 |
5 | Match rules that passively detect vulnerable libraries even in minified JS code.
6 |
7 | **Rules:** [match_rules/match-rules.tab](/match_rules/match-rules.tab)
8 | **Extension:** [Software Version Reporter](https://portswigger.net/bappstore/ae62baff8fa24150991bad5eaf6d4d38)
9 |
10 |
11 |
12 | ## Prototype Checker
13 |
14 | JS script that highlights custom fields in prototypes and constructors that can be useful in exploiting Prototype Pollution.
15 |
16 | **Script:** [prototype_checker/prototype_checker.js](/prototype_checker/prototype_checker.js)
17 | **Script Gadget Example:** [script.aculo.us XSS Script Gadget](https://github.com/BlackFan/client-side-prototype-pollution/blob/master/gadgets/scriptaculous.md)
18 |
19 | 
20 |
21 | ## Burp pollute.js
22 |
23 | [pollute.js](https://github.com/securitum/research/tree/master/r2020_prototype-pollution) is a script that highlights access to uninitialized properties using code instrumentation.
24 | By adding a small script to it, you can replace all Burp Proxy HTTP responses with modified code.
25 |
26 | ### Install
27 |
28 | * Install [pollute.js](burp_pollute/server/) dependencies
29 | * Setup webserver to run pollute.js using [pollute.php](burp_pollute/server/pollute.php) (or write your own wrapper)
30 | * Customize your link in [POLLUTE_JS](burp_pollute/extension/BurpExtender.java#L10)
31 | * Build Burp Suite extension
32 |
33 | Now you can setup logging conditions in pollute.js [PREAMBLE](burp_pollute/server/pollute.js#L13-L31). For example, to search for **DOM Clobbering** gadgets,
34 | replace
35 |
36 | `obj instanceof Object`
37 |
38 | with
39 |
40 | `(obj instanceof Window || obj instanceof Document)`
41 |
42 | If you want to log access to properties only after **Prototype Pollution** has already triggered, add the condition
43 |
44 | `typeof Object.prototype[1337] != 'undefined'`
45 |
46 | and call the page with
47 |
48 | `?__proto__[1337]=xxx`
49 |
50 | 
--------------------------------------------------------------------------------
/burp_pollute/extension/BurpExtender.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.net.MalformedURLException;
4 | import java.net.URL;
5 |
6 | public final class BurpExtender implements IBurpExtender, IProxyListener {
7 |
8 | private IExtensionHelpers helpers;
9 | private IBurpExtenderCallbacks callbacks;
10 | public static final String POLLUTE_JS = "https://attacker.tld/pollute.php?url=";
11 |
12 | @Override
13 | public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) {
14 | this.callbacks = callbacks;
15 | this.helpers = callbacks.getHelpers();
16 | callbacks.setExtensionName("Burp Pollute");
17 | callbacks.registerProxyListener(this);
18 | }
19 |
20 | @Override
21 | public void processProxyMessage(final boolean messageIsRequest, final IInterceptedProxyMessage message) {
22 | if (!messageIsRequest) {
23 | polluteJS(message.getMessageInfo());
24 | }
25 | }
26 |
27 | private void polluteJS(final IHttpRequestResponse httpRequestResponse) {
28 | byte[] originalResponse = httpRequestResponse.getResponse();
29 | IResponseInfo responseInfo = this.helpers.analyzeResponse(originalResponse);
30 |
31 | String inferredMimeType = responseInfo.getInferredMimeType();
32 | if (inferredMimeType.isEmpty()) {
33 | inferredMimeType = responseInfo.getStatedMimeType();
34 | }
35 | inferredMimeType = inferredMimeType.toLowerCase();
36 |
37 | if (inferredMimeType.contains("script")) {
38 | try {
39 | URL scriptURL = this.helpers.analyzeRequest(httpRequestResponse).getUrl();
40 | this.callbacks.printOutput("Pollute Response: " + scriptURL);
41 | URL polluteURL = new URL(POLLUTE_JS);
42 | IHttpService polluteHttpService = this.helpers.buildHttpService(polluteURL.getHost(), polluteURL.getPort(), polluteURL.getProtocol());
43 | byte[] polluteRequest = this.helpers.buildHttpRequest(new URL(POLLUTE_JS + scriptURL));
44 | IHttpRequestResponse modifiedRequestResponse = this.callbacks.makeHttpRequest(polluteHttpService, polluteRequest);
45 | httpRequestResponse.setResponse(modifiedRequestResponse.getResponse());
46 | } catch (MalformedURLException ex) {
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/burp_pollute/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pollute.js",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "pollute.js",
6 | "dependencies": {
7 | "acorn": "^7.4.0",
8 | "ast-types": "^0.13.3",
9 | "escodegen": "^2.0.0",
10 | "glob": "^7.1.6",
11 | "jspath": "^0.4.0",
12 | "yargs": "^15.4.1"
13 | },
14 | "devDependencies": {},
15 | "scripts": {
16 | "test": "echo \"Error: no test specified\" && exit 1"
17 | },
18 | "author": "",
19 | "license": "ISC"
20 | }
21 |
--------------------------------------------------------------------------------
/burp_pollute/server/pollute.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const acorn_1 = require("acorn");
4 | const escodegen = require("escodegen");
5 | const ast_types_1 = require("ast-types");
6 | const fs_1 = require("fs");
7 | const yargs = require("yargs");
8 | const glob = require("glob");
9 | const readline = require("readline");
10 | const GET_PROP = '$_GET_PROP';
11 | const IN_OPERATOR = '$_IN';
12 | const PREAMBLE = `
13 | window.$_SHOULD_LOG = true;
14 | window.$_IGNORED_PROPS = new Set([]);
15 | function $_GET_PROP(obj, prop) {
16 | if (window.$_SHOULD_LOG && !window.$_IGNORED_PROPS.has(prop) && obj instanceof Object && typeof obj === 'object' && !(prop in obj)) {
17 | console.group(\`obj[\${JSON.stringify(prop)}]\`);
18 | console.trace();
19 | console.groupEnd();
20 | }
21 | return obj[prop];
22 | }
23 | function $_IN(obj, prop) {
24 | const b = prop in obj;
25 | if (window.$_SHOULD_LOG && obj instanceof Object && !b) {
26 | console.group(\`\${JSON.stringify(prop)} in obj\`);
27 | console.trace();
28 | console.groupEnd();
29 | }
30 | return b;
31 | }
32 | `;
33 | function isLeftInAssignment(path) {
34 | return (path.parentPath.value.type === 'AssignmentExpression'
35 | && path.name === 'left');
36 | }
37 | function parentIsCallExpression(path) {
38 | return (path.parentPath.value.type === 'CallExpression');
39 | }
40 | function isWithinDeleteExpression(path) {
41 | return (path.parentPath.value.type === 'UnaryExpression'
42 | && path.parentPath.value.operator === 'delete');
43 | }
44 | function isWithinUpdateExpression(path) {
45 | return (path.parentPath.value.type === 'UpdateExpression');
46 | }
47 | const IGNORED_MEMBER_EXPRESSIONS = [
48 | {
49 | object: /^self|top|window|parent$/,
50 | property: /^addEventListener|alert|atob|blur|btoa|cancelAnimationFrame|cancelIdleCallback|captureEvents|clearInterval|clearTimeout|clientInformation|close|closed|confirm|createImageBitmap|crypto|customElements|defaultStatus|defaultstatus|devicePixelRatio|dispatchEvent|document|external|fetch|find|focus|frameElement|frames|getComputedStyle|getSelection|history|indexedDB|innerHeight|innerWidth|isSecureContext|length|localStorage|location|locationbar|matchMedia|menubar|moveBy|moveTo|name|navigator|open|openDatabase|opener|origin|outerHeight|outerWidth|pageXOffset|pageYOffset|parent|performance|personalbar|postMessage|print|prompt|queueMicrotask|releaseEvents|removeEventListener|requestAnimationFrame|requestIdleCallback|resizeBy|resizeTo|screen|screenLeft|screenTop|screenX|screenY|scroll|scrollBy|scrollTo|scrollX|scrollY|scrollbars|self|sessionStorage|setInterval|setTimeout|speechSynthesis|status|statusbar|stop|styleMedia|toolbar|top|visualViewport|window$/
51 | },
52 | {
53 | property: 'prototype'
54 | },
55 | {
56 | object: 'document',
57 | property: /^body|documentElement|head|getElementById|querySelector|querySelectorAll$/
58 | },
59 | {
60 | object: 'Object',
61 | property: /^length|name|prototype|assign|getOwnPropertyDescriptor|getOwnPropertyDescriptors|getOwnPropertyNames|getOwnPropertySymbols|is|preventExtensions|seal|create|defineProperties|defineProperty|freeze|getPrototypeOf|setPrototypeOf|isExtensible|isFrozen|isSealed|keys|entries|fromEntries|values$/
62 | }
63 | ];
64 | function ignored(path) {
65 | if (path.node.object.type !== 'Identifier') {
66 | return false;
67 | }
68 | const object = path.node.object.name;
69 | if (path.node.property.type !== 'Identifier') {
70 | return false;
71 | }
72 | const property = path.node.property.name;
73 | for (let rule of IGNORED_MEMBER_EXPRESSIONS) {
74 | let numRules = Number(rule.hasOwnProperty('object')) + Number(rule.hasOwnProperty('property'));
75 | ;
76 | let trueRules = 0;
77 | if (rule.hasOwnProperty('object')) {
78 | const r = rule.object;
79 | if (typeof r === 'string') {
80 | trueRules += object === r ? 1 : 0;
81 | }
82 | else {
83 | trueRules += r.test(object) ? 1 : 0;
84 | }
85 | }
86 | if (rule.hasOwnProperty('property')) {
87 | const r = rule.property;
88 | if (typeof r === 'string') {
89 | trueRules += property === r ? 1 : 0;
90 | }
91 | else {
92 | trueRules += r.test(property) ? 1 : 0;
93 | }
94 | }
95 | if (trueRules === numRules) {
96 | return true;
97 | }
98 | }
99 | return false;
100 | }
101 | function instrumentate(js) {
102 | let ast;
103 | try {
104 | ast = acorn_1.Parser.parse(js, { sourceType: 'module', 'locations': true });
105 | }
106 | catch (ex) {
107 | ast = acorn_1.Parser.parse(js, { sourceType: 'script', 'locations': true });
108 | }
109 | ast_types_1.visit(ast, {
110 | visitMemberExpression(path) {
111 | if (ignored(path)) {
112 | this.traverse(path);
113 | return;
114 | }
115 | let property;
116 | if (path.node.computed) {
117 | property = path.node.property;
118 | }
119 | else {
120 | property = ast_types_1.builders.literal(path.node.property['name']);
121 | }
122 | const n = ast_types_1.builders.callExpression(ast_types_1.builders.identifier(GET_PROP), [
123 | path.node['object'],
124 | property,
125 | ]);
126 | if (!isLeftInAssignment(path)
127 | && !parentIsCallExpression(path)
128 | && !isWithinDeleteExpression(path)
129 | && !isWithinUpdateExpression(path)) {
130 | path.parentPath.get(path.name).replace(n);
131 | }
132 | this.traverse(path);
133 | },
134 | visitBinaryExpression(path) {
135 | if (path.node.operator === 'in') {
136 | const n = ast_types_1.builders.callExpression(ast_types_1.builders.identifier(IN_OPERATOR), [
137 | path.node.right,
138 | path.node.left,
139 | ]);
140 | path.parentPath.get(path.name).replace(n);
141 | }
142 | this.traverse(path);
143 | }
144 | });
145 | return escodegen.generate(ast);
146 | }
147 | const args = yargs
148 | .command('$0 [filesOrDirs...]', 'Instrumentate JS code to simplify exploitation of prototype pollution issues', () => { })
149 | .option('e', {
150 | default: 'js',
151 | alias: 'extensions',
152 | help: 'Comma-separated list of extensions instrumentated in directories'
153 | })
154 | .argv;
155 | const allFiles = [];
156 | for (let f of args.filesOrDirs) {
157 | if (!fs_1.existsSync(f)) {
158 | console.error(`Path ${f} does not exist.`);
159 | process.exit(1);
160 | }
161 | const stat = fs_1.lstatSync(f);
162 | if (stat.isDirectory()) {
163 | const files = glob.sync(`${f}/**/*.+(${args.e.split(",").join('|')})`);
164 | allFiles.push(...files);
165 | }
166 | else {
167 | allFiles.push(f);
168 | }
169 | }
170 | function input() {
171 | return new Promise(resolve => {
172 | const rl = readline.createInterface({ input: process.stdin });
173 | rl.once('line', line => resolve(line));
174 | });
175 | }
176 | (async function () {
177 | for (let file of allFiles) {
178 | const content = fs_1.readFileSync(file).toString('utf-8');
179 | const instrumentated = instrumentate(content);
180 | fs_1.writeFileSync(file, PREAMBLE + instrumentated);
181 | console.log(`Saved ${file}...`);
182 | }
183 | process.exit(0);
184 | })();
--------------------------------------------------------------------------------
/burp_pollute/server/pollute.php:
--------------------------------------------------------------------------------
1 | array('follow_location' => false)));
4 | $js = file_get_contents($_GET['url'], false, $context);
5 |
6 | if(array_search('Content-Encoding: gzip', $http_response_header)) {
7 | $js = gzdecode($js);
8 | }
9 |
10 | $tmpname = tempnam('/tmp', 'pp');
11 | file_put_contents($tmpname, $js);
12 | exec('node pollute.js '.$tmpname);
13 | header('Content-Type: text/javascript');
14 | header('Access-Control-Allow-Origin: *');
15 | echo file_get_contents($tmpname);
16 | unlink($tmpname);
17 | }
18 | ?>
--------------------------------------------------------------------------------
/match_rules/match-rules.tab:
--------------------------------------------------------------------------------
1 | (String\(\w+\)\.split\(\/&\|;\/\)\,\s*function\() 1 VULN Purl (jQuery-URL-Parser) Prototype Pollution High Certain
2 | (\/\(\[\^\\\[\\\]\]\+\)\|\(\\\[\\\]\)\/g\s*) 1 VULN CanJS deparam Prototype Pollution High Certain
3 | (\.substr\(0,\s*\w+\s*-\s*1\)\.match\(\/\(\[\^\\\]\\?\[\]\+\|\(\\B\)\(\?\=\\\]\)\)\/g\)) 1 VULN MooTools More Prototype Pollution High Certain
4 | (\$\.each\(\s*\w+\.replace\(\s*\/\\\+\/g\s*,\s*['"] ['"]\s*\)\.split\(\s*['"]&['"]\s*\)) 1 VULN jQuery BBQ (deparam) Prototype Pollution High Certain
5 | (\s*\/\\\[\/\.test\(\s*\w+\[0\]\s*\)\s*&&\s*\/\\\]\$\/\.test\(\s*\w+\[\s*\w+\s*\]\s*\)\s*) 1 VULN deparam Prototype Pollution High Certain
6 | (['"]\[\]['"]\s*===\s*\w+\s*\?\s*\w+\.push\(\w+\)\s*:\s*\w+\[\w+\]\s*=\s*\w+) 1 VULN deparam Prototype Pollution High Certain
7 | ((\w+)\s*=\s*decodeURIComponent[\w+;,\s\(\)\.\{=\[\]'"-\/\?}]+\b(\w+)\s*=\s*\3\[\2\]\s*=\s*\3\[\2\]\s*\|\|\s*\{\}) 1 VULN backbone-query-parameters 2 Prototype Pollution High Certain
8 | (\w+\s*=\s*\/\\\[\(\[\^\\\]\]\*\)\]\/g) 1 VULN V4Fire Core Prototype Pollution High Certain
9 | (\w+\s*=\s*\w+\.split\(\/\\\&\(amp\\\;\)\?\/\)) 1 VULN jQuery Sparkle Prototype Pollution High Certain
10 | (\/\^\(\[\^\[\]\+\??\)\(\\\[\.\*\\\]\)\?\$\/\.exec\(\s*\w+\s*\)) 1 VULN jQuery query-object Prototype Pollution High Certain
11 | (\.match\(\/\(\^\[\^\[\]\+\)\(\\\[\.\*\\\]\$\)\?\/\)) 1 VULN queryToObject Prototype Pollution High Certain
12 | (\?\s*decodeURIComponent\(\s*\w+\.substr\(\s*\w+\s*\+\s*1\)\)\s*:\s*['"]['"]) 1 VULN getJsonFromUrl Prototype Pollution High Certain
13 | (\w+\.replace\(\s*['"]\[\]['"]\s*,\s*['"]\[['"]\.concat\() 1 VULN Unknown lib_0 Prototype Pollution High Certain
14 | (\w+\s*=\s*\/\(\\w\+\)\\\[\(\\d\+\)\\\]\/) 1 VULN component/querystring Prototype Pollution High Certain
15 | (\(\w+\s*=\s*\w+\.exec\(\w+\)\)\s*\?\s*\(\s*\w+\[\w+\[1\]\]\s*=\s*\w+\[\w+\[1\]\]\s*\|\|\s*\[\]) 1 VULN component/querystring #2 Prototype Pollution High Certain
16 | (\/\(\.\*\)\\\[\(\[\^\\\]\]\*\)\\\]\$\/\.exec\(\w+\)) 1 VULN YUI 3 querystring-parse Prototype Pollution High Certain
17 | (\w+\s*=\s*\w+\.split\(\/\\\.\(\.\+\)\?\/\)\[1\]) 1 VULN jquery.parseParams.js Prototype Pollution High Certain
18 | (\w+\s*=\s*\/\\\[\?\(\[\^\\\]\[\]\+\)\\\]\?\/g) 1 VULN flow.js Prototype Pollution High Certain
19 | (\w+\s*=\s*\w+\(\w+\[1\]\.slice\(\w+\s*\+\s*1,\s*\w+\[1\]\.indexOf\(['"]\]['"],\s*\w+\)\)\)) 1 VULN wishpond decodeQueryString Prototype Pollution High Certain
20 | (\w+\s*=\s*\w+\.slice\(0,\s*\w+\.indexOf\(['"]\\x?0?0['"]\)\)) 1 VULN PHP.js parse_str Prototype Pollution High Certain
21 | ("\[\]"\s*===\s*\w+\.substring\(\w+\.length\s*-\s*2\)[\s\?\)\(]*(?:\w+\[)?\w+\s*=\s*\w+\.substring\(0,\s*\w+\.length\s*-\s*2\)) 1 VULN Unknown lib_1 Prototype Pollution High Certain
22 | (\w+\.match\(\/\(\^\[\^\\\[\]\+\)\(\\\[\.\*\\\]\$\)\?\/\)) 1 VULN Unknown lib_2 Prototype Pollution High Certain
23 | (\(\[\^\\\\\[\^\\\\\]\]\+\)\(\(\\\\\[\(\^\\\\\[\^\\\\\]\)\\\\\]\)\*\)) 1 VULN Unknown lib_3 Prototype Pollution High Certain
24 | (['"]-1['"]\s*==\s*\w+\[1\]\.indexOf\(['"]\[['"]\)) 1 VULN inbound setUrlParams Prototype Pollution High Certain
25 | (\w+\s*=\s*\w+\.split\(['"]\.['"]\)\s*[;,]\s*\w+\s*=\s*\w+\.pop\(\)\s*[;,]\s*\w+\s*=\s*\w+\.reduce\() 1 VULN Unknown lib_4 Prototype Pollution High Certain
26 | (\w+\s*=\s*\w+\.split\(\/\\\]\\\[\?\|\\\[\/\)[\w\s=\(\;\,<\-]*\w+\.indexOf\(['"]\[['"]\)) 1 VULN Old mithril.js Prototype Pollution High Certain
27 | (\w+\s*=\s*\w+\.split\(['"]\.['"]\)[\s;,\w]+=\s*\w+\.pop\(\)[!\s;,\w]+\(\w+\.length\)) 1 VULN builder.io QueryString.deepen Prototype Pollution High Certain
28 | (\w+\s*=\s*\w+\.indexOf\(['"]\]['"],\s*\w+\)[\s\w;,]+=\s*decodeURIComponent\(\w+\.substring\(\w+\s*\+\s*1) 1 VULN Unknown lib_5 Prototype Pollution High Certain
29 | (\w+\.replace\(['"]\]['"],\s*['"]['"]\)[\w\s;,\(]+\.search\(\/\[\\\.\\\[\]\/\)) 1 VULN arg.js Prototype Pollution High Certain
30 | (\/\^\(\[\$a-zA-z_\]\[\$a-zA-z0-9\]\*\)\\\[\(\[\$a-zA-z0-9\]\*\)\\\]\$\/) 1 VULN R.js Prototype Pollution High Certain
31 | (\/\^\(\\w\+\)\\\[\(\\w\+\)\?\\\]\(\\\[\\\]\)\?\/) 1 VULN davis.js Prototype Pollution High Certain
32 | (\.match\(\/\^\[\\\[\\\]\]\*\(\[\^\\\[\\\]\]\+\)\\\]\*\(\.\*\)\/\)) 1 VULN SoundCloud SDK decodeParams Prototype Pollution High Certain
33 | ((\w+)\s*=\s*\w+\.split\(['"]\.['"]\)[\w+;,\s\(\)\.\{=\[\]'"<]+\b(\w+)\s*=\s*\2\[\w+\][\w+;,\s\(\)\.\{=\[\]'"<\-!\|&]+(\w+)\[\3\]\s*=\s*\{\}[\w+;,\s\(\)\.\{=\[\]'"<\-!\|&]+\b\4\s*=\s*\4\[\3\]) 1 VULN Unknown lib_7 Prototype Pollution High Certain
--------------------------------------------------------------------------------
/prototype_checker/prototype_checker.js:
--------------------------------------------------------------------------------
1 | def = []
2 | def['Object'] = ["length", "name", "prototype", "assign", "getOwnPropertyDescriptor", "getOwnPropertyDescriptors", "getOwnPropertyNames", "getOwnPropertySymbols", "is", "preventExtensions", "seal", "create", "defineProperties", "defineProperty", "freeze", "getPrototypeOf", "setPrototypeOf", "isExtensible", "isFrozen", "isSealed", "keys", "entries", "fromEntries", "values", "hasOwn"]
3 | def['Object.prototype'] = ["constructor", "__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__", "isPrototypeOf", "propertyIsEnumerable", "toString", "valueOf", "__proto__", "toLocaleString"]
4 |
5 | def['String'] = ["length", "name", "prototype", "fromCharCode", "fromCodePoint", "raw"]
6 | def['String.prototype'] = ["length", "constructor", "anchor", "big", "blink", "bold", "charAt", "charCodeAt", "codePointAt", "concat", "endsWith", "fontcolor", "fontsize", "fixed", "includes", "indexOf", "italics", "lastIndexOf", "link", "localeCompare", "match", "matchAll", "normalize", "padEnd", "padStart", "repeat", "replace", "search", "slice", "small", "split", "strike", "sub", "substr", "substring", "sup", "startsWith", "toString", "trim", "trimStart", "trimLeft", "trimEnd", "trimRight", "toLocaleLowerCase", "toLocaleUpperCase", "toLowerCase", "toUpperCase", "valueOf", "replaceAll", "at"]
7 |
8 | def['Number'] = ["length", "name", "prototype", "isFinite", "isInteger", "isNaN", "isSafeInteger", "parseFloat", "parseInt", "MAX_VALUE", "MIN_VALUE", "NaN", "NEGATIVE_INFINITY", "POSITIVE_INFINITY", "MAX_SAFE_INTEGER", "MIN_SAFE_INTEGER", "EPSILON"]
9 | def['Number.prototype'] = ["constructor", "toExponential", "toFixed", "toPrecision", "toString", "valueOf", "toLocaleString"]
10 |
11 | def['Array'] = ["length", "name", "prototype", "isArray", "from", "of"]
12 | def['Array.prototype'] = ["length", "constructor", "concat", "copyWithin", "fill", "find", "findIndex", "lastIndexOf", "pop", "push", "reverse", "shift", "unshift", "slice", "sort", "splice", "includes", "indexOf", "join", "keys", "entries", "values", "forEach", "filter", "flat", "flatMap", "map", "every", "some", "reduce", "reduceRight", "toLocaleString", "toString", "at"]
13 |
14 | def['Function'] = ["length", "name", "prototype", "arguments", "caller"]
15 | def['Function.prototype'] = ["length", "name", "arguments", "caller", "constructor", "apply", "bind", "call", "toString"]
16 |
17 | def['Boolean'] = ["length", "name", "prototype"]
18 | def['Boolean.prototype'] = ["constructor", "toString", "valueOf"]
19 |
20 | check = {
21 | 'Object': Object,
22 | 'Object.prototype': Object.prototype,
23 | 'String': String,
24 | 'String.prototype': String.prototype,
25 | 'Number': Number,
26 | 'Number.prototype': Number.prototype,
27 | 'Array': Array,
28 | 'Array.prototype': Array.prototype,
29 | 'Function': Function,
30 | 'Function.prototype': Function.prototype,
31 | 'Boolean': Boolean,
32 | 'Boolean.prototype': Boolean.prototype
33 | }
34 |
35 | function getType(obj) {
36 | if(obj == null) return 'null'
37 | var funcNameRegex = /function (.{1,})\(/
38 | var results = (funcNameRegex).exec((obj).constructor.toString())
39 | return (results && results.length > 1) ? results[1] : ""
40 | }
41 |
42 | function checkProperties(obj, objType, path) {
43 | var filtered = Object.getOwnPropertyNames(obj).filter(value => (!(def[objType] ?? []).includes(value)) && !(objType=="String" && /\d/.test(value)))
44 | filtered.forEach(k => {
45 | kpath = `${path}["${k}"]`
46 | type = getType(obj[k])
47 | if(!['Function'].includes(type))
48 | console.log(`%c${kpath} ${type}`, 'background: #222; color: #bada55;')
49 | else
50 | console.log(`${kpath} ${type}`)
51 | if(obj[k] != null)
52 | checkProperties(obj[k], type, `${kpath}`)
53 | })
54 | }
55 |
56 | Object.keys(check).forEach(key => {
57 | checkProperties(check[key], key, key)
58 | })
--------------------------------------------------------------------------------