Skip to content

Commit 983992b

Browse files
committed
Add check for safe tainted origins on unvalidated redirect analyzer
1 parent baa67ee commit 983992b

File tree

2 files changed

+81
-19
lines changed

2 files changed

+81
-19
lines changed

packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ const InjectionAnalyzer = require('./injection-analyzer')
44
const { UNVALIDATED_REDIRECT } = require('../vulnerabilities')
55
const { getNodeModulesPaths } = require('../path-line')
66
const { getRanges } = require('../taint-tracking/operations')
7-
const { HTTP_REQUEST_HEADER_VALUE } = require('../taint-tracking/origin-types')
7+
const {
8+
HTTP_REQUEST_HEADER_VALUE,
9+
HTTP_REQUEST_PATH,
10+
HTTP_REQUEST_PATH_PARAM
11+
} = require('../taint-tracking/origin-types')
812

913
const EXCLUDED_PATHS = getNodeModulesPaths('express/lib/response.js')
1014

@@ -15,9 +19,6 @@ class UnvalidatedRedirectAnalyzer extends InjectionAnalyzer {
1519
this.addSub('datadog:http:server:response:set-header:finish', ({ name, value }) => this.analyze(name, value))
1620
}
1721

18-
// TODO: In case the location header value is tainted, this analyzer should check the ranges of the tainted.
19-
// And do not report a vulnerability if source of the ranges (range.iinfo.type) are exclusively url or path params
20-
// to avoid false positives.
2122
analyze (name, value) {
2223
if (!this.isLocationHeader(name) || typeof value !== 'string') return
2324

@@ -32,12 +33,28 @@ class UnvalidatedRedirectAnalyzer extends InjectionAnalyzer {
3233
if (!value) return false
3334

3435
const ranges = getRanges(iastContext, value)
35-
return ranges && ranges.length > 0 && !this._isRefererHeader(ranges)
36+
return ranges && ranges.length > 0 && !this._areSafeRanges(ranges)
3637
}
3738

38-
_isRefererHeader (ranges) {
39-
return ranges && ranges.every(range => range.iinfo.type === HTTP_REQUEST_HEADER_VALUE &&
40-
range.iinfo.parameterName && range.iinfo.parameterName.toLowerCase() === 'referer')
39+
// Do not report vulnerability if ranges sources are exclusively url,
40+
// path params or referer header to avoid false positives.
41+
_areSafeRanges (ranges) {
42+
return ranges && ranges.every(
43+
range => this._isPathParam(range) || this._isUrl(range) || this._isRefererHeader(range)
44+
)
45+
}
46+
47+
_isRefererHeader (range) {
48+
return range.iinfo.type === HTTP_REQUEST_HEADER_VALUE &&
49+
range.iinfo.parameterName && range.iinfo.parameterName.toLowerCase() === 'referer'
50+
}
51+
52+
_isPathParam (range) {
53+
return range.iinfo.type === HTTP_REQUEST_PATH_PARAM
54+
}
55+
56+
_isUrl (range) {
57+
return range.iinfo.type === HTTP_REQUEST_PATH
4158
}
4259

4360
_getExcludedPaths () {

packages/dd-trace/test/appsec/iast/analyzers/unvalidated-redirect-analyzer.spec.js

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,23 @@
33
const { expect } = require('chai')
44
const proxyquire = require('proxyquire')
55
const overheadController = require('../../../../src/appsec/iast/overhead-controller')
6-
const { HTTP_REQUEST_HEADER_VALUE, HTTP_REQUEST_PARAMETER } =
6+
const {
7+
HTTP_REQUEST_HEADER_VALUE,
8+
HTTP_REQUEST_PARAMETER,
9+
HTTP_REQUEST_PATH,
10+
HTTP_REQUEST_PATH_PARAM
11+
} =
712
require('../../../../src/appsec/iast/taint-tracking/origin-types')
813

914
describe('unvalidated-redirect-analyzer', () => {
1015
const NOT_TAINTED_LOCATION = 'url.com'
1116
const TAINTED_LOCATION = 'evil.com'
1217

1318
const TAINTED_HEADER_REFERER_ONLY = 'TAINTED_HEADER_REFERER_ONLY'
14-
const TAINTED_HEADER_REFERER_AMONG_OTHERS = 'TAINTED_HEADER_REFERER_ONLY_AMONG_OTHERS'
19+
const TAINTED_PATH_PARAMS_ONLY = 'TAINTED_PATH_PARAMS_ONLY'
20+
const TAINTED_URL_ONLY = 'TAINTED_URL_ONLY'
21+
const TAINTED_SAFE_RANGES = 'TAINTED_SAFE_RANGES'
22+
const TAINTED_SAFE_RANGES_AMONG_OTHERS = 'TAINTED_SAFE_RANGES_AMONG_OTHERS'
1523

1624
const REFERER_RANGE = {
1725
iinfo: {
@@ -31,21 +39,40 @@ describe('unvalidated-redirect-analyzer', () => {
3139
parameterName: 'param2'
3240
}
3341
}
42+
const PATH_PARAM_RANGE = {
43+
iinfo: {
44+
type: HTTP_REQUEST_PATH_PARAM,
45+
parameterName: 'path_param'
46+
}
47+
}
48+
const URL_RANGE = {
49+
iinfo: {
50+
type: HTTP_REQUEST_PATH,
51+
parameterName: 'path'
52+
}
53+
}
3454

3555
const TaintTrackingMock = {
3656
isTainted: (iastContext, string) => {
3757
return string === TAINTED_LOCATION
3858
},
3959

4060
getRanges: (iastContext, value) => {
41-
if (value === NOT_TAINTED_LOCATION) return null
42-
43-
if (value === TAINTED_HEADER_REFERER_ONLY) {
44-
return [REFERER_RANGE]
45-
} else if (value === TAINTED_HEADER_REFERER_AMONG_OTHERS) {
46-
return [REFERER_RANGE, PARAMETER1_RANGE]
47-
} else {
48-
return [PARAMETER1_RANGE, PARAMETER2_RANGE]
61+
switch (value) {
62+
case NOT_TAINTED_LOCATION:
63+
return null
64+
case TAINTED_HEADER_REFERER_ONLY:
65+
return [REFERER_RANGE]
66+
case TAINTED_PATH_PARAMS_ONLY:
67+
return [PATH_PARAM_RANGE]
68+
case TAINTED_URL_ONLY:
69+
return [URL_RANGE]
70+
case TAINTED_SAFE_RANGES:
71+
return [REFERER_RANGE, PATH_PARAM_RANGE, URL_RANGE]
72+
case TAINTED_SAFE_RANGES_AMONG_OTHERS:
73+
return [REFERER_RANGE, PATH_PARAM_RANGE, URL_RANGE, PARAMETER1_RANGE]
74+
default:
75+
return [PARAMETER1_RANGE, PARAMETER2_RANGE]
4976
}
5077
}
5178
}
@@ -103,8 +130,26 @@ describe('unvalidated-redirect-analyzer', () => {
103130
expect(report).to.not.be.called
104131
})
105132

133+
it('should not report if tainted origin is path param exclusively', () => {
134+
unvalidatedRedirectAnalyzer.analyze('Location', TAINTED_PATH_PARAMS_ONLY)
135+
136+
expect(report).to.not.be.called
137+
})
138+
139+
it('should not report if tainted origin is url exclusively', () => {
140+
unvalidatedRedirectAnalyzer.analyze('Location', TAINTED_URL_ONLY)
141+
142+
expect(report).to.not.be.called
143+
})
144+
145+
it('should not report if all tainted origin are safe', () => {
146+
unvalidatedRedirectAnalyzer.analyze('Location', TAINTED_SAFE_RANGES)
147+
148+
expect(report).to.not.be.called
149+
})
150+
106151
it('should report if tainted origin contains referer header among others', () => {
107-
unvalidatedRedirectAnalyzer.analyze('Location', TAINTED_HEADER_REFERER_AMONG_OTHERS)
152+
unvalidatedRedirectAnalyzer.analyze('Location', TAINTED_SAFE_RANGES_AMONG_OTHERS)
108153

109154
expect(report).to.be.called
110155
})

0 commit comments

Comments
 (0)