{ "cells": [ { "cell_type": "markdown", "metadata": { "toc": true }, "source": [ "

Table of Contents

\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Guided Hunting - Solarwinds Post Compromise\r\n", "
\r\n", "  Details...\r\n", " \r\n", "**Notebook Version:** 1.0
\r\n", "**Python Version:** Python 3.7 (including Python 3.6 - AzureML)
\r\n", "**Required Packages**: kqlmagic, msticpy, pandas, numpy, matplotlib, networkx, ipywidgets, ipython, scikit_learn, dnspython, ipwhois, folium, holoviews
\r\n", "\r\n", "**Data Sources Required**:\r\n", "- Log Analytics \r\n", " - Heartbeat\r\n", " - SecurityAlert\r\n", " - SecurityEvent\r\n", " - DeviceProcessEvents\r\n", " - DeviceNetworkEvents\r\n", " - DeviceFileEvents\r\n", " - SigninEvents\r\n", " - AuditLogs\r\n", " - AzureNetworkAnalytics_CL\r\n", " \r\n", " \r\n", "- (Optional) \r\n", " - VirusTotal (with API key)\r\n", " - Alienvault OTX (with API key) \r\n", " - IBM Xforce (with API key) \r\n", " - CommonSecurityLog\r\n", "
\r\n", "\r\n", "This Notebook assists defenders in hunting for Solarwinds Post compromise Tactics , Tools and Procedures (TTPs) across different environments both on-prem and cloud data sources. \r\n", "\r\n", "You can read more about the attack in below technical blogs\r\n", "\r\n", "**References :**\r\n", "\r\n", "- https://msrc-blog.microsoft.com/2020/12/13/customer-guidance-on-recent-nation-state-cyber-attacks/\r\n", "- https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html\r\n", "- \r\n", "\r\n", "**How to use:**
\r\n", "Run the cells in this Notebook in order, at various points in the Notebook flow you will be prompted to enter or select options relevant to the scope of your triage.
\r\n", "This Notebook presumes you have Microsoft Sentinel Workspace settings and Threat Intelligence providers configured in a config file. If you do not have this in place please refer https://msticpy.readthedocs.io/en/latest/data_acquisition/TIProviders.html#configuration-file to https://github.com/Azure/Azure-Sentinel-Notebooks/blob/master/ConfiguringNotebookEnvironment.ipynb and to set this up." ] }, { "cell_type": "markdown", "metadata": { "ExecuteTime": { "end_time": "2020-12-15T21:39:44.390403Z", "start_time": "2020-12-15T21:39:44.380860Z" } }, "source": [ "---\n", "## Notebook initialization\n", "The next cell:\n", "- Checks for the correct Python version\n", "- Checks versions and optionally installs required packages\n", "- Imports the required packages into the notebook\n", "- Sets a number of configuration options.\n", "\n", "This should complete without errors. If you encounter errors or warnings look at the following two notebooks:\n", "- [TroubleShootingNotebooks](https://github.com/Azure/Azure-Sentinel-Notebooks/blob/master/TroubleShootingNotebooks.ipynb)\n", "- [ConfiguringNotebookEnvironment](https://github.com/Azure/Azure-Sentinel-Notebooks/blob/master/ConfiguringNotebookEnvironment.ipynb)\n", "\n", "If you are running in the Microsoft Sentinel Notebooks environment (Azure Notebooks or Azure ML) you can run live versions of these notebooks:\n", "- [Run TroubleShootingNotebooks](./TroubleShootingNotebooks.ipynb)\n", "- [Run ConfiguringNotebookEnvironment](./ConfiguringNotebookEnvironment.ipynb)\n", "\n", "You may also need to do some additional configuration to successfully use functions such as Threat Intelligence service lookup and Geo IP lookup. \n", "There are more details about this in the `ConfiguringNotebookEnvironment` notebook and in these documents:\n", "- [msticpy configuration](https://msticpy.readthedocs.io/en/latest/getting_started/msticpyconfig.html)\n", "- [Threat intelligence provider configuration](https://msticpy.readthedocs.io/en/latest/data_acquisition/TIProviders.html#configuration-file)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T17:26:20.819278Z", "start_time": "2020-12-16T17:26:20.784441Z" } }, "outputs": [], "source": [ "from pathlib import Path\r\n", "from IPython.display import display, HTML\r\n", "\r\n", "REQ_PYTHON_VER=(3, 6)\r\n", "REQ_MSTICPY_VER=(1, 0, 0)\r\n", "\r\n", "\r\n", "display(HTML(\"

Starting Notebook setup...

\"))\r\n", " \r\n", "\r\n", "# If not using Azure Notebooks, install msticpy with\r\n", "# %pip install msticpy\r\n", "from msticpy.nbtools import nbinit\r\n", "\r\n", "additional_packages = [\r\n", " \"tldextract\", \"IPWhois\", \"python-whois\"\r\n", "]\r\n", "nbinit.init_notebook(\r\n", " namespace=globals(),\r\n", " additional_packages=additional_packages,\r\n", ");\r\n", "\r\n", "from datetime import date\r\n", "from functools import lru_cache\r\n", "from msticpy.nbtools.foliummap import get_map_center, get_center_ip_entities\r\n", "from pandas import json_normalize\r\n", "from ruamel.yaml import YAML\r\n", "from tqdm.notebook import tqdm\r\n", "import IPython\r\n", "import csv\r\n", "import dns\r\n", "import glob\r\n", "import io\r\n", "import json\r\n", "import re\r\n", "import requests\r\n", "import tldextract\r\n", "import whois\r\n", "import zipfile\r\n", "from bokeh.plotting import figure" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-15T22:51:35.883260Z", "start_time": "2020-12-15T22:51:17.531950Z" } }, "outputs": [], "source": [ "# See if we have a Microsoft Sentinel Workspace defined in our config file.\n", "# If not, let the user specify Workspace and Tenant IDs\n", "\n", "ws_config = WorkspaceConfig()\n", "if not ws_config.config_loaded:\n", " ws_config.prompt_for_ws()\n", " \n", "qry_prov = QueryProvider(data_environment=\"AzureSentinel\")\n", "print(\"done\")\n", " \n", "ti = TILookup()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Configuration\n", "\n", "#### `msticpyconfig.yaml` configuration File\n", "You can configure primary and secondary TI providers and any required parameters in the `msticpyconfig.yaml` file. This is read from the current directory or you can set an environment variable (`MSTICPYCONFIG`) pointing to its location.\n", "\n", "To configure this file see the [ConfigureNotebookEnvironment notebook](https://github.com/Azure/Azure-Sentinel-Notebooks/blob/master/ConfiguringNotebookEnvironment.ipynb)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Authenticate to Microsoft Sentinel workspace\n", "qry_prov.connect(ws_config)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:00:53.013979Z", "start_time": "2020-12-16T18:00:53.008322Z" } }, "outputs": [], "source": [ "def get_solarwinds_queries_from_github(git_url, outputdir):\n", " r = requests.get(git_url)\n", "\n", " repo_zip = io.BytesIO(r.content)\n", "\n", " archive = zipfile.ZipFile(repo_zip, mode=\"r\")\n", "\n", " # Only extract Detections and Hunting Queries Folder\n", " for file in archive.namelist():\n", " if file.startswith(\n", " (\n", " \"Azure-Sentinel-master/Detections/DeviceEvents/SolarWinds_TEARDROP_Process-IOCs.yaml\",\n", " \"Azure-Sentinel-master/Detections/DeviceNetworkEvents/SolarWinds_SUNBURST_Network-IOCs.yaml\",\n", " \"Azure-Sentinel-master/Detections/DeviceProcessEvents/SolarWinds_SUNBURST_Process-IOCs.yaml\",\n", " \"Azure-Sentinel-master/Detections/DeviceFileEvents/SolarWinds_SUNBURST_&_SUPERNOVA_File-IOCs.yaml\",\n", " \"Azure-Sentinel-master/Detections/AuditLogs/MaliciousOAuthApp_O365AttackToolkit.yaml\",\n", " \"Azure-Sentinel-master/Detections/AuditLogs/MaliciousOAuthApp_PwnAuth.yaml\",\n", " \"Azure-Sentinel-master/Detections/AuditLogs/UseraddedtoPrivilgedGroups.yaml\",\n", " \"Azure-Sentinel-master/Detections/AuditLogs/ADFSDomainTrustMods.yaml\",\n", " \"Azure-Sentinel-master/Detections/AuditLogs/RareApplicationConsent.yaml\",\n", " \"Azure-Sentinel-master/Detections/SigninLogs/AzureAADPowerShellAnomaly.yaml\",\n", " \"Azure-Sentinel-master/Detections/OfficeActivity/MailItemsAccessedTimeSeries.yaml\",\n", " \"Azure-Sentinel-master/Detections/SecurityEvent/RDP_RareConnection.yaml\",\n", " \"Azure-Sentinel-master/Detections/SecurityEvent/RDP_Nesting.yaml\",\n", " \"Azure-Sentinel-master/Detections/SecurityEvent/UserCreatedAddedToBuiltinAdmins_1d.yaml\",\n", " \"Azure-Sentinel-master/Hunting Queries/SecurityEvent/HostsWithNewLogons.yaml\",\n", " \"Azure-Sentinel-master/Hunting Queries/MultipleDataSources/TrackingPrivAccounts.yaml\",\n", " \"Azure-Sentinel-master/Hunting Queries/SecurityEvent/ProcessEntropy.yaml\",\n", " \"Azure-Sentinel-master/Hunting Queries/SecurityEvent/RareProcbyServiceAccount.yaml\",\n", " \"Azure-Sentinel-master/Hunting Queries/SecurityEvent/uncommon_processes.yaml\"\n", " )\n", " ):\n", " archive.extract(file, path=outputdir)\n", " print(\"Downloaded and Extracted Files successfully\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:00:53.848269Z", "start_time": "2020-12-16T18:00:53.834945Z" } }, "outputs": [], "source": [ "def_path = Path.joinpath(Path(os.getcwd()))\r\n", "path_wgt = widgets.Text(value=str(def_path), \r\n", " description='Path to extract to zipped repo files: ', \r\n", " layout=widgets.Layout(width='50%'),\r\n", " style={'description_width': 'initial'})\r\n", "path_wgt" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:01:10.466863Z", "start_time": "2020-12-16T18:00:54.639740Z" } }, "outputs": [], "source": [ "# Download the Microsoft Sentinel Github repo as ZIP\n", "azsentinel_git_url = 'https://github.com/Azure/Azure-Sentinel/archive/master.zip'\n", "\n", "get_solarwinds_queries_from_github(git_url=azsentinel_git_url, outputdir=path_wgt.value)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:01:11.494383Z", "start_time": "2020-12-16T18:01:10.838554Z" } }, "outputs": [], "source": [ "QUERIES_PATH = 'Azure-Sentinel-master'\n", "sentinel_root = Path(path_wgt.value) / QUERIES_PATH\n", "\n", "display(HTML(\"

Listings under Detections...

\"))\n", "%ls '{sentinel_root}/Detections/'\n", "\n", "display(HTML(\"

Listings under Hunting Queries...

\"))\n", "%ls '{sentinel_root}/Hunting Queries/'" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:01:26.869159Z", "start_time": "2020-12-16T18:01:26.862783Z" } }, "outputs": [], "source": [ "def parse_yaml(parent_dir, child_dir):\n", "\n", " sentinel_repourl = \"https://github.com/Azure/Azure-Sentinel/blob/master\"\n", " \n", " # Collect list of files recusrively uinder a folder\n", " yaml_queries = glob.glob(f\"{parent_dir}/{child_dir}/**/*.yaml\", recursive=True)\n", " df = pd.DataFrame()\n", "\n", " # Parse and load yaml\n", " parsed_yaml = YAML(typ=\"safe\")\n", "\n", " # Recursively load yaml Files and append to dataframe\n", " for query in yaml_queries:\n", " with open(query, \"r\", encoding=\"utf-8\", errors=\"ignore\") as f:\n", " parsed_yaml_df = json_normalize(parsed_yaml.load(f))\n", " parsed_yaml_df[\"DetectionURL\"] = query.replace(parent_dir, sentinel_repourl)\n", " df = df.append(parsed_yaml_df, ignore_index=True, sort=True)\n", "\n", " if child_dir == \"Detections\":\n", " df[\"DetectionType\"] = \"Analytics\"\n", " elif child_dir == \"Hunting Queries\":\n", " df[\"DetectionType\"] = \"Hunting\"\n", "\n", " df[\"DetectionService\"] = \"Microsoft Sentinel Community Github\"\n", "\n", " return df" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:36:10.907807Z", "start_time": "2020-12-16T18:36:10.813688Z" } }, "outputs": [], "source": [ "base_dir = path_wgt.value + \"/Azure-Sentinel-master\"\n", "detections_df = parse_yaml(parent_dir=base_dir, child_dir=\"Detections\")\n", "hunting_df = parse_yaml(parent_dir=base_dir, child_dir=\"Hunting Queries\")\n", "\n", "frames = [detections_df, hunting_df]\n", "sentinel_github_df = pd.concat(frames).reset_index(drop=True)\n", "sentinel_github_df = sentinel_github_df.copy()\n", "sentinel_github_df[\"DetectionURL\"] = sentinel_github_df[\"DetectionURL\"].str.replace(\n", " \" \", \"%20\", regex=True\n", ")\n", "sentinel_github_df[\"IngestedDate\"] = date.today()\n", "\n", "# Displaying basic statistics of yaml files\n", "display(HTML(\"

Microsoft Sentinel Github Stats...

\"))\n", "print(\n", " f\"\"\"Total Queries in Microsoft Sentinel Github:: {len(sentinel_github_df)} \n", " No of Detections :: {len(detections_df)} \n", " No of Hunting Queries:: {len(hunting_df)}\n", " \"\"\"\n", ") \n", "\n", "display(sentinel_github_df.head())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# IOC Hunt\n", "\n", "## TearDrop Memory Only Dropper\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:01:33.612818Z", "start_time": "2020-12-16T18:01:31.619663Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"TEARDROP memory-only dropper\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "teardrop_df = qry_prov.exec_query(query)\n", "teardrop_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## SUNBURST and SUPERNOVA backdoor hashes" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:01:45.975680Z", "start_time": "2020-12-16T18:01:44.602010Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"SUNBURST and SUPERNOVA backdoor hashes\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "backdoor_df = qry_prov.exec_query(query)\n", "backdoor_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## SUNBURST network beacons" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:18:53.485809Z", "start_time": "2020-12-16T18:18:52.505866Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"SUNBURST network beacons\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "nwbeacon_df = qry_prov.exec_query(query)\n", "nwbeacon_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## SUNBURST suspicious SolarWinds child processes" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:20:04.300657Z", "start_time": "2020-12-16T18:20:03.722124Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"SUNBURST suspicious SolarWinds child processes\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "swprocess_df = qry_prov.exec_query(query)\n", "swprocess_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Checking for Solarwinds Servers \n", "\n", "To hunt for similar TTPs used in this attack, a good place to start is to build an inventory of the machines that have SolarWinds Orion components. Organizations might already have a software inventory management system to indicate hosts where the SolarWinds application is installed. Alternatively, Microsoft Sentinel could be leveraged to run a simple query to gather similar details. Below query will pull the hosts with SolarWinds process running in last 30 days based on Process execution/Sysmon logs either via host onboarded to Sentinel or onboarded via Microsoft Defender for Endpoints (MDE)\n", "\n", "## Asset Inventory \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-15T22:51:51.988656Z", "start_time": "2020-12-15T22:51:48.660679Z" } }, "outputs": [], "source": [ "# Gather Solarwinds details based on Process execution Logs from diverse data sources\n", "solarwinds_query = f\"\"\"\n", "let timeframe = 30d;\n", "(union isfuzzy=true\n", " (\n", " SecurityEvent\n", " | where TimeGenerated >= ago(timeframe)\n", " | where EventID == '4688'\n", " | where tolower(NewProcessName) has 'solarwinds'\n", " | extend MachineName = Computer , Process = NewProcessName\n", " ),\n", " (\n", " DeviceProcessEvents\n", " | where TimeGenerated >= ago(timeframe)\n", " | where tolower(InitiatingProcessFolderPath) has 'solarwinds'\n", " | extend MachineName = DeviceName , Process = InitiatingProcessFolderPath\n", " ),\n", " (\n", " Event\n", " | where TimeGenerated >= ago(timeframe)\n", " | where Source == \"Microsoft-Windows-Sysmon\"\n", " | where EventID == 1\n", " | extend Image = EventDetail.[4].[\"#text\"]\n", " | where tolower(Image) has 'solarwinds'\n", " | extend MachineName = Computer , Process = Image\n", " )\n", " )\n", " | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), make_set(Process) by MachineName\n", "\"\"\"\n", "print(\"Collecting data...\")\n", "solarwinds_assets = qry_prov.exec_query(solarwinds_query)\n", "md(f'Solarwinds Server Details Gathered', styles=[\"bold\", \"large\"])\n", "display(solarwinds_assets)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Lateral Movement" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Hosts with New Logons" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"Hosts with new logons\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "hostswithnewlogons_df = qry_prov.exec_query(query)\n", "hostswithnewlogons_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Rare RDP Connections" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"Rare RDP Connections\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "rarerdp_df = qry_prov.exec_query(query)\n", "rarerdp_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### RDP Nesting" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"RDP Nesting\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "rdpnest_df = qry_prov.exec_query(query)\n", "rdpnest_df" ] }, { "cell_type": "markdown", "metadata": { "ExecuteTime": { "end_time": "2020-12-16T19:23:08.425483Z", "start_time": "2020-12-16T19:23:08.423000Z" } }, "source": [ "### Rare application consent" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T19:27:18.186385Z", "start_time": "2020-12-16T19:27:16.556190Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"Rare application consent\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "rareappconsent_df = qry_prov.exec_query(query)\n", "rareappconsent_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Rare activity by High Value Account" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"Rare processes run by Service accounts\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "serviceaccproc_df = qry_prov.exec_query(query)\n", "serviceaccproc_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Suspicious Logons\n", "\n", "If you have suspicious Netblocks not expected in your organization(e.g.VPS Netblocks or Proxy severs) that you want to monior , define it in IP_Data and run below query using `ipv4_lookup` kql function\n", "\n", "\n", "```\n", "query = \"\"\"\n", "\n", "SigninLogs \n", "| where TimeGenerated > ago(360d) \n", "| where ResultType == 0 \n", "| extend additionalDetails = tostring(Status.additionalDetails) \n", "| evaluate ipv4_lookup(IP_Data, IPAddress, network, return_unmatched = false) \n", "| summarize make_set(additionalDetails), min(TimeGenerated), max(TimeGenerated) by IPAddress, UserPrincipalName \n", "| where array_length(set_additionalDetails) == 2 \n", "| where (set_additionalDetails[1] == \"MFA requirement satisfied by claim in the token\" and set_additionalDetails[0] == \"MFA requirement satisfied by claim provided by external provider\") or (set_additionalDetails[0] == \"MFA requirement satisfied by claim in the token\" and set_additionalDetails[1] == \"MFA requirement satisfied by claim provided by external provider\") \n", "| project IPAddress, UserPrincipalName, min_TimeGenerated, max_TimeGenerated\n", "\"\"\"\n", "print(\"Collecting data...\")\n", "suspmfalogons_df = qry_prov.exec_query(query)\n", "suspmfalogons_df\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Privilege Escalation\n", "## New Account Creation and Addition to Privileged groups" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T19:20:30.396779Z", "start_time": "2020-12-16T19:20:29.715590Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"New user created and added to the built-in administrators group\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "newaccprivgroups_df = qry_prov.exec_query(query)\n", "newaccprivgroups_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## User Added to Azure Active Directory Privileged Groups" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T19:13:57.710595Z", "start_time": "2020-12-16T19:13:56.352902Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"User added to Azure Active Directory Privileged Groups\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "aadprivgroups_df = qry_prov.exec_query(query)\n", "aadprivgroups_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ADFS Hunting\n", "\n", "Below Queries produces results from all Servers. Its is advisable to filter the results for ADFS servers in scope. \n", "\n", "### Uncommon processes " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"Uncommon processes - bottom 5%\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "domain_trust_df = qry_prov.exec_query(query)\n", "domain_trust_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Rare processes run by Service Accounts" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"Rare processes run by Service accounts\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "serviceaccproc_df = qry_prov.exec_query(query)\n", "serviceaccproc_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Fireeye Red Team Tools\n", "Check out KQL queries published by Sentinel Community at \n", " - https://github.com/FalconForceTeam/FalconFriday/blob/master/Uncategorized/FireEye_red_team_tool_countermeasures.md" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Azure Active Directory Hunting\n", "\n", "### Domain Federation trust Settings modification\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:45:54.742556Z", "start_time": "2020-12-16T18:45:52.967980Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"Modified domain federation trust settings\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "domain_trust_df = qry_prov.exec_query(query)\n", "domain_trust_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Addition of New Key Credentials to an Application or Service Principal" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:48:33.559077Z", "start_time": "2020-12-16T18:48:32.554255Z" } }, "outputs": [], "source": [ "query = \"\"\"\n", " let auditLookback = 1h;\n", " AuditLogs\n", " | where TimeGenerated > ago(auditLookback)\n", " | where OperationName has_any (\"Add service principal\", \"Certificates and secrets management\") // captures \"Add service principal\", \"Add service principal credentials\", and \"Update application – Certificates and secrets management\" events\n", " | extend targetDisplayName = TargetResources[0].displayName\n", " | extend targetId = TargetResources[0].id\n", " | extend targetType = TargetResources[0].type\n", " | extend keyEvents = TargetResources[0].modifiedProperties\n", " | where keyEvents has \"KeyIdentifier=\" and keyEvents has \"KeyUsage=Verify\"\n", " | where Result =~ \"success\"\n", " | mv-expand keyEvents\n", " | where keyEvents.displayName =~ \"KeyDescription\"\n", " | parse keyEvents.newValue with * \"KeyIdentifier=\" keyIdentifier:string \",KeyType=\" keyType:string \",KeyUsage=\" keyUsage:string \",DisplayName=\" keyDisplayName:string \"]\" *\n", " | parse keyEvents.oldValue with * \"KeyIdentifier=\" keyIdentifierOld:string \",\" *\n", " | where keyEvents.oldValue == \"[]\" or keyIdentifier != keyIdentifierOld\n", " | where keyUsage == \"Verify\"\n", " | extend UserAgent = iff(AdditionalDetails[0].key == \"User-Agent\",AdditionalDetails[0].value,\"\")\n", " | extend InitiatingUser = iff(isnotempty(InitiatedBy.user.userPrincipalName),InitiatedBy.user.userPrincipalName, InitiatedBy.app.displayName)\n", " | extend InitiatingIpAddress = iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress)\n", " // \n", " // Adding the below filter for detection-quality events; Microsoft Sentinel users can comment out this line and tune additional service principal events for their environment\n", " | where targetType =~ \"Application\"\n", "\"\"\"\n", "print(\"Collecting data...\")\n", "newkeycreds_df = qry_prov.exec_query(query)\n", "newkeycreds_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Suspicious Application Consent" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:50:45.580748Z", "start_time": "2020-12-16T18:50:44.389349Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"Suspicious application consent similar to O365 Attack Toolkit\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "o365toolkit_df = qry_prov.exec_query(query)\n", "o365toolkit_df" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:51:22.522931Z", "start_time": "2020-12-16T18:51:21.424873Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"Suspicious application consent similar to PwnAuth\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "o365pwnauth_df = qry_prov.exec_query(query)\n", "o365pwnauth_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Data Acccess and Exfiltration\n", "\n", "### Azure Active Directory PowerShell to access non-Active Directory Resources" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:30:43.152632Z", "start_time": "2020-12-16T18:30:41.130766Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"name\"] == \"Azure Active Directory PowerShell accessing non-AAD resources\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "adpowershell_df = qry_prov.exec_query(query)\n", "adpowershell_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Anomalous increase in Exchange Mail Items Accesed" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2020-12-16T18:42:22.052149Z", "start_time": "2020-12-16T18:42:19.877046Z" } }, "outputs": [], "source": [ "query = sentinel_github_df.loc[\n", " sentinel_github_df[\"id\"] == \"b4ceb583-4c44-4555-8ecf-39f572e827ba\"\n", "][\"query\"].reset_index(drop=True)[0]\n", "print(\"Collecting data...\")\n", "timeseriesmail_df = qry_prov.exec_query(query)\n", "timeseriesmail_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Resources\n", "\n", "- Read the [Get Started](https://nbviewer.jupyter.org/github/Azure/Azure-Sentinel-Notebooks/blob/master/A%20Getting%20Started%20Guide%20For%20Azure%20Sentinel%20ML%20Notebooks.ipynb) notebook.\n", "- View sample notebooks in the [Sample-Notebooks](https://github.com/Azure/Azure-Sentinel-Notebooks/tree/master/Sample-Notebooks) folder\n", "- View How-tos and Troubleshooting in the [HowTos](https://github.com/Azure/Azure-Sentinel-Notebooks/tree/master/HowTos) folder\n", "- https://msticpy.readthedocs.io/en/latest/GettingStarted.html\n", "- https://msticnb.readthedocs.io/en/latest/notebooklets.html#current-notebooklets\n", "\n", "- [Recent Nation-State Cyber Attacks](https://blogs.microsoft.com/on-the-issues/2020/12/13/customers-protect-nation-state-cyberattacks/)\n", "\n", "- [Behavior:Win32/Solorigate.C!dha threat description - Microsoft Security Intelligence ](https://blogs.microsoft.com/on-the-issues/2020/12/13/customers-protect-nation-state-cyberattacks/)\n", "\n", "- [Customer guidance on recent nation-state cyberattacks](https://www.microsoft.com/wdsi/threats/malware-encyclopedia-description?Name=Behavior:Win32/Solorigate.C!dha&ThreatID=2147771132)\n", "\n", "- [FireEye Advisory: Highly Evasive Attacker Leverages SolarWinds Supply Chain to Compromise Multiple Global Victims With SUNBURST Backdoor ](https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html)\n", "\n", "- [FireEye GitHub page: Sunburst Countermeasures  ](https://github.com/fireeye/sunburst_countermeasures)\n", "\n", "- [DHS Directive ](https://cyber.dhs.gov/ed/21-01/)\n", "\n", "- [SolarWinds Security Advisory ](https://www.solarwinds.com/securityadvisory)\n", "\n", "- [FalconFriday – Fireeye Red Team Tool Countermeasures KQL Queries ](https://github.com/FalconForceTeam/FalconFriday/blob/master/Uncategorized/FireEye_red_team_tool_countermeasures.md)\n", "\n", "- [microsoft/Microsoft-365-Defender-Hunting-Queries: Sample queries for Advanced hunting in Microsoft 365 Defender (github.com) ](https://github.com/microsoft/Microsoft-365-Defender-Hunting-Queries)\n", "\n", "- [Microsoft Sentinel SolarWinds Post Compromise Hunting Workbook](https://github.com/Azure/Azure-Sentinel/blob/master/Workbooks/SolarWindsPostCompromiseHunting.json)\n", "\n", "- [https://security.microsoft.com/threatanalytics3/2b74f636-146e-48dd-94f6-5cb5132467ca/overview]" ] } ], "metadata": { "hide_input": false, "kernelspec": { "display_name": "Python 3.8 - AzureML", "language": "python", "name": "python38-azureml" }, "language_info": { "name": "python", "version": "" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": false, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": true, "toc_position": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "257.323px" }, "toc_section_display": true, "toc_window_display": true }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 4 }