@contrast/protect 1.11.0 → 1.12.1
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-analysis/handlers.js +1 -1
- package/lib/input-tracing/handlers/index.js +30 -9
- 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 +5 -5
|
@@ -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];
|
|
@@ -147,12 +147,27 @@ module.exports = function(core) {
|
|
|
147
147
|
|
|
148
148
|
for (const result of stringInjectionResults) {
|
|
149
149
|
if (typeof sinkContext.value === 'object') {
|
|
150
|
-
traverseKeysAndValues(sinkContext.value, function(path, type, value) {
|
|
150
|
+
traverseKeysAndValues(sinkContext.value, function(path, type, value, obj) {
|
|
151
151
|
if (type !== 'Key' && !agentLib.isMongoQueryType(value)) return;
|
|
152
|
-
|
|
153
|
-
stringFindings
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
|
|
153
|
+
if (!stringFindings && type == 'Key' && value == '$accumulator') {
|
|
154
|
+
stringFindings =
|
|
155
|
+
handleStringValue(result, obj[value]?.['init'], agentLib) ||
|
|
156
|
+
handleStringValue(result, obj[value]?.['merge'], agentLib) ||
|
|
157
|
+
handleStringValue(result, obj[value]?.['finalize'], agentLib) ||
|
|
158
|
+
handleStringValue(result, obj[value]?.['accumulate'], agentLib);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!stringFindings && type == 'Key' && value == '$function') {
|
|
162
|
+
stringFindings =
|
|
163
|
+
handleStringValue(result, obj['$function']?.body, agentLib);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!stringFindings) {
|
|
167
|
+
stringFindings = handleStringValue(result, obj[value], agentLib);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (stringFindings) return true;
|
|
156
171
|
});
|
|
157
172
|
} else if (typeof sinkContext.value === 'string') {
|
|
158
173
|
stringFindings = handleStringValue(result, sinkContext.value, agentLib);
|
|
@@ -253,7 +268,10 @@ function getResultsByRuleId(ruleId, context) {
|
|
|
253
268
|
if (context.policy[ruleId] === OFF) {
|
|
254
269
|
return;
|
|
255
270
|
}
|
|
256
|
-
|
|
271
|
+
// because agent-lib stores all nosql-injection results under nosql-injection-mongo
|
|
272
|
+
const resultsMapRuleId = ruleId === 'nosql-injection' ? 'nosql-injection-mongo' : ruleId;
|
|
273
|
+
|
|
274
|
+
return context.resultsMap[resultsMapRuleId];
|
|
257
275
|
}
|
|
258
276
|
|
|
259
277
|
function handleObjectValue(result, object) {
|
|
@@ -306,7 +324,10 @@ function handleStringValue(result, cmd, agentLib) {
|
|
|
306
324
|
inputBoundaryIndex: 0,
|
|
307
325
|
};
|
|
308
326
|
} else {
|
|
309
|
-
|
|
327
|
+
// This is a temporary workaround, while `agent-lib` fixes
|
|
328
|
+
// the `checkSsjsInjectionSink` so it can detect the "TRUE-CLAUSE-1" correctly
|
|
329
|
+
// TODO: NODE-2897
|
|
330
|
+
const isAttack = result.idsList.includes('TRUE-CLAUSE-1') || agentLib.checkSsjsInjectionSink(cmd, inputIndex, result.value.length);
|
|
310
331
|
if (!isAttack) return findings;
|
|
311
332
|
|
|
312
333
|
findings = {
|
|
@@ -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.1",
|
|
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.1
|
|
21
|
-
"@contrast/common": "1.3.
|
|
22
|
-
"@contrast/core": "1.10.
|
|
23
|
-
"@contrast/esm-hooks": "1.6.
|
|
20
|
+
"@contrast/agent-lib": "^5.3.1",
|
|
21
|
+
"@contrast/common": "1.3.2",
|
|
22
|
+
"@contrast/core": "1.10.2",
|
|
23
|
+
"@contrast/esm-hooks": "1.6.2",
|
|
24
24
|
"@contrast/scopes": "1.2.0",
|
|
25
25
|
"ipaddr.js": "^2.0.1",
|
|
26
26
|
"semver": "^7.3.7"
|