dd-trace 4.20.0 → 4.22.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 +1 -0
- package/index.d.ts +29 -0
- package/package.json +8 -7
- package/packages/datadog-instrumentations/src/aerospike.js +47 -0
- package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
- package/packages/datadog-instrumentations/src/graphql.js +18 -4
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/http/client.js +10 -0
- package/packages/datadog-instrumentations/src/jest.js +11 -5
- package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
- package/packages/datadog-instrumentations/src/next.js +18 -6
- package/packages/datadog-instrumentations/src/restify.js +1 -1
- package/packages/datadog-instrumentations/src/rhea.js +15 -9
- package/packages/datadog-plugin-aerospike/src/index.js +113 -0
- package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
- package/packages/datadog-plugin-http/src/client.js +19 -2
- package/packages/datadog-plugin-kafkajs/src/consumer.js +51 -0
- package/packages/datadog-plugin-kafkajs/src/producer.js +55 -0
- package/packages/datadog-plugin-next/src/index.js +40 -14
- package/packages/dd-trace/src/appsec/activation.js +29 -0
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
- package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
- package/packages/dd-trace/src/appsec/blocking.js +95 -43
- package/packages/dd-trace/src/appsec/channels.js +4 -1
- package/packages/dd-trace/src/appsec/graphql.js +146 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +32 -31
- package/packages/dd-trace/src/appsec/recommended.json +1395 -2
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +36 -15
- package/packages/dd-trace/src/appsec/reporter.js +19 -0
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +15 -9
- package/packages/dd-trace/src/config.js +36 -2
- package/packages/dd-trace/src/datastreams/processor.js +107 -12
- package/packages/dd-trace/src/noop/proxy.js +4 -0
- package/packages/dd-trace/src/opentracing/span.js +2 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/git.js +2 -2
- package/packages/dd-trace/src/plugins/util/test.js +3 -2
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +3 -2
- package/packages/dd-trace/src/profiler.js +5 -3
- package/packages/dd-trace/src/profiling/config.js +8 -0
- package/packages/dd-trace/src/profiling/profiler.js +17 -10
- package/packages/dd-trace/src/profiling/profilers/events.js +181 -83
- package/packages/dd-trace/src/profiling/profilers/shared.js +33 -3
- package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +17 -12
- package/packages/dd-trace/src/proxy.js +25 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
- package/packages/dd-trace/src/spanleak.js +98 -0
- package/packages/dd-trace/src/startup-log.js +7 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +55 -9
- package/packages/dd-trace/src/telemetry/index.js +135 -43
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/telemetry/send-data.js +47 -5
- package/packages/dd-trace/src/tracer.js +4 -0
- package/scripts/install_plugin_modules.js +11 -3
|
@@ -3,93 +3,142 @@
|
|
|
3
3
|
const log = require('../log')
|
|
4
4
|
const blockedTemplates = require('./blocked_templates')
|
|
5
5
|
|
|
6
|
+
const detectedSpecificEndpoints = {}
|
|
7
|
+
|
|
6
8
|
let templateHtml = blockedTemplates.html
|
|
7
9
|
let templateJson = blockedTemplates.json
|
|
10
|
+
let templateGraphqlJson = blockedTemplates.graphqlJson
|
|
8
11
|
let blockingConfiguration
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
})
|
|
13
|
+
const specificBlockingTypes = {
|
|
14
|
+
GRAPHQL: 'graphql'
|
|
15
|
+
}
|
|
14
16
|
|
|
17
|
+
function getSpecificKey (method, url) {
|
|
18
|
+
return `${method}+${url}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function addSpecificEndpoint (method, url, type) {
|
|
22
|
+
detectedSpecificEndpoints[getSpecificKey(method, url)] = type
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getBlockWithRedirectData (rootSpan) {
|
|
15
26
|
let statusCode = blockingConfiguration.parameters.status_code
|
|
16
27
|
if (!statusCode || statusCode < 300 || statusCode >= 400) {
|
|
17
28
|
statusCode = 303
|
|
18
29
|
}
|
|
19
|
-
|
|
20
|
-
res.writeHead(statusCode, {
|
|
30
|
+
const headers = {
|
|
21
31
|
'Location': blockingConfiguration.parameters.location
|
|
22
|
-
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
rootSpan.addTags({
|
|
35
|
+
'appsec.blocked': 'true'
|
|
36
|
+
})
|
|
23
37
|
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
return { headers, statusCode }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getSpecificBlockingData (type) {
|
|
42
|
+
switch (type) {
|
|
43
|
+
case specificBlockingTypes.GRAPHQL:
|
|
44
|
+
return {
|
|
45
|
+
type: 'application/json',
|
|
46
|
+
body: templateGraphqlJson
|
|
47
|
+
}
|
|
26
48
|
}
|
|
27
49
|
}
|
|
28
50
|
|
|
29
|
-
function
|
|
51
|
+
function getBlockWithContentData (req, specificType, rootSpan) {
|
|
30
52
|
let type
|
|
31
53
|
let body
|
|
54
|
+
let statusCode
|
|
32
55
|
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
const specificBlockingType = specificType || detectedSpecificEndpoints[getSpecificKey(req.method, req.url)]
|
|
57
|
+
if (specificBlockingType) {
|
|
58
|
+
const specificBlockingContent = getSpecificBlockingData(specificBlockingType)
|
|
59
|
+
type = specificBlockingContent?.type
|
|
60
|
+
body = specificBlockingContent?.body
|
|
61
|
+
}
|
|
35
62
|
|
|
36
|
-
if (!
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
63
|
+
if (!type) {
|
|
64
|
+
// parse the Accept header, ex: Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
|
|
65
|
+
const accept = req.headers.accept?.split(',').map((str) => str.split(';', 1)[0].trim())
|
|
66
|
+
|
|
67
|
+
if (!blockingConfiguration || blockingConfiguration.parameters.type === 'auto') {
|
|
68
|
+
if (accept?.includes('text/html') && !accept.includes('application/json')) {
|
|
69
|
+
type = 'text/html; charset=utf-8'
|
|
70
|
+
body = templateHtml
|
|
71
|
+
} else {
|
|
72
|
+
type = 'application/json'
|
|
73
|
+
body = templateJson
|
|
74
|
+
}
|
|
40
75
|
} else {
|
|
41
|
-
type
|
|
42
|
-
|
|
76
|
+
if (blockingConfiguration.parameters.type === 'html') {
|
|
77
|
+
type = 'text/html; charset=utf-8'
|
|
78
|
+
body = templateHtml
|
|
79
|
+
} else {
|
|
80
|
+
type = 'application/json'
|
|
81
|
+
body = templateJson
|
|
82
|
+
}
|
|
43
83
|
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (blockingConfiguration?.type === 'block_request' && blockingConfiguration.parameters.status_code) {
|
|
87
|
+
statusCode = blockingConfiguration.parameters.status_code
|
|
44
88
|
} else {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
89
|
+
statusCode = 403
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const headers = {
|
|
93
|
+
'Content-Type': type,
|
|
94
|
+
'Content-Length': Buffer.byteLength(body)
|
|
52
95
|
}
|
|
53
96
|
|
|
54
97
|
rootSpan.addTags({
|
|
55
98
|
'appsec.blocked': 'true'
|
|
56
99
|
})
|
|
57
100
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
res.statusCode = blockingConfiguration.parameters.status_code
|
|
61
|
-
} else {
|
|
62
|
-
res.statusCode = 403
|
|
63
|
-
}
|
|
64
|
-
res.setHeader('Content-Type', type)
|
|
65
|
-
res.setHeader('Content-Length', Buffer.byteLength(body))
|
|
66
|
-
res.end(body)
|
|
101
|
+
return { body, statusCode, headers }
|
|
102
|
+
}
|
|
67
103
|
|
|
68
|
-
|
|
69
|
-
|
|
104
|
+
function getBlockingData (req, specificType, rootSpan) {
|
|
105
|
+
if (blockingConfiguration?.type === 'redirect_request' && blockingConfiguration.parameters.location) {
|
|
106
|
+
return getBlockWithRedirectData(rootSpan)
|
|
107
|
+
} else {
|
|
108
|
+
return getBlockWithContentData(req, specificType, rootSpan)
|
|
70
109
|
}
|
|
71
110
|
}
|
|
72
111
|
|
|
73
|
-
function block (req, res, rootSpan, abortController) {
|
|
112
|
+
function block (req, res, rootSpan, abortController, type) {
|
|
74
113
|
if (res.headersSent) {
|
|
75
114
|
log.warn('Cannot send blocking response when headers have already been sent')
|
|
76
115
|
return
|
|
77
116
|
}
|
|
78
117
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
118
|
+
const { body, headers, statusCode } = getBlockingData(req, type, rootSpan)
|
|
119
|
+
|
|
120
|
+
res.writeHead(statusCode, headers).end(body)
|
|
121
|
+
|
|
122
|
+
abortController?.abort()
|
|
85
123
|
}
|
|
86
124
|
|
|
87
125
|
function setTemplates (config) {
|
|
88
126
|
if (config.appsec.blockedTemplateHtml) {
|
|
89
127
|
templateHtml = config.appsec.blockedTemplateHtml
|
|
128
|
+
} else {
|
|
129
|
+
templateHtml = blockedTemplates.html
|
|
90
130
|
}
|
|
131
|
+
|
|
91
132
|
if (config.appsec.blockedTemplateJson) {
|
|
92
133
|
templateJson = config.appsec.blockedTemplateJson
|
|
134
|
+
} else {
|
|
135
|
+
templateJson = blockedTemplates.json
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (config.appsec.blockedTemplateGraphql) {
|
|
139
|
+
templateGraphqlJson = config.appsec.blockedTemplateGraphql
|
|
140
|
+
} else {
|
|
141
|
+
templateGraphqlJson = blockedTemplates.graphqlJson
|
|
93
142
|
}
|
|
94
143
|
}
|
|
95
144
|
|
|
@@ -98,7 +147,10 @@ function updateBlockingConfiguration (newBlockingConfiguration) {
|
|
|
98
147
|
}
|
|
99
148
|
|
|
100
149
|
module.exports = {
|
|
150
|
+
addSpecificEndpoint,
|
|
101
151
|
block,
|
|
152
|
+
specificBlockingTypes,
|
|
153
|
+
getBlockingData,
|
|
102
154
|
setTemplates,
|
|
103
155
|
updateBlockingConfiguration
|
|
104
156
|
}
|
|
@@ -6,7 +6,10 @@ const dc = require('dc-polyfill')
|
|
|
6
6
|
module.exports = {
|
|
7
7
|
bodyParser: dc.channel('datadog:body-parser:read:finish'),
|
|
8
8
|
cookieParser: dc.channel('datadog:cookie-parser:read:finish'),
|
|
9
|
-
|
|
9
|
+
startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
|
|
10
|
+
graphqlMiddlewareChannel: dc.tracingChannel('datadog:apollo:middleware'),
|
|
11
|
+
apolloChannel: dc.tracingChannel('datadog:apollo:request'),
|
|
12
|
+
apolloServerCoreChannel: dc.tracingChannel('datadog:apollo-server-core:request'),
|
|
10
13
|
incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
|
|
11
14
|
incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
|
|
12
15
|
passportVerify: dc.channel('datadog:passport:verify:finish'),
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { storage } = require('../../../datadog-core')
|
|
4
|
+
const { addSpecificEndpoint, specificBlockingTypes, getBlockingData } = require('./blocking')
|
|
5
|
+
const waf = require('./waf')
|
|
6
|
+
const addresses = require('./addresses')
|
|
7
|
+
const web = require('../plugins/util/web')
|
|
8
|
+
const {
|
|
9
|
+
startGraphqlResolve,
|
|
10
|
+
graphqlMiddlewareChannel,
|
|
11
|
+
apolloChannel,
|
|
12
|
+
apolloServerCoreChannel
|
|
13
|
+
} = require('./channels')
|
|
14
|
+
|
|
15
|
+
const graphqlRequestData = new WeakMap()
|
|
16
|
+
|
|
17
|
+
function enable () {
|
|
18
|
+
enableApollo()
|
|
19
|
+
enableGraphql()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function disable () {
|
|
23
|
+
disableApollo()
|
|
24
|
+
disableGraphql()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function onGraphqlStartResolve ({ context, resolverInfo }) {
|
|
28
|
+
const req = storage.getStore()?.req
|
|
29
|
+
|
|
30
|
+
if (!req) return
|
|
31
|
+
|
|
32
|
+
if (!resolverInfo || typeof resolverInfo !== 'object') return
|
|
33
|
+
|
|
34
|
+
const actions = waf.run({ ephemeral: { [addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo } }, req)
|
|
35
|
+
if (actions?.includes('block')) {
|
|
36
|
+
const requestData = graphqlRequestData.get(req)
|
|
37
|
+
if (requestData?.isInGraphqlRequest) {
|
|
38
|
+
requestData.blocked = true
|
|
39
|
+
context?.abortController?.abort()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function enterInApolloMiddleware (data) {
|
|
45
|
+
const req = data?.req || storage.getStore()?.req
|
|
46
|
+
if (!req) return
|
|
47
|
+
|
|
48
|
+
graphqlRequestData.set(req, {
|
|
49
|
+
inApolloMiddleware: true,
|
|
50
|
+
blocked: false
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function enterInApolloServerCoreRequest () {
|
|
55
|
+
const req = storage.getStore()?.req
|
|
56
|
+
if (!req) return
|
|
57
|
+
|
|
58
|
+
graphqlRequestData.set(req, {
|
|
59
|
+
isInGraphqlRequest: true,
|
|
60
|
+
blocked: false
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function exitFromApolloMiddleware (data) {
|
|
65
|
+
const req = data?.req || storage.getStore()?.req
|
|
66
|
+
const requestData = graphqlRequestData.get(req)
|
|
67
|
+
if (requestData) requestData.inApolloMiddleware = false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function enterInApolloRequest () {
|
|
71
|
+
const req = storage.getStore()?.req
|
|
72
|
+
|
|
73
|
+
const requestData = graphqlRequestData.get(req)
|
|
74
|
+
if (requestData?.inApolloMiddleware) {
|
|
75
|
+
requestData.isInGraphqlRequest = true
|
|
76
|
+
addSpecificEndpoint(req.method, req.originalUrl || req.url, specificBlockingTypes.GRAPHQL)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function beforeWriteApolloGraphqlResponse ({ abortController, abortData }) {
|
|
81
|
+
const req = storage.getStore()?.req
|
|
82
|
+
if (!req) return
|
|
83
|
+
|
|
84
|
+
const requestData = graphqlRequestData.get(req)
|
|
85
|
+
|
|
86
|
+
if (requestData?.blocked) {
|
|
87
|
+
const rootSpan = web.root(req)
|
|
88
|
+
if (!rootSpan) return
|
|
89
|
+
|
|
90
|
+
const blockingData = getBlockingData(req, specificBlockingTypes.GRAPHQL, rootSpan)
|
|
91
|
+
abortData.statusCode = blockingData.statusCode
|
|
92
|
+
abortData.headers = blockingData.headers
|
|
93
|
+
abortData.message = blockingData.body
|
|
94
|
+
|
|
95
|
+
abortController?.abort()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
graphqlRequestData.delete(req)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function enableApollo () {
|
|
102
|
+
graphqlMiddlewareChannel.subscribe({
|
|
103
|
+
start: enterInApolloMiddleware,
|
|
104
|
+
end: exitFromApolloMiddleware
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
apolloServerCoreChannel.subscribe({
|
|
108
|
+
start: enterInApolloServerCoreRequest,
|
|
109
|
+
asyncEnd: beforeWriteApolloGraphqlResponse
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
apolloChannel.subscribe({
|
|
113
|
+
start: enterInApolloRequest,
|
|
114
|
+
asyncEnd: beforeWriteApolloGraphqlResponse
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function disableApollo () {
|
|
119
|
+
graphqlMiddlewareChannel.unsubscribe({
|
|
120
|
+
start: enterInApolloMiddleware,
|
|
121
|
+
end: exitFromApolloMiddleware
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
apolloServerCoreChannel.unsubscribe({
|
|
125
|
+
start: enterInApolloServerCoreRequest,
|
|
126
|
+
asyncEnd: beforeWriteApolloGraphqlResponse
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
apolloChannel.unsubscribe({
|
|
130
|
+
start: enterInApolloRequest,
|
|
131
|
+
asyncEnd: beforeWriteApolloGraphqlResponse
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function enableGraphql () {
|
|
136
|
+
startGraphqlResolve.subscribe(onGraphqlStartResolve)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function disableGraphql () {
|
|
140
|
+
if (startGraphqlResolve.hasSubscribers) startGraphqlResolve.unsubscribe(onGraphqlStartResolve)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
enable,
|
|
145
|
+
disable
|
|
146
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer'),
|
|
5
5
|
'HARCODED_SECRET_ANALYZER': require('./hardcoded-secret-analyzer'),
|
|
6
|
+
'HEADER_INJECTION_ANALYZER': require('./header-injection-analyzer'),
|
|
6
7
|
'HSTS_HEADER_MISSING_ANALYZER': require('./hsts-header-missing-analyzer'),
|
|
7
8
|
'INSECURE_COOKIE_ANALYZER': require('./insecure-cookie-analyzer'),
|
|
8
9
|
'LDAP_ANALYZER': require('./ldap-injection-analyzer'),
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
|
+
const { HEADER_INJECTION } = require('../vulnerabilities')
|
|
5
|
+
const { getNodeModulesPaths } = require('../path-line')
|
|
6
|
+
const { HEADER_NAME_VALUE_SEPARATOR } = require('../vulnerabilities-formatter/constants')
|
|
7
|
+
const { getRanges } = require('../taint-tracking/operations')
|
|
8
|
+
const {
|
|
9
|
+
HTTP_REQUEST_COOKIE_NAME,
|
|
10
|
+
HTTP_REQUEST_COOKIE_VALUE,
|
|
11
|
+
HTTP_REQUEST_HEADER_VALUE
|
|
12
|
+
} = require('../taint-tracking/source-types')
|
|
13
|
+
|
|
14
|
+
const EXCLUDED_PATHS = getNodeModulesPaths('express')
|
|
15
|
+
const EXCLUDED_HEADER_NAMES = [
|
|
16
|
+
'location',
|
|
17
|
+
'sec-websocket-location',
|
|
18
|
+
'sec-websocket-accept',
|
|
19
|
+
'upgrade',
|
|
20
|
+
'connection'
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
class HeaderInjectionAnalyzer extends InjectionAnalyzer {
|
|
24
|
+
constructor () {
|
|
25
|
+
super(HEADER_INJECTION)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onConfigure () {
|
|
29
|
+
this.addSub('datadog:http:server:response:set-header:finish', ({ name, value }) => {
|
|
30
|
+
if (Array.isArray(value)) {
|
|
31
|
+
for (let i = 0; i < value.length; i++) {
|
|
32
|
+
const headerValue = value[i]
|
|
33
|
+
|
|
34
|
+
this.analyze({ name, value: headerValue })
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
this.analyze({ name, value })
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_isVulnerable ({ name, value }, iastContext) {
|
|
43
|
+
const lowerCasedHeaderName = name?.trim().toLowerCase()
|
|
44
|
+
|
|
45
|
+
if (this.isExcludedHeaderName(lowerCasedHeaderName) || typeof value !== 'string') return
|
|
46
|
+
|
|
47
|
+
const ranges = getRanges(iastContext, value)
|
|
48
|
+
if (ranges?.length > 0) {
|
|
49
|
+
return !(this.isCookieExclusion(lowerCasedHeaderName, ranges) ||
|
|
50
|
+
this.isSameHeaderExclusion(lowerCasedHeaderName, ranges) ||
|
|
51
|
+
this.isAccessControlAllowOriginExclusion(lowerCasedHeaderName, ranges))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_getEvidence (headerInfo, iastContext) {
|
|
58
|
+
const prefix = headerInfo.name + HEADER_NAME_VALUE_SEPARATOR
|
|
59
|
+
const prefixLength = prefix.length
|
|
60
|
+
|
|
61
|
+
const evidence = super._getEvidence(headerInfo.value, iastContext)
|
|
62
|
+
evidence.value = prefix + evidence.value
|
|
63
|
+
evidence.ranges = evidence.ranges.map(range => {
|
|
64
|
+
return {
|
|
65
|
+
...range,
|
|
66
|
+
start: range.start + prefixLength,
|
|
67
|
+
end: range.end + prefixLength
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return evidence
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
isExcludedHeaderName (name) {
|
|
75
|
+
return EXCLUDED_HEADER_NAMES.includes(name)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
isCookieExclusion (name, ranges) {
|
|
79
|
+
if (name === 'set-cookie') {
|
|
80
|
+
return ranges
|
|
81
|
+
.every(range => range.iinfo.type === HTTP_REQUEST_COOKIE_VALUE || range.iinfo.type === HTTP_REQUEST_COOKIE_NAME)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
isAccessControlAllowOriginExclusion (name, ranges) {
|
|
88
|
+
if (name === 'access-control-allow-origin') {
|
|
89
|
+
return ranges
|
|
90
|
+
.every(range => range.iinfo.type === HTTP_REQUEST_HEADER_VALUE)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
isSameHeaderExclusion (name, ranges) {
|
|
97
|
+
return ranges.length === 1 && name === ranges[0].iinfo.parameterName?.toLowerCase()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_getExcludedPaths () {
|
|
101
|
+
return EXCLUDED_PATHS
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = new HeaderInjectionAnalyzer()
|
|
@@ -3,27 +3,20 @@
|
|
|
3
3
|
const iastLog = require('../../../iast-log')
|
|
4
4
|
|
|
5
5
|
const COMMAND_PATTERN = '^(?:\\s*(?:sudo|doas)\\s+)?\\b\\S+\\b\\s(.*)'
|
|
6
|
+
const pattern = new RegExp(COMMAND_PATTERN, 'gmi')
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
extractSensitiveRanges (evidence) {
|
|
13
|
-
try {
|
|
14
|
-
this._pattern.lastIndex = 0
|
|
8
|
+
module.exports = function extractSensitiveRanges (evidence) {
|
|
9
|
+
try {
|
|
10
|
+
pattern.lastIndex = 0
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
} catch (e) {
|
|
23
|
-
iastLog.debug(e)
|
|
12
|
+
const regexResult = pattern.exec(evidence.value)
|
|
13
|
+
if (regexResult && regexResult.length > 1) {
|
|
14
|
+
const start = regexResult.index + (regexResult[0].length - regexResult[1].length)
|
|
15
|
+
const end = start + regexResult[1].length
|
|
16
|
+
return [{ start, end }]
|
|
24
17
|
}
|
|
25
|
-
|
|
18
|
+
} catch (e) {
|
|
19
|
+
iastLog.debug(e)
|
|
26
20
|
}
|
|
21
|
+
return []
|
|
27
22
|
}
|
|
28
|
-
|
|
29
|
-
module.exports = CommandSensitiveAnalyzer
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { HEADER_NAME_VALUE_SEPARATOR } = require('../../constants')
|
|
4
|
+
|
|
5
|
+
module.exports = function extractSensitiveRanges (evidence, namePattern, valuePattern) {
|
|
6
|
+
const evidenceValue = evidence.value
|
|
7
|
+
const sections = evidenceValue.split(HEADER_NAME_VALUE_SEPARATOR)
|
|
8
|
+
const headerName = sections[0]
|
|
9
|
+
const headerValue = sections.slice(1).join(HEADER_NAME_VALUE_SEPARATOR)
|
|
10
|
+
namePattern.lastIndex = 0
|
|
11
|
+
valuePattern.lastIndex = 0
|
|
12
|
+
if (namePattern.test(headerName) || valuePattern.test(headerValue)) {
|
|
13
|
+
return [{
|
|
14
|
+
start: headerName.length + HEADER_NAME_VALUE_SEPARATOR.length,
|
|
15
|
+
end: evidenceValue.length
|
|
16
|
+
}]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return []
|
|
20
|
+
}
|
|
@@ -2,15 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
const { stringifyWithRanges } = require('../../utils')
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
evidence.ranges = ranges
|
|
5
|
+
module.exports = function extractSensitiveRanges (evidence) {
|
|
6
|
+
// expect object evidence
|
|
7
|
+
const { value, ranges, sensitiveRanges } = stringifyWithRanges(evidence.value, evidence.rangesToApply, true)
|
|
8
|
+
evidence.value = value
|
|
9
|
+
evidence.ranges = ranges
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
}
|
|
11
|
+
return sensitiveRanges
|
|
14
12
|
}
|
|
15
|
-
|
|
16
|
-
module.exports = JsonSensitiveAnalyzer
|
|
@@ -3,33 +3,26 @@
|
|
|
3
3
|
const iastLog = require('../../../iast-log')
|
|
4
4
|
|
|
5
5
|
const LDAP_PATTERN = '\\(.*?(?:~=|=|<=|>=)(?<LITERAL>[^)]+)\\)'
|
|
6
|
+
const pattern = new RegExp(LDAP_PATTERN, 'gmi')
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
extractSensitiveRanges (evidence) {
|
|
13
|
-
try {
|
|
14
|
-
this._pattern.lastIndex = 0
|
|
15
|
-
const tokens = []
|
|
8
|
+
module.exports = function extractSensitiveRanges (evidence) {
|
|
9
|
+
try {
|
|
10
|
+
pattern.lastIndex = 0
|
|
11
|
+
const tokens = []
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
return tokens
|
|
28
|
-
} catch (e) {
|
|
29
|
-
iastLog.debug(e)
|
|
13
|
+
let regexResult = pattern.exec(evidence.value)
|
|
14
|
+
while (regexResult != null) {
|
|
15
|
+
if (!regexResult.groups.LITERAL) continue
|
|
16
|
+
// Computing indices manually since NodeJs 12 does not support d flag on regular expressions
|
|
17
|
+
// TODO Get indices from group by adding d flag in regular expression
|
|
18
|
+
const start = regexResult.index + (regexResult[0].length - regexResult.groups.LITERAL.length - 1)
|
|
19
|
+
const end = start + regexResult.groups.LITERAL.length
|
|
20
|
+
tokens.push({ start, end })
|
|
21
|
+
regexResult = pattern.exec(evidence.value)
|
|
30
22
|
}
|
|
31
|
-
return
|
|
23
|
+
return tokens
|
|
24
|
+
} catch (e) {
|
|
25
|
+
iastLog.debug(e)
|
|
32
26
|
}
|
|
27
|
+
return []
|
|
33
28
|
}
|
|
34
|
-
|
|
35
|
-
module.exports = LdapSensitiveAnalyzer
|