Skip to content

Commit 72597c5

Browse files
authored
Add blocking for fastify query and body (#5806)
* Add blocking for fastify query and body * query params channel name * sort channels * add fastify appsec test * fix http header fingerprint * change rules name * fix fingerprint test * add schema validation tests * linter
1 parent b249c2e commit 72597c5

File tree

9 files changed

+430
-26
lines changed

9 files changed

+430
-26
lines changed

.github/workflows/appsec.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,19 @@ jobs:
128128
- run: yarn test:appsec:plugins:ci
129129
- uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
130130

131+
fastify:
132+
runs-on: ubuntu-latest
133+
env:
134+
PLUGINS: fastify
135+
steps:
136+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
137+
- uses: ./.github/actions/node/oldest-maintenance-lts
138+
- uses: ./.github/actions/install
139+
- run: yarn test:appsec:plugins:ci
140+
- uses: ./.github/actions/node/active-lts
141+
- run: yarn test:appsec:plugins:ci
142+
- uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
143+
131144
graphql:
132145
runs-on: ubuntu-latest
133146
env:

packages/datadog-instrumentations/src/fastify.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const { addHook, channel, AsyncResource } = require('./helpers/instrument')
66
const errorChannel = channel('apm:fastify:middleware:error')
77
const handleChannel = channel('apm:fastify:request:handle')
88
const routeAddedChannel = channel('apm:fastify:route:added')
9+
const bodyParserReadCh = channel('datadog:fastify:body-parser:finish')
10+
const queryParamsReadCh = channel('datadog:fastify:query-params:finish')
911

1012
const parsingResources = new WeakMap()
1113

@@ -106,11 +108,37 @@ function preHandler (request, reply, done) {
106108

107109
function preValidation (request, reply, done) {
108110
const req = getReq(request)
111+
const res = getRes(reply)
109112
const parsingResource = parsingResources.get(req)
110113

111-
if (!parsingResource) return done()
114+
const processInContext = () => {
115+
if (queryParamsReadCh.hasSubscribers && request.query) {
116+
const abortController = new AbortController()
112117

113-
parsingResource.runInAsyncScope(() => done())
118+
queryParamsReadCh.publish({
119+
req,
120+
res,
121+
abortController,
122+
query: request.query
123+
})
124+
125+
if (abortController.signal.aborted) return
126+
}
127+
128+
if (bodyParserReadCh.hasSubscribers && request.body) {
129+
const abortController = new AbortController()
130+
131+
bodyParserReadCh.publish({ req, res, body: request.body, abortController })
132+
133+
if (abortController.signal.aborted) return
134+
}
135+
136+
done()
137+
}
138+
139+
if (!parsingResource) return processInContext()
140+
141+
parsingResource.runInAsyncScope(processInContext)
114142
}
115143

116144
function preParsing (request, reply, payload, done) {

packages/dd-trace/src/appsec/channels.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,36 @@ const dc = require('dc-polyfill')
44

55
// TODO: use TBD naming convention
66
module.exports = {
7+
apolloChannel: dc.tracingChannel('datadog:apollo:request'),
8+
apolloServerCoreChannel: dc.tracingChannel('datadog:apollo-server-core:request'),
79
bodyParser: dc.channel('datadog:body-parser:read:finish'),
10+
childProcessExecutionTracingChannel: dc.tracingChannel('datadog:child_process:execution'),
811
cookieParser: dc.channel('datadog:cookie-parser:read:finish'),
9-
multerParser: dc.channel('datadog:multer:read:finish'),
10-
startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
12+
expressMiddlewareError: dc.channel('apm:express:middleware:error'),
13+
expressProcessParams: dc.channel('datadog:express:process_params:start'),
14+
expressSession: dc.channel('datadog:express-session:middleware:finish'),
15+
fastifyBodyParser: dc.channel('datadog:fastify:body-parser:finish'),
16+
fastifyQueryParams: dc.channel('datadog:fastify:query-params:finish'),
17+
fsOperationStart: dc.channel('apm:fs:operation:start'),
1118
graphqlMiddlewareChannel: dc.tracingChannel('datadog:apollo:middleware'),
12-
apolloChannel: dc.tracingChannel('datadog:apollo:request'),
13-
apolloServerCoreChannel: dc.tracingChannel('datadog:apollo-server-core:request'),
14-
incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
19+
httpClientRequestStart: dc.channel('apm:http:client:request:start'),
1520
incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
16-
passportVerify: dc.channel('datadog:passport:verify:finish'),
17-
passportUser: dc.channel('datadog:passport:deserializeUser:finish'),
18-
expressSession: dc.channel('datadog:express-session:middleware:finish'),
19-
queryParser: dc.channel('datadog:query:read:finish'),
20-
setCookieChannel: dc.channel('datadog:iast:set-cookie'),
21+
incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
22+
multerParser: dc.channel('datadog:multer:read:finish'),
23+
mysql2OuterQueryStart: dc.channel('datadog:mysql2:outerquery:start'),
2124
nextBodyParsed: dc.channel('apm:next:body-parsed'),
2225
nextQueryParsed: dc.channel('apm:next:query-parsed'),
23-
expressProcessParams: dc.channel('datadog:express:process_params:start'),
24-
routerParam: dc.channel('datadog:router:param:start'),
26+
passportUser: dc.channel('datadog:passport:deserializeUser:finish'),
27+
passportVerify: dc.channel('datadog:passport:verify:finish'),
28+
pgPoolQueryStart: dc.channel('datadog:pg:pool:query:start'),
29+
pgQueryStart: dc.channel('apm:pg:query:start'),
30+
queryParser: dc.channel('datadog:query:read:finish'),
2531
responseBody: dc.channel('datadog:express:response:json:start'),
26-
responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'),
27-
httpClientRequestStart: dc.channel('apm:http:client:request:start'),
2832
responseSetHeader: dc.channel('datadog:http:server:response:set-header:start'),
33+
responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'),
34+
routerParam: dc.channel('datadog:router:param:start'),
35+
setCookieChannel: dc.channel('datadog:iast:set-cookie'),
2936
setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start'),
30-
pgQueryStart: dc.channel('apm:pg:query:start'),
31-
pgPoolQueryStart: dc.channel('datadog:pg:pool:query:start'),
32-
mysql2OuterQueryStart: dc.channel('datadog:mysql2:outerquery:start'),
33-
wafRunFinished: dc.channel('datadog:waf:run:finish'),
34-
fsOperationStart: dc.channel('apm:fs:operation:start'),
35-
expressMiddlewareError: dc.channel('apm:express:middleware:error'),
36-
childProcessExecutionTracingChannel: dc.tracingChannel('datadog:child_process:execution')
37+
startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
38+
wafRunFinished: dc.channel('datadog:waf:run:finish')
3739
}

packages/dd-trace/src/appsec/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const {
77
bodyParser,
88
cookieParser,
99
multerParser,
10+
fastifyBodyParser,
1011
incomingHttpRequestStart,
1112
incomingHttpRequestEnd,
1213
passportVerify,
@@ -16,6 +17,7 @@ const {
1617
nextBodyParsed,
1718
nextQueryParsed,
1819
expressProcessParams,
20+
fastifyQueryParams,
1921
responseBody,
2022
responseWriteHead,
2123
responseSetHeader,
@@ -67,6 +69,7 @@ function enable (_config) {
6769

6870
bodyParser.subscribe(onRequestBodyParsed)
6971
multerParser.subscribe(onRequestBodyParsed)
72+
fastifyBodyParser.subscribe(onRequestBodyParsed)
7073
cookieParser.subscribe(onRequestCookieParser)
7174
incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
7275
incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
@@ -77,6 +80,7 @@ function enable (_config) {
7780
nextBodyParsed.subscribe(onRequestBodyParsed)
7881
nextQueryParsed.subscribe(onRequestQueryParsed)
7982
expressProcessParams.subscribe(onRequestProcessParams)
83+
fastifyQueryParams.subscribe(onRequestQueryParsed)
8084
routerParam.subscribe(onRequestProcessParams)
8185
responseBody.subscribe(onResponseBody)
8286
responseWriteHead.subscribe(onResponseWriteHead)
@@ -357,6 +361,7 @@ function disable () {
357361
// Channel#unsubscribe() is undefined for non active channels
358362
if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
359363
if (multerParser.hasSubscribers) multerParser.unsubscribe(onRequestBodyParsed)
364+
if (fastifyBodyParser.hasSubscribers) fastifyBodyParser.unsubscribe(onRequestBodyParsed)
360365
if (cookieParser.hasSubscribers) cookieParser.unsubscribe(onRequestCookieParser)
361366
if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
362367
if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
@@ -367,6 +372,7 @@ function disable () {
367372
if (nextBodyParsed.hasSubscribers) nextBodyParsed.unsubscribe(onRequestBodyParsed)
368373
if (nextQueryParsed.hasSubscribers) nextQueryParsed.unsubscribe(onRequestQueryParsed)
369374
if (expressProcessParams.hasSubscribers) expressProcessParams.unsubscribe(onRequestProcessParams)
375+
if (fastifyQueryParams.hasSubscribers) fastifyQueryParams.unsubscribe(onRequestQueryParsed)
370376
if (routerParam.hasSubscribers) routerParam.unsubscribe(onRequestProcessParams)
371377
if (responseBody.hasSubscribers) responseBody.unsubscribe(onResponseBody)
372378
if (responseWriteHead.hasSubscribers) responseWriteHead.unsubscribe(onResponseWriteHead)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
'use strict'
2+
3+
const Axios = require('axios')
4+
const { assert } = require('chai')
5+
const getPort = require('get-port')
6+
const path = require('path')
7+
8+
const agent = require('../plugins/agent')
9+
const appsec = require('../../src/appsec')
10+
const Config = require('../../src/config')
11+
12+
withVersions('fastify', 'fastify', fastifyVersion => {
13+
describe('Attacker fingerprinting', () => {
14+
let server, axios
15+
16+
before(() => {
17+
return agent.load(['fastify', 'http'], { client: false })
18+
})
19+
20+
before((done) => {
21+
const fastify = require(`../../../../versions/fastify@${fastifyVersion}`).get()
22+
23+
const app = fastify()
24+
25+
app.post('/', (request, reply) => {
26+
reply.send('DONE')
27+
})
28+
29+
getPort().then((port) => {
30+
app.listen({ port }, () => {
31+
axios = Axios.create({ baseURL: `http://localhost:${port}` })
32+
done()
33+
})
34+
server = app.server
35+
})
36+
})
37+
38+
after(() => {
39+
server.close()
40+
return agent.close({ ritmReset: false })
41+
})
42+
43+
beforeEach(() => {
44+
appsec.enable(new Config(
45+
{
46+
appsec: {
47+
enabled: true,
48+
rules: path.join(__dirname, 'attacker-fingerprinting-rules.json')
49+
}
50+
}
51+
))
52+
})
53+
54+
afterEach(() => {
55+
appsec.disable()
56+
})
57+
58+
it('should report http fingerprints', async () => {
59+
await axios.post('/?key=testattack',
60+
{
61+
bodyParam: 'bodyValue'
62+
},
63+
{
64+
headers: {
65+
'User-Agent': 'test-user-agent',
66+
headerName: 'headerValue',
67+
'x-real-ip': '255.255.255.255'
68+
}
69+
}
70+
)
71+
72+
await agent.assertSomeTraces((traces) => {
73+
const span = traces[0][0]
74+
assert.property(span.meta, '_dd.appsec.fp.http.header')
75+
assert.equal(span.meta['_dd.appsec.fp.http.header'], 'hdr-0110000110-74c2908f-5-55682ec1')
76+
assert.property(span.meta, '_dd.appsec.fp.http.network')
77+
assert.equal(span.meta['_dd.appsec.fp.http.network'], 'net-1-0100000000')
78+
assert.property(span.meta, '_dd.appsec.fp.http.endpoint')
79+
assert.equal(span.meta['_dd.appsec.fp.http.endpoint'], 'http-post-8a5edab2-2c70e12b-be31090f')
80+
})
81+
})
82+
})
83+
})

packages/dd-trace/test/appsec/index.express.plugin.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ withVersions('express', 'express', version => {
6363
appsec.enable(new Config({
6464
appsec: {
6565
enabled: true,
66-
rules: path.join(__dirname, 'express-rules.json')
66+
rules: path.join(__dirname, 'rules-example.json')
6767
}
6868
}))
6969
})
@@ -214,7 +214,7 @@ withVersions('express', 'express', version => {
214214
appsec.enable(new Config({
215215
appsec: {
216216
enabled: true,
217-
rules: path.join(__dirname, 'express-rules.json')
217+
rules: path.join(__dirname, 'rules-example.json')
218218
}
219219
}))
220220
})

0 commit comments

Comments
 (0)