@newrelic/security-agent 1.1.1 → 1.3.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 +24 -0
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +211 -1
- package/lib/instrumentation-security/core/constants.js +1 -0
- package/lib/instrumentation-security/core/grpc-utils.js +53 -0
- package/lib/instrumentation-security/core/sec-utils.js +99 -57
- package/lib/instrumentation-security/core/security-metadata.js +12 -2
- package/lib/instrumentation-security/hooks/express/nr-express.js +43 -3
- package/lib/instrumentation-security/hooks/fs/nr-fs.js +4 -4
- package/lib/instrumentation-security/hooks/grpc-js/nr-grpc.js +301 -0
- package/lib/instrumentation-security/hooks/http/nr-http.js +34 -23
- package/lib/instrumentation-security/index.js +32 -3
- package/lib/nr-security-agent/index.js +7 -1
- package/lib/nr-security-agent/lib/core/Policy.js +0 -1
- package/lib/nr-security-agent/lib/core/commonUtils.js +19 -4
- package/lib/nr-security-agent/lib/core/connections/grpcClient.js +112 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/IASTUtils.js +3 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js +32 -12
- package/lib/nr-security-agent/lib/core/connections/websocket/response/startup-handshake-handler.js +1 -1
- package/lib/nr-security-agent/lib/core/connections/websocket/websocket.js +9 -4
- package/lib/nr-security-agent/lib/core/sec-agent-constants.js +2 -1
- package/lib/nr-security-agent/lib/core/sec-util.js +3 -1
- package/lib/nr-security-agent/lib/core/statusUtils.js +7 -6
- package/lib/nr-security-agent/lib/core/websocket-client.js +1 -1
- package/lib/nr-security-agent/lib/core/xss-utils.js +11 -5
- package/package.json +3 -2
package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const stringify = require('fast-safe-stringify');
|
|
8
8
|
|
|
9
9
|
const restClient = require('../../restclient');
|
|
10
|
+
const grpcClient = require('../../grpcClient');
|
|
10
11
|
const { Agent } = require('../../../agent');
|
|
11
12
|
const { FuzzFailEvent } = require('../../../FuzzFailEvent');
|
|
12
13
|
const { CSEC_HOME_TMP, COLON, NR_CSEC_FUZZ_REQUEST_ID, SLASH, CSEC_SEP, SEVERE } = require('../../../sec-agent-constants');
|
|
@@ -20,9 +21,6 @@ const LogMessage = require('../../../LogMessage');
|
|
|
20
21
|
require('dns').setDefaultResultOrder('ipv4first')
|
|
21
22
|
const PolicyManager = require('../../../Policy');
|
|
22
23
|
|
|
23
|
-
const {
|
|
24
|
-
IS_LAMBDA_ENV,
|
|
25
|
-
AWS_LAMBDA_FUNCTION_VERSION_ENV_IDENTIFIER } = require('../../../sec-agent-constants');
|
|
26
24
|
const statusUtils = require('../../../statusUtils');
|
|
27
25
|
|
|
28
26
|
const find = `${SLASH}{{NR_CSEC_VALIDATOR_HOME_TMP}}`;
|
|
@@ -53,7 +51,7 @@ function startIASTSchedular() {
|
|
|
53
51
|
if (isNaN(probingInterval)) {
|
|
54
52
|
probingInterval = 5;
|
|
55
53
|
}
|
|
56
|
-
|
|
54
|
+
|
|
57
55
|
iastIntervalConst = setInterval(() => {
|
|
58
56
|
let data = IASTUtil.generateIASTDataRequest();
|
|
59
57
|
let currentTime = Date.now();
|
|
@@ -125,13 +123,16 @@ function handler(json) {
|
|
|
125
123
|
function handleFuzzRequest(fuzzDetails) {
|
|
126
124
|
const { fuzzRequest } = fuzzDetails;
|
|
127
125
|
try {
|
|
128
|
-
if (
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
if (fuzzRequest.protocol == 'grpc') {
|
|
127
|
+
const config = parseGRPCRequestToFuzz(fuzzRequest);
|
|
128
|
+
config.headers['nr-csec-parent-id'] = fuzzRequest.id;
|
|
129
|
+
IASTUtil.completedRequestsMapInit(fuzzRequest.id);
|
|
130
|
+
grpcClient.fireRequest(config);
|
|
131
|
+
if (fuzzRequest.headers && fuzzRequest.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
|
|
132
|
+
logScannedApiId(fuzzRequest.headers[NR_CSEC_FUZZ_REQUEST_ID], fuzzRequest.requestURI)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
IASTUtil.removePendingRequestId(fuzzRequest.id);
|
|
135
136
|
|
|
136
137
|
} else {
|
|
137
138
|
const config = parseAxiosHttpRequestToFuzz(fuzzRequest);
|
|
@@ -153,7 +154,6 @@ function handleFuzzRequest(fuzzDetails) {
|
|
|
153
154
|
|
|
154
155
|
|
|
155
156
|
logger.info('Firing http request:: URL: ' + config.url);
|
|
156
|
-
|
|
157
157
|
const response = restClient.fireRequest(config);
|
|
158
158
|
handleFuzzResponse(response, fuzzDetails);
|
|
159
159
|
}
|
|
@@ -210,6 +210,26 @@ function parseAxiosHttpRequestToFuzz(requestObject) {
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Parses the passed fuzz request object to
|
|
215
|
+
* Axios config.
|
|
216
|
+
*
|
|
217
|
+
* @param {JSON} requestObject
|
|
218
|
+
*/
|
|
219
|
+
function parseGRPCRequestToFuzz(requestObject) {
|
|
220
|
+
let serverName = requestObject.serverName ? requestObject.serverName : LOCALHOST;
|
|
221
|
+
let host = serverName + COLON + requestObject.serverPort
|
|
222
|
+
return {
|
|
223
|
+
url: requestObject.protocol + COLON_SLASH_SLASH + host + requestObject.url,
|
|
224
|
+
requestURI: requestObject.requestURI,
|
|
225
|
+
protocol: requestObject.protocol,
|
|
226
|
+
serverPort: requestObject.serverPort,
|
|
227
|
+
method: requestObject.method,
|
|
228
|
+
data: requestObject.body,
|
|
229
|
+
headers: requestObject.headers,
|
|
230
|
+
timeout: 5000,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
213
233
|
|
|
214
234
|
|
|
215
235
|
/**
|
package/lib/nr-security-agent/lib/core/connections/websocket/response/startup-handshake-handler.js
CHANGED
|
@@ -18,7 +18,7 @@ module.exports.handler = function handler(json) {
|
|
|
18
18
|
|
|
19
19
|
initLogger.info(LOG_MESSAGES.AGENT_INIT_WITH_PROP, JSON.stringify(startupConfig.config));
|
|
20
20
|
initLogger.info('Security Agent is now ACTIVE for', applicationInfoModule.getInstance().applicationUUID);
|
|
21
|
-
const logMessage = new LogMessage.logMessage("
|
|
21
|
+
const logMessage = new LogMessage.logMessage("INFO", `Security Agent is ACTIVE for ${ applicationInfoModule.getInstance().applicationUUID}`, __filename, null);
|
|
22
22
|
require('../../../commonUtils').addLogEventtoBuffer(logMessage);
|
|
23
23
|
|
|
24
24
|
} catch (error) {
|
|
@@ -104,6 +104,7 @@ SecWebSocket.prototype.init = function init() {
|
|
|
104
104
|
const validatorService = commonUtils.getValidatorServiceEndpointURL();
|
|
105
105
|
const authHeaders = require('../../Auth-headers').getInstance();
|
|
106
106
|
initLogger.info(`Connecting to Validator at ${validatorService}`);
|
|
107
|
+
IASTUtil.IASTCleanup();
|
|
107
108
|
let cert = ''
|
|
108
109
|
try {
|
|
109
110
|
const certPath = commonUtils.getPathOfCACert();
|
|
@@ -138,6 +139,7 @@ SecWebSocket.prototype.init = function init() {
|
|
|
138
139
|
})
|
|
139
140
|
webSocket.on('close', () => {
|
|
140
141
|
logger.warn("WS connection closed");
|
|
142
|
+
this.obeyReconnect();
|
|
141
143
|
})
|
|
142
144
|
this.instance = webSocket;
|
|
143
145
|
};
|
|
@@ -172,7 +174,7 @@ SecWebSocket.prototype.obeyReconnect = function obeyReconnect() {
|
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
initTimeOut = setTimeout(() => {
|
|
175
|
-
|
|
177
|
+
|
|
176
178
|
logger.debug("Terminating ws instance and reconnecting");
|
|
177
179
|
try {
|
|
178
180
|
instance.terminate();
|
|
@@ -189,18 +191,21 @@ SecWebSocket.prototype.pingWS = async function pingWS() {
|
|
|
189
191
|
let currentTime = Date.now();
|
|
190
192
|
|
|
191
193
|
let timeDiffInPing = (currentTime - lastPongtime) / 1000;
|
|
192
|
-
|
|
194
|
+
logger.debug("Websocket ready state is:",self.instance.readyState)
|
|
195
|
+
|
|
196
|
+
if (timeDiffInPing > 40 && lastPongtime != 0 && self.instance.readyState > 1) {
|
|
193
197
|
self.obeyReconnect();
|
|
194
198
|
lastPongtime = 0;
|
|
195
199
|
return;
|
|
196
200
|
}
|
|
201
|
+
|
|
197
202
|
try {
|
|
198
203
|
await promisify(self.instance, self.instance.ping)(ACK, true, function (err) {
|
|
199
204
|
if (err) {
|
|
200
205
|
logger.debug("Error while pinging:", err);
|
|
201
206
|
commonUtils.setWSHealthStatus('Error');
|
|
202
207
|
statusUtils.addErrortoBuffer(err);
|
|
203
|
-
|
|
208
|
+
|
|
204
209
|
}
|
|
205
210
|
});
|
|
206
211
|
} catch (err) {
|
|
@@ -258,7 +263,7 @@ SecWebSocket.prototype.dispatch = async function dispatch(event) {
|
|
|
258
263
|
hc.getInstance().exitEventStats.sent++;
|
|
259
264
|
}
|
|
260
265
|
|
|
261
|
-
})
|
|
266
|
+
})
|
|
262
267
|
} catch (error) {
|
|
263
268
|
logger.debug(LOG_MESSAGES.ERROR_WHILE_SEND_EVENT, eventStr, error);
|
|
264
269
|
statusUtils.addErrortoBuffer(error);
|
|
@@ -111,6 +111,7 @@ module.exports = {
|
|
|
111
111
|
SEVERE: 'SEVERE',
|
|
112
112
|
HIGH: 'HIGH',
|
|
113
113
|
CRITICAL: 'CRITICAL',
|
|
114
|
+
HYPHEN: '-',
|
|
114
115
|
|
|
115
116
|
LOG_MESSAGES: {
|
|
116
117
|
LOADED_CSEC_ENVS_MSG: '[STEP-1][COMPLETE][env] Environment Information Gathering Done.',
|
|
@@ -165,7 +166,7 @@ module.exports = {
|
|
|
165
166
|
ERROR_WHILE_SEND_EVENT: 'ERROR While Sending Event: {}',
|
|
166
167
|
WS_CONNECTED: '[STEP-4][COMPLETE][WS] Connected To Prevent-web Service',
|
|
167
168
|
SENDING_APPINFO: '[APP_INFO] Sending Application Info To Prevent-web Service: ',
|
|
168
|
-
SENDING_APPINFO_COMPLETE: '[COMPLETE][APP_INFO] Application info sent to Prevent-Web service :',
|
|
169
|
+
SENDING_APPINFO_COMPLETE: '[STEP-3][COMPLETE][APP_INFO] Application info sent to Prevent-Web service :',
|
|
169
170
|
APPLY_INSTRUMENTATION: '[STEP-6][BEGIN][instrumentation] Applying Instrumentation',
|
|
170
171
|
AVAIL_DISK: 'Available Disk Space Is: ',
|
|
171
172
|
AVAIL_DISK_ERROR: 'Insufficient Disk Space Available To The Location %s Is : %s',
|
|
@@ -9,7 +9,7 @@ const { SecEvent } = require('./event');
|
|
|
9
9
|
const logs = require('./logging');
|
|
10
10
|
const { Agent } = require('./agent');
|
|
11
11
|
const shaUtil = require('./sha-size-util');
|
|
12
|
-
const { LOG_MESSAGES, NR_CSEC_FUZZ_REQUEST_ID, COLON, VULNERABLE, EXITEVENT, EMPTY_STR, RASP, SEVERE } = require('./sec-agent-constants');
|
|
12
|
+
const { LOG_MESSAGES, NR_CSEC_FUZZ_REQUEST_ID, COLON, VULNERABLE, EXITEVENT, EMPTY_STR, RASP, SEVERE, HYPHEN } = require('./sec-agent-constants');
|
|
13
13
|
const API = require('../../../nr-security-api');
|
|
14
14
|
const NRAgent = API.getNRAgent();
|
|
15
15
|
const HC = require('./health-check');
|
|
@@ -83,6 +83,7 @@ function generateSecEvent(securityMetadata) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
let apiId = shaUtil.getSHA256ForData(traceObject.stacktrace.join('|') + uri);
|
|
86
|
+
apiId = securityMetadata.eventType + HYPHEN + apiId;
|
|
86
87
|
const agentModule = Agent.getAgent();
|
|
87
88
|
const metaData = {};
|
|
88
89
|
if (traceObject.sourceDetails && traceObject.sourceDetails.evalObj) {
|
|
@@ -100,6 +101,7 @@ function generateSecEvent(securityMetadata) {
|
|
|
100
101
|
const isAPIBlocked = false;
|
|
101
102
|
const event = new SecEvent(traceObject.sourceDetails.fileName, traceObject.sourceDetails.funcName, traceObject.sourceDetails.lineNumber, securityMetadata.interceptedArgs, securityMetadata.executionId, agentModule.applicationInfo, securityMetadata.request, securityMetadata.eventType, traceObject.sourceDetails.source, securityMetadata.eventCategory, metaData, stakTrace, apiId, isAPIBlocked);
|
|
102
103
|
event.metaData.isClientDetectedFromXFF = isClientDetectedFromXFF;
|
|
104
|
+
event.httpRequest.route = uri;
|
|
103
105
|
return event;
|
|
104
106
|
} catch (e) {
|
|
105
107
|
statusUtils.addErrortoBuffer(e);
|
|
@@ -23,10 +23,11 @@ const logs = require('./logging');
|
|
|
23
23
|
const commonUtils = require('./commonUtils');
|
|
24
24
|
const logger = logs.getLogger();
|
|
25
25
|
const statusFile = njsAgentConstants.STATUS_LOG_FILE;
|
|
26
|
+
const sep = require('path').sep;
|
|
26
27
|
|
|
27
28
|
const statusTemplate = 'Snapshot timestamp: : %s\n' +
|
|
28
29
|
`CSEC Agent start timestamp: ${njsAgentConstants.AGENT_START_TIME} with application uuid:${commonUtils.getUUID()}\n` +
|
|
29
|
-
`
|
|
30
|
+
`CSEC_HOME: ${njsAgentConstants.CSEC_HOME}${sep}nr-security-home${sep}\n` +
|
|
30
31
|
`Agent location: ${AGENT_DIR}\n` +
|
|
31
32
|
`Using CSEC Agent for Node.js, Node version:${process.version}, PID:${process.pid}\n` +
|
|
32
33
|
`Process title: ${process.title}\n` +
|
|
@@ -35,10 +36,10 @@ const statusTemplate = 'Snapshot timestamp: : %s\n' +
|
|
|
35
36
|
`Current working directory: ${process.cwd()}\n` +
|
|
36
37
|
`Agent mode: ${commonUtils.getCSECmode()}\n` +
|
|
37
38
|
'Application server: %s\n' +
|
|
38
|
-
|
|
39
|
+
`Application Framework: %s\n` +
|
|
39
40
|
`Websocket connection to Prevent Web: ${commonUtils.getValidatorServiceEndpointURL()}, Status: %s\n` +
|
|
40
|
-
'Instrumentation successful
|
|
41
|
-
'Tracking loaded modules in the application
|
|
41
|
+
'Instrumentation successful\n' +
|
|
42
|
+
'Tracking loaded modules in the application\n' +
|
|
42
43
|
'Policy applied successfully. Policy version is: %s\n' +
|
|
43
44
|
'Started Health Check for Agent\n' +
|
|
44
45
|
'Started Inbound and Outbound monitoring \n' +
|
|
@@ -83,7 +84,7 @@ function getFormattedData() {
|
|
|
83
84
|
const appInfo = Agent.applicationInfo;
|
|
84
85
|
const deployedApplications = appInfo && appInfo.serverInfo && appInfo.serverInfo.deployedApplications;
|
|
85
86
|
const appLoc = deployedApplications[0].deployedPath;
|
|
86
|
-
const formattedSnapshot = util.format(statusTemplate, new Date().toString(), appLoc, appInfo.serverInfo.name,
|
|
87
|
+
const formattedSnapshot = util.format(statusTemplate, new Date().toString(), appLoc, appInfo.serverInfo.name, commonUtils.getFramework(), commonUtils.getWSHealthStatus(), appInfo.policyVersion, getKeyValPairs(lastHC.stats), getKeyValPairs(lastHC.serviceStatus), JSON.stringify(getBufferedErrors()), JSON.stringify(getBufferedHC()));
|
|
87
88
|
return formattedSnapshot;
|
|
88
89
|
}
|
|
89
90
|
|
|
@@ -97,7 +98,7 @@ function writeSnapshot() {
|
|
|
97
98
|
if (err) {
|
|
98
99
|
logger.debug(err.message);
|
|
99
100
|
const LogMessage = require('./LogMessage');
|
|
100
|
-
const logMessage = new LogMessage.logMessage("
|
|
101
|
+
const logMessage = new LogMessage.logMessage("DEBUG", 'Error in creating snapshot file', __filename, err);
|
|
101
102
|
commonUtils.addLogEventtoBuffer(logMessage);
|
|
102
103
|
} else {
|
|
103
104
|
logger.info('Snapshot updated to file: %s', statusFile);
|
|
@@ -82,7 +82,7 @@ const initialize = function () {
|
|
|
82
82
|
try {
|
|
83
83
|
const framework = NRAgent.environment.get('Framework')[0];
|
|
84
84
|
const dispatcher = NRAgent.environment.get('Dispatcher')[0];
|
|
85
|
-
const logMessage = new LogMessage.logMessage("
|
|
85
|
+
const logMessage = new LogMessage.logMessage("INFO", `Detected framework: ${framework} and dispatcher: ${dispatcher}`, __filename, null);
|
|
86
86
|
commonUtils.addLogEventtoBuffer(logMessage);
|
|
87
87
|
} catch (error) {
|
|
88
88
|
logger.debug("Error while extracting data from APM environment",error);
|
|
@@ -10,6 +10,7 @@ const querystring = require('querystring');
|
|
|
10
10
|
const { Agent } = require('./agent');
|
|
11
11
|
|
|
12
12
|
const htmlEntities = require('html-entities');
|
|
13
|
+
const { STRING } = require('./sec-agent-constants');
|
|
13
14
|
|
|
14
15
|
const tagNameRegex = /<([a-zA-Z_-]+[0-9]*|!--)/gmi;
|
|
15
16
|
const attribRegex = /([^(/\s<'">)]+?)(?:\s*)=\s*(('|")([\s\S]*?)(?:(?=(\\?))\5.)*?\3|.+?(?=\/>|>|\?>|\s|<\/|$))/gmi;
|
|
@@ -136,10 +137,15 @@ function decodeResponseData (responseBody, responseHeaders) {
|
|
|
136
137
|
|
|
137
138
|
function isXSS (combinedData) {
|
|
138
139
|
const attackConstructs = new Set();
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
140
|
+
try {
|
|
141
|
+
for (const data of combinedData) {
|
|
142
|
+
const constructs = getXSSConstructs(data);
|
|
143
|
+
constructs.forEach(attackConstructs.add, attackConstructs);
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
logger.debug("Error while getting rxss constucts:", error);
|
|
142
147
|
}
|
|
148
|
+
|
|
143
149
|
return attackConstructs;
|
|
144
150
|
}
|
|
145
151
|
|
|
@@ -152,7 +158,7 @@ function getXSSConstructs (data) {
|
|
|
152
158
|
let tmpStartPos = 0;
|
|
153
159
|
let matcher;
|
|
154
160
|
let attribMatcher;
|
|
155
|
-
while (currPos < data.length) {
|
|
161
|
+
while (currPos < data.length && typeof data == STRING ) {
|
|
156
162
|
let tagName;
|
|
157
163
|
|
|
158
164
|
matcher = tagNameRegex.exec(data);
|
|
@@ -233,7 +239,7 @@ function getXSSConstructs (data) {
|
|
|
233
239
|
currPos = tmpCurrPos;
|
|
234
240
|
}
|
|
235
241
|
|
|
236
|
-
if (data.charAt(currPos) !== ANGLE_END_CHAR) {
|
|
242
|
+
if (typeof data == STRING && data.charAt(currPos) !== ANGLE_END_CHAR) {
|
|
237
243
|
const tmp = data.indexOf(ANGLE_END, currPos);
|
|
238
244
|
if (tmp !== -1) {
|
|
239
245
|
currPos = tmp;
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/security-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "New Relic Security Agent for Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
|
-
"jsonVersion": "1.
|
|
6
|
+
"jsonVersion": "1.2.0",
|
|
7
7
|
"contributors": [
|
|
8
8
|
{
|
|
9
9
|
"name": "Sumit Suthar",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"ws": "^8.14.2"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
+
"@grpc/proto-loader": "^0.7.10",
|
|
61
62
|
"@hapi/hapi": "^21.3.0",
|
|
62
63
|
"@koa/router": "^12.0.0",
|
|
63
64
|
"@newrelic/newrelic-oss-cli": "^0.1.2",
|