from pathlib import Path from IPython.display import display, HTML REQ_PYTHON_VER=(3, 6) REQ_MSTICPY_VER=(1, 0, 0) display(HTML("

Starting Notebook setup...

")) # If not using Azure Notebooks, install msticpy with # %pip install msticpy from msticpy.nbtools import nbinit additional_packages = [ "tldextract", "IPWhois", "python-whois" ] nbinit.init_notebook( namespace=globals(), additional_packages=additional_packages, ); from datetime import date from functools import lru_cache from msticpy.nbtools.foliummap import get_map_center, get_center_ip_entities from pandas import json_normalize from ruamel.yaml import YAML from tqdm.notebook import tqdm import IPython import csv import dns import glob import io import json import re import requests import tldextract import whois import zipfile from bokeh.plotting import figure # See if we have a Microsoft Sentinel Workspace defined in our config file. # If not, let the user specify Workspace and Tenant IDs ws_config = WorkspaceConfig() if not ws_config.config_loaded: ws_config.prompt_for_ws() qry_prov = QueryProvider(data_environment="AzureSentinel") print("done") ti = TILookup() # Authenticate to Microsoft Sentinel workspace qry_prov.connect(ws_config) def get_solarwinds_queries_from_github(git_url, outputdir): r = requests.get(git_url) repo_zip = io.BytesIO(r.content) archive = zipfile.ZipFile(repo_zip, mode="r") # Only extract Detections and Hunting Queries Folder for file in archive.namelist(): if file.startswith( ( "Azure-Sentinel-master/Detections/DeviceEvents/SolarWinds_TEARDROP_Process-IOCs.yaml", "Azure-Sentinel-master/Detections/DeviceNetworkEvents/SolarWinds_SUNBURST_Network-IOCs.yaml", "Azure-Sentinel-master/Detections/DeviceProcessEvents/SolarWinds_SUNBURST_Process-IOCs.yaml", "Azure-Sentinel-master/Detections/DeviceFileEvents/SolarWinds_SUNBURST_&_SUPERNOVA_File-IOCs.yaml", "Azure-Sentinel-master/Detections/AuditLogs/MaliciousOAuthApp_O365AttackToolkit.yaml", "Azure-Sentinel-master/Detections/AuditLogs/MaliciousOAuthApp_PwnAuth.yaml", "Azure-Sentinel-master/Detections/AuditLogs/UseraddedtoPrivilgedGroups.yaml", "Azure-Sentinel-master/Detections/AuditLogs/ADFSDomainTrustMods.yaml", "Azure-Sentinel-master/Detections/AuditLogs/RareApplicationConsent.yaml", "Azure-Sentinel-master/Detections/SigninLogs/AzureAADPowerShellAnomaly.yaml", "Azure-Sentinel-master/Detections/OfficeActivity/MailItemsAccessedTimeSeries.yaml", "Azure-Sentinel-master/Detections/SecurityEvent/RDP_RareConnection.yaml", "Azure-Sentinel-master/Detections/SecurityEvent/RDP_Nesting.yaml", "Azure-Sentinel-master/Detections/SecurityEvent/UserCreatedAddedToBuiltinAdmins_1d.yaml", "Azure-Sentinel-master/Hunting Queries/SecurityEvent/HostsWithNewLogons.yaml", "Azure-Sentinel-master/Hunting Queries/MultipleDataSources/TrackingPrivAccounts.yaml", "Azure-Sentinel-master/Hunting Queries/SecurityEvent/ProcessEntropy.yaml", "Azure-Sentinel-master/Hunting Queries/SecurityEvent/RareProcbyServiceAccount.yaml", "Azure-Sentinel-master/Hunting Queries/SecurityEvent/uncommon_processes.yaml" ) ): archive.extract(file, path=outputdir) print("Downloaded and Extracted Files successfully") def_path = Path.joinpath(Path(os.getcwd())) path_wgt = widgets.Text(value=str(def_path), description='Path to extract to zipped repo files: ', layout=widgets.Layout(width='50%'), style={'description_width': 'initial'}) path_wgt # Download the Microsoft Sentinel Github repo as ZIP azsentinel_git_url = 'https://github.com/Azure/Azure-Sentinel/archive/master.zip' get_solarwinds_queries_from_github(git_url=azsentinel_git_url, outputdir=path_wgt.value) QUERIES_PATH = 'Azure-Sentinel-master' sentinel_root = Path(path_wgt.value) / QUERIES_PATH display(HTML("

Listings under Detections...

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

Listings under Hunting Queries...

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

Microsoft Sentinel Github Stats...

")) print( f"""Total Queries in Microsoft Sentinel Github:: {len(sentinel_github_df)} No of Detections :: {len(detections_df)} No of Hunting Queries:: {len(hunting_df)} """ ) display(sentinel_github_df.head()) query = sentinel_github_df.loc[ sentinel_github_df["name"] == "TEARDROP memory-only dropper" ]["query"].reset_index(drop=True)[0] print("Collecting data...") teardrop_df = qry_prov.exec_query(query) teardrop_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "SUNBURST and SUPERNOVA backdoor hashes" ]["query"].reset_index(drop=True)[0] print("Collecting data...") backdoor_df = qry_prov.exec_query(query) backdoor_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "SUNBURST network beacons" ]["query"].reset_index(drop=True)[0] print("Collecting data...") nwbeacon_df = qry_prov.exec_query(query) nwbeacon_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "SUNBURST suspicious SolarWinds child processes" ]["query"].reset_index(drop=True)[0] print("Collecting data...") swprocess_df = qry_prov.exec_query(query) swprocess_df # Gather Solarwinds details based on Process execution Logs from diverse data sources solarwinds_query = f""" let timeframe = 30d; (union isfuzzy=true ( SecurityEvent | where TimeGenerated >= ago(timeframe) | where EventID == '4688' | where tolower(NewProcessName) has 'solarwinds' | extend MachineName = Computer , Process = NewProcessName ), ( DeviceProcessEvents | where TimeGenerated >= ago(timeframe) | where tolower(InitiatingProcessFolderPath) has 'solarwinds' | extend MachineName = DeviceName , Process = InitiatingProcessFolderPath ), ( Event | where TimeGenerated >= ago(timeframe) | where Source == "Microsoft-Windows-Sysmon" | where EventID == 1 | extend Image = EventDetail.[4].["#text"] | where tolower(Image) has 'solarwinds' | extend MachineName = Computer , Process = Image ) ) | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), make_set(Process) by MachineName """ print("Collecting data...") solarwinds_assets = qry_prov.exec_query(solarwinds_query) md(f'Solarwinds Server Details Gathered', styles=["bold", "large"]) display(solarwinds_assets) query = sentinel_github_df.loc[ sentinel_github_df["name"] == "Hosts with new logons" ]["query"].reset_index(drop=True)[0] print("Collecting data...") hostswithnewlogons_df = qry_prov.exec_query(query) hostswithnewlogons_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "Rare RDP Connections" ]["query"].reset_index(drop=True)[0] print("Collecting data...") rarerdp_df = qry_prov.exec_query(query) rarerdp_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "RDP Nesting" ]["query"].reset_index(drop=True)[0] print("Collecting data...") rdpnest_df = qry_prov.exec_query(query) rdpnest_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "Rare application consent" ]["query"].reset_index(drop=True)[0] print("Collecting data...") rareappconsent_df = qry_prov.exec_query(query) rareappconsent_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "Rare processes run by Service accounts" ]["query"].reset_index(drop=True)[0] print("Collecting data...") serviceaccproc_df = qry_prov.exec_query(query) serviceaccproc_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "New user created and added to the built-in administrators group" ]["query"].reset_index(drop=True)[0] print("Collecting data...") newaccprivgroups_df = qry_prov.exec_query(query) newaccprivgroups_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "User added to Azure Active Directory Privileged Groups" ]["query"].reset_index(drop=True)[0] print("Collecting data...") aadprivgroups_df = qry_prov.exec_query(query) aadprivgroups_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "Uncommon processes - bottom 5%" ]["query"].reset_index(drop=True)[0] print("Collecting data...") domain_trust_df = qry_prov.exec_query(query) domain_trust_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "Rare processes run by Service accounts" ]["query"].reset_index(drop=True)[0] print("Collecting data...") serviceaccproc_df = qry_prov.exec_query(query) serviceaccproc_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "Modified domain federation trust settings" ]["query"].reset_index(drop=True)[0] print("Collecting data...") domain_trust_df = qry_prov.exec_query(query) domain_trust_df query = """ let auditLookback = 1h; AuditLogs | where TimeGenerated > ago(auditLookback) | 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 | extend targetDisplayName = TargetResources[0].displayName | extend targetId = TargetResources[0].id | extend targetType = TargetResources[0].type | extend keyEvents = TargetResources[0].modifiedProperties | where keyEvents has "KeyIdentifier=" and keyEvents has "KeyUsage=Verify" | where Result =~ "success" | mv-expand keyEvents | where keyEvents.displayName =~ "KeyDescription" | parse keyEvents.newValue with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" * | parse keyEvents.oldValue with * "KeyIdentifier=" keyIdentifierOld:string "," * | where keyEvents.oldValue == "[]" or keyIdentifier != keyIdentifierOld | where keyUsage == "Verify" | extend UserAgent = iff(AdditionalDetails[0].key == "User-Agent",AdditionalDetails[0].value,"") | extend InitiatingUser = iff(isnotempty(InitiatedBy.user.userPrincipalName),InitiatedBy.user.userPrincipalName, InitiatedBy.app.displayName) | extend InitiatingIpAddress = iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress) // // 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 | where targetType =~ "Application" """ print("Collecting data...") newkeycreds_df = qry_prov.exec_query(query) newkeycreds_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "Suspicious application consent similar to O365 Attack Toolkit" ]["query"].reset_index(drop=True)[0] print("Collecting data...") o365toolkit_df = qry_prov.exec_query(query) o365toolkit_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "Suspicious application consent similar to PwnAuth" ]["query"].reset_index(drop=True)[0] print("Collecting data...") o365pwnauth_df = qry_prov.exec_query(query) o365pwnauth_df query = sentinel_github_df.loc[ sentinel_github_df["name"] == "Azure Active Directory PowerShell accessing non-AAD resources" ]["query"].reset_index(drop=True)[0] print("Collecting data...") adpowershell_df = qry_prov.exec_query(query) adpowershell_df query = sentinel_github_df.loc[ sentinel_github_df["id"] == "b4ceb583-4c44-4555-8ecf-39f572e827ba" ]["query"].reset_index(drop=True)[0] print("Collecting data...") timeseriesmail_df = qry_prov.exec_query(query) timeseriesmail_df