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.
Files changed (120) hide show
  1. package/index.d.ts +8 -1
  2. package/package.json +5 -4
  3. package/packages/datadog-instrumentations/src/cucumber.js +13 -0
  4. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  5. package/packages/datadog-instrumentations/src/helpers/register.js +4 -0
  6. package/packages/datadog-instrumentations/src/http/client.js +2 -1
  7. package/packages/datadog-instrumentations/src/http/server.js +14 -0
  8. package/packages/datadog-instrumentations/src/http2/client.js +4 -0
  9. package/packages/datadog-instrumentations/src/jest.js +20 -17
  10. package/packages/datadog-instrumentations/src/next.js +6 -1
  11. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  12. package/packages/datadog-instrumentations/src/sequelize.js +51 -0
  13. package/packages/datadog-plugin-amqp10/src/consumer.js +1 -3
  14. package/packages/datadog-plugin-amqp10/src/producer.js +1 -3
  15. package/packages/datadog-plugin-amqplib/src/client.js +4 -3
  16. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  17. package/packages/datadog-plugin-amqplib/src/producer.js +1 -3
  18. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -0
  19. package/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +2 -1
  20. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +4 -2
  21. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +4 -3
  22. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -1
  23. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -0
  24. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +2 -1
  25. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +2 -1
  26. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +8 -1
  27. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +7 -1
  28. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  29. package/packages/datadog-plugin-cypress/src/plugin.js +150 -30
  30. package/packages/datadog-plugin-cypress/src/support.js +6 -3
  31. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -3
  32. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -3
  33. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -3
  34. package/packages/datadog-plugin-http/src/client.js +70 -67
  35. package/packages/datadog-plugin-http2/src/client.js +50 -46
  36. package/packages/datadog-plugin-jest/src/index.js +5 -4
  37. package/packages/datadog-plugin-jest/src/util.js +10 -1
  38. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -4
  39. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -3
  40. package/packages/datadog-plugin-memcached/src/index.js +2 -3
  41. package/packages/datadog-plugin-mocha/src/index.js +4 -2
  42. package/packages/datadog-plugin-pg/src/index.js +1 -1
  43. package/packages/datadog-plugin-redis/src/index.js +2 -13
  44. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  45. package/packages/datadog-plugin-rhea/src/producer.js +1 -5
  46. package/packages/dd-trace/src/appsec/blocked_templates.js +2 -101
  47. package/packages/dd-trace/src/appsec/blocking.js +60 -11
  48. package/packages/dd-trace/src/appsec/channels.js +3 -2
  49. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +7 -5
  50. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +2 -1
  51. package/packages/dd-trace/src/appsec/iast/analyzers/index.js +3 -0
  52. package/packages/dd-trace/src/appsec/iast/analyzers/insecure-cookie-analyzer.js +31 -0
  53. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +2 -1
  54. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +26 -5
  55. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +47 -0
  56. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +65 -4
  57. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +26 -0
  58. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +35 -3
  59. package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +2 -1
  60. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +2 -1
  61. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  62. package/packages/dd-trace/src/appsec/iast/path-line.js +16 -8
  63. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +19 -4
  64. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/range-utils.js +37 -0
  65. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +29 -0
  66. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +35 -0
  67. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +118 -0
  68. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +49 -0
  69. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +146 -0
  70. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +113 -0
  71. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +10 -0
  72. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -109
  73. package/packages/dd-trace/src/appsec/recommended.json +45 -46
  74. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +3 -1
  75. package/packages/dd-trace/src/appsec/remote_config/index.js +4 -0
  76. package/packages/dd-trace/src/appsec/rule_manager.js +49 -6
  77. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -7
  78. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  79. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -6
  80. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +10 -4
  81. package/packages/dd-trace/src/config.js +86 -9
  82. package/packages/dd-trace/src/constants.js +3 -1
  83. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +11 -3
  84. package/packages/dd-trace/src/exporters/common/util.js +9 -0
  85. package/packages/dd-trace/src/exporters/common/writer.js +3 -2
  86. package/packages/dd-trace/src/git_metadata_tagger.js +17 -0
  87. package/packages/dd-trace/src/git_properties.js +32 -0
  88. package/packages/dd-trace/src/plugin_manager.js +2 -0
  89. package/packages/dd-trace/src/plugins/cache.js +7 -0
  90. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
  91. package/packages/dd-trace/src/plugins/client.js +3 -2
  92. package/packages/dd-trace/src/plugins/consumer.js +14 -2
  93. package/packages/dd-trace/src/plugins/database.js +2 -2
  94. package/packages/dd-trace/src/plugins/inbound.js +7 -0
  95. package/packages/dd-trace/src/plugins/{outgoing.js → outbound.js} +2 -2
  96. package/packages/dd-trace/src/plugins/producer.js +19 -2
  97. package/packages/dd-trace/src/plugins/server.js +2 -2
  98. package/packages/dd-trace/src/plugins/storage.js +2 -0
  99. package/packages/dd-trace/src/plugins/tracing.js +11 -0
  100. package/packages/dd-trace/src/plugins/util/ci.js +63 -8
  101. package/packages/dd-trace/src/plugins/util/tags.js +5 -1
  102. package/packages/dd-trace/src/profiling/config.js +4 -2
  103. package/packages/dd-trace/src/profiling/constants.js +0 -1
  104. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  105. package/packages/dd-trace/src/proxy.js +4 -0
  106. package/packages/dd-trace/src/serverless.js +25 -0
  107. package/packages/dd-trace/src/service-naming/index.js +30 -0
  108. package/packages/dd-trace/src/service-naming/schemas/definition.js +24 -0
  109. package/packages/dd-trace/src/service-naming/schemas/index.js +6 -0
  110. package/packages/dd-trace/src/service-naming/schemas/util.js +5 -0
  111. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +5 -0
  112. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +64 -0
  113. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +33 -0
  114. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +5 -0
  115. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +52 -0
  116. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +21 -0
  117. package/packages/dd-trace/src/span_processor.js +3 -0
  118. package/packages/dd-trace/src/tracer.js +3 -2
  119. package/version.js +9 -0
  120. 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 { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
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 = createVulnerability(this._type, evidence, spanId, location)
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('WEAK_CIPHER')
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('WEAK_HASH')
13
+ super(WEAK_HASH)
13
14
  this.addSub('datadog:crypto:hashing:start', ({ algorithm }) => this.analyze(algorithm))
14
15
  }
15
16
 
@@ -51,7 +51,7 @@ function onIncomingHttpRequestStart (data) {
51
51
  }
52
52
  if (rootSpan.addTags) {
53
53
  rootSpan.addTags({
54
- [IAST_ENABLED_TAG_KEY]: isRequestAcquired ? '1' : '0'
54
+ [IAST_ENABLED_TAG_KEY]: isRequestAcquired ? 1 : 0
55
55
  })
56
56
  }
57
57
  }
@@ -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
- for (let i = 0; i < EXCLUDED_PATHS.length; i++) {
63
- if (filename.indexOf(EXCLUDED_PATHS[i]) > -1) {
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 }) => this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body')
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
- target = taintObject(iastContext, target, type)
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()