dd-trace 2.33.0 → 2.35.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/index.d.ts +8 -1
- package/package.json +5 -4
- package/packages/datadog-instrumentations/src/cucumber.js +13 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +4 -0
- package/packages/datadog-instrumentations/src/http/client.js +2 -1
- package/packages/datadog-instrumentations/src/http/server.js +14 -0
- package/packages/datadog-instrumentations/src/http2/client.js +4 -0
- package/packages/datadog-instrumentations/src/jest.js +20 -17
- package/packages/datadog-instrumentations/src/next.js +6 -1
- package/packages/datadog-instrumentations/src/playwright.js +1 -1
- package/packages/datadog-instrumentations/src/sequelize.js +51 -0
- package/packages/datadog-plugin-amqp10/src/consumer.js +1 -3
- package/packages/datadog-plugin-amqp10/src/producer.js +1 -3
- package/packages/datadog-plugin-amqplib/src/client.js +4 -3
- package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
- package/packages/datadog-plugin-amqplib/src/producer.js +1 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +3 -0
- package/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +4 -2
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +4 -3
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/services/s3.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +8 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +7 -1
- package/packages/datadog-plugin-cucumber/src/index.js +2 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +150 -30
- package/packages/datadog-plugin-cypress/src/support.js +6 -3
- package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -3
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -3
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -3
- package/packages/datadog-plugin-http/src/client.js +70 -67
- package/packages/datadog-plugin-http2/src/client.js +50 -46
- package/packages/datadog-plugin-jest/src/index.js +5 -4
- package/packages/datadog-plugin-jest/src/util.js +10 -1
- package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -4
- package/packages/datadog-plugin-kafkajs/src/producer.js +1 -3
- package/packages/datadog-plugin-memcached/src/index.js +2 -3
- package/packages/datadog-plugin-mocha/src/index.js +4 -2
- package/packages/datadog-plugin-pg/src/index.js +1 -1
- package/packages/datadog-plugin-redis/src/index.js +2 -13
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
- package/packages/datadog-plugin-rhea/src/producer.js +1 -5
- package/packages/dd-trace/src/appsec/blocked_templates.js +2 -101
- package/packages/dd-trace/src/appsec/blocking.js +60 -11
- package/packages/dd-trace/src/appsec/channels.js +3 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +7 -5
- package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/index.js +3 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/insecure-cookie-analyzer.js +31 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +26 -5
- package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +47 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +65 -4
- package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +26 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +35 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +2 -1
- package/packages/dd-trace/src/appsec/iast/index.js +1 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +16 -8
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +19 -4
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/range-utils.js +37 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +29 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +35 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +118 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +49 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +146 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +113 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +10 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -109
- package/packages/dd-trace/src/appsec/recommended.json +45 -46
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +3 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +4 -0
- package/packages/dd-trace/src/appsec/rule_manager.js +49 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -7
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -6
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +10 -4
- package/packages/dd-trace/src/config.js +86 -9
- package/packages/dd-trace/src/constants.js +3 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +11 -3
- package/packages/dd-trace/src/exporters/common/util.js +9 -0
- package/packages/dd-trace/src/exporters/common/writer.js +3 -2
- package/packages/dd-trace/src/git_metadata_tagger.js +17 -0
- package/packages/dd-trace/src/git_properties.js +32 -0
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- package/packages/dd-trace/src/plugins/cache.js +7 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
- package/packages/dd-trace/src/plugins/client.js +3 -2
- package/packages/dd-trace/src/plugins/consumer.js +14 -2
- package/packages/dd-trace/src/plugins/database.js +2 -2
- package/packages/dd-trace/src/plugins/inbound.js +7 -0
- package/packages/dd-trace/src/plugins/{outgoing.js → outbound.js} +2 -2
- package/packages/dd-trace/src/plugins/producer.js +19 -2
- package/packages/dd-trace/src/plugins/server.js +2 -2
- package/packages/dd-trace/src/plugins/storage.js +2 -0
- package/packages/dd-trace/src/plugins/tracing.js +11 -0
- package/packages/dd-trace/src/plugins/util/ci.js +63 -8
- package/packages/dd-trace/src/plugins/util/tags.js +5 -1
- package/packages/dd-trace/src/profiling/config.js +4 -2
- package/packages/dd-trace/src/profiling/constants.js +0 -1
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
- package/packages/dd-trace/src/proxy.js +4 -0
- package/packages/dd-trace/src/serverless.js +25 -0
- package/packages/dd-trace/src/service-naming/index.js +30 -0
- package/packages/dd-trace/src/service-naming/schemas/definition.js +24 -0
- package/packages/dd-trace/src/service-naming/schemas/index.js +6 -0
- package/packages/dd-trace/src/service-naming/schemas/util.js +5 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/index.js +5 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +64 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +33 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/index.js +5 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +52 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +21 -0
- package/packages/dd-trace/src/span_processor.js +3 -0
- package/packages/dd-trace/src/tracer.js +3 -2
- package/version.js +9 -0
- package/packages/dd-trace/src/plugins/incoming.js +0 -7
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
|
+
const { SSRF } = require('../vulnerabilities')
|
|
5
|
+
|
|
6
|
+
class SSRFAnalyzer extends InjectionAnalyzer {
|
|
7
|
+
constructor () {
|
|
8
|
+
super(SSRF)
|
|
9
|
+
|
|
10
|
+
this.addSub('apm:http:client:request:start', ({ args }) => {
|
|
11
|
+
if (typeof args.originalUrl === 'string') {
|
|
12
|
+
this.analyze(args.originalUrl)
|
|
13
|
+
} else if (args.options && args.options.host) {
|
|
14
|
+
this.analyze(args.options.host)
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
this.addSub('apm:http2:client:connect:start', ({ authority }) => {
|
|
19
|
+
if (authority && typeof authority === 'string') {
|
|
20
|
+
this.analyze(authority)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = new SSRFAnalyzer()
|
|
@@ -4,7 +4,7 @@ const Plugin = require('../../../../src/plugins/plugin')
|
|
|
4
4
|
const { storage } = require('../../../../../datadog-core')
|
|
5
5
|
const iastLog = require('../iast-log')
|
|
6
6
|
const { getFirstNonDDPathAndLine } = require('../path-line')
|
|
7
|
-
const {
|
|
7
|
+
const { addVulnerability } = require('../vulnerability-reporter')
|
|
8
8
|
const { getIastContext } = require('../iast-context')
|
|
9
9
|
const overheadController = require('../overhead-controller')
|
|
10
10
|
|
|
@@ -41,7 +41,7 @@ class Analyzer extends Plugin {
|
|
|
41
41
|
const location = this._getLocation()
|
|
42
42
|
if (!this._isExcluded(location)) {
|
|
43
43
|
const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
|
|
44
|
-
const vulnerability =
|
|
44
|
+
const vulnerability = this._createVulnerability(this._type, evidence, spanId, location)
|
|
45
45
|
addVulnerability(context, vulnerability)
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -59,9 +59,11 @@ class Analyzer extends Plugin {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
_getLocation () {
|
|
62
|
-
return getFirstNonDDPathAndLine()
|
|
62
|
+
return getFirstNonDDPathAndLine(this._getExcludedPaths())
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
_getExcludedPaths () {}
|
|
66
|
+
|
|
65
67
|
analyze (value) {
|
|
66
68
|
const store = storage.getStore()
|
|
67
69
|
const iastContext = getIastContext(store)
|
|
@@ -87,6 +89,36 @@ class Analyzer extends Plugin {
|
|
|
87
89
|
_checkOCE (context) {
|
|
88
90
|
return overheadController.hasQuota(overheadController.OPERATIONS.REPORT_VULNERABILITY, context)
|
|
89
91
|
}
|
|
92
|
+
|
|
93
|
+
_createVulnerability (type, evidence, spanId, location) {
|
|
94
|
+
if (type && evidence) {
|
|
95
|
+
const _spanId = spanId || 0
|
|
96
|
+
return {
|
|
97
|
+
type,
|
|
98
|
+
evidence,
|
|
99
|
+
location: {
|
|
100
|
+
spanId: _spanId,
|
|
101
|
+
...location
|
|
102
|
+
},
|
|
103
|
+
hash: this._createHash(this._createHashSource(type, evidence, location))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_createHashSource (type, evidence, location) {
|
|
110
|
+
return location ? `${type}:${location.path}:${location.line}` : type
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_createHash (hashSource) {
|
|
114
|
+
let hash = 0
|
|
115
|
+
let offset = 0
|
|
116
|
+
const size = hashSource.length
|
|
117
|
+
for (let i = 0; i < size; i++) {
|
|
118
|
+
hash = ((hash << 5) - hash) + hashSource.charCodeAt(offset++)
|
|
119
|
+
}
|
|
120
|
+
return hash
|
|
121
|
+
}
|
|
90
122
|
}
|
|
91
123
|
|
|
92
124
|
module.exports = Analyzer
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const Analyzer = require('./vulnerability-analyzer')
|
|
3
|
+
const { WEAK_CIPHER } = require('../vulnerabilities')
|
|
3
4
|
|
|
4
5
|
const INSECURE_CIPHERS = new Set([
|
|
5
6
|
'des', 'des-cbc', 'des-cfb', 'des-cfb1', 'des-cfb8', 'des-ecb', 'des-ede', 'des-ede-cbc', 'des-ede-cfb',
|
|
@@ -12,7 +13,7 @@ const INSECURE_CIPHERS = new Set([
|
|
|
12
13
|
|
|
13
14
|
class WeakCipherAnalyzer extends Analyzer {
|
|
14
15
|
constructor () {
|
|
15
|
-
super(
|
|
16
|
+
super(WEAK_CIPHER)
|
|
16
17
|
this.addSub('datadog:crypto:cipher:start', ({ algorithm }) => this.analyze(algorithm))
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const Analyzer = require('./vulnerability-analyzer')
|
|
3
|
+
const { WEAK_HASH } = require('../vulnerabilities')
|
|
3
4
|
|
|
4
5
|
const INSECURE_HASH_ALGORITHMS = new Set([
|
|
5
6
|
'md4', 'md4WithRSAEncryption', 'RSA-MD4',
|
|
@@ -9,7 +10,7 @@ const INSECURE_HASH_ALGORITHMS = new Set([
|
|
|
9
10
|
|
|
10
11
|
class WeakHashAnalyzer extends Analyzer {
|
|
11
12
|
constructor () {
|
|
12
|
-
super(
|
|
13
|
+
super(WEAK_HASH)
|
|
13
14
|
this.addSub('datadog:crypto:hashing:start', ({ algorithm }) => this.analyze(algorithm))
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -37,15 +37,16 @@ function getCallSiteInfo () {
|
|
|
37
37
|
return callsiteList
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function getFirstNonDDPathAndLineFromCallsites (callsites) {
|
|
40
|
+
function getFirstNonDDPathAndLineFromCallsites (callsites, externallyExcludedPaths) {
|
|
41
41
|
if (callsites) {
|
|
42
42
|
for (let i = 0; i < callsites.length; i++) {
|
|
43
43
|
const callsite = callsites[i]
|
|
44
44
|
const filepath = callsite.getFileName()
|
|
45
|
-
if (!isExcluded(callsite) && filepath.indexOf(pathLine.ddBasePath) === -1) {
|
|
45
|
+
if (!isExcluded(callsite, externallyExcludedPaths) && filepath.indexOf(pathLine.ddBasePath) === -1) {
|
|
46
46
|
return {
|
|
47
47
|
path: path.relative(process.cwd(), filepath),
|
|
48
|
-
line: callsite.getLineNumber()
|
|
48
|
+
line: callsite.getLineNumber(),
|
|
49
|
+
isInternal: !path.isAbsolute(filepath)
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -53,26 +54,33 @@ function getFirstNonDDPathAndLineFromCallsites (callsites) {
|
|
|
53
54
|
return null
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
function isExcluded (callsite) {
|
|
57
|
+
function isExcluded (callsite, externallyExcludedPaths) {
|
|
57
58
|
if (callsite.isNative()) return true
|
|
58
59
|
const filename = callsite.getFileName()
|
|
59
60
|
if (!filename) {
|
|
60
61
|
return true
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
let excludedPaths = EXCLUDED_PATHS
|
|
64
|
+
if (externallyExcludedPaths) {
|
|
65
|
+
excludedPaths = [...excludedPaths, ...externallyExcludedPaths]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < excludedPaths.length; i++) {
|
|
69
|
+
if (filename.indexOf(excludedPaths[i]) > -1) {
|
|
64
70
|
return true
|
|
65
71
|
}
|
|
66
72
|
}
|
|
73
|
+
|
|
67
74
|
for (let i = 0; i < EXCLUDED_PATH_PREFIXES.length; i++) {
|
|
68
75
|
if (filename.indexOf(EXCLUDED_PATH_PREFIXES[i]) === 0) {
|
|
69
76
|
return true
|
|
70
77
|
}
|
|
71
78
|
}
|
|
79
|
+
|
|
72
80
|
return false
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
function getFirstNonDDPathAndLine () {
|
|
76
|
-
return getFirstNonDDPathAndLineFromCallsites(getCallSiteInfo())
|
|
83
|
+
function getFirstNonDDPathAndLine (externallyExcludedPaths) {
|
|
84
|
+
return getFirstNonDDPathAndLineFromCallsites(getCallSiteInfo(), externallyExcludedPaths)
|
|
77
85
|
}
|
|
78
86
|
module.exports = pathLine
|
|
@@ -12,17 +12,32 @@ class TaintTrackingPlugin extends Plugin {
|
|
|
12
12
|
this._type = 'taint-tracking'
|
|
13
13
|
this.addSub(
|
|
14
14
|
'datadog:body-parser:read:finish',
|
|
15
|
-
({ req }) =>
|
|
15
|
+
({ req }) => {
|
|
16
|
+
const iastContext = getIastContext(storage.getStore())
|
|
17
|
+
if (iastContext && iastContext['body'] !== req.body) {
|
|
18
|
+
this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
|
|
19
|
+
iastContext['body'] = req.body
|
|
20
|
+
}
|
|
21
|
+
}
|
|
16
22
|
)
|
|
17
23
|
this.addSub(
|
|
18
24
|
'datadog:qs:parse:finish',
|
|
19
25
|
({ qs }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, qs))
|
|
26
|
+
|
|
27
|
+
this.addSub('apm:express:middleware:next', ({ req }) => {
|
|
28
|
+
if (req && req.body && typeof req.body === 'object') {
|
|
29
|
+
const iastContext = getIastContext(storage.getStore())
|
|
30
|
+
if (iastContext && iastContext['body'] !== req.body) {
|
|
31
|
+
this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
|
|
32
|
+
iastContext['body'] = req.body
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
})
|
|
20
36
|
}
|
|
21
37
|
|
|
22
|
-
_taintTrackingHandler (type, target, property) {
|
|
23
|
-
const iastContext = getIastContext(storage.getStore())
|
|
38
|
+
_taintTrackingHandler (type, target, property, iastContext = getIastContext(storage.getStore())) {
|
|
24
39
|
if (!property) {
|
|
25
|
-
|
|
40
|
+
taintObject(iastContext, target, type)
|
|
26
41
|
} else {
|
|
27
42
|
target[property] = taintObject(iastContext, target[property], type)
|
|
28
43
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function contains (rangeContainer, rangeContained) {
|
|
4
|
+
if (rangeContainer.start > rangeContained.start) {
|
|
5
|
+
return false
|
|
6
|
+
}
|
|
7
|
+
return rangeContainer.end >= rangeContained.end
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function intersects (rangeA, rangeB) {
|
|
11
|
+
return rangeB.start < rangeA.end && rangeB.end > rangeA.start
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function remove (range, rangeToRemove) {
|
|
15
|
+
if (!intersects(range, rangeToRemove)) {
|
|
16
|
+
return [range]
|
|
17
|
+
} else if (contains(rangeToRemove, range)) {
|
|
18
|
+
return []
|
|
19
|
+
} else {
|
|
20
|
+
const result = []
|
|
21
|
+
if (rangeToRemove.start > range.start) {
|
|
22
|
+
const offset = rangeToRemove.start - range.start
|
|
23
|
+
result.push({ start: range.start, end: range.start + offset })
|
|
24
|
+
}
|
|
25
|
+
if (rangeToRemove.end < range.end) {
|
|
26
|
+
const offset = range.end - rangeToRemove.end
|
|
27
|
+
result.push({ start: rangeToRemove.end, end: rangeToRemove.end + offset })
|
|
28
|
+
}
|
|
29
|
+
return result
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
contains,
|
|
35
|
+
intersects,
|
|
36
|
+
remove
|
|
37
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const iastLog = require('../../../iast-log')
|
|
4
|
+
|
|
5
|
+
const COMMAND_PATTERN = '^(?:\\s*(?:sudo|doas)\\s+)?\\b\\S+\\b\\s(.*)'
|
|
6
|
+
|
|
7
|
+
class CommandSensitiveAnalyzer {
|
|
8
|
+
constructor () {
|
|
9
|
+
this._pattern = new RegExp(COMMAND_PATTERN, 'gmi')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
extractSensitiveRanges (evidence) {
|
|
13
|
+
try {
|
|
14
|
+
this._pattern.lastIndex = 0
|
|
15
|
+
|
|
16
|
+
const regexResult = this._pattern.exec(evidence.value)
|
|
17
|
+
if (regexResult && regexResult.length > 1) {
|
|
18
|
+
const start = regexResult.index + (regexResult[0].length - regexResult[1].length)
|
|
19
|
+
const end = start + regexResult[1].length
|
|
20
|
+
return [{ start, end }]
|
|
21
|
+
}
|
|
22
|
+
} catch (e) {
|
|
23
|
+
iastLog.debug(e)
|
|
24
|
+
}
|
|
25
|
+
return []
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = CommandSensitiveAnalyzer
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const iastLog = require('../../../iast-log')
|
|
4
|
+
|
|
5
|
+
const LDAP_PATTERN = '\\(.*?(?:~=|=|<=|>=)(?<LITERAL>[^)]+)\\)'
|
|
6
|
+
|
|
7
|
+
class LdapSensitiveAnalyzer {
|
|
8
|
+
constructor () {
|
|
9
|
+
this._pattern = new RegExp(LDAP_PATTERN, 'gmi')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
extractSensitiveRanges (evidence) {
|
|
13
|
+
try {
|
|
14
|
+
this._pattern.lastIndex = 0
|
|
15
|
+
const tokens = []
|
|
16
|
+
|
|
17
|
+
let regexResult = this._pattern.exec(evidence.value)
|
|
18
|
+
while (regexResult != null) {
|
|
19
|
+
if (!regexResult.groups.LITERAL) continue
|
|
20
|
+
// Computing indices manually since NodeJs 12 does not support d flag on regular expressions
|
|
21
|
+
// TODO Get indices from group by adding d flag in regular expression
|
|
22
|
+
const start = regexResult.index + (regexResult[0].length - regexResult.groups.LITERAL.length - 1)
|
|
23
|
+
const end = start + regexResult.groups.LITERAL.length
|
|
24
|
+
tokens.push({ start, end })
|
|
25
|
+
regexResult = this._pattern.exec(evidence.value)
|
|
26
|
+
}
|
|
27
|
+
return tokens
|
|
28
|
+
} catch (e) {
|
|
29
|
+
iastLog.debug(e)
|
|
30
|
+
}
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = LdapSensitiveAnalyzer
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const iastLog = require('../../../iast-log')
|
|
4
|
+
|
|
5
|
+
const STRING_LITERAL = '\'(?:\'\'|[^\'])*\''
|
|
6
|
+
const POSTGRESQL_ESCAPED_LITERAL = '\\$([^$]*)\\$.*?\\$\\1\\$'
|
|
7
|
+
const MYSQL_STRING_LITERAL = '"(?:\\\\"|[^"])*"|\'(?:\\\\\'|[^\'])*\''
|
|
8
|
+
const LINE_COMMENT = '--.*$'
|
|
9
|
+
const BLOCK_COMMENT = '/\\*[\\s\\S]*\\*/'
|
|
10
|
+
const EXPONENT = '(?:E[-+]?\\d+[fd]?)?'
|
|
11
|
+
const INTEGER_NUMBER = '(?<!\\w)\\d+'
|
|
12
|
+
const DECIMAL_NUMBER = '\\d*\\.\\d+'
|
|
13
|
+
const HEX_NUMBER = 'x\'[0-9a-f]+\'|0x[0-9a-f]+'
|
|
14
|
+
const BIN_NUMBER = 'b\'[0-9a-f]+\'|0b[0-9a-f]+'
|
|
15
|
+
const NUMERIC_LITERAL =
|
|
16
|
+
`[-+]?(?:${
|
|
17
|
+
[
|
|
18
|
+
HEX_NUMBER,
|
|
19
|
+
BIN_NUMBER,
|
|
20
|
+
DECIMAL_NUMBER + EXPONENT,
|
|
21
|
+
INTEGER_NUMBER + EXPONENT
|
|
22
|
+
].join('|')
|
|
23
|
+
})`
|
|
24
|
+
const ORACLE_ESCAPED_LITERAL = 'q\'<.*?>\'|q\'\\(.*?\\)\'|q\'\\{.*?\\}\'|q\'\\[.*?\\]\'|q\'(?<ESCAPE>.).*?\\k<ESCAPE>\''
|
|
25
|
+
|
|
26
|
+
class SqlSensitiveAnalyzer {
|
|
27
|
+
constructor () {
|
|
28
|
+
this._patterns = {
|
|
29
|
+
ANSI: new RegExp( // Default
|
|
30
|
+
[
|
|
31
|
+
NUMERIC_LITERAL,
|
|
32
|
+
STRING_LITERAL,
|
|
33
|
+
LINE_COMMENT,
|
|
34
|
+
BLOCK_COMMENT
|
|
35
|
+
].join('|'),
|
|
36
|
+
'gmi'
|
|
37
|
+
),
|
|
38
|
+
MYSQL: new RegExp(
|
|
39
|
+
[
|
|
40
|
+
NUMERIC_LITERAL,
|
|
41
|
+
MYSQL_STRING_LITERAL,
|
|
42
|
+
LINE_COMMENT,
|
|
43
|
+
BLOCK_COMMENT
|
|
44
|
+
].join('|'),
|
|
45
|
+
'gmi'
|
|
46
|
+
),
|
|
47
|
+
POSTGRES: new RegExp(
|
|
48
|
+
[
|
|
49
|
+
NUMERIC_LITERAL,
|
|
50
|
+
POSTGRESQL_ESCAPED_LITERAL,
|
|
51
|
+
STRING_LITERAL,
|
|
52
|
+
LINE_COMMENT,
|
|
53
|
+
BLOCK_COMMENT
|
|
54
|
+
].join('|'),
|
|
55
|
+
'gmi'
|
|
56
|
+
),
|
|
57
|
+
ORACLE: new RegExp([
|
|
58
|
+
NUMERIC_LITERAL,
|
|
59
|
+
ORACLE_ESCAPED_LITERAL,
|
|
60
|
+
STRING_LITERAL,
|
|
61
|
+
LINE_COMMENT,
|
|
62
|
+
BLOCK_COMMENT
|
|
63
|
+
].join('|'),
|
|
64
|
+
'gmi')
|
|
65
|
+
}
|
|
66
|
+
this._patterns.SQLITE = this._patterns.MYSQL
|
|
67
|
+
this._patterns.MARIADB = this._patterns.MYSQL
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
extractSensitiveRanges (evidence) {
|
|
71
|
+
try {
|
|
72
|
+
let pattern = this._patterns[evidence.dialect]
|
|
73
|
+
if (!pattern) {
|
|
74
|
+
pattern = this._patterns['ANSI']
|
|
75
|
+
}
|
|
76
|
+
pattern.lastIndex = 0
|
|
77
|
+
const tokens = []
|
|
78
|
+
|
|
79
|
+
let regexResult = pattern.exec(evidence.value)
|
|
80
|
+
while (regexResult != null) {
|
|
81
|
+
let start = regexResult.index
|
|
82
|
+
let end = regexResult.index + regexResult[0].length
|
|
83
|
+
const startChar = evidence.value.charAt(start)
|
|
84
|
+
if (startChar === '\'' || startChar === '"') {
|
|
85
|
+
start++
|
|
86
|
+
end--
|
|
87
|
+
} else if (end > start + 1) {
|
|
88
|
+
const nextChar = evidence.value.charAt(start + 1)
|
|
89
|
+
if (startChar === '/' && nextChar === '*') {
|
|
90
|
+
start += 2
|
|
91
|
+
end -= 2
|
|
92
|
+
} else if (startChar === '-' && startChar === nextChar) {
|
|
93
|
+
start += 2
|
|
94
|
+
} else if (startChar.toLowerCase() === 'q' && nextChar === '\'') {
|
|
95
|
+
start += 3
|
|
96
|
+
end -= 2
|
|
97
|
+
} else if (startChar === '$') {
|
|
98
|
+
const match = regexResult[0]
|
|
99
|
+
const size = match.indexOf('$', 1) + 1
|
|
100
|
+
if (size > 1) {
|
|
101
|
+
start += size
|
|
102
|
+
end -= size
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
tokens.push({ start, end })
|
|
108
|
+
regexResult = pattern.exec(evidence.value)
|
|
109
|
+
}
|
|
110
|
+
return tokens
|
|
111
|
+
} catch (e) {
|
|
112
|
+
iastLog.debug(e)
|
|
113
|
+
}
|
|
114
|
+
return []
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = SqlSensitiveAnalyzer
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const iastLog = require('../../../iast-log')
|
|
4
|
+
|
|
5
|
+
const AUTHORITY = '^(?:[^:]+:)?//([^@]+)@'
|
|
6
|
+
const QUERY_FRAGMENT = '[?#&]([^=&;]+)=([^?#&]+)'
|
|
7
|
+
|
|
8
|
+
class UrlSensitiveAnalyzer {
|
|
9
|
+
constructor () {
|
|
10
|
+
this._pattern = new RegExp([AUTHORITY, QUERY_FRAGMENT].join('|'), 'gmi')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
extractSensitiveRanges (evidence) {
|
|
14
|
+
try {
|
|
15
|
+
const pattern = this._pattern
|
|
16
|
+
|
|
17
|
+
const ranges = []
|
|
18
|
+
let regexResult = pattern.exec(evidence.value)
|
|
19
|
+
|
|
20
|
+
while (regexResult != null) {
|
|
21
|
+
if (typeof regexResult[1] === 'string') {
|
|
22
|
+
// AUTHORITY regex match always ends by group + @
|
|
23
|
+
// it means that the match last chars - 1 are always the group
|
|
24
|
+
const end = regexResult.index + (regexResult[0].length - 1)
|
|
25
|
+
const start = end - regexResult[1].length
|
|
26
|
+
ranges.push({ start, end })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (typeof regexResult[3] === 'string') {
|
|
30
|
+
// QUERY_FRAGMENT regex always ends with the group
|
|
31
|
+
// it means that the match last chars are always the group
|
|
32
|
+
const end = regexResult.index + regexResult[0].length
|
|
33
|
+
const start = end - regexResult[3].length
|
|
34
|
+
ranges.push({ start, end })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
regexResult = pattern.exec(evidence.value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return ranges
|
|
41
|
+
} catch (e) {
|
|
42
|
+
iastLog.debug(e)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return []
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = UrlSensitiveAnalyzer
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const vulnerabilities = require('../../vulnerabilities')
|
|
4
|
+
|
|
5
|
+
const { contains, intersects, remove } = require('./range-utils')
|
|
6
|
+
|
|
7
|
+
const CommandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer')
|
|
8
|
+
const LdapSensitiveAnalyzer = require('./sensitive-analyzers/ldap-sensitive-analyzer')
|
|
9
|
+
const SqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyzer')
|
|
10
|
+
const UrlSensitiveAnalyzer = require('./sensitive-analyzers/url-sensitive-analyzer')
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line max-len
|
|
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,}))'
|
|
16
|
+
|
|
17
|
+
class SensitiveHandler {
|
|
18
|
+
constructor () {
|
|
19
|
+
this._namePattern = new RegExp(DEFAULT_IAST_REDACTION_NAME_PATTERN, 'gmi')
|
|
20
|
+
this._valuePattern = new RegExp(DEFAULT_IAST_REDACTION_VALUE_PATTERN, 'gmi')
|
|
21
|
+
|
|
22
|
+
this._sensitiveAnalyzers = new Map()
|
|
23
|
+
this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, new CommandSensitiveAnalyzer())
|
|
24
|
+
this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, new LdapSensitiveAnalyzer())
|
|
25
|
+
this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, new SqlSensitiveAnalyzer())
|
|
26
|
+
this._sensitiveAnalyzers.set(vulnerabilities.SSRF, new UrlSensitiveAnalyzer())
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
isSensibleName (name) {
|
|
30
|
+
this._namePattern.lastIndex = 0
|
|
31
|
+
return this._namePattern.test(name)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
isSensibleValue (value) {
|
|
35
|
+
this._valuePattern.lastIndex = 0
|
|
36
|
+
return this._valuePattern.test(value)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
isSensibleSource (source) {
|
|
40
|
+
return source != null && (this.isSensibleName(source.name) || this.isSensibleValue(source.value))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
scrubEvidence (vulnerabilityType, evidence, sourcesIndexes, sources) {
|
|
44
|
+
const sensitiveAnalyzer = this._sensitiveAnalyzers.get(vulnerabilityType)
|
|
45
|
+
if (sensitiveAnalyzer) {
|
|
46
|
+
const sensitiveRanges = sensitiveAnalyzer.extractSensitiveRanges(evidence)
|
|
47
|
+
return this.toRedactedJson(evidence, sensitiveRanges, sourcesIndexes, sources)
|
|
48
|
+
}
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
toRedactedJson (evidence, sensitive, sourcesIndexes, sources) {
|
|
53
|
+
const valueParts = []
|
|
54
|
+
const redactedSources = []
|
|
55
|
+
|
|
56
|
+
const { value, ranges } = evidence
|
|
57
|
+
|
|
58
|
+
let start = 0
|
|
59
|
+
let nextTaintedIndex = 0
|
|
60
|
+
let sourceIndex
|
|
61
|
+
|
|
62
|
+
let nextTainted = ranges.shift()
|
|
63
|
+
let nextSensitive = sensitive.shift()
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < value.length; i++) {
|
|
66
|
+
if (nextTainted != null && nextTainted.start === i) {
|
|
67
|
+
this.writeValuePart(valueParts, value.substring(start, i), sourceIndex)
|
|
68
|
+
|
|
69
|
+
sourceIndex = sourcesIndexes[nextTaintedIndex]
|
|
70
|
+
|
|
71
|
+
while (nextSensitive != null && contains(nextTainted, nextSensitive)) {
|
|
72
|
+
sourceIndex != null && redactedSources.push(sourceIndex)
|
|
73
|
+
nextSensitive = sensitive.shift()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (nextSensitive != null && intersects(nextSensitive, nextTainted)) {
|
|
77
|
+
sourceIndex != null && redactedSources.push(sourceIndex)
|
|
78
|
+
|
|
79
|
+
const entries = remove(nextSensitive, nextTainted)
|
|
80
|
+
nextSensitive = entries.length > 0 ? entries[0] : null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.isSensibleSource(sources[sourceIndex]) && redactedSources.push(sourceIndex)
|
|
84
|
+
|
|
85
|
+
if (redactedSources.indexOf(sourceIndex) > -1) {
|
|
86
|
+
this.writeRedactedValuePart(valueParts, sourceIndex)
|
|
87
|
+
} else {
|
|
88
|
+
const substringEnd = Math.min(nextTainted.end, value.length)
|
|
89
|
+
this.writeValuePart(valueParts, value.substring(nextTainted.start, substringEnd), sourceIndex)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
start = i + (nextTainted.end - nextTainted.start)
|
|
93
|
+
i = start - 1
|
|
94
|
+
nextTainted = ranges.shift()
|
|
95
|
+
nextTaintedIndex++
|
|
96
|
+
sourceIndex = null
|
|
97
|
+
} else if (nextSensitive != null && nextSensitive.start === i) {
|
|
98
|
+
this.writeValuePart(valueParts, value.substring(start, i), sourceIndex)
|
|
99
|
+
if (nextTainted != null && intersects(nextSensitive, nextTainted)) {
|
|
100
|
+
sourceIndex = sourcesIndexes[nextTaintedIndex]
|
|
101
|
+
sourceIndex != null && redactedSources.push(sourceIndex)
|
|
102
|
+
|
|
103
|
+
for (const entry of remove(nextSensitive, nextTainted)) {
|
|
104
|
+
if (entry.start === i) {
|
|
105
|
+
nextSensitive = entry
|
|
106
|
+
} else {
|
|
107
|
+
sensitive.unshift(entry)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.writeRedactedValuePart(valueParts)
|
|
113
|
+
|
|
114
|
+
start = i + (nextSensitive.end - nextSensitive.start)
|
|
115
|
+
i = start - 1
|
|
116
|
+
nextSensitive = sensitive.shift()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (start < value.length) {
|
|
121
|
+
this.writeValuePart(valueParts, value.substring(start))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { redactedValueParts: valueParts, redactedSources }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
writeValuePart (valueParts, value, source) {
|
|
128
|
+
if (value.length > 0) {
|
|
129
|
+
if (source != null) {
|
|
130
|
+
valueParts.push({ value, source })
|
|
131
|
+
} else {
|
|
132
|
+
valueParts.push({ value })
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
writeRedactedValuePart (valueParts, source) {
|
|
138
|
+
if (source != null) {
|
|
139
|
+
valueParts.push({ redacted: true, source })
|
|
140
|
+
} else {
|
|
141
|
+
valueParts.push({ redacted: true })
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = new SensitiveHandler()
|