@contrast/agent 4.6.0 → 4.7.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/bin/VERSION +1 -1
- package/bin/linux/contrast-service +0 -0
- package/bin/mac/contrast-service +0 -0
- package/bin/windows/contrast-service.exe +0 -0
- package/lib/assess/membrane/source-membrane.js +1 -14
- package/lib/assess/policy/propagators.json +5 -35
- package/lib/assess/policy/rules.json +5 -0
- package/lib/assess/policy/signatures.json +5 -0
- package/lib/assess/propagators/dustjs/escape-html.js +22 -0
- package/lib/assess/propagators/dustjs/escape-js.js +22 -0
- package/lib/assess/propagators/encode-uri/encode-uri-component.js +22 -0
- package/lib/assess/propagators/encode-uri/encode-uri.js +22 -0
- package/lib/assess/propagators/index.js +0 -2
- package/lib/assess/propagators/joi/values.js +26 -11
- package/lib/assess/propagators/mustache/escape.js +22 -0
- package/lib/assess/propagators/template-escape.js +84 -0
- package/lib/assess/propagators/templates.js +2 -3
- package/lib/assess/sinks/dustjs-linkedin-xss.js +131 -0
- package/lib/core/arch-components/rethinkdb.js +53 -0
- package/lib/core/config/options.js +1 -1
- package/lib/hooks/patcher.js +60 -37
- package/lib/util/source-map.js +1 -1
- package/package.json +2 -2
package/bin/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.28.0
|
|
Binary file
|
package/bin/mac/contrast-service
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -39,14 +39,6 @@ const signature = new Signature({
|
|
|
39
39
|
isModule: false
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
const numericCheck = /^[0-9]+$/;
|
|
43
|
-
function isNumeric(input) {
|
|
44
|
-
// regex from validator.js/lib/isNumeric.js:
|
|
45
|
-
// https://github.com/validatorjs/validator.js/blob/master/lib/isNumeric.js
|
|
46
|
-
// do we want to allow symbols (plus/minus sign, decimal point?)
|
|
47
|
-
return numericCheck.test(input);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
42
|
module.exports = class SourceMembrane extends Membrane {
|
|
51
43
|
/**
|
|
52
44
|
* @param {object} config agent config to use for array sampling.
|
|
@@ -304,8 +296,7 @@ module.exports = class SourceMembrane extends Membrane {
|
|
|
304
296
|
/**
|
|
305
297
|
* The `TagRanges` returned will include the `untrusted` tag. There will be
|
|
306
298
|
* `exclusion:${ruleId}` tags for input exclusions pertaining to specific
|
|
307
|
-
* rules and whose name matches the property name described in metadata.
|
|
308
|
-
* `limited-chars` tags are included if string is numeric.
|
|
299
|
+
* rules and whose name matches the property name described in metadata.
|
|
309
300
|
* @param {string} str string being tracked by membrane
|
|
310
301
|
* @param {object} metadata metadata about source type and key name
|
|
311
302
|
* @returns {TagRange[]}
|
|
@@ -342,10 +333,6 @@ module.exports = class SourceMembrane extends Membrane {
|
|
|
342
333
|
}
|
|
343
334
|
}
|
|
344
335
|
|
|
345
|
-
if (isNumeric(str)) {
|
|
346
|
-
tagRanges.push(new TagRange(start, stop, 'limited-chars'));
|
|
347
|
-
}
|
|
348
|
-
|
|
349
336
|
if (metadata.sourceType === 'header') {
|
|
350
337
|
if (metadata.path.toLocaleLowerCase() !== 'referer') {
|
|
351
338
|
tagRanges.push(new TagRange(start, stop, 'header'));
|
|
@@ -59,33 +59,15 @@
|
|
|
59
59
|
},
|
|
60
60
|
"mustache.escape": {
|
|
61
61
|
"enabled": true,
|
|
62
|
-
"
|
|
63
|
-
"target": "R",
|
|
64
|
-
"tags": ["html-encoded"],
|
|
65
|
-
"type": "overload",
|
|
66
|
-
"command": {
|
|
67
|
-
"type": "keep"
|
|
68
|
-
}
|
|
62
|
+
"provider": "./propagators/mustache/escape.js"
|
|
69
63
|
},
|
|
70
64
|
"dust.escapeHtml": {
|
|
71
65
|
"enabled": true,
|
|
72
|
-
"
|
|
73
|
-
"target": "R",
|
|
74
|
-
"tags": ["html-encoded"],
|
|
75
|
-
"type": "overload",
|
|
76
|
-
"command": {
|
|
77
|
-
"type": "keep"
|
|
78
|
-
}
|
|
66
|
+
"provider": "./propagators/dustjs/escape-html.js"
|
|
79
67
|
},
|
|
80
68
|
"dust.escapeJs": {
|
|
81
69
|
"enabled": true,
|
|
82
|
-
"
|
|
83
|
-
"target": "R",
|
|
84
|
-
"tags": ["javascript-encoded"],
|
|
85
|
-
"type": "overload",
|
|
86
|
-
"command": {
|
|
87
|
-
"type": "keep"
|
|
88
|
-
}
|
|
70
|
+
"provider": "./propagators/dustjs/escape-js.js"
|
|
89
71
|
},
|
|
90
72
|
"pug.compile": {
|
|
91
73
|
"enabled": true,
|
|
@@ -349,23 +331,11 @@
|
|
|
349
331
|
},
|
|
350
332
|
"encodeURI": {
|
|
351
333
|
"enabled": true,
|
|
352
|
-
"
|
|
353
|
-
"tags": ["weak-url-encoded"],
|
|
354
|
-
"source": "P",
|
|
355
|
-
"target": "R",
|
|
356
|
-
"command": {
|
|
357
|
-
"type": "keep"
|
|
358
|
-
}
|
|
334
|
+
"provider": "./propagators/encode-uri/encode-uri.js"
|
|
359
335
|
},
|
|
360
336
|
"encodeURIComponent": {
|
|
361
337
|
"enabled": true,
|
|
362
|
-
"
|
|
363
|
-
"tags": ["url-encoded"],
|
|
364
|
-
"source": "P",
|
|
365
|
-
"target": "R",
|
|
366
|
-
"command": {
|
|
367
|
-
"type": "keep"
|
|
368
|
-
}
|
|
338
|
+
"provider": "./propagators/encode-uri/encode-uri-component.js"
|
|
369
339
|
},
|
|
370
340
|
"process.__add": {
|
|
371
341
|
"enabled": true,
|
|
@@ -1371,6 +1371,11 @@
|
|
|
1371
1371
|
"type": "http",
|
|
1372
1372
|
"provider": "./sinks/hapi-16-xss"
|
|
1373
1373
|
},
|
|
1374
|
+
"reflected-xss_dustjs-linkedin": {
|
|
1375
|
+
"enabled": true,
|
|
1376
|
+
"type": "http",
|
|
1377
|
+
"provider": "./sinks/dustjs-linkedin-xss"
|
|
1378
|
+
},
|
|
1374
1379
|
"reflected-xss": {
|
|
1375
1380
|
"enabled": true,
|
|
1376
1381
|
"type": "hook",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright: 2021 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
|
+
'use strict';
|
|
16
|
+
const { propagate } = require('../template-escape');
|
|
17
|
+
|
|
18
|
+
function handler(data) {
|
|
19
|
+
propagate(data, 'html-encoded', 'dustjs-linkedin.escapeHtml');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports.handle = handler;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright: 2021 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
|
+
'use strict';
|
|
16
|
+
const { propagate } = require('../template-escape');
|
|
17
|
+
|
|
18
|
+
function handler(data) {
|
|
19
|
+
propagate(data, 'javascript-encoded', 'dustjs-linkedin.escapeJs');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports.handle = handler;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright: 2021 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
|
+
'use strict';
|
|
16
|
+
const { propagate } = require('../template-escape');
|
|
17
|
+
|
|
18
|
+
function handler(data) {
|
|
19
|
+
propagate(data, 'url-encoded', 'global.encodeURIComponent');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports.handle = handler;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright: 2021 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
|
+
'use strict';
|
|
16
|
+
const { propagate } = require('../template-escape');
|
|
17
|
+
|
|
18
|
+
function handler(data) {
|
|
19
|
+
propagate(data, 'weak-url-encoded', 'global.encodeURI');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports.handle = handler;
|
|
@@ -128,8 +128,6 @@ const generateHookWrappers = (agent, policyNode, key) => {
|
|
|
128
128
|
} else {
|
|
129
129
|
({ pre, post } = provider.handle);
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
propagatorDescriptor.provider = provider.handle;
|
|
133
131
|
} else {
|
|
134
132
|
// generic propagator
|
|
135
133
|
post = new Propagator(agent, propagatorDescriptor);
|
|
@@ -51,18 +51,9 @@ function instrumentJoiValues(values) {
|
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
const resultIsString = _.isString(result.value);
|
|
55
|
-
const argIsString = _.isString(value);
|
|
56
|
-
|
|
57
54
|
if (result.ref) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const resolvedTrackData = tracker.getData(result.value);
|
|
61
|
-
const refTrackData = tracker.getData(value);
|
|
62
|
-
const handler = getRefHandler(resolvedTrackData, refTrackData);
|
|
63
|
-
handler && handler(data, resolvedTrackData, refTrackData);
|
|
64
|
-
}
|
|
65
|
-
} else if (resultIsString) {
|
|
55
|
+
handler(result.value, value, data);
|
|
56
|
+
} else if (_.isString(result.value)) {
|
|
66
57
|
// use case is .valid() - safe
|
|
67
58
|
tracker.untrack(result.value);
|
|
68
59
|
}
|
|
@@ -70,6 +61,30 @@ function instrumentJoiValues(values) {
|
|
|
70
61
|
});
|
|
71
62
|
}
|
|
72
63
|
|
|
64
|
+
const stringHandler = (resultValue, argValue, data) => {
|
|
65
|
+
const resultIsString = _.isString(resultValue);
|
|
66
|
+
const argIsString = _.isString(argValue);
|
|
67
|
+
|
|
68
|
+
if (resultIsString && argIsString) {
|
|
69
|
+
const resolvedTrackData = tracker.getData(resultValue);
|
|
70
|
+
const refTrackData = tracker.getData(argValue);
|
|
71
|
+
const handler = getRefHandler(resolvedTrackData, refTrackData);
|
|
72
|
+
handler && handler(data, resolvedTrackData, refTrackData);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handler = (resultValue, argValue, data) => {
|
|
77
|
+
if (_.isString(resultValue)) {
|
|
78
|
+
return stringHandler(resultValue, argValue, data);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (_.isObject(resultValue)) {
|
|
82
|
+
for (const [key, value] of Object.entries(resultValue)) {
|
|
83
|
+
handler(value, argValue[key], data);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
73
88
|
/**
|
|
74
89
|
* Depending on which values are tracked, ref and/or target, returns the
|
|
75
90
|
* appropriate function to handle the scenario.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright: 2021 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
|
+
'use strict';
|
|
16
|
+
const { propagate } = require('../template-escape');
|
|
17
|
+
|
|
18
|
+
function handler(data) {
|
|
19
|
+
propagate(data, 'html-encoded', 'mustache.escape');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports.handle = handler;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright: 2021 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
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const tracker = require('../../tracker');
|
|
18
|
+
const TagRange = require('../models/tag-range');
|
|
19
|
+
const { CallContext, PropagationEvent, Signature } = require('../models');
|
|
20
|
+
|
|
21
|
+
function getEscapedTagRanges(input, result, start, stop, tag) {
|
|
22
|
+
const textArr = input.split('').slice(start, stop + 1);
|
|
23
|
+
const escapedArr = result.split('');
|
|
24
|
+
const overlap = textArr.filter((x) => {
|
|
25
|
+
if (escapedArr.includes(x)) {
|
|
26
|
+
return x;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (overlap.length === 0) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const newTagRanges = [];
|
|
33
|
+
let firstIndex = escapedArr.indexOf(overlap[0]);
|
|
34
|
+
let currIndex = firstIndex;
|
|
35
|
+
let nextIndex;
|
|
36
|
+
for (let i = 1; i < overlap.length; i++) {
|
|
37
|
+
nextIndex = escapedArr.indexOf(overlap[i], currIndex + 1);
|
|
38
|
+
if (nextIndex !== currIndex + 1) {
|
|
39
|
+
newTagRanges.push(new TagRange(firstIndex, currIndex, tag));
|
|
40
|
+
firstIndex = nextIndex;
|
|
41
|
+
}
|
|
42
|
+
if (i === overlap.length - 1) {
|
|
43
|
+
newTagRanges.push(new TagRange(firstIndex, nextIndex, tag));
|
|
44
|
+
}
|
|
45
|
+
currIndex = nextIndex;
|
|
46
|
+
}
|
|
47
|
+
return newTagRanges;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function propagator(data, tagName, signatureName) {
|
|
51
|
+
const input = data.args[0];
|
|
52
|
+
|
|
53
|
+
if (!input || !tracker.getData(input).tracked) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// adjust tag ranges
|
|
58
|
+
const tagRanges = [];
|
|
59
|
+
tracker.getData(input).tagRanges.forEach((range) => {
|
|
60
|
+
const { start, stop, tag } = range;
|
|
61
|
+
tagRanges.push(
|
|
62
|
+
...getEscapedTagRanges(input, data.result, start, stop, tag)
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
tagRanges.push(new TagRange(0, data.result.length - 1, tagName));
|
|
66
|
+
const result = tracker.track(data.result);
|
|
67
|
+
const trackData = tracker.getData(result);
|
|
68
|
+
trackData.tagRanges = tagRanges;
|
|
69
|
+
trackData.event = new PropagationEvent({
|
|
70
|
+
context: new CallContext({
|
|
71
|
+
...data,
|
|
72
|
+
obj: null
|
|
73
|
+
}),
|
|
74
|
+
parents: [trackData.event],
|
|
75
|
+
signature: new Signature(signatureName),
|
|
76
|
+
source: 'P',
|
|
77
|
+
target: 'R',
|
|
78
|
+
tagRanges,
|
|
79
|
+
tags: tagName
|
|
80
|
+
});
|
|
81
|
+
data.result = result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports.propagate = propagator;
|
|
@@ -21,10 +21,9 @@ const tracker = require('../../tracker.js');
|
|
|
21
21
|
|
|
22
22
|
const { PropagationEvent, Signature, CallContext } = require('../models');
|
|
23
23
|
const { isString } = require('../../util/is-string');
|
|
24
|
+
const injections = require('../../core/rewrite/injections');
|
|
24
25
|
|
|
25
|
-
const ContrastMethods =
|
|
26
|
-
'ContrastMethods'
|
|
27
|
-
);
|
|
26
|
+
const ContrastMethods = injections.get('ContrastMethods');
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
29
|
* In order to propagate through template literals, we leverage rewriting to
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright: 2021 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
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const patcher = require('../../hooks/patcher');
|
|
18
|
+
const { PATCH_TYPES } = require('../../constants');
|
|
19
|
+
const moduleHook = require('../../hooks/require');
|
|
20
|
+
const { CallContext, Signature } = require('../models');
|
|
21
|
+
const {
|
|
22
|
+
RULES: { REFLECTED_XSS }
|
|
23
|
+
} = require('../../constants');
|
|
24
|
+
|
|
25
|
+
const signature = new Signature({
|
|
26
|
+
moduleName: 'http.ClientResponse',
|
|
27
|
+
methodName: 'write',
|
|
28
|
+
isModule: true
|
|
29
|
+
});
|
|
30
|
+
const moduleName = 'dustjs-linkedin';
|
|
31
|
+
|
|
32
|
+
class Handler {
|
|
33
|
+
constructor({ report, isVulnerable, requiredTags, xss: { disallowedTags } }) {
|
|
34
|
+
this._isVulnerable = isVulnerable;
|
|
35
|
+
this.report = report;
|
|
36
|
+
this.requiredTags = requiredTags;
|
|
37
|
+
this.disallowedTags = disallowedTags;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
isVulnerable(input) {
|
|
41
|
+
const { requiredTags, disallowedTags } = this;
|
|
42
|
+
const ret = this._isVulnerable({
|
|
43
|
+
input,
|
|
44
|
+
ruleId: REFLECTED_XSS,
|
|
45
|
+
disallowedTags,
|
|
46
|
+
requiredTags
|
|
47
|
+
});
|
|
48
|
+
return ret;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
handle() {
|
|
52
|
+
moduleHook.resolve({ name: moduleName }, (dust) =>
|
|
53
|
+
this.handleRequire(dust)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getType(obj) {
|
|
58
|
+
return obj && obj.constructor && obj.constructor.name;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
isResponseType(typeName) {
|
|
62
|
+
return typeName === 'ServerResponse';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
patchDust(dust) {
|
|
66
|
+
const self = this;
|
|
67
|
+
patcher.patch(dust, 'stream', {
|
|
68
|
+
name: moduleName,
|
|
69
|
+
patchType: PATCH_TYPES.ASSESS_SINK,
|
|
70
|
+
alwaysRun: true,
|
|
71
|
+
post(data) {
|
|
72
|
+
self.patchStream(data.result);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Patch the pipe method and check wether the stream is targeting a
|
|
79
|
+
* response instance. If so, add the instance to the set of streams
|
|
80
|
+
* we should follow.
|
|
81
|
+
*/
|
|
82
|
+
patchStream(streamInstance) {
|
|
83
|
+
const self = this;
|
|
84
|
+
patcher.patch(streamInstance, 'pipe', {
|
|
85
|
+
name: 'dust.Stream.prototype',
|
|
86
|
+
patchType: PATCH_TYPES.ASSESS_SINK,
|
|
87
|
+
alwaysRun: true,
|
|
88
|
+
pre({ args: [maybeRes] }) {
|
|
89
|
+
self.patchStreamTarget(maybeRes);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
patchStreamTarget(target) {
|
|
95
|
+
const self = this;
|
|
96
|
+
const typeName = self.getType(target);
|
|
97
|
+
|
|
98
|
+
if (self.isResponseType(typeName)) {
|
|
99
|
+
patcher.patch(target, 'write', {
|
|
100
|
+
name: 'dust.pipeTarget',
|
|
101
|
+
patchType: PATCH_TYPES.ASSESS_SINK,
|
|
102
|
+
alwaysRun: true,
|
|
103
|
+
post({ args: [str], hooked }) {
|
|
104
|
+
if (self.isVulnerable(str)) {
|
|
105
|
+
self.report({
|
|
106
|
+
ruleId: REFLECTED_XSS,
|
|
107
|
+
signature,
|
|
108
|
+
input: str,
|
|
109
|
+
ctxt: new CallContext({
|
|
110
|
+
obj: typeName,
|
|
111
|
+
args: [str],
|
|
112
|
+
result: str,
|
|
113
|
+
stackOpts: {
|
|
114
|
+
constructorOpt: hooked
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
handleRequire(dust) {
|
|
125
|
+
this.patchDust(dust);
|
|
126
|
+
return dust;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = ({ common }) => new Handler(common);
|
|
131
|
+
module.exports.Handler = Handler;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright: 2021 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
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const agentEmitter = require('../../agent-emitter');
|
|
18
|
+
const { PATCH_TYPES } = require('../../constants');
|
|
19
|
+
const ModuleHook = require('../../hooks/require');
|
|
20
|
+
const patcher = require('../../hooks/patcher');
|
|
21
|
+
const logger = require('../logger')('contrast:arch-component');
|
|
22
|
+
|
|
23
|
+
ModuleHook.resolve({ name: 'rethinkdb' }, (rethinkdb) => {
|
|
24
|
+
patcher.patch(rethinkdb, 'connect', {
|
|
25
|
+
name: 'rethinkdb.arch_component',
|
|
26
|
+
patchType: PATCH_TYPES.ARCH_COMPONENT,
|
|
27
|
+
alwaysRun: true,
|
|
28
|
+
post(ctx) {
|
|
29
|
+
ctx.result
|
|
30
|
+
.then((res) => {
|
|
31
|
+
if (res.open) {
|
|
32
|
+
const url =
|
|
33
|
+
res.host == 'localhost'
|
|
34
|
+
? 'http://localhost'
|
|
35
|
+
: new URL(res.host).toString();
|
|
36
|
+
agentEmitter.emit('architectureComponent', {
|
|
37
|
+
vendor: 'RethinkDB',
|
|
38
|
+
url,
|
|
39
|
+
remotePort: res.port
|
|
40
|
+
});
|
|
41
|
+
} else {
|
|
42
|
+
logger.warn('unable to open RethinkDB connection');
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
.catch((err) => {
|
|
46
|
+
logger.warn(
|
|
47
|
+
'unable to report RethinkDB architecture component\n%o',
|
|
48
|
+
err
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
package/lib/hooks/patcher.js
CHANGED
|
@@ -97,12 +97,7 @@ Function.prototype._contrast_toString = function() {
|
|
|
97
97
|
return Reflect.apply(functionToString, this, arguments);
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
function runHooks(type,
|
|
101
|
-
const fnHooks = hooks.get(fn);
|
|
102
|
-
if (!fnHooks) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
100
|
+
function runHooks(type, data, thisTarget, fnHooks) {
|
|
106
101
|
fnHooks.forEach((hook, key) => {
|
|
107
102
|
if (hook[type]) {
|
|
108
103
|
hook[type].apply(thisTarget, [data]);
|
|
@@ -185,11 +180,17 @@ function runOriginalFunction(fn, { args, name }, target) {
|
|
|
185
180
|
return fn.apply(this, args);
|
|
186
181
|
}
|
|
187
182
|
|
|
188
|
-
|
|
183
|
+
const runWrapper =
|
|
184
|
+
!process.hrtime.bigint || !perfLoggingEnabled
|
|
185
|
+
? runWithoutRecordingTime
|
|
186
|
+
: runAndRecordTime;
|
|
187
|
+
|
|
188
|
+
function runWithoutRecordingTime(func) {
|
|
189
|
+
return () => func();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function runAndRecordTime(func, funcKey, type) {
|
|
189
193
|
return () => {
|
|
190
|
-
if (!process.hrtime.bigint || !perfLoggingEnabled) {
|
|
191
|
-
return func();
|
|
192
|
-
}
|
|
193
194
|
const start = process.hrtime.bigint();
|
|
194
195
|
const rv = func();
|
|
195
196
|
perfLogger[type](funcKey, Number(process.hrtime.bigint() - start));
|
|
@@ -217,6 +218,7 @@ function hookFunction(fn, options) {
|
|
|
217
218
|
const { funcKey } = options;
|
|
218
219
|
logger.trace(`hook ${funcKey}`);
|
|
219
220
|
|
|
221
|
+
// eslint-disable-next-line complexity
|
|
220
222
|
function hooked(...args) {
|
|
221
223
|
const target = new.target;
|
|
222
224
|
|
|
@@ -247,36 +249,59 @@ function hookFunction(fn, options) {
|
|
|
247
249
|
perfLogger.logEvent(funcKey);
|
|
248
250
|
}
|
|
249
251
|
|
|
250
|
-
|
|
252
|
+
let hasPreHook, hasPostHook;
|
|
253
|
+
const fnHooks = hooks && hooks.get(hooked);
|
|
254
|
+
|
|
255
|
+
// If there's a pre event run it in a no instrumentation scope
|
|
251
256
|
// this will prevent other instrumentation from running
|
|
252
257
|
// within the pre function
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
`${funcKey} pre`
|
|
260
|
-
);
|
|
258
|
+
if (fnHooks) {
|
|
259
|
+
for (const storedHooks of fnHooks.values()) {
|
|
260
|
+
hasPreHook = Boolean(hasPreHook || (storedHooks && storedHooks.pre));
|
|
261
|
+
hasPostHook = Boolean(hasPostHook || (storedHooks && storedHooks.post));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
261
264
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
if (hasPreHook) {
|
|
266
|
+
Scopes.runInNoInstrumentationScope(
|
|
267
|
+
runWrapper(() => runHooks('pre', data, this, fnHooks), funcKey, 'pre'),
|
|
268
|
+
`${funcKey} pre`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
268
271
|
|
|
269
|
-
// Run
|
|
272
|
+
// Run original function in scope that was passed in
|
|
273
|
+
// or just run the original function when the passed scope is undefined
|
|
274
|
+
if (options.scope) {
|
|
275
|
+
data.result = Scopes.runIn(
|
|
276
|
+
options.scope,
|
|
277
|
+
runWrapper(
|
|
278
|
+
() => getResult.call(this, fn, data, target),
|
|
279
|
+
funcKey,
|
|
280
|
+
'orig'
|
|
281
|
+
),
|
|
282
|
+
funcKey
|
|
283
|
+
);
|
|
284
|
+
} else {
|
|
285
|
+
data.result = runWrapper(
|
|
286
|
+
() => getResult.call(this, fn, data, target),
|
|
287
|
+
funcKey,
|
|
288
|
+
'orig'
|
|
289
|
+
)();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// If there's a post event run it in a no instrumentation scope
|
|
270
293
|
// this will prevent other instrumentation from running
|
|
271
294
|
// within the post function
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
295
|
+
if (hasPostHook) {
|
|
296
|
+
Scopes.runInNoInstrumentationScope(
|
|
297
|
+
runWrapper(
|
|
298
|
+
() => runHooks('post', data, this, fnHooks),
|
|
299
|
+
funcKey,
|
|
300
|
+
'post'
|
|
301
|
+
),
|
|
302
|
+
`${funcKey} post`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
280
305
|
|
|
281
306
|
return data.result;
|
|
282
307
|
}
|
|
@@ -436,8 +461,7 @@ function hook(obj, prop, opts) {
|
|
|
436
461
|
}
|
|
437
462
|
} catch (err) {
|
|
438
463
|
logger.info(
|
|
439
|
-
`unable to patch unconfigurable property ${opts.name}:${prop}
|
|
440
|
-
`,
|
|
464
|
+
`unable to patch unconfigurable property ${opts.name}:${prop}`,
|
|
441
465
|
err
|
|
442
466
|
);
|
|
443
467
|
}
|
|
@@ -543,7 +567,6 @@ function unwrap(fn) {
|
|
|
543
567
|
module.exports = {
|
|
544
568
|
patch,
|
|
545
569
|
preempt,
|
|
546
|
-
recordTime,
|
|
547
570
|
resetInstrumentation,
|
|
548
571
|
unpatch,
|
|
549
572
|
unwrap
|
package/lib/util/source-map.js
CHANGED
|
@@ -133,7 +133,7 @@ class SourceMapUtility {
|
|
|
133
133
|
const { line, source } = consumer.originalPositionFor({
|
|
134
134
|
line: lineNumber,
|
|
135
135
|
column
|
|
136
|
-
});
|
|
136
|
+
}) || { line: undefined, source: undefined };
|
|
137
137
|
|
|
138
138
|
if (line) lineNumber = line;
|
|
139
139
|
if (source) file = this.replaceSource(file, source);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/agent",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"description": "Node.js security instrumentation by Contrast Security",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"security",
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"proxyquire": "^2.1.0",
|
|
166
166
|
"qs": "^6.9.4",
|
|
167
167
|
"rethinkdb": "file:test/mock/rethinkdb",
|
|
168
|
-
"sequelize": "^6.
|
|
168
|
+
"sequelize": "^6.11.0",
|
|
169
169
|
"shellcheck": "^1.0.0",
|
|
170
170
|
"sinon": "^7.2.2",
|
|
171
171
|
"sinon-chai": "^3.3.0",
|