{ "cells": [ { "cell_type": "markdown", "source": [ "# Microsoft Sentinel Notebooks and MSTICPy\n", "## Examples of machine learning techniques in Jupyter notebooks\n", "\n", "Author: Ian Hellen\n", "
Co-Authors: Pete Bryan, Ashwin Patil\n", "\n", "Released: 26 Oct 2020" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Notebook Setup\n", "\n", "Please ensure that MSTICPy is installed before continuing with this notebook.\n", "\n", "The nbinit module loads required libraries and optionally installs require packages." ], "metadata": {} }, { "cell_type": "code", "source": [ "from pathlib import Path\n", "from IPython.display import display, HTML\n", "import warnings\n", "warnings.simplefilter(action='ignore', category=FutureWarning)\n", "\n", "REQ_PYTHON_VER = \"3.6\"\n", "REQ_MSTICPY_VER = \"1.0.0\"\n", "REQ_MP_EXTRAS = [\"ml\"]\n", "\n", "\n", "display(HTML(\"

Starting Notebook setup...

\"))\n", " \n", "import msticpy as mp\n", "mp.init_notebook(namespace=globals());\n" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1618337296472 } } }, { "cell_type": "markdown", "source": [ "## Retrieve sample data files" ], "metadata": {} }, { "cell_type": "code", "source": [ "from urllib.request import urlretrieve\n", "from pathlib import Path\n", "from tqdm.auto import tqdm\n", "\n", "github_uri = \"https://raw.githubusercontent.com/Azure/Azure-Sentinel-Notebooks/master/{file_name}\"\n", "github_files = {\n", " \"exchange_admin.pkl\": \"src/data\",\n", " \"processes_on_host.pkl\": \"src/data\",\n", " \"timeseries.pkl\": \"src/data\",\n", " \"data_queries.yaml\": \"src/data\",\n", "}\n", "\n", "Path(\"./src/data\").mkdir(exist_ok=True, parents=True)\n", "for file, path in tqdm(github_files.items(), desc=\"File download\"):\n", " file_path = Path(path).joinpath(file)\n", " url_path = f\"{path}/{file}\" if path else file\n", " urlretrieve(\n", " github_uri.format(file_name=url_path),\n", " file_path\n", " )\n", " assert Path(file_path).is_file()" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1618336504315 } } }, { "cell_type": "markdown", "source": [ "
\n", "\n", "# Time Series Analysis\n", "\n", "## Query network data\n", "The starting point is ingesting data to analyze.\n", "\n", "MSTICpy contains a number of [query providers](https://msticpy.readthedocs.io/en/latest/data_acquisition/DataProviders.html) \n", "that let you query and return data from several different sources. \n", "\n", "Below we are using the LocalData query provider to return data from sample files.

\n", "\n", "Data is returned in a Pandas [DataFrame](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html) for easy manipulation and to provide a common interface for other features in MSTICpy.
\n", "Here we are getting a summary of our network traffic for the time period we are interested in.\n" ], "metadata": {} }, { "cell_type": "code", "source": [ "query_range = nbwidgets.QueryTime(\n", " origin_time=pd.Timestamp(\"2020-07-13 00:00:00\"),\n", " before=1,\n", " units=\"week\"\n", ")\n", "query_range" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1618336448011 } } }, { "cell_type": "markdown", "source": [ "
\n", "\n", "This query fetches the total number of bytes send outbound on the network, grouped by hour.\n", "\n", "The input to the Timeseries analysis needs to be in the form of:\n", "- a datetime index (in a regular interval like an hour or day)\n", "- a scalar value used to determine anomalous values based on periodicity\n" ], "metadata": {} }, { "cell_type": "code", "source": [ "# Initialize the data provider and connect to our Splunk instance.\n", "qry_prov = QueryProvider(\"LocalData\", data_paths=[\"./src/data\"], query_paths=[\"./src/data\"])\n", "qry_prov.connect()\n", "\n", "ob_bytes_per_hour = qry_prov.Network.get_network_summary(query_range)\n", "md(\"Sample data:\", \"large\")\n", "ob_bytes_per_hour.head(3)" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1618336452828 } } }, { "cell_type": "markdown", "source": [ "## Using Timeseries decomposition to detect anomalous network activity\n", "\n", "Below we use MSTICpy's [time series analysis](https://msticpy.readthedocs.io/en/latest/msticpy.analysis.html?highlight=timeseries#module-msticpy.analysis.timeseries) \n", "machine learning capabilities to identify anomalies in our network traffic for further investigation.
\n", "As well as computing anomalies we visualize the data so that we can more easily see where these anomalies present themselves.\n" ], "metadata": {} }, { "cell_type": "code", "source": [ "from msticpy.analysis.timeseries import display_timeseries_anomalies\n", "from msticpy.analysis.timeseries import timeseries_anomalies_stl\n", "\n", "# Conduct our timeseries analysis\n", "ts_analysis = timeseries_anomalies_stl(ob_bytes_per_hour)\n", "# Visualize the timeseries and any anomalies\n", "display_timeseries_anomalies(data=ts_analysis, y= 'TotalBytesSent');\n", "\n", "md(\"We can see two clearly anomalous data points representing unusual outbound traffic.
\", \"bold\")" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1618336456021 } } }, { "cell_type": "markdown", "source": [ "### View the summary events marked as anomalous" ], "metadata": {} }, { "cell_type": "code", "source": [ "max_score, min_score = ts_analysis.score.max(), ts_analysis.min()\n", "ts_analysis[ts_analysis[\"anomalies\"] == 1]" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1618336459592 } } }, { "cell_type": "markdown", "source": [ "### Extract the anomaly period\n", "We can extract the **start** and **end** times of anomalous events and \n", "use this more-focused time range to query for unusual activity in this period.\n", "\n", "> **Note**: if more than one anomalous period is indicated we can use
\n", "> `msticpy.analysis.timeseries.extract_anomaly_periods()` function to isolate\n", "> time blocks around the anomalous periods." ], "metadata": {} }, { "cell_type": "code", "source": [ "# Identify when the anomalies occur so that we can use this timerange\n", "# to scope the next stage of our investigation.\n", "# Add a 1 hour buffer around the anomalies\n", "start = ts_analysis[ts_analysis['anomalies']==1]['TimeGenerated'].min() - pd.to_timedelta(1, unit='h')\n", "end = ts_analysis[ts_analysis['anomalies']==1]['TimeGenerated'].max() + pd.to_timedelta(1, unit='h')\n", "\n", "# md and md_warn are MSTICpy features to provide simple, and clean output in notebook cells\n", "md(\"Anomalous network traffic detected between:\", \"large\")\n", "md(f\"Start time: {start}
End time: {end}
\",)" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1618336462962 } } }, { "cell_type": "markdown", "source": [ "## Time Series Conclusion\n", "\n", "We would take these start and end times to zero in on which machines\n", "were responsible for the anomalous traffic. Once we find them we can\n", "use other techniques to analyze what's going on on these hosts." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Other Applications\n", "\n", "You can use the msticpy query function `MultiDataSource.get_timeseries_anomalies` on most Microsoft Sentinel tables to do this summarization directly.\n", "\n", "Three examples are shown below.\n", "\n", "```python\n", "start = pd.Timestamp(\"2020-09-01T00:00:00\")\n", "end = pd.Timestamp(\"2020-09-301T00:00:00\")\n", "\n", "# Sent bytes by hour (default) from Palo Alto devices\n", "time_series_net_bytes = qry_prov.MultiDataSource.get_timeseries_decompose(\n", " start=start,\n", " end=end,\n", " table=\"CommonSecurityLog\",\n", " timestampcolumn=\"TimeGenerated\",\n", " aggregatecolumn=\"SentBytes\",\n", " groupbycolumn=\"DeviceVendor\",\n", " aggregatefunction=\"sum(SentBytes)\",\n", " where_clause='|where DeviceVendor==\"Palo Alto Networks\"',\n", ")\n", "\n", "# Sign-in failure count in AAD\n", "time_series_signin_fail = qry_prov.MultiDataSource.get_timeseries_anomalies(\n", " table=\"SigninLogs\",\n", " start=start,\n", " end=end,\n", " timestampcolumn=\"TimeGenerated\",\n", " aggregatecolumn=\"AppDisplayName\",\n", " groupbycolumn=\"ResultType\",\n", " aggregatefunction=\"count(AppDisplayName)\",\n", " where_clause='| where ResultType in (50126, 50053, 50074, 50076)',\n", " add_query_items='| project-rename Total=AppDisplayName',\n", ")\n", "\n", "# Number of distinct processes by hour\n", "time_series_procs = qry_prov.MultiDataSource.get_timeseries_anomalies(\n", " table=\"SecurityEvent\",\n", " start=start,\n", " end=end,\n", " timestampcolumn=\"TimeGenerated\",\n", " aggregatecolumn=\"DistinctProcesses\",\n", " groupbycolumn=\"Account\",\n", " aggregatefunction=\"dcount(NewProcessName)\",\n", " where_clause=\"| where Computer='myhost.domain.con'\",\n", ")\n", "\n", "# Then submit to ts anomalies decomposition\n", "ts_analysis = timeseries_anomalies_stl(time_series_procs)\n", "# Visualize the timeseries and any anomalies\n", "display_timeseries_anomolies(data=ts_analysis, y='Total');\n", "```" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "
\n", "\n", "# Using Clustering \n", "## - Example: aggregating similar process patterns to highlight unusual logon sessions\n", "\n", "Sifting through thousands of events from a host is tedious in the extreme.\n", "We want to find a better way of identifying suspicious clusters of activity.\n", "\n", "Query the data and do some initial analysis of the results" ], "metadata": {} }, { "cell_type": "code", "source": [ "print(\"Getting process events...\", end=\"\")\n", "processes_on_host = qry_prov.WindowsSecurity.list_host_processes(\n", " query_range, host_name=\"MSTICAlertsWin1\"\n", ")\n", "\n", "md(\"Initial analysis of data set\", \"large, bold\")\n", "md(f\"Total processes in data set {len(processes_on_host)}\")\n", "for column in (\"Account\", \"NewProcessName\", \"CommandLine\"):\n", " md(f\"Total distinct {column} in data {processes_on_host[column].nunique()}\")\n", "md(\"
\")\n", "md(\"Try grouping by distinct Account, Process, Commandline
- we still have 1000s of rows!\", \"large\")\n", "display(\n", " processes_on_host\n", " .groupby([\"Account\", \"NewProcessName\", \"CommandLine\"])\n", " [[\"TimeGenerated\"]]\n", " .count()\n", " .rename(columns={\"TimeGenerated\": \"Count\"})\n", ")" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1620085574961 } } }, { "cell_type": "markdown", "source": [ "## Clustering motivation\n", "\n", "We want to find atypical commands being run and see if they are associated with\n", "the same user or time period
\n", "\n", "It is tedious to do repeated queries grouping on different attributes of events.
\n", "Instead we can specify features that we are interested in grouping around and use
\n", "clustering, a form of unsupervised learning, to group the data. \n", "\n", "A challenge when using simple grouping is that commands (commandlines) may vary slightly\n", "but are essentially repetitions of the same thing (e.g. contain dynamically-generated\n", "GUIDs or other temporary data).\n", "\n", "We can extract features of the commandline rather than using it in its raw form.\n", "\n", "Using clustering we can add arbitrarily many features to group on. Here we are using the following features:\n", "- Account name\n", "- Process name\n", "- Command line structure\n", "- Whether the process is a system session or not\n", "\n", "> **Note**: A downside to clustering is that text features (usually) need to be transformed from a string
\n", "> into a numeric representation." ], "metadata": {} }, { "cell_type": "code", "source": [ "from msticpy.sectools.eventcluster import dbcluster_events, add_process_features, char_ord_score\n", "from collections import Counter\n", "\n", "print(f\"Input data: {len(processes_on_host)} events\")\n", "print(\"Extracting features...\", end=\"\")\n", "feature_procs = add_process_features(input_frame=processes_on_host, path_separator=\"\\\\\")\n", "\n", "feature_procs[\"accountNum\"] = feature_procs.apply(\n", " lambda x: char_ord_score(x.Account), axis=1\n", ")\n", "print(\".\", end=\"\")\n", "\n", "# you might need to play around with the max_cluster_distance parameter.\n", "# decreasing this gives more clusters.\n", "cluster_columns = [\"commandlineTokensFull\", \"pathScore\", \"accountNum\", \"isSystemSession\"]\n", "print(\"done\")\n", "print(\"Clustering...\", end=\"\")\n", "(clus_events, dbcluster, x_data) = dbcluster_events(\n", " data=feature_procs,\n", " cluster_columns=cluster_columns,\n", " max_cluster_distance=0.0001,\n", ")\n", "print(\"done\")\n", "print(\"Number of input events:\", len(feature_procs))\n", "print(\"Number of clustered events:\", len(clus_events))\n", "\n", "print(\"Merging with source data and computing rarity...\", end=\"\")\n", "\n", "# Join the clustered results back to the original process frame\n", "procs_with_cluster = feature_procs.merge(\n", " clus_events[[*cluster_columns, \"ClusterSize\"]],\n", " on=[\"commandlineTokensFull\", \"accountNum\", \"pathScore\", \"isSystemSession\"],\n", ")\n", "\n", "# Compute Process pattern Rarity = inverse of cluster size\n", "procs_with_cluster[\"Rarity\"] = 1 / procs_with_cluster[\"ClusterSize\"]\n", "# count the number of processes for each logon ID\n", "lgn_proc_count = (\n", " pd.concat(\n", " [\n", " processes_on_host.groupby(\"TargetLogonId\")[\"TargetLogonId\"].count(),\n", " processes_on_host.groupby(\"SubjectLogonId\")[\"SubjectLogonId\"].count(),\n", " ]\n", " ).sum(level=0)\n", ").to_dict()\n", "print(\"done\")\n", "# Display the results\n", "md(\"

Sessions ordered by process rarity\", \"large, bold\")\n", "md(\"Higher score indicates higher number of unusual processes\")\n", "process_rarity = (procs_with_cluster.groupby([\"SubjectUserName\", \"SubjectLogonId\"])\n", " .agg({\"Rarity\": \"mean\", \"TimeGenerated\": \"count\"})\n", " .rename(columns={\"TimeGenerated\": \"ProcessCount\"})\n", " .reset_index())\n", "display(\n", " process_rarity\n", " .sort_values(\"Rarity\", ascending=False)\n", " .style.bar(subset=[\"Rarity\"], color=\"#d65f5f\")\n", ")" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1618336998754 } } }, { "cell_type": "code", "source": [ "# get the logon ID of the rarest session\n", "rarest_logonid = process_rarity[process_rarity[\"Rarity\"] == process_rarity.Rarity.max()].SubjectLogonId.iloc[0]\n", "# extract processes with this logonID\n", "sample_processes = (\n", " processes_on_host\n", " [processes_on_host[\"SubjectLogonId\"] == rarest_logonid]\n", " [[\"TimeGenerated\", \"CommandLine\"]]\n", " .sort_values(\"TimeGenerated\")\n", ")[5:25]\n", "# compute duration of session\n", "duration = sample_processes.TimeGenerated.max() - sample_processes.TimeGenerated.min()\n", "md(f\"{len(sample_processes)} processes executed in {duration.total_seconds()} sec\", \"bold\")\n", "display(sample_processes)\n", "md(\"Note: '[PLACEHOLDER]' in the CommandLine values replaces the password value.\")\n" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1618337003076 } } }, { "cell_type": "markdown", "source": [ "## Clustering conclusion\n", "We have narrowed down the task of sifting through > 20,000 processes \n", "to a few 10s and have them grouped into sessions ordered by the\n", "relative rarity of the process patterns" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Other Applications\n", "\n", "You can use this technique on other datasets where you want to group by multiple features of the data.\n", "\n", "The caveat is that you need to transform any non-numeric data field into a numeric form.\n", "\n", "msticpy has a few built-in functions to help with this:\n", "```python\n", "from msticpy.sectools import eventcluster\n", "\n", "# This will group similar names together (e.g. \"Administrator\" and \"administrator\")\n", "my_df[\"account_num\"] = eventcluster.char_ord_score_df(data=my_df, column=\"Account\")\n", "\n", "# This will create a distinct hash for even minor differences in the input.\n", "# This might be useful to detect imperfectly faked UA strings.\n", "my_df[\"ua_hash\"] = eventcluster.crc32_hash_df(data=my_df, column=\"UserAgent\")\n", "\n", "# This will return the number of delimiter chars in the string - often a \n", "# a good proxy for the structure of an input while ignoring variable text values\n", "# e.g. \n", "# \"https://my.dom.com/path1?u1=163.174.4.23\" will produce the same score as\n", "# \"https://www.contoso.com/azure?srcdom=moon.base.alpha.com\"\n", "# but\n", "# \"curl https://www.contoso.com/top/next?u=23 > ~/my_page\"\n", "# \"curl https://www.contoso.com/top_next?u=2.3 > ~/my_page.sh\"\n", "# will produce different values despite the similarity of the strings.\n", "# Note - you can override the default delimiter list of \" \\-/.,\"'|&:;%\\$()]\"\n", "my_df[\"request_struct\"] = eventcluster.delim_count_df(my_df, column=\"Request\")\n", "```\n", "\n", "You can use a combination of these and other functions on the same fields\n", "to measure different aspects of the data. For example, the following takes\n", "a hash of the browser version of the UA (user agent) string and a structural count of\n", "the delimiters used.\n", "\n", "Use the `ua_pref_hash` and `ua_delims` to cluster on identical browser versions that\n", "have the same UA string\n", "```python\n", "my_df[\"ua_prefix\"] = data=my_df[\"UserAgent\"].str.split(\")\")[-1])\n", "my_df[\"ua_pref_hash\"] = eventcluster.crc32_hash_df(data=my_df, column=\"ua_prefix\")\n", "my_df[\"ua_delims\"] = eventcluster.delim_count_df(data=my_df, column=\"UserAgent\")\n", "```" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "
\n", "\n", "# Detecting anomalous sequences using Markov Chain\n", "\n", "The **anomalous_sequence** MSTICPy package uses Markov Chain analysis to predict the probability
\n", "that a particular sequence of events will occur given what has happened in the past.\n", "\n", "Here we're applying it to Office activity. \n" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Query the data" ], "metadata": {} }, { "cell_type": "code", "source": [ "query = \"\"\"\n", "| where TimeGenerated >= ago(60d)\n", "| where RecordType_s == 'ExchangeAdmin'\n", "| where UserId_s !startswith \"NT AUTHORITY\"\n", "| where UserId_s !contains \"prod.outlook.com\" \n", "| extend params = todynamic(strcat('{\"', Operation_s, '\" : ', tostring(Parameters_s), '}')) \n", "| extend UserId = UserId_s, ClientIP = ClientIP_s, Operation = Operation_s\n", "| project TimeGenerated= Start_Time_t, UserId, ClientIP, Operation, params\n", "| sort by UserId asc, ClientIP asc, TimeGenerated asc\n", "| extend begin = row_window_session(TimeGenerated, 20m, 2m, UserId != prev(UserId) or ClientIP != prev(ClientIP))\n", "| summarize cmds=makelist(Operation), end=max(TimeGenerated), nCmds=count(), nDistinctCmds=dcount(Operation),\n", "params=makelist(params) by UserId, ClientIP, begin\n", "| project UserId, ClientIP, nCmds, nDistinctCmds, begin, end, duration=end-begin, cmds, params\n", "\"\"\"\n", "exchange_df = qry_prov.Azure.OfficeActivity(add_query_items=query)\n", "print(f\"Number of events {len(exchange_df)}\")\n", "exchange_df.drop(columns=\"params\").head()" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1617840728875 } } }, { "cell_type": "markdown", "source": [ "## Perform Anomalous Sequence analysis on the data\n", "\n", "The analysis groups events into sessions (time-bounded and linked by a common account). It then
\n", "builds a probability model for the types of *command* (E.g. \"SetMailboxProperty\")
\n", "and the parameters and parameter values used for that command.\n", "\n", "I.e. how likely is it that a given user would be running this sequence of commands in a logon session?\n", "\n", "Using this probability model, we can highlight sequences that have an extremely low probability, based
\n", "on prior behaviour.\n" ], "metadata": {} }, { "cell_type": "code", "source": [ "from msticpy.analysis.anomalous_sequence.utils.data_structures import Cmd\n", "from msticpy.analysis.anomalous_sequence import anomalous\n", "\n", "def process_exchange_session(session_with_params, include_vals):\n", " new_ses = []\n", " for cmd in session_with_params:\n", " c = list(cmd.keys())[0]\n", " par = list(cmd.values())[0]\n", " new_pars = set()\n", " if include_vals:\n", " new_pars = dict()\n", " for p in par:\n", " if include_vals:\n", " new_pars[p['Name']] = p['Value']\n", " else:\n", " new_pars.add(p['Name'])\n", " new_ses.append(Cmd(name=c, params=new_pars))\n", " return new_ses\n", "\n", "sessions = exchange_df.cmds.values.tolist()\n", "param_sessions = []\n", "param_value_sessions = []\n", "\n", "for ses in exchange_df.params.values.tolist():\n", " new_ses_set = process_exchange_session(session_with_params=ses, include_vals=False)\n", " new_ses_dict = process_exchange_session(session_with_params=ses, include_vals=True)\n", " param_sessions.append(new_ses_set)\n", " param_value_sessions.append(new_ses_dict)\n", "\n", "data = exchange_df\n", "data['session'] = sessions\n", "data['param_session'] = param_sessions\n", "data['param_value_session'] = param_value_sessions\n", "\n", "modelled_df = anomalous.score_sessions(\n", " data=data,\n", " session_column='param_value_session',\n", " window_length=3\n", ")\n", "\n", "anomalous.visualise_scored_sessions(\n", " data_with_scores=modelled_df,\n", " time_column='begin', # this will appear on the x-axis\n", " score_column='rarest_window3_likelihood', # this will appear on the y axis\n", " window_column='rarest_window3', # this will represent the session in the tool-tips\n", " source_columns=['UserId', 'ClientIP'], # specify any additional columns to appear in the tool-tips\n", ")" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1617840741087 } } }, { "cell_type": "markdown", "source": [ "The events are shown in descending order of likelihood (vertically), so the
\n", "events at the bottom of the chart are the ones most interesting to us.\n", "\n", "**Looking at these rare events, we can see potentially suspicious activity changing role memberships.**" ], "metadata": {} }, { "cell_type": "code", "source": [ "pd.set_option(\"display.html.table_schema\", False)\n", "\n", "likelihood_max=modelled_df[\"rarest_window3_likelihood\"].max()\n", "likelihood_min=modelled_df[\"rarest_window3_likelihood\"].min()\n", "slider_step = (likelihood_max - likelihood_min) / 20\n", "start_val = likelihood_min + slider_step\n", "threshold = widgets.FloatSlider(\n", " description=\"Select likelihood threshold\",\n", " max=likelihood_max,\n", " min=likelihood_min,\n", " value=start_val,\n", " step=start_val,\n", " layout=widgets.Layout(width=\"60%\"),\n", " style={\"description_width\": \"200px\"},\n", " readout_format=\".7f\"\n", ")\n", "\n", "\n", "def show_rows(change):\n", " thresh = change[\"new\"]\n", " pd_disp.update(modelled_df[modelled_df[\"rarest_window3_likelihood\"] < thresh])\n", "\n", "threshold.observe(show_rows, names=\"value\")\n", "md(\"Move the slider to see event sessions below the selected likelihood threshold\", \"bold\")\n", "display(HTML(\"
\"))\n", "display(threshold)\n", "display(HTML(\"
\"))\n", "md(f\"Range is {likelihood_min:.7f} (min likelihood) to {likelihood_max:.7f} (max likelihood)


\")\n", "pd_disp = display(\n", " modelled_df[modelled_df[\"rarest_window3_likelihood\"] < start_val],\n", " display_id=True\n", ")" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1617841520179 } } }, { "cell_type": "markdown", "source": [ "### Print out content of the selected events/commands in more readable format\n", "\n", "> Note for many events the output will be long" ], "metadata": {} }, { "cell_type": "code", "source": [ "import pprint\n", "\n", "rarest_events = (\n", " modelled_df[modelled_df[\"rarest_window3_likelihood\"] < threshold.value]\n", " [[\n", " \"UserId\", \"ClientIP\", \"begin\", \"end\", \"param_value_session\", \"rarest_window3_likelihood\"\n", " ]]\n", " .rename(columns={\"rarest_window3_likelihood\": \"likelihood\"})\n", " .sort_values(\"likelihood\")\n", ")\n", "for idx, (_, rarest_event) in enumerate(rarest_events.iterrows(), 1):\n", " md(f\"Event {idx}\", \"large\")\n", " display(pd.DataFrame(rarest_event[[\"UserId\", \"ClientIP\", \"begin\", \"end\", \"likelihood\"]]))\n", "\n", " md(\"
\")\n", " md(\"Param session details:\", \"bold\")\n", " for cmd in rarest_event.param_value_session:\n", " md(f\"Command: {cmd.name}\")\n", " md(pprint.pformat(cmd.params))\n", " md(\"

\")" ], "outputs": [], "execution_count": null, "metadata": { "gather": { "logged": 1617841645541 } } }, { "cell_type": "markdown", "source": [ "
\n", "\n", "# Resources\n", "## MSTICpy:\n", "- msticpy Github https://github.com/Microsoft/msticpy\n", "- msticpy Docs https://msticpy.readthedocs.io/en/latest/\n", "- msticpy Release Blog https://medium.com/@msticmed\n", "\n", "### MSTICpy maintainers:\n", "- Ian Hellen [@ianhellen](https://twitter.com/ianhellen)\n", "- Pete Bryan [@MSSPete](https://twitter.com/MSSPete)\n", "- Ashwin Patil [@ashwinpatil](https://twitter.com/ashwinpatil)\n", "\n", "## Microsoft Sentinel Notebooks:\n", "- Microsoft Sentinel Github Notebooks https://github.com/Azure/Azure-Sentinel-Notebooks/\n", " - (Samples with data in Sample-Notebooks folder)\n", "- Microsoft Sentinel Tech Community Blogs https://aka.ms/AzureSentinelBlog" ], "metadata": {} } ], "metadata": { "kernel_info": { "name": "python38-azureml" }, "kernelspec": { "name": "python38-azureml", "language": "python", "display_name": "Python 3.8 - AzureML" }, "language_info": { "name": "python", "version": "3.8.5", "mimetype": "text/x-python", "codemirror_mode": { "name": "ipython", "version": 3 }, "pygments_lexer": "ipython3", "nbconvert_exporter": "python", "file_extension": ".py" }, "microsoft": { "host": { "AzureML": { "notebookHasBeenCompleted": true } } }, "nteract": { "version": "nteract-front-end@1.0.0" }, "vscode": { "interpreter": { "hash": "0f1a8e166ce5c1ec1911a36e4fdbd34b2f623e2a3442791008b8ac429a1d6070" } }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": { "4c30743265444bf895f8672d314172ac": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "TextModel", "state": { "description": "Query start time (UTC):", "layout": "IPY_MODEL_380bc162c4b54ea98913ce5255fa28da", "style": "IPY_MODEL_bc1b97ab40c8438ba85c2510937f9ec0", "value": "2020-07-06 00:00:00" } }, "91cb50981213473e9998d2e2322619f8": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "27f7586811ee4a72b0bd5431174e8420": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ "IPY_MODEL_19f5751e938f45d8bd768abe57654fc6", "IPY_MODEL_0fc7c9c65965490c830610a053d866f2" ], "layout": "IPY_MODEL_ba1c9306dd5e4a9aaca23d200bddb5e4" } }, "c6a8c5f6b0054179a29c20f856fa9522": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "d115fcd972d8492a8ba17f79e927e903": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "9b78ce2e6cd74b32a5e9cd3d67b41527": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "2ad5f3c4f028422486d340e4feee3078": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "TextModel", "state": { "description": "Time (24hr)", "layout": "IPY_MODEL_7b560266d658437eb061865530333f6f", "style": "IPY_MODEL_e5aadef205c748fcaf51a6ccdc708e0e", "value": "00:00:00" } }, "76b75453fd964ccd97bff0e4661171c5": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ "IPY_MODEL_01ba1e590e824842974c46b92a572248", "IPY_MODEL_2ad5f3c4f028422486d340e4feee3078" ], "layout": "IPY_MODEL_fad8d1327484412eaa667c78e810d979" } }, "01ba1e590e824842974c46b92a572248": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DatePickerModel", "state": { "description": "Origin Date", "disabled": false, "layout": "IPY_MODEL_d989df2f97ed453db87a1a25b3b1b84b", "style": "IPY_MODEL_4bb0ca7c9d2140efbcab23ad776ccb83", "value": { "date": 13, "month": 6, "year": 2020 } } }, "7e3f296c3ae44d6698eaea631ce43bca": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "987c8f8d72c0495b92348113745ec55f": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "70%" } }, "1bc75883289349c4a9d4e0c0dba0dadf": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "TextModel", "state": { "description": "Time (24hr)", "layout": "IPY_MODEL_8dc37975c4df44da9a56f2102551a777", "style": "IPY_MODEL_c598b00d74804623a14e553ef00799bf", "value": "00:00:00" } }, "7afebe79bc2a4b4c883e2703e0a4fcd9": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "9c30a83472eb44609c79de6d7f4adb43": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "ProgressStyleModel", "state": { "description_width": "" } }, "c18f4a3635a24271bbb6cd22ae6b7afe": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "50%" } }, "ee502b67ea1c4eab82e4d243c02b8c73": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "SliderStyleModel", "state": { "description_width": "200px" } }, "2b73f1469341413a83189848a6e147ce": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DropdownModel", "state": { "_options_labels": [ "minute", "hour", "day", "week" ], "index": 3, "layout": "IPY_MODEL_9d870bab2ec2480e989a28e39974b49e", "style": "IPY_MODEL_8f0f37ef0c5d4a2aba9fe1ab395bc230" } }, "7fd4379b135442e880be60c07225c7b7": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "VBoxModel", "state": { "children": [ "IPY_MODEL_a2e3b13ca35745f096adf88b1b2f06a3", "IPY_MODEL_c4791dd3281740e99c36e9b1dd0d5178", "IPY_MODEL_e3f798d4a5ab4abe9242d6f5f7098f83" ], "layout": "IPY_MODEL_9c31801f75ae40d38947a7115c88f5eb" } }, "e5aadef205c748fcaf51a6ccdc708e0e": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "9113f350382c4d4aab835674cd5f0237": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "SliderStyleModel", "state": { "description_width": "initial" } }, "f17832d53284464c9c026467ceb41ab2": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "877eb05806e94a2d9a483192ff2fe37b": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "SliderStyleModel", "state": { "description_width": "initial" } }, "d1356e7bde6447b68e96d347f56fbc4a": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ "IPY_MODEL_386213aad72a446a91a574c448c07f05", "IPY_MODEL_ed884427443941b79eae35ecaede56fe" ], "layout": "IPY_MODEL_d115fcd972d8492a8ba17f79e927e903" } }, "ced87e84a0b8486190ecd97ee7e9c4d9": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HTMLModel", "state": { "layout": "IPY_MODEL_f17832d53284464c9c026467ceb41ab2", "style": "IPY_MODEL_59a62ce678e5478a81061a518ae8355e", "value": "

Set query time boundaries

" } }, "8037eeafd501428a9bb3f294fd000609": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "0882ca8f0b054004aaf6591e4dbb359c": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DropdownModel", "state": { "_options_labels": [ "minute", "hour", "day", "week" ], "index": 3, "layout": "IPY_MODEL_18bcb1655d9545609346dc96fadcc1fe", "style": "IPY_MODEL_7e3f296c3ae44d6698eaea631ce43bca" } }, "4b40f919cc6e4883a4438c518f7aa1a6": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "ProgressStyleModel", "state": { "description_width": "" } }, "8dde524a72ca47fa9b0b110111f86abd": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "b862a3d85adc495d84359398ca486dd2": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "40a81b67aa16475f856604e4cb5acf1a": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "50%" } }, "ba6f8485a4a640cea950629ddeaeba99": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "ba1c9306dd5e4a9aaca23d200bddb5e4": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "13e2344cd2774762bd54f242b8c44dfe": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "278ad59728c24b788b011bd3e813712e": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "initial" } }, "380bc162c4b54ea98913ce5255fa28da": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "50%" } }, "e3f798d4a5ab4abe9242d6f5f7098f83": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "TextModel", "state": { "description": "Query end time (UTC) : ", "layout": "IPY_MODEL_c18f4a3635a24271bbb6cd22ae6b7afe", "style": "IPY_MODEL_278ad59728c24b788b011bd3e813712e", "value": "2020-07-20 00:00:00" } }, "5b2daaeceea04259b680c257785170f3": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "FloatSliderModel", "state": { "description": "Select likelihood threshold", "layout": "IPY_MODEL_78428296b3f044c4999b780203af8403", "max": 0.010681888131276969, "min": 0.0000025413764681869644, "readout_format": ".7f", "step": 0.000536508714208626, "style": "IPY_MODEL_ee502b67ea1c4eab82e4d243c02b8c73", "value": 0.000536508714208626 } }, "9d870bab2ec2480e989a28e39974b49e": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "100px" } }, "69fbcd0371c642359e7d4fe65b5bc71f": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "VBoxModel", "state": { "children": [ "IPY_MODEL_3bae2a60c79045e3ba41fcdf7b9f9ebc", "IPY_MODEL_76b75453fd964ccd97bff0e4661171c5", "IPY_MODEL_b4659a11678c4245b01ebcffbcaf644c" ], "layout": "IPY_MODEL_8a6f67bfea514e67ad5955eddb43189f" } }, "386213aad72a446a91a574c448c07f05": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "IntProgressModel", "state": { "bar_style": "info", "description": "Progress:", "layout": "IPY_MODEL_c3c999b7957e4ce796725014c374b292", "style": "IPY_MODEL_9c30a83472eb44609c79de6d7f4adb43", "value": 100 } }, "abdefa161f0447659577406216849ea3": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "bc7fcd7d96f549f8b3383a8d6429e24a": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "initial" } }, "59a62ce678e5478a81061a518ae8355e": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "ed884427443941b79eae35ecaede56fe": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LabelModel", "state": { "layout": "IPY_MODEL_46e6a4a1759646b1ac41d1de18d4ab5d", "style": "IPY_MODEL_18729d4e1a0d41fd95c1faca1330b549", "value": "100%" } }, "65505c398f3e433197b41d3f1ff341f7": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ "IPY_MODEL_626275c71570448fbf584e2e8c2d5f7e", "IPY_MODEL_1bc75883289349c4a9d4e0c0dba0dadf" ], "layout": "IPY_MODEL_abdefa161f0447659577406216849ea3" } }, "c4791dd3281740e99c36e9b1dd0d5178": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "TextModel", "state": { "description": "Query start time (UTC):", "layout": "IPY_MODEL_31c1f260cb084bd693f71fef4a5d3845", "style": "IPY_MODEL_6db9845a51ce4df1b9e12dbee544b83f", "value": "2020-07-06 00:00:00" } }, "c3c999b7957e4ce796725014c374b292": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "d989df2f97ed453db87a1a25b3b1b84b": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "b5816bd9a55d4e889023bb9de4202982": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "70%" } }, "c598b00d74804623a14e553ef00799bf": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "bc1b97ab40c8438ba85c2510937f9ec0": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "initial" } }, "b8f5463d81294f28aa0bd31b72f95769": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "VBoxModel", "state": { "children": [ "IPY_MODEL_ced87e84a0b8486190ecd97ee7e9c4d9", "IPY_MODEL_65505c398f3e433197b41d3f1ff341f7", "IPY_MODEL_7fd4379b135442e880be60c07225c7b7" ], "layout": "IPY_MODEL_8dde524a72ca47fa9b0b110111f86abd" } }, "8dc37975c4df44da9a56f2102551a777": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "78428296b3f044c4999b780203af8403": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "60%" } }, "a2e3b13ca35745f096adf88b1b2f06a3": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ "IPY_MODEL_4aaa418fa48e43a397a963873975b2f4", "IPY_MODEL_2b73f1469341413a83189848a6e147ce" ], "layout": "IPY_MODEL_13e2344cd2774762bd54f242b8c44dfe" } }, "46e6a4a1759646b1ac41d1de18d4ab5d": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "9c31801f75ae40d38947a7115c88f5eb": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "3bae2a60c79045e3ba41fcdf7b9f9ebc": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HTMLModel", "state": { "layout": "IPY_MODEL_b64152d23e344d31ad4ce567989c71a6", "style": "IPY_MODEL_91cb50981213473e9998d2e2322619f8", "value": "

Set query time boundaries

" } }, "18729d4e1a0d41fd95c1faca1330b549": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "6db9845a51ce4df1b9e12dbee544b83f": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "initial" } }, "7b560266d658437eb061865530333f6f": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "4b9ed72ab5004db78d462304fa8e8acc": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ "IPY_MODEL_71d6580dbaba46048591f6e78a9f4dc8", "IPY_MODEL_0882ca8f0b054004aaf6591e4dbb359c" ], "layout": "IPY_MODEL_ba6f8485a4a640cea950629ddeaeba99" } }, "b64152d23e344d31ad4ce567989c71a6": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "19f5751e938f45d8bd768abe57654fc6": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "IntProgressModel", "state": { "bar_style": "info", "description": "Progress:", "layout": "IPY_MODEL_deea9207e4db4771a533ba7feafe1093", "style": "IPY_MODEL_4b40f919cc6e4883a4438c518f7aa1a6", "value": 100 } }, "71d6580dbaba46048591f6e78a9f4dc8": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "IntRangeSliderModel", "state": { "_model_name": "IntRangeSliderModel", "_view_name": "IntRangeSliderView", "description": "Time Range", "layout": "IPY_MODEL_b5816bd9a55d4e889023bb9de4202982", "max": 4, "min": -4, "style": "IPY_MODEL_9113f350382c4d4aab835674cd5f0237", "value": [ -1, 1 ] } }, "b4659a11678c4245b01ebcffbcaf644c": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "VBoxModel", "state": { "children": [ "IPY_MODEL_4b9ed72ab5004db78d462304fa8e8acc", "IPY_MODEL_4c30743265444bf895f8672d314172ac", "IPY_MODEL_8aa4edd1f4ac403b9b0dfc46e1ffc018" ], "layout": "IPY_MODEL_c6a8c5f6b0054179a29c20f856fa9522" } }, "4bb0ca7c9d2140efbcab23ad776ccb83": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "fad8d1327484412eaa667c78e810d979": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "0fc7c9c65965490c830610a053d866f2": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LabelModel", "state": { "layout": "IPY_MODEL_7afebe79bc2a4b4c883e2703e0a4fcd9", "style": "IPY_MODEL_b862a3d85adc495d84359398ca486dd2", "value": "100%" } }, "18bcb1655d9545609346dc96fadcc1fe": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "100px" } }, "31c1f260cb084bd693f71fef4a5d3845": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "50%" } }, "4a492d5b1b204ea596046abaa15a99f6": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "95%" } }, "626275c71570448fbf584e2e8c2d5f7e": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DatePickerModel", "state": { "description": "Origin Date", "disabled": false, "layout": "IPY_MODEL_8037eeafd501428a9bb3f294fd000609", "style": "IPY_MODEL_9b78ce2e6cd74b32a5e9cd3d67b41527", "value": { "date": 13, "month": 6, "year": 2020 } } }, "deea9207e4db4771a533ba7feafe1093": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "4aaa418fa48e43a397a963873975b2f4": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "IntRangeSliderModel", "state": { "_model_name": "IntRangeSliderModel", "_view_name": "IntRangeSliderView", "description": "Time Range", "layout": "IPY_MODEL_987c8f8d72c0495b92348113745ec55f", "max": 4, "min": -4, "style": "IPY_MODEL_877eb05806e94a2d9a483192ff2fe37b", "value": [ -1, 1 ] } }, "184475986c3f4c7ca441f5819f518aa6": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "95%" } }, "d32f9e99095149069a06961cba2ba950": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "95%" } }, "8aa4edd1f4ac403b9b0dfc46e1ffc018": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "TextModel", "state": { "description": "Query end time (UTC) : ", "layout": "IPY_MODEL_40a81b67aa16475f856604e4cb5acf1a", "style": "IPY_MODEL_bc7fcd7d96f549f8b3383a8d6429e24a", "value": "2020-07-20 00:00:00" } }, "8a6f67bfea514e67ad5955eddb43189f": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "8f0f37ef0c5d4a2aba9fe1ab395bc230": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } } }, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 0 }