@contrast/protect 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/hardening/handlers.js +2 -2
- package/lib/hardening/install/node-serialize0.js +1 -1
- package/lib/input-tracing/handlers/index.js +27 -8
- package/lib/input-tracing/index.js +1 -0
- package/lib/input-tracing/install/child-process.js +1 -1
- package/lib/input-tracing/install/eval.js +1 -1
- package/lib/input-tracing/install/fs.js +1 -1
- package/lib/input-tracing/install/function.js +1 -1
- package/lib/input-tracing/install/http.js +1 -1
- package/lib/input-tracing/install/marsdb.js +80 -0
- package/lib/input-tracing/install/mongodb.js +2 -1
- package/lib/input-tracing/install/mysql.js +1 -1
- package/lib/input-tracing/install/postgres.js +1 -1
- package/lib/input-tracing/install/sequelize.js +1 -1
- package/lib/input-tracing/install/sqlite3.js +1 -1
- package/lib/input-tracing/install/vm.js +1 -1
- package/lib/semantic-analysis/handlers.js +2 -2
- package/lib/semantic-analysis/install/libxmljs.js +1 -1
- package/lib/semantic-analysis/utils/xml-analysis.js +1 -1
- package/package.json +4 -4
|
@@ -39,7 +39,7 @@ module.exports = function(core) {
|
|
|
39
39
|
hardening.handleUntrustedDeserialization = function(sourceContext, sinkContext) {
|
|
40
40
|
const ruleId = 'untrusted-deserialization';
|
|
41
41
|
const mode = sourceContext.policy[ruleId];
|
|
42
|
-
const { name, value,
|
|
42
|
+
const { name, value, stacktraceOpts } = sinkContext;
|
|
43
43
|
|
|
44
44
|
if (mode === 'off') return;
|
|
45
45
|
|
|
@@ -49,7 +49,7 @@ module.exports = function(core) {
|
|
|
49
49
|
const blocked = BLOCKING_MODES.includes(mode);
|
|
50
50
|
const results = getResults(sourceContext, ruleId);
|
|
51
51
|
|
|
52
|
-
captureStacktrace(sinkContext,
|
|
52
|
+
captureStacktrace(sinkContext, stacktraceOpts);
|
|
53
53
|
results.push({
|
|
54
54
|
value: sinkContext.value,
|
|
55
55
|
blocked,
|
|
@@ -45,7 +45,7 @@ module.exports = function(core) {
|
|
|
45
45
|
const sinkContext = {
|
|
46
46
|
name: `${name}.${method}`,
|
|
47
47
|
value,
|
|
48
|
-
|
|
48
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
49
49
|
};
|
|
50
50
|
hardening.handleUntrustedDeserialization(sourceContext, sinkContext);
|
|
51
51
|
},
|
|
@@ -35,8 +35,8 @@ module.exports = function(core) {
|
|
|
35
35
|
} = core;
|
|
36
36
|
|
|
37
37
|
function handleFindings(sourceContext, sinkContext, ruleId, result, findings) {
|
|
38
|
-
const {
|
|
39
|
-
captureStacktrace(sinkContext,
|
|
38
|
+
const { stacktraceOpts } = sinkContext;
|
|
39
|
+
captureStacktrace(sinkContext, stacktraceOpts);
|
|
40
40
|
result.exploitMetadata.push({ sinkContext, findings });
|
|
41
41
|
|
|
42
42
|
const mode = sourceContext.policy[ruleId];
|
|
@@ -142,17 +142,33 @@ module.exports = function(core) {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
|
|
145
146
|
if (stringInjectionResults) {
|
|
146
147
|
let stringFindings = null;
|
|
147
148
|
|
|
148
149
|
for (const result of stringInjectionResults) {
|
|
149
150
|
if (typeof sinkContext.value === 'object') {
|
|
150
|
-
traverseKeysAndValues(sinkContext.value, function(path, type, value) {
|
|
151
|
+
traverseKeysAndValues(sinkContext.value, function(path, type, value, obj) {
|
|
151
152
|
if (type !== 'Key' && !agentLib.isMongoQueryType(value)) return;
|
|
152
|
-
|
|
153
|
-
stringFindings
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
|
|
154
|
+
if (!stringFindings && type == 'Key' && value == '$accumulator') {
|
|
155
|
+
stringFindings =
|
|
156
|
+
handleStringValue(result, obj[value]?.['init'], agentLib) ||
|
|
157
|
+
handleStringValue(result, obj[value]?.['merge'], agentLib) ||
|
|
158
|
+
handleStringValue(result, obj[value]?.['finalize'], agentLib) ||
|
|
159
|
+
handleStringValue(result, obj[value]?.['accumulate'], agentLib);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!stringFindings && type == 'Key' && value == '$function') {
|
|
163
|
+
stringFindings =
|
|
164
|
+
handleStringValue(result, obj['$function']?.body, agentLib);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!stringFindings) {
|
|
168
|
+
stringFindings = handleStringValue(result, obj[value], agentLib);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (stringFindings) return true;
|
|
156
172
|
});
|
|
157
173
|
} else if (typeof sinkContext.value === 'string') {
|
|
158
174
|
stringFindings = handleStringValue(result, sinkContext.value, agentLib);
|
|
@@ -253,7 +269,10 @@ function getResultsByRuleId(ruleId, context) {
|
|
|
253
269
|
if (context.policy[ruleId] === OFF) {
|
|
254
270
|
return;
|
|
255
271
|
}
|
|
256
|
-
|
|
272
|
+
// because agent-lib stores all nosql-injection results under nosql-injection-mongo
|
|
273
|
+
const resultsMapRuleId = ruleId === 'nosql-injection' ? 'nosql-injection-mongo' : ruleId;
|
|
274
|
+
|
|
275
|
+
return context.resultsMap[resultsMapRuleId];
|
|
257
276
|
}
|
|
258
277
|
|
|
259
278
|
function handleObjectValue(result, object) {
|
|
@@ -27,6 +27,7 @@ module.exports = function(core) {
|
|
|
27
27
|
require('./install/child-process')(core);
|
|
28
28
|
require('./install/fs')(core);
|
|
29
29
|
require('./install/mongodb')(core);
|
|
30
|
+
require('./install/marsdb')(core);
|
|
30
31
|
require('./install/mysql')(core);
|
|
31
32
|
require('./install/postgres')(core);
|
|
32
33
|
require('./install/sequelize')(core);
|
|
@@ -37,7 +37,7 @@ module.exports = function(core) {
|
|
|
37
37
|
const sinkContext = {
|
|
38
38
|
name,
|
|
39
39
|
value,
|
|
40
|
-
|
|
40
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
inputTracing.handleCommandInjection(sourceContext, sinkContext);
|
|
@@ -47,7 +47,7 @@ module.exports = function(core) {
|
|
|
47
47
|
const sinkContext = {
|
|
48
48
|
name: 'eval',
|
|
49
49
|
value,
|
|
50
|
-
|
|
50
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
51
51
|
};
|
|
52
52
|
inputTracing.ssjsInjection(sourceContext, sinkContext);
|
|
53
53
|
}
|
|
@@ -103,7 +103,7 @@ module.exports = function(core) {
|
|
|
103
103
|
const sinkContext = {
|
|
104
104
|
name,
|
|
105
105
|
value,
|
|
106
|
-
|
|
106
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
107
107
|
};
|
|
108
108
|
inputTracing.handlePathTraversal(sourceContext, sinkContext);
|
|
109
109
|
core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(sourceContext, sinkContext);
|
|
@@ -48,7 +48,7 @@ module.exports = function(core) {
|
|
|
48
48
|
const sinkContext = {
|
|
49
49
|
name: 'Function',
|
|
50
50
|
value: fnBody,
|
|
51
|
-
|
|
51
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
inputTracing.ssjsInjection(sourceContext, sinkContext);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2022 Contrast Security, Inc
|
|
3
|
+
* Contact: support@contrastsecurity.com
|
|
4
|
+
* License: Commercial
|
|
5
|
+
|
|
6
|
+
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
* made available through public repositories, use of this Software is subject to
|
|
9
|
+
* the applicable End User Licensing Agreement found at
|
|
10
|
+
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
* between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
* way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
const { patchType } = require('../constants');
|
|
18
|
+
|
|
19
|
+
module.exports = function (core) {
|
|
20
|
+
const {
|
|
21
|
+
depHooks,
|
|
22
|
+
patcher,
|
|
23
|
+
protect: { getSourceContext, inputTracing },
|
|
24
|
+
} = core;
|
|
25
|
+
|
|
26
|
+
const methods = ['find', 'findOne', 'update', 'insert', 'remove', 'ids', 'count'];
|
|
27
|
+
|
|
28
|
+
function getCursorQueryData(args) {
|
|
29
|
+
const query = args[0];
|
|
30
|
+
if (!query) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (Object.prototype.toString.call(query) === '[object String]') {
|
|
35
|
+
return query.toString();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (query['$where']) {
|
|
39
|
+
return query['$where'].toString();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return query;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function install() {
|
|
46
|
+
depHooks.resolve({ name: 'marsdb' }, marsdb => {
|
|
47
|
+
methods.forEach((method) => {
|
|
48
|
+
const name = `marsdb.Collection.prototype.${method}`;
|
|
49
|
+
|
|
50
|
+
patcher.patch(marsdb.Collection.prototype, method, {
|
|
51
|
+
name,
|
|
52
|
+
patchType,
|
|
53
|
+
pre({ args, hooked, orig }) {
|
|
54
|
+
const value = getCursorQueryData(args);
|
|
55
|
+
const sourceContext = getSourceContext(name);
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
!sourceContext ||
|
|
59
|
+
!value ||
|
|
60
|
+
!Object.keys(value).length
|
|
61
|
+
) return;
|
|
62
|
+
|
|
63
|
+
const sinkContext = {
|
|
64
|
+
name,
|
|
65
|
+
value,
|
|
66
|
+
stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
|
|
67
|
+
};
|
|
68
|
+
inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const marsdbInstr = (core.protect.inputTracing.marsdbInstrumentation = {
|
|
76
|
+
install,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return marsdbInstr;
|
|
80
|
+
};
|
|
@@ -62,7 +62,7 @@ module.exports = function (core) {
|
|
|
62
62
|
const sinkContext = {
|
|
63
63
|
name,
|
|
64
64
|
value,
|
|
65
|
-
|
|
65
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
66
66
|
};
|
|
67
67
|
inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
|
|
68
68
|
}
|
|
@@ -122,6 +122,7 @@ module.exports = function (core) {
|
|
|
122
122
|
'countDocuments',
|
|
123
123
|
'count',
|
|
124
124
|
'distinct',
|
|
125
|
+
'aggregate'
|
|
125
126
|
],
|
|
126
127
|
patchType,
|
|
127
128
|
preHookFn: v4CollectionVal
|
|
@@ -58,7 +58,7 @@ module.exports = function(core) {
|
|
|
58
58
|
const sinkContext = {
|
|
59
59
|
name,
|
|
60
60
|
value,
|
|
61
|
-
|
|
61
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
inputTracing.handleSqlInjection(sourceContext, sinkContext);
|
|
@@ -42,7 +42,7 @@ module.exports = function(core) {
|
|
|
42
42
|
const sinkContext = {
|
|
43
43
|
name,
|
|
44
44
|
value,
|
|
45
|
-
|
|
45
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
inputTracing.handleSqlInjection(sourceContext, sinkContext);
|
|
@@ -51,7 +51,7 @@ module.exports = function(core) {
|
|
|
51
51
|
const sinkContext = {
|
|
52
52
|
name,
|
|
53
53
|
value,
|
|
54
|
-
|
|
54
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
inputTracing.handleSqlInjection(sourceContext, sinkContext);
|
|
@@ -46,7 +46,7 @@ module.exports = function(core) {
|
|
|
46
46
|
const sinkContext = {
|
|
47
47
|
name,
|
|
48
48
|
value,
|
|
49
|
-
|
|
49
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
inputTracing.handleSqlInjection(sourceContext, sinkContext);
|
|
@@ -40,7 +40,7 @@ module.exports = function(core) {
|
|
|
40
40
|
const sinkContext = {
|
|
41
41
|
name,
|
|
42
42
|
value: arg,
|
|
43
|
-
|
|
43
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
44
44
|
};
|
|
45
45
|
inputTracing.ssjsInjection(sourceContext, sinkContext);
|
|
46
46
|
}
|
|
@@ -41,8 +41,8 @@ module.exports = function(core) {
|
|
|
41
41
|
const { protect: { agentLib, semanticAnalysis, throwSecurityException }, captureStacktrace } = core;
|
|
42
42
|
|
|
43
43
|
function handleResult(sourceContext, sinkContext, ruleId, mode, finding) {
|
|
44
|
-
const { value,
|
|
45
|
-
captureStacktrace(sinkContext,
|
|
44
|
+
const { value, stacktraceOpts } = sinkContext;
|
|
45
|
+
captureStacktrace(sinkContext, stacktraceOpts);
|
|
46
46
|
const result = {
|
|
47
47
|
blocked: false,
|
|
48
48
|
ruleId,
|
|
@@ -62,7 +62,7 @@ module.exports = function(core) {
|
|
|
62
62
|
const sinkContext = {
|
|
63
63
|
name: 'libxmljs.parseXmlString',
|
|
64
64
|
value,
|
|
65
|
-
|
|
65
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
try {
|
|
@@ -40,7 +40,7 @@ const ENTITY_TYPES = {
|
|
|
40
40
|
// We only use this against lowercase strings; removed A-Z for speed
|
|
41
41
|
const FILE_PATTERN_WINDOWS = /^[\\\\]*[a-z]{1,3}:.*/;
|
|
42
42
|
|
|
43
|
-
const entityRegex = /<!ENTITY\s+(?<name>[a-zA-Z0-f]+)\s+(?<type>SYSTEM|PUBLIC)\s+"(?<uri1>.*?)"\s*("(?<uri2>.*?)"\s*)?>/g;
|
|
43
|
+
const entityRegex = /<!ENTITY\s+(?<name>[a-zA-Z0-f]+)\s+(?<type>SYSTEM|PUBLIC)\s+['"](?<uri1>.*?)['"]\s*(['"](?<uri2>.*?)['"]\s*)?>/g;
|
|
44
44
|
|
|
45
45
|
// Helper Functions
|
|
46
46
|
const indicators = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/protect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Contrast service providing framework-agnostic Protect support",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"test": "../scripts/test.sh"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@contrast/agent-lib": "^5.
|
|
20
|
+
"@contrast/agent-lib": "^5.3.0",
|
|
21
21
|
"@contrast/common": "1.3.1",
|
|
22
|
-
"@contrast/core": "1.10.
|
|
23
|
-
"@contrast/esm-hooks": "1.6.
|
|
22
|
+
"@contrast/core": "1.10.1",
|
|
23
|
+
"@contrast/esm-hooks": "1.6.1",
|
|
24
24
|
"@contrast/scopes": "1.2.0",
|
|
25
25
|
"ipaddr.js": "^2.0.1",
|
|
26
26
|
"semver": "^7.3.7"
|