As an automation platform, Cortex XSOAR fetches data that represents events set at defined moments in time. That metadata is stored within Incidents, will be queried from various systems, and may undergo conversions as it is moves from machines to humans. With its various integrations, Cortex XSOAR ingests datetimes from sources that use different standards, yet manages to keep track of all of them.
In this blog post, we will go over dates in Cortex XSOAR, showing where they are presented and used, as well as how they are stored and passed around.
We will present a real world use case for extracting the dates being passed to the elements of a dashboard. With that in mind, we will go deeper onto the technicalities of passing timeframes to widgets and present an object oriented approach to interpreting and converting those, ensuring that this becomes an easy process, even when using third party tools.
The codebase for this post is available on the NVISO Github Repository.
Let’s look at the use of dates in Cortex XSOAR throughout the GUI and let’s pay attention to the formats we encounter:
Within incident layout tabs, incident fields of type “date” are formatted in a human readable way.
However in the raw context of an Incident, we see the same dates but stored in the ISO 8601 format:
The dates we can observe in the raw context are formatted to be machine readable, this is what Integrations, Automations, and Playbooks read.
The dates visible in the layout tab are rendered live from those in the context. Cortex XSOAR adapts this view depending on the preferred timezone of the current user, which is saved in it’s user profile. This explains the 1 hour difference between the raw dates and their human readable counterparts in our examples above.
Moving on to the dashboards page, we get a time picker to selectively view Incident and Indicator data restricted to a given period of time. In the next part, we will find out how this time frame is passed down to the underlying code generating the tables and graphs that make up the dashboard. For that purpose, we will build a new dashboard comprised of a single automation based Widget.
We just saw that Dashboards introduce a date picker element, it lets you select both relative timeframes such as “Last 7 days” and explicit timeframes where you define two precise dates in time. To find out how this is effectively passed down, we will use an automation based widget and dump the parameters provided to this automation.
If you need help on creating an automation, please refer to the XSOAR documentation on automations.
Let’s create an automation with the following code, not forgetting to add a ‘widget
‘ tag to it.
import json demisto.results(json.dumps(demisto.args()))
The snippet above will print the arguments passed down to the automation.
To run our automation and get it’s output, we need to create a new dashboard and add a text element to it, it’s content will be populated by our automation. For help on creating a dashboard and automation based widgets, please refer to XSOAR – add a widget to a dashboard and XSOAR – creating a widget automation.
We start our reversing effort by using the dashboard with an explicit timeframe:
At first glance, we identify the two arguments that interest us, “to” and “from”, each containing an ISO 8601 string corresponding respectively to the lower and higher bounds of our selected timeframe.
When we use relative dates, we get still get ISO 8601 strings, However, the “to” argument now holds a default value pointing to January first of year 1.
Finally, when we use the ‘All dates’ date picker, we get two of these arbitrary strings.
The findings above can be understood as being a standard on passing dates and time frames, and we can assume that all builtin Cortex XSOAR content can handle it. However, this may not be the case for third party tools. To interface with the latter, and to create our own dashboard compatible content, we need a way to interpret these dashboard parameters.
We have now identified how the dates that define the beginning and the end of a daterange are passed to the elements of a dashboard, after a user selects that date range in the web interface. This opens new capabilities, as we are now not bound anymore to dashboard elements builtin to Cortex XSOAR, but can start to imagine querying period relevant data in third party systems to visualize in our dashboard.
In a future post, we will use our findings to query Microsoft Sentinel for some Incident data, and display the results of that search in dashboards, as well as within incidents. However, a first hurdle will be that not every system we interact with will blindly accept the from and to fields that Cortex XSOAR passes on to us, especially if we get one of those special values. We will first have to come up with a software wrapper that will let us obtain date objects that we can more easily manipulate in Python.
To use the dates stored in our Cortex XSOAR Incidents, and to build our own automation based dashboard widgets, we have come up with an Object Oriented wrapper.
This wrapper introduces classes to describe both these explicit datetimes and their relative counterparts, as well as factories to craft these out of standard XSOAR parameters.
The following snippet describes the different classes:
from abc import ABC class NitroDate(ABC): pass class NitroRegularDate(NitroDate): def __init__(self, date: datetime = None): self.date = date class NitroUnlimitedPastDate(NitroDate): pass class NitroUnlimitedFutureDate(NitroDate): pass class NitroUndefinedDate(NitroDate): pass
NitroDate
is an empty parent class, with 4 child classes:
NitroRegularDate
NitroUnlimitedPastDate
NitroUnlimitedFutureDate
NitroUndefinedDate
NitroRegularDate
represents an explicit date, and stores it as a datetime object.
NitroUnlimitedPastDate
and NitroUnlimitedFutureDate
are both representations of the special date January 1st year 1, but reflect the context they were mentioned in.
NitroUnlimitedPastDate
represents that special value having been passed from a “from” argument, such as with the “Up to X days ago” time picker.
NitroUnlimitedFutureDate
represents that special value having been passed from a “to” argument, such as with the “From x days ago” time picker.
Finally, NitroUndefinedDate
represents either the special value when we cannot identify the argument it was passed from, or the fact that we could not properly parse a date given in input.
Now that we’ve defined the classes we will use to represent our datetimes, we need to build them, preferably from the data supplied by Cortex XSOAR.
from abc import ABC from datetime import datetime, timezone from enum import Enum import dateutil class NitroDateHint(Enum): Future = 1 Past = 2 # an Enum used as a flag for functions that build NitroDates class NitroDateFactory(ABC): """this class is a factory, as in it's able to generate NitroDates from a variety of initial arguments""" @classmethod def from_iso_8601_string(cls, arg: str = ""): """ this function is able to create a NitroDate from an iso 8601 datestring :param arg: the iso 8601 string :type arg: str """ try: date = dateutil.parser.isoparse(arg) except Exception as e: raise NitroDateParsingError from e return NitroRegularDate(date=date) @classmethod def from_regular_xsoar_date_range_arg(cls, arg: str = "", hint: NitroDateHint = None): """ this function is able to create a NitroDate from a single argument passed by a xsoar GUI element and a Hint :param arg: the iso 8601 string or cheatlike string :type arg: str :param hint: a hint to know whether the date, if a predetermined value, should be interpreted as future or past :type hint: NitroDateHint """ if arg == "0001-01-01T00:00:00Z": if hint is None: return NitroUndefinedDate() elif hint == NitroDateHint.Future: return NitroUnlimitedFutureDate() elif hint == NitroDateHint.Past: return NitroUnlimitedPastDate() else: return cls.from_iso_8601_string(arg=arg) @classmethod def from_regular_xsoar_date_range_args(cls, the_args: dict) -> (NitroDate, NitroDate): """ this function is able to create NitroDates from the two arguments passed by a xsoar GUI element :param the_args: the args passed to the xsoar automation by the timepicker GUI element :type the_args: dict """ ret = [NitroUndefinedDate(), NitroUndefinedDate()] if isinstance(the_args, dict): for word, i, hint in [("from", 0, NitroDateHint.Past), ("to", 1, NitroDateHint.Future)]: if isinstance(tmp := the_args.get(word, None), str): nitro_date = cls.from_regular_xsoar_date_range_arg(arg=tmp, hint=hint) # print(f"arg={tmp}, hint={hint}, date={nitro_date}") if isinstance(nitro_date, NitroDate): ret[i] = nitro_date return ret
The Factory presented above eases work during the development of a dashboard widget, by allowing to get two NitroDates
with this simple call
FromDate, ToDate = NitroDateFactory.from_regular_xsoar_date_range_args(demisto.args())
The following screenshot demonstrates the use of this factory function and the type and value of it’s outputs when run against Cortex XSOAR data
From there on, we can check the type of FromDate
and ToDate
and more easily build logic to query third party systems. At that stage, the wrapper correctly identifies the datetimes and timeframes, which it returns as standardized python objects, whether they were passed down in a function call or stored in an incident, and is able to detect errors in their formatting.
In a future post, we use this mechanism to query external APIs in a Cortex XSOAR dashboard.
XSOAR documentation on automations
XSOAR – add a widget to a dashboard
XSOAR – creating a widget automation
Benjamin Danjoux
Benjamin is a senior engineer in NVISO’s SOAR engineering team.
As the SOAR engineering design lead, he is responsible for the overall architecture and organization of the automated workflows running on Palo Alto Cortex XSOAR, which enables the NVISO SOC analysts to detect attackers in customer environments.