Socket
Socket
Sign inDemoInstall

@contrast/protect

Package Overview
Dependencies
Maintainers
17
Versions
73
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@contrast/protect - npm Package Compare versions

Comparing version 1.5.0 to 1.6.0

2

lib/index.js

@@ -41,3 +41,3 @@ /*

protect.install = function() {
installChildComponentsSync(protect)
installChildComponentsSync(protect);
};

@@ -44,0 +44,0 @@

@@ -20,2 +20,3 @@ /*

const address = require('ipaddr.js');
const { Rule } = require('@contrast/common');

@@ -58,2 +59,3 @@ //

},
config,
} = core;

@@ -132,8 +134,64 @@

// this is called before the request goes away. this is where probe detection takes
// place. basically loop through findings.resultsList and, for all those that weren't
// blocked, run scoreAtom() with preferWorthWatching: false. for any that have a score
// >= 90 report them as probes.
/**
* handleRequestEnd()
*
* Invoked when the request is complete.
*
* @param {Object} sourceContext
*/
inputAnalysis.handleRequestEnd = function handleRequestEnd(sourceContext) {
throw new Error('nyi', sourceContext);
if (!config.protect.probe_analysis.enable) return;
const { resultsMap } = sourceContext.findings;
const probesRules = [Rule.CMD_INJECTION, Rule.PATH_TRAVERSAL, Rule.SQL_INJECTION, Rule.XXE];
const props = {};
// Detecting probes
Object.values(resultsMap).forEach(resultsByRuleId => {
resultsByRuleId.forEach((resultByRuleId) => {
const {
ruleId,
blocked,
details,
value,
inputType
} = resultByRuleId;
if (blocked || !blocked && details.length > 0 || !probesRules.some(rule => rule === ruleId)) return;
const { policy: { rulesMask } } = sourceContext;
const results = (agentLib.scoreAtom(
rulesMask,
value,
agentLib.InputType[inputType],
{
preferWorthWatching: false
}
) || []).filter(({ score }) => score >= 90);
if (!results.length) return;
results.forEach(result => {
const isAlreadyBlocked = (resultsMap[result.ruleId] || []).some(element =>
element.blocked && element.inputType === inputType && element.value === value
);
if (isAlreadyBlocked) return;
const probe = Object.assign({}, resultByRuleId, result, {
mappedId: result.ruleId
});
const key = [probe.ruleId, probe.inputType, ...probe.path, probe.value].join('|');
props[key] = probe;
});
});
});
Object.values(props).forEach(prop => {
if (!resultsMap[prop.ruleId]) {
resultsMap[prop.ruleId] = [];
}
resultsMap[prop.ruleId].push(prop);
});
};

@@ -140,0 +198,0 @@

