@contrast/protect 1.3.0 → 1.4.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/install/express4.js +2 -3
- package/lib/error-handlers/install/fastify3.js +2 -4
- package/lib/error-handlers/install/koa2.js +1 -2
- package/lib/{cli-rewriter.js → get-source-context.js} +13 -15
- package/lib/hardening/constants.js +20 -0
- package/lib/hardening/handlers.js +65 -0
- package/lib/hardening/index.js +29 -0
- package/lib/hardening/install/node-serialize0.js +59 -0
- package/lib/index.d.ts +3 -21
- package/lib/index.js +4 -0
- package/lib/input-analysis/handlers.js +169 -7
- package/lib/input-analysis/index.js +4 -0
- package/lib/input-analysis/install/body-parser1.js +13 -21
- package/lib/input-analysis/install/cookie-parser1.js +13 -15
- package/lib/input-analysis/install/express4.js +8 -13
- package/lib/input-analysis/install/fastify3.js +4 -5
- package/lib/input-analysis/install/formidable1.js +4 -5
- package/lib/input-analysis/install/http.js +12 -3
- package/lib/input-analysis/install/koa-body5.js +5 -10
- package/lib/input-analysis/install/koa-bodyparser4.js +6 -10
- package/lib/input-analysis/install/koa2.js +13 -24
- package/lib/input-analysis/install/multer1.js +5 -6
- package/lib/input-analysis/install/qs6.js +7 -11
- package/lib/input-analysis/install/universal-cookie4.js +3 -7
- package/lib/input-analysis/ip-analysis.js +76 -0
- package/lib/input-analysis/virtual-patches.js +109 -0
- package/lib/input-tracing/handlers/index.js +86 -22
- package/lib/input-tracing/index.js +10 -4
- package/lib/input-tracing/install/child-process.js +13 -7
- package/lib/input-tracing/install/eval.js +60 -0
- package/lib/input-tracing/install/fs.js +4 -2
- package/lib/input-tracing/install/http.js +63 -0
- package/lib/input-tracing/install/mongodb.js +20 -20
- package/lib/input-tracing/install/mysql.js +3 -2
- package/lib/input-tracing/install/postgres.js +5 -4
- package/lib/input-tracing/install/sequelize.js +7 -5
- package/lib/input-tracing/install/sqlite3.js +6 -4
- package/lib/input-tracing/install/vm.js +132 -0
- package/lib/make-source-context.js +4 -1
- package/lib/semantic-analysis/handlers.js +160 -0
- package/lib/semantic-analysis/index.js +38 -0
- package/package.json +7 -9
- package/lib/utils.js +0 -84
|
@@ -21,29 +21,25 @@ module.exports = (core) => {
|
|
|
21
21
|
const {
|
|
22
22
|
depHooks,
|
|
23
23
|
logger,
|
|
24
|
-
|
|
24
|
+
protect,
|
|
25
25
|
protect: { inputAnalysis },
|
|
26
26
|
} = core;
|
|
27
27
|
|
|
28
28
|
function contrastNext(req, origNext, fnName) {
|
|
29
29
|
return function next(origErr) {
|
|
30
|
-
const sourceContext =
|
|
30
|
+
const sourceContext = protect.getSourceContext(fnName);
|
|
31
31
|
let securityException;
|
|
32
32
|
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
sourceContext
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
securityException = err;
|
|
44
|
-
} else {
|
|
45
|
-
logger.error({ err }, 'Unexpected error during input analysis');
|
|
46
|
-
}
|
|
33
|
+
if (sourceContext && req.body && Object.keys(req.body).length) {
|
|
34
|
+
sourceContext.parsedBody = req.body;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
inputAnalysis.handleParsedBody(sourceContext, req.body);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
if (isSecurityException(err)) {
|
|
40
|
+
securityException = err;
|
|
41
|
+
} else {
|
|
42
|
+
logger.error({ err }, 'Unexpected error during input analysis');
|
|
47
43
|
}
|
|
48
44
|
}
|
|
49
45
|
}
|
|
@@ -96,11 +92,7 @@ module.exports = (core) => {
|
|
|
96
92
|
function contrastHooked(...args) {
|
|
97
93
|
const parser = fn.original(...args);
|
|
98
94
|
const hookedParser = function (req, res, next) {
|
|
99
|
-
|
|
100
|
-
parser(req, res, next);
|
|
101
|
-
} else {
|
|
102
|
-
parser(req, res, contrastNext(req, next, fnName));
|
|
103
|
-
}
|
|
95
|
+
parser(req, res, contrastNext(req, next, fnName));
|
|
104
96
|
};
|
|
105
97
|
|
|
106
98
|
Object.defineProperty(hookedParser, 'name', {
|
|
@@ -23,7 +23,7 @@ module.exports = (core) => {
|
|
|
23
23
|
depHooks,
|
|
24
24
|
patcher,
|
|
25
25
|
logger,
|
|
26
|
-
|
|
26
|
+
protect,
|
|
27
27
|
protect: { inputAnalysis },
|
|
28
28
|
} = core;
|
|
29
29
|
|
|
@@ -40,23 +40,21 @@ module.exports = (core) => {
|
|
|
40
40
|
const [req, , origNext] = data.args;
|
|
41
41
|
|
|
42
42
|
function contrastNext(origErr) {
|
|
43
|
-
const sourceContext =
|
|
43
|
+
const sourceContext = protect.getSourceContext('cookie-parser');
|
|
44
|
+
|
|
44
45
|
let securityException;
|
|
45
46
|
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if ((req.cookies && Object.keys(req.cookies).length) || (req.signedCookies && Object.keys(req.signedCookies).length)) {
|
|
50
|
-
sourceContext.parsedCookies = { ...req.cookies, ...req.signedCookies };
|
|
47
|
+
if (sourceContext
|
|
48
|
+
&& ((req.cookies && Object.keys(req.cookies).length) || (req.signedCookies && Object.keys(req.signedCookies).length))) {
|
|
49
|
+
sourceContext.parsedCookies = { ...req.cookies, ...req.signedCookies };
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
51
|
+
try {
|
|
52
|
+
inputAnalysis.handleCookies(sourceContext, sourceContext.parsedCookies);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (isSecurityException(err)) {
|
|
55
|
+
securityException = err;
|
|
56
|
+
} else {
|
|
57
|
+
logger.error({ err }, 'Unexpected error during input analysis');
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
}
|
|
@@ -28,7 +28,7 @@ module.exports = (core) => {
|
|
|
28
28
|
depHooks,
|
|
29
29
|
patcher,
|
|
30
30
|
logger,
|
|
31
|
-
|
|
31
|
+
protect,
|
|
32
32
|
protect: { inputAnalysis },
|
|
33
33
|
} = core;
|
|
34
34
|
|
|
@@ -47,16 +47,13 @@ module.exports = (core) => {
|
|
|
47
47
|
const [req, , origNext] = data.args;
|
|
48
48
|
|
|
49
49
|
function contrastNext(origErr) {
|
|
50
|
-
const sourceContext =
|
|
50
|
+
const sourceContext = protect.getSourceContext('Express.query');
|
|
51
51
|
let securityException;
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// which means that we've already handled/analyzed it.
|
|
58
|
-
// So we check whether we already have the `parsedQuery` property in the context
|
|
59
|
-
} else if (req.query && Object.keys(req.query).length && (!('parsedQuery' in sourceContext))) {
|
|
53
|
+
// It is possible for the query to be already parsed by `qs`
|
|
54
|
+
// which means that we've already handled/analyzed it.
|
|
55
|
+
// So we check whether we already have the `parsedQuery` property in the context
|
|
56
|
+
if (sourceContext && req.query && Object.keys(req.query).length && (!('parsedQuery' in sourceContext))) {
|
|
60
57
|
sourceContext.parsedQuery = req.query;
|
|
61
58
|
|
|
62
59
|
try {
|
|
@@ -87,11 +84,9 @@ module.exports = (core) => {
|
|
|
87
84
|
patchType,
|
|
88
85
|
pre(data) {
|
|
89
86
|
const { obj: { params } } = data;
|
|
90
|
-
const sourceContext =
|
|
87
|
+
const sourceContext = protect.getSourceContext('Express.Layer.handle_request');
|
|
91
88
|
|
|
92
|
-
if (
|
|
93
|
-
logger.debug('source context not available in `express.Layer.prototype.handle_request` hook');
|
|
94
|
-
} else if (params && Object.keys(params).length) {
|
|
89
|
+
if (sourceContext && params && Object.keys(params).length) {
|
|
95
90
|
sourceContext.parsedParams = params;
|
|
96
91
|
inputAnalysis.handleUrlParams(sourceContext, params);
|
|
97
92
|
}
|
|
@@ -28,7 +28,7 @@ module.exports = (core) => {
|
|
|
28
28
|
depHooks,
|
|
29
29
|
patcher,
|
|
30
30
|
logger,
|
|
31
|
-
|
|
31
|
+
protect,
|
|
32
32
|
protect: { inputAnalysis },
|
|
33
33
|
} = core;
|
|
34
34
|
|
|
@@ -62,12 +62,11 @@ module.exports = (core) => {
|
|
|
62
62
|
* @param {Function} done callback to signal the hook is finished.
|
|
63
63
|
*/
|
|
64
64
|
function preValidationHook(request, reply, done) {
|
|
65
|
-
const sourceContext =
|
|
65
|
+
const sourceContext = protect.getSourceContext('Fastify.preValidationHook');
|
|
66
|
+
|
|
66
67
|
let securityException;
|
|
67
68
|
|
|
68
|
-
if (
|
|
69
|
-
logger.debug('source context not available in fastify prevalidation hook');
|
|
70
|
-
} else {
|
|
69
|
+
if (sourceContext) {
|
|
71
70
|
try {
|
|
72
71
|
if (request.params) {
|
|
73
72
|
sourceContext.parsedParams = request.params;
|
|
@@ -22,7 +22,7 @@ module.exports = (core) => {
|
|
|
22
22
|
depHooks,
|
|
23
23
|
patcher,
|
|
24
24
|
logger,
|
|
25
|
-
|
|
25
|
+
protect,
|
|
26
26
|
protect: { inputAnalysis },
|
|
27
27
|
} = core;
|
|
28
28
|
|
|
@@ -36,12 +36,11 @@ module.exports = (core) => {
|
|
|
36
36
|
const origCb = data.args[1];
|
|
37
37
|
|
|
38
38
|
function hookedCb(...cbArgs) {
|
|
39
|
-
const sourceContext =
|
|
39
|
+
const sourceContext = protect.getSourceContext('formidable');
|
|
40
|
+
|
|
40
41
|
const [, fields, files] = cbArgs;
|
|
41
42
|
|
|
42
|
-
if (
|
|
43
|
-
logger.debug('source context not available in `formidable` hook');
|
|
44
|
-
} else {
|
|
43
|
+
if (sourceContext) {
|
|
45
44
|
if (fields) {
|
|
46
45
|
sourceContext.parsedBody = fields;
|
|
47
46
|
inputAnalysis.handleParsedBody(sourceContext, fields);
|
|
@@ -33,7 +33,6 @@ class HttpInstrumentation {
|
|
|
33
33
|
this.config = core.config;
|
|
34
34
|
this.logger = logger.child({ name: 'contrast:protect:input-analysis' });
|
|
35
35
|
this.depHooks = core.depHooks;
|
|
36
|
-
this.messages = core.messages;
|
|
37
36
|
this.protect = core.protect;
|
|
38
37
|
this.makeSourceContext = this.protect.makeSourceContext;
|
|
39
38
|
this.maxBodySize = 16 * 1024 * 1024;
|
|
@@ -166,6 +165,7 @@ class HttpInstrumentation {
|
|
|
166
165
|
const connectInputs = {
|
|
167
166
|
headers: HttpInstrumentation.removeCookies(reqData.headers),
|
|
168
167
|
uriPath: reqData.uriPath,
|
|
168
|
+
rawUrl: req.url,
|
|
169
169
|
// TODO AGENT-203 - need to handle method-tampering rule.
|
|
170
170
|
method: reqData.method,
|
|
171
171
|
};
|
|
@@ -174,9 +174,18 @@ class HttpInstrumentation {
|
|
|
174
174
|
if (reqData.standardUrlParsing) {
|
|
175
175
|
connectInputs.queries = reqData.queries;
|
|
176
176
|
}
|
|
177
|
+
if (inputAnalysis.virtualPatchesEvaluators?.length) {
|
|
178
|
+
store.protect.virtualPatchesEvaluators.push(...inputAnalysis.virtualPatchesEvaluators.map((e) => new Map(e)));
|
|
179
|
+
}
|
|
180
|
+
if (inputAnalysis.ipDenylist?.length) {
|
|
181
|
+
block = inputAnalysis.handleIpDenylist(store.protect, inputAnalysis.ipDenylist);
|
|
182
|
+
}
|
|
183
|
+
if (inputAnalysis.ipAllowlist?.length) {
|
|
184
|
+
const allowed = inputAnalysis.handleIpAllowlist(store.protect, inputAnalysis.ipAllowlist);
|
|
185
|
+
if (!block) Object.assign(store.protect, { allowed });
|
|
186
|
+
}
|
|
177
187
|
|
|
178
|
-
block = inputAnalysis.handleConnect(store.protect, connectInputs);
|
|
179
|
-
|
|
188
|
+
block = block || inputAnalysis.handleConnect(store.protect, connectInputs);
|
|
180
189
|
} catch (err) {
|
|
181
190
|
this.logger.error({ err }, 'Error during input analysis');
|
|
182
191
|
}
|
|
@@ -21,8 +21,7 @@ module.exports = (core) => {
|
|
|
21
21
|
const {
|
|
22
22
|
depHooks,
|
|
23
23
|
patcher,
|
|
24
|
-
|
|
25
|
-
scopes: { sources },
|
|
24
|
+
protect,
|
|
26
25
|
protect: { inputAnalysis },
|
|
27
26
|
} = core;
|
|
28
27
|
|
|
@@ -39,15 +38,11 @@ module.exports = (core) => {
|
|
|
39
38
|
const [ctx, origNext] = data.args;
|
|
40
39
|
|
|
41
40
|
async function contrastNext(origErr) {
|
|
42
|
-
const sourceContext =
|
|
41
|
+
const sourceContext = protect.getSourceContext('koa-body');
|
|
43
42
|
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (ctx.request.body && Object.keys(ctx.request.body).length) {
|
|
48
|
-
sourceContext.parsedBody = ctx.request.body;
|
|
49
|
-
inputAnalysis.handleParsedBody(sourceContext, ctx.request.body);
|
|
50
|
-
}
|
|
43
|
+
if (sourceContext && ctx.request.body && Object.keys(ctx.request.body).length) {
|
|
44
|
+
sourceContext.parsedBody = ctx.request.body;
|
|
45
|
+
inputAnalysis.handleParsedBody(sourceContext, ctx.request.body);
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
await origNext(origErr);
|
|
@@ -21,8 +21,7 @@ module.exports = (core) => {
|
|
|
21
21
|
const {
|
|
22
22
|
depHooks,
|
|
23
23
|
patcher,
|
|
24
|
-
|
|
25
|
-
scopes: { sources },
|
|
24
|
+
protect,
|
|
26
25
|
protect: { inputAnalysis },
|
|
27
26
|
} = core;
|
|
28
27
|
|
|
@@ -39,15 +38,12 @@ module.exports = (core) => {
|
|
|
39
38
|
const [ctx, origNext] = data.args;
|
|
40
39
|
|
|
41
40
|
async function contrastNext(origErr) {
|
|
42
|
-
const sourceContext =
|
|
41
|
+
const sourceContext = protect.getSourceContext('koa-bodyparser');
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
sourceContext.parsedBody = ctx.request.body;
|
|
49
|
-
inputAnalysis.handleParsedBody(sourceContext, ctx.request.body);
|
|
50
|
-
}
|
|
43
|
+
|
|
44
|
+
if (sourceContext && ctx.request.body && Object.keys(ctx.request.body).length) {
|
|
45
|
+
sourceContext.parsedBody = ctx.request.body;
|
|
46
|
+
inputAnalysis.handleParsedBody(sourceContext, ctx.request.body);
|
|
51
47
|
}
|
|
52
48
|
|
|
53
49
|
await origNext(origErr);
|
|
@@ -26,8 +26,7 @@ module.exports = (core) => {
|
|
|
26
26
|
const {
|
|
27
27
|
depHooks,
|
|
28
28
|
patcher,
|
|
29
|
-
|
|
30
|
-
scopes: { sources },
|
|
29
|
+
protect,
|
|
31
30
|
protect: { inputAnalysis },
|
|
32
31
|
} = core;
|
|
33
32
|
|
|
@@ -38,11 +37,9 @@ module.exports = (core) => {
|
|
|
38
37
|
depHooks.resolve({ name: 'koa', version: '>=2.3.0' }, (Koa) => {
|
|
39
38
|
function contrastStartMiddleware(ctx, next) {
|
|
40
39
|
if (ctx.query && Object.keys(ctx.query).length) {
|
|
41
|
-
const sourceContext =
|
|
40
|
+
const sourceContext = protect.getSourceContext('Koa startMiddleware');
|
|
42
41
|
|
|
43
|
-
if (!sourceContext) {
|
|
44
|
-
logger.debug('source context not available in `qs` hook');
|
|
45
|
-
} else if (!('parsedQuery' in sourceContext)) {
|
|
42
|
+
if (sourceContext && !('parsedQuery' in sourceContext)) {
|
|
46
43
|
sourceContext.parsedQuery = ctx.query;
|
|
47
44
|
inputAnalysis.handleQueryParams(sourceContext, ctx.query);
|
|
48
45
|
}
|
|
@@ -76,15 +73,11 @@ module.exports = (core) => {
|
|
|
76
73
|
name: `[${router}].layer.prototype`,
|
|
77
74
|
patchType,
|
|
78
75
|
post({ result }) {
|
|
79
|
-
const sourceContext =
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (Object.keys(result).length) {
|
|
85
|
-
sourceContext.parsedParams = result;
|
|
86
|
-
inputAnalysis.handleUrlParams(sourceContext, result);
|
|
87
|
-
}
|
|
76
|
+
const sourceContext = protect.getSourceContext(`[${router}].layer`);
|
|
77
|
+
|
|
78
|
+
if (sourceContext && Object.keys(result).length) {
|
|
79
|
+
sourceContext.parsedParams = result;
|
|
80
|
+
inputAnalysis.handleUrlParams(sourceContext, result);
|
|
88
81
|
}
|
|
89
82
|
}
|
|
90
83
|
});
|
|
@@ -107,15 +100,11 @@ module.exports = (core) => {
|
|
|
107
100
|
const [ctx, origNext] = data.args;
|
|
108
101
|
|
|
109
102
|
async function contrastNext(origErr) {
|
|
110
|
-
const sourceContext =
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (ctx.cookie) {
|
|
116
|
-
sourceContext.parsedCookies = ctx.cookie;
|
|
117
|
-
inputAnalysis.handleCookies(sourceContext, ctx.cookie);
|
|
118
|
-
}
|
|
103
|
+
const sourceContext = protect.getSourceContext('koa-cookie');
|
|
104
|
+
|
|
105
|
+
if (sourceContext && ctx.cookie) {
|
|
106
|
+
sourceContext.parsedCookies = ctx.cookie;
|
|
107
|
+
inputAnalysis.handleCookies(sourceContext, ctx.cookie);
|
|
119
108
|
}
|
|
120
109
|
|
|
121
110
|
await origNext(origErr);
|
|
@@ -23,7 +23,8 @@ module.exports = (core) => {
|
|
|
23
23
|
depHooks,
|
|
24
24
|
patcher,
|
|
25
25
|
logger,
|
|
26
|
-
scopes: {
|
|
26
|
+
scopes: { wrap },
|
|
27
|
+
protect,
|
|
27
28
|
protect: { inputAnalysis },
|
|
28
29
|
} = core;
|
|
29
30
|
|
|
@@ -41,11 +42,9 @@ module.exports = (core) => {
|
|
|
41
42
|
|
|
42
43
|
// We are getting the sourceContext here because in the time of calling
|
|
43
44
|
// the contrastNext() method the context is lost
|
|
44
|
-
const sourceContext =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
45
|
+
const sourceContext = protect.getSourceContext('multer');
|
|
46
|
+
|
|
47
|
+
if (!sourceContext) return;
|
|
49
48
|
|
|
50
49
|
function contrastNext(origErr) {
|
|
51
50
|
let securityException;
|
|
@@ -21,8 +21,7 @@ module.exports = (core) => {
|
|
|
21
21
|
const {
|
|
22
22
|
depHooks,
|
|
23
23
|
patcher,
|
|
24
|
-
|
|
25
|
-
scopes: { sources },
|
|
24
|
+
protect,
|
|
26
25
|
protect: { inputAnalysis },
|
|
27
26
|
} = core;
|
|
28
27
|
|
|
@@ -34,16 +33,13 @@ module.exports = (core) => {
|
|
|
34
33
|
patchType,
|
|
35
34
|
post({ args, result }) {
|
|
36
35
|
if (result && Object.keys(result).length) {
|
|
37
|
-
const sourceContext =
|
|
36
|
+
const sourceContext = protect.getSourceContext('qs');
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// the respective library that's using it (e.g. `formidable`, `co-body`) because in
|
|
45
|
-
// some cases its use is optional and we cannot rely on it.
|
|
46
|
-
} else if (sourceContext.reqData?.queries === args[0]) {
|
|
38
|
+
// We need to run analysis for the `qs` result only when it's used as a query parser.
|
|
39
|
+
// `qs` is used also for parsing bodies, but these cases we handle individually with
|
|
40
|
+
// the respective library that's using it (e.g. `formidable`, `co-body`) because in
|
|
41
|
+
// some cases its use is optional and we cannot rely on it.
|
|
42
|
+
if (sourceContext && sourceContext.reqData?.queries === args[0]) {
|
|
47
43
|
sourceContext.parsedQuery = result;
|
|
48
44
|
inputAnalysis.handleQueryParams(sourceContext, result);
|
|
49
45
|
}
|
|
@@ -21,12 +21,10 @@ module.exports = (core) => {
|
|
|
21
21
|
const {
|
|
22
22
|
depHooks,
|
|
23
23
|
patcher,
|
|
24
|
-
|
|
25
|
-
scopes: { sources },
|
|
24
|
+
protect,
|
|
26
25
|
protect: { inputAnalysis },
|
|
27
26
|
} = core;
|
|
28
27
|
|
|
29
|
-
|
|
30
28
|
// Patch `universal-cookie` package
|
|
31
29
|
function install() {
|
|
32
30
|
depHooks.resolve({ name: 'universal-cookie', file: 'cjs/utils.js' }, (uCookieUtils) => patcher.patch(uCookieUtils, 'parseCookies', {
|
|
@@ -34,11 +32,9 @@ module.exports = (core) => {
|
|
|
34
32
|
patchType,
|
|
35
33
|
post({ result }) {
|
|
36
34
|
if (result && Object.keys(result).length) {
|
|
37
|
-
const sourceContext =
|
|
35
|
+
const sourceContext = protect.getSourceContext('universal-cookie');
|
|
38
36
|
|
|
39
|
-
if (
|
|
40
|
-
logger.debug('source context not available in `universal-cookie` hook');
|
|
41
|
-
} else {
|
|
37
|
+
if (sourceContext) {
|
|
42
38
|
sourceContext.parsedCookies = result;
|
|
43
39
|
inputAnalysis.handleCookies(sourceContext, result);
|
|
44
40
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2022 Contrast Security, Inc
|
|
3
|
+
* Contact: support@contrastsecurity.com
|
|
4
|
+
* License: Commercial
|
|
5
|
+
|
|
6
|
+
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
* made available through public repositories, use of this Software is subject to
|
|
9
|
+
* the applicable End User Licensing Agreement found at
|
|
10
|
+
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
* between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
* way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const { Event } = require('@contrast/common');
|
|
19
|
+
const address = require('ipaddr.js');
|
|
20
|
+
|
|
21
|
+
module.exports = (core) => {
|
|
22
|
+
const {
|
|
23
|
+
messages,
|
|
24
|
+
protect: { inputAnalysis },
|
|
25
|
+
} = core;
|
|
26
|
+
|
|
27
|
+
const ipAllowlist = inputAnalysis.ipAllowlist = [];
|
|
28
|
+
const ipDenylist = inputAnalysis.ipDenylist = [];
|
|
29
|
+
|
|
30
|
+
messages.on(Event.SERVER_SETTINGS_UPDATE, (serverUpdate) => {
|
|
31
|
+
const now = new Date().getTime();
|
|
32
|
+
const updatedIpAllowList = serverUpdate.features?.defend?.ipAllowlist.map((ipEntry) => ipEntryMap(ipEntry, now));
|
|
33
|
+
const updatedIpDenyList = serverUpdate.features?.defend?.ipDenylist.map((ipEntry) => ipEntryMap(ipEntry, now));
|
|
34
|
+
|
|
35
|
+
if (updatedIpAllowList) {
|
|
36
|
+
ipAllowlist.length = 0;
|
|
37
|
+
ipAllowlist.push(...updatedIpAllowList);
|
|
38
|
+
}
|
|
39
|
+
if (updatedIpDenyList) {
|
|
40
|
+
ipDenylist.length = 0;
|
|
41
|
+
ipDenylist.push(...updatedIpDenyList);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function ipEntryMap(ipEntry, startTime) {
|
|
47
|
+
const { ip, expires } = ipEntry;
|
|
48
|
+
let doesExpire, expiresAt, cidr;
|
|
49
|
+
|
|
50
|
+
if (expires) {
|
|
51
|
+
doesExpire = true;
|
|
52
|
+
expiresAt = startTime + expires;
|
|
53
|
+
} else {
|
|
54
|
+
doesExpire = false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const slashIdx = ip.indexOf('/');
|
|
58
|
+
const isCIDR = slashIdx >= 0;
|
|
59
|
+
const ipInstance = isCIDR
|
|
60
|
+
? address.process(ip.substr(0, slashIdx))
|
|
61
|
+
: address.process(ip);
|
|
62
|
+
|
|
63
|
+
const normalizedValue = ipInstance.toNormalizedString();
|
|
64
|
+
|
|
65
|
+
if (isCIDR) {
|
|
66
|
+
cidr = { range: address.parseCIDR(ip), kind: ipInstance.kind() };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...ipEntry,
|
|
71
|
+
doesExpire,
|
|
72
|
+
expiresAt,
|
|
73
|
+
normalizedValue,
|
|
74
|
+
cidr
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2022 Contrast Security, Inc
|
|
3
|
+
* Contact: support@contrastsecurity.com
|
|
4
|
+
* License: Commercial
|
|
5
|
+
|
|
6
|
+
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
* made available through public repositories, use of this Software is subject to
|
|
9
|
+
* the applicable End User Licensing Agreement found at
|
|
10
|
+
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
* between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
* way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const { Event } = require('@contrast/common');
|
|
19
|
+
|
|
20
|
+
module.exports = (core) => {
|
|
21
|
+
const {
|
|
22
|
+
messages,
|
|
23
|
+
protect: { inputAnalysis },
|
|
24
|
+
} = core;
|
|
25
|
+
|
|
26
|
+
const virtualPatchesEvaluators = inputAnalysis.virtualPatchesEvaluators = [];
|
|
27
|
+
|
|
28
|
+
messages.on(Event.SERVER_SETTINGS_UPDATE, (serverUpdate) => {
|
|
29
|
+
const virtualPatches = serverUpdate.settings?.defend.virtualPatches;
|
|
30
|
+
if (virtualPatches) {
|
|
31
|
+
buildVPEvaluators(virtualPatches, virtualPatchesEvaluators);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function buildVPEvaluators(virtualPatches, evaluatorsArray) {
|
|
37
|
+
evaluatorsArray.length = 0;
|
|
38
|
+
for (const { headers, parameters, urls, uuid, name } of virtualPatches) {
|
|
39
|
+
const evaluators = new Map();
|
|
40
|
+
|
|
41
|
+
if (headers?.length) {
|
|
42
|
+
evaluators.set('HEADERS', (reqHeaders) => {
|
|
43
|
+
let result;
|
|
44
|
+
for (const { evaluation, name, value } of headers) {
|
|
45
|
+
const evalCheck = buildEvaluationCheck(evaluation);
|
|
46
|
+
const keyIndex = reqHeaders.indexOf(name.toLowerCase());
|
|
47
|
+
|
|
48
|
+
result = keyIndex !== -1 && evalCheck(reqHeaders[keyIndex + 1], value);
|
|
49
|
+
if (!result) break;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (parameters?.length) {
|
|
57
|
+
evaluators.set('PARAMETERS', (reqParameters) => {
|
|
58
|
+
let result;
|
|
59
|
+
for (const { evaluation, name, value } of parameters) {
|
|
60
|
+
const evalCheck = buildEvaluationCheck(evaluation);
|
|
61
|
+
|
|
62
|
+
result = evalCheck(reqParameters[name], value);
|
|
63
|
+
if (!result) break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (urls?.length) {
|
|
71
|
+
evaluators.set('URLS', (reqUrl) => {
|
|
72
|
+
let result;
|
|
73
|
+
for (const { evaluation, value } of urls) {
|
|
74
|
+
const evalCheck = buildEvaluationCheck(evaluation);
|
|
75
|
+
|
|
76
|
+
result = evalCheck(reqUrl, value);
|
|
77
|
+
if (!result) break;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return result;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (evaluators.size) {
|
|
85
|
+
evaluators.set('metadata', { name, uuid });
|
|
86
|
+
evaluatorsArray.push(evaluators);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildEvaluationCheck(evaluation) {
|
|
92
|
+
switch (evaluation) {
|
|
93
|
+
case 'MATCHES':
|
|
94
|
+
return (reqValue, matchedValue) => new RegExp(matchedValue, 'i').test(reqValue);
|
|
95
|
+
case 'EQUALS':
|
|
96
|
+
return (reqValue, matchedValue) => reqValue.toString() === matchedValue.toString();
|
|
97
|
+
case 'CONTAINS':
|
|
98
|
+
return (reqValue, matchedValue) => reqValue.includes(matchedValue);
|
|
99
|
+
case 'DOESNT_MATCH':
|
|
100
|
+
return (reqValue, matchedValue) => !new RegExp(matchedValue, 'i').test(reqValue);
|
|
101
|
+
// This is a typo but it is how it's passed from ContrastUI
|
|
102
|
+
case 'DOESNT_EQUALS':
|
|
103
|
+
return (reqValue, matchedValue) => reqValue.toString() !== matchedValue.toString();
|
|
104
|
+
case 'DOESNT_CONTAIN':
|
|
105
|
+
return (reqValue, matchedValue) => !reqValue.includes(matchedValue);
|
|
106
|
+
default:
|
|
107
|
+
return () => false;
|
|
108
|
+
}
|
|
109
|
+
}
|