ProtectWise

Cloud based Security Network DVR.

Network Security · ProtectWise

Details

IDProtectWise
ProviderVerizon Communications
CategoryNetwork Security
From Version5.0.0
Supported ModulesAgentix 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:

  1. Go to ‘Settings > Integrations > Servers & Services’
  2. Locate the Protectwise integration by searching for ‘Protectwise’ using the search box on the top of the page.
    1. 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.

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

  1. Press the ‘Test’ button to validate connection.
  2. After completing the test successfully, press the ‘Done’ button.

Commands:

protectwise-event-info - Lookup a single event and its associated observations for ProtectWise.
protectwise-event-pcap-download - Event Pcap Download.
protectwise-event-pcap-info - Get ProtectWise Event Pcap info.
protectwise-observation-info - Lookup a single observation for ProtectWise.
protectwise-observation-pcap-download - Observation Pcap Download.
protectwise-observation-pcap-info - Get ProtectWise Observation Pcap info.
protectwise-search-events - search Events ,Events are resources that describe a threat and contains a collection of observations.
protectwise-search-observations - search observations in ProtectWise.
protectwise-show-sensors - Collection of all available sensors.
 

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

image

image

image
image

 image

 

 

Configuration parameters

  • url — Url (required)
  • email — Email
  • password — Password
  • credentials_login — Email
  • token — API Token
  • credentials_api_token
  • insecure — Trust any certificate (not secure)
  • messageFilter — Only fetch events with this text in the name
  • threatCategory — Filter by threat category
  • killChainStage — Filter by killchain stage
  • threatLevel — Filter by threat level (LOW, MEDIUM, or HIGH)
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • proxy — Use system proxy settings
  • maxFetch — Maximum events to fetch per fetch

Commands (19)

  • event-pcap-download Deprecated

    The Event PCAP file to download.

  • event-pcap-info Deprecated

    Returns details of the ProtectWise Event PCAP files.

  • get-token Deprecated

    Returns the API token, which is used in the integration configuration.

  • observation-pcap-download Deprecated

    Downloads the observation PCAP file.

  • observation-pcap-info Deprecated

    Returns PCAP details of observations in Protectwise.

  • observation-search Deprecated

    Searches for observations in ProtectWise.

  • protectwise-event-info

    Searches for a single event in ProtectWise. Deprecated - use the protectwise-event-info command.

  • protectwise-event-pcap-download

    Event Pcap Download. Deprecated - use the protectwise-event-pcap-info command.

  • protectwise-event-pcap-info

    Get ProtectWise Event Pcap info. Deprecated - use the protectwise-observation-pcap-download command.

  • protectwise-observation-info

    Lookup a single observation for ProtectWise. Deprecated - use the protectwise-event-pcap-download command.

  • protectwise-observation-pcap-download

    Downloads the Observation PCAP file. Deprecated - use the protectwise-observation-pcap-info command.

  • protectwise-observation-pcap-info

    Returns ProtectWise Observation PCAP file information.

  • protectwise-search-events

    Searches for events. Events are resources that describe a threat and contains a collection of observations. Deprecated - use the protectwise-search-events command.

  • protectwise-search-observations

    Searches for observations in ProtectWise. Deprecated - use the protectwise-search-observations command.

  • protectwise-show-sensors

    Returns all available sensors.

  • pw-event-get Deprecated

    Returns information about a single event and its associated observations.

  • pw-observation-get Deprecated

    Searches for a single observation in ProtectWise.

  • search Deprecated

    Searches for events. Events are resources that describe a threat and contain a collection of observations.

  • sensors Deprecated

    Returns 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 + '"';
}