@newrelic/security-agent 0.0.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/CHANGELOG.md +0 -0
- package/CONTRIBUTING.md +23 -0
- package/LICENSE +43 -0
- package/README.md +98 -0
- package/THIRD_PARTY_NOTICES.md +2909 -0
- package/index.js +24 -0
- package/lib/instrumentation-security/core/constants.js +66 -0
- package/lib/instrumentation-security/core/event-constants.js +49 -0
- package/lib/instrumentation-security/core/request-manager.js +73 -0
- package/lib/instrumentation-security/core/route-manager.js +30 -0
- package/lib/instrumentation-security/core/sec-trace.js +134 -0
- package/lib/instrumentation-security/core/sec-utils.js +212 -0
- package/lib/instrumentation-security/core/security-metadata.js +34 -0
- package/lib/instrumentation-security/core/stack-trace.js +156 -0
- package/lib/instrumentation-security/hooks/director/nr-director.js +98 -0
- package/lib/instrumentation-security/hooks/express/nr-express.js +106 -0
- package/lib/instrumentation-security/hooks/fastify/nr-fastify.js +155 -0
- package/lib/instrumentation-security/hooks/fs/nr-fs.js +391 -0
- package/lib/instrumentation-security/hooks/hapi/nr-hapi.js +131 -0
- package/lib/instrumentation-security/hooks/http/nr-http.js +461 -0
- package/lib/instrumentation-security/hooks/koa/@koa/nr-@koa-router.js +110 -0
- package/lib/instrumentation-security/hooks/ldap/nr-ldap.js +81 -0
- package/lib/instrumentation-security/hooks/mongodb/common.js +239 -0
- package/lib/instrumentation-security/hooks/mongodb/constants.js +91 -0
- package/lib/instrumentation-security/hooks/mongodb/nr-mongodb.js +49 -0
- package/lib/instrumentation-security/hooks/mongodb/v2-mongo.js +91 -0
- package/lib/instrumentation-security/hooks/mongodb/v3-mongo.js +36 -0
- package/lib/instrumentation-security/hooks/mongodb/v4-mongo.js +17 -0
- package/lib/instrumentation-security/hooks/mysql/nr-mysql.js +232 -0
- package/lib/instrumentation-security/hooks/native/nr-childProcess.js +131 -0
- package/lib/instrumentation-security/hooks/postgres/nr-postgres.js +131 -0
- package/lib/instrumentation-security/hooks/require/nr-require.js +93 -0
- package/lib/instrumentation-security/hooks/restify/nr-restify.js +104 -0
- package/lib/instrumentation-security/hooks/xpath/nr-xpath.js +91 -0
- package/lib/instrumentation-security/index.js +171 -0
- package/lib/nr-security-agent/index.js +170 -0
- package/lib/nr-security-agent/lib/core/Auth-headers.js +58 -0
- package/lib/nr-security-agent/lib/core/ContainerProperties.js +42 -0
- package/lib/nr-security-agent/lib/core/ECSProperties.js +34 -0
- package/lib/nr-security-agent/lib/core/FuzzFailEvent.js +27 -0
- package/lib/nr-security-agent/lib/core/LinkingMetadata.js +21 -0
- package/lib/nr-security-agent/lib/core/LogMessage.js +47 -0
- package/lib/nr-security-agent/lib/core/PodProperties.js +36 -0
- package/lib/nr-security-agent/lib/core/Policy.js +105 -0
- package/lib/nr-security-agent/lib/core/agent-status.js +56 -0
- package/lib/nr-security-agent/lib/core/agent.js +146 -0
- package/lib/nr-security-agent/lib/core/applicationinfo.js +634 -0
- package/lib/nr-security-agent/lib/core/commonUtils.js +411 -0
- package/lib/nr-security-agent/lib/core/connections/aws-lambda-client.js +55 -0
- package/lib/nr-security-agent/lib/core/connections/restclient.js +20 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/index.js +25 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/collecotor-policy-error-handler.js +18 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/collector-policy-handler.js +20 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js +171 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/index.js +47 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/no-op-handler.js +12 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/startup-handshake-handler.js +27 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/unsupported-agent-handler.js +18 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/ws-reconnect.js +14 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/websocket.js +443 -0
- package/lib/nr-security-agent/lib/core/event.js +186 -0
- package/lib/nr-security-agent/lib/core/health-check.js +159 -0
- package/lib/nr-security-agent/lib/core/json-file-handler.js +66 -0
- package/lib/nr-security-agent/lib/core/jsonValidator.js +149 -0
- package/lib/nr-security-agent/lib/core/lambda-delegated-task-service.js +90 -0
- package/lib/nr-security-agent/lib/core/logging.js +176 -0
- package/lib/nr-security-agent/lib/core/sec-agent-constants.js +173 -0
- package/lib/nr-security-agent/lib/core/sec-util.js +166 -0
- package/lib/nr-security-agent/lib/core/sha-size-util.js +338 -0
- package/lib/nr-security-agent/lib/core/sha_worker.js +24 -0
- package/lib/nr-security-agent/lib/core/startup-config.js +29 -0
- package/lib/nr-security-agent/lib/core/statusUtils.js +157 -0
- package/lib/nr-security-agent/lib/core/timeunit.js +44 -0
- package/lib/nr-security-agent/lib/core/websocket-client.js +335 -0
- package/lib/nr-security-agent/lib/core/worker-entities.js +82 -0
- package/lib/nr-security-agent/lib/core/worker.js +148 -0
- package/lib/nr-security-agent/lib/core/xss-utils.js +297 -0
- package/lib/nr-security-agent/lib/models/lambda-delegated-task/constants.js +12 -0
- package/lib/nr-security-agent/lib/models/lambda-delegated-task/index.js +15 -0
- package/lib/nr-security-agent/lib/models/lambda-delegated-task/lambda-delegated-sha-calculation.js +37 -0
- package/lib/nr-security-agent/lib/models/lambda-delegated-task/lambda-delegated-task-base.js +50 -0
- package/lib/nr-security-agent/lib/models/lambda-delegated-task/lambda-delegated-task-type.js +17 -0
- package/lib/nr-security-agent/resources/cert.pem +31 -0
- package/lib/nr-security-agent/resources/config.js +19 -0
- package/lib/nr-security-agent/resources/default-policy.yaml +36 -0
- package/lib/nr-security-api/index.js +111 -0
- package/package.json +95 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = initialize
|
|
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 } = require('../../core/constants');
|
|
9
|
+
const ARRAY_TYPE = 'Array';
|
|
10
|
+
const STRING_TYPE = 'string';
|
|
11
|
+
const BUFFER_TYPE = 'Buffer';
|
|
12
|
+
const UINT8ARRAY_TYPE = 'Uint8Array';
|
|
13
|
+
const OBJECT_TYPE = 'object'
|
|
14
|
+
const unescapeJs = require('unescape-js');
|
|
15
|
+
const ConType = require('content-type');
|
|
16
|
+
const lodash = require('lodash');
|
|
17
|
+
const URL = require('url')
|
|
18
|
+
const secUtils = require('../../core/sec-utils');
|
|
19
|
+
const API = require("../../../nr-security-api");
|
|
20
|
+
const logger = API.getLogger();
|
|
21
|
+
const NRAgent = API.getNRAgent();
|
|
22
|
+
const securityMetaData = require('../../core/security-metadata');
|
|
23
|
+
const { EVENT_TYPE, EVENT_CATEGORY } = require('../../core/event-constants');
|
|
24
|
+
const hc = require('../../../nr-security-agent/lib/core/health-check');
|
|
25
|
+
|
|
26
|
+
const NRCSECTRACINGDATA = 'NR-CSEC-TRACING-DATA';
|
|
27
|
+
const SELFTEST = 'self-test';
|
|
28
|
+
|
|
29
|
+
const requestIp = require('request-ip');
|
|
30
|
+
const isInvalid = require('is-invalid-path');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const semver = require('semver');
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
const CSEC_SEP = ':IAST:';
|
|
37
|
+
const find = '{{NR_CSEC_VALIDATOR_HOME_TMP}}';
|
|
38
|
+
const CSEC_HOME_TMP_CONST = new RegExp(find, 'g');
|
|
39
|
+
const CSEC_HOME = NRAgent && NRAgent.config && NRAgent.config.newrelic_home ? NRAgent.config.newrelic_home : require('path').join(process.cwd());
|
|
40
|
+
const CSEC_HOME_TMP = `${CSEC_HOME}/nr-security-home/tmp/language-agent/${process.env.applicationUUID}`
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Entry point of http module hook
|
|
44
|
+
* @param {*} shim
|
|
45
|
+
* @param {*} mod
|
|
46
|
+
* @param {*} moduleName
|
|
47
|
+
*/
|
|
48
|
+
function initialize(shim, mod, moduleName) {
|
|
49
|
+
logger.info('Instrumenting HTTP module');
|
|
50
|
+
emitHook(shim, mod, moduleName);
|
|
51
|
+
createServerHook(shim, mod, moduleName);
|
|
52
|
+
outboundHook(shim, mod, 'request', moduleName);
|
|
53
|
+
if (semver.gte(process.version, '8.0.0')) {
|
|
54
|
+
outboundHook(shim, mod, 'get', moduleName);
|
|
55
|
+
}
|
|
56
|
+
setupRequestMapCleanup()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Adds a listener for when a transaction finishes
|
|
61
|
+
* so the request manager map entry can be cleaned up
|
|
62
|
+
*/
|
|
63
|
+
function setupRequestMapCleanup() {
|
|
64
|
+
if (NRAgent) {
|
|
65
|
+
NRAgent.on('transactionFinished', function onTransactionFinished(transaction) {
|
|
66
|
+
const requestData = requestManager.getRequestFromId(transaction.id);
|
|
67
|
+
if (requestData && requestData.tempFiles && !lodash.isEmpty(requestData.tempFiles)) {
|
|
68
|
+
const tempFiles = requestData.tempFiles;
|
|
69
|
+
for (let i = 0; i < tempFiles.length; i++) {
|
|
70
|
+
const file = tempFiles[i];
|
|
71
|
+
deleteTempFileForIAST(file);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Here removing request data corresponding to transactionId
|
|
75
|
+
requestManager.gcRequestMap(transaction.id)
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Hook to intercept outbound http requests.
|
|
83
|
+
* @param {*} shim
|
|
84
|
+
* @param {*} mod
|
|
85
|
+
* @param {*} moduleName
|
|
86
|
+
*/
|
|
87
|
+
function outboundHook(shim, mod, method, moduleName) {
|
|
88
|
+
shim.wrap(mod, method, function makeRequestWrapper(shim, fn) {
|
|
89
|
+
return function requestWrapper() {
|
|
90
|
+
const request = requestManager.getRequest(shim);
|
|
91
|
+
|
|
92
|
+
const data = {};
|
|
93
|
+
data.headers = {};
|
|
94
|
+
data.method = arguments[0].method;
|
|
95
|
+
data.path = arguments[0].path;
|
|
96
|
+
data.protocol = arguments[0].protocol ? arguments[0].protocol : 'http:';
|
|
97
|
+
data.headers.host = arguments[0].hostname ? arguments[0].hostname : arguments[0].host;
|
|
98
|
+
data.port = arguments[0].port;
|
|
99
|
+
|
|
100
|
+
if (method === 'get') {
|
|
101
|
+
data.method = 'GET';
|
|
102
|
+
data.path = '/';
|
|
103
|
+
data.headers.host = arguments[0];
|
|
104
|
+
try {
|
|
105
|
+
data.headers.host = data.headers.host.replace(/(^\w+:|^)\/\//, '');
|
|
106
|
+
} catch (error) {
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let completeURL = data.protocol + '//' + data.headers.host + (data.port ? (':' + data.port) : '') + data.path;
|
|
112
|
+
completeURL = completeURL.split(QUESTION_MARK)[0];
|
|
113
|
+
|
|
114
|
+
if (request && !lodash.isEmpty(data.headers.host) && !lodash.isEmpty(data.path)) {
|
|
115
|
+
const appUUID = API.getSecAgent().applicationInfo.applicationUUID;
|
|
116
|
+
|
|
117
|
+
const id = secUtils.getExecutionId().toString();
|
|
118
|
+
let generateFlag = false;
|
|
119
|
+
if (arguments[0] && arguments[0].headers && arguments[0].headers.SECHOOK === id) {
|
|
120
|
+
generateFlag = true;
|
|
121
|
+
}
|
|
122
|
+
if (!generateFlag) {
|
|
123
|
+
const traceObject = secUtils.getTraceObject(shim);
|
|
124
|
+
const secMetadata = securityMetaData.getSecurityMetaData(request, data, traceObject, secUtils.getExecutionId(), EVENT_TYPE.HTTP_REQUEST, EVENT_CATEGORY.HTTP)
|
|
125
|
+
this.secEvent = API.generateSecEvent(secMetadata);
|
|
126
|
+
API.sendEvent(this.secEvent);
|
|
127
|
+
if (request.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
|
|
128
|
+
callbackHook(shim, arguments, arguments.length - 1, this.secEvent);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let traceHeader = EMPTY_STRING;
|
|
133
|
+
|
|
134
|
+
if (request.headers['nr-csec-tracing-data']) {
|
|
135
|
+
traceHeader = request.headers['nr-csec-tracing-data'];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (arguments[0] instanceof Object) {
|
|
139
|
+
if (arguments[0] && arguments[0].headers && request.headers) {
|
|
140
|
+
arguments[0].headers.SECHOOK = id;
|
|
141
|
+
arguments[0].headers[NRCSECTRACINGDATA] = traceHeader + appUUID + '/' + this.secEvent.apiId + '/' + id + ';';
|
|
142
|
+
} else {
|
|
143
|
+
arguments[0].headers = {};
|
|
144
|
+
arguments[0].headers[NRCSECTRACINGDATA] = traceHeader + appUUID + '/' + this.secEvent.apiId + '/' + id + ';';
|
|
145
|
+
arguments[0].headers.SECHOOK = id;
|
|
146
|
+
}
|
|
147
|
+
if (request && request.headers[NR_CSEC_FUZZ_REQUEST_ID] && arguments[0].headers) {
|
|
148
|
+
arguments[0].headers[NR_CSEC_FUZZ_REQUEST_ID] = request.headers[NR_CSEC_FUZZ_REQUEST_ID];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return fn.apply(this, arguments);
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Function to parse fuzzheaders and create temp files in case of dynamic scanning
|
|
159
|
+
* Not functional
|
|
160
|
+
* @param {*} fuzzheaders
|
|
161
|
+
*/
|
|
162
|
+
function parseFuzzheaders(requestData, transactionId) {
|
|
163
|
+
let fuzzheaders = requestData.headers;
|
|
164
|
+
const policy = API.getPolicy();
|
|
165
|
+
const dynamicScanningFlag = policy.data ? policy.data.vulnerabilityScan.iastScan.enabled : false;
|
|
166
|
+
if (fuzzheaders[NR_CSEC_FUZZ_REQUEST_ID] && dynamicScanningFlag) {
|
|
167
|
+
try {
|
|
168
|
+
const additionalData = fuzzheaders[NR_CSEC_FUZZ_REQUEST_ID].split(CSEC_SEP);
|
|
169
|
+
logger.debug('AdditionalData:', additionalData);
|
|
170
|
+
if (additionalData.length >= 7) {
|
|
171
|
+
for (let i = 6; i < additionalData.length; i++) {
|
|
172
|
+
let file = additionalData[i].trim();
|
|
173
|
+
file = file.replace(CSEC_HOME_TMP_CONST, CSEC_HOME_TMP);
|
|
174
|
+
file = path.resolve(file);
|
|
175
|
+
const parentDir = path.dirname(file);
|
|
176
|
+
if (!isInvalid(parentDir) && isPathInside(parentDir, CSEC_HOME_TMP)) {
|
|
177
|
+
try {
|
|
178
|
+
if (!fs.existsSync(parentDir)) {
|
|
179
|
+
secUtils.createPathIfNotExist(parentDir);
|
|
180
|
+
} else {
|
|
181
|
+
logger.debug(parentDir + ' Already Exists');
|
|
182
|
+
}
|
|
183
|
+
fs.closeSync(fs.openSync(file, 'w'));
|
|
184
|
+
|
|
185
|
+
requestData.tempFiles = [];
|
|
186
|
+
requestData.tempFiles.push(file);
|
|
187
|
+
requestManager.setRequest(transactionId, requestData);
|
|
188
|
+
|
|
189
|
+
} catch (error) {
|
|
190
|
+
logger.debug(error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch (error) {
|
|
196
|
+
logger.debug(error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Utility function to add request data in a Map
|
|
203
|
+
* @param {*} shim
|
|
204
|
+
* @param {*} request
|
|
205
|
+
* @param {*} resp
|
|
206
|
+
*/
|
|
207
|
+
function addRequestData(shim, request) {
|
|
208
|
+
try {
|
|
209
|
+
const data = Object.assign({});
|
|
210
|
+
const segment = shim.getActiveSegment();
|
|
211
|
+
if (segment && segment.transaction) {
|
|
212
|
+
data.body = null;
|
|
213
|
+
data.headers = request.headers;
|
|
214
|
+
data.url = request.url;
|
|
215
|
+
data.method = request.method;
|
|
216
|
+
data.httpVersion = request.httpVersion;
|
|
217
|
+
data.serverPort = segment.transaction.port;
|
|
218
|
+
data.contextPath = '/';
|
|
219
|
+
const queryObject = URL.parse(request.url, true).query;
|
|
220
|
+
data.parameterMap = {};
|
|
221
|
+
if (queryObject) {
|
|
222
|
+
Object.keys(queryObject).forEach(function (key) {
|
|
223
|
+
if (queryObject[key]) {
|
|
224
|
+
if (!data.parameterMap[key]) {
|
|
225
|
+
data.parameterMap[key] = new Array(queryObject[key].toString());
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
data.clientIP = requestIp.getClientIp(request);
|
|
231
|
+
const transactionId = segment.transaction.id;
|
|
232
|
+
const storedRequest = requestManager.getRequestFromId(transactionId);
|
|
233
|
+
if (storedRequest && storedRequest.uri) {
|
|
234
|
+
data.uri = storedRequest.uri;
|
|
235
|
+
data.parameterMap = storedRequest.parameterMap;
|
|
236
|
+
}
|
|
237
|
+
requestManager.setRequest(transactionId, data);
|
|
238
|
+
parseFuzzheaders(data, transactionId);
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
logger.debug("Error while preparing incoming request:", error);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Wrappert to hook http.server.emit()
|
|
248
|
+
* @param {*} shim
|
|
249
|
+
* @param {*} mod
|
|
250
|
+
* @param {*} moduleName
|
|
251
|
+
*/
|
|
252
|
+
function emitHook(shim, mod, moduleName) {
|
|
253
|
+
shim.wrap(mod && mod.Server && mod.Server.prototype, 'emit', function makeEmitWrapper(shim, fn) {
|
|
254
|
+
return function emitWrapper() {
|
|
255
|
+
const req = arguments[1];
|
|
256
|
+
const resp = arguments[2];
|
|
257
|
+
|
|
258
|
+
if (arguments[0] == 'request') {
|
|
259
|
+
if (NRAgent && NRAgent.config.security.detection.rxss.enabled) {
|
|
260
|
+
responseHook(resp, req, shim);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
shim.wrap(req, 'on', function makeOnWrapper(shim, fn) {
|
|
264
|
+
return function OnWrapper() {
|
|
265
|
+
if (!requestManager.getRequest(shim)) {
|
|
266
|
+
addRequestData(shim, req);
|
|
267
|
+
}
|
|
268
|
+
if (arguments[0] === 'end') {
|
|
269
|
+
if (req && req.headers[SELFTEST] != 1) {
|
|
270
|
+
hc.getInstance().registerHttpRequestCount();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (arguments[0] === 'data') {
|
|
274
|
+
onDataHook(shim, arguments)
|
|
275
|
+
}
|
|
276
|
+
return fn.apply(this, arguments);
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return fn.apply(this, arguments);
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Wrapper for http.createServer method
|
|
288
|
+
* @param {*} shim
|
|
289
|
+
* @param {*} mod
|
|
290
|
+
*/
|
|
291
|
+
function createServerHook(shim, mod) {
|
|
292
|
+
shim.wrap(mod, 'createServer', function createServerWrapper(shim, fn) {
|
|
293
|
+
return function wrappedCreateServer() {
|
|
294
|
+
shim.wrap(arguments, '0', function appWrapper(shim, fn) {
|
|
295
|
+
return function wrappedApp() {
|
|
296
|
+
let req = arguments[0];
|
|
297
|
+
if (!requestManager.getRequest(shim)) {
|
|
298
|
+
addRequestData(shim, req);
|
|
299
|
+
}
|
|
300
|
+
return fn.apply(this, arguments);
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
return fn.apply(this, arguments);
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Wrapper to hook http request body
|
|
310
|
+
* @param {*} shim
|
|
311
|
+
* @param {*} mod
|
|
312
|
+
*/
|
|
313
|
+
function onDataHook(shim, mod) {
|
|
314
|
+
shim.wrap(mod, '1', function makeOnDataWrapper(shim, fn) {
|
|
315
|
+
return function onDataWrapper() {
|
|
316
|
+
const chunk = arguments[0];
|
|
317
|
+
const chunkType = typeof chunk;
|
|
318
|
+
segment = shim.getActiveSegment()
|
|
319
|
+
if (chunkType === STRING_TYPE || chunkType === ARRAY_TYPE || chunkType === BUFFER_TYPE || chunkType === UINT8ARRAY_TYPE || chunkType === OBJECT_TYPE) {
|
|
320
|
+
let data = chunk.toString();
|
|
321
|
+
const transactionId = segment.transaction.id;
|
|
322
|
+
const requestData = requestManager.getRequestFromId(transactionId);
|
|
323
|
+
if (requestData) {
|
|
324
|
+
data = requestData.body ? requestData.body.concat(chunk.toString()) : chunk.toString();
|
|
325
|
+
requestManager.updateRequestBody(shim, data);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
}
|
|
329
|
+
return fn.apply(this, arguments);
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Wrapper to hook response.write() and reponse.end() method
|
|
336
|
+
* @param {*} resp
|
|
337
|
+
* @param {*} req
|
|
338
|
+
* @param {*} shim
|
|
339
|
+
*/
|
|
340
|
+
function responseHook(resp, req, shim) {
|
|
341
|
+
// wrapper for response.write()
|
|
342
|
+
resp.res = {};
|
|
343
|
+
resp.res.body = EMPTY_STRING;
|
|
344
|
+
|
|
345
|
+
shim.wrap(resp, 'write', function makeWriteWrapper(shim, fn) {
|
|
346
|
+
return function wrappedWrite() {
|
|
347
|
+
const response = this;
|
|
348
|
+
responseBodyCompute(response, arguments);
|
|
349
|
+
return fn.apply(this, arguments);
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
// wrapper for response.end()
|
|
353
|
+
shim.wrap(resp, 'end', function makeEndWrapper(shim, fn) {
|
|
354
|
+
return function wrappedEnd() {
|
|
355
|
+
const response = this;
|
|
356
|
+
responseBodyCompute(response, arguments);
|
|
357
|
+
const request = requestManager.getRequest(shim);
|
|
358
|
+
const construct = API.checkForReflectedXSS(request, response.res.body, response.getHeaders());
|
|
359
|
+
const policy = API.getPolicy();
|
|
360
|
+
const dynamicScanningFlag = policy.data ? (policy.data.vulnerabilityScan?.enabled && policy.data.vulnerabilityScan.iastScan.enabled) : false;
|
|
361
|
+
if (request && (construct || dynamicScanningFlag)) {
|
|
362
|
+
const args = [];
|
|
363
|
+
args.push(construct);
|
|
364
|
+
args.push(response.res.body);
|
|
365
|
+
const traceObject = secUtils.getTraceObject(shim);
|
|
366
|
+
const secMetadata = securityMetaData.getSecurityMetaData(request, args, traceObject, secUtils.getExecutionId(), EVENT_TYPE.REFLECTED_XSS, EVENT_CATEGORY.REFLECTED_XSS)
|
|
367
|
+
const secEvent = API.generateSecEvent(secMetadata);
|
|
368
|
+
|
|
369
|
+
API.sendEvent(secEvent);
|
|
370
|
+
}
|
|
371
|
+
return fn.apply(this, arguments);
|
|
372
|
+
}
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function responseBodyCompute(response, args) {
|
|
377
|
+
let encoding = UTF8;
|
|
378
|
+
const type = response.getHeader(CONTENT_TYPE);
|
|
379
|
+
let contentType = TEXT_HTML;
|
|
380
|
+
if (type) {
|
|
381
|
+
const obj = ConType.parse(type);
|
|
382
|
+
contentType = obj.type;
|
|
383
|
+
encoding = obj.parameters.charset;
|
|
384
|
+
}
|
|
385
|
+
if (args[0]) {
|
|
386
|
+
switch (contentType) {
|
|
387
|
+
case APPLICATION_JSON:
|
|
388
|
+
response.res.body += unescapeJs(args[0].toString(encoding));
|
|
389
|
+
break;
|
|
390
|
+
case APPLICATION_XML:
|
|
391
|
+
case APPLICATION_XHTML:
|
|
392
|
+
case TEXT_PLAIN:
|
|
393
|
+
case TEXT_HTML:
|
|
394
|
+
case APPLICATION_X_FORM_URLENCODED:
|
|
395
|
+
case MULTIPART_FORM_DATA: {
|
|
396
|
+
const data = args[0].toString(encoding);
|
|
397
|
+
response.res.body += data;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Callback hook to generate exit event
|
|
406
|
+
* @param {*} shim
|
|
407
|
+
* @param {*} mod
|
|
408
|
+
* @param {*} fun
|
|
409
|
+
* @param {*} secEvent
|
|
410
|
+
*/
|
|
411
|
+
function callbackHook(shim, mod, fun, secEvent) {
|
|
412
|
+
shim.secEvent = secEvent;
|
|
413
|
+
shim.wrap(mod, fun, function callbackWrapper(shim, fn) {
|
|
414
|
+
if (!shim.isFunction(fn)) {
|
|
415
|
+
return fn;
|
|
416
|
+
}
|
|
417
|
+
return function wrapper() {
|
|
418
|
+
if (arguments && shim.secEvent) {
|
|
419
|
+
API.generateExitEvent(shim.secEvent);
|
|
420
|
+
delete shim.secEvent;
|
|
421
|
+
}
|
|
422
|
+
return fn.apply(this, arguments);
|
|
423
|
+
}
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Utility to check if child path is inside parent path
|
|
429
|
+
* @param {*} childPath
|
|
430
|
+
* @param {*} parentPath
|
|
431
|
+
* @returns
|
|
432
|
+
*/
|
|
433
|
+
function isPathInside(childPath, parentPath) {
|
|
434
|
+
childPath = path.resolve(childPath);
|
|
435
|
+
parentPath = path.resolve(parentPath);
|
|
436
|
+
|
|
437
|
+
if (childPath === parentPath) {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
childPath += path.sep;
|
|
442
|
+
parentPath += path.sep;
|
|
443
|
+
|
|
444
|
+
return childPath.startsWith(parentPath);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Delete Temporary file for IAST. Do not call this in RASP
|
|
450
|
+
* @param {*} file
|
|
451
|
+
*/
|
|
452
|
+
function deleteTempFileForIAST(file) {
|
|
453
|
+
try {
|
|
454
|
+
if (fs.existsSync(file)) {
|
|
455
|
+
logger.debug("Deleting temporary file:", file);
|
|
456
|
+
fs.unlinkSync(file);
|
|
457
|
+
}
|
|
458
|
+
} catch (error) {
|
|
459
|
+
logger.debug("Unable to delete temporary file", error);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = initialize
|
|
7
|
+
const requestManager = require('../../../core/request-manager');
|
|
8
|
+
const secUtils = require('../../../core/sec-utils');
|
|
9
|
+
const { ATTHERATE, DOUBLE_DOLLAR } = require('../../../core/constants');
|
|
10
|
+
const lodash = require('lodash');
|
|
11
|
+
const routeManager = require('../../../core/route-manager');
|
|
12
|
+
const url = require('url');
|
|
13
|
+
const API = require("../../../../nr-security-api");
|
|
14
|
+
const logger = API.getLogger();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Entry point of koa-router module hook
|
|
18
|
+
* @param {*} shim
|
|
19
|
+
* @param {*} mod
|
|
20
|
+
* @param {*} moduleName
|
|
21
|
+
*/
|
|
22
|
+
function initialize(shim, mod, moduleName) {
|
|
23
|
+
logger.info('Instrumenting @koa/router');
|
|
24
|
+
const VerbMethods = ['all', 'delete', 'get', 'head', 'opts', 'post', 'put', 'patch'];
|
|
25
|
+
|
|
26
|
+
// RouteMap hooks
|
|
27
|
+
VerbMethods.forEach(function (fun) {
|
|
28
|
+
shim.wrap(mod && mod.prototype, fun, function wrapRoute(shim, fn) {
|
|
29
|
+
if (!shim.isFunction(fn)) {
|
|
30
|
+
return fn
|
|
31
|
+
}
|
|
32
|
+
return function wrappedRoute() {
|
|
33
|
+
try {
|
|
34
|
+
const stakTrace = secUtils.traceElementForRoute();
|
|
35
|
+
const splittedStack = stakTrace[0].split(DOUBLE_DOLLAR);
|
|
36
|
+
const key = lodash.upperCase(splittedStack[1]) + ATTHERATE + arguments[0];
|
|
37
|
+
routeManager.setRoute(key, splittedStack[0]);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
}
|
|
40
|
+
const route = fn.apply(this, arguments);
|
|
41
|
+
return route;
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const layerModule = shim.require("./lib/layer")
|
|
48
|
+
paramHook(shim, layerModule, moduleName);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Wrapper for params method, to intercept path params and query params
|
|
56
|
+
* @param {*} shim
|
|
57
|
+
* @param {*} mod
|
|
58
|
+
* @param {*} moduleName
|
|
59
|
+
*/
|
|
60
|
+
function paramHook(shim, mod, moduleName) {
|
|
61
|
+
shim.wrap(mod && mod.prototype, 'params', function wrapMatch(shim, fn) {
|
|
62
|
+
return function wrappedMatch() {
|
|
63
|
+
const segment = shim.getActiveSegment();
|
|
64
|
+
if (segment && segment.transaction) {
|
|
65
|
+
let request = requestManager.getRequestFromId(segment.transaction.id);
|
|
66
|
+
try {
|
|
67
|
+
const uri = this.path;
|
|
68
|
+
if (request) {
|
|
69
|
+
request.uri = uri;
|
|
70
|
+
requestManager.setRequest(segment.transaction.id, request);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
} catch (error) {
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const result = fn.apply(this, arguments);
|
|
78
|
+
try {
|
|
79
|
+
var url_parts = url.parse(request.url, true);
|
|
80
|
+
// logic to get query param
|
|
81
|
+
if (url_parts.query) {
|
|
82
|
+
Object.keys(url_parts.query).forEach(function (key) {
|
|
83
|
+
if (url_parts.query[key]) {
|
|
84
|
+
request.parameterMap[key] = new Array(url_parts.query[key].toString());
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// logic to get path param
|
|
89
|
+
if (!lodash.isEmpty(result)) {
|
|
90
|
+
Object.keys(result).forEach(function (key) {
|
|
91
|
+
if (result[key]) {
|
|
92
|
+
if (!request.parameterMap[key]) {
|
|
93
|
+
request.parameterMap[key] = new Array(result[key].toString());
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
requestManager.setRequest(segment.transaction.id, request);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
else{
|
|
105
|
+
return fn.apply(this,arguments);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = initialize
|
|
7
|
+
const requestManager = require('../../core/request-manager');
|
|
8
|
+
const secUtils = require("../../core/sec-utils");
|
|
9
|
+
const API = require("../../../nr-security-api");
|
|
10
|
+
const securityMetaData = require('../../core/security-metadata');
|
|
11
|
+
const { EVENT_TYPE, EVENT_CATEGORY } = require('../../core/event-constants');
|
|
12
|
+
const { NR_CSEC_FUZZ_REQUEST_ID } = require('../../core/constants');
|
|
13
|
+
const logger = API.getLogger();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Entry point of ldapjs and ldapts module hook
|
|
17
|
+
* @param {*} shim
|
|
18
|
+
* @param {*} mod
|
|
19
|
+
* @param {*} moduleName
|
|
20
|
+
*/
|
|
21
|
+
function initialize(shim, mod, moduleName){
|
|
22
|
+
logger.info('Instrumenting '+ moduleName )
|
|
23
|
+
|
|
24
|
+
if(moduleName==='ldapjs' || moduleName==='ldapts'){
|
|
25
|
+
ldapHooks(shim, mod && mod.Client && mod.Client.prototype, 'search', moduleName);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* wrapper to hook ldapjs and ldapts module
|
|
31
|
+
* @param {*} shim
|
|
32
|
+
* @param {*} mod
|
|
33
|
+
* @param {*} methodName
|
|
34
|
+
* @param {*} moduleName
|
|
35
|
+
*/
|
|
36
|
+
function ldapHooks(shim, mod, methodName, moduleName){
|
|
37
|
+
shim.wrap(mod, methodName, function makeWrapper(shim,fn){
|
|
38
|
+
return function wrapper(){
|
|
39
|
+
const param = {};
|
|
40
|
+
param.name = arguments[0];
|
|
41
|
+
param.filter = arguments[1].filter;
|
|
42
|
+
|
|
43
|
+
const interceptedArgs = [param];
|
|
44
|
+
shim.interceptedArgs = interceptedArgs;
|
|
45
|
+
const request = requestManager.getRequest(shim);
|
|
46
|
+
if(request){
|
|
47
|
+
const traceObject = secUtils.getTraceObject(shim);
|
|
48
|
+
const secMetadata = securityMetaData.getSecurityMetaData(request, interceptedArgs, traceObject, secUtils.getExecutionId(), EVENT_TYPE.LDAP, EVENT_CATEGORY.LDAP)
|
|
49
|
+
const secEvent = API.generateSecEvent(secMetadata);
|
|
50
|
+
API.sendEvent(secEvent);
|
|
51
|
+
if (request.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
|
|
52
|
+
callbackHook(shim, arguments, arguments.length - 1, secEvent);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return fn.apply(this, arguments);
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Callback hook to generate exit event
|
|
62
|
+
* @param {*} shim
|
|
63
|
+
* @param {*} mod
|
|
64
|
+
* @param {*} fun
|
|
65
|
+
* @param {*} secEvent
|
|
66
|
+
*/
|
|
67
|
+
function callbackHook(shim, mod, fun, secEvent) {
|
|
68
|
+
shim.secEvent = secEvent;
|
|
69
|
+
shim.wrap(mod, fun, function callbackWrapper(shim, fn) {
|
|
70
|
+
if (!shim.isFunction(fn)) {
|
|
71
|
+
return fn;
|
|
72
|
+
}
|
|
73
|
+
return function wrapper() {
|
|
74
|
+
if ((arguments[0] === null || arguments[0] === undefined) && shim.secEvent) {
|
|
75
|
+
API.generateExitEvent(shim.secEvent);
|
|
76
|
+
delete shim.secEvent;
|
|
77
|
+
}
|
|
78
|
+
return fn.apply(this, arguments);
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
}
|