@contrast/protect 1.38.0 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/error-handlers/common-handler.test.js +52 -0
- package/lib/error-handlers/index.test.js +32 -0
- package/lib/error-handlers/init-domain.test.js +34 -0
- package/lib/error-handlers/install/express4.test.js +238 -0
- package/lib/error-handlers/install/fastify.test.js +130 -0
- package/lib/error-handlers/install/hapi.test.js +102 -0
- package/lib/error-handlers/install/koa2.test.js +83 -0
- package/lib/error-handlers/install/restify.test.js +57 -0
- package/lib/get-source-context.test.js +35 -0
- package/lib/hardening/handlers.test.js +89 -0
- package/lib/hardening/index.test.js +31 -0
- package/lib/hardening/install/node-serialize0.test.js +61 -0
- package/lib/index.test.js +53 -0
- package/lib/input-analysis/handlers.test.js +1604 -0
- package/lib/input-analysis/index.test.js +45 -0
- package/lib/input-analysis/install/body-parser1.test.js +134 -0
- package/lib/input-analysis/install/busboy1.test.js +81 -0
- package/lib/input-analysis/install/cookie-parser1.test.js +144 -0
- package/lib/input-analysis/install/express4.test.js +208 -0
- package/lib/input-analysis/install/fastify.test.js +96 -0
- package/lib/input-analysis/install/formidable1.test.js +114 -0
- package/lib/input-analysis/install/hapi.test.js +300 -0
- package/lib/input-analysis/install/http.test.js +264 -0
- package/lib/input-analysis/install/koa-body5.test.js +92 -0
- package/lib/input-analysis/install/koa-bodyparser4.test.js +92 -0
- package/lib/input-analysis/install/koa2.test.js +259 -0
- package/lib/input-analysis/install/multer1.test.js +209 -0
- package/lib/input-analysis/install/qs6.test.js +79 -0
- package/lib/input-analysis/install/restify.test.js +98 -0
- package/lib/input-analysis/install/universal-cookie4.test.js +70 -0
- package/lib/input-analysis/ip-analysis.test.js +71 -0
- package/lib/input-analysis/virtual-patches.test.js +106 -0
- package/lib/input-tracing/handlers/index.test.js +1236 -0
- package/lib/input-tracing/index.test.js +62 -0
- package/lib/input-tracing/install/child-process.test.js +133 -0
- package/lib/input-tracing/install/eval.test.js +78 -0
- package/lib/input-tracing/install/fs.test.js +108 -0
- package/lib/input-tracing/install/function.test.js +81 -0
- package/lib/input-tracing/install/http.test.js +85 -0
- package/lib/input-tracing/install/http2.test.js +83 -0
- package/lib/input-tracing/install/marsdb.test.js +126 -0
- package/lib/input-tracing/install/mongodb.test.js +282 -0
- package/lib/input-tracing/install/mssql.test.js +81 -0
- package/lib/input-tracing/install/mysql.test.js +108 -0
- package/lib/input-tracing/install/postgres.test.js +125 -0
- package/lib/input-tracing/install/sequelize.test.js +78 -0
- package/lib/input-tracing/install/spdy.test.js +76 -0
- package/lib/input-tracing/install/sqlite3.test.js +88 -0
- package/lib/input-tracing/install/vm.test.js +176 -0
- package/lib/make-response-blocker.test.js +99 -0
- package/lib/make-source-context.test.js +219 -0
- package/lib/policy.test.js +446 -0
- package/lib/semantic-analysis/handlers.test.js +379 -0
- package/lib/semantic-analysis/index.test.js +38 -0
- package/lib/semantic-analysis/install/libxmljs.test.js +156 -0
- package/lib/semantic-analysis/utils/xml-analysis.test.js +156 -0
- package/lib/throw-security-exception.test.js +37 -0
- package/package.json +5 -5
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai');
|
|
4
|
+
const sinon = require('sinon');
|
|
5
|
+
const proxyquire = require('proxyquire');
|
|
6
|
+
|
|
7
|
+
const MODULES = [
|
|
8
|
+
'childProcess',
|
|
9
|
+
'eval',
|
|
10
|
+
'fs',
|
|
11
|
+
'function',
|
|
12
|
+
'http',
|
|
13
|
+
'http2',
|
|
14
|
+
'marsdb',
|
|
15
|
+
'mongodb',
|
|
16
|
+
'mssql',
|
|
17
|
+
'mysql',
|
|
18
|
+
'postgres',
|
|
19
|
+
'sequelize',
|
|
20
|
+
'spdy',
|
|
21
|
+
'sqlite3',
|
|
22
|
+
'vm',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
describe('protect input-tracing', function () {
|
|
26
|
+
let core, inputTracing;
|
|
27
|
+
|
|
28
|
+
beforeEach(function () {
|
|
29
|
+
core = {};
|
|
30
|
+
core.protect = {};
|
|
31
|
+
|
|
32
|
+
const hooksMock = (hookProp) => (core) => {
|
|
33
|
+
core.protect.inputTracing[hookProp] = { install: sinon.stub() };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
inputTracing = proxyquire('.', {
|
|
37
|
+
'./install/child-process': hooksMock('childProcess'),
|
|
38
|
+
'./install/eval': hooksMock('eval'),
|
|
39
|
+
'./install/fs': hooksMock('fs'),
|
|
40
|
+
'./install/function': hooksMock('function'),
|
|
41
|
+
'./install/http': hooksMock('http'),
|
|
42
|
+
'./install/http2': hooksMock('http2'),
|
|
43
|
+
'./install/marsdb': hooksMock('marsdb'),
|
|
44
|
+
'./install/mongodb': hooksMock('mongodb'),
|
|
45
|
+
'./install/mssql': hooksMock('mssql'),
|
|
46
|
+
'./install/mysql': hooksMock('mysql'),
|
|
47
|
+
'./install/postgres': hooksMock('postgres'),
|
|
48
|
+
'./install/sequelize': hooksMock('sequelize'),
|
|
49
|
+
'./install/spdy': hooksMock('spdy'),
|
|
50
|
+
'./install/sqlite3': hooksMock('sqlite3'),
|
|
51
|
+
'./install/vm': hooksMock('vm'),
|
|
52
|
+
})(core);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('calls the install() method of the required hooks', function () {
|
|
56
|
+
inputTracing.install();
|
|
57
|
+
|
|
58
|
+
MODULES.forEach((module) => {
|
|
59
|
+
expect(inputTracing[module].install).to.have.been.called;
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const sinon = require('sinon');
|
|
4
|
+
const { expect } = require('chai');
|
|
5
|
+
const patcher = require('@contrast/patcher');
|
|
6
|
+
const mocks = require('@contrast/test/mocks/');
|
|
7
|
+
|
|
8
|
+
describe('protect input-tracing child-process', function () {
|
|
9
|
+
const cpInstr = require('./child-process');
|
|
10
|
+
const store = { protect: {} };
|
|
11
|
+
let cp, origCp, core;
|
|
12
|
+
|
|
13
|
+
beforeEach(function () {
|
|
14
|
+
origCp = {
|
|
15
|
+
exec: sinon.stub(),
|
|
16
|
+
execSync: sinon.stub()
|
|
17
|
+
};
|
|
18
|
+
cp = {
|
|
19
|
+
exec: origCp.exec,
|
|
20
|
+
execSync: origCp.execSync
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
core = mocks.core();
|
|
24
|
+
core.logger = mocks.logger();
|
|
25
|
+
core.depHooks = mocks.depHooks();
|
|
26
|
+
core.depHooks.resolve.yields(cp);
|
|
27
|
+
core.scopes = mocks.scopes();
|
|
28
|
+
core.scopes.sources = {
|
|
29
|
+
getStore: sinon.stub().returns(store)
|
|
30
|
+
};
|
|
31
|
+
core.patcher = patcher(core);
|
|
32
|
+
core.protect = mocks.protect();
|
|
33
|
+
require('../../get-source-context')(core);
|
|
34
|
+
|
|
35
|
+
cpInstr(core);
|
|
36
|
+
|
|
37
|
+
core.protect.inputTracing.cpInstrumentation.install();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('handleCommandInjection() is called with expected values', function () {
|
|
41
|
+
const methodArgs = ['foo', 'bar'];
|
|
42
|
+
|
|
43
|
+
['exec', 'execSync'].forEach((method) => {
|
|
44
|
+
const name = `child_process.${method}`;
|
|
45
|
+
|
|
46
|
+
it(`${name}`, function () {
|
|
47
|
+
cp[method](...methodArgs);
|
|
48
|
+
|
|
49
|
+
const value = methodArgs[0];
|
|
50
|
+
|
|
51
|
+
expect(core.protect.inputTracing.handleCommandInjection).to.have.been.calledWith(
|
|
52
|
+
store.protect,
|
|
53
|
+
{ name, value, stacktraceOpts: { constructorOpt: cp[method], prependFrames: [sinon.match.func] } }
|
|
54
|
+
);
|
|
55
|
+
expect(core.protect.semanticAnalysis.handleCommandInjectionCommandBackdoors).to.have.been.calledWith(
|
|
56
|
+
store.protect,
|
|
57
|
+
{ name, value, stacktraceOpts: { constructorOpt: cp[method], prependFrames: [sinon.match.func] } }
|
|
58
|
+
);
|
|
59
|
+
expect(core.protect.semanticAnalysis.handleCmdInjectionSemanticChainedCommands).to.have.been.calledWith(
|
|
60
|
+
store.protect,
|
|
61
|
+
{ name, value, stacktraceOpts: { constructorOpt: cp[method], prependFrames: [sinon.match.func] } }
|
|
62
|
+
);
|
|
63
|
+
expect(core.protect.semanticAnalysis.handleCmdInjectionSemanticDangerous).to.have.been.calledWith(
|
|
64
|
+
store.protect,
|
|
65
|
+
{ name, value, stacktraceOpts: { constructorOpt: cp[method], prependFrames: [sinon.match.func] } }
|
|
66
|
+
);
|
|
67
|
+
expect(core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass).to.have.been.calledWith(
|
|
68
|
+
store.protect,
|
|
69
|
+
{ name, value, stacktraceOpts: { constructorOpt: cp[method], prependFrames: [sinon.match.func] } }
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('handleCommandInjection() is not called when method args are not relevant', function () {
|
|
76
|
+
const methodArgs = [1, undefined];
|
|
77
|
+
|
|
78
|
+
['exec', 'execSync'].forEach((method) => {
|
|
79
|
+
const name = `child_process.${method}`;
|
|
80
|
+
|
|
81
|
+
it(name, function () {
|
|
82
|
+
cp[method](...methodArgs);
|
|
83
|
+
|
|
84
|
+
expect(core.protect.inputTracing.handleCommandInjection).not.to.have.been.called;
|
|
85
|
+
expect(core.protect.semanticAnalysis.handleCommandInjectionCommandBackdoors).not.to.have.been.called;
|
|
86
|
+
expect(core.protect.semanticAnalysis.handleCmdInjectionSemanticChainedCommands).not.to.have.been.called;
|
|
87
|
+
expect(core.protect.semanticAnalysis.handleCmdInjectionSemanticDangerous).not.to.have.been.called;
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('handleCommandInjection() is not called when instrumentation is locked', function () {
|
|
93
|
+
const methodArgs = ['foo', 'bar'];
|
|
94
|
+
|
|
95
|
+
beforeEach(function () {
|
|
96
|
+
core.scopes.instrumentation.isLocked.returns(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
['exec', 'execSync'].forEach((method) => {
|
|
100
|
+
const name = `child_process.${method}`;
|
|
101
|
+
|
|
102
|
+
it(name, function () {
|
|
103
|
+
cp[method](...methodArgs);
|
|
104
|
+
|
|
105
|
+
expect(core.protect.inputTracing.handleCommandInjection).not.to.have.been.called;
|
|
106
|
+
expect(core.protect.semanticAnalysis.handleCommandInjectionCommandBackdoors).not.to.have.been.called;
|
|
107
|
+
expect(core.protect.semanticAnalysis.handleCmdInjectionSemanticChainedCommands).not.to.have.been.called;
|
|
108
|
+
expect(core.protect.semanticAnalysis.handleCmdInjectionSemanticDangerous).not.to.have.been.called;
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('handleCommandInjection() is not called when there is no Protect sourceContext', function () {
|
|
114
|
+
const methodArgs = ['foo', 'bar'];
|
|
115
|
+
|
|
116
|
+
beforeEach(function () {
|
|
117
|
+
core.scopes.sources.getStore.returns({ protect: null });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
['exec', 'execSync'].forEach((method) => {
|
|
121
|
+
const name = `child_process.${method}`;
|
|
122
|
+
|
|
123
|
+
it(name, function () {
|
|
124
|
+
cp[method](...methodArgs);
|
|
125
|
+
|
|
126
|
+
expect(core.protect.inputTracing.handleCommandInjection).not.to.have.been.called;
|
|
127
|
+
expect(core.protect.semanticAnalysis.handleCommandInjectionCommandBackdoors).not.to.have.been.called;
|
|
128
|
+
expect(core.protect.semanticAnalysis.handleCmdInjectionSemanticChainedCommands).not.to.have.been.called;
|
|
129
|
+
expect(core.protect.semanticAnalysis.handleCmdInjectionSemanticDangerous).not.to.have.been.called;
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const sinon = require('sinon');
|
|
4
|
+
const { expect } = require('chai');
|
|
5
|
+
const patcher = require('@contrast/patcher');
|
|
6
|
+
const mocks = require('@contrast/test/mocks');
|
|
7
|
+
|
|
8
|
+
describe('protect input-tracing eval', function () {
|
|
9
|
+
const evalInstr = require('./eval');
|
|
10
|
+
const store = { protect: {} };
|
|
11
|
+
let evalArg, core;
|
|
12
|
+
|
|
13
|
+
beforeEach(function () {
|
|
14
|
+
evalArg = 'function() { return "hi"; }';
|
|
15
|
+
|
|
16
|
+
if (!global.ContrastMethods) {
|
|
17
|
+
global.ContrastMethods = {};
|
|
18
|
+
}
|
|
19
|
+
global.ContrastMethods.eval = sinon.stub();
|
|
20
|
+
|
|
21
|
+
core = mocks.core();
|
|
22
|
+
core.logger = mocks.logger();
|
|
23
|
+
core.depHooks = mocks.depHooks();
|
|
24
|
+
core.depHooks.resolve.yields(global.ContrastMethods.eval);
|
|
25
|
+
core.scopes = mocks.scopes();
|
|
26
|
+
sinon.stub(core.scopes.sources, 'getStore').returns(store);
|
|
27
|
+
core.patcher = patcher(core);
|
|
28
|
+
core.protect = mocks.protect();
|
|
29
|
+
require('../../get-source-context')(core);
|
|
30
|
+
|
|
31
|
+
evalInstr(core);
|
|
32
|
+
|
|
33
|
+
core.protect.inputTracing.evalInstrumentation.install();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(function () {
|
|
37
|
+
Reflect.deleteProperty(global.ContrastMethods);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('ssjsInjection() is called with expected values', function () {
|
|
41
|
+
global.ContrastMethods.eval(evalArg);
|
|
42
|
+
|
|
43
|
+
expect(core.protect.inputTracing.ssjsInjection).to.have.been.calledWith(
|
|
44
|
+
store.protect,
|
|
45
|
+
{ name: 'eval', value: evalArg, stacktraceOpts: { constructorOpt: sinon.match.func, prependFrames: [sinon.match.func] } }
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('ssjsInjection() is not called when method args are not relevant', function () {
|
|
50
|
+
global.ContrastMethods.eval(1, evalArg);
|
|
51
|
+
|
|
52
|
+
expect(core.protect.inputTracing.ssjsInjection).not.to.have.been.called;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('ssjsInjection() is not called when instrumentation is locked', function () {
|
|
56
|
+
core.scopes.instrumentation.isLocked.returns(true);
|
|
57
|
+
|
|
58
|
+
global.ContrastMethods.eval(evalArg);
|
|
59
|
+
|
|
60
|
+
expect(core.protect.inputTracing.ssjsInjection).not.to.have.been.called;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('ssjsInjection() is not called when there is no Protect sourceContext', function () {
|
|
64
|
+
core.scopes.sources.getStore.returns({ protect: null });
|
|
65
|
+
|
|
66
|
+
global.ContrastMethods.eval(evalArg);
|
|
67
|
+
|
|
68
|
+
expect(core.protect.inputTracing.ssjsInjection).not.to.have.been.called;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('logs an error when there is no `global.ContrastMethods.eval` method', function () {
|
|
72
|
+
global.ContrastMethods.eval = null;
|
|
73
|
+
|
|
74
|
+
core.protect.inputTracing.evalInstrumentation.install();
|
|
75
|
+
|
|
76
|
+
expect(core.logger.error).to.have.been.calledOnceWith('Cannot install `eval` instrumentation - Contrast method DNE');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const sinon = require('sinon');
|
|
4
|
+
const { expect } = require('chai');
|
|
5
|
+
const { FS_METHODS } = require('@contrast/common');
|
|
6
|
+
const patcher = require('@contrast/patcher');
|
|
7
|
+
const mocks = require('@contrast/test/mocks');
|
|
8
|
+
|
|
9
|
+
describe('protect input-tracing fs', function () {
|
|
10
|
+
const store = { protect: {} };
|
|
11
|
+
let fs, fsPromises, core;
|
|
12
|
+
|
|
13
|
+
beforeEach(function () {
|
|
14
|
+
fs = {
|
|
15
|
+
promises: {}
|
|
16
|
+
};
|
|
17
|
+
fsPromises = {};
|
|
18
|
+
|
|
19
|
+
for (const { name, promises, sync } of FS_METHODS) {
|
|
20
|
+
fs[name] = sinon.stub();
|
|
21
|
+
if (sync) {
|
|
22
|
+
fs[`${name}Sync`] = sinon.stub();
|
|
23
|
+
}
|
|
24
|
+
if (promises) {
|
|
25
|
+
fsPromises[name] = sinon.stub();
|
|
26
|
+
fs.promises[name] = sinon.stub();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
core = mocks.core();
|
|
31
|
+
core.logger = mocks.logger();
|
|
32
|
+
core.depHooks = mocks.depHooks();
|
|
33
|
+
core.depHooks.resolve.withArgs({ name: 'fs' }).yields(fs);
|
|
34
|
+
core.depHooks.resolve.withArgs({ name: 'fs/promises' }).yields(fsPromises);
|
|
35
|
+
core.scopes = mocks.scopes();
|
|
36
|
+
sinon.stub(core.scopes.sources, 'getStore').returns(store);
|
|
37
|
+
core.patcher = patcher(core);
|
|
38
|
+
core.protect = mocks.protect();
|
|
39
|
+
require('../../get-source-context')(core);
|
|
40
|
+
|
|
41
|
+
require('./fs')(core).install();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
for (const { name, promises, sync, indices } of FS_METHODS) {
|
|
45
|
+
const methodsUnderTest = [{ name: `fs.${name}`, getFn: () => fs[name] }];
|
|
46
|
+
if (sync) {
|
|
47
|
+
methodsUnderTest.push({ name: `fs.${name}Sync`, getFn: () => fs[`${name}Sync`] });
|
|
48
|
+
}
|
|
49
|
+
if (promises) {
|
|
50
|
+
methodsUnderTest.push({ name: `fsPromises.${name}`, getFn: () => fsPromises[name] });
|
|
51
|
+
methodsUnderTest.push({ name: `fs.promises.${name}`, getFn: () => fs.promises[name] });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const { name, getFn } of methodsUnderTest) {
|
|
55
|
+
describe(name, function () {
|
|
56
|
+
it('handlePathTraversal() is called with expected values', function () {
|
|
57
|
+
const method = getFn();
|
|
58
|
+
const methodArgs = ['foo', 'bar'];
|
|
59
|
+
method(...methodArgs);
|
|
60
|
+
|
|
61
|
+
for (const idx of indices) {
|
|
62
|
+
const value = methodArgs[idx];
|
|
63
|
+
|
|
64
|
+
expect(core.protect.inputTracing.handlePathTraversal).to.have.been.calledWith(
|
|
65
|
+
store.protect,
|
|
66
|
+
{
|
|
67
|
+
name,
|
|
68
|
+
value,
|
|
69
|
+
stacktraceOpts: {
|
|
70
|
+
constructorOpt: method,
|
|
71
|
+
prependFrames: [sinon.match.func]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('handlePathTraversal() is not called when method args are not relevant', function () {
|
|
79
|
+
const method = getFn();
|
|
80
|
+
const methodArgs = [1, undefined];
|
|
81
|
+
method(...methodArgs);
|
|
82
|
+
|
|
83
|
+
expect(core.protect.inputTracing.handlePathTraversal).not.to.have.been.called;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
it('handlePathTraversal() is not called when instrumentation is locked', function () {
|
|
88
|
+
const method = getFn();
|
|
89
|
+
const methodArgs = ['foo', 'bar'];
|
|
90
|
+
core.scopes.instrumentation.isLocked.returns(true);
|
|
91
|
+
method(...methodArgs);
|
|
92
|
+
|
|
93
|
+
expect(core.protect.inputTracing.handlePathTraversal).not.to.have.been.called;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
it('handlePathTraversal() is not called when there is no Protect sourceContext', function () {
|
|
98
|
+
const method = getFn();
|
|
99
|
+
const methodArgs = ['foo', 'bar'];
|
|
100
|
+
core.scopes.sources.getStore.returns({ protect: null });
|
|
101
|
+
method(...methodArgs);
|
|
102
|
+
|
|
103
|
+
expect(core.protect.inputTracing.handlePathTraversal).not.to.have.been.called;
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const sinon = require('sinon');
|
|
4
|
+
const { expect } = require('chai');
|
|
5
|
+
const patcher = require('@contrast/patcher');
|
|
6
|
+
const mocks = require('@contrast/test/mocks');
|
|
7
|
+
|
|
8
|
+
describe('protect input-tracing function', function () {
|
|
9
|
+
const functionInstr = require('./function');
|
|
10
|
+
const store = { protect: {} };
|
|
11
|
+
let constructorArg, core;
|
|
12
|
+
|
|
13
|
+
beforeEach(function () {
|
|
14
|
+
constructorArg = 'return "hi";';
|
|
15
|
+
|
|
16
|
+
if (!global.ContrastMethods) {
|
|
17
|
+
global.ContrastMethods = {};
|
|
18
|
+
}
|
|
19
|
+
global.ContrastMethods.Function = sinon.stub();
|
|
20
|
+
|
|
21
|
+
core = mocks.core();
|
|
22
|
+
core.logger = mocks.logger();
|
|
23
|
+
core.depHooks = mocks.depHooks();
|
|
24
|
+
core.depHooks.resolve.yields(global.ContrastMethods.Function);
|
|
25
|
+
core.scopes = mocks.scopes();
|
|
26
|
+
sinon.stub(core.scopes.sources, 'getStore').returns(store);
|
|
27
|
+
core.patcher = patcher(core);
|
|
28
|
+
core.protect = mocks.protect();
|
|
29
|
+
require('../../get-source-context')(core);
|
|
30
|
+
|
|
31
|
+
functionInstr(core);
|
|
32
|
+
|
|
33
|
+
core.protect.inputTracing.functionInstrumentation.install();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(function () {
|
|
37
|
+
Reflect.deleteProperty(global.ContrastMethods);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('ssjsInjection() is called with expected values', function () {
|
|
41
|
+
global.ContrastMethods.Function(constructorArg);
|
|
42
|
+
|
|
43
|
+
expect(core.protect.inputTracing.ssjsInjection).to.have.been.calledWith(
|
|
44
|
+
store.protect,
|
|
45
|
+
{ name: 'Function', value: constructorArg, stacktraceOpts: { constructorOpt: sinon.match.func, prependFrames: [sinon.match.func] } }
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('ssjsInjection() is called only with the value for the function body', function () {
|
|
50
|
+
global.ContrastMethods.Function('a', 'return a');
|
|
51
|
+
|
|
52
|
+
expect(core.protect.inputTracing.ssjsInjection).to.have.been.calledOnceWithExactly(
|
|
53
|
+
store.protect,
|
|
54
|
+
{ name: 'Function', value: 'return a', stacktraceOpts: { constructorOpt: sinon.match.func, prependFrames: [sinon.match.func] } }
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('ssjsInjection() is not called when instrumentation is locked', function () {
|
|
59
|
+
core.scopes.instrumentation.isLocked.returns(true);
|
|
60
|
+
|
|
61
|
+
global.ContrastMethods.Function(constructorArg);
|
|
62
|
+
|
|
63
|
+
expect(core.protect.inputTracing.ssjsInjection).not.to.have.been.called;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('ssjsInjection() is not called when there is no Protect sourceContext', function () {
|
|
67
|
+
core.scopes.sources.getStore.returns({ protect: null });
|
|
68
|
+
|
|
69
|
+
global.ContrastMethods.Function(constructorArg);
|
|
70
|
+
|
|
71
|
+
expect(core.protect.inputTracing.ssjsInjection).not.to.have.been.called;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('logs an error when there is no `global.ContrastMethods.eval` method', function () {
|
|
75
|
+
global.ContrastMethods.Function = null;
|
|
76
|
+
|
|
77
|
+
core.protect.inputTracing.functionInstrumentation.install();
|
|
78
|
+
|
|
79
|
+
expect(core.logger.error).to.have.been.calledOnceWith('Cannot install `Function` instrumentation - Contrast method DNE');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const sinon = require('sinon');
|
|
4
|
+
const { expect } = require('chai');
|
|
5
|
+
const scopes = require('@contrast/scopes');
|
|
6
|
+
const patcher = require('@contrast/patcher');
|
|
7
|
+
const mocks = require('@contrast/test/mocks');
|
|
8
|
+
|
|
9
|
+
describe('protect input-tracing http', function () {
|
|
10
|
+
let core, inputTracing;
|
|
11
|
+
const store = { protect: {} };
|
|
12
|
+
|
|
13
|
+
beforeEach(function () {
|
|
14
|
+
core = mocks.core();
|
|
15
|
+
core.logger = mocks.logger();
|
|
16
|
+
core.patcher = patcher(core);
|
|
17
|
+
core.scopes = scopes(core);
|
|
18
|
+
sinon.stub(core.scopes.sources, 'getStore').returns(store);
|
|
19
|
+
core.depHooks = mocks.depHooks();
|
|
20
|
+
core.protect = mocks.protect();
|
|
21
|
+
require('../../get-source-context')(core);
|
|
22
|
+
({ protect: { inputTracing } } = core);
|
|
23
|
+
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('http', function () {
|
|
27
|
+
let http;
|
|
28
|
+
|
|
29
|
+
beforeEach(function () {
|
|
30
|
+
http = function () { };
|
|
31
|
+
http.ServerResponse = function () { };
|
|
32
|
+
http.ServerResponse.prototype.end = sinon.stub();
|
|
33
|
+
http.ServerResponse.prototype.write = sinon.stub();
|
|
34
|
+
core.depHooks.resolve.yields(http);
|
|
35
|
+
require('./http')(core).install();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
['write', 'end'].forEach((method) => {
|
|
39
|
+
describe(`instruments ServerResponse.${method}()`, function () {
|
|
40
|
+
it('handleReflectedXss() is called with valid expected values', function () {
|
|
41
|
+
const serverResponse = new http.ServerResponse();
|
|
42
|
+
const value = '" onmouseover="alert(3)"';
|
|
43
|
+
serverResponse[method](value);
|
|
44
|
+
|
|
45
|
+
expect(inputTracing.handleReflectedXss).to.have.been.calledOnce;
|
|
46
|
+
expect(inputTracing.handleReflectedXss).to.have.been.calledWith(
|
|
47
|
+
{},
|
|
48
|
+
{
|
|
49
|
+
name: `http.ServerResponse.prototype.${method}`,
|
|
50
|
+
value,
|
|
51
|
+
stacktraceOpts: {
|
|
52
|
+
constructorOpt: serverResponse[method]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('handleReflectedXss() is not called if instrumentation is locked', function () {
|
|
59
|
+
sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
|
|
60
|
+
const serverResponse = new http.ServerResponse();
|
|
61
|
+
const value = '" onmouseover="alert(3)"';
|
|
62
|
+
serverResponse[method](value);
|
|
63
|
+
|
|
64
|
+
expect(inputTracing.handleReflectedXss).not.to.have.been.called;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('handleReflectedXss() is not called if there is no sourceContext', function () {
|
|
68
|
+
core.scopes.sources.getStore.returns({ protect: undefined });
|
|
69
|
+
const serverResponse = new http.ServerResponse();
|
|
70
|
+
const value = '" onmouseover="alert(3)"';
|
|
71
|
+
serverResponse[method](value);
|
|
72
|
+
|
|
73
|
+
expect(inputTracing.handleReflectedXss).not.to.have.been.called;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('handleReflectedXss() is not called if there is no value', function () {
|
|
77
|
+
const serverResponse = new http.ServerResponse();
|
|
78
|
+
serverResponse[method]();
|
|
79
|
+
|
|
80
|
+
expect(inputTracing.handleReflectedXss).not.to.have.been.called;
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const sinon = require('sinon');
|
|
4
|
+
const { expect } = require('chai');
|
|
5
|
+
const scopes = require('@contrast/scopes');
|
|
6
|
+
const patcher = require('@contrast/patcher');
|
|
7
|
+
const mocks = require('@contrast/test/mocks');
|
|
8
|
+
|
|
9
|
+
describe('protect input-tracing http2', function () {
|
|
10
|
+
let core, inputTracing;
|
|
11
|
+
const store = { protect: {} };
|
|
12
|
+
|
|
13
|
+
beforeEach(function () {
|
|
14
|
+
core = mocks.core();
|
|
15
|
+
core.logger = mocks.logger();
|
|
16
|
+
core.patcher = patcher(core);
|
|
17
|
+
core.scopes = scopes(core);
|
|
18
|
+
sinon.stub(core.scopes.sources, 'getStore').returns(store);
|
|
19
|
+
core.depHooks = mocks.depHooks();
|
|
20
|
+
core.protect = mocks.protect();
|
|
21
|
+
require('../../get-source-context')(core);
|
|
22
|
+
({ protect: { inputTracing } } = core);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('http2', function () {
|
|
26
|
+
let http2;
|
|
27
|
+
|
|
28
|
+
beforeEach(function () {
|
|
29
|
+
http2 = function () { };
|
|
30
|
+
http2.Http2ServerResponse = function () { };
|
|
31
|
+
http2.Http2ServerResponse.prototype.end = sinon.stub();
|
|
32
|
+
http2.Http2ServerResponse.prototype.write = sinon.stub();
|
|
33
|
+
core.depHooks.resolve.yields(http2);
|
|
34
|
+
require('./http2')(core).install();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
['write', 'end'].forEach((method) => {
|
|
38
|
+
it('handleReflectedXss() is not called if there is no value', function () {
|
|
39
|
+
const serverResponse = new http2.Http2ServerResponse();
|
|
40
|
+
serverResponse[method]();
|
|
41
|
+
|
|
42
|
+
expect(inputTracing.handleReflectedXss).not.to.have.been.called;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('handleReflectedXss() is not called if there is no sourceContext', function () {
|
|
46
|
+
core.scopes.sources.getStore.returns({ protect: undefined });
|
|
47
|
+
const serverResponse = new http2.Http2ServerResponse();
|
|
48
|
+
const value = "<script>alert('xss');</script>";
|
|
49
|
+
serverResponse[method](value);
|
|
50
|
+
|
|
51
|
+
expect(inputTracing.handleReflectedXss).not.to.have.been.called;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('handleReflectedXss() is not called if instrumentation is locked', function () {
|
|
55
|
+
sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
|
|
56
|
+
const serverResponse = new http2.Http2ServerResponse();
|
|
57
|
+
const value = "<script>alert('xss');</script>";
|
|
58
|
+
serverResponse[method](value);
|
|
59
|
+
|
|
60
|
+
expect(inputTracing.handleReflectedXss).not.to.have.been.called;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe(`instruments Http2ServerResponse.${method}()`, function () {
|
|
64
|
+
it('handleReflectedXss() is called with valid expected values', function () {
|
|
65
|
+
const serverResponse = new http2.Http2ServerResponse();
|
|
66
|
+
const value = "<script>alert('xss');</script>";
|
|
67
|
+
serverResponse[method](value);
|
|
68
|
+
|
|
69
|
+
expect(inputTracing.handleReflectedXss).to.have.been.calledOnceWith(
|
|
70
|
+
{},
|
|
71
|
+
{
|
|
72
|
+
name: `http2.Http2ServerResponse.prototype.${method}`,
|
|
73
|
+
value,
|
|
74
|
+
stacktraceOpts: {
|
|
75
|
+
constructorOpt: serverResponse[method]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|