Skip to content

Commit 275bb7e

Browse files
authored
Support tainted strings coming from database for SQLi, SSTi and Code injection (#4904)
1 parent 28bca83 commit 275bb7e

26 files changed

+1537
-773
lines changed

docs/test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ tracer.init({
131131
requestSampling: 50,
132132
maxConcurrentRequests: 4,
133133
maxContextOperations: 30,
134+
dbRowsToTaint: 12,
134135
deduplicationEnabled: true,
135136
redactionEnabled: true,
136137
redactionNamePattern: 'password',
@@ -147,6 +148,7 @@ tracer.init({
147148
requestSampling: 50,
148149
maxConcurrentRequests: 4,
149150
maxContextOperations: 30,
151+
dbRowsToTaint: 6,
150152
deduplicationEnabled: true,
151153
redactionEnabled: true,
152154
redactionNamePattern: 'password',

index.d.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,7 @@ declare namespace tracer {
764764
*/
765765
maxDepth?: number
766766
}
767-
767+
768768
/**
769769
* Configuration enabling LLM Observability. Enablement is superceded by the DD_LLMOBS_ENABLED environment variable.
770770
*/
@@ -2203,6 +2203,12 @@ declare namespace tracer {
22032203
*/
22042204
cookieFilterPattern?: string,
22052205

2206+
/**
2207+
* Defines the number of rows to taint in data coming from databases
2208+
* @default 1
2209+
*/
2210+
dbRowsToTaint?: number,
2211+
22062212
/**
22072213
* Whether to enable vulnerability deduplication
22082214
*/
@@ -2247,7 +2253,7 @@ declare namespace tracer {
22472253
* Disable LLM Observability tracing.
22482254
*/
22492255
disable (): void,
2250-
2256+
22512257
/**
22522258
* Instruments a function by automatically creating a span activated on its
22532259
* scope.
@@ -2289,10 +2295,10 @@ declare namespace tracer {
22892295
/**
22902296
* Decorate a function in a javascript runtime that supports function decorators.
22912297
* Note that this is **not** supported in the Node.js runtime, but is in TypeScript.
2292-
*
2298+
*
22932299
* In TypeScript, this decorator is only supported in contexts where general TypeScript
22942300
* function decorators are supported.
2295-
*
2301+
*
22962302
* @param options Optional LLM Observability span options.
22972303
*/
22982304
decorate (options: llmobs.LLMObsNamelessSpanOptions): any
@@ -2309,7 +2315,7 @@ declare namespace tracer {
23092315
/**
23102316
* Sets inputs, outputs, tags, metadata, and metrics as provided for a given LLM Observability span.
23112317
* Note that with the exception of tags, this method will override any existing values for the provided fields.
2312-
*
2318+
*
23132319
* For example:
23142320
* ```javascript
23152321
* llmobs.trace({ kind: 'llm', name: 'myLLM', modelName: 'gpt-4o', modelProvider: 'openai' }, () => {
@@ -2322,7 +2328,7 @@ declare namespace tracer {
23222328
* })
23232329
* })
23242330
* ```
2325-
*
2331+
*
23262332
* @param span The span to annotate (defaults to the current LLM Observability span if not provided)
23272333
* @param options An object containing the inputs, outputs, tags, metadata, and metrics to set on the span.
23282334
*/
@@ -2498,14 +2504,14 @@ declare namespace tracer {
24982504
* LLM Observability span kind. One of `agent`, `workflow`, `task`, `tool`, `retrieval`, `embedding`, or `llm`.
24992505
*/
25002506
kind: llmobs.spanKind,
2501-
2507+
25022508
/**
25032509
* The ID of the underlying user session. Required for tracking sessions.
25042510
*/
25052511
sessionId?: string,
25062512

25072513
/**
2508-
* The name of the ML application that the agent is orchestrating.
2514+
* The name of the ML application that the agent is orchestrating.
25092515
* If not provided, the default value will be set to mlApp provided during initalization, or `DD_LLMOBS_ML_APP`.
25102516
*/
25112517
mlApp?: string,

packages/datadog-instrumentations/src/pg.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ function wrapQuery (query) {
6262
abortController
6363
})
6464

65-
const finish = asyncResource.bind(function (error) {
65+
const finish = asyncResource.bind(function (error, res) {
6666
if (error) {
6767
errorCh.publish(error)
6868
}
69-
finishCh.publish()
69+
finishCh.publish({ result: res?.rows })
7070
})
7171

7272
if (abortController.signal.aborted) {
@@ -119,15 +119,15 @@ function wrapQuery (query) {
119119
if (newQuery.callback) {
120120
const originalCallback = callbackResource.bind(newQuery.callback)
121121
newQuery.callback = function (err, res) {
122-
finish(err)
122+
finish(err, res)
123123
return originalCallback.apply(this, arguments)
124124
}
125125
} else if (newQuery.once) {
126126
newQuery
127127
.once('error', finish)
128-
.once('end', () => finish())
128+
.once('end', (res) => finish(null, res))
129129
} else {
130-
newQuery.then(() => finish(), finish)
130+
newQuery.then((res) => finish(null, res), finish)
131131
}
132132

133133
try {

packages/datadog-instrumentations/src/sequelize.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ addHook({ name: 'sequelize', versions: ['>=4'] }, Sequelize => {
1313
const finishCh = channel('datadog:sequelize:query:finish')
1414

1515
shimmer.wrap(Sequelize.prototype, 'query', query => {
16-
return function (sql) {
16+
return function (sql, options) {
1717
if (!startCh.hasSubscribers) {
1818
return query.apply(this, arguments)
1919
}
@@ -27,9 +27,14 @@ addHook({ name: 'sequelize', versions: ['>=4'] }, Sequelize => {
2727
dialect = this.dialect.name
2828
}
2929

30-
function onFinish () {
30+
function onFinish (result) {
31+
const type = options?.type || 'RAW'
32+
if (type === 'RAW' && result?.length > 1) {
33+
result = result[0]
34+
}
35+
3136
asyncResource.bind(function () {
32-
finishCh.publish()
37+
finishCh.publish({ result })
3338
}, this).apply(this)
3439
}
3540

@@ -40,7 +45,7 @@ addHook({ name: 'sequelize', versions: ['>=4'] }, Sequelize => {
4045
})
4146

4247
const promise = query.apply(this, arguments)
43-
promise.then(onFinish, onFinish)
48+
promise.then(onFinish, () => { onFinish() })
4449

4550
return promise
4651
}, this).apply(this, arguments)

packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ class CodeInjectionAnalyzer extends InjectionAnalyzer {
1111
onConfigure () {
1212
this.addSub('datadog:eval:call', ({ script }) => this.analyze(script))
1313
}
14+
15+
_areRangesVulnerable () {
16+
return true
17+
}
1418
}
1519

1620
module.exports = new CodeInjectionAnalyzer()
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
'use strict'
22
const Analyzer = require('./vulnerability-analyzer')
3-
const { isTainted, getRanges } = require('../taint-tracking/operations')
3+
const { getRanges } = require('../taint-tracking/operations')
4+
const { SQL_ROW_VALUE } = require('../taint-tracking/source-types')
45

56
class InjectionAnalyzer extends Analyzer {
67
_isVulnerable (value, iastContext) {
7-
if (value) {
8-
return isTainted(iastContext, value)
8+
const ranges = value && getRanges(iastContext, value)
9+
if (ranges?.length > 0) {
10+
return this._areRangesVulnerable(ranges)
911
}
12+
1013
return false
1114
}
1215

1316
_getEvidence (value, iastContext) {
1417
const ranges = getRanges(iastContext, value)
1518
return { value, ranges }
1619
}
20+
21+
_areRangesVulnerable (ranges) {
22+
return ranges?.some(range => range.iinfo.type !== SQL_ROW_VALUE)
23+
}
1724
}
1825

1926
module.exports = InjectionAnalyzer

packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
8282
return knexDialect.toUpperCase()
8383
}
8484
}
85+
86+
_areRangesVulnerable () {
87+
return true
88+
}
8589
}
8690

8791
module.exports = new SqlInjectionAnalyzer()

packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ class TemplateInjectionAnalyzer extends InjectionAnalyzer {
1313
this.addSub('datadog:handlebars:register-partial:start', ({ partial }) => this.analyze(partial))
1414
this.addSub('datadog:pug:compile:start', ({ source }) => this.analyze(source))
1515
}
16+
17+
_areRangesVulnerable () {
18+
return true
19+
}
1620
}
1721

1822
module.exports = new TemplateInjectionAnalyzer()

packages/dd-trace/src/appsec/iast/iast-plugin.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ class IastPlugin extends Plugin {
9898
}
9999
}
100100

101-
enable () {
101+
enable (iastConfig) {
102+
this.iastConfig = iastConfig
102103
this.configure(true)
103104
}
104105

packages/dd-trace/src/appsec/iast/taint-tracking/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ module.exports = {
1818
enableTaintTracking (config, telemetryVerbosity) {
1919
enableRewriter(telemetryVerbosity)
2020
enableTaintOperations(telemetryVerbosity)
21-
taintTrackingPlugin.enable()
21+
taintTrackingPlugin.enable(config)
2222

23-
kafkaContextPlugin.enable()
24-
kafkaConsumerPlugin.enable()
23+
kafkaContextPlugin.enable(config)
24+
kafkaConsumerPlugin.enable(config)
2525

2626
setMaxTransactions(config.maxConcurrentRequests)
2727
},

0 commit comments

Comments
 (0)