@contrast/assess 1.73.0 → 1.74.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/dataflow/sinks/install/child-process.js +2 -6
- package/lib/dataflow/sinks/install/express/reflected-xss.js +0 -1
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +0 -1
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +0 -1
- package/lib/dataflow/sinks/install/fs.js +0 -1
- package/lib/dataflow/sinks/install/function.js +1 -2
- package/lib/dataflow/sinks/install/hapi/unvalidated-redirect.js +0 -1
- package/lib/dataflow/sinks/install/http/request.js +0 -1
- package/lib/dataflow/sinks/install/http/server-response.js +1 -2
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +0 -1
- package/lib/dataflow/sinks/install/libxmljs.js +0 -1
- package/lib/dataflow/sinks/install/marsdb.js +0 -1
- package/lib/dataflow/sinks/install/mssql.js +0 -1
- package/lib/dataflow/sinks/install/mysql.js +0 -1
- package/lib/dataflow/sinks/install/node-serialize.js +0 -1
- package/lib/dataflow/sinks/install/postgres.js +0 -1
- package/lib/dataflow/sinks/install/restify.js +0 -1
- package/lib/dataflow/sinks/install/sequelize.js +1 -2
- package/lib/dataflow/sinks/install/sqlite3.js +0 -1
- package/lib/dataflow/sinks/install/vm.js +1 -2
- package/lib/dataflow/sources/index.js +1 -0
- package/lib/dataflow/sources/install/@sap.js +132 -0
- package/package.json +3 -3
|
@@ -51,8 +51,7 @@ module.exports = function(core) {
|
|
|
51
51
|
command,
|
|
52
52
|
secondArg,
|
|
53
53
|
thirdArg,
|
|
54
|
-
hooked
|
|
55
|
-
orig
|
|
54
|
+
hooked
|
|
56
55
|
) {
|
|
57
56
|
const strInfo = tracker.getData(command);
|
|
58
57
|
if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags))
|
|
@@ -91,7 +90,6 @@ module.exports = function(core) {
|
|
|
91
90
|
source: 'P0',
|
|
92
91
|
stacktraceOpts: {
|
|
93
92
|
constructorOpt: hooked,
|
|
94
|
-
prependFrames: [orig],
|
|
95
93
|
},
|
|
96
94
|
});
|
|
97
95
|
|
|
@@ -120,8 +118,7 @@ module.exports = function(core) {
|
|
|
120
118
|
commandInfo,
|
|
121
119
|
origArgs,
|
|
122
120
|
options,
|
|
123
|
-
hooked
|
|
124
|
-
orig
|
|
121
|
+
hooked
|
|
125
122
|
) {
|
|
126
123
|
if (!Array.isArray(origArgs) || !origArgs?.length) return;
|
|
127
124
|
|
|
@@ -177,7 +174,6 @@ module.exports = function(core) {
|
|
|
177
174
|
source: 'P1',
|
|
178
175
|
stacktraceOpts: {
|
|
179
176
|
contructorOpt: hooked,
|
|
180
|
-
prependFrames: [orig],
|
|
181
177
|
},
|
|
182
178
|
});
|
|
183
179
|
|
|
@@ -65,7 +65,7 @@ module.exports = function (core) {
|
|
|
65
65
|
patcher.patch(global.ContrastMethods, 'Function', {
|
|
66
66
|
name: 'global.ContrastMethods.Function',
|
|
67
67
|
patchType,
|
|
68
|
-
pre({ args: origArgs, hooked,
|
|
68
|
+
pre({ args: origArgs, hooked, name }) {
|
|
69
69
|
if (!getSinkContext(ruleId)) return;
|
|
70
70
|
|
|
71
71
|
const fnBody = origArgs[origArgs.length - 1];
|
|
@@ -131,7 +131,6 @@ module.exports = function (core) {
|
|
|
131
131
|
source: `P${origArgs.length - 1}`,
|
|
132
132
|
stacktraceOpts: {
|
|
133
133
|
contructorOpt: hooked,
|
|
134
|
-
prependFrames: [orig],
|
|
135
134
|
},
|
|
136
135
|
});
|
|
137
136
|
|
|
@@ -76,7 +76,7 @@ module.exports = function(core) {
|
|
|
76
76
|
WEAK_URL_ENCODED,
|
|
77
77
|
];
|
|
78
78
|
|
|
79
|
-
const preHook = (moduleName, responseName, method) => ({ args, obj: response, result, hooked
|
|
79
|
+
const preHook = (moduleName, responseName, method) => ({ args, obj: response, result, hooked }) => {
|
|
80
80
|
const methodName = `${`${responseName}.prototype`}.${method}`;
|
|
81
81
|
const name = `${moduleName}.${methodName}`;
|
|
82
82
|
const sourceContext = getSinkContext(ruleId);
|
|
@@ -113,7 +113,6 @@ module.exports = function(core) {
|
|
|
113
113
|
source: 'P0',
|
|
114
114
|
stacktraceOpts: {
|
|
115
115
|
constructorOpt: hooked,
|
|
116
|
-
prependFrames: [orig]
|
|
117
116
|
},
|
|
118
117
|
tags: strInfo.tags,
|
|
119
118
|
});
|
|
@@ -69,7 +69,7 @@ module.exports = function (core) {
|
|
|
69
69
|
around(next, data) {
|
|
70
70
|
if (!getSinkContext(ruleId) || !data.args[0]) return next();
|
|
71
71
|
|
|
72
|
-
const { args, hooked
|
|
72
|
+
const { args, hooked } = data;
|
|
73
73
|
const query = typeof args[0] === 'string' ? args[0] : args[0].query;
|
|
74
74
|
|
|
75
75
|
try {
|
|
@@ -121,7 +121,6 @@ module.exports = function (core) {
|
|
|
121
121
|
source: 'P0',
|
|
122
122
|
stacktraceOpts: {
|
|
123
123
|
contructorOpt: hooked,
|
|
124
|
-
prependFrames: [orig],
|
|
125
124
|
},
|
|
126
125
|
});
|
|
127
126
|
|
|
@@ -142,7 +142,7 @@ module.exports = function (core) {
|
|
|
142
142
|
return argsInfo;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
function around(next, { args: origArgs, hooked,
|
|
145
|
+
function around(next, { args: origArgs, hooked, name }) {
|
|
146
146
|
if (!getSinkContext(ruleId) || isLocked(ruleId)) return next();
|
|
147
147
|
|
|
148
148
|
const methodPath = StringPrototypeSplit.call(name, '.');
|
|
@@ -232,7 +232,6 @@ module.exports = function (core) {
|
|
|
232
232
|
source: `P${vulnerableArg.idx}`,
|
|
233
233
|
stacktraceOpts: {
|
|
234
234
|
contructorOpt: hooked,
|
|
235
|
-
prependFrames: [orig],
|
|
236
235
|
},
|
|
237
236
|
});
|
|
238
237
|
|
|
@@ -21,6 +21,7 @@ module.exports = function (core) {
|
|
|
21
21
|
const sources = core.assess.dataflow.sources = {};
|
|
22
22
|
|
|
23
23
|
core.initComponentSync(require('./handler'));
|
|
24
|
+
core.initComponentSync(require('./install/@sap'));
|
|
24
25
|
require('./install/express')(core);
|
|
25
26
|
require('./install/fastify')(core);
|
|
26
27
|
require('./install/hapi')(core);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2026 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
|
+
|
|
18
|
+
const { kComponentName, ComponentBase } = require('@contrast/core/lib/ioc/core');
|
|
19
|
+
const {
|
|
20
|
+
InputType,
|
|
21
|
+
traverseValues,
|
|
22
|
+
primordials: { ArrayPrototypeJoin, StringPrototypeSlice },
|
|
23
|
+
} = require('@contrast/common');
|
|
24
|
+
const { patchType } = require('../common');
|
|
25
|
+
|
|
26
|
+
const COMPONENT_NAME = 'assess.dataflow.sources.sapServiceInstrumentation';
|
|
27
|
+
|
|
28
|
+
module.exports = class SAPInstrumentation extends ComponentBase {
|
|
29
|
+
static [kComponentName] = COMPONENT_NAME;
|
|
30
|
+
|
|
31
|
+
install() {
|
|
32
|
+
const {
|
|
33
|
+
depHooks,
|
|
34
|
+
patcher,
|
|
35
|
+
assess,
|
|
36
|
+
} = this.core;
|
|
37
|
+
|
|
38
|
+
depHooks.resolve({ name: '@sap/cds', version: '*', file: 'lib/srv/cds.Service.js' }, (Service) => {
|
|
39
|
+
patcher.patch(Service.prototype, 'dispatch', {
|
|
40
|
+
name: '@sap/cds.Service.prototype.dispatch',
|
|
41
|
+
patchType,
|
|
42
|
+
pre(data) {
|
|
43
|
+
const sourceContext = assess.getSourceContext();
|
|
44
|
+
if (!sourceContext || sourceContext.cdsDispatchHandled) return;
|
|
45
|
+
|
|
46
|
+
// dispatch can run multiple times e.g. with $batch
|
|
47
|
+
sourceContext.cdsDispatchHandled = true;
|
|
48
|
+
const cdsReq = data.args[0];
|
|
49
|
+
|
|
50
|
+
// this should also take care of cdsReq.req._data and cdsReq.data
|
|
51
|
+
if (cdsReq?.req?.body) {
|
|
52
|
+
assess.dataflow.sources.handle({
|
|
53
|
+
context: 'req.body',
|
|
54
|
+
data: cdsReq.req.body,
|
|
55
|
+
name: data.name,
|
|
56
|
+
inputType: InputType.BODY,
|
|
57
|
+
sourceContext,
|
|
58
|
+
stacktraceOpts: {
|
|
59
|
+
constructorOpt: data.hooked,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (cdsReq?.req?._raw) {
|
|
64
|
+
assess.dataflow.sources.handle({
|
|
65
|
+
context: 'req',
|
|
66
|
+
data: cdsReq,
|
|
67
|
+
name: data.name,
|
|
68
|
+
keys: ['_raw'],
|
|
69
|
+
inputType: InputType.BODY,
|
|
70
|
+
sourceContext,
|
|
71
|
+
stacktraceOpts: {
|
|
72
|
+
constructorOpt: data.hooked,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (cdsReq?.params?.length) {
|
|
79
|
+
assess.dataflow.sources.handle({
|
|
80
|
+
context: 'req.params',
|
|
81
|
+
data: cdsReq.params,
|
|
82
|
+
name: data.name,
|
|
83
|
+
inputType: InputType.URL_PARAMETER,
|
|
84
|
+
sourceContext,
|
|
85
|
+
stacktraceOpts: {
|
|
86
|
+
constructorOpt: data.hooked,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Handle cdsReq.query (CQN AST) since the OData/REST URL parser is deadzoned.
|
|
92
|
+
// Only track user-controlled leaf nodes, which are `val` and `ref` values.
|
|
93
|
+
// Other strings in the CQN are grammar tokens or schema-derived identifiers.
|
|
94
|
+
// Note: this also handles cdsReq.req._target since it's an alias for query values e.g.
|
|
95
|
+
// req._target.ref[0].where[2].val === cdsReq.query.SELECT.from.ref[0].where[2].val
|
|
96
|
+
const queries = Array.isArray(cdsReq?.query)
|
|
97
|
+
? cdsReq.query
|
|
98
|
+
: cdsReq?.query ? [cdsReq.query] : [];
|
|
99
|
+
|
|
100
|
+
for (const cqn of queries) {
|
|
101
|
+
if (!cqn || typeof cqn !== 'object') continue;
|
|
102
|
+
|
|
103
|
+
traverseValues(cqn, (path, _type, _value, parent) => {
|
|
104
|
+
const lastKey = path[path.length - 1];
|
|
105
|
+
const inValObject = !Array.isArray(parent) && lastKey === 'val';
|
|
106
|
+
const inRefArray = Array.isArray(parent) && path[path.length - 2] === 'ref';
|
|
107
|
+
if (!inValObject && !inRefArray) return;
|
|
108
|
+
|
|
109
|
+
// Build context as 'req.query' + path without the trailing leaf key
|
|
110
|
+
// (sources.handle appends the leaf key automatically).
|
|
111
|
+
const fullPath = ArrayPrototypeJoin.call(path, '.');
|
|
112
|
+
const lastKeyLen = `${lastKey}`.length + (path.length > 1 ? 1 : 0);
|
|
113
|
+
const prefix = StringPrototypeSlice.call(fullPath, 0, fullPath.length - lastKeyLen);
|
|
114
|
+
|
|
115
|
+
assess.dataflow.sources.handle({
|
|
116
|
+
context: prefix ? `req.query.${prefix}` : 'req.query',
|
|
117
|
+
data: parent,
|
|
118
|
+
name: data.name,
|
|
119
|
+
keys: [lastKey],
|
|
120
|
+
inputType: InputType.URL_PARAMETER,
|
|
121
|
+
sourceContext,
|
|
122
|
+
stacktraceOpts: {
|
|
123
|
+
constructorOpt: data.hooked,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/assess",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.74.0",
|
|
4
4
|
"description": "Contrast service providing framework-agnostic Assess support",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"@contrast/logger": "1.36.2",
|
|
30
30
|
"@contrast/patcher": "1.35.2",
|
|
31
31
|
"@contrast/rewriter": "1.40.3",
|
|
32
|
-
"@contrast/route-coverage": "1.57.
|
|
32
|
+
"@contrast/route-coverage": "1.57.1",
|
|
33
33
|
"@contrast/scopes": "1.33.2",
|
|
34
34
|
"@contrast/sources": "1.9.2",
|
|
35
|
-
"@contrast/stack-trace-factory": "1.
|
|
35
|
+
"@contrast/stack-trace-factory": "1.4.0",
|
|
36
36
|
"semver": "7.6.0"
|
|
37
37
|
}
|
|
38
38
|
}
|