dd-trace 3.36.0 → 3.38.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/LICENSE-3rdparty.csv +2 -0
- package/ext/tags.d.ts +1 -0
- package/ext/tags.js +1 -0
- package/index.d.ts +1 -0
- package/package.json +9 -6
- package/packages/datadog-esbuild/index.js +30 -25
- package/packages/datadog-instrumentations/src/body-parser.js +4 -3
- package/packages/datadog-instrumentations/src/cookie-parser.js +37 -0
- package/packages/datadog-instrumentations/src/cucumber.js +24 -4
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +45 -0
- package/packages/datadog-instrumentations/src/express.js +3 -2
- package/packages/datadog-instrumentations/src/graphql.js +5 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +5 -1
- package/packages/datadog-instrumentations/src/http/server.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +20 -11
- package/packages/datadog-instrumentations/src/knex.js +62 -1
- package/packages/datadog-instrumentations/src/mocha.js +19 -4
- package/packages/datadog-instrumentations/src/mongodb.js +63 -0
- package/packages/datadog-instrumentations/src/mongoose.js +140 -1
- package/packages/datadog-instrumentations/src/next.js +62 -80
- package/packages/datadog-instrumentations/src/pg.js +14 -15
- package/packages/datadog-instrumentations/src/playwright.js +26 -5
- package/packages/datadog-plugin-cucumber/src/index.js +17 -5
- package/packages/datadog-plugin-cypress/src/plugin.js +38 -8
- package/packages/datadog-plugin-jest/src/index.js +19 -4
- package/packages/datadog-plugin-jest/src/util.js +45 -2
- package/packages/datadog-plugin-memcached/src/index.js +10 -5
- package/packages/datadog-plugin-mocha/src/index.js +19 -6
- package/packages/datadog-plugin-mysql/src/index.js +2 -2
- package/packages/datadog-plugin-next/src/index.js +14 -5
- package/packages/datadog-plugin-pg/src/index.js +2 -2
- package/packages/dd-trace/src/appsec/channels.js +4 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +166 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +21 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -2
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +25 -12
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +13 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +9 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +13 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +169 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +45 -14
- package/packages/dd-trace/src/appsec/recommended.json +549 -24
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
- package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -3
- package/packages/dd-trace/src/appsec/reporter.js +7 -5
- package/packages/dd-trace/src/appsec/telemetry.js +2 -2
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +18 -5
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +5 -4
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +1 -14
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -13
- package/packages/dd-trace/src/config.js +8 -0
- package/packages/dd-trace/src/datastreams/processor.js +6 -2
- package/packages/dd-trace/src/format.js +9 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
- package/packages/dd-trace/src/opentracing/tracer.js +0 -2
- package/packages/dd-trace/src/plugin_manager.js +4 -3
- package/packages/dd-trace/src/plugins/database.js +14 -4
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/outbound.js +4 -3
- package/packages/dd-trace/src/plugins/util/ci.js +17 -0
- package/packages/dd-trace/src/plugins/util/git.js +26 -4
- package/packages/dd-trace/src/plugins/util/test.js +16 -1
- package/packages/dd-trace/src/profiling/config.js +36 -5
- package/packages/dd-trace/src/profiling/profilers/wall.js +7 -1
- package/packages/dd-trace/src/service-naming/extra-services.js +24 -0
- package/packages/dd-trace/src/telemetry/index.js +10 -1
- package/packages/dd-trace/src/telemetry/metrics.js +0 -5
|
@@ -196,6 +196,10 @@ class SinkIastPlugin extends IastPlugin {
|
|
|
196
196
|
addSub (iastPluginSub, handler) {
|
|
197
197
|
return super.addSub({ tagKey: TagKey.VULNERABILITY_TYPE, ...iastPluginSub }, handler)
|
|
198
198
|
}
|
|
199
|
+
|
|
200
|
+
addNotSinkSub (iastPluginSub, handler) {
|
|
201
|
+
return super.addSub(iastPluginSub, handler)
|
|
202
|
+
}
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
module.exports = {
|
|
@@ -18,15 +18,14 @@ let onRemoveTransaction = (transactionId, iastContext) => {}
|
|
|
18
18
|
|
|
19
19
|
function onRemoveTransactionInformationTelemetry (transactionId, iastContext) {
|
|
20
20
|
const metrics = TaintedUtils.getMetrics(transactionId, iastTelemetry.verbosity)
|
|
21
|
-
if (metrics
|
|
21
|
+
if (metrics?.requestCount) {
|
|
22
22
|
REQUEST_TAINTED.add(metrics.requestCount, null, iastContext)
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function removeTransaction (iastContext) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
28
|
+
if (transactionId) {
|
|
30
29
|
onRemoveTransaction(transactionId, iastContext)
|
|
31
30
|
|
|
32
31
|
TaintedUtils.removeTransaction(transactionId)
|
|
@@ -36,8 +35,8 @@ function removeTransaction (iastContext) {
|
|
|
36
35
|
|
|
37
36
|
function newTaintedString (iastContext, string, name, type) {
|
|
38
37
|
let result = string
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
39
|
+
if (transactionId) {
|
|
41
40
|
result = TaintedUtils.newTaintedString(transactionId, string, name, type)
|
|
42
41
|
} else {
|
|
43
42
|
result = string
|
|
@@ -47,15 +46,17 @@ function newTaintedString (iastContext, string, name, type) {
|
|
|
47
46
|
|
|
48
47
|
function taintObject (iastContext, object, type, keyTainting, keyType) {
|
|
49
48
|
let result = object
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
50
|
+
if (transactionId) {
|
|
52
51
|
const queue = [{ parent: null, property: null, value: object }]
|
|
53
52
|
const visited = new WeakSet()
|
|
53
|
+
|
|
54
54
|
while (queue.length > 0) {
|
|
55
55
|
const { parent, property, value, key } = queue.pop()
|
|
56
56
|
if (value === null) {
|
|
57
57
|
continue
|
|
58
58
|
}
|
|
59
|
+
|
|
59
60
|
try {
|
|
60
61
|
if (typeof value === 'string') {
|
|
61
62
|
const tainted = TaintedUtils.newTaintedString(transactionId, value, property, type)
|
|
@@ -71,11 +72,13 @@ function taintObject (iastContext, object, type, keyTainting, keyType) {
|
|
|
71
72
|
}
|
|
72
73
|
} else if (typeof value === 'object' && !visited.has(value)) {
|
|
73
74
|
visited.add(value)
|
|
75
|
+
|
|
74
76
|
const keys = Object.keys(value)
|
|
75
77
|
for (let i = 0; i < keys.length; i++) {
|
|
76
78
|
const key = keys[i]
|
|
77
79
|
queue.push({ parent: value, property: property ? `${property}.${key}` : key, value: value[key], key })
|
|
78
80
|
}
|
|
81
|
+
|
|
79
82
|
if (parent && keyTainting && key) {
|
|
80
83
|
const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
|
|
81
84
|
parent[taintedProperty] = value
|
|
@@ -91,8 +94,8 @@ function taintObject (iastContext, object, type, keyTainting, keyType) {
|
|
|
91
94
|
|
|
92
95
|
function isTainted (iastContext, string) {
|
|
93
96
|
let result = false
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
98
|
+
if (transactionId) {
|
|
96
99
|
result = TaintedUtils.isTainted(transactionId, string)
|
|
97
100
|
} else {
|
|
98
101
|
result = false
|
|
@@ -102,8 +105,8 @@ function isTainted (iastContext, string) {
|
|
|
102
105
|
|
|
103
106
|
function getRanges (iastContext, string) {
|
|
104
107
|
let result = []
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
109
|
+
if (transactionId) {
|
|
107
110
|
result = TaintedUtils.getRanges(transactionId, string)
|
|
108
111
|
} else {
|
|
109
112
|
result = []
|
|
@@ -111,6 +114,15 @@ function getRanges (iastContext, string) {
|
|
|
111
114
|
return result
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
function addSecureMark (iastContext, string, mark) {
|
|
118
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
119
|
+
if (transactionId) {
|
|
120
|
+
return TaintedUtils.addSecureMarksToTaintedString(transactionId, string, mark)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return string
|
|
124
|
+
}
|
|
125
|
+
|
|
114
126
|
function enableTaintOperations (telemetryVerbosity) {
|
|
115
127
|
if (isInfoAllowed(telemetryVerbosity)) {
|
|
116
128
|
onRemoveTransaction = onRemoveTransactionInformationTelemetry
|
|
@@ -132,6 +144,7 @@ function setMaxTransactions (transactions) {
|
|
|
132
144
|
}
|
|
133
145
|
|
|
134
146
|
module.exports = {
|
|
147
|
+
addSecureMark,
|
|
135
148
|
createTransaction,
|
|
136
149
|
removeTransaction,
|
|
137
150
|
newTaintedString,
|
|
@@ -11,8 +11,8 @@ const {
|
|
|
11
11
|
HTTP_REQUEST_HEADER_VALUE,
|
|
12
12
|
HTTP_REQUEST_HEADER_NAME,
|
|
13
13
|
HTTP_REQUEST_PARAMETER,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
HTTP_REQUEST_PATH_PARAM,
|
|
15
|
+
HTTP_REQUEST_URI
|
|
16
16
|
} = require('./source-types')
|
|
17
17
|
|
|
18
18
|
class TaintTrackingPlugin extends SourceIastPlugin {
|
|
@@ -93,9 +93,9 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
93
93
|
taintUrl (req, iastContext) {
|
|
94
94
|
this.execSource({
|
|
95
95
|
handler: function () {
|
|
96
|
-
req.url = newTaintedString(iastContext, req.url,
|
|
96
|
+
req.url = newTaintedString(iastContext, req.url, HTTP_REQUEST_URI, HTTP_REQUEST_URI)
|
|
97
97
|
},
|
|
98
|
-
tag: [
|
|
98
|
+
tag: [HTTP_REQUEST_URI],
|
|
99
99
|
iastContext
|
|
100
100
|
})
|
|
101
101
|
}
|
|
@@ -8,5 +8,6 @@ module.exports = {
|
|
|
8
8
|
HTTP_REQUEST_HEADER_VALUE: 'http.request.header',
|
|
9
9
|
HTTP_REQUEST_PARAMETER: 'http.request.parameter',
|
|
10
10
|
HTTP_REQUEST_PATH: 'http.request.path',
|
|
11
|
-
HTTP_REQUEST_PATH_PARAM: 'http.request.path.parameter'
|
|
11
|
+
HTTP_REQUEST_PATH_PARAM: 'http.request.path.parameter',
|
|
12
|
+
HTTP_REQUEST_URI: 'http.request.uri'
|
|
12
13
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { stringifyWithRanges } = require('../../utils')
|
|
4
|
+
|
|
5
|
+
class JsonSensitiveAnalyzer {
|
|
6
|
+
extractSensitiveRanges (evidence) {
|
|
7
|
+
// expect object evidence
|
|
8
|
+
const { value, ranges, sensitiveRanges } = stringifyWithRanges(evidence.value, evidence.rangesToApply, true)
|
|
9
|
+
evidence.value = value
|
|
10
|
+
evidence.ranges = ranges
|
|
11
|
+
|
|
12
|
+
return sensitiveRanges
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = JsonSensitiveAnalyzer
|
|
@@ -5,14 +5,12 @@ const vulnerabilities = require('../../vulnerabilities')
|
|
|
5
5
|
const { contains, intersects, remove } = require('./range-utils')
|
|
6
6
|
|
|
7
7
|
const CommandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer')
|
|
8
|
+
const JsonSensitiveAnalyzer = require('./sensitive-analyzers/json-sensitive-analyzer')
|
|
8
9
|
const LdapSensitiveAnalyzer = require('./sensitive-analyzers/ldap-sensitive-analyzer')
|
|
9
10
|
const SqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyzer')
|
|
10
11
|
const UrlSensitiveAnalyzer = require('./sensitive-analyzers/url-sensitive-analyzer')
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)'
|
|
14
|
-
// eslint-disable-next-line max-len
|
|
15
|
-
const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))'
|
|
13
|
+
const { DEFAULT_IAST_REDACTION_NAME_PATTERN, DEFAULT_IAST_REDACTION_VALUE_PATTERN } = require('./sensitive-regex')
|
|
16
14
|
|
|
17
15
|
const REDACTED_SOURCE_BUFFER = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|
18
16
|
|
|
@@ -23,6 +21,7 @@ class SensitiveHandler {
|
|
|
23
21
|
|
|
24
22
|
this._sensitiveAnalyzers = new Map()
|
|
25
23
|
this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, new CommandSensitiveAnalyzer())
|
|
24
|
+
this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, new JsonSensitiveAnalyzer())
|
|
26
25
|
this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, new LdapSensitiveAnalyzer())
|
|
27
26
|
this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, new SqlSensitiveAnalyzer())
|
|
28
27
|
const urlSensitiveAnalyzer = new UrlSensitiveAnalyzer()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// eslint-disable-next-line max-len
|
|
2
|
+
const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)'
|
|
3
|
+
// eslint-disable-next-line max-len
|
|
4
|
+
const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))'
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
DEFAULT_IAST_REDACTION_NAME_PATTERN,
|
|
8
|
+
DEFAULT_IAST_REDACTION_VALUE_PATTERN
|
|
9
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const sensitiveHandler = require('./evidence-redaction/sensitive-handler')
|
|
4
|
+
const { stringifyWithRanges } = require('./utils')
|
|
2
5
|
|
|
3
6
|
class VulnerabilityFormatter {
|
|
4
7
|
constructor () {
|
|
@@ -38,6 +41,13 @@ class VulnerabilityFormatter {
|
|
|
38
41
|
getUnredactedValueParts (evidence, sourcesIndexes) {
|
|
39
42
|
const valueParts = []
|
|
40
43
|
let fromIndex = 0
|
|
44
|
+
|
|
45
|
+
if (typeof evidence.value === 'object' && evidence.rangesToApply) {
|
|
46
|
+
const { value, ranges } = stringifyWithRanges(evidence.value, evidence.rangesToApply)
|
|
47
|
+
evidence.value = value
|
|
48
|
+
evidence.ranges = ranges
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
evidence.ranges.forEach((range, rangeIndex) => {
|
|
42
52
|
if (fromIndex < range.start) {
|
|
43
53
|
valueParts.push({ value: evidence.value.substring(fromIndex, range.start) })
|
|
@@ -45,14 +55,16 @@ class VulnerabilityFormatter {
|
|
|
45
55
|
valueParts.push({ value: evidence.value.substring(range.start, range.end), source: sourcesIndexes[rangeIndex] })
|
|
46
56
|
fromIndex = range.end
|
|
47
57
|
})
|
|
58
|
+
|
|
48
59
|
if (fromIndex < evidence.value.length) {
|
|
49
60
|
valueParts.push({ value: evidence.value.substring(fromIndex) })
|
|
50
61
|
}
|
|
62
|
+
|
|
51
63
|
return { valueParts }
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
formatEvidence (type, evidence, sourcesIndexes, sources) {
|
|
55
|
-
if (!evidence.ranges) {
|
|
67
|
+
if (!evidence.ranges && !evidence.rangesToApply) {
|
|
56
68
|
if (typeof evidence.value === 'undefined') {
|
|
57
69
|
return undefined
|
|
58
70
|
} else {
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto')
|
|
4
|
+
const { DEFAULT_IAST_REDACTION_VALUE_PATTERN } = require('./evidence-redaction/sensitive-regex')
|
|
5
|
+
|
|
6
|
+
const STRINGIFY_RANGE_KEY = 'DD_' + crypto.randomBytes(20).toString('hex')
|
|
7
|
+
const STRINGIFY_SENSITIVE_KEY = STRINGIFY_RANGE_KEY + 'SENSITIVE'
|
|
8
|
+
const STRINGIFY_SENSITIVE_NOT_STRING_KEY = STRINGIFY_SENSITIVE_KEY + 'NOTSTRING'
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line max-len
|
|
11
|
+
const KEYS_REGEX_WITH_SENSITIVE_RANGES = new RegExp(`(?:"(${STRINGIFY_RANGE_KEY}_\\d+_))|(?:"(${STRINGIFY_SENSITIVE_KEY}_\\d+_(\\d+)_))|("${STRINGIFY_SENSITIVE_NOT_STRING_KEY}_\\d+_([\\s0-9.a-zA-Z]*)")`, 'gm')
|
|
12
|
+
const KEYS_REGEX_WITHOUT_SENSITIVE_RANGES = new RegExp(`"(${STRINGIFY_RANGE_KEY}_\\d+_)`, 'gm')
|
|
13
|
+
|
|
14
|
+
const sensitiveValueRegex = new RegExp(DEFAULT_IAST_REDACTION_VALUE_PATTERN, 'gmi')
|
|
15
|
+
|
|
16
|
+
function iterateObject (target, fn, levelKeys = [], depth = 50) {
|
|
17
|
+
Object.keys(target).forEach((key) => {
|
|
18
|
+
const nextLevelKeys = [...levelKeys, key]
|
|
19
|
+
const val = target[key]
|
|
20
|
+
|
|
21
|
+
fn(val, nextLevelKeys, target, key)
|
|
22
|
+
|
|
23
|
+
if (val !== null && typeof val === 'object') {
|
|
24
|
+
iterateObject(val, fn, nextLevelKeys, depth - 1)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function stringifyWithRanges (obj, objRanges, loadSensitiveRanges = false) {
|
|
30
|
+
let value
|
|
31
|
+
const ranges = []
|
|
32
|
+
const sensitiveRanges = []
|
|
33
|
+
objRanges = objRanges || {}
|
|
34
|
+
|
|
35
|
+
if (objRanges || loadSensitiveRanges) {
|
|
36
|
+
const cloneObj = Array.isArray(obj) ? [] : {}
|
|
37
|
+
let counter = 0
|
|
38
|
+
const allRanges = {}
|
|
39
|
+
const sensitiveKeysMapping = {}
|
|
40
|
+
|
|
41
|
+
iterateObject(obj, (val, levelKeys, parent, key) => {
|
|
42
|
+
let currentLevelClone = cloneObj
|
|
43
|
+
for (let i = 0; i < levelKeys.length - 1; i++) {
|
|
44
|
+
let levelKey = levelKeys[i]
|
|
45
|
+
|
|
46
|
+
if (!currentLevelClone[levelKey]) {
|
|
47
|
+
const sensitiveKey = sensitiveKeysMapping[levelKey]
|
|
48
|
+
if (currentLevelClone[sensitiveKey]) {
|
|
49
|
+
levelKey = sensitiveKey
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
currentLevelClone = currentLevelClone[levelKey]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (loadSensitiveRanges) {
|
|
57
|
+
const sensitiveKey = sensitiveKeysMapping[key]
|
|
58
|
+
if (sensitiveKey) {
|
|
59
|
+
key = sensitiveKey
|
|
60
|
+
} else {
|
|
61
|
+
sensitiveValueRegex.lastIndex = 0
|
|
62
|
+
|
|
63
|
+
if (sensitiveValueRegex.test(key)) {
|
|
64
|
+
const current = counter++
|
|
65
|
+
const id = `${STRINGIFY_SENSITIVE_KEY}_${current}_${key.length}_`
|
|
66
|
+
key = `${id}${key}`
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof val === 'string') {
|
|
72
|
+
const ranges = objRanges[levelKeys.join('.')]
|
|
73
|
+
if (ranges) {
|
|
74
|
+
const current = counter++
|
|
75
|
+
const id = `${STRINGIFY_RANGE_KEY}_${current}_`
|
|
76
|
+
|
|
77
|
+
allRanges[id] = ranges
|
|
78
|
+
currentLevelClone[key] = `${id}${val}`
|
|
79
|
+
} else {
|
|
80
|
+
currentLevelClone[key] = val
|
|
81
|
+
}
|
|
82
|
+
if (loadSensitiveRanges) {
|
|
83
|
+
const current = counter++
|
|
84
|
+
const id = `${STRINGIFY_SENSITIVE_KEY}_${current}_${val.length}_`
|
|
85
|
+
|
|
86
|
+
currentLevelClone[key] = `${id}${currentLevelClone[key]}`
|
|
87
|
+
}
|
|
88
|
+
} else if (typeof val !== 'object' || val === null) {
|
|
89
|
+
if (loadSensitiveRanges) {
|
|
90
|
+
const current = counter++
|
|
91
|
+
const id = `${STRINGIFY_SENSITIVE_NOT_STRING_KEY}_${current}_`
|
|
92
|
+
|
|
93
|
+
// this is special, in the final string we should modify "key_value_[null|false|true]..."
|
|
94
|
+
// by null|false|..... ignoring the beginning and ending quotes
|
|
95
|
+
currentLevelClone[key] = id + val
|
|
96
|
+
} else {
|
|
97
|
+
currentLevelClone[key] = val
|
|
98
|
+
}
|
|
99
|
+
} else if (Array.isArray(val)) {
|
|
100
|
+
currentLevelClone[key] = []
|
|
101
|
+
} else {
|
|
102
|
+
currentLevelClone[key] = {}
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
value = JSON.stringify(cloneObj, null, 2)
|
|
107
|
+
|
|
108
|
+
if (counter > 0) {
|
|
109
|
+
let keysRegex
|
|
110
|
+
if (loadSensitiveRanges) {
|
|
111
|
+
keysRegex = KEYS_REGEX_WITH_SENSITIVE_RANGES
|
|
112
|
+
} else {
|
|
113
|
+
keysRegex = KEYS_REGEX_WITHOUT_SENSITIVE_RANGES
|
|
114
|
+
}
|
|
115
|
+
keysRegex.lastIndex = 0
|
|
116
|
+
|
|
117
|
+
let regexRes = keysRegex.exec(value)
|
|
118
|
+
while (regexRes) {
|
|
119
|
+
const offset = regexRes.index + 1 // +1 to increase the " char
|
|
120
|
+
|
|
121
|
+
if (regexRes[1]) {
|
|
122
|
+
// is a range
|
|
123
|
+
const rangesId = regexRes[1]
|
|
124
|
+
value = value.replace(rangesId, '')
|
|
125
|
+
|
|
126
|
+
const updatedRanges = allRanges[rangesId].map(range => {
|
|
127
|
+
return {
|
|
128
|
+
...range,
|
|
129
|
+
start: range.start + offset,
|
|
130
|
+
end: range.end + offset
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
ranges.push(...updatedRanges)
|
|
135
|
+
} else if (regexRes[2]) {
|
|
136
|
+
// is a sensitive string literal
|
|
137
|
+
const sensitiveId = regexRes[2]
|
|
138
|
+
|
|
139
|
+
sensitiveRanges.push({
|
|
140
|
+
start: offset,
|
|
141
|
+
end: offset + parseInt(regexRes[3])
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
value = value.replace(sensitiveId, '')
|
|
145
|
+
} else if (regexRes[4]) {
|
|
146
|
+
// is a sensitive value (number, null, false, ...)
|
|
147
|
+
const sensitiveId = regexRes[4]
|
|
148
|
+
const originalValue = regexRes[5]
|
|
149
|
+
|
|
150
|
+
sensitiveRanges.push({
|
|
151
|
+
start: regexRes.index,
|
|
152
|
+
end: regexRes.index + originalValue.length
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
value = value.replace(sensitiveId, originalValue)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
keysRegex.lastIndex = 0
|
|
159
|
+
regexRes = keysRegex.exec(value)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
value = JSON.stringify(obj, null, 2)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { value, ranges, sensitiveRanges }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = { stringifyWithRanges }
|
|
@@ -5,6 +5,7 @@ module.exports = {
|
|
|
5
5
|
LDAP_INJECTION: 'LDAP_INJECTION',
|
|
6
6
|
NO_HTTPONLY_COOKIE: 'NO_HTTPONLY_COOKIE',
|
|
7
7
|
NO_SAMESITE_COOKIE: 'NO_SAMESITE_COOKIE',
|
|
8
|
+
NOSQL_MONGODB_INJECTION: 'NOSQL_MONGODB_INJECTION',
|
|
8
9
|
PATH_TRAVERSAL: 'PATH_TRAVERSAL',
|
|
9
10
|
SQL_INJECTION: 'SQL_INJECTION',
|
|
10
11
|
SSRF: 'SSRF',
|
|
@@ -5,11 +5,14 @@ const RuleManager = require('./rule_manager')
|
|
|
5
5
|
const remoteConfig = require('./remote_config')
|
|
6
6
|
const {
|
|
7
7
|
bodyParser,
|
|
8
|
+
cookieParser,
|
|
8
9
|
graphqlFinishExecute,
|
|
9
10
|
incomingHttpRequestStart,
|
|
10
11
|
incomingHttpRequestEnd,
|
|
11
12
|
passportVerify,
|
|
12
|
-
queryParser
|
|
13
|
+
queryParser,
|
|
14
|
+
nextBodyParsed,
|
|
15
|
+
nextQueryParsed
|
|
13
16
|
} = require('./channels')
|
|
14
17
|
const waf = require('./waf')
|
|
15
18
|
const addresses = require('./addresses')
|
|
@@ -29,6 +32,8 @@ function enable (_config) {
|
|
|
29
32
|
if (isEnabled) return
|
|
30
33
|
|
|
31
34
|
try {
|
|
35
|
+
appsecTelemetry.enable(_config.telemetry)
|
|
36
|
+
|
|
32
37
|
setTemplates(_config)
|
|
33
38
|
|
|
34
39
|
RuleManager.applyRules(_config.appsec.rules, _config.appsec)
|
|
@@ -37,12 +42,13 @@ function enable (_config) {
|
|
|
37
42
|
|
|
38
43
|
Reporter.setRateLimit(_config.appsec.rateLimit)
|
|
39
44
|
|
|
40
|
-
appsecTelemetry.enable(_config.telemetry)
|
|
41
|
-
|
|
42
45
|
incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
|
|
43
46
|
incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
|
|
44
47
|
bodyParser.subscribe(onRequestBodyParsed)
|
|
48
|
+
nextBodyParsed.subscribe(onRequestBodyParsed)
|
|
49
|
+
nextQueryParsed.subscribe(onRequestQueryParsed)
|
|
45
50
|
queryParser.subscribe(onRequestQueryParsed)
|
|
51
|
+
cookieParser.subscribe(onRequestCookieParser)
|
|
46
52
|
graphqlFinishExecute.subscribe(onGraphqlFinishExecute)
|
|
47
53
|
|
|
48
54
|
if (_config.appsec.eventTracking.enabled) {
|
|
@@ -110,12 +116,13 @@ function incomingHttpEndTranslator ({ req, res }) {
|
|
|
110
116
|
payload[addresses.HTTP_INCOMING_PARAMS] = req.params
|
|
111
117
|
}
|
|
112
118
|
|
|
119
|
+
// we need to keep this to support other cookie parsers
|
|
113
120
|
if (req.cookies && typeof req.cookies === 'object') {
|
|
114
|
-
payload[addresses.HTTP_INCOMING_COOKIES] =
|
|
121
|
+
payload[addresses.HTTP_INCOMING_COOKIES] = req.cookies
|
|
122
|
+
}
|
|
115
123
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
124
|
+
if (req.query && typeof req.query === 'object') {
|
|
125
|
+
payload[addresses.HTTP_INCOMING_QUERY] = req.query
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
waf.run(payload, req)
|
|
@@ -125,27 +132,50 @@ function incomingHttpEndTranslator ({ req, res }) {
|
|
|
125
132
|
Reporter.finishRequest(req, res)
|
|
126
133
|
}
|
|
127
134
|
|
|
128
|
-
function onRequestBodyParsed ({ req, res, abortController }) {
|
|
135
|
+
function onRequestBodyParsed ({ req, res, body, abortController }) {
|
|
136
|
+
if (body === undefined || body === null) return
|
|
137
|
+
|
|
138
|
+
if (!req) {
|
|
139
|
+
const store = storage.getStore()
|
|
140
|
+
req = store?.req
|
|
141
|
+
}
|
|
142
|
+
|
|
129
143
|
const rootSpan = web.root(req)
|
|
130
144
|
if (!rootSpan) return
|
|
131
145
|
|
|
132
|
-
if (req.body === undefined || req.body === null) return
|
|
133
|
-
|
|
134
146
|
const results = waf.run({
|
|
135
|
-
[addresses.HTTP_INCOMING_BODY]:
|
|
147
|
+
[addresses.HTTP_INCOMING_BODY]: body
|
|
136
148
|
}, req)
|
|
137
149
|
|
|
138
150
|
handleResults(results, req, res, rootSpan, abortController)
|
|
139
151
|
}
|
|
140
152
|
|
|
141
|
-
function onRequestQueryParsed ({ req, res, abortController }) {
|
|
153
|
+
function onRequestQueryParsed ({ req, res, query, abortController }) {
|
|
154
|
+
if (!query || typeof query !== 'object') return
|
|
155
|
+
|
|
156
|
+
if (!req) {
|
|
157
|
+
const store = storage.getStore()
|
|
158
|
+
req = store?.req
|
|
159
|
+
}
|
|
160
|
+
|
|
142
161
|
const rootSpan = web.root(req)
|
|
143
162
|
if (!rootSpan) return
|
|
144
163
|
|
|
145
|
-
|
|
164
|
+
const results = waf.run({
|
|
165
|
+
[addresses.HTTP_INCOMING_QUERY]: query
|
|
166
|
+
}, req)
|
|
167
|
+
|
|
168
|
+
handleResults(results, req, res, rootSpan, abortController)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function onRequestCookieParser ({ req, res, abortController, cookies }) {
|
|
172
|
+
if (!cookies || typeof cookies !== 'object') return
|
|
173
|
+
|
|
174
|
+
const rootSpan = web.root(req)
|
|
175
|
+
if (!rootSpan) return
|
|
146
176
|
|
|
147
177
|
const results = waf.run({
|
|
148
|
-
[addresses.
|
|
178
|
+
[addresses.HTTP_INCOMING_COOKIES]: cookies
|
|
149
179
|
}, req)
|
|
150
180
|
|
|
151
181
|
handleResults(results, req, res, rootSpan, abortController)
|
|
@@ -201,6 +231,7 @@ function disable () {
|
|
|
201
231
|
if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
|
|
202
232
|
if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
|
|
203
233
|
if (queryParser.hasSubscribers) queryParser.unsubscribe(onRequestQueryParsed)
|
|
234
|
+
if (cookieParser.hasSubscribers) cookieParser.unsubscribe(onRequestCookieParser)
|
|
204
235
|
if (passportVerify.hasSubscribers) passportVerify.unsubscribe(onPassportVerify)
|
|
205
236
|
}
|
|
206
237
|
|