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 | 2× 2× 2× 2× 2× 2× 2× 2× 2× 2× 2× 2× 2× 79× 79× 79× 79× 79× 79× 79× 79× 79× 2× 52× 52× 52× 52× 52× 52× 52× 52× 52× 2× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 3× 2× 427× 369× 58× 3× 55× 55× 55× 55× 55× 2× 359× 359× 359× 359× 359× 359× 359× 11× 348× 348× 348× 348× 14× 348× 348× 2436× 348× 79× 79× 52× 348× 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 = []; Iif (analyticsData.file_upload === 'true') { transfer_types.push('uploads'); } Eif (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) { Iif (!(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) Iif (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; } Iif (!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)); } }; |