dd-trace 3.19.0 → 3.21.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/grpc/client.js +9 -5
- package/packages/datadog-instrumentations/src/grpc/server.js +8 -4
- package/packages/datadog-instrumentations/src/helpers/register.js +4 -0
- package/packages/datadog-instrumentations/src/jest.js +20 -17
- package/packages/datadog-instrumentations/src/mocha.js +8 -4
- package/packages/datadog-instrumentations/src/next.js +6 -1
- package/packages/datadog-instrumentations/src/pg.js +3 -4
- package/packages/datadog-instrumentations/src/playwright.js +11 -1
- package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
- package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
- package/packages/datadog-plugin-amqplib/src/client.js +3 -4
- package/packages/datadog-plugin-amqplib/src/consumer.js +3 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +3 -1
- 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-google-cloud-pubsub/src/client.js +3 -4
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +3 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +3 -1
- package/packages/datadog-plugin-http/src/client.js +2 -1
- package/packages/datadog-plugin-http2/src/client.js +2 -1
- package/packages/datadog-plugin-jest/src/util.js +10 -1
- package/packages/datadog-plugin-kafkajs/src/consumer.js +6 -1
- package/packages/datadog-plugin-kafkajs/src/producer.js +3 -1
- package/packages/datadog-plugin-pg/src/index.js +5 -5
- package/packages/datadog-plugin-rhea/src/consumer.js +3 -1
- package/packages/datadog-plugin-rhea/src/producer.js +5 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +2 -1
- 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 +22 -5
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +40 -4
- 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 +2 -1
- 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 +95 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +144 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +113 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +8 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -76
- package/packages/dd-trace/src/config.js +59 -25
- package/packages/dd-trace/src/constants.js +3 -1
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -4
- 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 +0 -2
- package/packages/dd-trace/src/plugins/client.js +2 -3
- package/packages/dd-trace/src/plugins/consumer.js +2 -17
- package/packages/dd-trace/src/plugins/incoming.js +7 -0
- package/packages/dd-trace/src/plugins/{outbound.js → outgoing.js} +2 -2
- package/packages/dd-trace/src/plugins/producer.js +2 -17
- package/packages/dd-trace/src/plugins/server.js +2 -2
- package/packages/dd-trace/src/plugins/tracing.js +0 -11
- package/packages/dd-trace/src/plugins/util/ci.js +62 -7
- package/packages/dd-trace/src/plugins/util/tags.js +5 -1
- package/packages/dd-trace/src/plugins/util/test.js +34 -17
- 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/span_processor.js +3 -0
- package/packages/dd-trace/src/telemetry/send-data.js +13 -3
- package/packages/dd-trace/src/tracer.js +3 -2
- package/version.js +9 -0
- package/packages/dd-trace/src/plugins/inbound.js +0 -7
- package/packages/dd-trace/src/service-naming/index.js +0 -41
- package/packages/dd-trace/src/service-naming/schemas/definition.js +0 -28
- package/packages/dd-trace/src/service-naming/schemas/index.js +0 -6
- package/packages/dd-trace/src/service-naming/schemas/v0.js +0 -66
- package/packages/dd-trace/src/service-naming/schemas/v1.js +0 -58
|
@@ -9,7 +9,7 @@ class PGPlugin extends DatabasePlugin {
|
|
|
9
9
|
static get system () { return 'postgres' }
|
|
10
10
|
|
|
11
11
|
start ({ params = {}, query, processId }) {
|
|
12
|
-
const service = getServiceName(this
|
|
12
|
+
const service = getServiceName(this, params)
|
|
13
13
|
const originalStatement = query.text
|
|
14
14
|
|
|
15
15
|
this.startSpan('pg.query', {
|
|
@@ -31,12 +31,12 @@ class PGPlugin extends DatabasePlugin {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function getServiceName (
|
|
35
|
-
if (typeof config.service === 'function') {
|
|
36
|
-
return config.service(params)
|
|
34
|
+
function getServiceName (tracer, params) {
|
|
35
|
+
if (typeof tracer.config.service === 'function') {
|
|
36
|
+
return tracer.config.service(params)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
return config.service
|
|
39
|
+
return tracer.config.service || `${tracer._tracer._tracer._service}-postgres`
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
module.exports = PGPlugin
|
|
@@ -19,10 +19,12 @@ class RheaConsumerPlugin extends ConsumerPlugin {
|
|
|
19
19
|
const name = getResourceNameFromMessage(msgObj)
|
|
20
20
|
const childOf = extractTextMap(msgObj, this.tracer)
|
|
21
21
|
|
|
22
|
-
this.startSpan({
|
|
22
|
+
this.startSpan('amqp.receive', {
|
|
23
23
|
childOf,
|
|
24
|
+
service: this.config.service,
|
|
24
25
|
resource: name,
|
|
25
26
|
type: 'worker',
|
|
27
|
+
kind: 'consumer',
|
|
26
28
|
meta: {
|
|
27
29
|
'component': 'rhea',
|
|
28
30
|
'amqp.link.source.address': name,
|
|
@@ -9,13 +9,17 @@ class RheaProducerPlugin extends ProducerPlugin {
|
|
|
9
9
|
|
|
10
10
|
constructor (...args) {
|
|
11
11
|
super(...args)
|
|
12
|
+
|
|
12
13
|
this.addTraceSub('encode', this.encode.bind(this))
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
start ({ targetAddress, host, port }) {
|
|
16
17
|
const name = targetAddress || 'amq.topic'
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
this.startSpan('amqp.send', {
|
|
20
|
+
service: this.config.service || `${this.tracer._service}-amqp-producer`,
|
|
18
21
|
resource: name,
|
|
22
|
+
kind: 'producer',
|
|
19
23
|
meta: {
|
|
20
24
|
'component': 'rhea',
|
|
21
25
|
'amqp.link.target.address': name,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const InjectionAnalyzer = require('./injection-analyzer')
|
|
3
|
+
const { COMMAND_INJECTION } = require('../vulnerabilities')
|
|
3
4
|
|
|
4
5
|
class CommandInjectionAnalyzer extends InjectionAnalyzer {
|
|
5
6
|
constructor () {
|
|
6
|
-
super(
|
|
7
|
+
super(COMMAND_INJECTION)
|
|
7
8
|
this.addSub('datadog:child_process:execution:start', ({ command }) => this.analyze(command))
|
|
8
9
|
}
|
|
9
10
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const InjectionAnalyzer = require('./injection-analyzer')
|
|
3
|
+
const { LDAP_INJECTION } = require('../vulnerabilities')
|
|
3
4
|
|
|
4
5
|
class LdapInjectionAnalyzer extends InjectionAnalyzer {
|
|
5
6
|
constructor () {
|
|
6
|
-
super(
|
|
7
|
+
super(LDAP_INJECTION)
|
|
7
8
|
this.addSub('datadog:ldapjs:client:search', ({ base, filter }) => this.analyzeAll(base, filter))
|
|
8
9
|
}
|
|
9
10
|
}
|
|
@@ -4,10 +4,11 @@ const path = require('path')
|
|
|
4
4
|
const { getIastContext } = require('../iast-context')
|
|
5
5
|
const { storage } = require('../../../../../datadog-core')
|
|
6
6
|
const InjectionAnalyzer = require('./injection-analyzer')
|
|
7
|
+
const { PATH_TRAVERSAL } = require('../vulnerabilities')
|
|
7
8
|
|
|
8
9
|
class PathTraversalAnalyzer extends InjectionAnalyzer {
|
|
9
10
|
constructor () {
|
|
10
|
-
super(
|
|
11
|
+
super(PATH_TRAVERSAL)
|
|
11
12
|
this.addSub('apm:fs:operation:start', obj => {
|
|
12
13
|
const pathArguments = []
|
|
13
14
|
if (obj.dest) {
|
|
@@ -40,13 +41,29 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
|
|
|
40
41
|
this.analyze(pathArguments)
|
|
41
42
|
})
|
|
42
43
|
|
|
43
|
-
this.exclusionList = [
|
|
44
|
+
this.exclusionList = [
|
|
45
|
+
path.join('node_modules', 'send') + path.sep
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
this.internalExclusionList = [
|
|
49
|
+
'node:fs',
|
|
50
|
+
'node:internal/fs',
|
|
51
|
+
'node:internal\\fs',
|
|
52
|
+
'fs.js',
|
|
53
|
+
'internal/fs',
|
|
54
|
+
'internal\\fs'
|
|
55
|
+
]
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
_isExcluded (location) {
|
|
47
|
-
let ret =
|
|
59
|
+
let ret = true
|
|
48
60
|
if (location && location.path) {
|
|
49
|
-
|
|
61
|
+
// Exclude from reporting those vulnerabilities which location is from an internal fs call
|
|
62
|
+
if (location.isInternal) {
|
|
63
|
+
ret = this.internalExclusionList.some(elem => location.path.includes(elem))
|
|
64
|
+
} else {
|
|
65
|
+
ret = this.exclusionList.some(elem => location.path.includes(elem))
|
|
66
|
+
}
|
|
50
67
|
}
|
|
51
68
|
return ret
|
|
52
69
|
}
|
|
@@ -59,7 +76,7 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
|
|
|
59
76
|
|
|
60
77
|
if (value && value.constructor === Array) {
|
|
61
78
|
for (const val of value) {
|
|
62
|
-
if (this._isVulnerable(val, iastContext)) {
|
|
79
|
+
if (this._isVulnerable(val, iastContext) && this._checkOCE(iastContext)) {
|
|
63
80
|
this._report(val, iastContext)
|
|
64
81
|
// no support several evidences in the same vulnerability, just report the 1st one
|
|
65
82
|
break
|
|
@@ -1,12 +1,48 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
+
|
|
2
3
|
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
|
+
const { SQL_INJECTION } = require('../vulnerabilities')
|
|
5
|
+
const { getRanges } = require('../taint-tracking/operations')
|
|
6
|
+
const { storage } = require('../../../../../datadog-core')
|
|
7
|
+
const { getIastContext } = require('../iast-context')
|
|
8
|
+
const { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
|
|
3
9
|
|
|
4
10
|
class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
5
11
|
constructor () {
|
|
6
|
-
super(
|
|
7
|
-
this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql))
|
|
8
|
-
this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql))
|
|
9
|
-
this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text))
|
|
12
|
+
super(SQL_INJECTION)
|
|
13
|
+
this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
|
|
14
|
+
this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
|
|
15
|
+
this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, 'POSTGRES'))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_getEvidence (value, iastContext, dialect) {
|
|
19
|
+
const ranges = getRanges(iastContext, value)
|
|
20
|
+
return { value, ranges, dialect }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
analyze (value, dialect) {
|
|
24
|
+
const store = storage.getStore()
|
|
25
|
+
const iastContext = getIastContext(store)
|
|
26
|
+
if (store && !iastContext) return
|
|
27
|
+
this._reportIfVulnerable(value, iastContext, dialect)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_reportIfVulnerable (value, context, dialect) {
|
|
31
|
+
if (this._isVulnerable(value, context) && this._checkOCE(context)) {
|
|
32
|
+
this._report(value, context, dialect)
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_report (value, context, dialect) {
|
|
39
|
+
const evidence = this._getEvidence(value, context, dialect)
|
|
40
|
+
const location = this._getLocation()
|
|
41
|
+
if (!this._isExcluded(location)) {
|
|
42
|
+
const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
|
|
43
|
+
const vulnerability = createVulnerability(this._type, evidence, spanId, location)
|
|
44
|
+
addVulnerability(context, vulnerability)
|
|
45
|
+
}
|
|
10
46
|
}
|
|
11
47
|
}
|
|
12
48
|
|
|
@@ -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
|
|
|
@@ -45,7 +45,8 @@ function getFirstNonDDPathAndLineFromCallsites (callsites) {
|
|
|
45
45
|
if (!isExcluded(callsite) && 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
|
}
|
|
@@ -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,95 @@
|
|
|
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
|
+
|
|
25
|
+
class SqlSensitiveAnalyzer {
|
|
26
|
+
constructor () {
|
|
27
|
+
this._patterns = {
|
|
28
|
+
MYSQL: new RegExp(
|
|
29
|
+
[
|
|
30
|
+
NUMERIC_LITERAL,
|
|
31
|
+
MYSQL_STRING_LITERAL,
|
|
32
|
+
LINE_COMMENT,
|
|
33
|
+
BLOCK_COMMENT
|
|
34
|
+
].join('|'),
|
|
35
|
+
'gmi'
|
|
36
|
+
),
|
|
37
|
+
POSTGRES: new RegExp(
|
|
38
|
+
[
|
|
39
|
+
NUMERIC_LITERAL,
|
|
40
|
+
POSTGRESQL_ESCAPED_LITERAL,
|
|
41
|
+
STRING_LITERAL,
|
|
42
|
+
LINE_COMMENT,
|
|
43
|
+
BLOCK_COMMENT
|
|
44
|
+
].join('|'),
|
|
45
|
+
'gmi'
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
extractSensitiveRanges (evidence) {
|
|
51
|
+
try {
|
|
52
|
+
const pattern = this._patterns[evidence.dialect]
|
|
53
|
+
pattern.lastIndex = 0
|
|
54
|
+
const tokens = []
|
|
55
|
+
|
|
56
|
+
let regexResult = pattern.exec(evidence.value)
|
|
57
|
+
while (regexResult != null) {
|
|
58
|
+
let start = regexResult.index
|
|
59
|
+
let end = regexResult.index + regexResult[0].length
|
|
60
|
+
const startChar = evidence.value.charAt(start)
|
|
61
|
+
if (startChar === '\'' || startChar === '"') {
|
|
62
|
+
start++
|
|
63
|
+
end--
|
|
64
|
+
} else if (end > start + 1) {
|
|
65
|
+
const nextChar = evidence.value.charAt(start + 1)
|
|
66
|
+
if (startChar === '/' && nextChar === '*') {
|
|
67
|
+
start += 2
|
|
68
|
+
end -= 2
|
|
69
|
+
} else if (startChar === '-' && startChar === nextChar) {
|
|
70
|
+
start += 2
|
|
71
|
+
} else if (startChar.toLowerCase() === 'q' && nextChar === '\'') {
|
|
72
|
+
start += 3
|
|
73
|
+
end -= 2
|
|
74
|
+
} else if (startChar === '$') {
|
|
75
|
+
const match = regexResult[0]
|
|
76
|
+
const size = match.indexOf('$', 1) + 1
|
|
77
|
+
if (size > 1) {
|
|
78
|
+
start += size
|
|
79
|
+
end -= size
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
tokens.push({ start, end })
|
|
85
|
+
regexResult = pattern.exec(evidence.value)
|
|
86
|
+
}
|
|
87
|
+
return tokens
|
|
88
|
+
} catch (e) {
|
|
89
|
+
iastLog.debug(e)
|
|
90
|
+
}
|
|
91
|
+
return []
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = SqlSensitiveAnalyzer
|
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
|
|
11
|
+
// eslint-disable-next-line max-len
|
|
12
|
+
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)?)'
|
|
13
|
+
// eslint-disable-next-line max-len
|
|
14
|
+
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,}))'
|
|
15
|
+
|
|
16
|
+
class SensitiveHandler {
|
|
17
|
+
constructor () {
|
|
18
|
+
this._namePattern = new RegExp(DEFAULT_IAST_REDACTION_NAME_PATTERN, 'gmi')
|
|
19
|
+
this._valuePattern = new RegExp(DEFAULT_IAST_REDACTION_VALUE_PATTERN, 'gmi')
|
|
20
|
+
|
|
21
|
+
this._sensitiveAnalyzers = new Map()
|
|
22
|
+
this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, new CommandSensitiveAnalyzer())
|
|
23
|
+
this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, new LdapSensitiveAnalyzer())
|
|
24
|
+
this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, new SqlSensitiveAnalyzer())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
isSensibleName (name) {
|
|
28
|
+
this._namePattern.lastIndex = 0
|
|
29
|
+
return this._namePattern.test(name)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
isSensibleValue (value) {
|
|
33
|
+
this._valuePattern.lastIndex = 0
|
|
34
|
+
return this._valuePattern.test(value)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
isSensibleSource (source) {
|
|
38
|
+
return source != null && (this.isSensibleName(source.name) || this.isSensibleValue(source.value))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
scrubEvidence (vulnerabilityType, evidence, sourcesIndexes, sources) {
|
|
42
|
+
const sensitiveAnalyzer = this._sensitiveAnalyzers.get(vulnerabilityType)
|
|
43
|
+
if (sensitiveAnalyzer) {
|
|
44
|
+
const sensitiveRanges = sensitiveAnalyzer.extractSensitiveRanges(evidence)
|
|
45
|
+
return this.toRedactedJson(evidence, sensitiveRanges, sourcesIndexes, sources)
|
|
46
|
+
}
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
toRedactedJson (evidence, sensitive, sourcesIndexes, sources) {
|
|
51
|
+
const valueParts = []
|
|
52
|
+
const redactedSources = []
|
|
53
|
+
|
|
54
|
+
const { value, ranges } = evidence
|
|
55
|
+
|
|
56
|
+
let start = 0
|
|
57
|
+
let nextTaintedIndex = 0
|
|
58
|
+
let sourceIndex
|
|
59
|
+
|
|
60
|
+
let nextTainted = ranges.shift()
|
|
61
|
+
let nextSensitive = sensitive.shift()
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < value.length; i++) {
|
|
64
|
+
if (nextTainted != null && nextTainted.start === i) {
|
|
65
|
+
this.writeValuePart(valueParts, value.substring(start, i), sourceIndex)
|
|
66
|
+
|
|
67
|
+
sourceIndex = sourcesIndexes[nextTaintedIndex]
|
|
68
|
+
|
|
69
|
+
while (nextSensitive != null && contains(nextTainted, nextSensitive)) {
|
|
70
|
+
sourceIndex != null && redactedSources.push(sourceIndex)
|
|
71
|
+
nextSensitive = sensitive.shift()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (nextSensitive != null && intersects(nextSensitive, nextTainted)) {
|
|
75
|
+
sourceIndex != null && redactedSources.push(sourceIndex)
|
|
76
|
+
|
|
77
|
+
const entries = remove(nextSensitive, nextTainted)
|
|
78
|
+
nextSensitive = entries.length > 0 ? entries[0] : null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.isSensibleSource(sources[sourceIndex]) && redactedSources.push(sourceIndex)
|
|
82
|
+
|
|
83
|
+
if (redactedSources.indexOf(sourceIndex) > -1) {
|
|
84
|
+
this.writeRedactedValuePart(valueParts, sourceIndex)
|
|
85
|
+
} else {
|
|
86
|
+
const substringEnd = Math.min(nextTainted.end, value.length)
|
|
87
|
+
this.writeValuePart(valueParts, value.substring(nextTainted.start, substringEnd), sourceIndex)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
start = i + (nextTainted.end - nextTainted.start)
|
|
91
|
+
i = start - 1
|
|
92
|
+
nextTainted = ranges.shift()
|
|
93
|
+
nextTaintedIndex++
|
|
94
|
+
sourceIndex = null
|
|
95
|
+
} else if (nextSensitive != null && nextSensitive.start === i) {
|
|
96
|
+
this.writeValuePart(valueParts, value.substring(start, i), sourceIndex)
|
|
97
|
+
if (nextTainted != null && intersects(nextSensitive, nextTainted)) {
|
|
98
|
+
sourceIndex = sourcesIndexes[nextTaintedIndex]
|
|
99
|
+
sourceIndex != null && redactedSources.push(sourceIndex)
|
|
100
|
+
|
|
101
|
+
for (const entry of remove(nextSensitive, nextTainted)) {
|
|
102
|
+
if (entry.start === i) {
|
|
103
|
+
nextSensitive = entry
|
|
104
|
+
} else {
|
|
105
|
+
sensitive.push(entry)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.writeRedactedValuePart(valueParts)
|
|
111
|
+
|
|
112
|
+
start = i + (nextSensitive.end - nextSensitive.start)
|
|
113
|
+
i = start - 1
|
|
114
|
+
nextSensitive = sensitive.shift()
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (start < value.length) {
|
|
119
|
+
this.writeValuePart(valueParts, value.substring(start))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { redactedValueParts: valueParts, redactedSources }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
writeValuePart (valueParts, value, source) {
|
|
126
|
+
if (value.length > 0) {
|
|
127
|
+
if (source != null) {
|
|
128
|
+
valueParts.push({ value, source })
|
|
129
|
+
} else {
|
|
130
|
+
valueParts.push({ value })
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
writeRedactedValuePart (valueParts, source) {
|
|
136
|
+
if (source != null) {
|
|
137
|
+
valueParts.push({ redacted: true, source })
|
|
138
|
+
} else {
|
|
139
|
+
valueParts.push({ redacted: true })
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = new SensitiveHandler()
|