Skip to content

Commit 7eae478

Browse files
committed
IAST metrics
1 parent 5156b6a commit 7eae478

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3204
-307
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"dependencies": {
6565
"@datadog/native-appsec": "2.0.0",
6666
"@datadog/native-iast-rewriter": "2.0.1",
67-
"@datadog/native-iast-taint-tracking": "1.1.1",
67+
"@datadog/native-iast-taint-tracking": "1.2.1",
6868
"@datadog/native-metrics": "^1.5.0",
6969
"@datadog/pprof": "^2.0.0",
7070
"@datadog/sketches-js": "^2.1.0",

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ const InjectionAnalyzer = require('./injection-analyzer')
44
class CommandInjectionAnalyzer extends InjectionAnalyzer {
55
constructor () {
66
super('COMMAND_INJECTION')
7-
this.addSub('datadog:child_process:execution:start', ({ command }) => this.analyze(command))
7+
}
8+
9+
onConfigure () {
10+
this.addSub(
11+
{ channelName: 'datadog:child_process:execution:start' },
12+
({ command }, iastPluginContext) => this.analyze(command, iastPluginContext)
13+
)
814
}
915
}
1016

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ const InjectionAnalyzer = require('./injection-analyzer')
44
class LdapInjectionAnalyzer extends InjectionAnalyzer {
55
constructor () {
66
super('LDAP_INJECTION')
7-
this.addSub('datadog:ldapjs:client:search', ({ base, filter }) => this.analyzeAll(base, filter))
7+
}
8+
9+
onConfigure () {
10+
this.addSub(
11+
{ channelName: 'datadog:ldapjs:client:search' },
12+
({ base, filter }, iastPluginContext) => this.analyzeAll(iastPluginContext, base, filter))
813
}
914
}
1015

packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
'use strict'
2-
const { getIastContext } = require('../iast-context')
3-
const { storage } = require('../../../../../datadog-core')
42
const InjectionAnalyzer = require('./injection-analyzer')
53

64
class PathTraversalAnalyzer extends InjectionAnalyzer {
75
constructor () {
86
super('PATH_TRAVERSAL')
9-
this.addSub('apm:fs:operation:start', obj => {
7+
this.addSub({ channelName: 'apm:fs:operation:start' }, (obj, iastPluginContext) => {
108
const pathArguments = []
119
if (obj.dest) {
1210
pathArguments.push(obj.dest)
@@ -35,12 +33,12 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
3533
if (obj.target) {
3634
pathArguments.push(obj.target)
3735
}
38-
this.analyze(pathArguments)
36+
this.analyze(pathArguments, iastPluginContext)
3937
})
4038
}
4139

42-
analyze (value) {
43-
const iastContext = getIastContext(storage.getStore())
40+
analyze (value, iastPluginContext) {
41+
const iastContext = iastPluginContext.iastContext
4442
if (!iastContext) {
4543
return
4644
}

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,21 @@ const InjectionAnalyzer = require('./injection-analyzer')
44
class SqlInjectionAnalyzer extends InjectionAnalyzer {
55
constructor () {
66
super('SQL_INJECTION')
7-
this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql))
8-
this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql))
9-
this.addSub('apm:pg:query:start', ({ originalQuery }) => this.analyze(originalQuery))
7+
}
8+
9+
onConfigure () {
10+
this.addSub(
11+
{ channelName: 'apm:mysql:query:start' },
12+
({ sql }, iastPluginContext) => this.analyze(sql, iastPluginContext)
13+
)
14+
this.addSub(
15+
{ channelName: 'apm:mysql2:query:start' },
16+
({ sql }, iastPluginContext) => this.analyze(sql, iastPluginContext)
17+
)
18+
this.addSub(
19+
{ channelName: 'apm:pg:query:start' },
20+
({ originalQuery }, iastPluginContext) => this.analyze(originalQuery, iastPluginContext)
21+
)
1022
}
1123
}
1224

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,16 @@
11
'use strict'
22

3-
const Plugin = require('../../../../src/plugins/plugin')
4-
const { storage } = require('../../../../../datadog-core')
5-
const iastLog = require('../iast-log')
63
const { getFirstNonDDPathAndLine } = require('../path-line')
74
const { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
8-
const { getIastContext } = require('../iast-context')
95
const overheadController = require('../overhead-controller')
6+
const { SinkIastPlugin } = require('../iast-plugin')
107

11-
class Analyzer extends Plugin {
8+
class Analyzer extends SinkIastPlugin {
129
constructor (type) {
1310
super()
1411
this._type = type
1512
}
1613

17-
_wrapHandler (handler) {
18-
return (message, name) => {
19-
try {
20-
handler(message, name)
21-
} catch (e) {
22-
iastLog.errorAndPublish(e)
23-
}
24-
}
25-
}
26-
27-
addSub (channelName, handler) {
28-
super.addSub(channelName, this._wrapHandler(handler))
29-
}
30-
3114
_isVulnerable (value, context) {
3215
return false
3316
}
@@ -56,17 +39,20 @@ class Analyzer extends Plugin {
5639
return getFirstNonDDPathAndLine()
5740
}
5841

59-
analyze (value) {
60-
const store = storage.getStore()
61-
const iastContext = getIastContext(store)
62-
if (store && !iastContext) return
63-
this._reportIfVulnerable(value, iastContext)
42+
_invalidContext (iastPluginContext) {
43+
return !iastPluginContext || (iastPluginContext.store && !iastPluginContext.iastContext)
6444
}
6545

66-
analyzeAll (...values) {
67-
const store = storage.getStore()
68-
const iastContext = getIastContext(store)
69-
if (store && !iastContext) return
46+
analyze (value, iastPluginContext) {
47+
if (this._invalidContext(iastPluginContext)) return
48+
49+
this._reportIfVulnerable(value, iastPluginContext.iastContext)
50+
}
51+
52+
analyzeAll (iastPluginContext, ...values) {
53+
if (this._invalidContext(iastPluginContext)) return
54+
55+
const iastContext = iastPluginContext.iastContext
7056
for (let i = 0; i < values.length; i++) {
7157
const value = values[i]
7258
if (this._isVulnerable(value, iastContext)) {
@@ -81,6 +67,10 @@ class Analyzer extends Plugin {
8167
_checkOCE (context) {
8268
return overheadController.hasQuota(overheadController.OPERATIONS.REPORT_VULNERABILITY, context)
8369
}
70+
71+
addSub (iastSub, handler) {
72+
super.addSub({ tag: this._type, ...iastSub }, handler)
73+
}
8474
}
8575

8676
module.exports = Analyzer

packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ const INSECURE_CIPHERS = new Set([
1313
class WeakCipherAnalyzer extends Analyzer {
1414
constructor () {
1515
super('WEAK_CIPHER')
16-
this.addSub('datadog:crypto:cipher:start', ({ algorithm }) => this.analyze(algorithm))
16+
}
17+
18+
onConfigure () {
19+
this.addSub(
20+
{ channelName: 'datadog:crypto:cipher:start' },
21+
({ algorithm }, iastPluginContext) => this.analyze(algorithm, iastPluginContext))
1722
}
1823

1924
_isVulnerable (algorithm) {

packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ const INSECURE_HASH_ALGORITHMS = new Set([
1010
class WeakHashAnalyzer extends Analyzer {
1111
constructor () {
1212
super('WEAK_HASH')
13-
this.addSub('datadog:crypto:hashing:start', ({ algorithm }) => this.analyze(algorithm))
13+
}
14+
15+
onConfigure () {
16+
this.addSub(
17+
{ channelName: 'datadog:crypto:hashing:start' },
18+
({ algorithm }, iastPluginContext) => this.analyze(algorithm, iastPluginContext)
19+
)
1420
}
1521

1622
_isVulnerable (algorithm) {

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

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use strict'
22

33
const log = require('../../log')
4-
const telemetryLogs = require('./telemetry/logs')
4+
const telemetry = require('../telemetry')
5+
const telemetryLogs = require('../telemetry/api/logs-plugin')
56
const { calculateDDBasePath } = require('../../util')
67

78
const ddBasePath = calculateDDBasePath(__dirname)
@@ -11,19 +12,23 @@ const STACK_FRAME_LINE_REGEX = /^\s*at\s/gm
1112
function sanitize (logEntry, stack) {
1213
if (!stack) return logEntry
1314

14-
let stackLines = stack.split(EOL)
15+
if (telemetry.isDebugEnabled()) {
16+
logEntry.stack_trace = stack
17+
} else {
18+
let stackLines = stack.split(EOL)
1519

16-
const firstIndex = stackLines.findIndex(l => l.match(STACK_FRAME_LINE_REGEX))
20+
const firstIndex = stackLines.findIndex(l => l.match(STACK_FRAME_LINE_REGEX))
1721

18-
const isDDCode = firstIndex > -1 && stackLines[firstIndex].includes(ddBasePath)
19-
stackLines = stackLines
20-
.filter((line, index) => (isDDCode && index < firstIndex) || line.includes(ddBasePath))
21-
.map(line => line.replace(ddBasePath, ''))
22+
const isDDCode = firstIndex > -1 && stackLines[firstIndex].includes(ddBasePath)
23+
stackLines = stackLines
24+
.filter((line, index) => (isDDCode && index < firstIndex) || line.includes(ddBasePath))
25+
.map(line => line.replace(ddBasePath, ''))
2226

23-
logEntry.stack_trace = stackLines.join(EOL)
27+
logEntry.stack_trace = stackLines.join(EOL)
2428

25-
if (!isDDCode) {
26-
logEntry.message = 'omitted'
29+
if (!isDDCode) {
30+
logEntry.message = 'omitted'
31+
}
2732
}
2833

2934
return logEntry
@@ -81,7 +86,7 @@ const iastLog = {
8186

8287
publish (data, level) {
8388
if (telemetryLogs.isLevelEnabled(level)) {
84-
const telemetryLog = getTelemetryLog(data, level)
89+
const telemetryLog = getTelemetryLog(data, level, telemetry.verbosity)
8590
telemetryLogs.publish(telemetryLog)
8691
}
8792
return this
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
'use strict'
2+
3+
const { Metric, Scope } = require('../telemetry/metric')
4+
5+
const IAST_NAMESPACE = 'iast'
6+
7+
const PropagationType = {
8+
STRING: 'STRING',
9+
JSON: 'JSON',
10+
URL: 'URL'
11+
}
12+
13+
const MetricTag = {
14+
VULNERABILITY_TYPE: 'vulnerability_type',
15+
SOURCE_TYPE: 'source_type',
16+
PROPAGATION_TYPE: 'propagation_type'
17+
}
18+
19+
function getExecutedMetric (metricTag) {
20+
return metricTag === MetricTag.VULNERABILITY_TYPE ? EXECUTED_SINK : EXECUTED_SOURCE
21+
}
22+
23+
function getInstrumentedMetric (metricTag) {
24+
return metricTag === MetricTag.VULNERABILITY_TYPE ? INSTRUMENTED_SINK : INSTRUMENTED_SOURCE
25+
}
26+
27+
const INSTRUMENTED_PROPAGATION =
28+
new Metric('instrumented.propagation', Scope.GLOBAL, MetricTag.PROPAGATION_TYPE, IAST_NAMESPACE)
29+
const INSTRUMENTED_SOURCE = new Metric('instrumented.source', Scope.GLOBAL, MetricTag.SOURCE_TYPE, IAST_NAMESPACE)
30+
const INSTRUMENTED_SINK = new Metric('instrumented.sink', Scope.GLOBAL, MetricTag.VULNERABILITY_TYPE, IAST_NAMESPACE)
31+
32+
const EXECUTED_SOURCE = new Metric('executed.source', Scope.REQUEST, MetricTag.SOURCE_TYPE, IAST_NAMESPACE)
33+
const EXECUTED_SINK = new Metric('executed.sink', Scope.REQUEST, MetricTag.VULNERABILITY_TYPE, IAST_NAMESPACE)
34+
35+
const REQUEST_TAINTED = new Metric('request.tainted', Scope.REQUEST, null, IAST_NAMESPACE)
36+
37+
// DEBUG using metrics
38+
const EXECUTED_PROPAGATION =
39+
new Metric('executed.propagation', Scope.REQUEST, MetricTag.PROPAGATION_TYPE, IAST_NAMESPACE)
40+
const EXECUTED_TAINTED = new Metric('executed.tainted', Scope.REQUEST, null, IAST_NAMESPACE)
41+
42+
// DEBUG using log endpoint
43+
// const SOURCE_DEBUG = new Metric('source.debug', Scope.GLOBAL, null, IAST_NAMESPACE)
44+
// const PROPAGATION_DEBUG = new Metric('propagation.debug', Scope.GLOBAL, null, IAST_NAMESPACE)
45+
// const SINK_DEBUG = new Metric('sink.debug', Scope.GLOBAL, null, IAST_NAMESPACE)
46+
// const TAINTED_DEBUG = new Metric('tainted.debug', Scope.GLOBAL, null, IAST_NAMESPACE)
47+
// const TAINTED_SINK_DEBUG = new Metric('tainted.sink.debug', Scope.GLOBAL, null, IAST_NAMESPACE)
48+
49+
// DEBUG using distribution endpoint
50+
const INSTRUMENTATION_TIME = new Metric('instrumentation.time', Scope.GLOBAL, null, IAST_NAMESPACE)
51+
// const EXECUTION_TIME = new Metric('execution.time', Scope.GLOBAL, null, IAST_NAMESPACE)
52+
53+
const IastMetric = {
54+
INSTRUMENTED_PROPAGATION,
55+
INSTRUMENTED_SOURCE,
56+
INSTRUMENTED_SINK,
57+
58+
EXECUTED_PROPAGATION,
59+
EXECUTED_SOURCE,
60+
EXECUTED_SINK,
61+
EXECUTED_TAINTED,
62+
63+
REQUEST_TAINTED,
64+
65+
INSTRUMENTATION_TIME
66+
}
67+
68+
const metrics = new Map()
69+
for (const iastMetricName in IastMetric) {
70+
const iastMetric = IastMetric[iastMetricName]
71+
metrics.set(iastMetric.name, iastMetric)
72+
}
73+
74+
module.exports = {
75+
IastMetric,
76+
PropagationType,
77+
MetricTag,
78+
79+
getExecutedMetric,
80+
getInstrumentedMetric
81+
}

0 commit comments

Comments
 (0)