├── 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 | ![Screenshot at 18-34-30](https://user-images.githubusercontent.com/3295867/132973030-42b61f1c-f25d-451c-a034-1788d2fb0ff9.png) 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 | ![Screenshot at 18-44-47](https://user-images.githubusercontent.com/3295867/134192810-cb32d3b5-7ec4-47c0-89c8-1976e128af41.png) -------------------------------------------------------------------------------- /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 | }) --------------------------------------------------------------------------------