dd-trace 5.19.0 → 5.20.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/ext/formats.d.ts +1 -0
- package/ext/formats.js +2 -1
- package/init.js +3 -15
- package/package.json +3 -2
- package/packages/datadog-instrumentations/src/helpers/register.js +13 -11
- package/packages/datadog-instrumentations/src/http/client.js +7 -1
- package/packages/datadog-instrumentations/src/http/server.js +50 -13
- package/packages/datadog-instrumentations/src/mocha/main.js +21 -8
- package/packages/datadog-instrumentations/src/process.js +29 -0
- package/packages/datadog-instrumentations/src/vitest.js +47 -23
- package/packages/datadog-plugin-aws-sdk/src/base.js +15 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +3 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +37 -8
- package/packages/datadog-plugin-vitest/src/index.js +2 -1
- package/packages/dd-trace/src/appsec/blocking.js +10 -1
- package/packages/dd-trace/src/appsec/channels.js +4 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +2 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +2 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +11 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/code-injection-sensitive-analyzer.js +25 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +2 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +12 -7
- package/packages/dd-trace/src/appsec/rasp.js +121 -7
- package/packages/dd-trace/src/appsec/recommended.json +220 -2
- package/packages/dd-trace/src/config.js +41 -42
- package/packages/dd-trace/src/data_streams.js +44 -0
- package/packages/dd-trace/src/datastreams/pathway.js +4 -2
- package/packages/dd-trace/src/log/index.js +32 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +43 -0
- package/packages/dd-trace/src/opentracing/tracer.js +10 -6
- package/packages/dd-trace/src/plugins/ci_plugin.js +9 -2
- package/packages/dd-trace/src/plugins/plugin.js +12 -1
- package/packages/dd-trace/src/proxy.js +1 -0
- package/packages/dd-trace/src/tracer.js +2 -0
|
@@ -9,6 +9,8 @@ let templateHtml = blockedTemplates.html
|
|
|
9
9
|
let templateJson = blockedTemplates.json
|
|
10
10
|
let templateGraphqlJson = blockedTemplates.graphqlJson
|
|
11
11
|
|
|
12
|
+
const responseBlockedSet = new WeakSet()
|
|
13
|
+
|
|
12
14
|
const specificBlockingTypes = {
|
|
13
15
|
GRAPHQL: 'graphql'
|
|
14
16
|
}
|
|
@@ -117,6 +119,8 @@ function block (req, res, rootSpan, abortController, actionParameters) {
|
|
|
117
119
|
|
|
118
120
|
res.writeHead(statusCode, headers).end(body)
|
|
119
121
|
|
|
122
|
+
responseBlockedSet.add(res)
|
|
123
|
+
|
|
120
124
|
abortController?.abort()
|
|
121
125
|
}
|
|
122
126
|
|
|
@@ -144,11 +148,16 @@ function setTemplates (config) {
|
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
|
|
151
|
+
function isBlocked (res) {
|
|
152
|
+
return responseBlockedSet.has(res)
|
|
153
|
+
}
|
|
154
|
+
|
|
147
155
|
module.exports = {
|
|
148
156
|
addSpecificEndpoint,
|
|
149
157
|
block,
|
|
150
158
|
specificBlockingTypes,
|
|
151
159
|
getBlockingData,
|
|
152
160
|
getBlockingAction,
|
|
153
|
-
setTemplates
|
|
161
|
+
setTemplates,
|
|
162
|
+
isBlocked
|
|
154
163
|
}
|
|
@@ -19,5 +19,8 @@ module.exports = {
|
|
|
19
19
|
nextQueryParsed: dc.channel('apm:next:query-parsed'),
|
|
20
20
|
responseBody: dc.channel('datadog:express:response:json:start'),
|
|
21
21
|
responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'),
|
|
22
|
-
httpClientRequestStart: dc.channel('apm:http:client:request:start')
|
|
22
|
+
httpClientRequestStart: dc.channel('apm:http:client:request:start'),
|
|
23
|
+
responseSetHeader: dc.channel('datadog:http:server:response:set-header:start'),
|
|
24
|
+
setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start')
|
|
25
|
+
|
|
23
26
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
|
+
CODE_INJECTION_ANALYZER: require('./code-injection-analyzer'),
|
|
4
5
|
COMMAND_INJECTION_ANALYZER: require('./command-injection-analyzer'),
|
|
5
6
|
HARCODED_PASSWORD_ANALYZER: require('./hardcoded-password-analyzer'),
|
|
6
7
|
HARCODED_SECRET_ANALYZER: require('./hardcoded-secret-analyzer'),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
|
+
const { CODE_INJECTION } = require('../vulnerabilities')
|
|
5
|
+
|
|
6
|
+
class CodeInjectionAnalyzer extends InjectionAnalyzer {
|
|
7
|
+
constructor () {
|
|
8
|
+
super(CODE_INJECTION)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
onConfigure () {
|
|
12
|
+
this.addSub('datadog:eval:call', ({ script }) => this.analyze(script))
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = new CodeInjectionAnalyzer()
|
|
@@ -14,7 +14,8 @@ const csiMethods = [
|
|
|
14
14
|
{ src: 'toUpperCase', dst: 'stringCase' },
|
|
15
15
|
{ src: 'trim' },
|
|
16
16
|
{ src: 'trimEnd' },
|
|
17
|
-
{ src: 'trimStart', dst: 'trim' }
|
|
17
|
+
{ src: 'trimStart', dst: 'trim' },
|
|
18
|
+
{ src: 'eval', allowedWithoutCallee: true }
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
module.exports = {
|
|
@@ -10,6 +10,7 @@ const { isDebugAllowed } = require('../telemetry/verbosity')
|
|
|
10
10
|
const { taintObject } = require('./operations-taint-object')
|
|
11
11
|
|
|
12
12
|
const mathRandomCallCh = dc.channel('datadog:random:call')
|
|
13
|
+
const evalCallCh = dc.channel('datadog:eval:call')
|
|
13
14
|
|
|
14
15
|
const JSON_VALUE = 'json.value'
|
|
15
16
|
|
|
@@ -18,6 +19,7 @@ function noop (res) { return res }
|
|
|
18
19
|
// Otherwise you may end up rewriting a method and not providing its rewritten implementation
|
|
19
20
|
const TaintTrackingNoop = {
|
|
20
21
|
concat: noop,
|
|
22
|
+
eval: noop,
|
|
21
23
|
join: noop,
|
|
22
24
|
parse: noop,
|
|
23
25
|
plusOperator: noop,
|
|
@@ -136,6 +138,15 @@ function csiMethodsOverrides (getContext) {
|
|
|
136
138
|
return res
|
|
137
139
|
},
|
|
138
140
|
|
|
141
|
+
eval: function (res, fn, target, script) {
|
|
142
|
+
// eslint-disable-next-line no-eval
|
|
143
|
+
if (evalCallCh.hasSubscribers && fn === globalThis.eval) {
|
|
144
|
+
evalCallCh.publish({ script })
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return res
|
|
148
|
+
},
|
|
149
|
+
|
|
139
150
|
parse: function (res, fn, target, json) {
|
|
140
151
|
if (fn === JSON.parse) {
|
|
141
152
|
try {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = function extractSensitiveRanges (evidence) {
|
|
4
|
+
const newRanges = []
|
|
5
|
+
if (evidence.ranges[0].start > 0) {
|
|
6
|
+
newRanges.push({
|
|
7
|
+
start: 0,
|
|
8
|
+
end: evidence.ranges[0].start
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < evidence.ranges.length; i++) {
|
|
13
|
+
const currentRange = evidence.ranges[i]
|
|
14
|
+
const nextRange = evidence.ranges[i + 1]
|
|
15
|
+
|
|
16
|
+
const start = currentRange.end
|
|
17
|
+
const end = nextRange?.start || evidence.value.length
|
|
18
|
+
|
|
19
|
+
if (start < end) {
|
|
20
|
+
newRanges.push({ start, end })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return newRanges
|
|
25
|
+
}
|
|
@@ -5,6 +5,7 @@ const vulnerabilities = require('../../vulnerabilities')
|
|
|
5
5
|
|
|
6
6
|
const { contains, intersects, remove } = require('./range-utils')
|
|
7
7
|
|
|
8
|
+
const codeInjectionSensitiveAnalyzer = require('./sensitive-analyzers/code-injection-sensitive-analyzer')
|
|
8
9
|
const commandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer')
|
|
9
10
|
const hardcodedPasswordAnalyzer = require('./sensitive-analyzers/hardcoded-password-analyzer')
|
|
10
11
|
const headerSensitiveAnalyzer = require('./sensitive-analyzers/header-sensitive-analyzer')
|
|
@@ -23,6 +24,7 @@ class SensitiveHandler {
|
|
|
23
24
|
this._valuePattern = new RegExp(DEFAULT_IAST_REDACTION_VALUE_PATTERN, 'gmi')
|
|
24
25
|
|
|
25
26
|
this._sensitiveAnalyzers = new Map()
|
|
27
|
+
this._sensitiveAnalyzers.set(vulnerabilities.CODE_INJECTION, codeInjectionSensitiveAnalyzer)
|
|
26
28
|
this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, commandSensitiveAnalyzer)
|
|
27
29
|
this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, jsonSensitiveAnalyzer)
|
|
28
30
|
this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, ldapSensitiveAnalyzer)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// eslint-disable-next-line max-len
|
|
2
|
-
const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)'
|
|
2
|
+
const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|(?:sur|last)name|user(?:name)?|address|e?mail)'
|
|
3
3
|
// eslint-disable-next-line max-len
|
|
4
|
-
const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))'
|
|
4
|
+
const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,})|[\\w\\.-]+@[a-zA-Z\\d\\.-]+\\.[a-zA-Z]{2,})'
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
7
|
DEFAULT_IAST_REDACTION_NAME_PATTERN,
|
|
@@ -13,7 +13,8 @@ const {
|
|
|
13
13
|
nextBodyParsed,
|
|
14
14
|
nextQueryParsed,
|
|
15
15
|
responseBody,
|
|
16
|
-
responseWriteHead
|
|
16
|
+
responseWriteHead,
|
|
17
|
+
responseSetHeader
|
|
17
18
|
} = require('./channels')
|
|
18
19
|
const waf = require('./waf')
|
|
19
20
|
const addresses = require('./addresses')
|
|
@@ -23,7 +24,7 @@ const apiSecuritySampler = require('./api_security_sampler')
|
|
|
23
24
|
const web = require('../plugins/util/web')
|
|
24
25
|
const { extractIp } = require('../plugins/util/ip_extractor')
|
|
25
26
|
const { HTTP_CLIENT_IP } = require('../../../../ext/tags')
|
|
26
|
-
const { block, setTemplates, getBlockingAction } = require('./blocking')
|
|
27
|
+
const { isBlocked, block, setTemplates, getBlockingAction } = require('./blocking')
|
|
27
28
|
const { passportTrackEvent } = require('./passport')
|
|
28
29
|
const { storage } = require('../../../datadog-core')
|
|
29
30
|
const graphql = require('./graphql')
|
|
@@ -62,6 +63,7 @@ function enable (_config) {
|
|
|
62
63
|
cookieParser.subscribe(onRequestCookieParser)
|
|
63
64
|
responseBody.subscribe(onResponseBody)
|
|
64
65
|
responseWriteHead.subscribe(onResponseWriteHead)
|
|
66
|
+
responseSetHeader.subscribe(onResponseSetHeader)
|
|
65
67
|
|
|
66
68
|
if (_config.appsec.eventTracking.enabled) {
|
|
67
69
|
passportVerify.subscribe(onPassportVerify)
|
|
@@ -223,11 +225,10 @@ function onPassportVerify ({ credentials, user }) {
|
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
const responseAnalyzedSet = new WeakSet()
|
|
226
|
-
const responseBlockedSet = new WeakSet()
|
|
227
228
|
|
|
228
229
|
function onResponseWriteHead ({ req, res, abortController, statusCode, responseHeaders }) {
|
|
229
230
|
// avoid "write after end" error
|
|
230
|
-
if (
|
|
231
|
+
if (isBlocked(res)) {
|
|
231
232
|
abortController?.abort()
|
|
232
233
|
return
|
|
233
234
|
}
|
|
@@ -255,15 +256,18 @@ function onResponseWriteHead ({ req, res, abortController, statusCode, responseH
|
|
|
255
256
|
handleResults(results, req, res, rootSpan, abortController)
|
|
256
257
|
}
|
|
257
258
|
|
|
259
|
+
function onResponseSetHeader ({ res, abortController }) {
|
|
260
|
+
if (isBlocked(res)) {
|
|
261
|
+
abortController?.abort()
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
258
265
|
function handleResults (actions, req, res, rootSpan, abortController) {
|
|
259
266
|
if (!actions || !req || !res || !rootSpan || !abortController) return
|
|
260
267
|
|
|
261
268
|
const blockingAction = getBlockingAction(actions)
|
|
262
269
|
if (blockingAction) {
|
|
263
270
|
block(req, res, rootSpan, abortController, blockingAction)
|
|
264
|
-
if (!abortController.signal || abortController.signal.aborted) {
|
|
265
|
-
responseBlockedSet.add(res)
|
|
266
|
-
}
|
|
267
271
|
}
|
|
268
272
|
}
|
|
269
273
|
|
|
@@ -290,6 +294,7 @@ function disable () {
|
|
|
290
294
|
if (responseBody.hasSubscribers) responseBody.unsubscribe(onResponseBody)
|
|
291
295
|
if (passportVerify.hasSubscribers) passportVerify.unsubscribe(onPassportVerify)
|
|
292
296
|
if (responseWriteHead.hasSubscribers) responseWriteHead.unsubscribe(onResponseWriteHead)
|
|
297
|
+
if (responseSetHeader.hasSubscribers) responseSetHeader.unsubscribe(onResponseSetHeader)
|
|
293
298
|
}
|
|
294
299
|
|
|
295
300
|
module.exports = {
|
|
@@ -3,23 +3,118 @@
|
|
|
3
3
|
const { storage } = require('../../../datadog-core')
|
|
4
4
|
const web = require('./../plugins/util/web')
|
|
5
5
|
const addresses = require('./addresses')
|
|
6
|
-
const { httpClientRequestStart } = require('./channels')
|
|
6
|
+
const { httpClientRequestStart, setUncaughtExceptionCaptureCallbackStart } = require('./channels')
|
|
7
7
|
const { reportStackTrace } = require('./stack_trace')
|
|
8
8
|
const waf = require('./waf')
|
|
9
|
+
const { getBlockingAction, block } = require('./blocking')
|
|
10
|
+
const log = require('../log')
|
|
9
11
|
|
|
10
12
|
const RULE_TYPES = {
|
|
11
13
|
SSRF: 'ssrf'
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
class DatadogRaspAbortError extends Error {
|
|
17
|
+
constructor (req, res, blockingAction) {
|
|
18
|
+
super('DatadogRaspAbortError')
|
|
19
|
+
this.name = 'DatadogRaspAbortError'
|
|
20
|
+
this.req = req
|
|
21
|
+
this.res = res
|
|
22
|
+
this.blockingAction = blockingAction
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let config, abortOnUncaughtException
|
|
27
|
+
|
|
28
|
+
function removeAllListeners (emitter, event) {
|
|
29
|
+
const listeners = emitter.listeners(event)
|
|
30
|
+
emitter.removeAllListeners(event)
|
|
31
|
+
|
|
32
|
+
let cleaned = false
|
|
33
|
+
return function () {
|
|
34
|
+
if (cleaned === true) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
cleaned = true
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < listeners.length; ++i) {
|
|
40
|
+
emitter.on(event, listeners[i])
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function findDatadogRaspAbortError (err, deep = 10) {
|
|
46
|
+
if (err instanceof DatadogRaspAbortError) {
|
|
47
|
+
return err
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (err.cause && deep > 0) {
|
|
51
|
+
return findDatadogRaspAbortError(err.cause, deep - 1)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleUncaughtExceptionMonitor (err) {
|
|
56
|
+
const abortError = findDatadogRaspAbortError(err)
|
|
57
|
+
if (!abortError) return
|
|
58
|
+
|
|
59
|
+
const { req, res, blockingAction } = abortError
|
|
60
|
+
block(req, res, web.root(req), null, blockingAction)
|
|
61
|
+
|
|
62
|
+
if (!process.hasUncaughtExceptionCaptureCallback()) {
|
|
63
|
+
const cleanUp = removeAllListeners(process, 'uncaughtException')
|
|
64
|
+
const handler = () => {
|
|
65
|
+
process.removeListener('uncaughtException', handler)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
process.removeListener('uncaughtException', handler)
|
|
70
|
+
cleanUp()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
process.on('uncaughtException', handler)
|
|
74
|
+
} else {
|
|
75
|
+
// uncaughtException event is not executed when hasUncaughtExceptionCaptureCallback is true
|
|
76
|
+
let previousCb
|
|
77
|
+
const cb = ({ currentCallback, abortController }) => {
|
|
78
|
+
setUncaughtExceptionCaptureCallbackStart.unsubscribe(cb)
|
|
79
|
+
if (!currentCallback) {
|
|
80
|
+
abortController.abort()
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
previousCb = currentCallback
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setUncaughtExceptionCaptureCallbackStart.subscribe(cb)
|
|
88
|
+
|
|
89
|
+
process.setUncaughtExceptionCaptureCallback(null)
|
|
90
|
+
|
|
91
|
+
// For some reason, previous callback was defined before the instrumentation
|
|
92
|
+
// We can not restore it, so we let the app decide
|
|
93
|
+
if (previousCb) {
|
|
94
|
+
process.setUncaughtExceptionCaptureCallback(() => {
|
|
95
|
+
process.setUncaughtExceptionCaptureCallback(null)
|
|
96
|
+
process.setUncaughtExceptionCaptureCallback(previousCb)
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
15
101
|
|
|
16
102
|
function enable (_config) {
|
|
17
103
|
config = _config
|
|
18
104
|
httpClientRequestStart.subscribe(analyzeSsrf)
|
|
105
|
+
|
|
106
|
+
process.on('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
|
|
107
|
+
abortOnUncaughtException = process.execArgv?.includes('--abort-on-uncaught-exception')
|
|
108
|
+
|
|
109
|
+
if (abortOnUncaughtException) {
|
|
110
|
+
log.warn('The --abort-on-uncaught-exception flag is enabled. The RASP module will not block operations.')
|
|
111
|
+
}
|
|
19
112
|
}
|
|
20
113
|
|
|
21
114
|
function disable () {
|
|
22
115
|
if (httpClientRequestStart.hasSubscribers) httpClientRequestStart.unsubscribe(analyzeSsrf)
|
|
116
|
+
|
|
117
|
+
process.off('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
|
|
23
118
|
}
|
|
24
119
|
|
|
25
120
|
function analyzeSsrf (ctx) {
|
|
@@ -32,17 +127,18 @@ function analyzeSsrf (ctx) {
|
|
|
32
127
|
const persistent = {
|
|
33
128
|
[addresses.HTTP_OUTGOING_URL]: url
|
|
34
129
|
}
|
|
35
|
-
|
|
36
|
-
// block the request if SSRF attempt
|
|
130
|
+
|
|
37
131
|
const result = waf.run({ persistent }, req, RULE_TYPES.SSRF)
|
|
38
|
-
|
|
132
|
+
|
|
133
|
+
const res = store?.res
|
|
134
|
+
handleResult(result, req, res, ctx.abortController)
|
|
39
135
|
}
|
|
40
136
|
|
|
41
137
|
function getGenerateStackTraceAction (actions) {
|
|
42
138
|
return actions?.generate_stack
|
|
43
139
|
}
|
|
44
140
|
|
|
45
|
-
function handleResult (actions, req) {
|
|
141
|
+
function handleResult (actions, req, res, abortController) {
|
|
46
142
|
const generateStackTraceAction = getGenerateStackTraceAction(actions)
|
|
47
143
|
if (generateStackTraceAction && config.appsec.stackTrace.enabled) {
|
|
48
144
|
const rootSpan = web.root(req)
|
|
@@ -53,10 +149,28 @@ function handleResult (actions, req) {
|
|
|
53
149
|
config.appsec.stackTrace.maxStackTraces
|
|
54
150
|
)
|
|
55
151
|
}
|
|
152
|
+
|
|
153
|
+
if (!abortController || abortOnUncaughtException) return
|
|
154
|
+
|
|
155
|
+
const blockingAction = getBlockingAction(actions)
|
|
156
|
+
if (blockingAction) {
|
|
157
|
+
const rootSpan = web.root(req)
|
|
158
|
+
// Should block only in express
|
|
159
|
+
if (rootSpan?.context()._name === 'express.request') {
|
|
160
|
+
const abortError = new DatadogRaspAbortError(req, res, blockingAction)
|
|
161
|
+
abortController.abort(abortError)
|
|
162
|
+
|
|
163
|
+
// TODO Delete this when support for node 16 is removed
|
|
164
|
+
if (!abortController.signal.reason) {
|
|
165
|
+
abortController.signal.reason = abortError
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
56
169
|
}
|
|
57
170
|
|
|
58
171
|
module.exports = {
|
|
59
172
|
enable,
|
|
60
173
|
disable,
|
|
61
|
-
handleResult
|
|
174
|
+
handleResult,
|
|
175
|
+
handleUncaughtExceptionMonitor // exported only for testing purpose
|
|
62
176
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "2.2",
|
|
3
3
|
"metadata": {
|
|
4
|
-
"rules_version": "1.
|
|
4
|
+
"rules_version": "1.13.0"
|
|
5
5
|
},
|
|
6
6
|
"rules": [
|
|
7
7
|
{
|
|
@@ -6285,6 +6285,55 @@
|
|
|
6285
6285
|
"stack_trace"
|
|
6286
6286
|
]
|
|
6287
6287
|
},
|
|
6288
|
+
{
|
|
6289
|
+
"id": "rasp-932-100",
|
|
6290
|
+
"name": "Shell injection exploit",
|
|
6291
|
+
"enabled": false,
|
|
6292
|
+
"tags": {
|
|
6293
|
+
"type": "command_injection",
|
|
6294
|
+
"category": "vulnerability_trigger",
|
|
6295
|
+
"cwe": "77",
|
|
6296
|
+
"capec": "1000/152/248/88",
|
|
6297
|
+
"confidence": "0",
|
|
6298
|
+
"module": "rasp"
|
|
6299
|
+
},
|
|
6300
|
+
"conditions": [
|
|
6301
|
+
{
|
|
6302
|
+
"parameters": {
|
|
6303
|
+
"resource": [
|
|
6304
|
+
{
|
|
6305
|
+
"address": "server.sys.shell.cmd"
|
|
6306
|
+
}
|
|
6307
|
+
],
|
|
6308
|
+
"params": [
|
|
6309
|
+
{
|
|
6310
|
+
"address": "server.request.query"
|
|
6311
|
+
},
|
|
6312
|
+
{
|
|
6313
|
+
"address": "server.request.body"
|
|
6314
|
+
},
|
|
6315
|
+
{
|
|
6316
|
+
"address": "server.request.path_params"
|
|
6317
|
+
},
|
|
6318
|
+
{
|
|
6319
|
+
"address": "grpc.server.request.message"
|
|
6320
|
+
},
|
|
6321
|
+
{
|
|
6322
|
+
"address": "graphql.server.all_resolvers"
|
|
6323
|
+
},
|
|
6324
|
+
{
|
|
6325
|
+
"address": "graphql.server.resolver"
|
|
6326
|
+
}
|
|
6327
|
+
]
|
|
6328
|
+
},
|
|
6329
|
+
"operator": "shi_detector"
|
|
6330
|
+
}
|
|
6331
|
+
],
|
|
6332
|
+
"transformers": [],
|
|
6333
|
+
"on_match": [
|
|
6334
|
+
"stack_trace"
|
|
6335
|
+
]
|
|
6336
|
+
},
|
|
6288
6337
|
{
|
|
6289
6338
|
"id": "rasp-934-100",
|
|
6290
6339
|
"name": "Server-side request forgery exploit",
|
|
@@ -8388,6 +8437,57 @@
|
|
|
8388
8437
|
}
|
|
8389
8438
|
],
|
|
8390
8439
|
"processors": [
|
|
8440
|
+
{
|
|
8441
|
+
"id": "http-endpoint-fingerprint",
|
|
8442
|
+
"generator": "http_endpoint_fingerprint",
|
|
8443
|
+
"conditions": [
|
|
8444
|
+
{
|
|
8445
|
+
"operator": "exists",
|
|
8446
|
+
"parameters": {
|
|
8447
|
+
"inputs": [
|
|
8448
|
+
{
|
|
8449
|
+
"address": "waf.context.event"
|
|
8450
|
+
},
|
|
8451
|
+
{
|
|
8452
|
+
"address": "server.business_logic.users.login.failure"
|
|
8453
|
+
},
|
|
8454
|
+
{
|
|
8455
|
+
"address": "server.business_logic.users.login.success"
|
|
8456
|
+
}
|
|
8457
|
+
]
|
|
8458
|
+
}
|
|
8459
|
+
}
|
|
8460
|
+
],
|
|
8461
|
+
"parameters": {
|
|
8462
|
+
"mappings": [
|
|
8463
|
+
{
|
|
8464
|
+
"method": [
|
|
8465
|
+
{
|
|
8466
|
+
"address": "server.request.method"
|
|
8467
|
+
}
|
|
8468
|
+
],
|
|
8469
|
+
"uri_raw": [
|
|
8470
|
+
{
|
|
8471
|
+
"address": "server.request.uri.raw"
|
|
8472
|
+
}
|
|
8473
|
+
],
|
|
8474
|
+
"body": [
|
|
8475
|
+
{
|
|
8476
|
+
"address": "server.request.body"
|
|
8477
|
+
}
|
|
8478
|
+
],
|
|
8479
|
+
"query": [
|
|
8480
|
+
{
|
|
8481
|
+
"address": "server.request.query"
|
|
8482
|
+
}
|
|
8483
|
+
],
|
|
8484
|
+
"output": "_dd.appsec.fp.http.endpoint"
|
|
8485
|
+
}
|
|
8486
|
+
]
|
|
8487
|
+
},
|
|
8488
|
+
"evaluate": false,
|
|
8489
|
+
"output": true
|
|
8490
|
+
},
|
|
8391
8491
|
{
|
|
8392
8492
|
"id": "extract-content",
|
|
8393
8493
|
"generator": "extract_schema",
|
|
@@ -8537,6 +8637,124 @@
|
|
|
8537
8637
|
},
|
|
8538
8638
|
"evaluate": false,
|
|
8539
8639
|
"output": true
|
|
8640
|
+
},
|
|
8641
|
+
{
|
|
8642
|
+
"id": "http-header-fingerprint",
|
|
8643
|
+
"generator": "http_header_fingerprint",
|
|
8644
|
+
"conditions": [
|
|
8645
|
+
{
|
|
8646
|
+
"operator": "exists",
|
|
8647
|
+
"parameters": {
|
|
8648
|
+
"inputs": [
|
|
8649
|
+
{
|
|
8650
|
+
"address": "waf.context.event"
|
|
8651
|
+
},
|
|
8652
|
+
{
|
|
8653
|
+
"address": "server.business_logic.users.login.failure"
|
|
8654
|
+
},
|
|
8655
|
+
{
|
|
8656
|
+
"address": "server.business_logic.users.login.success"
|
|
8657
|
+
}
|
|
8658
|
+
]
|
|
8659
|
+
}
|
|
8660
|
+
}
|
|
8661
|
+
],
|
|
8662
|
+
"parameters": {
|
|
8663
|
+
"mappings": [
|
|
8664
|
+
{
|
|
8665
|
+
"headers": [
|
|
8666
|
+
{
|
|
8667
|
+
"address": "server.request.headers.no_cookies"
|
|
8668
|
+
}
|
|
8669
|
+
],
|
|
8670
|
+
"output": "_dd.appsec.fp.http.header"
|
|
8671
|
+
}
|
|
8672
|
+
]
|
|
8673
|
+
},
|
|
8674
|
+
"evaluate": false,
|
|
8675
|
+
"output": true
|
|
8676
|
+
},
|
|
8677
|
+
{
|
|
8678
|
+
"id": "http-network-fingerprint",
|
|
8679
|
+
"generator": "http_network_fingerprint",
|
|
8680
|
+
"conditions": [
|
|
8681
|
+
{
|
|
8682
|
+
"operator": "exists",
|
|
8683
|
+
"parameters": {
|
|
8684
|
+
"inputs": [
|
|
8685
|
+
{
|
|
8686
|
+
"address": "waf.context.event"
|
|
8687
|
+
},
|
|
8688
|
+
{
|
|
8689
|
+
"address": "server.business_logic.users.login.failure"
|
|
8690
|
+
},
|
|
8691
|
+
{
|
|
8692
|
+
"address": "server.business_logic.users.login.success"
|
|
8693
|
+
}
|
|
8694
|
+
]
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
],
|
|
8698
|
+
"parameters": {
|
|
8699
|
+
"mappings": [
|
|
8700
|
+
{
|
|
8701
|
+
"headers": [
|
|
8702
|
+
{
|
|
8703
|
+
"address": "server.request.headers.no_cookies"
|
|
8704
|
+
}
|
|
8705
|
+
],
|
|
8706
|
+
"output": "_dd.appsec.fp.http.network"
|
|
8707
|
+
}
|
|
8708
|
+
]
|
|
8709
|
+
},
|
|
8710
|
+
"evaluate": false,
|
|
8711
|
+
"output": true
|
|
8712
|
+
},
|
|
8713
|
+
{
|
|
8714
|
+
"id": "session-fingerprint",
|
|
8715
|
+
"generator": "session_fingerprint",
|
|
8716
|
+
"conditions": [
|
|
8717
|
+
{
|
|
8718
|
+
"operator": "exists",
|
|
8719
|
+
"parameters": {
|
|
8720
|
+
"inputs": [
|
|
8721
|
+
{
|
|
8722
|
+
"address": "waf.context.event"
|
|
8723
|
+
},
|
|
8724
|
+
{
|
|
8725
|
+
"address": "server.business_logic.users.login.failure"
|
|
8726
|
+
},
|
|
8727
|
+
{
|
|
8728
|
+
"address": "server.business_logic.users.login.success"
|
|
8729
|
+
}
|
|
8730
|
+
]
|
|
8731
|
+
}
|
|
8732
|
+
}
|
|
8733
|
+
],
|
|
8734
|
+
"parameters": {
|
|
8735
|
+
"mappings": [
|
|
8736
|
+
{
|
|
8737
|
+
"cookies": [
|
|
8738
|
+
{
|
|
8739
|
+
"address": "server.request.cookies"
|
|
8740
|
+
}
|
|
8741
|
+
],
|
|
8742
|
+
"session_id": [
|
|
8743
|
+
{
|
|
8744
|
+
"address": "usr.session_id"
|
|
8745
|
+
}
|
|
8746
|
+
],
|
|
8747
|
+
"user_id": [
|
|
8748
|
+
{
|
|
8749
|
+
"address": "usr.id"
|
|
8750
|
+
}
|
|
8751
|
+
],
|
|
8752
|
+
"output": "_dd.appsec.fp.session"
|
|
8753
|
+
}
|
|
8754
|
+
]
|
|
8755
|
+
},
|
|
8756
|
+
"evaluate": false,
|
|
8757
|
+
"output": true
|
|
8540
8758
|
}
|
|
8541
8759
|
],
|
|
8542
8760
|
"scanners": [
|
|
@@ -9562,4 +9780,4 @@
|
|
|
9562
9780
|
}
|
|
9563
9781
|
}
|
|
9564
9782
|
]
|
|
9565
|
-
}
|
|
9783
|
+
}
|