{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# MSTICPy Pivot Functions\n", "\n", "We recently released a new version of *MSTICPy* with a feature called **Pivot functions**.\n", "You must have msticpy installed to run this notebook:\n", "```\n", "%pip install --upgrade msticpy\n", "```\n", "\n", "MSTICpy versions >= 1.0.0\n", "\n", "This feature has three main goals:\n", "- Making it easy to discover and invoke *MSTICPy* functionality\n", "- Creating a standardized way to call pivotable functions\n", "- Letting you assemble multiple functions into re-usable pipelines.\n", "\n", "Here are a couple of examples showing calling different kinds of\n", "enrichment functions from the IpAddress entity:\n", "\n", "```python\n", "\n", " >>> from msticpy.datamodel.entities import IpAddress, Host\n", " >>> IpAddress.util.ip_type(ip_str=\"157.53.1.1\"))\n", " ip result\n", " 157.53.1.1 Public\n", "\n", " >>> IpAddress.util.whois(\"157.53.1.1\"))\n", " asn asn_cidr asn_country_code asn_date asn_description asn_registry nets .....\n", " NA NA US 2015-04-01 NA arin [{'cidr': '157.53.0.0/16'...\n", "\n", " >>> IpAddress.util.geoloc(value=\"157.53.1.1\"))\n", " CountryCode CountryName State City Longitude Latitude Asn...\n", " US United States None None -97.822 37.751 None...\n", "```\n", "\n", "This second example shows a pivot function that does a data query for host\n", "logon events from a Host entity.\n", "\n", "```python\n", " >>> Host.AzureSentinel.list_host_logons(host_name=\"VictimPc\")\n", " Account EventID TimeGenerated Computer SubjectUserName SubjectDomainName\n", " NT AUTHORITY\\SYSTEM 4624 2020-10-01 22:39:36.987000+00:00 VictimPc.Contoso.Azure VictimPc$ CONTOSO\n", " NT AUTHORITY\\SYSTEM 4624 2020-10-01 22:39:37.220000+00:00 VictimPc.Contoso.Azure VictimPc$ CONTOSO\n", " NT AUTHORITY\\SYSTEM 4624 2020-10-01 22:39:42.603000+00:00 VictimPc.Contoso.Azure VictimPc$ CONTOSO\n", "```\n", "\n", "The pivot functionality exposes operations relevant to a particular\n", "entity as methods (or functions) of that entity. These operations include:\n", "\n", "- Data queries\n", "- Threat intelligence lookups\n", "- Other data lookups such as geo-location or domain resolution\n", "- and other local functionality\n", "\n", "You can also add other functions from 3rd party Python packages or\n", "ones you write yourself as pivot functions.\n", "\n", "\n", "## Terminology\n", "Before we get into things let's clear up a few terms.\n", "\n", "### Entities\n", "These are Python classes that represent real-world objects\n", "commonly encountered in CyberSec investigations and hunting. E.g. Host,\n", "URL, IP Address, Account, etc.\n", "\n", "### Pivoting\n", "This comes from the common practice in CyberSec investigations\n", "of navigating from one suspect entity to another. E.g. you might start\n", "with an alert identifying a potentially malicious IP Address, from there you\n", "'pivot' to see which hosts or accounts were communicating with that \n", "address. From there you might pivot again to look at processes running on\n", "the host or Office activity for the account." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Background Reading" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This article is available in Notebook form so that you can try out the examples. [TODO]\n", "\n", "There is also full documenation of the Pivot functionality on our [ReadtheDocs page](https://msticpy.readthedocs.io/en/latest/data_analysis/PivotFunctions.html)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Life before pivot functions\n", "\n", "Before Pivot functions your ability to use the various bits of\n", "functionality in *MSTICPy* was always bounded by you knowledge of\n", "where a certain function was (or your enthusiasm for reading the docs).\n", "\n", "For example, suppose you had an IP address that you wanted to do \n", "some simple enrichment on." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "ip_addr = \"20.72.193.242\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First you'd need to locate and import the functions. There\n", "might also be (as in the GeoIPLiteLookup class) some initialization\n", "step you'd need to do before using the functionality." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "from msticpy.context.ip_utils import get_ip_type\n", "from msticpy.context.ip_utils import get_whois_info\n", "from msticpy.context.geoip import GeoLiteLookup\n", "geoip = GeoLiteLookup()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next you might have to check the help for each function to\n", "work it parameters." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on function get_ip_type in module msticpy.context.ip_utils:\n", "\n", "get_ip_type(ip: str = None, ip_str: str = None) -> str\n", " Validate value is an IP address and determine IPType category.\n", " \n", " (IPAddress category is e.g. Private/Public/Multicast).\n", " \n", " Parameters\n", " ----------\n", " ip : str\n", " The string of the IP Address\n", " ip_str : str\n", " The string of the IP Address - alias for `ip`\n", " \n", " Returns\n", " -------\n", " str\n", " Returns ip type string using ip address module\n", "\n" ] } ], "source": [ "help(get_ip_type)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then finally run the functions" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Public'" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "get_ip_type(ip_addr)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('MICROSOFT-CORP-MSN-AS-BLOCK, US',\n", " {'nir': None,\n", " 'asn_registry': 'arin',\n", " 'asn': '8075',\n", " 'asn_cidr': '20.64.0.0/10',\n", " 'asn_country_code': 'US',\n", " 'asn_date': '2017-10-18',\n", " 'asn_description': 'MICROSOFT-CORP-MSN-AS-BLOCK, US',\n", " 'query': '20.72.193.242',\n", " 'nets': [{'cidr': '20.34.0.0/15, 20.48.0.0/12, 20.36.0.0/14, 20.40.0.0/13, 20.33.0.0/16, 20.128.0.0/16, 20.64.0.0/10',\n", " 'name': 'MSFT',\n", " 'handle': 'NET-20-33-0-0-1',\n", " 'range': '20.33.0.0 - 20.128.255.255',\n", " 'description': 'Microsoft Corporation',\n", " 'country': 'US',\n", " 'state': 'WA',\n", " 'city': 'Redmond',\n", " 'address': 'One Microsoft Way',\n", " 'postal_code': '98052',\n", " 'emails': ['msndcc@microsoft.com',\n", " 'IOC@microsoft.com',\n", " 'abuse@microsoft.com'],\n", " 'created': '2017-10-18',\n", " 'updated': '2017-10-18'}],\n", " 'raw': None,\n", " 'referral': None,\n", " 'raw_referral': None})" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "get_whois_info(ip_addr)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "([{'continent': {'code': 'NA',\n", " 'geoname_id': 6255149,\n", " 'names': {'de': 'Nordamerika',\n", " 'en': 'North America',\n", " 'es': 'Norteamérica',\n", " 'fr': 'Amérique du Nord',\n", " 'ja': '北アメリカ',\n", " 'pt-BR': 'América do Norte',\n", " 'ru': 'Северная Америка',\n", " 'zh-CN': '北美洲'}},\n", " 'country': {'geoname_id': 6252001,\n", " 'iso_code': 'US',\n", " 'names': {'de': 'USA',\n", " 'en': 'United States',\n", " 'es': 'Estados Unidos',\n", " 'fr': 'États-Unis',\n", " 'ja': 'アメリカ合衆国',\n", " 'pt-BR': 'Estados Unidos',\n", " 'ru': 'США',\n", " 'zh-CN': '美国'}},\n", " 'location': {'accuracy_radius': 1000,\n", " 'latitude': 47.6032,\n", " 'longitude': -122.3412,\n", " 'time_zone': 'America/Los_Angeles'},\n", " 'registered_country': {'geoname_id': 6252001,\n", " 'iso_code': 'US',\n", " 'names': {'de': 'USA',\n", " 'en': 'United States',\n", " 'es': 'Estados Unidos',\n", " 'fr': 'États-Unis',\n", " 'ja': 'アメリカ合衆国',\n", " 'pt-BR': 'Estados Unidos',\n", " 'ru': 'США',\n", " 'zh-CN': '美国'}},\n", " 'subdivisions': [{'geoname_id': 5815135,\n", " 'iso_code': 'WA',\n", " 'names': {'en': 'Washington',\n", " 'es': 'Washington',\n", " 'fr': 'Washington',\n", " 'ja': 'ワシントン州',\n", " 'ru': 'Вашингтон',\n", " 'zh-CN': '华盛顿州'}}],\n", " 'traits': {'ip_address': '20.72.193.242', 'prefix_len': 18}}],\n", " [IpAddress(Address=20.72.193.242, Location={ 'AdditionalData': {},\n", " 'CountryCode': 'US',\n", " ...)])" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "geoip.lookup_ip(ip_addr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At which point you'd discover that the output from each\n", "function was somewhat raw and it would take a bit more\n", "work if you wanted to combine it in any way (say in a single table).\n", "\n", "We'll see how pivot functions address these problems in the remainder\n", "of the notebook." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Getting Started with Pivot functions\n", "Typically we use *MSTICPy*'s `init_notebook` function that handles\n", "checking versions and importing some commonly-used packages and modules\n", "(both *MSTICPy* and 3rd party packages like *pandas*" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "import msticpy as mp\n", "mp.init_notebook(verbosity=0);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Pivot subsystem is loaded as part of the `init_notebook`\n", "process. This also import entities such as IpAddress, Host, Url, etc.\n", "into the notebook namespace.\n", "\n", "One class of pivot functions that are not added to entities\n", "in `init_notebook` is data queries. These are loaded when you\n", "create and connect to a QueryProvider\n", "\n", "Let's load our data query provider for MS Sentinel" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "qry_prov = QueryProvider(\"MSSentinel\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "msticpy.init_notebook loads and instantiates the Pivot class.\n", "\n", "You can do that manually, if needed:\n", "```python\n", "from msticpy.init.pivot import Pivot\n", "pivot = Pivot(namespace=globals())\n", "```\n", "\n", "Why do we need to pass `namespace=globals()`?\n", "Pivot searches through the current objects defined in the Python/notebook\n", "namespace. This is most relevant for QueryProviders. In most other cases\n", "(like GeoIP and ThreatIntel providers, it will create new ones if it\n", "can't find existing ones)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Easy discovery of functionality" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Find the entity name you need\n", "\n", "The simplest way to do this is to use the `entities.find_entity`\n", "function.\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Match found 'IpAddress'\n" ] }, { "data": { "text/plain": [ "msticpy.datamodel.entities.ip_address.IpAddress" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "entities.find_entity(\"ip\")" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "No exact match found for 'azure'. \n", "Closest matches are 'AzureResource', 'Url', 'Malware'\n" ] } ], "source": [ "entities.find_entity(\"azure\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Listing pivot functions available for an entity\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once you have the entity you can use the `pivots()`\n", "function to see which pivot functions are available for it." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['RiskIQ.articles',\n", " 'RiskIQ.artifacts',\n", " 'RiskIQ.certificates',\n", " 'RiskIQ.components',\n", " 'RiskIQ.cookies',\n", " 'RiskIQ.hostpair_children',\n", " 'RiskIQ.hostpair_parents',\n", " 'RiskIQ.malware',\n", " 'RiskIQ.projects',\n", " 'RiskIQ.reputation',\n", " 'RiskIQ.resolutions',\n", " 'RiskIQ.services',\n", " 'RiskIQ.summary',\n", " 'RiskIQ.trackers',\n", " 'RiskIQ.whois',\n", " 'VT.vt_communicating_files',\n", " 'VT.vt_historical_ssl_certificates',\n", " 'VT.vt_historical_whois',\n", " 'VT.vt_referrer_files',\n", " 'VT.vt_resolutions',\n", " 'VT.vt_subdomains',\n", " 'geoloc',\n", " 'ip_type',\n", " 'ti.lookup_ip',\n", " 'tilookup_ip',\n", " 'util.geoloc',\n", " 'util.geoloc_ips',\n", " 'util.ip_rev_resolve',\n", " 'util.ip_type',\n", " 'util.whois',\n", " 'vt_communicating_files',\n", " 'vt_historical_ssl_certificates',\n", " 'vt_historical_whois',\n", " 'vt_referrer_files',\n", " 'vt_resolutions',\n", " 'vt_subdomains',\n", " 'whois']" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "IpAddress.pivots()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some of the function names are a little unweildy but, in \n", "many cases, this is necessary to avoid name collisions.\n", "You might notice from the list that the functions are\n", "grouped into containers such as \"ti\" and \"util\" in \n", "the above example.\n", "\n", "Although this makes the function name even longer we thought\n", "that this helped to keep related functionality together - so\n", "you don't get a TI lookup, when you thought you were running\n", "a query.\n", "\n", "Fortunately Jupyter notebooks/IPython support tab completion\n", "so you should not normally have to remember these names.\n" ] }, { "attachments": { "64c1580e-21f7-4ed3-af12-4f59ced1d67b.png": { "image/png": "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YgWam5vR19eH8+fPY9asWYiKikJaWpqD5EZFRWHEiBH44x//iMbGRly5cgWPPfYY7rrrLjz55JMBk9xQg5JLySVkKKHkBg9Krh94Krl5eXkOz40da72b+sqVKwHphzvJBYB77rlH9z0TEhIwfvx4m8ekWCYlJaG21nZOSDnyqxW/9evXQwiBb7/91uF9y8rKIIR1doBAMJjlCvYjzmazGUIIJCQk2Dy+du1aCGEd6faGwZDcDz/8EEIIZGZmOtTkmkwmjBs3DlFRUTh16pSD5L766qs2NbkHDx7EXXfdhdTU1IiVXEIIGUooucGDkusHnkquHlK0jhw5EpB+eCK5Uvy0NbJFRUUOjwEDkqv92d3+PceOHavu+5NPPunRSGsgGEzJ1dum7L/2c54zZw6EEDh69KhXfR8MyZWzTezZs0f3xrPly5cjKioKO3bscJDctrY2G8n97rvvcNddd+Gee+6h5BJCyCBAyQ0elFw/CITkfv755wHphyeSW19fDyEEpk+frj62dOlS3RvSPJFcrfgNN8mV7c1m7yaDHwzJlftz+vRpXcl94403KLmEEBIiUHKDByXXD/yR3CeeeMJr2XHVD08kFxi4Aa2yshJmsxkJCQlYunSpQztXkit/wtfuu5TcYCzGEAqSO3PmTLd90GMwJHfRokUcySWEkDCBkhs8KLl+4KvkGgwGKIoCRVHQ09MTkH54Oqoob0DLyclR///EiRMO7VxJbl5enkP9qrxBK9DThenhieTK/r+0YoXN42VlZeqMCP5IriwReO2117zqu5TcU6dOefwad5L73nvvQQiBJUuW6NbkJiYmIioqChcuXKDkEkLIEEPJDR6UXD/wVHJLS0tVAS0vL8eECRNUAQvEF0HOXvDSihVobW112z4xMREJCQl44oknbEoXtEhJnD59Oq5duwbAOoK7detWVRK19agnTpyAENa5eHNycjzqh694Irmy1jgpKUmd+eDAgQPqHLL+Sq7cXyEEVq1ahaamJgDuZ1d45hnralIZGRnqa9zhTnLr6urUz2T16tW6syssWLBAd3YFSi4hhAQXSm7woOR6gfyZ21W0uGonF4kIBHJmA/to58nV8vLLL6tt9GZ+ANxP26VXBqA3t6s2gSpl8ERyzWazOq2WNoqiqDeN+SO57vbXWf2xlG933x13c+tOmTLF5iTz1ltvObSR8+SO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" } }, "cell_type": "markdown", "metadata": {}, "source": [ "![image.png](attachment:64c1580e-21f7-4ed3-af12-4f59ced1d67b.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The containers (\"util\", etc.) are also callable\n", "functions - they just return the list of functions they contain." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "geoloc (pivot function)\n", "geoloc_ips (pivot function)\n", "ip_rev_resolve (pivot function)\n", "ip_type (pivot function)\n", "whois (pivot function)\n" ] } ], "source": [ "IpAddress.util()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we're ready to run any of the functions for this entity" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
ipresultsrc_row_index
020.72.193.242Public0
\n", "
" ], "text/plain": [ " ip result src_row_index\n", "0 20.72.193.242 Public 0" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ip_addr = \"20.72.193.242\"\n", "IpAddress.util.ip_type(ip_addr)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
asnasn_cidrasn_country_codeasn_dateasn_descriptionasn_registrynetsnirqueryrawraw_referralreferral
0807520.64.0.0/10US2017-10-18MICROSOFT-CORP-MSN-AS-BLOCK, USarin[{'cidr': '20.40.0.0/13, 20.34.0.0/15, 20.48.0.0/12, 20.64.0.0/10, 20.33.0.0/16, 20.128.0.0/16, ...None20.72.193.242NoneNoneNone
\n", "
" ], "text/plain": [ " asn asn_cidr asn_country_code asn_date \\\n", "0 8075 20.64.0.0/10 US 2017-10-18 \n", "\n", " asn_description asn_registry \\\n", "0 MICROSOFT-CORP-MSN-AS-BLOCK, US arin \n", "\n", " nets \\\n", "0 [{'cidr': '20.40.0.0/13, 20.34.0.0/15, 20.48.0.0/12, 20.64.0.0/10, 20.33.0.0/16, 20.128.0.0/16, ... \n", "\n", " nir query raw raw_referral referral \n", "0 None 20.72.193.242 None None None " ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "IpAddress.util.whois(ip_addr)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
qnamerdtyperesponseip_addresssrc_row_index
020.72.193.242PTRNone of DNS query names exist: 20.72.193.242., 20.72.193.242.corp.microsoft.com.20.72.193.2420
\n", "
" ], "text/plain": [ " qname rdtype \\\n", "0 20.72.193.242 PTR \n", "\n", " response \\\n", "0 None of DNS query names exist: 20.72.193.242., 20.72.193.242.corp.microsoft.com. \n", "\n", " ip_address src_row_index \n", "0 20.72.193.242 0 " ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "IpAddress.util.ip_rev_resolve(ip_addr)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
CountryCodeCountryNameStateLongitudeLatitudeTimeGeneratedTypeIpAddress
0USUnited StatesWashington-122.341447.60342022-04-22 03:03:14.422813geolocation20.72.193.242
\n", "
" ], "text/plain": [ " CountryCode CountryName State Longitude Latitude \\\n", "0 US United States Washington -122.3414 47.6034 \n", "\n", " TimeGenerated Type IpAddress \n", "0 2022-04-22 03:03:14.422813 geolocation 20.72.193.242 " ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "IpAddress.util.geoloc(ip_addr)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
IocIocTypeSafeIocQuerySubtypeProviderResultSeverityDetailsRawResultReferenceStatus
020.72.193.242ipv420.72.193.242NoneRiskIQTruehigh{'summary': {'resolutions': 0, 'certificates': 0, 'malware_hashes': 0, 'projects': 0, 'articles'...{'summary': {'resolutions': 0, 'certificates': 0, 'malware_hashes': 0, 'projects': 0, 'articles'...https://community.riskiq.com0
020.72.193.242ipv420.72.193.242NoneTorTrueinformationNot found.Nonehttps://check.torproject.org/exit-addresses0
020.72.193.242ipv420.72.193.242NoneVirusTotalTrueinformation{'verbose_msg': 'IP address in dataset', 'response_code': 1, 'positives': 0, 'detected_urls': []}{'detected_urls': [], 'asn': 8075, 'country': 'US', 'response_code': 1, 'as_owner': 'MICROSOFT-C...https://www.virustotal.com/vtapi/v2/ip-address/report0
020.72.193.242ipv420.72.193.242NoneXForceFalseinformationAuthorization failed. Check account and key details.<Response [401 Unauthorized]>https://api.xforce.ibmcloud.com/ipr/20.72.193.242401
\n", "
" ], "text/plain": [ " Ioc IocType SafeIoc QuerySubtype Provider Result \\\n", "0 20.72.193.242 ipv4 20.72.193.242 None RiskIQ True \n", "0 20.72.193.242 ipv4 20.72.193.242 None Tor True \n", "0 20.72.193.242 ipv4 20.72.193.242 None VirusTotal True \n", "0 20.72.193.242 ipv4 20.72.193.242 None XForce False \n", "\n", " Severity \\\n", "0 high \n", "0 information \n", "0 information \n", "0 information \n", "\n", " Details \\\n", "0 {'summary': {'resolutions': 0, 'certificates': 0, 'malware_hashes': 0, 'projects': 0, 'articles'... \n", "0 Not found. \n", "0 {'verbose_msg': 'IP address in dataset', 'response_code': 1, 'positives': 0, 'detected_urls': []} \n", "0 Authorization failed. Check account and key details. \n", "\n", " RawResult \\\n", "0 {'summary': {'resolutions': 0, 'certificates': 0, 'malware_hashes': 0, 'projects': 0, 'articles'... \n", "0 None \n", "0 {'detected_urls': [], 'asn': 8075, 'country': 'US', 'response_code': 1, 'as_owner': 'MICROSOFT-C... \n", "0 \n", "\n", " Reference Status \n", "0 https://community.riskiq.com 0 \n", "0 https://check.torproject.org/exit-addresses 0 \n", "0 https://www.virustotal.com/vtapi/v2/ip-address/report 0 \n", "0 https://api.xforce.ibmcloud.com/ipr/20.72.193.242 401 " ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "IpAddress.ti.lookup_ip(ip_addr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that we didn't need to worry about either the parameter\n", "name or format (more on this in the next section). Also, \n", "whatever the function, the output is always returned\n", "as a pandas DataFrame." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### For Data query functions you *do* need to worry about the parameter name\n", "Data query functions are a little more complex than most other functions\n", "and specifically often support many parameters. Rather than try\n", "to guess which parameter you meant, we require you to be explicit.\n", "\n", "To use a data query, we need to authenticate to the provider." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Connecting... " ] }, { "data": { "text/html": [ "\n", " \n", "
\n", " popup schema 8ecf8077-cf51-4820-aadd-14040956f35d@loganalytics\n", "
\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "connected\n" ] } ], "source": [ "qry_prov.connect(workspace=\"CyberSecuritySoc\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We should now have many more data query pivots\n", "attached to our entities" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['MSSentinel_cybersecuritysoc.VMComputer_vmcomputer',\n", " 'MSSentinel_cybersecuritysoc.auditd_auditd_all',\n", " 'MSSentinel_cybersecuritysoc.az_nsg_interface',\n", " 'MSSentinel_cybersecuritysoc.az_nsg_net_flows',\n", " 'MSSentinel_cybersecuritysoc.az_nsg_net_flows_depr',\n", " 'MSSentinel_cybersecuritysoc.heartbeat',\n", " 'MSSentinel_cybersecuritysoc.heartbeat_for_host_depr',\n", " 'MSSentinel_cybersecuritysoc.sec_alerts',\n", " 'MSSentinel_cybersecuritysoc.sent_bookmarks',\n", " 'MSSentinel_cybersecuritysoc.syslog_all_syslog',\n", " 'MSSentinel_cybersecuritysoc.syslog_cron_activity',\n", " 'MSSentinel_cybersecuritysoc.syslog_logon_failures',\n", " 'MSSentinel_cybersecuritysoc.syslog_logons',\n", " 'MSSentinel_cybersecuritysoc.syslog_squid_activity',\n", " 'MSSentinel_cybersecuritysoc.syslog_sudo_activity',\n", " 'MSSentinel_cybersecuritysoc.syslog_user_group_activity',\n", " 'MSSentinel_cybersecuritysoc.syslog_user_logon',\n", " 'MSSentinel_cybersecuritysoc.wevt_all_events',\n", " 'MSSentinel_cybersecuritysoc.wevt_events_by_id',\n", " 'MSSentinel_cybersecuritysoc.wevt_get_process_tree',\n", " 'MSSentinel_cybersecuritysoc.wevt_list_other_events',\n", " 'MSSentinel_cybersecuritysoc.wevt_logon_attempts',\n", " 'MSSentinel_cybersecuritysoc.wevt_logon_failures',\n", " 'MSSentinel_cybersecuritysoc.wevt_logon_session',\n", " 'MSSentinel_cybersecuritysoc.wevt_logons',\n", " 'MSSentinel_cybersecuritysoc.wevt_parent_process',\n", " 'MSSentinel_cybersecuritysoc.wevt_process_session',\n", " 'MSSentinel_cybersecuritysoc.wevt_processes',\n", " 'RiskIQ.articles',\n", " 'RiskIQ.artifacts',\n", " 'RiskIQ.certificates',\n", " 'RiskIQ.components',\n", " 'RiskIQ.cookies',\n", " 'RiskIQ.hostpair_children',\n", " 'RiskIQ.hostpair_parents',\n", " 'RiskIQ.malware',\n", " 'RiskIQ.projects',\n", " 'RiskIQ.reputation',\n", " 'RiskIQ.resolutions',\n", " 'RiskIQ.summary',\n", " 'RiskIQ.trackers',\n", " 'RiskIQ.whois',\n", " 'dns_is_resolvable',\n", " 'dns_resolve',\n", " 'util.dns_components',\n", " 'util.dns_in_abuse_list',\n", " 'util.dns_is_resolvable',\n", " 'util.dns_resolve',\n", " 'util.dns_validate_tld']" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Host.pivots()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you are not sure of the parameters required by the query\n", "you can use the built-in help" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1;31mSignature:\u001b[0m \u001b[0mHost\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mMSSentinel_cybersecuritysoc\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msec_alerts\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m->\u001b[0m \u001b[0mUnion\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mpandas\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcore\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mframe\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mDataFrame\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mAny\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mDocstring:\u001b[0m\n", "Retrieves list of alerts with a common host, account or process\n", "\n", "Parameters\n", "----------\n", "account_name: str (optional)\n", " The account name to find\n", "add_query_items: str (optional)\n", " Additional query clauses\n", "end: datetime\n", " Query end time\n", "host_name: str (optional)\n", " The hostname to find\n", "path_separator: str (optional)\n", " Path separator\n", " (default value is: \\\\)\n", "process_name: str (optional)\n", " The process name to find\n", "query_project: str (optional)\n", " Column project statement\n", " (default value is: | project-rename StartTimeUtc = StartTime, EndTim...)\n", "start: datetime\n", " Query start time\n", "table: str (optional)\n", " Table name\n", " (default value is: SecurityAlert)\n", "\u001b[1;31mFile:\u001b[0m f:\\anaconda\\envs\\msticpy\\lib\\functools.py\n", "\u001b[1;31mType:\u001b[0m function\n" ] } ], "source": [ "Host.MSSentinel_cybersecuritysoc.sec_alerts?" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "application/javascript": "try {IPython.notebook.kernel.execute(\"NOTEBOOK_URL = '\" + window.location + \"'\");} catch(err) {;}", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TenantIdTimeGeneratedAlertDisplayNameAlertNameSeverityDescriptionProviderNameVendorNameVendorOriginalIdSystemAlertIdResourceIdSourceComputerIdAlertTypeConfidenceLevelConfidenceScoreIsIncidentStartTimeUtcEndTimeUtcProcessingEndTimeRemediationStepsExtendedPropertiesEntitiesSourceSystemWorkspaceSubscriptionIdWorkspaceResourceGroupExtendedLinksProductNameProductComponentNameAlertLinkStatusCompromisedEntityTacticsTypeComputersrc_hostnamesrc_accountnamesrc_procnamehost_matchacct_matchproc_match
08ecf8077-cf51-4820-aadd-14040956f35d2021-03-11 12:05:14.355000+00:00Suspected credential theft activitySuspected credential theft activityMediumThis program exhibits suspect characteristics potentially associated with credential theft. Onc...MDATPMicrosoftda637509097413415122_-841817867bf226b1b-8bda-31f7-c848-1f8bbb5f5922WindowsDefenderAtpNaNFalse2021-03-09 17:56:55.275000+00:002021-03-09 17:56:55.275000+00:002021-03-11 12:05:13.759000+00:00[\\r\\n \"1. Make sure the machine is completely updated and all your software has the latest patc...{\\r\\n \"MicrosoftDefenderAtp.Category\": \"CredentialAccess\",\\r\\n \"MicrosoftDefenderAtp.Investiga...[\\r\\n {\\r\\n \"$id\": \"4\",\\r\\n \"DnsDomain\": \"na.contosohotels.com\",\\r\\n \"HostName\": \"vict...DetectionMicrosoft Defender Advanced Threat Protectionhttps://securitycenter.microsoft.com/alert/da637509097413415122_-841817867?tid=4b2462a4-bbee-495...Newvictim00.na.contosohotels.comCredentialAccessSecurityAlertvictim00victim00TrueFalseFalse
18ecf8077-cf51-4820-aadd-14040956f35d2021-03-11 13:24:53.495000+00:00'Mimikatz' hacktool was detected'Mimikatz' hacktool was detectedLowReadily available tools, such as hacking programs, can be used by unauthorized individuals to sp...MDATPMicrosoftda637510393722104539_-1180405651ef04126b-2683-0a98-d01c-77ee6b1115acWindowsDefenderAvNaNFalse2021-03-11 06:00:14.083000+00:002021-03-11 06:00:14.083000+00:002021-03-11 13:24:53.379000+00:00[\\r\\n \"1. Make sure the machine is completely updated and all your software has the latest patc...{\\r\\n \"MicrosoftDefenderAtp.Category\": \"Malware\",\\r\\n \"MicrosoftDefenderAtp.InvestigationId\": ...[\\r\\n {\\r\\n \"$id\": \"4\",\\r\\n \"DnsDomain\": \"na.contosohotels.com\",\\r\\n \"HostName\": \"vict...DetectionMicrosoft Defender Advanced Threat Protectionhttps://securitycenter.microsoft.com/alert/da637510393722104539_-1180405651?tid=4b2462a4-bbee-49...Newvictim00.na.contosohotels.comUnknownSecurityAlertvictim00victim00TrueFalseFalse
28ecf8077-cf51-4820-aadd-14040956f35d2021-03-11 13:24:53.490000+00:00Suspected credential theft activitySuspected credential theft activityMediumThis program exhibits suspect characteristics potentially associated with credential theft. Onc...MDATPMicrosoftda637509097413415122_-841817867bf226b1b-8bda-31f7-c848-1f8bbb5f5922WindowsDefenderAtpNaNFalse2021-03-09 17:56:55.275000+00:002021-03-09 17:56:55.275000+00:002021-03-11 13:24:53.363000+00:00[\\r\\n \"1. Make sure the machine is completely updated and all your software has the latest patc...{\\r\\n \"MicrosoftDefenderAtp.Category\": \"CredentialAccess\",\\r\\n \"MicrosoftDefenderAtp.Investiga...[\\r\\n {\\r\\n \"$id\": \"4\",\\r\\n \"DnsDomain\": \"na.contosohotels.com\",\\r\\n \"HostName\": \"vict...DetectionMicrosoft Defender Advanced Threat Protectionhttps://securitycenter.microsoft.com/alert/da637509097413415122_-841817867?tid=4b2462a4-bbee-495...Newvictim00.na.contosohotels.comCredentialAccessSecurityAlertvictim00victim00TrueFalseFalse
38ecf8077-cf51-4820-aadd-14040956f35d2021-03-11 13:19:42.521000+00:00Malicious credential theft tool execution detectedMalicious credential theft tool execution detectedHighA known credential theft tool execution command line was detected.\\nEither the process itself or...MDATPMicrosoftda637508847019595161_-562481393753680a5-4d20-2726-61b4-9c36e620ea26WindowsDefenderAtpNaNFalse2021-03-09 10:56:58.134000+00:002021-03-09 10:56:58.134000+00:002021-03-11 13:19:42.289000+00:00[\\r\\n \"1. Make sure the machine is completely updated and all your software has the latest patc...{\\r\\n \"MicrosoftDefenderAtp.Category\": \"CredentialAccess\",\\r\\n \"MicrosoftDefenderAtp.Investiga...[\\r\\n {\\r\\n \"$id\": \"4\",\\r\\n \"DnsDomain\": \"na.contosohotels.com\",\\r\\n \"HostName\": \"vict...DetectionMicrosoft Defender Advanced Threat Protectionhttps://securitycenter.microsoft.com/alert/da637508847019595161_-562481393?tid=4b2462a4-bbee-495...Newvictim00.na.contosohotels.comCredentialAccessSecurityAlertvictim00victim00TrueFalseFalse
48ecf8077-cf51-4820-aadd-14040956f35d2021-03-11 14:30:14.730000+00:00'Mimikatz' hacktool was detected'Mimikatz' hacktool was detectedLowReadily available tools, such as hacking programs, can be used by unauthorized individuals to sp...MDATPMicrosoftda637510393722104539_-1180405651ef04126b-2683-0a98-d01c-77ee6b1115acWindowsDefenderAvNaNFalse2021-03-11 06:00:14.083000+00:002021-03-11 06:00:14.083000+00:002021-03-11 14:30:14.450000+00:00[\\r\\n \"1. Make sure the machine is completely updated and all your software has the latest patc...{\\r\\n \"MicrosoftDefenderAtp.Category\": \"Malware\",\\r\\n \"MicrosoftDefenderAtp.InvestigationId\": ...[\\r\\n {\\r\\n \"$id\": \"4\",\\r\\n \"DnsDomain\": \"na.contosohotels.com\",\\r\\n \"HostName\": \"vict...DetectionMicrosoft Defender Advanced Threat Protectionhttps://securitycenter.microsoft.com/alert/da637510393722104539_-1180405651?tid=4b2462a4-bbee-49...Newvictim00.na.contosohotels.comUnknownSecurityAlertvictim00victim00TrueFalseFalse
\n", "
" ], "text/plain": [ " TenantId TimeGenerated \\\n", "0 8ecf8077-cf51-4820-aadd-14040956f35d 2021-03-11 12:05:14.355000+00:00 \n", "1 8ecf8077-cf51-4820-aadd-14040956f35d 2021-03-11 13:24:53.495000+00:00 \n", "2 8ecf8077-cf51-4820-aadd-14040956f35d 2021-03-11 13:24:53.490000+00:00 \n", "3 8ecf8077-cf51-4820-aadd-14040956f35d 2021-03-11 13:19:42.521000+00:00 \n", "4 8ecf8077-cf51-4820-aadd-14040956f35d 2021-03-11 14:30:14.730000+00:00 \n", "\n", " AlertDisplayName \\\n", "0 Suspected credential theft activity \n", "1 'Mimikatz' hacktool was detected \n", "2 Suspected credential theft activity \n", "3 Malicious credential theft tool execution detected \n", "4 'Mimikatz' hacktool was detected \n", "\n", " AlertName Severity \\\n", "0 Suspected credential theft activity Medium \n", "1 'Mimikatz' hacktool was detected Low \n", "2 Suspected credential theft activity Medium \n", "3 Malicious credential theft tool execution detected High \n", "4 'Mimikatz' hacktool was detected Low \n", "\n", " Description \\\n", "0 This program exhibits suspect characteristics potentially associated with credential theft. Onc... \n", "1 Readily available tools, such as hacking programs, can be used by unauthorized individuals to sp... \n", "2 This program exhibits suspect characteristics potentially associated with credential theft. Onc... \n", "3 A known credential theft tool execution command line was detected.\\nEither the process itself or... \n", "4 Readily available tools, such as hacking programs, can be used by unauthorized individuals to sp... \n", "\n", " ProviderName VendorName VendorOriginalId \\\n", "0 MDATP Microsoft da637509097413415122_-841817867 \n", "1 MDATP Microsoft da637510393722104539_-1180405651 \n", "2 MDATP Microsoft da637509097413415122_-841817867 \n", "3 MDATP Microsoft da637508847019595161_-562481393 \n", "4 MDATP Microsoft da637510393722104539_-1180405651 \n", "\n", " SystemAlertId ResourceId SourceComputerId \\\n", "0 bf226b1b-8bda-31f7-c848-1f8bbb5f5922 \n", "1 ef04126b-2683-0a98-d01c-77ee6b1115ac \n", "2 bf226b1b-8bda-31f7-c848-1f8bbb5f5922 \n", "3 753680a5-4d20-2726-61b4-9c36e620ea26 \n", "4 ef04126b-2683-0a98-d01c-77ee6b1115ac \n", "\n", " AlertType ConfidenceLevel ConfidenceScore IsIncident \\\n", "0 WindowsDefenderAtp NaN False \n", "1 WindowsDefenderAv NaN False \n", "2 WindowsDefenderAtp NaN False \n", "3 WindowsDefenderAtp NaN False \n", "4 WindowsDefenderAv NaN False \n", "\n", " StartTimeUtc EndTimeUtc \\\n", "0 2021-03-09 17:56:55.275000+00:00 2021-03-09 17:56:55.275000+00:00 \n", "1 2021-03-11 06:00:14.083000+00:00 2021-03-11 06:00:14.083000+00:00 \n", "2 2021-03-09 17:56:55.275000+00:00 2021-03-09 17:56:55.275000+00:00 \n", "3 2021-03-09 10:56:58.134000+00:00 2021-03-09 10:56:58.134000+00:00 \n", "4 2021-03-11 06:00:14.083000+00:00 2021-03-11 06:00:14.083000+00:00 \n", "\n", " ProcessingEndTime \\\n", "0 2021-03-11 12:05:13.759000+00:00 \n", "1 2021-03-11 13:24:53.379000+00:00 \n", "2 2021-03-11 13:24:53.363000+00:00 \n", "3 2021-03-11 13:19:42.289000+00:00 \n", "4 2021-03-11 14:30:14.450000+00:00 \n", "\n", " RemediationSteps \\\n", "0 [\\r\\n \"1. Make sure the machine is completely updated and all your software has the latest patc... \n", "1 [\\r\\n \"1. Make sure the machine is completely updated and all your software has the latest patc... \n", "2 [\\r\\n \"1. Make sure the machine is completely updated and all your software has the latest patc... \n", "3 [\\r\\n \"1. Make sure the machine is completely updated and all your software has the latest patc... \n", "4 [\\r\\n \"1. Make sure the machine is completely updated and all your software has the latest patc... \n", "\n", " ExtendedProperties \\\n", "0 {\\r\\n \"MicrosoftDefenderAtp.Category\": \"CredentialAccess\",\\r\\n \"MicrosoftDefenderAtp.Investiga... \n", "1 {\\r\\n \"MicrosoftDefenderAtp.Category\": \"Malware\",\\r\\n \"MicrosoftDefenderAtp.InvestigationId\": ... \n", "2 {\\r\\n \"MicrosoftDefenderAtp.Category\": \"CredentialAccess\",\\r\\n \"MicrosoftDefenderAtp.Investiga... \n", "3 {\\r\\n \"MicrosoftDefenderAtp.Category\": \"CredentialAccess\",\\r\\n \"MicrosoftDefenderAtp.Investiga... \n", "4 {\\r\\n \"MicrosoftDefenderAtp.Category\": \"Malware\",\\r\\n \"MicrosoftDefenderAtp.InvestigationId\": ... \n", "\n", " Entities \\\n", "0 [\\r\\n {\\r\\n \"$id\": \"4\",\\r\\n \"DnsDomain\": \"na.contosohotels.com\",\\r\\n \"HostName\": \"vict... \n", "1 [\\r\\n {\\r\\n \"$id\": \"4\",\\r\\n \"DnsDomain\": \"na.contosohotels.com\",\\r\\n \"HostName\": \"vict... \n", "2 [\\r\\n {\\r\\n \"$id\": \"4\",\\r\\n \"DnsDomain\": \"na.contosohotels.com\",\\r\\n \"HostName\": \"vict... \n", "3 [\\r\\n {\\r\\n \"$id\": \"4\",\\r\\n \"DnsDomain\": \"na.contosohotels.com\",\\r\\n \"HostName\": \"vict... \n", "4 [\\r\\n {\\r\\n \"$id\": \"4\",\\r\\n \"DnsDomain\": \"na.contosohotels.com\",\\r\\n \"HostName\": \"vict... \n", "\n", " SourceSystem WorkspaceSubscriptionId WorkspaceResourceGroup ExtendedLinks \\\n", "0 Detection \n", "1 Detection \n", "2 Detection \n", "3 Detection \n", "4 Detection \n", "\n", " ProductName ProductComponentName \\\n", "0 Microsoft Defender Advanced Threat Protection \n", "1 Microsoft Defender Advanced Threat Protection \n", "2 Microsoft Defender Advanced Threat Protection \n", "3 Microsoft Defender Advanced Threat Protection \n", "4 Microsoft Defender Advanced Threat Protection \n", "\n", " AlertLink \\\n", "0 https://securitycenter.microsoft.com/alert/da637509097413415122_-841817867?tid=4b2462a4-bbee-495... \n", "1 https://securitycenter.microsoft.com/alert/da637510393722104539_-1180405651?tid=4b2462a4-bbee-49... \n", "2 https://securitycenter.microsoft.com/alert/da637509097413415122_-841817867?tid=4b2462a4-bbee-495... \n", "3 https://securitycenter.microsoft.com/alert/da637508847019595161_-562481393?tid=4b2462a4-bbee-495... \n", "4 https://securitycenter.microsoft.com/alert/da637510393722104539_-1180405651?tid=4b2462a4-bbee-49... \n", "\n", " Status CompromisedEntity Tactics Type \\\n", "0 New victim00.na.contosohotels.com CredentialAccess SecurityAlert \n", "1 New victim00.na.contosohotels.com Unknown SecurityAlert \n", "2 New victim00.na.contosohotels.com CredentialAccess SecurityAlert \n", "3 New victim00.na.contosohotels.com CredentialAccess SecurityAlert \n", "4 New victim00.na.contosohotels.com Unknown SecurityAlert \n", "\n", " Computer src_hostname src_accountname src_procname host_match acct_match \\\n", "0 victim00 victim00 True False \n", "1 victim00 victim00 True False \n", "2 victim00 victim00 True False \n", "3 victim00 victim00 True False \n", "4 victim00 victim00 True False \n", "\n", " proc_match \n", "0 False \n", "1 False \n", "2 False \n", "3 False \n", "4 False " ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Host.MSSentinel_cybersecuritysoc.sec_alerts(host_name=\"victim00\").head(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Browse and search for Pivot functions with the pivot browse" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "7802c79e943c4b289ae27f59572698e3", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(HBox(children=(VBox(children=(HTML(value='Entities'), Select(description='entity', layou…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "Pivot.browse()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Standardized way of calling Pivot functions\n", "\n", "Due to various factors (historical, underlying data,\n", "developer laziness and forgetfullness, etc.) the functionality\n", "in *MSTICPy* can be inconsistent in the way it uses input\n", "parameters.\n", "\n", "Also, many functions will only accept inputs as a single\n", "value, or a list or a DataFrame or some unpredictable combination\n", "of these.\n", "\n", "Pivot functions allow you to largely forget about this - you\n", "can use the same function whether you have:\n", "- a single value\n", "- a list (or any iterable) of values\n", "- a DataFrame with the input value in one of the columns.\n", "\n", "Let's take an example. \n", "\n", "Suppose we have a set of IP addresses pasted\n", "from somewhere that we want to use as input." ] }, { "cell_type": "raw", "metadata": {}, "source": [ "0, 172.217.15.99, Public\n", "1, 40.85.232.64, Public\n", "2, 20.38.98.100, Public\n", "3, 23.96.64.84, Public\n", "4, 65.55.44.108, Public\n", "5, 131.107.147.209, Public\n", "6, 10.0.3.4, Private\n", "7, 10.0.3.5, Private\n", "8, 13.82.152.48, Public" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We need to convert this into a Python data object of some sort.\n", "To do this we can use another Pivot utility `%%txt2df`. This is a\n", "Jupyter/IPython magic function so you can just paste you data in\n", "a cell.\n", "Use `%%txt2df --help` in an empty cell to see the full syntax.\n", "\n", "The example below we specify a comma separator, that the\n", "data has a headers row and to save the converted data as\n", "a DataFrame named \"ip_df\".\n", "\n", "> Warning this will overwrite any existing variable of this\n", "name" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idxiptype
00172.217.15.99Public
1140.85.232.64Public
2220.38.98.100Public
3323.96.64.84Public
4465.55.44.108Public
55131.107.147.209Public
6610.0.3.4Private
7710.0.3.5Private
8813.82.152.48Public
\n", "
" ], "text/plain": [ " idx ip type\n", "0 0 172.217.15.99 Public\n", "1 1 40.85.232.64 Public\n", "2 2 20.38.98.100 Public\n", "3 3 23.96.64.84 Public\n", "4 4 65.55.44.108 Public\n", "5 5 131.107.147.209 Public\n", "6 6 10.0.3.4 Private\n", "7 7 10.0.3.5 Private\n", "8 8 13.82.152.48 Public" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%%txt2df --sep , --headers --name ip_df\n", "idx, ip, type\n", "0, 172.217.15.99, Public\n", "1, 40.85.232.64, Public\n", "2, 20.38.98.100, Public\n", "3, 23.96.64.84, Public\n", "4, 65.55.44.108, Public\n", "5, 131.107.147.209, Public\n", "6, 10.0.3.4, Private\n", "7, 10.0.3.5, Private\n", "8, 13.82.152.48, Public\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For our example we'll also create a standard Python list\n", "from the ip column." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['172.217.15.99', '40.85.232.64', '20.38.98.100', '23.96.64.84', '65.55.44.108', '131.107.147.209', '10.0.3.4', '10.0.3.5', '13.82.152.48']\n" ] } ], "source": [ "ip_list = list(ip_df.ip)\n", "print(ip_list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How did this work before?\n", "\n", "If you recall the earlier example of `get_ip_type`, passing it\n", "a list or DataFrame doesn't result in anything useful." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['172.217.15.99', '40.85.232.64', '20.38.98.100', '23.96.64.84', '65.55.44.108', '131.107.147.209', '10.0.3.4', '10.0.3.5', '13.82.152.48'] does not appear to be an IPv4 or IPv6 address\n" ] }, { "data": { "text/plain": [ "'Unspecified'" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "get_ip_type(ip_list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Pivot versions are much more forgiving" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The pivotized version of get_ip_type can accept and correctly process\n", "a list" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
ipresultsrc_row_index
0172.217.15.99Public0
140.85.232.64Public1
220.38.98.100Public2
323.96.64.84Public3
465.55.44.108Public4
5131.107.147.209Public5
610.0.3.4Private6
710.0.3.5Private7
813.82.152.48Public8
\n", "
" ], "text/plain": [ " ip result src_row_index\n", "0 172.217.15.99 Public 0\n", "1 40.85.232.64 Public 1\n", "2 20.38.98.100 Public 2\n", "3 23.96.64.84 Public 3\n", "4 65.55.44.108 Public 4\n", "5 131.107.147.209 Public 5\n", "6 10.0.3.4 Private 6\n", "7 10.0.3.5 Private 7\n", "8 13.82.152.48 Public 8" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "IpAddress.util.ip_type(ip_list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When using a DataFrame as an input to pivot, things are a little more\n", "complicated.\n", "We have to pass the DataFrame to the function and also supply \n", "the name of the column thatcontains the input data." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
nirasn_registryasnasn_cidrasn_country_codeasn_dateasn_descriptionquerynetsrawreferralraw_referral
0NaNarin15169172.217.15.0/24US2012-04-16GOOGLE, US172.217.15.99[{'cidr': '172.217.0.0/16', 'name': 'GOOGLE', 'handle': 'NET-172-217-0-0-1', 'range': '172.217.0...NaNNaNNaN
1NaNarin807540.80.0.0/12US2015-02-23MICROSOFT-CORP-MSN-AS-BLOCK, US40.85.232.64[{'cidr': '40.80.0.0/12, 40.124.0.0/16, 40.74.0.0/15, 40.76.0.0/14, 40.120.0.0/14, 40.125.0.0/17...NaNNaNNaN
2NaNarin807520.36.0.0/14US2017-10-18MICROSOFT-CORP-MSN-AS-BLOCK, US20.38.98.100[{'cidr': '20.128.0.0/16, 20.33.0.0/16, 20.34.0.0/15, 20.36.0.0/14, 20.64.0.0/10, 20.40.0.0/13, ...NaNNaNNaN
3NaNarin807523.96.0.0/14US2013-06-18MICROSOFT-CORP-MSN-AS-BLOCK, US23.96.64.84[{'cidr': '23.96.0.0/13', 'name': 'MSFT', 'handle': 'NET-23-96-0-0-1', 'range': '23.96.0.0 - 23....NaNNaNNaN
4NaNarin807565.52.0.0/14US2001-02-14MICROSOFT-CORP-MSN-AS-BLOCK, US65.55.44.108[{'cidr': '65.52.0.0/14', 'name': 'MICROSOFT-1BLK', 'handle': 'NET-65-52-0-0-1', 'range': '65.52...NaNNaNNaN
5NaNarin3598131.107.0.0/16US1988-11-11MICROSOFT-CORP-AS, US131.107.147.209[{'cidr': '131.107.0.0/16', 'name': 'MICROSOFT', 'handle': 'NET-131-107-0-0-1', 'range': '131.10...NaNNaNNaN
6NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
7NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
8NaNarin807513.64.0.0/11US2015-03-26MICROSOFT-CORP-MSN-AS-BLOCK, US13.82.152.48[{'cidr': '13.64.0.0/11, 13.96.0.0/13, 13.104.0.0/14', 'name': 'MSFT', 'handle': 'NET-13-64-0-0-...NaNNaNNaN
\n", "
" ], "text/plain": [ " nir asn_registry asn asn_cidr asn_country_code asn_date \\\n", "0 NaN arin 15169 172.217.15.0/24 US 2012-04-16 \n", "1 NaN arin 8075 40.80.0.0/12 US 2015-02-23 \n", "2 NaN arin 8075 20.36.0.0/14 US 2017-10-18 \n", "3 NaN arin 8075 23.96.0.0/14 US 2013-06-18 \n", "4 NaN arin 8075 65.52.0.0/14 US 2001-02-14 \n", "5 NaN arin 3598 131.107.0.0/16 US 1988-11-11 \n", "6 NaN NaN NaN NaN NaN NaN \n", "7 NaN NaN NaN NaN NaN NaN \n", "8 NaN arin 8075 13.64.0.0/11 US 2015-03-26 \n", "\n", " asn_description query \\\n", "0 GOOGLE, US 172.217.15.99 \n", "1 MICROSOFT-CORP-MSN-AS-BLOCK, US 40.85.232.64 \n", "2 MICROSOFT-CORP-MSN-AS-BLOCK, US 20.38.98.100 \n", "3 MICROSOFT-CORP-MSN-AS-BLOCK, US 23.96.64.84 \n", "4 MICROSOFT-CORP-MSN-AS-BLOCK, US 65.55.44.108 \n", "5 MICROSOFT-CORP-AS, US 131.107.147.209 \n", "6 NaN NaN \n", "7 NaN NaN \n", "8 MICROSOFT-CORP-MSN-AS-BLOCK, US 13.82.152.48 \n", "\n", " nets \\\n", "0 [{'cidr': '172.217.0.0/16', 'name': 'GOOGLE', 'handle': 'NET-172-217-0-0-1', 'range': '172.217.0... \n", "1 [{'cidr': '40.80.0.0/12, 40.124.0.0/16, 40.74.0.0/15, 40.76.0.0/14, 40.120.0.0/14, 40.125.0.0/17... \n", "2 [{'cidr': '20.128.0.0/16, 20.33.0.0/16, 20.34.0.0/15, 20.36.0.0/14, 20.64.0.0/10, 20.40.0.0/13, ... \n", "3 [{'cidr': '23.96.0.0/13', 'name': 'MSFT', 'handle': 'NET-23-96-0-0-1', 'range': '23.96.0.0 - 23.... \n", "4 [{'cidr': '65.52.0.0/14', 'name': 'MICROSOFT-1BLK', 'handle': 'NET-65-52-0-0-1', 'range': '65.52... \n", "5 [{'cidr': '131.107.0.0/16', 'name': 'MICROSOFT', 'handle': 'NET-131-107-0-0-1', 'range': '131.10... \n", "6 NaN \n", "7 NaN \n", "8 [{'cidr': '13.64.0.0/11, 13.96.0.0/13, 13.104.0.0/14', 'name': 'MSFT', 'handle': 'NET-13-64-0-0-... \n", "\n", " raw referral raw_referral \n", "0 NaN NaN NaN \n", "1 NaN NaN NaN \n", "2 NaN NaN NaN \n", "3 NaN NaN NaN \n", "4 NaN NaN NaN \n", "5 NaN NaN NaN \n", "6 NaN NaN NaN \n", "7 NaN NaN NaN \n", "8 NaN NaN NaN " ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "IpAddress.util.whois(ip_df, column=\"ip\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> Note: for most functions you can ignore the parameter\n", "name and just specify it as a positional parameter.\n", "You can also use the original parameter name of the underlying\n", "function or the placeholder name \"value\".\n", "\n", "The following are all equivalent:\n", "```python\n", "IpAddress.util.ip_type(ip_list)\n", "IpAddress.util.ip_type(ip_str=ip_list)\n", "IpAddress.util.ip_type(value=ip_list)\n", "IpAddress.util.ip_type(data=ip_list)\n", "```\n", "\n", "When passing both a DataFrame and column name use:\n", "```python\n", "IpAddress.util.ip_type(data=ip_df, column=\"col_name\")\n", "```\n", "You can also pass an entity instance of an entity\n", "as a input parameter. The pivot code knows which attribute\n", "or attributes of an entity will provider the input value." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
ipresult
040.85.232.64Public
\n", "
" ], "text/plain": [ " ip result\n", "0 40.85.232.64 Public" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ip_entity = IpAddress(Address=\"40.85.232.64\")\n", "IpAddress.util.ip_type(ip_entity)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Iterable/DataFrame inputs and single-value functions\n", "\n", "Many of the underlying functions only accept single values\n", "as inputs. Examples of these are the data query functions - typically\n", "they expect a single host name, IP address, etc.\n", "\n", "Pivot knows about the type of parameters that the function accepts.\n", "It will adjust the input to match the expectations of the underlying\n", "function. If a list or DataFrame is passed as input to a single-value\n", "function Pivot will split the input and call the function once for\n", "each value. It then combines the output into a single DataFrame\n", "before returning the results. \n", "\n", "You can read a bit more about how this is done in the Appendix TODO" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Data queries - where does the time range come from?\n", "\n", "The Pivot class has a buit-in time range. This is used by\n", "default for all queries. Don't worry - you can change it easily" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "TimeSpan(start=2022-06-08 22:10:15.959575+00:00, end=2022-06-09 22:10:15.959575+00:00, period=1 day, 0:00:00)" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mp.pivot.timespan" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can edit the time range interactively" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "fed253eb36b248d295e2469285be0dc7", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(HTML(value='

Set time range for pivot functions.

'), HBox(children=(DatePicker(value=dat…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "mp.pivot.edit_query_time()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or by setting the timespan property directly" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "from msticpy.common.timespan import TimeSpan\n", "# TimeSpan accepts datetimes or datestrings\n", "timespan = TimeSpan(start=\"02/01/2021\", end=\"02/15/2021\")\n", "mp.pivot.timespan = timespan" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is also a convenience function\n", "for setting the time directly with Python datetimes or date strings" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "mp.pivot.current.set_timespan(start=\"2020-02-06 03:00:00\", end=\"2021-02-15 01:42:42\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also override the built-in time settings by specifying\n", "`start` and `end` as parameters." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TenantIdTimeGeneratedAlertDisplayNameAlertNameSeverityDescriptionProviderNameVendorNameVendorOriginalIdSystemAlertIdResourceIdSourceComputerIdAlertTypeConfidenceLevelConfidenceScoreIsIncidentStartTimeUtcEndTimeUtcProcessingEndTimeRemediationStepsExtendedPropertiesEntitiesSourceSystemWorkspaceSubscriptionIdWorkspaceResourceGroupExtendedLinksProductNameProductComponentNameAlertLinkStatusCompromisedEntityTacticsTechniquesTypeComputersrc_hostnamesrc_accountnamesrc_procnamehost_matchacct_matchproc_match
\n", "
" ], "text/plain": [ "Empty DataFrame\n", "Columns: [TenantId, TimeGenerated, AlertDisplayName, AlertName, Severity, Description, ProviderName, VendorName, VendorOriginalId, SystemAlertId, ResourceId, SourceComputerId, AlertType, ConfidenceLevel, ConfidenceScore, IsIncident, StartTimeUtc, EndTimeUtc, ProcessingEndTime, RemediationSteps, ExtendedProperties, Entities, SourceSystem, WorkspaceSubscriptionId, WorkspaceResourceGroup, ExtendedLinks, ProductName, ProductComponentName, AlertLink, Status, CompromisedEntity, Tactics, Techniques, Type, Computer, src_hostname, src_accountname, src_procname, host_match, acct_match, proc_match]\n", "Index: []" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dt1 = mp.pivot.timespan.start\n", "dt2 = mp.pivot.timespan.end\n", "Host.MSSentinel_cybersecuritysoc.sec_alerts(host_name=\"victim00\", start=dt1, end=dt2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Supplying extra parameters\n", "\n", "The Pivot layer will pass any unused keyword parameters to the\n", "underlying function. This *does not* usually apply to positional parameters -\n", "if you want parameters to get to the function, you have to name them\n", "explicitly.\n", "In this example the `add_query_items` parameter is passed to the underlying\n", "query function" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "application/javascript": "try {IPython.notebook.kernel.execute(\"NOTEBOOK_URL = '\" + window.location + \"'\");} catch(err) {;}", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
LogonTypecount_
0521650
136808
249426
32109
41044
507
698
\n", "
" ], "text/plain": [ " LogonType count_\n", "0 5 21650\n", "1 3 6808\n", "2 4 9426\n", "3 2 109\n", "4 10 44\n", "5 0 7\n", "6 9 8" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Host.MSSentinel_cybersecuritysoc.wevt_logons(\n", " host_name=\"victimPc\",\n", " add_query_items=\"| summarize count() by LogonType\"\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Pivot Pipelines\n", "\n", "Because all pivot functions accept DataFrames as input\n", "and produce DataFrames as output, it means that it is possible\n", "to chain pivot functions into a pipeline.\n", "\n", "### Joining input to output\n", "You can join the input to the output. This usually only makes sense\n", "when the input is a DataFrame. It\n", "lets you keep the previously accumumated results and tag on the\n", "additional columns produced by the pivot function you are calling.\n", "\n", "The `join` parameter supports \"inner\", \"left\", \"right\" and \"outer\"\n", "joins (be careful with the latter though!)\n", "See [pivot joins documentation](https://msticpy.readthedocs.io/en/latest/data_analysis/PivotFunctions.html#joining-input-to-output-data)\n", "\n", "Although joining is useful in pipelines you can use it on\n", "any function whether in a pipeline or not." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idxiptypenirasn_registryasnasn_cidrasn_country_codeasn_dateasn_descriptionquerynetsrawreferralraw_referral
00172.217.15.99PublicNaNarin15169172.217.15.0/24US2012-04-16GOOGLE, US172.217.15.99[{'cidr': '172.217.0.0/16', 'name': 'GOOGLE', 'handle': 'NET-172-217-0-0-1', 'range': '172.217.0...NaNNaNNaN
1140.85.232.64PublicNaNarin807540.80.0.0/12US2015-02-23MICROSOFT-CORP-MSN-AS-BLOCK, US40.85.232.64[{'cidr': '40.80.0.0/12, 40.124.0.0/16, 40.74.0.0/15, 40.76.0.0/14, 40.120.0.0/14, 40.125.0.0/17...NaNNaNNaN
2220.38.98.100PublicNaNarin807520.36.0.0/14US2017-10-18MICROSOFT-CORP-MSN-AS-BLOCK, US20.38.98.100[{'cidr': '20.128.0.0/16, 20.33.0.0/16, 20.34.0.0/15, 20.36.0.0/14, 20.64.0.0/10, 20.40.0.0/13, ...NaNNaNNaN
3323.96.64.84PublicNaNarin807523.96.0.0/14US2013-06-18MICROSOFT-CORP-MSN-AS-BLOCK, US23.96.64.84[{'cidr': '23.96.0.0/13', 'name': 'MSFT', 'handle': 'NET-23-96-0-0-1', 'range': '23.96.0.0 - 23....NaNNaNNaN
4465.55.44.108PublicNaNarin807565.52.0.0/14US2001-02-14MICROSOFT-CORP-MSN-AS-BLOCK, US65.55.44.108[{'cidr': '65.52.0.0/14', 'name': 'MICROSOFT-1BLK', 'handle': 'NET-65-52-0-0-1', 'range': '65.52...NaNNaNNaN
55131.107.147.209PublicNaNarin3598131.107.0.0/16US1988-11-11MICROSOFT-CORP-AS, US131.107.147.209[{'cidr': '131.107.0.0/16', 'name': 'MICROSOFT', 'handle': 'NET-131-107-0-0-1', 'range': '131.10...NaNNaNNaN
6813.82.152.48PublicNaNarin807513.64.0.0/11US2015-03-26MICROSOFT-CORP-MSN-AS-BLOCK, US13.82.152.48[{'cidr': '13.64.0.0/11, 13.96.0.0/13, 13.104.0.0/14', 'name': 'MSFT', 'handle': 'NET-13-64-0-0-...NaNNaNNaN
\n", "
" ], "text/plain": [ " idx ip type nir asn_registry asn asn_cidr \\\n", "0 0 172.217.15.99 Public NaN arin 15169 172.217.15.0/24 \n", "1 1 40.85.232.64 Public NaN arin 8075 40.80.0.0/12 \n", "2 2 20.38.98.100 Public NaN arin 8075 20.36.0.0/14 \n", "3 3 23.96.64.84 Public NaN arin 8075 23.96.0.0/14 \n", "4 4 65.55.44.108 Public NaN arin 8075 65.52.0.0/14 \n", "5 5 131.107.147.209 Public NaN arin 3598 131.107.0.0/16 \n", "6 8 13.82.152.48 Public NaN arin 8075 13.64.0.0/11 \n", "\n", " asn_country_code asn_date asn_description \\\n", "0 US 2012-04-16 GOOGLE, US \n", "1 US 2015-02-23 MICROSOFT-CORP-MSN-AS-BLOCK, US \n", "2 US 2017-10-18 MICROSOFT-CORP-MSN-AS-BLOCK, US \n", "3 US 2013-06-18 MICROSOFT-CORP-MSN-AS-BLOCK, US \n", "4 US 2001-02-14 MICROSOFT-CORP-MSN-AS-BLOCK, US \n", "5 US 1988-11-11 MICROSOFT-CORP-AS, US \n", "6 US 2015-03-26 MICROSOFT-CORP-MSN-AS-BLOCK, US \n", "\n", " query \\\n", "0 172.217.15.99 \n", "1 40.85.232.64 \n", "2 20.38.98.100 \n", "3 23.96.64.84 \n", "4 65.55.44.108 \n", "5 131.107.147.209 \n", "6 13.82.152.48 \n", "\n", " nets \\\n", "0 [{'cidr': '172.217.0.0/16', 'name': 'GOOGLE', 'handle': 'NET-172-217-0-0-1', 'range': '172.217.0... \n", "1 [{'cidr': '40.80.0.0/12, 40.124.0.0/16, 40.74.0.0/15, 40.76.0.0/14, 40.120.0.0/14, 40.125.0.0/17... \n", "2 [{'cidr': '20.128.0.0/16, 20.33.0.0/16, 20.34.0.0/15, 20.36.0.0/14, 20.64.0.0/10, 20.40.0.0/13, ... \n", "3 [{'cidr': '23.96.0.0/13', 'name': 'MSFT', 'handle': 'NET-23-96-0-0-1', 'range': '23.96.0.0 - 23.... \n", "4 [{'cidr': '65.52.0.0/14', 'name': 'MICROSOFT-1BLK', 'handle': 'NET-65-52-0-0-1', 'range': '65.52... \n", "5 [{'cidr': '131.107.0.0/16', 'name': 'MICROSOFT', 'handle': 'NET-131-107-0-0-1', 'range': '131.10... \n", "6 [{'cidr': '13.64.0.0/11, 13.96.0.0/13, 13.104.0.0/14', 'name': 'MSFT', 'handle': 'NET-13-64-0-0-... \n", "\n", " raw referral raw_referral \n", "0 NaN NaN NaN \n", "1 NaN NaN NaN \n", "2 NaN NaN NaN \n", "3 NaN NaN NaN \n", "4 NaN NaN NaN \n", "5 NaN NaN NaN \n", "6 NaN NaN NaN " ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "IpAddress.util.whois(ip_df, column=\"ip\", join=\"inner\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "### Pipelines \n", "\n", "Pivot pipelines are implemented pandas customr accessors.\n", "Read more about [Extending pandas here](https://pandas.pydata.org/pandas-docs/stable/development/extending.html)\n", "\n", "When you load Pivot it adds the `mp_pivot` accessor to the pandas\n", "`DataFrame` class. This\n", "appears as an attribute to DataFrames.\n", "\n", "```python\n", ">>> ips_df.mp_pivot\n", "\n", "```\n", "\n", "The main pipelining function `run` is a method of `mp_pivot`.\n", "`run` requires two parameters - the pivot function to run and\n", "the column to use as input. See [mp_pivot.run documentation](https://msticpy.readthedocs.io/en/latest/data_analysis/PivotFunctions.html#mp-pivot-run)\n" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [], "source": [ "# Create a dataframe for input\n", "ip_list = [\n", " \"192.168.40.32\",\n", " \"192.168.1.216\",\n", " \"192.168.153.17\",\n", " \"3.88.48.125\",\n", " \"10.200.104.20\",\n", " \"192.168.90.101\",\n", " \"192.168.150.50\",\n", " \"172.16.100.31\",\n", " \"192.168.30.189\",\n", " \"10.100.199.10\",\n", "]\n", "ips_df = pd.DataFrame(ip_list, columns=[\"IP\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Pipeline example\n", "\n", "Here is an example of using `mp_pivot` to call 4 pivot functions, each\n", "using the output of the previous function as input and using\n", "the `join` parameter to accumulate the results from each\n", "stage.\n", "\n", "Let's step through it line by line.\n", "1. The whole thing is surrounded by a pair of parentheses - this is just\n", " to let us split the whole expression over multiple lines without\n", " Python complaining.\n", "2. Next we have `ips_df` - this is just the starting DataFrame, our input data.\n", "3. Next we call the `mp_pivot.run()` accessor method on this dataframe.\n", " We pass it the pivot function that we want to run and the input column name.\n", " This column name is the column in ips_df where our input IP addresses are.\n", " We've also specified an `join` type of inner. In this case the join type doesn't\n", " really matter since we know we get exactly one output row for every input row.\n", "4. We're using the pandas `query` function to filter out unwanted entries\n", " from the previous stage. In this case we only want Public IP addresses. \n", " This illustrates that you can intersperse standard pandas functions\n", " in the same pipeline. We could have also added a column selector expression\n", " ([[\"col1\", \"col2\"...]]) if we wanted to filter the columns passed to the \n", " next stage\n", "5. We are calling a further pivot function - `whois`. Remember the \"column\" parameter\n", " always refers to the input column, i.e. the column from previous stage\n", " that we want to use in this stage.\n", "6. We are calling `geoloc` to get geo location details joining with a left\n", " join - this preserves the input data rows and adds null columns in any cases\n", " where the pivot function returned no result.\n", "7. Is the same as 6 except is a data query to see if we have any alerts\n", " that contain these IP addresses. Remember, in the case of data queries\n", " we have to name the specific query parameter that we want the input to \n", " go to. In this case, each row value in the \"ip\" column from the previous\n", " stage will be sent to the query.\n", "8. Finally we close the parentheses to form a valid Python expression.\n", " The whole expression returns a DataFrame so we can add further pandas\n", " operations here (like `.head(5)` shown here)." ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "application/javascript": "try {IPython.notebook.kernel.execute(\"NOTEBOOK_URL = '\" + window.location + \"'\");} catch(err) {;}", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
IPipresultasnasn_cidrasn_country_codeasn_dateasn_descriptionasn_registrynetsnirqueryrawraw_referralreferralCountryCodeCountryNameStateCityLongitudeLatitudeAsnedgesType_xAdditionalData...AlertTypeConfidenceLevelConfidenceScoreIsIncidentStartTimeUtcEndTimeUtcProcessingEndTimeRemediationStepsExtendedPropertiesEntitiesSourceSystemWorkspaceSubscriptionIdWorkspaceResourceGroupExtendedLinksProductNameProductComponentNameAlertLinkStatusCompromisedEntityTacticsType_ySystemAlertId1ExtendedProperties1Entities1MatchingIps
03.88.48.1253.88.48.125Public146183.80.0.0/12US2017-12-20AMAZON-AES, USarin[{'cidr': '3.0.0.0/9', 'name': 'AT-88-Z', 'handle': 'NET-3-0-0-0-1', 'range': '3.0.0.0 - 3.127.2...None3.88.48.125NoneNoneNoneUSUnited StatesVirginiaAshburn-77.472839.0481None{}geolocation{}...8ecf8077-cf51-4820-aadd-14040956f35d_8a369bd2-97b6-4fe2-922a-cd170faf25bcNaNFalse2020-12-19 13:04:59+00:002020-12-19 19:04:59+00:002020-12-19 19:10:17+00:00{\\r\\n \"Query\": \"// The query_now parameter (in UTC format) was prepended to the query to reflec...[\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"Address\": \"3.88.48.125\",\\r\\n \"Type\": \"ip\"\\r\\n }\\r\\n]Detectiond1d8779d-38d7-4f06-91db-9cbc8de0176fsocAzure SentinelScheduled AlertsNewCommandAndControlSecurityAlertfdc54c12-efba-38b0-8379-f06d7fbfd34a{\\r\\n \"Query\": \"// The query_now parameter (in UTC format) was prepended to the query to reflec...[\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"Address\": \"3.88.48.125\",\\r\\n \"Type\": \"ip\"\\r\\n }\\r\\n][3.88.48.125]
13.88.48.1253.88.48.125Public146183.80.0.0/12US2017-12-20AMAZON-AES, USarin[{'cidr': '3.0.0.0/9', 'name': 'AT-88-Z', 'handle': 'NET-3-0-0-0-1', 'range': '3.0.0.0 - 3.127.2...None3.88.48.125NoneNoneNoneUSUnited StatesVirginiaAshburn-77.472839.0481None{}geolocation{}...ThreatIntelligence83NaNFalse2020-12-23 13:48:23+00:002020-12-23 13:48:23+00:002020-12-23 14:08:15+00:00{\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim...[\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo...Detectiond1d8779d-38d7-4f06-91db-9cbc8de0176fsocAzure SentinelMicrosoft Threat Intelligence AnalyticsNew3.88.48.125UnknownSecurityAlert625ff9af-dddc-0cf8-9d4b-e79067fa2e71{\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim...[\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo...[3.88.48.125]
23.88.48.1253.88.48.125Public146183.80.0.0/12US2017-12-20AMAZON-AES, USarin[{'cidr': '3.0.0.0/9', 'name': 'AT-88-Z', 'handle': 'NET-3-0-0-0-1', 'range': '3.0.0.0 - 3.127.2...None3.88.48.125NoneNoneNoneUSUnited StatesVirginiaAshburn-77.472839.0481None{}geolocation{}...ThreatIntelligence83NaNFalse2020-12-23 13:48:23+00:002020-12-23 13:48:23+00:002020-12-23 14:08:15+00:00{\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim...[\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo...Detectiond1d8779d-38d7-4f06-91db-9cbc8de0176fsocAzure SentinelMicrosoft Threat Intelligence AnalyticsNew3.88.48.125UnknownSecurityAlertc977f904-ab30-d57e-986f-9d6ebf72771b{\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim...[\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo...[3.88.48.125]
33.88.48.1253.88.48.125Public146183.80.0.0/12US2017-12-20AMAZON-AES, USarin[{'cidr': '3.0.0.0/9', 'name': 'AT-88-Z', 'handle': 'NET-3-0-0-0-1', 'range': '3.0.0.0 - 3.127.2...None3.88.48.125NoneNoneNoneUSUnited StatesVirginiaAshburn-77.472839.0481None{}geolocation{}...ThreatIntelligence83NaNFalse2020-12-23 13:48:23+00:002020-12-23 13:48:23+00:002020-12-23 14:08:15+00:00{\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim...[\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo...Detectiond1d8779d-38d7-4f06-91db-9cbc8de0176fsocAzure SentinelMicrosoft Threat Intelligence AnalyticsNew3.88.48.125UnknownSecurityAlert9ee547e4-cba1-47d1-e1f9-87247b693a52{\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim...[\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo...[3.88.48.125]
43.88.48.1253.88.48.125Public146183.80.0.0/12US2017-12-20AMAZON-AES, USarin[{'cidr': '3.0.0.0/9', 'name': 'AT-88-Z', 'handle': 'NET-3-0-0-0-1', 'range': '3.0.0.0 - 3.127.2...None3.88.48.125NoneNoneNoneUSUnited StatesVirginiaAshburn-77.472839.0481None{}geolocation{}...ThreatIntelligence83NaNFalse2020-12-23 13:48:23+00:002020-12-23 13:48:23+00:002020-12-23 14:08:16+00:00{\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim...[\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo...Detectiond1d8779d-38d7-4f06-91db-9cbc8de0176fsocAzure SentinelMicrosoft Threat Intelligence AnalyticsNew3.88.48.125UnknownSecurityAlert83a0e08a-1adb-ef75-1c56-f6c9ce25ca69{\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim...[\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo...[3.88.48.125]
\n", "

5 rows × 63 columns

\n", "
" ], "text/plain": [ " IP ip result asn asn_cidr asn_country_code \\\n", "0 3.88.48.125 3.88.48.125 Public 14618 3.80.0.0/12 US \n", "1 3.88.48.125 3.88.48.125 Public 14618 3.80.0.0/12 US \n", "2 3.88.48.125 3.88.48.125 Public 14618 3.80.0.0/12 US \n", "3 3.88.48.125 3.88.48.125 Public 14618 3.80.0.0/12 US \n", "4 3.88.48.125 3.88.48.125 Public 14618 3.80.0.0/12 US \n", "\n", " asn_date asn_description asn_registry \\\n", "0 2017-12-20 AMAZON-AES, US arin \n", "1 2017-12-20 AMAZON-AES, US arin \n", "2 2017-12-20 AMAZON-AES, US arin \n", "3 2017-12-20 AMAZON-AES, US arin \n", "4 2017-12-20 AMAZON-AES, US arin \n", "\n", " nets \\\n", "0 [{'cidr': '3.0.0.0/9', 'name': 'AT-88-Z', 'handle': 'NET-3-0-0-0-1', 'range': '3.0.0.0 - 3.127.2... \n", "1 [{'cidr': '3.0.0.0/9', 'name': 'AT-88-Z', 'handle': 'NET-3-0-0-0-1', 'range': '3.0.0.0 - 3.127.2... \n", "2 [{'cidr': '3.0.0.0/9', 'name': 'AT-88-Z', 'handle': 'NET-3-0-0-0-1', 'range': '3.0.0.0 - 3.127.2... \n", "3 [{'cidr': '3.0.0.0/9', 'name': 'AT-88-Z', 'handle': 'NET-3-0-0-0-1', 'range': '3.0.0.0 - 3.127.2... \n", "4 [{'cidr': '3.0.0.0/9', 'name': 'AT-88-Z', 'handle': 'NET-3-0-0-0-1', 'range': '3.0.0.0 - 3.127.2... \n", "\n", " nir query raw raw_referral referral CountryCode CountryName \\\n", "0 None 3.88.48.125 None None None US United States \n", "1 None 3.88.48.125 None None None US United States \n", "2 None 3.88.48.125 None None None US United States \n", "3 None 3.88.48.125 None None None US United States \n", "4 None 3.88.48.125 None None None US United States \n", "\n", " State City Longitude Latitude Asn edges Type_x \\\n", "0 Virginia Ashburn -77.4728 39.0481 None {} geolocation \n", "1 Virginia Ashburn -77.4728 39.0481 None {} geolocation \n", "2 Virginia Ashburn -77.4728 39.0481 None {} geolocation \n", "3 Virginia Ashburn -77.4728 39.0481 None {} geolocation \n", "4 Virginia Ashburn -77.4728 39.0481 None {} geolocation \n", "\n", " AdditionalData ... \\\n", "0 {} ... \n", "1 {} ... \n", "2 {} ... \n", "3 {} ... \n", "4 {} ... \n", "\n", " AlertType \\\n", "0 8ecf8077-cf51-4820-aadd-14040956f35d_8a369bd2-97b6-4fe2-922a-cd170faf25bc \n", "1 ThreatIntelligence \n", "2 ThreatIntelligence \n", "3 ThreatIntelligence \n", "4 ThreatIntelligence \n", "\n", " ConfidenceLevel ConfidenceScore IsIncident StartTimeUtc \\\n", "0 NaN False 2020-12-19 13:04:59+00:00 \n", "1 83 NaN False 2020-12-23 13:48:23+00:00 \n", "2 83 NaN False 2020-12-23 13:48:23+00:00 \n", "3 83 NaN False 2020-12-23 13:48:23+00:00 \n", "4 83 NaN False 2020-12-23 13:48:23+00:00 \n", "\n", " EndTimeUtc ProcessingEndTime RemediationSteps \\\n", "0 2020-12-19 19:04:59+00:00 2020-12-19 19:10:17+00:00 \n", "1 2020-12-23 13:48:23+00:00 2020-12-23 14:08:15+00:00 \n", "2 2020-12-23 13:48:23+00:00 2020-12-23 14:08:15+00:00 \n", "3 2020-12-23 13:48:23+00:00 2020-12-23 14:08:15+00:00 \n", "4 2020-12-23 13:48:23+00:00 2020-12-23 14:08:16+00:00 \n", "\n", " ExtendedProperties \\\n", "0 {\\r\\n \"Query\": \"// The query_now parameter (in UTC format) was prepended to the query to reflec... \n", "1 {\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim... \n", "2 {\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim... \n", "3 {\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim... \n", "4 {\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim... \n", "\n", " Entities \\\n", "0 [\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"Address\": \"3.88.48.125\",\\r\\n \"Type\": \"ip\"\\r\\n }\\r\\n] \n", "1 [\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo... \n", "2 [\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo... \n", "3 [\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo... \n", "4 [\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo... \n", "\n", " SourceSystem WorkspaceSubscriptionId WorkspaceResourceGroup \\\n", "0 Detection d1d8779d-38d7-4f06-91db-9cbc8de0176f soc \n", "1 Detection d1d8779d-38d7-4f06-91db-9cbc8de0176f soc \n", "2 Detection d1d8779d-38d7-4f06-91db-9cbc8de0176f soc \n", "3 Detection d1d8779d-38d7-4f06-91db-9cbc8de0176f soc \n", "4 Detection d1d8779d-38d7-4f06-91db-9cbc8de0176f soc \n", "\n", " ExtendedLinks ProductName ProductComponentName \\\n", "0 Azure Sentinel Scheduled Alerts \n", "1 Azure Sentinel Microsoft Threat Intelligence Analytics \n", "2 Azure Sentinel Microsoft Threat Intelligence Analytics \n", "3 Azure Sentinel Microsoft Threat Intelligence Analytics \n", "4 Azure Sentinel Microsoft Threat Intelligence Analytics \n", "\n", " AlertLink Status CompromisedEntity Tactics Type_y \\\n", "0 New CommandAndControl SecurityAlert \n", "1 New 3.88.48.125 Unknown SecurityAlert \n", "2 New 3.88.48.125 Unknown SecurityAlert \n", "3 New 3.88.48.125 Unknown SecurityAlert \n", "4 New 3.88.48.125 Unknown SecurityAlert \n", "\n", " SystemAlertId1 \\\n", "0 fdc54c12-efba-38b0-8379-f06d7fbfd34a \n", "1 625ff9af-dddc-0cf8-9d4b-e79067fa2e71 \n", "2 c977f904-ab30-d57e-986f-9d6ebf72771b \n", "3 9ee547e4-cba1-47d1-e1f9-87247b693a52 \n", "4 83a0e08a-1adb-ef75-1c56-f6c9ce25ca69 \n", "\n", " ExtendedProperties1 \\\n", "0 {\\r\\n \"Query\": \"// The query_now parameter (in UTC format) was prepended to the query to reflec... \n", "1 {\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim... \n", "2 {\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim... \n", "3 {\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim... \n", "4 {\\r\\n \"Query\": \"CommonSecurityLog| where RequestURL hasprefix(\\\"www.arboretum.hu\\\") | where Tim... \n", "\n", " Entities1 \\\n", "0 [\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"Address\": \"3.88.48.125\",\\r\\n \"Type\": \"ip\"\\r\\n }\\r\\n] \n", "1 [\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo... \n", "2 [\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo... \n", "3 [\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo... \n", "4 [\\r\\n {\\r\\n \"$id\": \"3\",\\r\\n \"DnsDomain\": \"www.arboretum.hu\",\\r\\n \"HostName\": \"www.arbo... \n", "\n", " MatchingIps \n", "0 [3.88.48.125] \n", "1 [3.88.48.125] \n", "2 [3.88.48.125] \n", "3 [3.88.48.125] \n", "4 [3.88.48.125] \n", "\n", "[5 rows x 63 columns]" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(\n", " ips_df\n", " .mp_pivot.run(IpAddress.util.ip_type, column=\"IP\", join=\"inner\")\n", " .query(\"result == 'Public'\").head(10)\n", " .mp_pivot.run(IpAddress.util.whois, column=\"ip\", join=\"left\")\n", " .mp_pivot.run(IpAddress.util.geoloc, column=\"ip\", join=\"left\")\n", " .mp_pivot.run(IpAddress.MSSentinel_cybersecuritysoc.sec_list_alerts_for_ip, source_ip_list=\"ip\", join=\"left\")\n", ").head(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Other pipeline functions\n", "\n", "In addition to `run`, the `mp_pivot` accessor also \n", "has the following functions:\n", "- `display` - this simply displays the data at the point called in\n", " the pipeline. You can add an optional title, filtering and the number\n", " or rows to display\n", "- `tee` - this forks a copy of the dataframe at the point it is\n", " called in the pipeline. It will assign the forked copy to the name\n", " given in the `var_name` parameter. If there is an existing variable of\n", " the same name it will not overwrite it unless you add the `clobber=True`\n", " parameter.\n", " \n", "In both cases the pipelined data is passed through unchanged.\n", "See [Pivot functions help](https://msticpy.readthedocs.io/en/latest/data_analysis/PivotFunctions.html#mp-pivot-display)\n", "for more details.\n", "\n", "Use of these is shown below" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", " ...\n", " .mp_pivot.run(entities.IpAddress.util.geoloc, column=\"ip\", join=\"left\")\n", " .mp_pivot.display(title=\"Geo Lookup\", cols=[\"IP\", \"City\"]) # << display an intermediate result\n", " .mp_pivot.tee(var_name=\"geoip_df\", clobber=True) # << save a copy called 'geoip_df'\n", " .mp_pivot.run(entities.IpAddress.AzureSentinel.SecurityAlert_list_alerts_for_ip, source_ip_list=\"ip\", join=\"left\")\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the next release we've also implemented:\n", "- `tee_exec` - this executes a function on a forked copy of the DataFrame\n", " The function must be a pandas function or custom accessor. A\n", " good example of the use of this might be creating a plot or summary\n", " table to display partway through the pipeline." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extending Pivot - adding your own (or someone else's) functions\n", "\n", "You can add pivot functions of your own. You need to supply:\n", "- the function\n", "- some metadata that describes where the function can be found\n", " and how the function works\n", "\n", "\n", "Full details of this are [described here](https://msticpy.readthedocs.io/en/latest/data_analysis/PivotFunctions.html#adding-custom-functions-to-the-pivot-interface)." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "from hashlib import md5\n", "\n", "def my_func2(input: str):\n", " md5_hash = \"-\".join(hex(b)[2:] for b in md5(\"hello\".encode(\"utf-8\")).digest())\n", " return {\n", " \"Title\": input.upper(),\n", " \"Hash\": md5_hash\n", " }\n", "\n", "\n", "mp.Pivot.add_pivot_function(\n", " func=my_func2,\n", " container=\"cyber\", # which container it will appear in on the entity\n", " input_type=\"value\",\n", " entity_map={\"Host\": \"HostName\"},\n", " func_input_value_arg=\"input\",\n", " func_new_name=\"il_upper_hash_name\",\n", ")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Now run the function" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "Host.cyber.il_upper_hash_name(\"host_name\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusion\n", "\n", "We've taken a short tour through the *MSTICPy* looking at how\n", "they make the functionality in the package easier to discover\n", "and use.\n", "I'm particularly excited about the pipeline functionality.\n", "In the next release we're going to make it possible to define\n", "reusable pipelines in configuration files and execute them\n", "with a single function call. This should help streamline\n", "some common patterns in notebooks for Cyber hunting and investigation.\n", "\n", "Please send any feedback or suggestions for improvements\n", "to msticpy@microsoft.com or create an issue on https://github.com/microsoft/msticpy.\n", "\n", "Happy hunting!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Appendix - how do pivot wrappers work?\n", "\n", "In Python you can create functions that return other functions.\n", "On the way they can change how the arguments and output are\n", "processed.\n", "\n", "Take this simple function that just applies proper capitalization\n", "to an input string." ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello\n" ] } ], "source": [ "def print_me(arg):\n", " print(arg.capitalize())\n", " \n", "print_me(\"hello\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we try to pass a list to this function we get an \n", "expected exception about lists not supporting `capitalize`" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'print_me' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mprint_me\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"hello\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"world\"\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mNameError\u001b[0m: name 'print_me' is not defined" ] } ], "source": [ "print_me([\"hello\", \"world\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We could create a wrapper function that checked the\n", "input and iterated over the individual items if arg is a list.\n", "The works but we don't want to have to do this for every \n", "function that we want to have flexible input!" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello\n", "How\n", "Are\n", "You\n", "?\n" ] } ], "source": [ "def print_me_list(arg):\n", " if isinstance(arg, list):\n", " for item in arg:\n", " print_me(item)\n", " else:\n", " print_me(arg)\n", " \n", "print_me_list(\"hello\")\n", "print_me_list([\"how\", \"are\", \"you\", \"?\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Instead we can create a function wrapper. The outer function\n", "`dont_care_func` defines an inner function, `list_or_str` and then\n", "returns this function. The inner function `list_or_str` is what\n", "implements the same \"is-this-a-string-or-list\" logic that we \n", "saw in the previous example. \n", "Crucially though, it isn't hard-coded to call `print_me` but\n", "calls whatever function passed to it from the outer function\n", "`dont_care_func`." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "# Our magic wrapper\n", "\n", "def dont_care_func(func):\n", " \n", " def list_or_str(arg):\n", " if isinstance(arg, list):\n", " for item in arg:\n", " func(item)\n", " else:\n", " func(arg)\n", " return list_or_str" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How do we use this?\n", "\n", "We simply pass the function that we want to wrap to\n", "`dont_care_func`. Recall, that this function just returns\n", "an instance of the inner function. In this particular instance\n", "the value `func` will have been replaced by the actual function\n", "`print_me`." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "print_stuff = dont_care_func(print_me)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we have a wrapped version of `print_me` that can\n", "handle different types of input. Magic!" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello\n", "How\n", "Are\n", "You\n", "?\n" ] } ], "source": [ "print_stuff(\"hello\")\n", "print_stuff([\"how\", \"are\", \"you\", \"?\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also define further functions and create wrapped\n", "versions of those by passing them to `dont_care_func`." ] }, { "cell_type": "code", "execution_count": 118, "metadata": {}, "outputs": [], "source": [ "def shout_me(arg):\n", " print(arg.upper(), \"\\U0001F92C!\", end=\" \")\n", " \n", "shout_stuff = dont_care_func(shout_me)" ] }, { "cell_type": "code", "execution_count": 119, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HELLO 🤬! HOW 🤬! ARE 🤬! YOU 🤬! ? 🤬! " ] } ], "source": [ "shout_stuff(\"hello\")\n", "shout_stuff([\"how\", \"are\", \"you\", \"?\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The wrapper functionality in Pivot is a bit more complex than\n", "this but essentially operates this way." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3.9.7 ('msticpy')", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.7" }, "vscode": { "interpreter": { "hash": "0f1a8e166ce5c1ec1911a36e4fdbd34b2f623e2a3442791008b8ac429a1d6070" } }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": { "2aeaa3525526453282e5e1934aa4a923": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "95%" } }, "d24faa135bc84d798e4028cbbf8a91b9": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "width": "95%" } } }, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 4 }