@contrast/assess 1.5.0 → 1.6.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/event-factory.js +10 -5
- package/lib/dataflow/propagation/install/decode-uri-component.js +5 -2
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +5 -2
- package/lib/dataflow/propagation/install/escape-html.js +7 -4
- package/lib/dataflow/propagation/install/escape.js +5 -2
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +5 -2
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +5 -2
- package/lib/dataflow/propagation/install/querystring/parse.js +8 -3
- package/lib/dataflow/propagation/install/string/replace.js +5 -1
- package/lib/dataflow/propagation/install/unescape.js +5 -2
- package/lib/dataflow/propagation/install/validator/methods.js +60 -51
- package/lib/dataflow/sinks/index.js +4 -0
- package/lib/dataflow/sinks/install/child-process.js +150 -13
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +19 -8
- package/lib/dataflow/sinks/install/fs.js +136 -0
- package/lib/dataflow/sinks/install/http.js +27 -13
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +17 -8
- package/lib/dataflow/sinks/install/marsdb.js +135 -0
- package/lib/dataflow/sinks/install/mongodb.js +205 -0
- package/lib/dataflow/sinks/install/mssql.js +11 -7
- package/lib/dataflow/sinks/install/mysql.js +122 -0
- package/lib/dataflow/sinks/install/postgres.js +13 -12
- package/lib/dataflow/sinks/install/sqlite3.js +12 -7
- package/lib/dataflow/sources/handler.js +10 -9
- package/package.json +2 -2
|
@@ -166,17 +166,22 @@ module.exports = function(core) {
|
|
|
166
166
|
|
|
167
167
|
const sourceContext = sources.getStore()?.assess;
|
|
168
168
|
if (!sourceContext) {
|
|
169
|
-
logger.debug('
|
|
169
|
+
logger.debug('no sourceContext found during sink event creation');
|
|
170
170
|
return null;
|
|
171
171
|
}
|
|
172
|
-
|
|
173
172
|
const signature = signatures.get(name);
|
|
173
|
+
if (!signature) {
|
|
174
|
+
logger.debug({ name }, 'no signature found for sink event name');
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
if (!history.length) {
|
|
178
|
+
logger.debug({ data }, 'empty history for sink event');
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
174
181
|
if (
|
|
175
|
-
!signature ||
|
|
176
|
-
!history.length ||
|
|
177
182
|
((source && !source.match(annotationRegExp)) || (!source && !signature.source))
|
|
178
183
|
) {
|
|
179
|
-
logger.debug({ data }, '
|
|
184
|
+
logger.debug({ data }, 'malformed or missing sink event source field');
|
|
180
185
|
return null;
|
|
181
186
|
}
|
|
182
187
|
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const {
|
|
19
|
+
DataflowTag: { URL_ENCODED }
|
|
20
|
+
} = require('@contrast/common');
|
|
18
21
|
const {
|
|
19
22
|
createFullLengthCopyTags
|
|
20
23
|
} = require('../../tag-utils');
|
|
@@ -47,7 +50,7 @@ module.exports = function(core) {
|
|
|
47
50
|
// the result is not tracked, so we don't need to check for that
|
|
48
51
|
const history = [argInfo];
|
|
49
52
|
const newTags = createFullLengthCopyTags(argInfo.tags, result.length);
|
|
50
|
-
delete newTags[
|
|
53
|
+
delete newTags[URL_ENCODED];
|
|
51
54
|
|
|
52
55
|
if (!Object.keys(newTags).length) return;
|
|
53
56
|
|
|
@@ -64,7 +67,7 @@ module.exports = function(core) {
|
|
|
64
67
|
args: [{ value: argInfo.value, tracked: true }],
|
|
65
68
|
tags: newTags,
|
|
66
69
|
history,
|
|
67
|
-
removedTags: [
|
|
70
|
+
removedTags: [URL_ENCODED],
|
|
68
71
|
stacktraceOpts: {
|
|
69
72
|
constructorOpt: hooked,
|
|
70
73
|
prependFrames: [orig]
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const {
|
|
19
|
+
DataflowTag: { WEAK_URL_ENCODED }
|
|
20
|
+
} = require('@contrast/common');
|
|
18
21
|
const {
|
|
19
22
|
createFullLengthCopyTags
|
|
20
23
|
} = require('../../../tag-utils');
|
|
@@ -48,7 +51,7 @@ module.exports = function(core) {
|
|
|
48
51
|
const history = [argInfo];
|
|
49
52
|
const newTags = createFullLengthCopyTags(argInfo.tags, result.length);
|
|
50
53
|
|
|
51
|
-
newTags[
|
|
54
|
+
newTags[WEAK_URL_ENCODED] = [0, result.length - 1];
|
|
52
55
|
|
|
53
56
|
const event = createPropagationEvent({
|
|
54
57
|
name: 'ejs.utils.escapeXML',
|
|
@@ -62,7 +65,7 @@ module.exports = function(core) {
|
|
|
62
65
|
},
|
|
63
66
|
args: [{ value: argInfo.value, tracked: true }],
|
|
64
67
|
tags: newTags,
|
|
65
|
-
addedTags: [
|
|
68
|
+
addedTags: [WEAK_URL_ENCODED],
|
|
66
69
|
history,
|
|
67
70
|
stacktraceOpts: {
|
|
68
71
|
constructorOpt: hooked,
|
|
@@ -15,10 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const {
|
|
19
|
+
DataflowTag: { HTML_ENCODED }
|
|
20
|
+
} = require('@contrast/common');
|
|
18
21
|
const {
|
|
19
22
|
createFullLengthCopyTags
|
|
20
23
|
} = require('../../tag-utils');
|
|
21
|
-
const { patchType
|
|
24
|
+
const { patchType } = require('../common');
|
|
22
25
|
|
|
23
26
|
module.exports = function(core) {
|
|
24
27
|
const {
|
|
@@ -48,12 +51,12 @@ module.exports = function(core) {
|
|
|
48
51
|
const history = [argInfo];
|
|
49
52
|
const newTags = createFullLengthCopyTags(argInfo.tags, result.length);
|
|
50
53
|
|
|
51
|
-
newTags[
|
|
54
|
+
newTags[HTML_ENCODED] = [0, result.length - 1];
|
|
52
55
|
|
|
53
56
|
const event = createPropagationEvent({
|
|
54
57
|
name: 'escape-html',
|
|
55
58
|
object: {
|
|
56
|
-
value:
|
|
59
|
+
value: 'escape-html',
|
|
57
60
|
tracked: false
|
|
58
61
|
},
|
|
59
62
|
result: {
|
|
@@ -63,7 +66,7 @@ module.exports = function(core) {
|
|
|
63
66
|
args: [{ value: argInfo.value, tracked: true }],
|
|
64
67
|
tags: newTags,
|
|
65
68
|
history,
|
|
66
|
-
addedTags: [
|
|
69
|
+
addedTags: [HTML_ENCODED],
|
|
67
70
|
stacktraceOpts: {
|
|
68
71
|
constructorOpt: hooked,
|
|
69
72
|
prependFrames: [orig]
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const {
|
|
19
|
+
DataflowTag: { WEAK_URL_ENCODED }
|
|
20
|
+
} = require('@contrast/common');
|
|
18
21
|
const {
|
|
19
22
|
createFullLengthCopyTags
|
|
20
23
|
} = require('../../tag-utils');
|
|
@@ -46,7 +49,7 @@ module.exports = function(core) {
|
|
|
46
49
|
const history = [argInfo];
|
|
47
50
|
const newTags = createFullLengthCopyTags(argInfo.tags, result.length);
|
|
48
51
|
|
|
49
|
-
newTags[
|
|
52
|
+
newTags[WEAK_URL_ENCODED] = [0, result.length - 1];
|
|
50
53
|
|
|
51
54
|
const event = createPropagationEvent({
|
|
52
55
|
name: 'global.escape',
|
|
@@ -61,7 +64,7 @@ module.exports = function(core) {
|
|
|
61
64
|
args: [{ value: argInfo.value, tracked: true }],
|
|
62
65
|
tags: newTags,
|
|
63
66
|
history,
|
|
64
|
-
addedTags: [
|
|
67
|
+
addedTags: [WEAK_URL_ENCODED],
|
|
65
68
|
stacktraceOpts: {
|
|
66
69
|
constructorOpt: hooked,
|
|
67
70
|
prependFrames: [orig]
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const {
|
|
19
|
+
DataflowTag: { HTML_ENCODED }
|
|
20
|
+
} = require('@contrast/common');
|
|
18
21
|
const {
|
|
19
22
|
createFullLengthCopyTags
|
|
20
23
|
} = require('../../tag-utils');
|
|
@@ -48,7 +51,7 @@ module.exports = function(core) {
|
|
|
48
51
|
const history = [argInfo];
|
|
49
52
|
const newTags = createFullLengthCopyTags(argInfo.tags, result.length);
|
|
50
53
|
|
|
51
|
-
newTags[
|
|
54
|
+
newTags[HTML_ENCODED] = [0, result.length - 1];
|
|
52
55
|
|
|
53
56
|
const event = createPropagationEvent({
|
|
54
57
|
name: 'handlebars.Utils.escapeExpression',
|
|
@@ -62,7 +65,7 @@ module.exports = function(core) {
|
|
|
62
65
|
},
|
|
63
66
|
args: [{ value: argInfo.value, tracked: true }],
|
|
64
67
|
tags: newTags,
|
|
65
|
-
addedTags: [
|
|
68
|
+
addedTags: [HTML_ENCODED],
|
|
66
69
|
history,
|
|
67
70
|
stacktraceOpts: {
|
|
68
71
|
constructorOpt: hooked,
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const {
|
|
19
|
+
DataflowTag: { SQL_ENCODED }
|
|
20
|
+
} = require('@contrast/common');
|
|
18
21
|
const {
|
|
19
22
|
createFullLengthCopyTags
|
|
20
23
|
} = require('../../tag-utils');
|
|
@@ -43,7 +46,7 @@ module.exports = function(core) {
|
|
|
43
46
|
const history = [argInfo];
|
|
44
47
|
const newTags = createFullLengthCopyTags(argInfo.tags, result.length);
|
|
45
48
|
|
|
46
|
-
newTags[
|
|
49
|
+
newTags[SQL_ENCODED] = [0, result.length - 1];
|
|
47
50
|
|
|
48
51
|
const event = createPropagationEvent({
|
|
49
52
|
name: eventName,
|
|
@@ -57,7 +60,7 @@ module.exports = function(core) {
|
|
|
57
60
|
},
|
|
58
61
|
args: [{ value: argInfo.value, tracked: true }],
|
|
59
62
|
tags: newTags,
|
|
60
|
-
addedTags: [
|
|
63
|
+
addedTags: [SQL_ENCODED],
|
|
61
64
|
history,
|
|
62
65
|
stacktraceOpts: {
|
|
63
66
|
constructorOpt: hooked,
|
|
@@ -14,8 +14,13 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
|
+
|
|
17
18
|
const util = require('util');
|
|
18
19
|
const querystring = require('querystring');
|
|
20
|
+
const {
|
|
21
|
+
DataflowTag: { URL_ENCODED }
|
|
22
|
+
} = require('@contrast/common');
|
|
23
|
+
|
|
19
24
|
const { patchType } = require('../../common');
|
|
20
25
|
const { createSubsetTags, createAppendTags } = require('../../../tag-utils');
|
|
21
26
|
|
|
@@ -73,9 +78,9 @@ module.exports = function(core) {
|
|
|
73
78
|
event.tags = createAppendTags(event.tags, resultInfo.tags, 0);
|
|
74
79
|
Object.assign(resultInfo, event);
|
|
75
80
|
}
|
|
76
|
-
if (event.tags[
|
|
77
|
-
delete event.tags[
|
|
78
|
-
event.removedTags = [
|
|
81
|
+
if (event.tags[URL_ENCODED]) {
|
|
82
|
+
delete event.tags[URL_ENCODED];
|
|
83
|
+
event.removedTags = [URL_ENCODED];
|
|
79
84
|
}
|
|
80
85
|
const { extern } = resultInfo || tracker.track(result, event);
|
|
81
86
|
if (extern) {
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const {
|
|
19
|
+
DataflowTag: { UNTRUSTED }
|
|
20
|
+
} = require('@contrast/common');
|
|
18
21
|
const { patchType } = require('../../common');
|
|
19
22
|
const { createSubsetTags, createAppendTags } = require('../../../tag-utils');
|
|
20
23
|
|
|
@@ -148,7 +151,8 @@ module.exports = function(core) {
|
|
|
148
151
|
!sources.getStore()?.assess ||
|
|
149
152
|
instrumentation.isLocked() ||
|
|
150
153
|
!data.result ||
|
|
151
|
-
|
|
154
|
+
// todo: can we reuse this optimization in other propagators? e.g those performing substring-like operations
|
|
155
|
+
!data._accumTags?.[UNTRUSTED]
|
|
152
156
|
) return;
|
|
153
157
|
|
|
154
158
|
const { _replacementInfo, obj, args, result, hooked, orig } = data;
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const {
|
|
19
|
+
DataflowTag: { WEAK_URL_ENCODED }
|
|
20
|
+
} = require('@contrast/common');
|
|
18
21
|
const {
|
|
19
22
|
createFullLengthCopyTags
|
|
20
23
|
} = require('../../tag-utils');
|
|
@@ -45,7 +48,7 @@ module.exports = function(core) {
|
|
|
45
48
|
const resultInfo = tracker.getData(result);
|
|
46
49
|
const history = [argInfo];
|
|
47
50
|
const newTags = createFullLengthCopyTags(argInfo.tags, result.length);
|
|
48
|
-
delete newTags[
|
|
51
|
+
delete newTags[WEAK_URL_ENCODED];
|
|
49
52
|
|
|
50
53
|
if (!Object.keys(newTags).length) return;
|
|
51
54
|
|
|
@@ -62,7 +65,7 @@ module.exports = function(core) {
|
|
|
62
65
|
args: [{ value: argInfo.value, tracked: true }],
|
|
63
66
|
tags: newTags,
|
|
64
67
|
history,
|
|
65
|
-
removedTags: [
|
|
68
|
+
removedTags: [WEAK_URL_ENCODED],
|
|
66
69
|
stacktraceOpts: {
|
|
67
70
|
constructorOpt: hooked,
|
|
68
71
|
prependFrames: [orig]
|
|
@@ -15,57 +15,66 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const {
|
|
19
|
+
DataflowTag: {
|
|
20
|
+
ALPHANUM_SPACE_HYPHEN,
|
|
21
|
+
CUSTOM_VALIDATED,
|
|
22
|
+
HTML_ENCODED,
|
|
23
|
+
LIMITED_CHARS,
|
|
24
|
+
}
|
|
25
|
+
} = require('@contrast/common');
|
|
26
|
+
|
|
18
27
|
module.exports = {
|
|
19
28
|
validators: {
|
|
20
|
-
isAfter:
|
|
21
|
-
isAlpha:
|
|
22
|
-
isAlphanumeric:
|
|
23
|
-
isBase32:
|
|
24
|
-
isBase58:
|
|
25
|
-
isBase64:
|
|
26
|
-
isBefore:
|
|
27
|
-
isBIC:
|
|
28
|
-
isBoolean:
|
|
29
|
-
isBtcAddress:
|
|
30
|
-
isCreditCard:
|
|
31
|
-
isDate:
|
|
32
|
-
isDecimal:
|
|
33
|
-
isEAN:
|
|
34
|
-
isEthereumAddress:
|
|
35
|
-
isFloat:
|
|
36
|
-
isHash:
|
|
37
|
-
isHexadecimal:
|
|
38
|
-
isHexColor:
|
|
39
|
-
isHSL:
|
|
40
|
-
isIBAN:
|
|
41
|
-
isIdentityCard:
|
|
42
|
-
isIMEI:
|
|
43
|
-
isInt:
|
|
44
|
-
isIP:
|
|
45
|
-
isIPRange:
|
|
46
|
-
isISBN:
|
|
47
|
-
isISIN:
|
|
48
|
-
isISO8601:
|
|
49
|
-
isISO31661Alpha2:
|
|
50
|
-
isISO31661Alpha3:
|
|
51
|
-
isISRC:
|
|
52
|
-
isISSN:
|
|
53
|
-
isJWT:
|
|
54
|
-
isLatLong:
|
|
55
|
-
isLicensePlate:
|
|
56
|
-
isMACAddress:
|
|
57
|
-
isMagnetURI:
|
|
58
|
-
isMD5:
|
|
59
|
-
isMobilePhone:
|
|
60
|
-
isNumeric:
|
|
61
|
-
isOctal:
|
|
62
|
-
isPassportNumber:
|
|
63
|
-
isPostalCode:
|
|
64
|
-
isSemVer:
|
|
65
|
-
isTaxID:
|
|
66
|
-
isUUID:
|
|
67
|
-
isVAT:
|
|
68
|
-
matches:
|
|
29
|
+
isAfter: ALPHANUM_SPACE_HYPHEN,
|
|
30
|
+
isAlpha: ALPHANUM_SPACE_HYPHEN,
|
|
31
|
+
isAlphanumeric: ALPHANUM_SPACE_HYPHEN,
|
|
32
|
+
isBase32: ALPHANUM_SPACE_HYPHEN,
|
|
33
|
+
isBase58: ALPHANUM_SPACE_HYPHEN,
|
|
34
|
+
isBase64: ALPHANUM_SPACE_HYPHEN,
|
|
35
|
+
isBefore: ALPHANUM_SPACE_HYPHEN,
|
|
36
|
+
isBIC: ALPHANUM_SPACE_HYPHEN,
|
|
37
|
+
isBoolean: LIMITED_CHARS,
|
|
38
|
+
isBtcAddress: ALPHANUM_SPACE_HYPHEN,
|
|
39
|
+
isCreditCard: LIMITED_CHARS,
|
|
40
|
+
isDate: LIMITED_CHARS,
|
|
41
|
+
isDecimal: LIMITED_CHARS,
|
|
42
|
+
isEAN: LIMITED_CHARS,
|
|
43
|
+
isEthereumAddress: ALPHANUM_SPACE_HYPHEN,
|
|
44
|
+
isFloat: LIMITED_CHARS,
|
|
45
|
+
isHash: ALPHANUM_SPACE_HYPHEN,
|
|
46
|
+
isHexadecimal: ALPHANUM_SPACE_HYPHEN,
|
|
47
|
+
isHexColor: ALPHANUM_SPACE_HYPHEN,
|
|
48
|
+
isHSL: ALPHANUM_SPACE_HYPHEN,
|
|
49
|
+
isIBAN: LIMITED_CHARS,
|
|
50
|
+
isIdentityCard: ALPHANUM_SPACE_HYPHEN,
|
|
51
|
+
isIMEI: LIMITED_CHARS,
|
|
52
|
+
isInt: LIMITED_CHARS,
|
|
53
|
+
isIP: LIMITED_CHARS,
|
|
54
|
+
isIPRange: LIMITED_CHARS,
|
|
55
|
+
isISBN: LIMITED_CHARS,
|
|
56
|
+
isISIN: LIMITED_CHARS,
|
|
57
|
+
isISO8601: ALPHANUM_SPACE_HYPHEN,
|
|
58
|
+
isISO31661Alpha2: ALPHANUM_SPACE_HYPHEN,
|
|
59
|
+
isISO31661Alpha3: ALPHANUM_SPACE_HYPHEN,
|
|
60
|
+
isISRC: ALPHANUM_SPACE_HYPHEN,
|
|
61
|
+
isISSN: LIMITED_CHARS,
|
|
62
|
+
isJWT: ALPHANUM_SPACE_HYPHEN,
|
|
63
|
+
isLatLong: LIMITED_CHARS,
|
|
64
|
+
isLicensePlate: ALPHANUM_SPACE_HYPHEN,
|
|
65
|
+
isMACAddress: ALPHANUM_SPACE_HYPHEN,
|
|
66
|
+
isMagnetURI: ALPHANUM_SPACE_HYPHEN,
|
|
67
|
+
isMD5: ALPHANUM_SPACE_HYPHEN,
|
|
68
|
+
isMobilePhone: LIMITED_CHARS,
|
|
69
|
+
isNumeric: LIMITED_CHARS,
|
|
70
|
+
isOctal: ALPHANUM_SPACE_HYPHEN,
|
|
71
|
+
isPassportNumber: LIMITED_CHARS,
|
|
72
|
+
isPostalCode: LIMITED_CHARS,
|
|
73
|
+
isSemVer: LIMITED_CHARS,
|
|
74
|
+
isTaxID: LIMITED_CHARS,
|
|
75
|
+
isUUID: ALPHANUM_SPACE_HYPHEN,
|
|
76
|
+
isVAT: ALPHANUM_SPACE_HYPHEN,
|
|
77
|
+
matches: CUSTOM_VALIDATED,
|
|
69
78
|
},
|
|
70
79
|
untrackers: [
|
|
71
80
|
'equals',
|
|
@@ -74,9 +83,9 @@ module.exports = {
|
|
|
74
83
|
'isRFC3339'
|
|
75
84
|
],
|
|
76
85
|
sanitizers: {
|
|
77
|
-
escape:
|
|
86
|
+
escape: HTML_ENCODED
|
|
78
87
|
},
|
|
79
88
|
custom: {
|
|
80
|
-
isEmail:
|
|
89
|
+
isEmail: LIMITED_CHARS
|
|
81
90
|
}
|
|
82
91
|
};
|
|
@@ -41,10 +41,14 @@ module.exports = function (core) {
|
|
|
41
41
|
require('./install/fastify')(core);
|
|
42
42
|
require('./install/koa')(core);
|
|
43
43
|
require('./install/child-process')(core);
|
|
44
|
+
require('./install/fs')(core);
|
|
44
45
|
require('./install/http')(core);
|
|
46
|
+
require('./install/mongodb')(core);
|
|
45
47
|
require('./install/mssql')(core);
|
|
48
|
+
require('./install/mysql')(core);
|
|
46
49
|
require('./install/postgres')(core);
|
|
47
50
|
require('./install/sqlite3')(core);
|
|
51
|
+
require('./install/marsdb')(core);
|
|
48
52
|
|
|
49
53
|
sinks.install = function() {
|
|
50
54
|
callChildComponentMethodsSync(core.assess.dataflow.sinks, 'install');
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
|
+
const {
|
|
18
|
+
DataflowTag: { UNTRUSTED }
|
|
19
|
+
} = require('@contrast/common');
|
|
20
|
+
|
|
17
21
|
const { patchType } = require('../common');
|
|
18
|
-
const { Rule, isString } = require('@contrast/common');
|
|
22
|
+
const { Rule, isString, inspect } = require('@contrast/common');
|
|
19
23
|
|
|
20
24
|
module.exports = function (core) {
|
|
21
25
|
const {
|
|
@@ -31,14 +35,14 @@ module.exports = function (core) {
|
|
|
31
35
|
},
|
|
32
36
|
} = core;
|
|
33
37
|
|
|
34
|
-
const
|
|
35
|
-
const store = sources.getStore()?.assess;
|
|
36
|
-
if (!store || !data.args[0] || !isString(data.args[0])) return;
|
|
38
|
+
const safeTags = [];
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
function commandCheck(name, command, secondArg, thirdArg, hooked) {
|
|
41
|
+
const strInfo = tracker.getData(command);
|
|
42
|
+
if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) return {
|
|
43
|
+
strInfo: null,
|
|
44
|
+
reported: false
|
|
45
|
+
};
|
|
42
46
|
|
|
43
47
|
const event = createSinkEvent({
|
|
44
48
|
name,
|
|
@@ -52,11 +56,19 @@ module.exports = function (core) {
|
|
|
52
56
|
value: strInfo.value,
|
|
53
57
|
tracked: true,
|
|
54
58
|
},
|
|
55
|
-
|
|
59
|
+
(secondArg && {
|
|
60
|
+
value: inspect(secondArg),
|
|
61
|
+
tracked: false
|
|
62
|
+
}),
|
|
63
|
+
(thirdArg && {
|
|
64
|
+
value: inspect(thirdArg),
|
|
65
|
+
tracked: false
|
|
66
|
+
})
|
|
67
|
+
].filter(Boolean),
|
|
56
68
|
tags: strInfo.tags,
|
|
57
69
|
source: 'P0',
|
|
58
70
|
stacktraceOpts: {
|
|
59
|
-
|
|
71
|
+
constructorOpt: hooked,
|
|
60
72
|
},
|
|
61
73
|
});
|
|
62
74
|
|
|
@@ -65,18 +77,143 @@ module.exports = function (core) {
|
|
|
65
77
|
ruleId: Rule.CMD_INJECTION,
|
|
66
78
|
sinkEvent: event,
|
|
67
79
|
});
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
strInfo,
|
|
83
|
+
reported: true
|
|
84
|
+
};
|
|
68
85
|
}
|
|
69
|
-
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
strInfo,
|
|
89
|
+
reported: false
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function argumentsCheck(name, command, commandInfo, args, options, hooked) {
|
|
94
|
+
if (!Array.isArray(args) || !args?.length) return;
|
|
95
|
+
|
|
96
|
+
const trackedArgs = [];
|
|
97
|
+
let vulnerableArgIdx;
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < args.length; i++) {
|
|
100
|
+
const trackData = tracker.getData(args[i]);
|
|
101
|
+
|
|
102
|
+
trackedArgs.push(trackData);
|
|
103
|
+
|
|
104
|
+
if (!trackData) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
!vulnerableArgIdx &&
|
|
110
|
+
vulnerableArgIdx != 0 &&
|
|
111
|
+
isVulnerable(UNTRUSTED, safeTags, trackData.tags)
|
|
112
|
+
) {
|
|
113
|
+
vulnerableArgIdx = i;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (vulnerableArgIdx != 0 && !vulnerableArgIdx) return;
|
|
118
|
+
|
|
119
|
+
const event = createSinkEvent({
|
|
120
|
+
name,
|
|
121
|
+
history: [trackedArgs[vulnerableArgIdx]],
|
|
122
|
+
object: {
|
|
123
|
+
value: 'child_process',
|
|
124
|
+
tracked: false,
|
|
125
|
+
},
|
|
126
|
+
args: [
|
|
127
|
+
{
|
|
128
|
+
value: commandInfo?.value || command,
|
|
129
|
+
tracked: !!commandInfo,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
value: inspect(args),
|
|
133
|
+
tracked: true
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
value: inspect(options),
|
|
137
|
+
tracked: false
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
tags: trackedArgs[vulnerableArgIdx].tags,
|
|
141
|
+
source: 'P1',
|
|
142
|
+
stacktraceOpts: {
|
|
143
|
+
contructorOpt: hooked,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (event) {
|
|
148
|
+
reportFindings({
|
|
149
|
+
ruleId: Rule.CMD_INJECTION,
|
|
150
|
+
sinkEvent: event,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
70
154
|
|
|
71
155
|
core.assess.dataflow.sinks.cmdInjection = {
|
|
72
156
|
install() {
|
|
73
157
|
depHooks.resolve({ name: 'child_process' }, cp => {
|
|
74
|
-
['spawn', 'spawnSync'
|
|
158
|
+
['spawn', 'spawnSync'].forEach((method) => {
|
|
159
|
+
const name = `child_process.${method}`;
|
|
160
|
+
patcher.patch(cp, method, {
|
|
161
|
+
name,
|
|
162
|
+
patchType,
|
|
163
|
+
pre(data) {
|
|
164
|
+
const store = sources.getStore()?.assess;
|
|
165
|
+
const [command] = data.args;
|
|
166
|
+
|
|
167
|
+
if (!store || !command || !isString(command)) return;
|
|
168
|
+
|
|
169
|
+
const cpArgs = Array.isArray(data.args[1]) && data.args[1];
|
|
170
|
+
const options = cpArgs ? data.args[2] : data.args[1];
|
|
171
|
+
|
|
172
|
+
const cmdCheck = commandCheck(name, command, cpArgs, options, data.hooked);
|
|
173
|
+
|
|
174
|
+
if (cmdCheck.reported || !options?.shell) return;
|
|
175
|
+
|
|
176
|
+
argumentsCheck(name, command, cmdCheck.strInfo, cpArgs, options, data.hooked);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
['exec', 'execSync'].forEach((method) => {
|
|
182
|
+
const name = `child_process.${method}`;
|
|
183
|
+
patcher.patch(cp, method, {
|
|
184
|
+
name,
|
|
185
|
+
patchType,
|
|
186
|
+
pre(data) {
|
|
187
|
+
const store = sources.getStore()?.assess;
|
|
188
|
+
const [command, secondArg, thirdArg] = data.args;
|
|
189
|
+
|
|
190
|
+
if (!store || !command || !isString(command)) return;
|
|
191
|
+
|
|
192
|
+
commandCheck(name, command, secondArg, thirdArg, data.hooked);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
['execFile', 'execFileSync'].forEach((method) => {
|
|
75
198
|
const name = `child_process.${method}`;
|
|
76
199
|
patcher.patch(cp, method, {
|
|
77
200
|
name,
|
|
78
201
|
patchType,
|
|
79
|
-
pre
|
|
202
|
+
pre(data) {
|
|
203
|
+
const store = sources.getStore()?.assess;
|
|
204
|
+
const [command] = data.args;
|
|
205
|
+
|
|
206
|
+
if (!store || !command || !isString(command)) return;
|
|
207
|
+
|
|
208
|
+
const cpArgs = Array.isArray(data.args[1]) && data.args[1];
|
|
209
|
+
const options = cpArgs ? data.args[2] : data.args[1];
|
|
210
|
+
|
|
211
|
+
if (!options?.shell) return;
|
|
212
|
+
|
|
213
|
+
const cmdInfo = tracker.getData(command);
|
|
214
|
+
|
|
215
|
+
argumentsCheck(name, command, cmdInfo, cpArgs, options, data.hooked);
|
|
216
|
+
}
|
|
80
217
|
});
|
|
81
218
|
});
|
|
82
219
|
});
|