| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367 |
3×
3×
3×
3×
3×
3×
3×
3×
3×
3×
3×
3×
3×
3×
3×
3×
3×
| 'use strict';
// (c) 2016, 2017 Menlo Security. All rights reserved.
//
// Safefile antivirus scan, sandbox and file export policy.
//
// Currently used to return a fixed json document for secure copy
// file export to a remote scp server using public/private keys.
//
const fs = require('fs');
const _ = require('lodash');
const config = require(__dirname + '/../config');
const logger = require(__dirname + '/../loggers/logger');
const fileTypeHelper = require(__dirname + '/../../pnr-common/lib/node/fileType');
//export namespace as `safefilePolicy`
const safefilePolicy = exports;
// Get the SCP Export Credentials
function getScpCredentials() {
// Read the credentials in off of the disk
try {
// First obtain the MSIP Private Key off of the disk
// removing the final newline character
const msip_private_key = fs.readFileSync(
config.safefile.scp_json_path +
'/scp-file-extraction', 'utf8').toString().trim();
if (!msip_private_key) {
throw new Error('Missing MSIP Key');
}
if (!config.forensic.public_keys) {
throw new Error('Missing SCP Remote Server Key');
}
// TODO: Although we look up scp_key here we have to redo that in fileserver
// because node.js config reader cannot handle newlines (whereas safeview's
// can. Ideally both of these would be done in the scp_plugin.py)
const scpCreds = {
'msip_key': msip_private_key,
'scp_key': config.forensic.public_keys};
logger.info('event="obtained-scp-credentials"');
return scpCreds;
}
catch (e) {
logger.error('event="no-scp-credentials"', e);
}
return null;
}
// Get the SCP Export JSON
function getScpExportJSON() {
try {
Eif (!config.safefile || !config.safefile.enabled ||
!config.forensic.host || !config.forensic.public_keys) {
return null;
}
// Get the scp plugin json which is base64 encoded, decode it and
// put it into the response
const scpexport = {};
scpexport.plugin = 'scpexport';
scpexport.always = true; // Always Run
scpexport.criticalpath = false; // Should not block results
scpexport.config = {'host': config.forensic.host,
'port': config.forensic.port,
'user': config.forensic.user,
'path': config.forensic.path,
'password': config.forensic.password};
// Gets the credentials out of config and into a separate object
// so that we can log/pass around the config
scpexport.credentials = getScpCredentials();
return scpexport;
}
catch (e) {
logger.error('event="failed-scpexport-config"', e);
}
return null;
}
// Build up the constant containing the SCP Export configuration
// and credentials. This will remain constant as if these settings
// are changed, then pnr-e is restarted and this config will be
// reloaded (and the constant reinitialized with the new values)
const scpExportJSON = getScpExportJSON();
// Add the AV File Virus Scan policy
function getSophosAvEngineJSON(policy) {
// Sophos Embedded Anti-Virus Scan Engine
// o Action for transfers which can't be scanned (ignore|block)
const onError = policy.on_error || 'allow'; //'allow' or 'block'
// o Prompt for passwords for encrypted files (true|false)
const passwordPrompt = policy.password_prompt || false;
const avScanJSON = {};
avScanJSON.plugin = 'av_scan';
avScanJSON.always = true; // Always Run
avScanJSON.criticalpath = true; // Should hold up results until complete
avScanJSON.config = {
'on_error': onError,
'password_prompt': passwordPrompt,
'time_out': config.safefile.avengine_timeout
};
avScanJSON.credentials = {};
return avScanJSON;
}
// Add the AV File Sandbox policy
function getSandboxJSON(policy) {
// Sophos File Sandbox
// o Max Sandbox Duration (in minutes)
const timeOut = policy.timeout || 15; // POLICY
// o Action for transfers which can't be sandboxed (ignore|block)
const onError = policy.on_error || 'allow'; // POLICY
const sandboxJSON = {};
sandboxJSON.plugin = 'sandbox';
sandboxJSON.always = true; // Always Run
sandboxJSON.criticalpath = true; // Should hold up results until complete
sandboxJSON.config = {
'time_out': timeOut,
'sandbox_max_size': config.safefile.sandbox_max_size,
'sandbox_base_url': config.safefile.sandbox_base_url,
'sandbox_attempts': config.safefile.sandbox_attempts,
'requests_proxied': config.safefile.sandbox_requests_proxied,
'block_score': config.safefile.sandbox_block_score,
'on_error': onError
};
// Add the cloud sandbox credentials
// In EC2, these credentials are provided by runtime_config.ini from S3
sandboxJSON.credentials = {
'api_version': config.safefile.sandbox_api_version,
'access_key': config.safefile.sandbox_access_key,
'secret_key': config.safefile.sandbox_secret_key,
'proxy_key': config.dashboard.license_string,
};
return sandboxJSON;
}
// Add the FireEye AX policy
function getFireEyeAXJSON(policy) {
// FireEye AX Sandbox
// o Max size for files submitted to the FireEye AX.
// This should match the file size configured on the AX itself, which is unfortunately
// not pragmatically queriable.
const maxSize = policy.max_size || config.safefile.fireeye_ax_max_size;
// o IP or DNS address for the FireEye AX
const baseURL = policy.base_url;
// o Username for the FireEye AX, for a user configured with the 'api_analyst' profile
const username = policy.username;
// o Password for the above username
const password = policy.password;
// o CA Cert for the FireEye AX
const ca_cert = policy.ca_cert;
// o Max FireEye processing duration before timeout (in minutes)
const timeOut = policy.timeout || config.safefile.fireeye_ax_timeout;
// o Action for transfers which fail, timeout or cannot complete succesfully
const onError = policy.on_error || config.safefile.fireeye_ax_on_error;
// o Action for files which are greater than the specified max size
const onOversize = policy.on_oversize || config.safefile.fireeye_ax_on_oversize;
// o Comma separated AX OS Profiles to analyse with. An empty string will use all
// profiles supported.
const profiles = policy.profiles || [];
// o Analysis type to perform. 0-Sandbox, 1-Live
const analysisType = policy.analysis_type || config.safefile.fireeye_ax_analysis_type;
// o Duration to perform analysis for (in minutes)
const analysisDuration = policy.analysis_duration ||
config.safefile.fireeye_ax_analysis_duration;
// o Analysis application
// TODO: From the docs:
// For AX Series appliances (7.7 and higher) and CM Series
// appliances that manage AX Series appliances (7.7
// and higher), setting the application value to –1 allows
// the AX Series appliance to choose the application for
// you. For other appliances, setting the application
// value to 0 allows the AX Series appliance to choose
// the application for you.
// So: The choice of -1 or 0 needs to be captured as there
// appears to be no API (from the docs available to me) to query the AX to
// determine this pragmatically. I'm thinking a dashboard checkbox?
// This may not even be an issue, as Macnica testing so far has shown 0
// to work fine, and a -1 has been changed to a 0, even though that was
// supposedy version 7.7.5.
// NOT POLICY / UI configurable
const application = config.safefile.fireeye_ax_application;
// PNR-3750 decided to not make the following configurable.
// o Submission priority. 0 or 1. Priority = 1 will push new job to the top
// of the list.
// NOT POLICY / UI configurable
const priority = config.safefile.fireeye_ax_priority;
// o Force all submissions to be re-analysed, even if they have been seen before
// (true/false string).
// PNR-3750 decided to force all submissions as it is required for submitting
// against multiple profiles.
// NOT POLICY / UI configurable
const forceAnalysis = config.safefile.fireeye_ax_force_analysis;
// o Control 'prefetch'. Unsure of purpose, but must be 1 if analysisType is 0
// API docs say True / False, but actually should be 1 and 0 (as in the curl example)
// PNR-3750 - setting is N/A as files are always submitted and prefetch is not used.
// NOT POLICY / UI configurable.
const prefetch = config.safefile.fireeye_ax_prefetch;
const fireeye_axJSON = {};
fireeye_axJSON.plugin = 'fireeye_ax';
fireeye_axJSON.always = true; // Always Run
fireeye_axJSON.criticalpath = true; // Should hold up results until complete
fireeye_axJSON.config = {
'max_size': maxSize,
'base_url': baseURL,
'attempts': config.safefile.fireeye_ax_attempts,
'time_out': timeOut,
'on_error': onError,
'on_oversize': onOversize,
// These are config options to the AX
'application': application,
'analysis_duration': analysisDuration,
'priority': priority,
'profiles': profiles,
'analysis_type': analysisType,
'force_analysis': forceAnalysis,
'prefetch': prefetch,
};
// Add the fireeye ax credentials
fireeye_axJSON.credentials = {
'username': username,
'password': password,
'ca_cert': ca_cert,
};
return fireeye_axJSON;
}
function isEnabled(module, policy, extendedAVChecks, analyticsData,
modulesToRun) {
if (!extendedAVChecks || !policy[module] || !policy[module].enabled) {
return false;
}
if (_.indexOf(modulesToRun, module) === -1) {
return false;
}
let transfer_types = [];
if (analyticsData.file_upload === 'true') {
transfer_types.push('uploads');
}
if (analyticsData.file_download === 'true') {
transfer_types.push('downloads');
}
return !(_.isEmpty(_.intersection(transfer_types, policy[module].transfer_types)));
}
// Add the AV File Scan / File Export policy
// This is a list of File plugins and their config/credentials
// that needs to be executed before allowing the original file
// download through to the user.
safefilePolicy.addFileProcessing = function (policy, response, analyticsData,
policyAction, icapFileDownload,
avscanCapability, sandboxCapability) {
if (!(config.safefile && config.safefile.enabled)) {
return;
}
// Anti-Virus and category information
response.category = analyticsData.cats;
response.vt_action = policyAction.download_original;
response.vt_score = analyticsData.sha256_score;
// File Processing
response.file_processing = [];
// TODO: For now we have one plugin 'scpexport' defined in config.
// The plugin list should come from the policyAction but at the moment
// it is being stored in global config until the Policy Server and
// Policy UI changes are available (see PNR-2520 and PNR-2519)
if (config.safefile && config.safefile.enabled &&
config.forensic.host && config.forensic.public_keys) {
// Put the SCP Export JSON into the response
if (scpExportJSON) {
response.file_processing.push(scpExportJSON);
}
}
if (policyAction.skipAV || !avscanCapability) {
//if av_scan is off or skip av is true do not do content inspection
return;
}
if (!policy) {
logger.warn('msg="No content inspection policy found for tenant" tid='+
analyticsData.tid);
return;
}
// File downloads originating from the non-isolated proxy route (ICAP) need
// extra consideration to prevent webapp resources (audio / video / etc)
// from being checked by AV plugins that take an extended duration.
// Determine if the file can be sent for extended duration AV checks when file
// download is via the icap route.
let extendedAVChecks = (!icapFileDownload ||
fileTypeHelper.isAvExtendedCandidate(analyticsData.file_type));
//TODO: Generalize this to work with the custom modules as well
let modulesToRun = ['av_scan', 'sandbox', 'fireeye'];
if (policyAction.modules) { //has hit a content inspection exception
modulesToRun = _.intersection(modulesToRun, policyAction.modules);
}
let logInfo = {av_engine_setting: config.safefile.avengine,
av_engine_capability: avscanCapability,
sandbox_setting: config.safefile.sandbox,
sandbox_capability: sandboxCapability,
fireeye_ax_setting: config.safefile.fireeye_ax,
extended_av_checks: extendedAVChecks,
modules_to_run: modulesToRun.join(',')
};
logger.info('event="safefile-state" ' +
_.map(logInfo, (val, key) => {
return key + '="' + val + '"';
}).join(' ')
);
// Sophos Embedded Anti-Virus Scan Engine
if (config.safefile && config.safefile.avengine &&
avscanCapability && policy.av_scan.enabled &&
_.indexOf(modulesToRun, 'av_scan') !== -1)
{
response.file_processing.push(getSophosAvEngineJSON(policy.av_scan));
// We can only sandbox if we have avScan enabled, we are doing 'extended'
// checks are enabled and the tenant has the sandboxCapability enabled
if (config.safefile.sandbox && sandboxCapability &&
isEnabled('sandbox', policy, extendedAVChecks, analyticsData, modulesToRun)) {
response.file_processing.push(getSandboxJSON(policy.sandbox));
}
}
// TODO: FireEye AX plugin - Simple implementation until PNR-3749/50/51 ready
// See comments within the function about work there too.
if (config.safefile && config.safefile.fireeye_ax &&
isEnabled('fireeye', policy, extendedAVChecks, analyticsData, modulesToRun)) {
response.file_processing.push(getFireEyeAXJSON(policy.fireeye));
}
};
|