@newrelic/security-agent 1.5.0 → 2.1.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/CHANGELOG.md +31 -0
- package/THIRD_PARTY_NOTICES.md +48 -1
- package/lib/instrumentation-security/core/constants.js +8 -1
- package/lib/instrumentation-security/core/event-constants.js +5 -3
- package/lib/instrumentation-security/core/request-manager.js +40 -8
- package/lib/instrumentation-security/core/sec-utils.js +6 -5
- package/lib/instrumentation-security/hooks/director/nr-director.js +6 -6
- package/lib/instrumentation-security/hooks/express/nr-express.js +11 -8
- package/lib/instrumentation-security/hooks/fastify/nr-fastify.js +6 -6
- package/lib/instrumentation-security/hooks/fs/nr-fs.js +11 -28
- package/lib/instrumentation-security/hooks/grpc-js/nr-grpc.js +4 -4
- package/lib/instrumentation-security/hooks/hapi/nr-hapi.js +7 -7
- package/lib/instrumentation-security/hooks/http/nr-http.js +137 -13
- package/lib/instrumentation-security/hooks/koa/@koa/nr-@koa-router.js +5 -5
- package/lib/instrumentation-security/hooks/ldap/nr-ldap.js +4 -0
- package/lib/instrumentation-security/hooks/mongodb/nr-mongodb.js +4 -0
- package/lib/instrumentation-security/hooks/mysql/nr-mysql.js +4 -0
- package/lib/instrumentation-security/hooks/native/nr-childProcess.js +4 -0
- package/lib/instrumentation-security/hooks/postgres/nr-postgres.js +4 -0
- package/lib/instrumentation-security/hooks/restify/nr-restify.js +7 -7
- package/lib/instrumentation-security/hooks/undici/nr-undici.js +4 -0
- package/lib/instrumentation-security/hooks/xpath/nr-xpath.js +4 -0
- package/lib/nr-security-agent/index.js +38 -5
- package/lib/nr-security-agent/lib/core/Auth-headers.js +20 -0
- package/lib/nr-security-agent/lib/core/ExceptionReporting.js +1 -0
- package/lib/nr-security-agent/lib/core/Policy.js +0 -12
- package/lib/nr-security-agent/lib/core/agent.js +5 -1
- package/lib/nr-security-agent/lib/core/applicationinfo.js +2 -2
- package/lib/nr-security-agent/lib/core/commonUtils.js +141 -45
- package/lib/nr-security-agent/lib/core/connections/websocket/proxy-util.js +61 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/IASTUtils.js +34 -6
- package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js +36 -6
- package/lib/nr-security-agent/lib/core/connections/websocket/response/startup-handshake-handler.js +5 -1
- package/lib/nr-security-agent/lib/core/connections/websocket/response/ws-reconnect.js +7 -4
- package/lib/nr-security-agent/lib/core/connections/websocket/websocket.js +32 -14
- package/lib/nr-security-agent/lib/core/event.js +2 -2
- package/lib/nr-security-agent/lib/core/health-check.js +8 -2
- package/lib/nr-security-agent/lib/core/sec-agent-constants.js +2 -3
- package/lib/nr-security-agent/lib/core/sec-util.js +40 -4
- package/lib/nr-security-agent/lib/core/websocket-client.js +35 -12
- package/lib/nr-security-api/index.js +47 -15
- package/package.json +10 -4
- package/lib/nr-security-agent/lib/core/statusUtils.js +0 -165
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
module.exports = initialize
|
|
7
7
|
const requestManager = require('../../core/request-manager');
|
|
8
|
-
const { NR_CSEC_FUZZ_REQUEST_ID, QUESTION_MARK, EMPTY_STRING, UTF8, CONTENT_TYPE, TEXT_HTML, APPLICATION_JSON, APPLICATION_XML, APPLICATION_XHTML, TEXT_PLAIN, APPLICATION_X_FORM_URLENCODED, MULTIPART_FORM_DATA, COMMA } = require('../../core/constants');
|
|
8
|
+
const { NR_CSEC_FUZZ_REQUEST_ID, QUESTION_MARK, EMPTY_STRING, UTF8, CONTENT_TYPE, TEXT_HTML, APPLICATION_JSON, APPLICATION_XML, APPLICATION_XHTML, TEXT_PLAIN, APPLICATION_X_FORM_URLENCODED, MULTIPART_FORM_DATA, COMMA, SEMI_COLON, EQUAL, SECURE, HTTPONLY, SAMESITE, STRICT } = require('../../core/constants');
|
|
9
9
|
const ARRAY_TYPE = 'Array';
|
|
10
10
|
const STRING_TYPE = 'string';
|
|
11
11
|
const BUFFER_TYPE = 'Buffer';
|
|
@@ -34,12 +34,14 @@ const semver = require('semver');
|
|
|
34
34
|
const ExceptionReporting = require('../../../nr-security-agent/lib/core/ExceptionReporting');
|
|
35
35
|
|
|
36
36
|
const CSEC_SEP = ':IAST:';
|
|
37
|
-
const
|
|
37
|
+
const sep = require('path').sep;
|
|
38
|
+
const find = `${sep}{{NR_CSEC_VALIDATOR_HOME_TMP}}`;
|
|
38
39
|
const CSEC_HOME_TMP_CONST = new RegExp(find, 'g');
|
|
39
40
|
let CSEC_HOME = NRAgent && NRAgent.config.newrelic_home ? NRAgent.config.newrelic_home : NRAgent && NRAgent.config.logging.filepath ? path.dirname(NRAgent.config.logging.filepath) : require('path').join(process.cwd());
|
|
40
41
|
const CSEC_HOME_TMP = `${CSEC_HOME}/nr-security-home/tmp/language-agent/${process.env.applicationUUID}`
|
|
41
42
|
let lastTransactionId = EMPTY_STRING;
|
|
42
43
|
let uncaughtExceptionReportFlag = false;
|
|
44
|
+
const requestBodyLimit = 500;
|
|
43
45
|
|
|
44
46
|
|
|
45
47
|
/**
|
|
@@ -90,6 +92,10 @@ function setupRequestMapCleanup() {
|
|
|
90
92
|
* @param {*} moduleName
|
|
91
93
|
*/
|
|
92
94
|
function outboundHook(shim, mod, method, moduleName) {
|
|
95
|
+
if (API && API.getNRAgent() && API.getNRAgent().config.security.exclude_from_iast_scan.iast_detection_category.ssrf) {
|
|
96
|
+
logger.warn('ssrf detection is disabled');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
93
99
|
logger.debug(`Instrumenting ${moduleName}.${method}`);
|
|
94
100
|
shim.wrap(mod, method, function makeRequestWrapper(shim, fn) {
|
|
95
101
|
if (!shim.isFunction(fn)) {
|
|
@@ -172,8 +178,12 @@ function parseFuzzheaders(requestData, transactionId) {
|
|
|
172
178
|
const additionalData = fuzzheaders[NR_CSEC_FUZZ_REQUEST_ID].split(CSEC_SEP);
|
|
173
179
|
logger.debug('AdditionalData:', additionalData);
|
|
174
180
|
if (additionalData.length >= 8) {
|
|
175
|
-
let encryptedData = additionalData[6];
|
|
176
|
-
let hashVerifier = additionalData[7];
|
|
181
|
+
let encryptedData = additionalData[6].trim();
|
|
182
|
+
let hashVerifier = additionalData[7].trim();
|
|
183
|
+
|
|
184
|
+
if (lodash.isEmpty(encryptedData) || lodash.isEmpty(hashVerifier)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
177
187
|
let decryptedData = secUtils.decryptData(encryptedData);
|
|
178
188
|
let verifiedHash = secUtils.hashVerifier(decryptedData, hashVerifier);
|
|
179
189
|
logger.debug("verifiedHash:", verifiedHash);
|
|
@@ -184,6 +194,7 @@ function parseFuzzheaders(requestData, transactionId) {
|
|
|
184
194
|
logger.debug("fliesTocreate:", filesToCreate);
|
|
185
195
|
for (let i = 0; i < filesToCreate.length; i++) {
|
|
186
196
|
let file = filesToCreate[i].trim();
|
|
197
|
+
file = decodeURIComponent(file);
|
|
187
198
|
file = file.replace(CSEC_HOME_TMP_CONST, CSEC_HOME_TMP);
|
|
188
199
|
file = path.resolve(file);
|
|
189
200
|
const parentDir = path.dirname(file);
|
|
@@ -222,15 +233,15 @@ function parseFuzzheaders(requestData, transactionId) {
|
|
|
222
233
|
function addRequestData(shim, request) {
|
|
223
234
|
try {
|
|
224
235
|
const data = Object.assign({});
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
236
|
+
const transaction = shim.tracer.getTransaction();
|
|
237
|
+
if (transaction) {
|
|
227
238
|
data.protocol = (request.connection && request.connection.encrypted) ? 'https' : 'http';
|
|
228
239
|
data.body = null;
|
|
229
240
|
data.headers = request.headers;
|
|
230
241
|
data.url = request.url;
|
|
231
242
|
data.method = request.method;
|
|
232
243
|
data.httpVersion = request.httpVersion;
|
|
233
|
-
data.serverPort =
|
|
244
|
+
data.serverPort = transaction.port;
|
|
234
245
|
data.contextPath = '/';
|
|
235
246
|
const queryObject = URL.parse(request.url, true).query;
|
|
236
247
|
data.parameterMap = {};
|
|
@@ -244,7 +255,8 @@ function addRequestData(shim, request) {
|
|
|
244
255
|
});
|
|
245
256
|
}
|
|
246
257
|
data.clientIP = requestIp.getClientIp(request);
|
|
247
|
-
|
|
258
|
+
data.dataTruncated = false;
|
|
259
|
+
const transactionId = transaction.id;
|
|
248
260
|
const storedRequest = requestManager.getRequestFromId(transactionId);
|
|
249
261
|
lastTransactionId = transactionId;
|
|
250
262
|
if (storedRequest && storedRequest.uri) {
|
|
@@ -284,7 +296,7 @@ function emitHook(shim, mod, moduleName) {
|
|
|
284
296
|
const resp = arguments[2];
|
|
285
297
|
|
|
286
298
|
if (arguments[0] == 'request') {
|
|
287
|
-
if (NRAgent && NRAgent.config.security.detection.rxss.enabled) {
|
|
299
|
+
if (NRAgent && !NRAgent.config.security.exclude_from_iast_scan.iast_detection_category.rxss && NRAgent.config.security.detection.rxss.enabled) {
|
|
288
300
|
responseHook(resp, req, shim);
|
|
289
301
|
}
|
|
290
302
|
|
|
@@ -356,14 +368,28 @@ function onDataHook(shim, mod) {
|
|
|
356
368
|
return function onDataWrapper() {
|
|
357
369
|
const chunk = arguments[0];
|
|
358
370
|
const chunkType = typeof chunk;
|
|
359
|
-
|
|
371
|
+
const transaction = shim.tracer.getTransaction();
|
|
360
372
|
if (chunkType === STRING_TYPE || chunkType === ARRAY_TYPE || chunkType === BUFFER_TYPE || chunkType === UINT8ARRAY_TYPE || chunkType === OBJECT_TYPE) {
|
|
361
373
|
let data = chunk.toString();
|
|
362
|
-
const transactionId =
|
|
374
|
+
const transactionId = transaction.id;
|
|
363
375
|
const requestData = requestManager.getRequestFromId(transactionId);
|
|
364
376
|
if (requestData) {
|
|
365
377
|
data = requestData.body ? requestData.body.concat(chunk.toString()) : chunk.toString();
|
|
366
|
-
requestManager.updateRequestBody(shim, data);
|
|
378
|
+
requestManager.updateRequestBody(shim, data, false);
|
|
379
|
+
try {
|
|
380
|
+
const contentLength = Buffer.byteLength(data, 'utf8');
|
|
381
|
+
let bodyLimit = requestBodyLimit;
|
|
382
|
+
if (!isNaN(bodyLimit)) {
|
|
383
|
+
bodyLimit = bodyLimit * 1000;
|
|
384
|
+
if (contentLength && contentLength > bodyLimit) {
|
|
385
|
+
data = truncateStringToBytes(data, bodyLimit);
|
|
386
|
+
requestManager.updateRequestBody(shim, data, true);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
} catch (error) {
|
|
391
|
+
logger.error("Error while truncating request body", error);
|
|
392
|
+
}
|
|
367
393
|
}
|
|
368
394
|
|
|
369
395
|
}
|
|
@@ -418,6 +444,11 @@ function responseHook(resp, req, shim) {
|
|
|
418
444
|
} catch (error) {
|
|
419
445
|
logger.debug("Error while reporting error", error);
|
|
420
446
|
}
|
|
447
|
+
try {
|
|
448
|
+
secureCookieCheck(response, shim);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
logger.debug("Error while generating secure cookie event");
|
|
451
|
+
}
|
|
421
452
|
|
|
422
453
|
responseBodyCompute(response, arguments);
|
|
423
454
|
|
|
@@ -594,7 +625,7 @@ function deleteTempFileForIAST(file) {
|
|
|
594
625
|
*/
|
|
595
626
|
process.on('uncaughtException', (err, origin) => {
|
|
596
627
|
console.error(err);
|
|
597
|
-
if(uncaughtExceptionReportFlag){
|
|
628
|
+
if (uncaughtExceptionReportFlag) {
|
|
598
629
|
process.exit(1);
|
|
599
630
|
}
|
|
600
631
|
const request = requestManager.getRequestFromId(lastTransactionId);
|
|
@@ -606,3 +637,96 @@ process.on('uncaughtException', (err, origin) => {
|
|
|
606
637
|
uncaughtExceptionReportFlag = true;
|
|
607
638
|
});
|
|
608
639
|
|
|
640
|
+
/**
|
|
641
|
+
* utility to parse response headers for cookie check
|
|
642
|
+
* @param {*} headersString
|
|
643
|
+
* @returns
|
|
644
|
+
*/
|
|
645
|
+
function responseRawHeaderParsing(headersString) {
|
|
646
|
+
let setCookieValue;
|
|
647
|
+
try {
|
|
648
|
+
const headersArray = headersString.split('\r\n');
|
|
649
|
+
for (const header of headersArray) {
|
|
650
|
+
if (header.toLowerCase().startsWith('set-cookie:')) {
|
|
651
|
+
setCookieValue = header.trim().replace('set-cookie:', '');
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
} catch (error) {
|
|
656
|
+
|
|
657
|
+
}
|
|
658
|
+
return setCookieValue;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Utility to check insecure cookie settings
|
|
663
|
+
* @param {*} response
|
|
664
|
+
* @param {*} shim
|
|
665
|
+
*/
|
|
666
|
+
function secureCookieCheck(response, shim) {
|
|
667
|
+
let cookieHeader = response.getHeader('set-cookie') ? response.getHeader('set-cookie') : responseRawHeaderParsing(response._header);
|
|
668
|
+
let cookieHeaderList = [];
|
|
669
|
+
if (!cookieHeader) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (typeof cookieHeader === 'string') {
|
|
673
|
+
cookieHeaderList.push(cookieHeader)
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
cookieHeaderList = cookieHeaderList.concat(cookieHeader);
|
|
677
|
+
}
|
|
678
|
+
let csec_request = requestManager.getRequest(shim);
|
|
679
|
+
try {
|
|
680
|
+
let parameters = [];
|
|
681
|
+
for (const cookieString of cookieHeaderList) {
|
|
682
|
+
if (cookieString) {
|
|
683
|
+
const keyValuePairs = cookieString.split(SEMI_COLON);
|
|
684
|
+
let params = {
|
|
685
|
+
"isHttpOnly": false,
|
|
686
|
+
"isSecure": false,
|
|
687
|
+
"isSameSiteStrict": false,
|
|
688
|
+
};
|
|
689
|
+
for (let i = 0; i < keyValuePairs.length; i++) {
|
|
690
|
+
const pair = keyValuePairs[i];
|
|
691
|
+
const [key, value] = pair.trim().split(EQUAL);
|
|
692
|
+
if (i == 0) {
|
|
693
|
+
params.name = key;
|
|
694
|
+
params.value = value;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (key.toLowerCase() === SECURE) {
|
|
698
|
+
params.isSecure = true;
|
|
699
|
+
}
|
|
700
|
+
else if (key.toLowerCase() === HTTPONLY) {
|
|
701
|
+
params.isHttpOnly = true;
|
|
702
|
+
}
|
|
703
|
+
else if (key.toLowerCase() === SAMESITE && value.toLowerCase() === STRICT) {
|
|
704
|
+
params.isSameSiteStrict = true;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
parameters.push(params);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const traceObject = secUtils.getTraceObject(shim);
|
|
711
|
+
const secMetadata = securityMetaData.getSecurityMetaData(csec_request, parameters, traceObject, secUtils.getExecutionId(), EVENT_TYPE.SECURE_COOKIE, EVENT_CATEGORY.SECURE_COOKIE)
|
|
712
|
+
const secEvent = API.generateSecEvent(secMetadata);
|
|
713
|
+
API.sendEvent(secEvent);
|
|
714
|
+
} catch (error) {
|
|
715
|
+
logger.debug("Error while generating secure cookie event:", error);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function truncateStringToBytes(inputString, maxBytes) {
|
|
721
|
+
if (!inputString || maxBytes <= 0) {
|
|
722
|
+
return EMPTY_STRING; // Return an empty string if input is invalid
|
|
723
|
+
}
|
|
724
|
+
const buffer = Buffer.from(inputString, UTF8);
|
|
725
|
+
if (buffer.length <= maxBytes) {
|
|
726
|
+
return inputString;
|
|
727
|
+
}
|
|
728
|
+
const truncatedBuffer = buffer.slice(0, maxBytes);
|
|
729
|
+
const truncatedString = truncatedBuffer.toString(UTF8);
|
|
730
|
+
return truncatedString;
|
|
731
|
+
}
|
|
732
|
+
|
|
@@ -62,14 +62,14 @@ function paramHook(shim, mod, moduleName) {
|
|
|
62
62
|
shim.wrap(mod && mod.prototype, 'params', function wrapMatch(shim, fn) {
|
|
63
63
|
logger.debug(`Instrumenting ${moduleName}.prototype.params`);
|
|
64
64
|
return function wrappedMatch() {
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
let request = requestManager.getRequestFromId(
|
|
65
|
+
const transaction = shim.tracer.getTransaction();
|
|
66
|
+
if (transaction) {
|
|
67
|
+
let request = requestManager.getRequestFromId(transaction.id);
|
|
68
68
|
try {
|
|
69
69
|
const uri = this.path;
|
|
70
70
|
if (request) {
|
|
71
71
|
request.uri = uri;
|
|
72
|
-
requestManager.setRequest(
|
|
72
|
+
requestManager.setRequest(transaction.id, request);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
} catch (error) {
|
|
@@ -97,7 +97,7 @@ function paramHook(shim, mod, moduleName) {
|
|
|
97
97
|
}
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
-
requestManager.setRequest(
|
|
100
|
+
requestManager.setRequest(transaction.id, request);
|
|
101
101
|
} catch (error) {
|
|
102
102
|
|
|
103
103
|
}
|
|
@@ -19,6 +19,10 @@ const logger = API.getLogger();
|
|
|
19
19
|
* @param {*} moduleName
|
|
20
20
|
*/
|
|
21
21
|
function initialize(shim, mod, moduleName){
|
|
22
|
+
if(API && API.getNRAgent() && API.getNRAgent().config.security.exclude_from_iast_scan.iast_detection_category.ldap_injection){
|
|
23
|
+
logger.warn('ldap_injection detection is disabled');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
22
26
|
logger.info('Instrumenting '+ moduleName )
|
|
23
27
|
|
|
24
28
|
if(moduleName==='ldapjs' || moduleName==='ldapts'){
|
|
@@ -32,6 +32,10 @@ module.exports = initialize;
|
|
|
32
32
|
* @param {Shim} shim
|
|
33
33
|
*/
|
|
34
34
|
function initialize(shim, mongodb, moduleName) {
|
|
35
|
+
if(API && API.getNRAgent() && API.getNRAgent().config.security.exclude_from_iast_scan.iast_detection_category.nosql_injection){
|
|
36
|
+
logger.warn('nosql_injection detection is disabled');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
35
39
|
logger.info(`Instrumenting ${moduleName}`);
|
|
36
40
|
if (!mongodb) {
|
|
37
41
|
return;
|
|
@@ -29,6 +29,10 @@ module.exports = initialize;
|
|
|
29
29
|
*/
|
|
30
30
|
function initialize(shim, mysql, moduleName) {
|
|
31
31
|
modName = moduleName;
|
|
32
|
+
if(API && API.getNRAgent() && API.getNRAgent().config.security.exclude_from_iast_scan.iast_detection_category.sql_injection){
|
|
33
|
+
logger.warn('sql_injection detection is disabled');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
32
36
|
logger.info("Instrumenting", moduleName);
|
|
33
37
|
if (shim._moduleRoot.includes("mysql2")) {
|
|
34
38
|
promiseInitialize(shim, mysql, moduleName);
|
|
@@ -13,6 +13,10 @@ const { NR_CSEC_FUZZ_REQUEST_ID } = require('../../core/constants');
|
|
|
13
13
|
const logger = API.getLogger();
|
|
14
14
|
|
|
15
15
|
function initialize(shim, childProcess, moduleName) {
|
|
16
|
+
if(API && API.getNRAgent() && API.getNRAgent().config.security.exclude_from_iast_scan.iast_detection_category.command_injection){
|
|
17
|
+
logger.warn('command_injection detection is disabled');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
16
20
|
logger.info('Instrumenting child_process');
|
|
17
21
|
execFileHook(shim, childProcess, moduleName);
|
|
18
22
|
spawnHook(shim, childProcess, moduleName);
|
|
@@ -15,6 +15,10 @@ const logger = API.getLogger();
|
|
|
15
15
|
const { NR_CSEC_FUZZ_REQUEST_ID } = require('../../core/constants');
|
|
16
16
|
|
|
17
17
|
module.exports = function initialize(shim, pgsql, moduleName) {
|
|
18
|
+
if(API && API.getNRAgent() && API.getNRAgent().config.security.exclude_from_iast_scan.iast_detection_category.sql_injection){
|
|
19
|
+
logger.warn('sql_injection detection is disabled');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
18
22
|
logger.info('Instrumenting '+ moduleName)
|
|
19
23
|
// allows for native wrapping to not happen if not necessary
|
|
20
24
|
// when env var is true
|
|
@@ -66,17 +66,17 @@ function initialize(shim, mod, moduleName) {
|
|
|
66
66
|
* @param {*} req
|
|
67
67
|
*/
|
|
68
68
|
function extractParams(shim, req) {
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
69
|
+
const transaction = shim.tracer.getTransaction();
|
|
70
|
+
if (transaction) {
|
|
71
71
|
|
|
72
|
-
let request = requestManager.getRequestFromId(
|
|
72
|
+
let request = requestManager.getRequestFromId(transaction.id);
|
|
73
73
|
if (req.params && request) {
|
|
74
74
|
try {
|
|
75
75
|
Object.keys(req.params).forEach(function (key) {
|
|
76
76
|
if (req.params[key]) {
|
|
77
77
|
if (!request.parameterMap[key]) {
|
|
78
78
|
request.parameterMap[key] = new Array(req.params[key].toString());
|
|
79
|
-
requestManager.setRequest(
|
|
79
|
+
requestManager.setRequest(transaction.id, request);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
});
|
|
@@ -91,7 +91,7 @@ function extractParams(shim, req) {
|
|
|
91
91
|
Object.keys(query).forEach(function (key) {
|
|
92
92
|
if (query[key]) {
|
|
93
93
|
request.parameterMap[key] = new Array(query[key].toString());
|
|
94
|
-
requestManager.setRequest(
|
|
94
|
+
requestManager.setRequest(transaction.id, request);
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
} catch (error) {
|
|
@@ -100,7 +100,7 @@ function extractParams(shim, req) {
|
|
|
100
100
|
}
|
|
101
101
|
if (req.route && req.route.path && request) {
|
|
102
102
|
request.uri = req.route.path;
|
|
103
|
-
requestManager.setRequest(
|
|
103
|
+
requestManager.setRequest(transaction.id, request);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
-
}
|
|
106
|
+
}
|
|
@@ -24,6 +24,10 @@ const url = require('url');
|
|
|
24
24
|
* @param {*} moduleName
|
|
25
25
|
*/
|
|
26
26
|
function initialize(shim, mod, moduleName) {
|
|
27
|
+
if(API && API.getNRAgent() && API.getNRAgent().config.security.exclude_from_iast_scan.iast_detection_category.ssrf){
|
|
28
|
+
logger.warn('ssrf detection is disabled');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
27
31
|
logger.info('Instrumenting ' + moduleName);
|
|
28
32
|
fetchHook(shim, mod, "fetch");
|
|
29
33
|
let util = shim.require('./lib/core/util');
|
|
@@ -19,6 +19,10 @@ const logger = API.getLogger();
|
|
|
19
19
|
* @param {*} moduleName
|
|
20
20
|
*/
|
|
21
21
|
function initialize(shim, mod, moduleName){
|
|
22
|
+
if(API && API.getNRAgent() && API.getNRAgent().config.security.exclude_from_iast_scan.iast_detection_category.xpath_injection){
|
|
23
|
+
logger.warn('xpath_injection detection is disabled');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
22
26
|
logger.info('Instrumenting '+ moduleName)
|
|
23
27
|
|
|
24
28
|
if(moduleName==='xpath'){
|
|
@@ -17,7 +17,7 @@ const njsAgentConstants = require('./lib/core/sec-agent-constants');
|
|
|
17
17
|
const NRLogger = API.newrelic.shim.logger.child({ component: 'security-agent' });
|
|
18
18
|
NRLogger.info("Starting New Relic Node.js Security Agent ");
|
|
19
19
|
try {
|
|
20
|
-
if (NRAgent.config.security.enabled){
|
|
20
|
+
if (NRAgent.config.security.enabled) {
|
|
21
21
|
require('../instrumentation-security');
|
|
22
22
|
}
|
|
23
23
|
} catch (error) {
|
|
@@ -117,11 +117,44 @@ function initialize() {
|
|
|
117
117
|
if (NRAgent) {
|
|
118
118
|
NRAgent.on('started', () => {
|
|
119
119
|
NRLogger.info("Started New Relic Node.js Security Agent");
|
|
120
|
+
logger.info('NR Agent started with agent_run_id:', NRAgent.config.run_id, NRAgent.config.account_id);
|
|
121
|
+
if (Agent.getAgent().status.getStatus() != 'connecting') {
|
|
122
|
+
Agent.getAgent().delayed = false;
|
|
123
|
+
wsClient.obeyReconnect();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
120
126
|
if (NRAgent.config.security.enabled || !NRAgent.config.high_security) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
let delayFromConfig = parseInt(NRAgent.config.security.scan_schedule.delay);
|
|
128
|
+
let delay = delayFromConfig;
|
|
129
|
+
if (isNaN(delay) || delay < 0) {
|
|
130
|
+
delay = 0;
|
|
131
|
+
}
|
|
132
|
+
NRAgent.config.security.scan_schedule.delay = delay;
|
|
133
|
+
const allowSampling = NRAgent.config.security.scan_schedule.always_sample_traces;
|
|
134
|
+
|
|
135
|
+
if (delay >= 0) {
|
|
136
|
+
logger.debug("IAST delay is set to:", delay);
|
|
137
|
+
logger.info("Security Agent delay scan time is set to:", commonUtils.getScheduledScanTime(delay))
|
|
138
|
+
Agent.getAgent().delayed = true;
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
Agent.getAgent().delayed = false;
|
|
141
|
+
logger.info("IAST scanning delay of %s minutes over", delay);
|
|
142
|
+
commonUtils.honourDurationConfiguration();
|
|
143
|
+
commonUtils.executeCronSchedule();
|
|
144
|
+
if (!allowSampling) {
|
|
145
|
+
wsClient.initialize();
|
|
146
|
+
Agent.getAgent().status.setStatus('connecting');
|
|
147
|
+
Agent.setNRAgent(NRAgent);
|
|
148
|
+
}
|
|
149
|
+
}, delay * 60000);
|
|
150
|
+
}
|
|
151
|
+
logger.info("Security Agent is running with config:", JSON.stringify(NRAgent.config.security));
|
|
152
|
+
if (allowSampling) {
|
|
153
|
+
wsClient.initialize();
|
|
154
|
+
Agent.getAgent().status.setStatus('connecting');
|
|
155
|
+
Agent.setNRAgent(NRAgent);
|
|
156
|
+
}
|
|
157
|
+
|
|
125
158
|
}
|
|
126
159
|
else {
|
|
127
160
|
logger.warn("security.enabled flag is set to false");
|
|
@@ -11,6 +11,7 @@ const logger = logs.getLogger();
|
|
|
11
11
|
const initLogger = logs.getInitLogger();
|
|
12
12
|
const LinkingMetaData = require('./LinkingMetadata');
|
|
13
13
|
const LogMessage = require('./LogMessage');
|
|
14
|
+
const lodash = require('lodash');
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
/* eslint-disable camelcase */
|
|
@@ -37,12 +38,29 @@ function AuthHeaders() {
|
|
|
37
38
|
authHeaders["NR-CSEC-IAST-DATA-TRANSFER-MODE"] = "PULL";
|
|
38
39
|
authHeaders['NR-CSEC-ENTITY-NAME'] = metadata['entity.name'] ? metadata['entity.name'] : '';
|
|
39
40
|
authHeaders['NR-CSEC-ENTITY-GUID'] = metadata['entity.guid'] ? metadata['entity.guid'] : '';
|
|
41
|
+
let instanceCount = parseInt(NRAgent.config.security.scan_controllers.scan_instance_count);
|
|
42
|
+
if (isNaN(instanceCount)) {
|
|
43
|
+
instanceCount = 0;
|
|
44
|
+
}
|
|
45
|
+
if (instanceCount < 0 || instanceCount > 1) {
|
|
46
|
+
instanceCount = 0;
|
|
47
|
+
}
|
|
48
|
+
authHeaders['NR-CSEC-IAST-SCAN-INSTANCE-COUNT'] = instanceCount ? instanceCount : 0
|
|
49
|
+
if ((NRAgent.config.security.iast_test_identifier && !lodash.isEmpty(NRAgent.config.security.iast_test_identifier)) || process.env.NEW_RELIC_SECURITY_IAST_TEST_IDENTIFIER) {
|
|
50
|
+
let iast_test_identifier = NRAgent.config.security.iast_test_identifier || process.env.NEW_RELIC_SECURITY_IAST_TEST_IDENTIFIER
|
|
51
|
+
authHeaders['NR-CSEC-IAST-TEST-IDENTIFIER'] = iast_test_identifier;
|
|
52
|
+
logger.debug("iast_test_identifier is:", iast_test_identifier);
|
|
53
|
+
authHeaders['NR-CSEC-IAST-SCAN-INSTANCE-COUNT'] = 1;
|
|
54
|
+
}
|
|
55
|
+
authHeaders['NR-CSEC-PROCESS-START-TIME'] = Date.now() - Math.floor(process.uptime() * 1000);
|
|
56
|
+
authHeaders['NR-CSEC-IGNORED-VUL-CATEGORIES'] = commonUtils.listSkipDetectionCategory();
|
|
40
57
|
} catch (error) {
|
|
41
58
|
logger.error("Exception in preparing auth headers:", error);
|
|
42
59
|
const logMessage = new LogMessage.logMessage("SEVERE", 'Exception in preparing auth headers', __filename, error);
|
|
43
60
|
commonUtils.addLogEventtoBuffer(logMessage);
|
|
44
61
|
}
|
|
45
62
|
|
|
63
|
+
|
|
46
64
|
return authHeaders;
|
|
47
65
|
}
|
|
48
66
|
AuthHeaders.prototype.constructor = AuthHeaders;
|
|
@@ -75,6 +93,8 @@ function getInstance() {
|
|
|
75
93
|
return authHeaders;
|
|
76
94
|
}
|
|
77
95
|
|
|
96
|
+
|
|
97
|
+
|
|
78
98
|
module.exports = {
|
|
79
99
|
getInstance
|
|
80
100
|
};
|
|
@@ -89,6 +89,7 @@ function exceptionReporting(exception, request, responseCode) {
|
|
|
89
89
|
this.timestamp = Date.now();
|
|
90
90
|
this.httpRequest = request;
|
|
91
91
|
this.httpRequest.route = request.uri ? request.uri : request.url;
|
|
92
|
+
this.httpRequest.requestURI = request.url.split('?')[0];
|
|
92
93
|
this.exception = null;
|
|
93
94
|
this.counter = 1;
|
|
94
95
|
this.responseCode = responseCode ? responseCode : null
|
|
@@ -17,7 +17,6 @@ const logger = logs.getLogger();
|
|
|
17
17
|
const initLogger = logs.getInitLogger();
|
|
18
18
|
const jsonValidator = require('./jsonValidator');
|
|
19
19
|
const LOG_MESSAGES = require('./sec-agent-constants').LOG_MESSAGES;
|
|
20
|
-
const statusUtils = require('./statusUtils');
|
|
21
20
|
|
|
22
21
|
function Policy() {
|
|
23
22
|
this.data = {};
|
|
@@ -72,7 +71,6 @@ function setDefaultPolicy() {
|
|
|
72
71
|
logger.info(LOG_MESSAGES.DEFAULT_POL_SET, JSON.stringify(policy.data));
|
|
73
72
|
} catch (e) {
|
|
74
73
|
logger.error(LOG_MESSAGES.UNABLE_SET_DEFAULT_POL, e);
|
|
75
|
-
statusUtils.addErrortoBuffer(e);
|
|
76
74
|
};
|
|
77
75
|
}
|
|
78
76
|
|
|
@@ -90,16 +88,6 @@ function appInfoVersionUpdate(policy, data) {
|
|
|
90
88
|
}
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
/**
|
|
94
|
-
* Function to send updated policy to validator
|
|
95
|
-
* @param {*} data
|
|
96
|
-
*/
|
|
97
|
-
function sendUpdatedPolicy(data) {
|
|
98
|
-
const wsClient = Agent.getAgent().client;
|
|
99
|
-
let updatedPolicy = Object.assign({}, data);
|
|
100
|
-
updatedPolicy.jsonName = 'lc-policy';
|
|
101
|
-
wsClient.dispatcher(updatedPolicy);
|
|
102
|
-
}
|
|
103
91
|
module.exports = {
|
|
104
92
|
getInstance,
|
|
105
93
|
setPolicyData,
|
|
@@ -42,6 +42,7 @@ function Agent(applicationInfo, logger, client, status) {
|
|
|
42
42
|
this.logger = logger;
|
|
43
43
|
this.status = status;
|
|
44
44
|
this.wsStatus = 0;
|
|
45
|
+
this.delayed = false;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
Agent.prototype = {};
|
|
@@ -96,6 +97,7 @@ Agent.prototype.setAgentStatus = function setAgentStatus(status) {
|
|
|
96
97
|
Agent.eventEmitter.emit(EVENTS.AGENT_STATUS_UPDATED_EVENT, status);
|
|
97
98
|
};
|
|
98
99
|
|
|
100
|
+
|
|
99
101
|
Agent.EVENTS = EVENTS;
|
|
100
102
|
|
|
101
103
|
Agent.eventEmitter = new EventEmitter();
|
|
@@ -130,13 +132,15 @@ Agent.setNRAgent = function setNRAgent(agent) {
|
|
|
130
132
|
}
|
|
131
133
|
const CSECAgent = Agent.getAgent();
|
|
132
134
|
if (CSECAgent) {
|
|
133
|
-
CSECAgent.applicationInfo.
|
|
135
|
+
CSECAgent.applicationInfo.appEntityGuid = NRAgent.config.entity_guid;
|
|
136
|
+
CSECAgent.applicationInfo.appAccountId = NRAgent.config.account_id;
|
|
134
137
|
CSECAgent.applicationInfo.linkingMetadata = LinkingMetaData.getLinkingMetadata();
|
|
135
138
|
CSECAgent.applicationInfo.linkingMetadata.agentRunId = NRAgent.config.run_id;
|
|
136
139
|
CSECAgent.setApplicationInfo(CSECAgent.applicationInfo);
|
|
137
140
|
}
|
|
138
141
|
};
|
|
139
142
|
|
|
143
|
+
|
|
140
144
|
module.exports = {
|
|
141
145
|
Agent
|
|
142
146
|
};
|
|
@@ -90,10 +90,10 @@ function ApplicationInfo () {
|
|
|
90
90
|
const k8sHost = isK8S();
|
|
91
91
|
const ECSEnv = isECSEnv();
|
|
92
92
|
const isLambda = IS_LAMBDA_ENV;
|
|
93
|
-
|
|
94
93
|
this.jsonName = APPLICATION_INFO_KEYWORD;
|
|
95
94
|
if (NRAgent && NRAgent.config) {
|
|
96
|
-
this.
|
|
95
|
+
this.appEntityGuid = NRAgent.config.entity_guid;
|
|
96
|
+
this.appAccountId = NRAgent.config.account_id;
|
|
97
97
|
}
|
|
98
98
|
this.eventType = 'sec_appinfo';
|
|
99
99
|
this.startTime = Date.now();
|