Details
| ID | ProtectWise |
|---|---|
| Provider | Verizon Communications |
| Category | Network Security |
| From Version | 5.0.0 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Attack Surface Management Cloud Runtime Security Cloud Posture Security Exposure Management |
README
When integrating Protectwise with Cortex XSOAR, event data is received in a continues stream of data which can be handled by Cortex XSOAR.
To set up the integration on Cortex XSOAR:
- Go to ‘Settings > Integrations > Servers & Services’
- Locate the Protectwise integration by searching for ‘Protectwise’ using the search box on the top of the page.
- Click ‘Add instance’ to create and configure a new integration. You should configure the following Protectwise and Cortex XSOAR-specific settings:
Name: A textual name for the integration instance.
- Click ‘Add instance’ to create and configure a new integration. You should configure the following Protectwise and Cortex XSOAR-specific settings:
URL: The hostname or IP address of the application. Make sure it is reachable with respect to IP address and port.
Email & Password: the credentials for accessing the API.
Do not validate certificate (insecure): Select to avoid server certification validation. You may want to do this in case Cortex XSOAR cannot validate the integration server certificate (due to missing CA certificate).
Only fetch events with this text in the name: To only pull events with a specific name, specify it here. Cortex XSOAR will look for one of the filter values in the Event name (comparison is case insensitive).
Separate multiple names with a comma. For example: Progression,Lateral Movement
Filter by threat category: To pull threats according to threat category.
Filter by killchain stage: To pull threats according to threat killchain stage.
Filter by LOW , MEDIUM , or HIGH threatLevel: To pull threats according to Threat Level.
Fetch incidents: Select whether to automatically create Cortex XSOAR incidents from the integration's events.
If this option is checked, the first fetch will search for events 10 minutes back from the moment you turn on Fetching. Subsequently, new offences will be fetched as soon as they are generated. Use the "Query to fetch offences" option to pull older offences as incidents.
The next fetch interval depends on the systemwide interval (default 1 min).
Incident type: Specify the Cortex XSOAR incident type that will be set for incidents from this integration.
Use system proxy settings: Select whether to communicate via the system proxy server or not.
Cortex XSOAR engine: If relevant, select the engine that acts as a proxy to the server.
Engines are used when you need to access a remote network segments and there are network devices such as proxies, firewalls, etc. that prevent the Cortex XSOAR server from accessing the remote networks.
For more information on Cortex XSOAR engines see:
Cortex XSOAR 6.13 - Engines
Cortex XSOAR 8 Cloud- Engines
Cortex XSOAR 8.7 On-prem - Engines
- Press the ‘Test’ button to validate connection.
- After completing the test successfully, press the ‘Done’ button.
Commands:
Example:
The following shows how fields provided by the API are mapped as labels in fetched Events.
[killChainStage] Fortification
[observedAt] 2017-08-04T13:00:03.436Z
[isUpdate] true
[type] MaliciousFlow
[threatLevel] High
[category] Suspicious
[observationCount] 2
[sensorId] 1849
[cid] 1820
[message] Critical Lateral Movement Activity on Hosts: 192.168.2.81,192.168.2.170
[confidence] 100
[endedAt] 2017-08-04T12:59:49.156Z
[threatScore] 70
[id] 000555ed127a1ca0b771fc0e4270cfcc24510b32d7ff9b9d66dfedcf
[startedAt] 2017-08-04T12:59:49.156Z
[threatSubCategory] None
[priority] false
[agentId] 1849
[observedStage] Realtime
[netflowCount] 1
[sensorIds] 1849
[Brand] ProtectWise
[Instance] ProtectWise_instance_1
Configuration parameters
url— Url (required)email— Emailpassword— Passwordcredentials_login— Emailtoken— API Tokencredentials_api_token—insecure— Trust any certificate (not secure)messageFilter— Only fetch events with this text in the namethreatCategory— Filter by threat categorykillChainStage— Filter by killchain stagethreatLevel— Filter by threat level (LOW, MEDIUM, or HIGH)isFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalproxy— Use system proxy settingsmaxFetch— Maximum events to fetch per fetch
Commands (19)
-
event-pcap-downloadDeprecatedThe Event PCAP file to download.
-
event-pcap-infoDeprecatedReturns details of the ProtectWise Event PCAP files.
-
get-tokenDeprecatedReturns the API token, which is used in the integration configuration.
-
observation-pcap-downloadDeprecatedDownloads the observation PCAP file.
-
observation-pcap-infoDeprecatedReturns PCAP details of observations in Protectwise.
-
observation-searchDeprecatedSearches for observations in ProtectWise.
-
protectwise-event-infoSearches for a single event in ProtectWise. Deprecated - use the protectwise-event-info command.
-
protectwise-event-pcap-downloadEvent Pcap Download. Deprecated - use the protectwise-event-pcap-info command.
-
protectwise-event-pcap-infoGet ProtectWise Event Pcap info. Deprecated - use the protectwise-observation-pcap-download command.
-
protectwise-observation-infoLookup a single observation for ProtectWise. Deprecated - use the protectwise-event-pcap-download command.
-
protectwise-observation-pcap-downloadDownloads the Observation PCAP file. Deprecated - use the protectwise-observation-pcap-info command.
-
protectwise-observation-pcap-infoReturns ProtectWise Observation PCAP file information.
-
protectwise-search-eventsSearches for events. Events are resources that describe a threat and contains a collection of observations. Deprecated - use the protectwise-search-events command.
-
protectwise-search-observationsSearches for observations in ProtectWise. Deprecated - use the protectwise-search-observations command.
-
protectwise-show-sensorsReturns all available sensors.
-
pw-event-getDeprecatedReturns information about a single event and its associated observations.
-
pw-observation-getDeprecatedSearches for a single observation in ProtectWise.
-
searchDeprecatedSearches for events. Events are resources that describe a threat and contain a collection of observations.
-
sensorsDeprecatedReturns the specified, or all available sensors.
var DEFAULT_EVENT_HEADERS = ['message','id','type','sensorId','threatScore','killChainStage','confidence','startedAt','observedAt','endedAt','observationCount','netflowCount','observedStage']; var DEFAULT_NETFLOW_HEADERS = ['key', 'id.srcIp', 'id.dstIp', 'id.srcPort', 'id.dstPort', 'id.layer4Proto']; var DEFAULT_OBSERVATION_HEADERS = ['id','killChainStage','source','sensorId','threatScore','severity','confidence','occurredAt','observedAt','endedAt','netflowId','observedStage']; var DEFAULT_SENSOR_HEADERS = ['id', 'friendly_name', 'ip_address', 'last_seen', 'customer_id', 'enabled']; var TIME_FIELDS = ['startedAt', 'occurredAt', 'endedAt', 'observedAt']; var serverUrl = params.url.replace(/[\/]+$/, '') + '/'; var getToken = function() { let token = params.credentials_api_token ? params.credentials_api_token.password : params.token; let email = params.credentials_login ? params.credentials_login.identifier : params.email; let password = params.credentials_login ? params.credentials_login.password : params.password; if (token && token.length === 0) { if ((email&&email.length === 0) || (password&&password.length === 0)){ throw 'If token configuration is empty , you must provide email+password configuration params for auth'; } var tokResult = http( serverUrl + 'token', { Headers: {'Content-Type': ['application/json']}, Method: 'POST', Body: JSON.stringify({'email': email, 'password': password}), }, params.insecure, params.proxy ); var body; if (tokResult.StatusCode !== 200 && tokResult.StatusCode !== 201) { throw 'Failed to create token, request status code: ' + tokResult.StatusCode + ', body: ' + tokResult.Body; } try { body = JSON.parse(tokResult.Body); } catch (ex) { throw 'Error parsing token - ' + tokResult.Body + ' - ' + ex; } return body.token; } return token; }; // If value for any argument is empty remove it var cleanArgs = function(args) { return Object.keys(args).filter(function(k) {return args[k];}).reduce(function(clean, k) {clean[k] = args[k]; return clean;}, {}); }; var doReq = function(url, token, raw, args, saveToFile) { if (args) { url += encodeToURLQuery(cleanArgs(args)); } var res = http( url, { Headers: {'X-Access-Token': [ token ]}, Method: 'GET', SaveToFile: saveToFile ? true : false }, params.insecure, params.proxy ); if (res.StatusCode !== 200) { throw 'Failed to execute ' + url + ' : ' + res.StatusCode + ', body: ' + res.Body; } try { return (raw ? (saveToFile ? res.Path : res.Body) : JSON.parse(res.Body)); } catch (ex) { throw 'Error parsing response - ' + res.Body + ' - ' + ex; } }; var filterClone = function(src, filter) { if (src && Array.isArray(src) && filter && Array.isArray(filter)) { var items = []; src.forEach(function(s) { var item = {}; filter.forEach(function( col ) { item[col] = dq(s, col); }); items.push(item); }); return items; } else return undefined; }; var parseTime = function(time) { if ((typeof time === 'string' || time instanceof String) && (time.indexOf("-") >= 0 || time.indexOf("/") >= 0 )){ var d = new Date(time); return d.getTime(); } return time; }; var eventsSearch = function(start, end, eventType, killChainStage, threatLevel, threatCategory, observationStage, ip, expandDetails, minLimit, maxLimit, reverseOrder, nextPage, token) { return doReq(serverUrl + 'events', token, true, {start: parseTime(start), end: parseTime(end), eventType: eventType, killChainStage: killChainStage, threatLevel: threatLevel, threatCategory: threatCategory, observationStage: observationStage, ip: ip, expandDetails: expandDetails, minLimit: minLimit, maxLimit: maxLimit, reverseOrder: reverseOrder, nextPage: nextPage}); }; var observationSearch = function(sensorId,start,end,type,killChainStage,threatLevel,threatCategory,hasKillChain,ip,expandDetails,minLimit,maxLimit,reverseOrder,nextPage,signatureId,token) { return doReq(serverUrl + 'observations', token, true, {sensorId: sensorId, start: parseTime(start), end: parseTime(end), type: type, killChainStage: killChainStage, threatLevel: threatLevel, threatCategory: threatCategory, hasKillChain: hasKillChain, ip: ip, expandDetails: expandDetails, minLimit: minLimit, maxLimit: maxLimit, reverseOrder: reverseOrder, nextPage: nextPage, signatureId: signatureId}); }; var createIncidentFromEvent = function(event) { var keys = Object.keys(event); var labels = []; for (var i = 0; i<keys.length; i++) { val = event[keys[i]]; if (TIME_FIELDS.indexOf(keys[i]) > -1) { val = convertTimestampToString(val); } labels.push({'type': keys[i], 'value': String(val)}); } return { "name": event.message, "labels": labels, "rawJSON": JSON.stringify(event), "occurred": convertTimestampToString(event.observedAt) }; }; var token = getToken(); switch (command) { case 'test-module': if (token && token.length > 0) { return true; } return false; case 'fetch-incidents': var lastRun = getLastRun(); var now = (new Date()).getTime(); if (!lastRun || !lastRun.time) { // First time, retrieve events from the last 10 min lastRun = {time: now - 10 * 60 * 1000}; } var data = eventsSearch(lastRun.time , now , params.eventType, params.killChainStage, params.threatLevel, params.threatCategory, null, null, null, null, null, null, null, token); try { var res = JSON.parse(data).events; res.reverse(); // events are fetched in descending order so reversing to process older first } catch (ex) { throw 'Error parsing event fetch - ' + data + ' - ' + ex; } var incidents = []; for (var i = 0; i < res.length; i++) { if (incidents.length >= Math.min(parseInt(params.maxFetch), 50)) { break; } var skip = true; if (params.messageFilter && res[i].message) { filters = params.messageFilter.split(','); for (var j = 0; j < filters.length; j++) { if (filters[j] && filters[j].length > 0 && res[i].message.toLowerCase().indexOf(filters[j].toLowerCase()) > -1) { skip = false; break; } } } else { skip = false; } if(!skip) { incidents.push(createIncidentFromEvent(res[i])); lastRun.time = Math.max(lastRun.time, res[i].startedAt)+1; } } setLastRun(lastRun); return JSON.stringify(incidents); case 'sensors': case 'protectwise-show-sensors': var url = serverUrl + 'sensors'; if (args.sensorId && args.sensorId.length > 0 ) { url = url + '/' + args.sensorId; } var headers = args.headers ? args.headers : DEFAULT_SENSOR_HEADERS; var res = doReq(url, token); var items = []; if (!Array.isArray(res)) { res = [res]; } items = filterClone(res, headers); return { Type: entryTypes.note, Contents: res, ContentsFormat: formats.json, HumanReadable: tableToMarkdown('Protectwise sensors', items, headers), EntryContext: {'Protectwise.Sensor(val.id == obj.id)': items} }; case 'search': case 'protectwise-search-events': var res; var raw = eventsSearch(args.start, args.end, args.eventType, args.killChainStage, args.threatLevel, args.threatCategory, args.observationStage, args.ip, args.expandDetails, args.minLimit, args.maxLimit, args.reverseOrder, args.nextPage, token); try { res = JSON.parse(raw).events; } catch (ex) { throw 'Error parsing event search - ' + raw + ' - ' + ex; } var eventHeaders = args.headers ? args.headers : DEFAULT_EVENT_HEADERS; var items = []; if (!Array.isArray(res)) { res = [res]; } items = filterClone(res, eventHeaders); items.forEach(function(item) { TIME_FIELDS.forEach(function (timeKey) { if (item[timeKey]) { item[timeKey] = convertTimestampToString(item[timeKey]); } }); }); return { Type: entryTypes.note, Contents: raw, ContentsFormat: formats.json, HumanReadable: tableToMarkdown('Protectwise Event Search', items, eventHeaders), EntryContext: {'Protectwise.Event(val.id == obj.id)': items} }; case 'pw-event-get': case 'protectwise-event-info': var url = serverUrl + 'events/' + args.id; var eventHeaders = args.headers ? args.headers : DEFAULT_EVENT_HEADERS; var res = doReq(url, token); var event = {}; eventHeaders.forEach(function( col ) { event[col] = res[col]; }); TIME_FIELDS.forEach(function (timeKey) { if (event[timeKey]) { event[timeKey] = convertTimestampToString(event[timeKey]); } }); var md = tableToMarkdown('Protectwise Event ' + res.id, [event], eventHeaders); if (res.netflows && res.netflows.length > 0) { var nf = filterClone(res.netflows, DEFAULT_NETFLOW_HEADERS); for(var i = 0; i < nf.length; i++) { nf[i].srcIp = nf[i]['id.srcIp']; nf[i].dstIp = nf[i]['id.dstIp']; nf[i].srcPort = nf[i]['id.srcPort']; nf[i].dstPort = nf[i]['id.dstPort']; nf[i].layer4Proto = nf[i]['id.layer4Proto']; } event.Netflows = nf; md += '\n' + tableToMarkdown('Related Netflows', nf, DEFAULT_NETFLOW_HEADERS); } if (res.observations && res.observations.length > 0) { var obs = filterClone(res.observations, DEFAULT_OBSERVATION_HEADERS); obs.forEach(function(item) { TIME_FIELDS.forEach(function (timeKey) { if (item[timeKey]) { item[timeKey] = convertTimestampToString(item[timeKey]); } }); }); event.Observations = obs; md += '\n' + tableToMarkdown('Related Observations', obs, DEFAULT_OBSERVATION_HEADERS); } return { Type: entryTypes.note, Contents: res, ContentsFormat: formats.json, HumanReadable: md, EntryContext: {'Protectwise.Event(val.id == obj.id)': event} }; case 'observation-search': case 'protectwise-search-observations': var res; var raw = observationSearch(args.sensorId, args.start, args.end, args.type, args.killChainStage, args.threatLevel, args.threatCategory, args.hasKillChain, args.ip, args.expandDetails, args.minLimit, args.maxLimit, args.reverseOrder, args.nextPage, args.signatureId, token); try { res = JSON.parse(raw).observations; } catch (ex) { throw 'Error parsing observation search - ' + raw + ' - ' + ex; } var headers = args.headers ? args.headers : DEFAULT_OBSERVATION_HEADERS; var items = []; if (!Array.isArray(res)) { res = [res]; } items = filterClone(res, headers); items.forEach(function(item) { TIME_FIELDS.forEach(function (timeKey) { if (item[timeKey]) { item[timeKey] = convertTimestampToString(item[timeKey]); } }); }); return { Type: entryTypes.note, Contents: raw, ContentsFormat: formats.json, HumanReadable: tableToMarkdown('Protectwise Observation Search', items, headers), EntryContext: {'Protectwise.Observation(val.id == obj.id)': items} }; case 'pw-observation-get': case 'protectwise-observation-info': var url = serverUrl + 'observations/' + args.id; var headers = args.headers ? args.headers : DEFAULT_OBSERVATION_HEADERS; var res = doReq(url, token, false, {'sensorId': args.sensorId}); var obj = {}; headers.forEach(function( col ) { obj[col] = res[col]; }); TIME_FIELDS.forEach(function (timeKey) { if (obj[timeKey]) { obj[timeKey] = convertTimestampToString(obj[timeKey]); } }); var md = tableToMarkdown('Protectwise Observation ' + res.id, [obj], headers); return { Type: entryTypes.note, Contents: res, ContentsFormat: formats.json, HumanReadable: md, EntryContext: {'Protectwise.Observation(val.id == obj.id)': obj} }; case 'event-pcap-download': case 'protectwise-event-pcap-download': var filename = (args.filename && args.filename.length > 0) ? args.filename : (args.eventId + '.pcap'); var url = serverUrl + 'pcaps/events/' + args.eventId; res = doReq(url, token, true, {filename: filename}, true); return {Type: 3, FileID: res, File: filename, Contents: 'we must have contents for an entry'}; case 'event-pcap-info': case 'protectwise-event-pcap-info': var url = serverUrl + 'pcaps/events/'+ args.eventId + '/info'; var res = doReq(url, token); var md = '### Protectwise PCAP for Event ' + res.id + '\n - Estimated size: ' + res.estimatedSize + ' bytes'; if (res.netflows && res.netflows.length > 0) { var nf = filterClone(res.netflows, DEFAULT_NETFLOW_HEADERS); for(var i = 0; i < nf.length; i++) { nf[i].srcIp = nf[i]['id.srcIp']; nf[i].dstIp = nf[i]['id.dstIp']; nf[i].srcPort = nf[i]['id.srcPort']; nf[i].dstPort = nf[i]['id.dstPort']; nf[i].layer4Proto = nf[i]['id.layer4Proto']; } event.Netflows = nf; md += '\n' + tableToMarkdown('Included Netflows', res.netflows); } return { Type: entryTypes.note, Contents: res, ContentsFormat: formats.json, HumanReadable: md, EntryContext: {'Protectwise.Event(val.id == obj.id)': {'id': res.id, 'PCAPSize': res.estimatedSize}} }; case 'observation-pcap-download': case 'protectwise-observation-pcap-download': var filename = (args.filename && args.filename.length > 0) ? args.filename : ( args.sensorId+'-'+args.id + '.pcap'); var url = serverUrl + 'pcaps/observations/' + args.sensorId + '/' + args.id; res = doReq(url, token, true, {filename: filename}, true); return {Type: 3, FileID: res, File: filename, Contents: 'we must have contents for an entry'}; case 'observation-pcap-info': case 'protectwise-observation-pcap-info': var url = serverUrl + 'pcaps/observations/' + args.sensorId + '/' + args.id + '/info'; var res = doReq(url, token); var md = '### Protectwise PCAP for Observation ' + res.id + '\n - Estimated size: ' + res.estimatedSize + ' bytes'; if (res.netflows && res.netflows.length > 0) { var nf = filterClone(res.netflows, DEFAULT_NETFLOW_HEADERS); for(var i = 0; i < nf.length; i++) { nf[i].srcIp = nf[i]['id.srcIp']; nf[i].dstIp = nf[i]['id.dstIp']; nf[i].srcPort = nf[i]['id.srcPort']; nf[i].dstPort = nf[i]['id.dstPort']; nf[i].layer4Proto = nf[i]['id.layer4Proto']; } event.Netflows = nf; md += '\n' + tableToMarkdown('Included Netflows', res.netflows); } return { Type: entryTypes.note, Contents: res, ContentsFormat: formats.json, HumanReadable: md, EntryContext: {'Protectwise.Observation(val.id == obj.id && val.sensorId == obj.sensorId)': {'id': args.id, 'sensorId': args.sensorId, 'PCAPSize': res.estimatedSize}} }; case 'get-token': return token; default: return 'The Protectwise integration has no command "' + command + '"'; }




