@contrast/protect 1.53.1 → 1.54.1
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/package.json +15 -12
- package/lib/error-handlers/common-handler.test.js +0 -52
- package/lib/error-handlers/index.test.js +0 -32
- package/lib/error-handlers/init-domain.test.js +0 -22
- package/lib/error-handlers/install/express.test.js +0 -290
- package/lib/error-handlers/install/fastify.test.js +0 -130
- package/lib/error-handlers/install/hapi.test.js +0 -102
- package/lib/error-handlers/install/koa2.test.js +0 -83
- package/lib/error-handlers/install/restify.test.js +0 -57
- package/lib/get-source-context.test.js +0 -35
- package/lib/hardening/handlers.test.js +0 -89
- package/lib/hardening/index.test.js +0 -31
- package/lib/hardening/install/node-serialize0.test.js +0 -58
- package/lib/index.test.js +0 -53
- package/lib/input-analysis/handlers.test.js +0 -1604
- package/lib/input-analysis/index.test.js +0 -45
- package/lib/input-analysis/install/body-parser1.test.js +0 -134
- package/lib/input-analysis/install/busboy1.test.js +0 -81
- package/lib/input-analysis/install/cookie-parser1.test.js +0 -144
- package/lib/input-analysis/install/express.test.js +0 -241
- package/lib/input-analysis/install/fastify.test.js +0 -96
- package/lib/input-analysis/install/formidable1.test.js +0 -114
- package/lib/input-analysis/install/hapi.test.js +0 -292
- package/lib/input-analysis/install/http.test.js +0 -270
- package/lib/input-analysis/install/koa-body5.test.js +0 -92
- package/lib/input-analysis/install/koa-bodyparser4.test.js +0 -92
- package/lib/input-analysis/install/koa2.test.js +0 -259
- package/lib/input-analysis/install/multer1.test.js +0 -209
- package/lib/input-analysis/install/qs6.test.js +0 -79
- package/lib/input-analysis/install/restify.test.js +0 -98
- package/lib/input-analysis/install/universal-cookie4.test.js +0 -70
- package/lib/input-analysis/ip-analysis.test.js +0 -71
- package/lib/input-analysis/virtual-patches.test.js +0 -106
- package/lib/input-tracing/handlers/index.test.js +0 -1236
- package/lib/input-tracing/index.test.js +0 -62
- package/lib/input-tracing/install/child-process.test.js +0 -133
- package/lib/input-tracing/install/eval.test.js +0 -78
- package/lib/input-tracing/install/fs.test.js +0 -108
- package/lib/input-tracing/install/function.test.js +0 -81
- package/lib/input-tracing/install/http.test.js +0 -85
- package/lib/input-tracing/install/http2.test.js +0 -83
- package/lib/input-tracing/install/marsdb.test.js +0 -126
- package/lib/input-tracing/install/mongodb.test.js +0 -280
- package/lib/input-tracing/install/mssql.test.js +0 -81
- package/lib/input-tracing/install/mysql.test.js +0 -108
- package/lib/input-tracing/install/postgres.test.js +0 -117
- package/lib/input-tracing/install/sequelize.test.js +0 -78
- package/lib/input-tracing/install/spdy.test.js +0 -76
- package/lib/input-tracing/install/sqlite3.test.js +0 -88
- package/lib/input-tracing/install/vm.test.js +0 -176
- package/lib/make-response-blocker.test.js +0 -99
- package/lib/make-source-context.test.js +0 -219
- package/lib/policy.test.js +0 -446
- package/lib/semantic-analysis/handlers.test.js +0 -379
- package/lib/semantic-analysis/index.test.js +0 -38
- package/lib/semantic-analysis/install/libxmljs.test.js +0 -156
- package/lib/semantic-analysis/utils/xml-analysis.test.js +0 -156
- package/lib/throw-security-exception.test.js +0 -37
|
@@ -1,126 +0,0 @@
|
|
|
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
|
-
const { patchType } = require('../constants');
|
|
9
|
-
const methods = ['find', 'findOne', 'update', 'insert', 'remove', 'ids', 'count'];
|
|
10
|
-
|
|
11
|
-
describe('protect input-tracing marsdb', function () {
|
|
12
|
-
const sourceContext = {};
|
|
13
|
-
const store = { protect: sourceContext };
|
|
14
|
-
|
|
15
|
-
let core, inputTracing, mockMarsDb, marsdbInstr;
|
|
16
|
-
|
|
17
|
-
beforeEach(function () {
|
|
18
|
-
core = mocks.core();
|
|
19
|
-
core.logger = mocks.logger();
|
|
20
|
-
core.patcher = patcher(core);
|
|
21
|
-
core.scopes = scopes(core);
|
|
22
|
-
sinon.stub(core.scopes.sources, 'getStore').returns(store);
|
|
23
|
-
core.depHooks = mocks.depHooks();
|
|
24
|
-
core.protect = mocks.protect();
|
|
25
|
-
require('../../get-source-context')(core);
|
|
26
|
-
({ protect: { inputTracing } } = core);
|
|
27
|
-
|
|
28
|
-
sinon.spy(core.patcher, 'patch');
|
|
29
|
-
|
|
30
|
-
mockMarsDb = {
|
|
31
|
-
Collection: function Collection() { },
|
|
32
|
-
};
|
|
33
|
-
methods.forEach((method) => {
|
|
34
|
-
mockMarsDb.Collection.prototype[method] = () => { };
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
core.depHooks.resolve.yields(mockMarsDb);
|
|
38
|
-
|
|
39
|
-
marsdbInstr = require('./marsdb')(core);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe('patcher patches', function () {
|
|
43
|
-
it('patches all relevant objects in marsdb', function () {
|
|
44
|
-
marsdbInstr.install();
|
|
45
|
-
expect(core.patcher.patch).to.have.callCount(methods.length);
|
|
46
|
-
|
|
47
|
-
methods.forEach((method) => {
|
|
48
|
-
expect(core.patcher.patch).to.have.been.calledWithMatch(
|
|
49
|
-
mockMarsDb.Collection.prototype, method, {
|
|
50
|
-
name: `marsdb.Collection.prototype.${method}`,
|
|
51
|
-
patchType,
|
|
52
|
-
pre: sinon.match.func
|
|
53
|
-
}
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe('patcher pre-hooks', function () {
|
|
60
|
-
let hooksFns;
|
|
61
|
-
const correctArg = { id: 'some-query' };
|
|
62
|
-
const incorrectArgs = [null, '', undefined, {}];
|
|
63
|
-
const captureStackTraceArgs = (method) => ([
|
|
64
|
-
{
|
|
65
|
-
name: `marsdb.Collection.prototype.${method}`,
|
|
66
|
-
value: correctArg
|
|
67
|
-
}, { constructorOpt: sinon.match.func, prependFrames: [sinon.match.func] }
|
|
68
|
-
]);
|
|
69
|
-
|
|
70
|
-
const inputTracingNosqlInjectionArgs = (method) => ([sourceContext,
|
|
71
|
-
{
|
|
72
|
-
name: `marsdb.Collection.prototype.${method}`,
|
|
73
|
-
value: correctArg
|
|
74
|
-
}]);
|
|
75
|
-
|
|
76
|
-
beforeEach(function () {
|
|
77
|
-
marsdbInstr.install();
|
|
78
|
-
hooksFns = methods.reduce((acc, method) => {
|
|
79
|
-
acc[method] = new mockMarsDb.Collection()[method];
|
|
80
|
-
|
|
81
|
-
return acc;
|
|
82
|
-
}, {});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
methods.forEach((method) => {
|
|
86
|
-
it(`marsdb.Collection.prototype.${method} calls \`inputTracing.nosqlInjectionMongo\` when called with expected arguments`, function () {
|
|
87
|
-
const fn = hooksFns[method];
|
|
88
|
-
|
|
89
|
-
fn(correctArg);
|
|
90
|
-
|
|
91
|
-
const stackTraceArgs = captureStackTraceArgs(method);
|
|
92
|
-
const inputTracingArgs = inputTracingNosqlInjectionArgs(method);
|
|
93
|
-
expect(inputTracing.nosqlInjectionMongo).to.have.been.calledWith(inputTracingArgs[0], {
|
|
94
|
-
...inputTracingArgs[1],
|
|
95
|
-
stacktraceData: {
|
|
96
|
-
constructorOpt: stackTraceArgs[1].constructorOpt, prependFrames: stackTraceArgs[1].prependFrames
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it(`marsdb.Collection.prototype.${method} does not call \`inputTracing.nosqlInjectionMongo\` and \`core.captureStacktrace\` when called with wrong/no arguments`, function () {
|
|
102
|
-
const fn = hooksFns[method];
|
|
103
|
-
|
|
104
|
-
for (let i = 0; i < incorrectArgs.length; i++) {
|
|
105
|
-
fn(incorrectArgs[i]);
|
|
106
|
-
|
|
107
|
-
expect(core.captureStacktrace).to.not.have.been.called;
|
|
108
|
-
expect(inputTracing.nosqlInjectionMongo).to.not.have.been.called;
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it(`marsdb.Collection.prototype.${method} does not call \`inputTracing.nosqlInjectionMongo\` and \`core.captureStacktrace\` when there is no sourceContext`, function () {
|
|
113
|
-
const fn = hooksFns[method];
|
|
114
|
-
|
|
115
|
-
core.scopes.sources.getStore.returns(null);
|
|
116
|
-
|
|
117
|
-
fn(correctArg);
|
|
118
|
-
|
|
119
|
-
expect(core.captureStacktrace).to.not.have.been.called;
|
|
120
|
-
expect(inputTracing.nosqlInjectionMongo).to.not.have.been.called;
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const sinon = require('sinon');
|
|
4
|
-
const { expect } = require('chai');
|
|
5
|
-
const { initProtectFixture } = require('@contrast/test/fixtures');
|
|
6
|
-
|
|
7
|
-
describe('protect input-tracing mongodb', function() {
|
|
8
|
-
let core, instr, simulateRequestScope, nosqlInjectionMongo;
|
|
9
|
-
|
|
10
|
-
class Db {
|
|
11
|
-
command() { }
|
|
12
|
-
eval() { }
|
|
13
|
-
aggregate() { }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
class Collection {
|
|
17
|
-
find() { }
|
|
18
|
-
findOne() { }
|
|
19
|
-
findAndModify() { }
|
|
20
|
-
findOneAndDelete() { }
|
|
21
|
-
findOneAndReplace() { }
|
|
22
|
-
findOneAndUpdate() { }
|
|
23
|
-
remove() { }
|
|
24
|
-
removeOne() { }
|
|
25
|
-
replaceOne() { }
|
|
26
|
-
removeMany() { }
|
|
27
|
-
rename() { }
|
|
28
|
-
update() { }
|
|
29
|
-
updateOne() { }
|
|
30
|
-
updateMany() { }
|
|
31
|
-
deleteOne() { }
|
|
32
|
-
deleteMany() { }
|
|
33
|
-
aggregate() { }
|
|
34
|
-
mapReduce() { }
|
|
35
|
-
group() { }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
before(function() {
|
|
39
|
-
({ core, simulateRequestScope } = initProtectFixture());
|
|
40
|
-
require('../../get-source-context')(core);
|
|
41
|
-
|
|
42
|
-
core.depHooks.resolve.yields({ Collection, Db });
|
|
43
|
-
|
|
44
|
-
instr = require('./mongodb')(core);
|
|
45
|
-
instr.install();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
beforeEach(function() {
|
|
49
|
-
sinon.stub(core.protect.inputTracing, 'nosqlInjectionMongo');
|
|
50
|
-
({ nosqlInjectionMongo } = core.protect.inputTracing);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
[
|
|
54
|
-
{ subject: Collection, method: 'find' },
|
|
55
|
-
{ subject: Collection, method: 'findOne' },
|
|
56
|
-
{ subject: Collection, method: 'findAndModify' },
|
|
57
|
-
{ subject: Collection, method: 'findOneAndDelete' },
|
|
58
|
-
{ subject: Collection, method: 'findOneAndReplace' },
|
|
59
|
-
{ subject: Collection, method: 'findOneAndUpdate' },
|
|
60
|
-
{ subject: Collection, method: 'remove' },
|
|
61
|
-
{ subject: Collection, method: 'replaceOne' },
|
|
62
|
-
{ subject: Collection, method: 'update' },
|
|
63
|
-
{ subject: Collection, method: 'updateOne' },
|
|
64
|
-
{ subject: Collection, method: 'updateMany' },
|
|
65
|
-
{ subject: Collection, method: 'deleteOne' },
|
|
66
|
-
{ subject: Collection, method: 'deleteMany' },
|
|
67
|
-
{ subject: Db, method: 'command' },
|
|
68
|
-
].forEach(({ subject, method }) => {
|
|
69
|
-
describe(`${subject.name}.prototype.${method}()`, function() {
|
|
70
|
-
it('skips instrumentation if assess store is missing', function() {
|
|
71
|
-
simulateRequestScope(() => {
|
|
72
|
-
const expObject = { $ne: 'test' };
|
|
73
|
-
|
|
74
|
-
(new subject())[method]({ expObject });
|
|
75
|
-
|
|
76
|
-
expect(nosqlInjectionMongo).not.to.have.been.called;
|
|
77
|
-
}, {});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('skips instrumentation if the argument is not a string or an non-empty object', function() {
|
|
81
|
-
simulateRequestScope(function() {
|
|
82
|
-
(new subject())[method]({});
|
|
83
|
-
expect(nosqlInjectionMongo).not.to.have.been.called;
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('analyzes the input for a nosql-injection with string input', function() {
|
|
88
|
-
simulateRequestScope(function() {
|
|
89
|
-
const attackString = 'sleep(1)';
|
|
90
|
-
const sc = core.scopes.sources.getStore()?.protect;
|
|
91
|
-
|
|
92
|
-
(new subject())[method](attackString);
|
|
93
|
-
|
|
94
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: attackString, name: `mongodb.${subject.name}.prototype.${method}` });
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('analyzes the input for a nosql-injection with an object input', function() {
|
|
99
|
-
simulateRequestScope(function() {
|
|
100
|
-
const expObject = { $ne: 'test' };
|
|
101
|
-
const sc = core.scopes.sources.getStore()?.protect;
|
|
102
|
-
|
|
103
|
-
(new subject())[method]({ expObject });
|
|
104
|
-
|
|
105
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: { expObject }, name: `mongodb.${subject.name}.prototype.${method}` });
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('Db.prototype.aggregate and Collection.prototype.aggregate', function() {
|
|
112
|
-
it('analyzes for a nosql-injection from user input in the $function operator', function() {
|
|
113
|
-
simulateRequestScope(function() {
|
|
114
|
-
const sc = core.scopes.sources.getStore()?.protect;
|
|
115
|
-
const attackString = 'sleep(1)';
|
|
116
|
-
(new Collection()).aggregate([
|
|
117
|
-
{
|
|
118
|
-
$function: {
|
|
119
|
-
body: attackString
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
]);
|
|
123
|
-
(new Db()).aggregate([
|
|
124
|
-
{
|
|
125
|
-
$function: {
|
|
126
|
-
body: attackString
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
]);
|
|
130
|
-
|
|
131
|
-
expect(nosqlInjectionMongo).to.have.been.calledTwice;
|
|
132
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: attackString, name: 'mongodb.Db.prototype.aggregate' });
|
|
133
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: attackString, name: 'mongodb.Collection.prototype.aggregate' });
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
[
|
|
138
|
-
'init',
|
|
139
|
-
'merge',
|
|
140
|
-
'accumulate',
|
|
141
|
-
'finalize'
|
|
142
|
-
].forEach((prop) => {
|
|
143
|
-
it(`analyzes for a nosql-injection from user input in the $accumulator operator and ${prop} property`, function() {
|
|
144
|
-
simulateRequestScope(function() {
|
|
145
|
-
const attackString = 'sleep(1)';
|
|
146
|
-
const sc = core.scopes.sources.getStore()?.protect;
|
|
147
|
-
(new Collection()).aggregate([
|
|
148
|
-
{
|
|
149
|
-
$accumulator: {
|
|
150
|
-
[prop]: attackString
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
]);
|
|
154
|
-
|
|
155
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: attackString, name: 'mongodb.Collection.prototype.aggregate' });
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('Collection.prototype.mapReduce', function() {
|
|
162
|
-
it('analyzes for a nosql-injection from user input in the first 2 arguments of `mapReduce` method', function() {
|
|
163
|
-
const options = { options: 'something' };
|
|
164
|
-
simulateRequestScope(function() {
|
|
165
|
-
const attackString1 = 'sleep(1)';
|
|
166
|
-
const attackString2 = 'sleep(2)';
|
|
167
|
-
const sc = core.scopes.sources.getStore()?.protect;
|
|
168
|
-
(new Collection()).mapReduce(
|
|
169
|
-
attackString1,
|
|
170
|
-
'devData#1',
|
|
171
|
-
options
|
|
172
|
-
);
|
|
173
|
-
(new Collection()).mapReduce(
|
|
174
|
-
'devData#2',
|
|
175
|
-
attackString2,
|
|
176
|
-
options
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
expect(nosqlInjectionMongo).to.have.a.callCount(4);
|
|
180
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: attackString1, name: 'mongodb.Collection.prototype.mapReduce' });
|
|
181
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: 'devData#1', name: 'mongodb.Collection.prototype.mapReduce' });
|
|
182
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: attackString2, name: 'mongodb.Collection.prototype.mapReduce' });
|
|
183
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: 'devData#2', name: 'mongodb.Collection.prototype.mapReduce' });
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('analyzes for a nosql-injection from user input in the third argument of `mapReduce` method', function() {
|
|
188
|
-
simulateRequestScope(function() {
|
|
189
|
-
const attackString = 'sleep(1)';
|
|
190
|
-
const expObj = { $ne: 'test' };
|
|
191
|
-
const options1 = { query: { expObj } };
|
|
192
|
-
const options2 = { finalize: attackString };
|
|
193
|
-
const sc = core.scopes.sources.getStore()?.protect;
|
|
194
|
-
(new Collection()).mapReduce(
|
|
195
|
-
'devData#1',
|
|
196
|
-
'devData#2',
|
|
197
|
-
options1
|
|
198
|
-
);
|
|
199
|
-
(new Collection()).mapReduce(
|
|
200
|
-
'devData#3',
|
|
201
|
-
'devData#4',
|
|
202
|
-
options2
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
expect(nosqlInjectionMongo).to.have.a.callCount(6);
|
|
206
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: 'devData#1', name: 'mongodb.Collection.prototype.mapReduce' });
|
|
207
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: 'devData#2', name: 'mongodb.Collection.prototype.mapReduce' });
|
|
208
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: attackString, name: 'mongodb.Collection.prototype.mapReduce' });
|
|
209
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: 'devData#3', name: 'mongodb.Collection.prototype.mapReduce' });
|
|
210
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: 'devData#4', name: 'mongodb.Collection.prototype.mapReduce' });
|
|
211
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: { expObj }, name: 'mongodb.Collection.prototype.mapReduce' });
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe('Collection.prototype.group', function() {
|
|
217
|
-
it('analyzes for a nosql-injection from user input in the first, the second and the fourth argument of `group` method', function() {
|
|
218
|
-
simulateRequestScope(function() {
|
|
219
|
-
const attackString1 = 'sleep(1)';
|
|
220
|
-
const attackString2 = 'sleep(2)';
|
|
221
|
-
const condition = { expObj: { $ne: 'test' } };
|
|
222
|
-
const initState = { init: 'state' };
|
|
223
|
-
const sc = core.scopes.sources.getStore()?.protect;
|
|
224
|
-
(new Collection()).group(
|
|
225
|
-
attackString1,
|
|
226
|
-
condition,
|
|
227
|
-
initState,
|
|
228
|
-
attackString2
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
expect(nosqlInjectionMongo).to.have.a.callCount(3);
|
|
232
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: attackString1, name: 'mongodb.Collection.prototype.group' });
|
|
233
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: attackString2, name: 'mongodb.Collection.prototype.group' });
|
|
234
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: { expObj: { $ne: 'test' } }, name: 'mongodb.Collection.prototype.group' });
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
describe('Db.prototype.eval() and other edge cases', function() {
|
|
240
|
-
it('skips patching if the method is missing', function() {
|
|
241
|
-
class Collection {
|
|
242
|
-
findOne() { }
|
|
243
|
-
}
|
|
244
|
-
class Db {
|
|
245
|
-
command() { }
|
|
246
|
-
}
|
|
247
|
-
core.depHooks.resolve
|
|
248
|
-
.withArgs(sinon.match({ name: 'mongodb' }))
|
|
249
|
-
.yields({ Collection, Db }, 'v5.x.x');
|
|
250
|
-
|
|
251
|
-
const instr = require('./mongodb')(core);
|
|
252
|
-
instr.install();
|
|
253
|
-
expect(core.logger.trace).to.have.been.calledWith({ name: 'mongodb.Db.prototype.eval', version: 'v5.x.x' }, 'method not found - skipping instrumentation');
|
|
254
|
-
expect(core.logger.trace).to.have.been.calledWith({ name: 'mongodb.Collection.prototype.findOneAndDelete', version: 'v5.x.x' }, 'method not found - skipping instrumentation');
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it('analyzes for a nosql-injection from the eval methhod', function() {
|
|
258
|
-
simulateRequestScope(function() {
|
|
259
|
-
const attackString = 'sleep(1)';
|
|
260
|
-
const sc = core.scopes.sources.getStore()?.protect;
|
|
261
|
-
(new Db()).eval(attackString);
|
|
262
|
-
|
|
263
|
-
expect(nosqlInjectionMongo).to.have.a.callCount(1);
|
|
264
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: attackString, name: 'mongodb.Db.prototype.eval' });
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('analyzes the `.filter` property on a command method', function() {
|
|
269
|
-
simulateRequestScope(function() {
|
|
270
|
-
const expObject = { $ne: 'test' };
|
|
271
|
-
const sc = core.scopes.sources.getStore()?.protect;
|
|
272
|
-
|
|
273
|
-
(new Db()).command({ filter: { expObject } });
|
|
274
|
-
|
|
275
|
-
expect(nosqlInjectionMongo).to.have.been.calledWithMatch(sc, { value: { expObject }, name: 'mongodb.Db.prototype.command' });
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
@@ -1,81 +0,0 @@
|
|
|
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 mssql', function () {
|
|
10
|
-
class PreparedStatement { }
|
|
11
|
-
class Request { }
|
|
12
|
-
let core, handleSqlInjection;
|
|
13
|
-
|
|
14
|
-
beforeEach(function () {
|
|
15
|
-
core = mocks.core();
|
|
16
|
-
core.logger = mocks.logger();
|
|
17
|
-
core.patcher = patcher(core);
|
|
18
|
-
core.scopes = scopes(core);
|
|
19
|
-
sinon.stub(core.scopes.sources, 'getStore').returns({ protect: {} });
|
|
20
|
-
core.depHooks = mocks.depHooks();
|
|
21
|
-
core.protect = mocks.protect();
|
|
22
|
-
handleSqlInjection = core.protect.inputTracing.handleSqlInjection;
|
|
23
|
-
require('../../get-source-context')(core);
|
|
24
|
-
|
|
25
|
-
PreparedStatement.prototype.prepare = sinon.stub();
|
|
26
|
-
Request.prototype.batch = sinon.stub();
|
|
27
|
-
Request.prototype.query = sinon.stub();
|
|
28
|
-
|
|
29
|
-
core.depHooks.resolve
|
|
30
|
-
.withArgs(sinon.match({ file: 'lib/base/prepared-statement.js' }))
|
|
31
|
-
.yields(PreparedStatement);
|
|
32
|
-
|
|
33
|
-
core.depHooks.resolve
|
|
34
|
-
.withArgs(sinon.match({ file: 'lib/base/request.js' }))
|
|
35
|
-
.yields(Request);
|
|
36
|
-
|
|
37
|
-
require('./mssql')(core).install();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
[
|
|
41
|
-
{ subject: PreparedStatement, method: 'prepare' },
|
|
42
|
-
{ subject: Request, method: 'batch' },
|
|
43
|
-
{ subject: Request, method: 'query' },
|
|
44
|
-
].forEach(({ subject, method }) => {
|
|
45
|
-
describe(`${subject.name}.prototype.${method}()`, function () {
|
|
46
|
-
it('does not call handleSqlInjection() if there is no sourceContext', function () {
|
|
47
|
-
core.scopes.sources.getStore.returns({});
|
|
48
|
-
|
|
49
|
-
subject.prototype[method]();
|
|
50
|
-
expect(handleSqlInjection).not.to.have.been.called;
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('does not call handleSqlInjection() if there is no argument', function () {
|
|
54
|
-
subject.prototype[method]();
|
|
55
|
-
expect(handleSqlInjection).not.to.have.been.called;
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('does not call handleSqlInjection() if the argument is not a string', function () {
|
|
59
|
-
subject.prototype[method](4);
|
|
60
|
-
expect(handleSqlInjection).not.to.have.been.called;
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('calls handleSqlInjection() as expected', function () {
|
|
64
|
-
const value = ';drop table Foo--';
|
|
65
|
-
|
|
66
|
-
subject.prototype[method](value);
|
|
67
|
-
expect(handleSqlInjection).to.have.been.calledWith(
|
|
68
|
-
{},
|
|
69
|
-
{
|
|
70
|
-
name: `mssql.${subject.name}.prototype.${method}`,
|
|
71
|
-
value,
|
|
72
|
-
stacktraceOpts: {
|
|
73
|
-
constructorOpt: subject.prototype[method],
|
|
74
|
-
prependFrames: [sinon.match.func],
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
});
|
|
@@ -1,108 +0,0 @@
|
|
|
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 mysql', 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
|
-
core.depHooks = mocks.depHooks();
|
|
19
|
-
core.protect = mocks.protect();
|
|
20
|
-
require('../../get-source-context')(core);
|
|
21
|
-
inputTracing = core.protect.inputTracing;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
sinon.stub(core.scopes.sources, 'getStore').returns(store);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('mysql', function () {
|
|
28
|
-
let Connection;
|
|
29
|
-
|
|
30
|
-
beforeEach(function () {
|
|
31
|
-
Connection = function () { };
|
|
32
|
-
Connection.prototype.query = sinon.stub();
|
|
33
|
-
core.depHooks.resolve.yields(Connection);
|
|
34
|
-
require('./mysql')(core).install();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('instruments connection.query()', function () {
|
|
38
|
-
it('handleSqlInjection() is called with valid expected values', function () {
|
|
39
|
-
const conn = new Connection();
|
|
40
|
-
const value = 'SELECT "foo"';
|
|
41
|
-
conn.query(value);
|
|
42
|
-
|
|
43
|
-
expect(inputTracing.handleSqlInjection).to.have.been.calledWith(
|
|
44
|
-
{},
|
|
45
|
-
{ name: 'mysql.Connection.prototype.query', value, stacktraceOpts: { constructorOpt: conn.query, prependFrames: [sinon.match.func] } }
|
|
46
|
-
);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('handleSqlInjection() is not called if there is no sourceContext', function () {
|
|
50
|
-
core.scopes.sources.getStore.returns({ protect: undefined });
|
|
51
|
-
const conn = new Connection();
|
|
52
|
-
const value = 'SELECT "foo"';
|
|
53
|
-
conn.query(value);
|
|
54
|
-
|
|
55
|
-
expect(inputTracing.handleSqlInjection).not.to.have.been.called;
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('handleSqlInjection() is not called if the sql value is empty or not a string', function () {
|
|
59
|
-
const conn = new Connection();
|
|
60
|
-
conn.query('');
|
|
61
|
-
conn.query(100);
|
|
62
|
-
|
|
63
|
-
expect(inputTracing.handleSqlInjection).not.to.have.been.called;
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe('mysql2', function () {
|
|
69
|
-
let Connection;
|
|
70
|
-
|
|
71
|
-
beforeEach(function () {
|
|
72
|
-
Connection = function () { };
|
|
73
|
-
Connection.prototype.execute = sinon.stub();
|
|
74
|
-
core.depHooks.resolve.yields(Connection);
|
|
75
|
-
require('./mysql')(core).install();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe('instruments connection.execute()', function () {
|
|
79
|
-
it('handleSqlInjection() is called with expected values', function () {
|
|
80
|
-
const conn = new Connection();
|
|
81
|
-
const value = 'SELECT "foo"';
|
|
82
|
-
conn.execute(value);
|
|
83
|
-
|
|
84
|
-
expect(inputTracing.handleSqlInjection).to.have.been.calledWith(
|
|
85
|
-
{},
|
|
86
|
-
{ name: 'mysql2.Connection.prototype.execute', value, stacktraceOpts: { constructorOpt: conn.execute, prependFrames: [sinon.match.func] } }
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('handleSqlInjection() is not called if there is no sourceContext', function () {
|
|
91
|
-
core.scopes.sources.getStore.returns({ protect: undefined });
|
|
92
|
-
const conn = new Connection();
|
|
93
|
-
const value = 'SELECT "foo"';
|
|
94
|
-
conn.execute(value);
|
|
95
|
-
|
|
96
|
-
expect(inputTracing.handleSqlInjection).not.to.have.been.called;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('handleSqlInjection() is not called if the sql value is empty or not a string', function () {
|
|
100
|
-
const conn = new Connection();
|
|
101
|
-
conn.execute('');
|
|
102
|
-
conn.execute(100);
|
|
103
|
-
|
|
104
|
-
expect(inputTracing.handleSqlInjection).not.to.have.been.called;
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
});
|