@@ -39,44 +39,42 @@ /*

function install() {
depHooks.resolve({ name: 'fastify', version: '>=3 <5' }, (fastify) => {
return patcher.patch(fastify, {
name: 'fastify.build',
patchType,
post({ result: server }) {
server.addHook('preValidation', function(request, reply, done) {
let securityException;
const sourceContext = protect.getSourceContext('Fastify.preValidationHook');
depHooks.resolve({ name: 'fastify', version: '>=3 <5' }, (fastify) => patcher.patch(fastify, {
name: 'fastify.build',
patchType,
post({ result: server }) {
server.addHook('preValidation', function(request, reply, done) {
let securityException;
const sourceContext = protect.getSourceContext('Fastify.preValidationHook');
if (sourceContext) {
try {
if (request.params) {
sourceContext.parsedParams = request.params;
inputAnalysis.handleUrlParams(sourceContext, request.params);
}
if (request.cookies) {
sourceContext.parsedCookies = request.cookies;
inputAnalysis.handleCookies(sourceContext, request.cookies);
}
if (request.body) {
sourceContext.parsedBody = request.body;
inputAnalysis.handleParsedBody(sourceContext, request.body);
}
if (sourceContext) {
try {
if (request.params) {
sourceContext.parsedParams = request.params;
inputAnalysis.handleUrlParams(sourceContext, request.params);
}
if (request.cookies) {
sourceContext.parsedCookies = request.cookies;
inputAnalysis.handleCookies(sourceContext, request.cookies);
}
if (request.body) {
sourceContext.parsedBody = request.body;
inputAnalysis.handleParsedBody(sourceContext, request.body);
}
if (request.query) {
sourceContext.parsedQuery = request.query;
inputAnalysis.handleQueryParams(sourceContext, request.query);
}
} catch (err) {
if (isSecurityException(err)) {
securityException = err;
} else {
logger.error({ err }, 'Unexpected error during input analysis');
}
if (request.query) {
sourceContext.parsedQuery = request.query;
inputAnalysis.handleQueryParams(sourceContext, request.query);
}
} catch (err) {
if (isSecurityException(err)) {
securityException = err;
} else {
logger.error({ err }, 'Unexpected error during input analysis');
}
}
}
done(securityException);
});
},
});
});
done(securityException);
});
},
}));
}

@@ -83,0 +81,0 @@

@@ -30,7 +30,6 @@ /*

constructor(core) {
const { logger } = core;
this.messages = core.messages;
this.scope = core.scopes.sources;
this.config = core.config;
this.logger = logger.child({ name: 'contrast:protect:input-analysis' });
this.logger = core.logger.child('contrast:protect:input-analysis');
this.depHooks = core.depHooks;

@@ -185,3 +184,6 @@ this.protect = core.protect;

res.on('finish', () => messages.emit(Event.PROTECT, store));
res.on('finish', () => {
this.protect.inputAnalysis.handleRequestEnd(store.protect);
messages.emit(Event.PROTECT, store);
});

@@ -188,0 +190,0 @@ // don't put inputs in the store; they are a param to each handler. findings

@@ -57,2 +57,3 @@ /*

core.protect.semanticAnalysis.handleCmdInjectionSemanticDangerous(sourceContext, sinkContext);
core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(sourceContext, sinkContext);
}

@@ -59,0 +60,0 @@ });

@@ -109,2 +109,3 @@ /*

inputTracing.handlePathTraversal(sourceContext, sinkContext);
core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(sourceContext, sinkContext);
}

@@ -111,0 +112,0 @@ }

@@ -37,19 +37,21 @@ /*

patcher.patch(global.ContrastMethods, 'Function', {
name: 'global.ContrastMethods.Function',
patchType,
pre: ({ args, hooked, orig }) => {
if (instrumentation.isLocked()) return;
Object.assign(global.ContrastMethods, {
Function: patcher.patch(global.ContrastMethods.Function, {
name: 'global.ContrastMethods.Function',
patchType,
pre: ({ args, hooked, orig }) => {
if (instrumentation.isLocked()) return;
const sourceContext = protect.getSourceContext('Function');
const fnBody = args[args.length - 1];
const sourceContext = protect.getSourceContext('Function');
const fnBody = args[args.length - 1];
if (!sourceContext || !fnBody || !isString(fnBody)) return;
if (!sourceContext || !fnBody || !isString(fnBody)) return;
const sinkContext = captureStacktrace(
{ name: 'Function', value: fnBody },
{ constructorOpt: hooked, prependFrames: [orig] }
);
inputTracing.ssjsInjection(sourceContext, sinkContext);
}
const sinkContext = captureStacktrace(
{ name: 'Function', value: fnBody },
{ constructorOpt: hooked, prependFrames: [orig] }
);
inputTracing.ssjsInjection(sourceContext, sinkContext);
}
})
});

@@ -56,0 +58,0 @@ }

@@ -108,3 +108,3 @@ /*

const protectionRules = remoteSettings?.settings?.defend?.protectionRules
const protectionRules = remoteSettings?.settings?.defend?.protectionRules;
if (protectionRules) {

@@ -111,0 +111,0 @@ updateFromProtectionRules(protectionRules);

@@ -19,2 +19,3 @@ /*

const {
Rule,
BLOCKING_MODES,

@@ -30,3 +31,9 @@ ProtectRuleMode: { OFF },

// The sink instrumentation for this rule is in `protect/lib/input-tracing/install/child-process.js
const getRuleResults = function(obj, prop) {
return obj[prop] || (obj[prop] = []);
};
// Semantic analysis currently shares instrumentation with the input-tracing sinks.
// See files in protect/lib/input-tracing/install/.
module.exports = function(core) {

@@ -36,3 +43,2 @@ const { protect: { agentLib, semanticAnalysis, throwSecurityException } } = core;

function handleResult(sourceContext, sinkContext, ruleId, mode, finding) {
const sinkResults = sourceContext.findings.semanticResultsMap[ruleId];
const result = {

@@ -45,3 +51,3 @@ blocked: false,

sourceContext.findings.semanticResultsMap[ruleId] = sinkResults ? [...sinkResults, result] : [result];
getRuleResults(sourceContext.findings.semanticResultsMap, ruleId).push(result);

@@ -57,4 +63,3 @@ if (BLOCKING_MODES.includes(mode)) {

semanticAnalysis.handleCmdInjectionSemanticDangerous = function(sourceContext, sinkContext) {
const ruleId = 'cmd-injection-semantic-dangerous-paths';
const mode = sourceContext.policy[ruleId];
const mode = sourceContext.policy[Rule.CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS];

@@ -66,3 +71,3 @@ if (mode == OFF) return;

if (result) {
handleResult(sourceContext, sinkContext, ruleId, mode);
handleResult(sourceContext, sinkContext, Rule.CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS, mode);
}

@@ -72,4 +77,3 @@ };

semanticAnalysis.handleCmdInjectionSemanticChainedCommands = function(sourceContext, sinkContext) {
const ruleId = 'cmd-injection-semantic-chained-commands';
const mode = sourceContext.policy[ruleId];
const mode = sourceContext.policy[Rule.CMD_INJECTION_SEMANTIC_CHAINED_COMMANDS];

@@ -81,3 +85,3 @@ if (mode == OFF) return;

if (indexOfChaining != -1) {
handleResult(sourceContext, sinkContext, ruleId, mode);
handleResult(sourceContext, sinkContext, Rule.CMD_INJECTION_SEMANTIC_CHAINED_COMMANDS, mode);
}

@@ -87,4 +91,3 @@ };

semanticAnalysis.handleCommandInjectionCommandBackdoors = function(sourceContext, sinkContext) {
const ruleId = 'cmd-injection-command-backdoors';
const mode = sourceContext.policy[ruleId];
const mode = sourceContext.policy[Rule.CMD_INJECTION_COMMAND_BACKDOORS];

@@ -96,6 +99,18 @@ if (mode == OFF) return;

if (finding) {
handleResult(sourceContext, sinkContext, ruleId, mode, finding);
handleResult(sourceContext, sinkContext, Rule.CMD_INJECTION_COMMAND_BACKDOORS, mode, finding);
}
};
semanticAnalysis.handlePathTraversalFileSecurityBypass = function(sourceContext, sinkContext) {
const mode = sourceContext.policy[Rule.PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS];
if (mode == OFF) return;
if (agentLib.isDangerousPath(sinkContext.value, true)) {
handleResult(sourceContext, sinkContext, Rule.PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS, mode, {
findings: { path: sinkContext.value }
});
}
};
return semanticAnalysis;

@@ -132,16 +147,18 @@ };

!found &&
value &&
type === 'Value' &&
isString(value) &&
isBackdoorDetected(value, command)
type === 'Value' &&
isBackdoorDetected(value, command)
) {
let key;
key = inputType === InputType.HEADER ? obj.indexOf(command) - 1 : path[path.length - 1];
if (Number.isInteger(key) && obj[key]) {
key = obj[key];
if (inputType === InputType.HEADER) {
key = obj[path[0] - 1]
} else {
key = path[path.length - 1];
}
path = path.length === 1 ? [] : Array.from(path).slice(0, path.length - 1);
inputType = path.length > 1 ? InputType.JSON_VALUE : inputType;
found = { key, inputType, path, value: command };
found = {
key,
inputType: path.length > 1 ? InputType.JSON_VALUE : inputType,
path: path.slice(0, -1),
value: command
};
}

@@ -148,0 +165,0 @@ });

{
"name": "@contrast/protect",
"version": "1.5.0",
"version": "1.6.0",
"description": "Contrast service providing framework-agnostic Protect support",

@@ -23,5 +23,5 @@ "license": "SEE LICENSE IN LICENSE",

"@contrast/agent-lib": "^5.1.0",
"@contrast/common": "1.1.1",
"@contrast/core": "1.4.0",
"@contrast/esm-hooks": "1.1.5",
"@contrast/common": "1.1.2",
"@contrast/core": "1.5.0",
"@contrast/esm-hooks": "1.1.6",
"@contrast/scopes": "1.1.1",

@@ -28,0 +28,0 @@ "builtin-modules": "^3.2.0",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc