-
Notifications
You must be signed in to change notification settings - Fork 306
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
249 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
'use strict' | ||
|
||
const shimmer = require('../../datadog-shimmer') | ||
const { channel, addHook, AsyncResource } = require('./helpers/instrument') | ||
|
||
const multerReadCh = channel('datadog:multer:read:finish') | ||
|
||
function publishRequestBodyAndNext (req, res, next) { | ||
return shimmer.wrapFunction(next, next => function () { | ||
if (multerReadCh.hasSubscribers && req) { | ||
const abortController = new AbortController() | ||
const body = req.body | ||
|
||
multerReadCh.publish({ req, res, body, abortController }) | ||
|
||
if (abortController.signal.aborted) return | ||
} | ||
|
||
return next.apply(this, arguments) | ||
}) | ||
} | ||
|
||
addHook({ | ||
name: 'multer', | ||
file: 'lib/make-middleware.js', | ||
versions: ['>=1.4.4 < 2.0.0'] | ||
}, makeMiddleware => { | ||
return shimmer.wrapFunction(makeMiddleware, makeMiddleware => function () { | ||
const middleware = makeMiddleware.apply(this, arguments) | ||
|
||
return shimmer.wrapFunction(middleware, middleware => function wrapMulterMiddleware (req, res, next) { | ||
const nextResource = new AsyncResource('bound-anonymous-fn') | ||
arguments[2] = nextResource.bind(publishRequestBodyAndNext(req, res, next)) | ||
return middleware.apply(this, arguments) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
'use strict' | ||
|
||
const dc = require('dc-polyfill') | ||
const axios = require('axios') | ||
const agent = require('../../dd-trace/test/plugins/agent') | ||
const { storage } = require('../../datadog-core') | ||
|
||
withVersions('multer', 'multer', version => { | ||
describe('multer parser instrumentation', () => { | ||
const multerReadCh = dc.channel('datadog:multer:read:finish') | ||
let port, server, middlewareProcessBodyStub, formData | ||
|
||
before(() => { | ||
return agent.load(['http', 'express', 'multer'], { client: false }) | ||
}) | ||
|
||
before((done) => { | ||
const express = require('../../../versions/express').get() | ||
const multer = require(`../../../versions/multer@${version}`).get() | ||
const uploadToMemory = multer({ storage: multer.memoryStorage(), limits: { fileSize: 200000 } }) | ||
|
||
const app = express() | ||
|
||
app.post('/', uploadToMemory.single('file'), (req, res) => { | ||
middlewareProcessBodyStub(req.body.key) | ||
res.end('DONE') | ||
}) | ||
server = app.listen(0, () => { | ||
port = server.address().port | ||
done() | ||
}) | ||
}) | ||
|
||
beforeEach(async () => { | ||
middlewareProcessBodyStub = sinon.stub() | ||
|
||
formData = new FormData() | ||
formData.append('key', 'value') | ||
}) | ||
|
||
after(() => { | ||
server.close() | ||
return agent.close({ ritmReset: false }) | ||
}) | ||
|
||
it('should not abort the request by default', async () => { | ||
const res = await axios.post(`http://localhost:${port}/`, formData) | ||
|
||
expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) | ||
expect(res.data).to.be.equal('DONE') | ||
}) | ||
|
||
it('should not abort the request with non blocker subscription', async () => { | ||
function noop () {} | ||
multerReadCh.subscribe(noop) | ||
|
||
const form = new FormData() | ||
form.append('key', 'value') | ||
|
||
const res = await axios.post(`http://localhost:${port}/`, formData) | ||
|
||
expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) | ||
expect(res.data).to.be.equal('DONE') | ||
|
||
multerReadCh.unsubscribe(noop) | ||
}) | ||
|
||
it('should abort the request when abortController.abort() is called', async () => { | ||
function blockRequest ({ res, abortController }) { | ||
res.end('BLOCKED') | ||
abortController.abort() | ||
} | ||
multerReadCh.subscribe(blockRequest) | ||
|
||
const res = await axios.post(`http://localhost:${port}/`, formData) | ||
|
||
expect(middlewareProcessBodyStub).not.to.be.called | ||
expect(res.data).to.be.equal('BLOCKED') | ||
|
||
multerReadCh.unsubscribe(blockRequest) | ||
}) | ||
|
||
it('should not lose the http async context', async () => { | ||
let store | ||
let payload | ||
|
||
function handler (data) { | ||
store = storage.getStore() | ||
payload = data | ||
} | ||
multerReadCh.subscribe(handler) | ||
|
||
const res = await axios.post(`http://localhost:${port}/`, formData) | ||
|
||
expect(store).to.have.property('req', payload.req) | ||
expect(store).to.have.property('res', payload.res) | ||
expect(store).to.have.property('span') | ||
|
||
expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) | ||
expect(res.data).to.be.equal('DONE') | ||
|
||
multerReadCh.unsubscribe(handler) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
'use strict' | ||
|
||
const { channel } = require('dc-polyfill') | ||
const axios = require('axios') | ||
const path = require('path') | ||
const agent = require('../plugins/agent') | ||
const appsec = require('../../src/appsec') | ||
const Config = require('../../src/config') | ||
const { json } = require('../../src/appsec/blocked_templates') | ||
|
||
const multerReadCh = channel('datadog:multer:read:finish') | ||
|
||
withVersions('multer', 'multer', version => { | ||
describe('Suspicious request blocking - multer', () => { | ||
let port, server, requestBody, onMulterRead | ||
|
||
before(() => { | ||
return agent.load(['express', 'multer', 'http'], { client: false }) | ||
}) | ||
|
||
before((done) => { | ||
const express = require('../../../../versions/express').get() | ||
const multer = require(`../../../../versions/multer@${version}`).get() | ||
const uploadToMemory = multer({ storage: multer.memoryStorage(), limits: { fileSize: 200000 } }) | ||
|
||
const app = express() | ||
|
||
app.post('/', uploadToMemory.single('file'), (req, res) => { | ||
requestBody(req) | ||
res.end('DONE') | ||
}) | ||
|
||
server = app.listen(port, () => { | ||
port = server.address().port | ||
done() | ||
}) | ||
}) | ||
|
||
beforeEach(async () => { | ||
requestBody = sinon.stub() | ||
onMulterRead = sinon.stub() | ||
multerReadCh.subscribe(onMulterRead) | ||
|
||
appsec.enable(new Config({ appsec: { enabled: true, rules: path.join(__dirname, 'body-parser-rules.json') } })) | ||
}) | ||
|
||
afterEach(() => { | ||
sinon.restore() | ||
appsec.disable() | ||
}) | ||
|
||
after(() => { | ||
server.close() | ||
return agent.close({ ritmReset: false }) | ||
}) | ||
|
||
it('should not block the request without an attack', async () => { | ||
const form = new FormData() | ||
form.append('key', 'value') | ||
|
||
const res = await axios.post(`http://localhost:${port}/`, form) | ||
|
||
expect(requestBody).to.be.calledOnce | ||
expect(res.data).to.be.equal('DONE') | ||
}) | ||
|
||
it('should block the request when attack is detected', async () => { | ||
try { | ||
const form = new FormData() | ||
form.append('key', 'testattack') | ||
|
||
await axios.post(`http://localhost:${port}/`, form) | ||
|
||
return Promise.reject(new Error('Request should not return 200')) | ||
} catch (e) { | ||
expect(e.response.status).to.be.equals(403) | ||
expect(e.response.data).to.be.deep.equal(JSON.parse(json)) | ||
expect(requestBody).not.to.be.called | ||
} | ||
}) | ||
}) | ||
}) |