MSTICpy includes a feature called nbinit that handles the process of installing and importing modules into a notebook environment. This was developed to allow for a clearer starting cell in notebooks and to avoid users being presented with a very large cell block at the top of a notebook.
By passing the notebook namespace to init_notebook() this function handles the job of installing and importing core MSTICpy packages along with any others that might be needed by a notebook.
You must have msticpy installed to run this notebook:
%pip install --upgrade msticpy[timeseries, splunk, azsentinel]
MSTICpy versions > 0.8.5
The notebook also uses MSTIC Notebooklets:
%pip install --upgrade msticnb
from msticpy.nbtools import nbinit
extra_imports = [
"msticpy.vis.timeseries, display_timeseries_anomolies",
"msticpy.analysis.timeseries, timeseries_anomalies_stl",
"datetime, datetime",
"msticpy.vis.nbdisplay, draw_alert_entity_graph",
"msticpy.context.ip_utils, convert_to_ip_entities",
"msticpy.vis.ti_browser, browse_results",
"IPython.display, Image",
"msticpy.context.ip_utils, get_whois_info",
"msticpy.context.ip_utils, get_ip_type"
]
nbinit.init_notebook(
namespace=globals(),
additional_packages=["pyvis"],
extra_imports=extra_imports,
);
from msticpy.context import TILookup
ti = TILookup()
--------------------------------------------------------------------------- ImportError Traceback (most recent call last) ~\AppData\Local\Temp\ipykernel_45856\3785168399.py in <module> ----> 1 from msticpy.nbtools import nbinit 2 extra_imports = [ 3 "msticpy.vis.timeseries, display_timeseries_anomolies", 4 "msticpy.analysis.timeseries, timeseries_anomalies_stl", 5 "datetime, datetime", ImportError: cannot import name 'nbinit' from 'msticpy.nbtools' (e:\src\msticpy\msticpy\nbtools\__init__.py)
The starting point for many notebooks is ingesting data to conduct analysis or investigation of. MSTICpy has a number of query providers to allow users to query and return data from a number of sources. Below we are using the Splunk query provider to return data from our Splunk instance.
Note: Using Splunk API via splunk-sdk Python package - the MSTICpy Splunk provider is in beta.
Data is returned in a Pandas DataFrame for easy manipulation and to provide a common interface for other features in MSTICpy.
Here we are getting a summary of our network traffic in the time period we are interested in.
splunk_host = widgets.Text(description='Splunk Host:')
splunk_user = widgets.Text(description='Splunk User:')
splunk_pwd = widgets.Password(description='Splunk Pwd:')
display(splunk_host)
display(splunk_user)
display(splunk_pwd)
# Initialize a Splunk provider and connect to our Splunk instance.
splunk_prov = QueryProvider("Splunk")
splunk_prov.connect(host=splunk_host.value, username=splunk_user.value, password=splunk_pwd.value)
connected
# Define a Splunk Query and run it.
splunk_query = "search host=network_sum index=blackhat earliest=0 | table TimeGenerated, TotalBytesSent"
stldemo = splunk_prov.exec_query(splunk_query)
stldemo['TimeGenerated'] = pd.to_datetime(stldemo['TimeGenerated'])
stldemo.set_index('TimeGenerated', inplace=True)
stldemo.sort_index(inplace=True)
stldemo.head()
TotalBytesSent | |
---|---|
TimeGenerated | |
2020-07-06 00:00:00+00:00 | 10823 |
2020-07-06 01:00:00+00:00 | 14821 |
2020-07-06 02:00:00+00:00 | 13532 |
2020-07-06 03:00:00+00:00 | 11947 |
2020-07-06 04:00:00+00:00 | 11193 |
Once we have queried the data, there are a number of analysis features within MSTICpy to help understand the data and identify potential security incidents.
In order to effectively hunt in a dataset analysts need to focus on specific events of interest. Below we use MSTICpy's time series analysis machine learning capabilities to identify anomalies in our network traffic for further investigation.
As well as computing anomalies we visualize the data so that we can more easily see where these anomalies present themselves.
Note: Visualization powered by Bokeh
# Conduct our timeseries analyis
output = timeseries_anomalies_stl(stldemo)
# Visualize the timeseries and any anomalies
display_timeseries_anomolies(data=output, y= 'TotalBytesSent')
# Identify when the anomalies occur so that we can use this timnerange to scope the next stage of our investigation.
start = output[output['anomalies']==1]['TimeGenerated'].min()
end = output[output['anomalies']==1]['TimeGenerated'].max() + pd.to_timedelta(1, unit='h')
# md and md_warn are MSTICpy features to provide simple, and clean output in notebook cells
md(f"Anomalous session start time: {start} - end time: {end}")
Anomalous session start time: 2020-07-10 18:00:00+00:00 - end time: 2020-07-10 22:00:00+00:00
With the time series analysis identifying several events of interest we need additional context to be able to effectively conduct a security investigation. MSTICpy has a range of features to help enrich key data types and provide that context depending on the entities being used.
To get these entities we again query Splunk to get the IP addresses associated with the anomalous traffic.
splunk_query = "search host=network_raw index=blackhat earliest=0 | table TimeGenerated, Action, SourceIP, DestinationIP, TotalBytesSent"
net_data = splunk_prov.exec_query(splunk_query)
# We need to identify what network endpoints are associated with the anomalies
net_data['TotalBytesSent'] = net_data['TotalBytesSent'].astype(int)
grouped_df = net_data.groupby(['SourceIP', 'DestinationIP'])
noisy_hosts = grouped_df['TotalBytesSent'].agg(np.sum).sort_values(ascending=False)
md("Top talkers during anomolous session: ", 'bold')
display(noisy_hosts[:5])
source_ip, dest_ip = noisy_hosts.index[0][0:2]
Top talkers during anomolous session:
SourceIP DestinationIP 20.185.182.48 31.220.60.108 8328 10.16.12.1 40.124.45.19 1004 10.0.3.5 40.124.45.19 621 10.4.5.12 13.71.172.130 247 40.77.232.95 189 Name: TotalBytesSent, dtype: int32
MSTICpy can help analysts investigate an IP address, using open source information such as passive DNS data, IP geolocation and threat intelligence feeds to provide valuable context.
Note: Whois module uses ipwhois, TI module uses services from OTX, VirusTotal, XForce, AzureSentinel, and OpenPageRank
# Get and display WhoIs data
md(f"Target IP: {dest_ip}", 'bold')
md(f"{dest_ip} is a {get_ip_type(dest_ip)} IP address")
whois_info = get_whois_info(dest_ip)
md(f'Whois Registrar Info :', styles=["bold"])
md(f"ASN Owner: {whois_info[0]}")
md(f"ASN Address: {whois_info[1]['nets'][0]['address']}")
# Get Passive DNS results
result = ti.lookup_ioc(observable=dest_ip, ico_type="ipv4", ioc_query_type="passivedns", providers=["XForce"])
md(f"Passive DNS records for {dest_ip}:", styles=["bold"])
for res in ti.result_to_df(result)['RawResult'][0]['Passive']['records']:
print(res['value']," - ", res['last'])
# Lookup ip IPAddress in threat intel feeds
resp = ti.lookup_ioc(observable=dest_ip)
md(f"Threat Intel results for {dest_ip}:", styles=["bold"])
ti.result_to_df(resp)
Target IP: 31.220.60.108
31.220.60.108 is a Public IP address
Whois Registrar Info :
ASN Owner: AS-HOSTINGER, LT
ASN Address: Hostinger International Ltd. 61 Lordou Vyronos Lumiel Building, 4th floor 6023 Larnaca CYPRUS
Passive DNS records for 31.220.60.108:
joblly.com - 2020-07-09T10:48:00Z cdn-xhr.com - 2020-07-09T10:48:00Z rackxhr.com - 2020-07-09T10:48:00Z hixrq.net - 2020-07-09T10:48:00Z idpcdn-cloud.com - 2020-07-09T10:48:00Z thxrq.com - 2020-07-09T10:48:00Z hivnd.net - 2020-07-09T10:48:00Z
Threat Intel results for 31.220.60.108:
Ioc | IocType | QuerySubtype | Provider | Result | Severity | Details | RawResult | Reference | Status | |
---|---|---|---|---|---|---|---|---|---|---|
OTX | 31.220.60.108 | ipv4 | None | OTX | True | high | {'pulse_count': 6, 'names': ['Card Skimmer Found Hitting Vulnerable E-Commerce Sites', 'Credit c... | {'sections': ['general', 'geo', 'reputation', 'url_list', 'passive_dns', 'malware', 'nids_list',... | https://otx.alienvault.com/api/v1/indicators/IPv4/31.220.60.108/general | 0 |
XForce | 31.220.60.108 | ipv4 | None | XForce | True | high | {'score': 7.1, 'cats': {'Malware': 71}, 'categoryDescriptions': {'Malware': 'This category lists... | {'ip': '31.220.60.108', 'history': [{'created': '2012-03-22T07:26:00.000Z', 'reason': 'Regional ... | https://api.xforce.ibmcloud.com/ipr/31.220.60.108 | 0 |
As well as returning the raw data from these enrichment sources MSTICpy has features to allow for visualization of that data to make it more accessible. Below we get the IP geolocation and use the Folium Map feature to plot the IP address location on an interactive map.
Note: uses the Python Folium package, which is a wrapper around Leafletjs
# Plot IP geolocation on a map
folium_map = FoliumMap(zoom_start=4)
md('<h3>Location of remote IP</h3>')
folium_map.add_ip_cluster(ip_entities=convert_to_ip_entities(dest_ip), color="red")
folium_map.center_map()
folium_map
--------------------------------------------------------------------------- NameError Traceback (most recent call last) ~\AppData\Local\Temp\ipykernel_45856\33716795.py in <module> 1 # Plot IP geolocation on a map ----> 2 folium_map = FoliumMap(zoom_start=4) 3 md('<h3>Location of remote IP</h3>') 4 folium_map.add_ip_cluster(ip_entities=convert_to_ip_entities(dest_ip), color="red") 5 folium_map.center_map() NameError: name 'FoliumMap' is not defined
Once we have some context on our remote IP address we can pivot our investigation to look at the local host that has been communicating with it.
As well as Splunk, MSTICpy has a query provider for Azure Sentinel. For the next phase of our investigation we are going to use this query provider to acquire data.
With the Splunk connection we provided connection details directly to our query provider when calling .connect()
. We can also store details in a msticpy configuration file (msticpyconfig.yaml
) and pass them to the query provider programmatically. Here we use the Workspace Config feature to access this configuration and retrieve the items we need to authenticate to Azure Sentinel.
Note: the authentication flow for Azure Sentinel is different from Splunk and use the Oauth2.0 device code process.
# Initalize and connect to Azure Sentinel using details from our config file.
qry_prov = QueryProvider('LogAnalytics')
wkspace = WorkspaceConfig()
qry_prov.connect(wkspace.code_connect_str)
Please wait. Loading Kqlmagic extension...
Once connected we can query Azure Sentinel in a similar way to Splunk - by providing a text query string.
We substitute the source_ip
value we obtained in the previous section Enrich and Pivot on IP Addresses
# Query Azure Sentinel to get host details.
query = f"Heartbeat | where ComputerIP == '{source_ip}'"
host = qry_prov.exec_query(query)
host_name = host['Computer'].iloc[0]
md(f"Host to investigate: <b>{host_name}t</b>")
Host to investigate: BlackHatDemoHost
Now that we have identified our host we want to perform some standard analysis to get a summary of the host. Rather than code these steps individually each time we create a notebook that investigates hosts we have grouped together several MSTICpy features and investigation steps into a single function we call a notebooklet - by calling this notebooklet we can easily conduct analysis that would require hundreds of lines of code if coded directly in a notebook.
# Initalize our notebooklets
import msticnb as nb
from msticnb.common import TimeSpan
nb.init()
tspan = TimeSpan(start=start, end=end)
# Select our notebooklet
nblet = nb.nblts.azsent.host.HostSummary()
# Run our notebooklet
out = nblet.run(value=host_name, timespan=tspan)
Loaded providers: LogAnalytics, azuredata, geolitelookup, tilookup
{ 'AdditionalData': {}, 'AzureDetails': { 'ResourceDetails': { 'Admin User': 'mstic_admin', 'Azure Location': 'eastus', 'Disks': [], 'Image': 'Windows-10 rs5-pro', 'Network Interfaces': [ '/subscriptions/40dcc8bf-0478-4f3b-b275-ed0a94f2c013/resourceGroups/BlackHatDemo/providers/Microsoft.Network/networkInterfaces/blackhatdemohost86'], 'Tags': "{'Role': 'Demo'}", 'VM Size': 'Standard_B2s'}, 'ResourceGroup': 'BlackHatDemo', 'ResourceId': '/subscriptions/40dcc8bf-0478-4f3b-b275-ed0a94f2c013/resourceGroups/BlackHatDemo/providers/Microsoft.Compute/virtualMachines/BlackHatDemoHost', 'ResourceProvider': 'Microsoft.Compute', 'ResourceType': 'virtualMachines', 'Solutions': '"security", "changeTracking", "networkMonitoring", "serviceMap", ' '"securityCenterFree", "securityInsights", "windowsFirewall", ' '"windowsEventForwarding"', 'SubscriptionDetails': { 'Display Name': 'ASI Hunting Demo Environment', 'Spending Limit': <SpendingLimit.off: 'Off'>, 'State': 'SubscriptionState.enabled', 'Subscription ID': '40dcc8bf-0478-4f3b-b275-ed0a94f2c013', 'Subscription Location': 'Internal_2014-09-01', 'Subscription Quota': 'Internal_2014-09-01'}, 'SubscriptionId': '40dcc8bf-0478-4f3b-b275-ed0a94f2c013'}, 'Environment': 'Azure', 'HostName': 'BlackHatDemoHos', 'IPAddress': { 'AdditionalData': {}, 'Address': '20.185.182.48', 'Location': { 'AdditionalData': {}, 'CountryName': 'United States', 'Latitude': 38.71, 'Longitude': -78.16, 'Type': 'geolocation'}, 'Type': 'ipaddress'}, 'OSName': '', 'OSType': 'Windows', 'OSVMajorVersion': '10', 'OSVMinorVersion': '0', 'OmsSolutions': [ '"security"', '"changeTracking"', '"networkMonitoring"', '"serviceMap"', '"securityCenterFree"', '"securityInsights"', '"windowsFirewall"', '"windowsEventForwarding"'], 'SourceComputerId': '73a015ec-e2b6-4bf7-b353-ebeafb54254e', 'Type': 'host', 'VMUUID': '3f2b6a14-4c02-41aa-a2e8-6859ee4c7847', 'private_ips': [], 'public_ips': []}
As well as providing query providers to get data from SIEM solutions such as Azure Sentinel, MSTICpy also has the capability to acquire data from other sources such as the Azure APIs. Below we use these features to collect information and metrics on our host.
We then use the MSTICpy interactive timeline visualization in order to display this data.
from msticpy.data.azure_data import AzureData
# Initalize and connect to Azure
az = AzureData()
az.connect()
# Get details on our subscription and virtal machines
sub_id = az.get_subscriptions().iloc[0]['Subscription ID']
resources = az.get_resources(sub_id)
display(resources[resources['name'] == "BlackHatDemoHost"])
res_id = resources[resources['name'] == "BlackHatDemoHost"].iloc[0]['resource_id']
resource_id | name | resource_type | location | tags | plan | properties | kind | managed_by | sku | identity | state | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
295 | /subscriptions/40dcc8bf-0478-4f3b-b275-ed0a94f2c013/resourceGroups/BlackHatDemo/providers/Micros... | BlackHatDemoHost | Microsoft.Compute/virtualMachines | eastus | {'Role': 'Demo'} | None | None | None | None | None | None | None |
# Get details on our target resource
az.get_resource_details(resource_id=res_id, sub_id=sub_id)
{'resource_id': '/subscriptions/40dcc8bf-0478-4f3b-b275-ed0a94f2c013/resourceGroups/BlackHatDemo/providers/Microsoft.Compute/virtualMachines/BlackHatDemoHost', 'name': 'BlackHatDemoHost', 'resource_type': 'Microsoft.Compute/virtualMachines', 'location': 'eastus', 'tags': {'Role': 'Demo'}, 'plan': None, 'properties': {'vmId': '3f2b6a14-4c02-41aa-a2e8-6859ee4c7847', 'hardwareProfile': {'vmSize': 'Standard_B2s'}, 'storageProfile': {'imageReference': {'publisher': 'MicrosoftWindowsDesktop', 'offer': 'Windows-10', 'sku': 'rs5-pro', 'version': 'latest', 'exactVersion': '17763.1282.2006061952'}, 'osDisk': {'osType': 'Windows', 'name': 'BlackHatDemoHost_OsDisk_1_dd1ef27d863e4e14ab3b446a4ab3ab20', 'createOption': 'FromImage', 'caching': 'ReadWrite', 'managedDisk': {'id': '/subscriptions/40dcc8bf-0478-4f3b-b275-ed0a94f2c013/resourceGroups/BLACKHATDEMO/providers/Microsoft.Compute/disks/BlackHatDemoHost_OsDisk_1_dd1ef27d863e4e14ab3b446a4ab3ab20'}}, 'dataDisks': []}, 'osProfile': {'computerName': 'BlackHatDemoHos', 'adminUsername': 'mstic_admin', 'windowsConfiguration': {'provisionVMAgent': True, 'enableAutomaticUpdates': True, 'patchSettings': {'patchMode': 'AutomaticByOS'}}, 'secrets': [], 'allowExtensionOperations': True, 'requireGuestProvisionSignal': True}, 'networkProfile': {'networkInterfaces': [{'id': '/subscriptions/40dcc8bf-0478-4f3b-b275-ed0a94f2c013/resourceGroups/BlackHatDemo/providers/Microsoft.Network/networkInterfaces/blackhatdemohost86'}]}, 'licenseType': 'Windows_Client', 'provisioningState': 'Succeeded'}, 'kind': None, 'managed_by': None, 'sku': None, 'identity': None, 'state': <azure.mgmt.compute.v2019_12_01.models._models_py3.VirtualMachineInstanceView at 0x1b6bcf7b1c0>}
# Get metrics from the Azure virtual machine.
mets = az.get_metrics(metrics="Percentage CPU,Disk Read Bytes,Disk Write Bytes", resource_id=res_id, sub_id=sub_id, sample_time="hour", start_time=10)
disk_read_data = mets['Disk Read Bytes']
disk_read_data['Type'] = 'Disk Read'
disk_write_data = mets['Disk Write Bytes']
disk_write_data['Type'] = "Disk Write"
disk_data = pd.concat([disk_read_data, disk_write_data])
# Visualize those metrics
nbdisplay.display_timeline_values(data=mets['Percentage CPU'], title="Host CPU Usage", time_column = 'Time', y='Data', height=400, source_columns=['Time', 'Data'], kind='line', range_tool=False)
nbdisplay.display_timeline_values(data=disk_data, title="Host Disk Usage", time_column = 'Time', y='Data', height=400, source_columns=['Time', 'Data'], kind='line', group_by='Type', range_tool=False)
One thing we want to investigate in more detail is any security alerts associated with the host. Security Alerts contain complex, detailed data that is hard to read in a regular Pandas DataFrame. To make it easier, MSTICpy provides an interactive widget to allow you to pick alerts from a list and see the details in an expanded output format.
Note: Previously we were getting data from a query provider we provided the query as a string. Part of the MSTICpy dataprovider functionality is to be able to create and store parameterized queries (in YAML files). MSTICpy comes with a set of pre-built queries for many common scenarios. Below we use one fo these to get a list of alerts related to the host we are investigating.
related_alerts = qry_prov.SecurityAlert.list_related_alerts(start=start, end=end, host_name=host_name)
display(related_alerts)
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 | Type | Computer | src_hostname | src_accountname | src_procname | host_match | acct_match | proc_match | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 52b1ab41-869e-4138-9e40-2a4457f09bf0 | 2020-07-10 19:09:23+00:00 | Suspicious Activity Detected | Suspicious Activity Detected | Medium | Analysis of host data has detected a sequence of one or more processes running on BlackHatDemoHo... | Detection-WarmPathV2 | Microsoft | e3549ae5-3e95-4be7-8ba8-9e1b9d97e926 | 2518078950729219999_e3549ae5-3e95-4be7-8ba8-9e1b9d97e926 | /subscriptions/40dcc8bf-0478-4f3b-b275-ed0a94f2c013/resourceGroups/BlackHatDemo/providers/Micros... | 73a015ec-e2b6-4bf7-b353-ebeafb54254e | VM_SuspiciousActivity | Unknown | 0.0 | False | 2020-07-10 18:28:47+00:00 | 2020-07-10 18:37:39+00:00 | 2020-07-10 19:09:54+00:00 | [\r\n "Review each of the individual line items in this alert to see if you recognize them as l... | {\r\n "Machine Name": "BlackHatDemoHos",\r\n "Command List": "FTP session was established.\nNe... | [\r\n {\r\n "$id": "4",\r\n "HostName": "BlackHatDemoHos",\r\n "AzureID": "/subscripti... | Detection | 40dcc8bf-0478-4f3b-b275-ed0a94f2c013 | asihuntomsworkspacerg | Azure Security Center | https://portal.azure.com/#blade/Microsoft_Azure_Security/AlertBlade/alertId/2518078950729219999_... | SecurityAlert | BlackHatDemoHos | BlackHatDemoHos | True | False | False | ||||
1 | 52b1ab41-869e-4138-9e40-2a4457f09bf0 | 2020-07-10 19:09:23+00:00 | Suspicious Activity Detected | Suspicious Activity Detected | Medium | Analysis of host data has detected a sequence of one or more processes running on BlackHatDemoHo... | Detection-WarmPathV2 | Microsoft | e3549ae5-3e95-4be7-8ba8-9e1b9d97e926 | 95ba8569-5df3-351e-b082-ce9666943e0b | /subscriptions/40dcc8bf-0478-4f3b-b275-ed0a94f2c013/resourceGroups/BlackHatDemo/providers/Micros... | 73a015ec-e2b6-4bf7-b353-ebeafb54254e | VM_SuspiciousActivity | Unknown | 0.0 | False | 2020-07-10 18:28:47+00:00 | 2020-07-10 18:37:39+00:00 | 2020-07-10 19:09:54+00:00 | [\r\n "Review each of the individual line items in this alert to see if you recognize them as l... | {\r\n "Machine Name": "BlackHatDemoHos",\r\n "Command List": "FTP session was established.\nNe... | [\r\n {\r\n "$id": "4",\r\n "HostName": "BlackHatDemoHos",\r\n "AzureID": "/subscripti... | Detection | 40dcc8bf-0478-4f3b-b275-ed0a94f2c013 | asihuntomsworkspacerg | Azure Security Center | https://portal.azure.com/#blade/Microsoft_Azure_Security/AlertBlade/alertId/2518078950729219999_... | SecurityAlert | BlackHatDemoHos | BlackHatDemoHos | True | False | False | ||||
2 | 52b1ab41-869e-4138-9e40-2a4457f09bf0 | 2020-07-10 18:41:18+00:00 | RDP Brute Force | RDP Brute Force | Medium | ASI Scheduled Alerts | Microsoft | 52c2edec-dc25-445e-b81a-b54bf44570a3 | cf949989-cf21-7ae1-5c02-56122b111f43 | 52b1ab41-869e-4138-9e40-2a4457f09bf0_765132a3-cf2f-40cf-b45c-cd6be9b942b7 | Unknown | NaN | False | 2020-07-10 18:27:28+00:00 | 2020-07-10 18:27:39+00:00 | 2020-07-10 18:41:18+00:00 | {\r\n "Query": "let bruteforce_hosts = (\r\nSecurityEvent\r\n| where Computer contains \"blackh... | [\r\n {\r\n "$id": "3",\r\n "Address": "174.127.235.80",\r\n "Type": "ip"\r\n },\r\n ... | Detection | 40dcc8bf-0478-4f3b-b275-ed0a94f2c013 | asihuntomsworkspacerg | Azure Sentinel | Scheduled Alerts | SecurityAlert | BlackHatDemoHos | BlackHatDemoHos | True | False | False |
related_alerts['CompromisedEntity'] = related_alerts['Computer']
def disp_full_alert(alert):
global related_alert
related_alert = SecurityAlert(alert)
return nbdisplay.format_alert(related_alert, show_entities=True)
rel_alert_select = nbwidgets.SelectAlert(alerts=related_alerts, action=disp_full_alert)
rel_alert_select.display()
VBox(children=(Text(value='', description='Filter alerts by title:', style=DescriptionStyle(description_width=…
0 | |
---|---|
TenantId | 52b1ab41-869e-4138-9e40-2a4457f09bf0 |
TimeGenerated | 2020-07-10 19:09:23+00:00 |
AlertDisplayName | Suspicious Activity Detected |
AlertName | Suspicious Activity Detected |
Severity | Medium |
Description | Analysis of host data has detected a sequence of one or more processes running on BlackHatDemoHos that have historically been associated with malicious activity. While individual commands may appear benign the alert is scored based on an aggregation of these commands. This could either be legitimate activity, or an indication of a compromised host. |
ProviderName | Detection-WarmPathV2 |
VendorName | Microsoft |
VendorOriginalId | e3549ae5-3e95-4be7-8ba8-9e1b9d97e926 |
SystemAlertId | 2518078950729219999_e3549ae5-3e95-4be7-8ba8-9e1b9d97e926 |
ResourceId | /subscriptions/40dcc8bf-0478-4f3b-b275-ed0a94f2c013/resourceGroups/BlackHatDemo/providers/Microsoft.Compute/virtualMachines/BlackHatDemoHost |
SourceComputerId | 73a015ec-e2b6-4bf7-b353-ebeafb54254e |
AlertType | VM_SuspiciousActivity |
ConfidenceLevel | Unknown |
ConfidenceScore | 0 |
IsIncident | False |
StartTimeUtc | 2020-07-10 18:28:47+00:00 |
EndTimeUtc | 2020-07-10 18:37:39+00:00 |
ProcessingEndTime | 2020-07-10 19:09:54+00:00 |
RemediationSteps | [\r\n "Review each of the individual line items in this alert to see if you recognize them as legitimate administrative activity."\r\n] |
ExtendedProperties | {'Machine Name': 'BlackHatDemoHos', 'Command List': 'FTP session was established. New user was created. PING command was executed. Administrators group members enumeration. New user was added to the Administrators group. New scheduled task was created.', 'Account List': 'BLACKHATDEMOHOS\timvic', 'compromised host': 'BlackHatDemoHos', 'resourceType': 'Virtual Machine', 'ServiceId': '14fa08c7-c48e-4c18-950c-8148024b4398', 'ReportingSystem': 'Azure'} |
Entities | [{'$id': '4', 'HostName': 'BlackHatDemoHos', 'AzureID': '/subscriptions/40dcc8bf-0478-4f3b-b275-ed0a94f2c013/resourceGroups/BlackHatDemo/providers/Microsoft.Compute/virtualMachines/BlackHatDemoHost', 'OMSAgentID': '73a015ec-e2b6-4bf7-b353-ebeafb54254e', 'Type': 'host'}, {'$id': '5', 'Name': 'timvic', 'NTDomain': 'BLACKHATDEMOHOS', 'Host': {'$ref': '4'}, 'IsDomainJoined': True, 'Type': 'account'}] |
SourceSystem | Detection |
WorkspaceSubscriptionId | 40dcc8bf-0478-4f3b-b275-ed0a94f2c013 |
WorkspaceResourceGroup | asihuntomsworkspacerg |
ExtendedLinks | |
ProductName | Azure Security Center |
ProductComponentName | |
AlertLink | https://portal.azure.com/#blade/Microsoft_Azure_Security/AlertBlade/alertId/2518078950729219999_e3549ae5-3e95-4be7-8ba8-9e1b9d97e926/subscriptionId/40dcc8bf-0478-4f3b-b275-ed0a94f2c013/resourceGroup/BlackHatDemo/referencedFrom/alertDeepLink/location/centralus |
Type | SecurityAlert |
Computer | BlackHatDemoHos |
src_hostname | BlackHatDemoHos |
src_accountname | |
src_procname | |
host_match | True |
acct_match | False |
proc_match | False |
CompromisedEntity | BlackHatDemoHos |
0 | |
---|---|
Machine Name | BlackHatDemoHos |
Command List | FTP session was established.\nNew user was created.\nPING command was executed.\nAdministrators group members enumeration.\nNew user was added to the Administrators group.\nNew scheduled task was created. |
Account List | BLACKHATDEMOHOS\timvic |
compromised host | BlackHatDemoHos |
resourceType | Virtual Machine |
ServiceId | 14fa08c7-c48e-4c18-950c-8148024b4398 |
ReportingSystem | Azure |
Graphs provide a great way to understand the relationship between items. As alerts are often associated with multiple different entities being able to view a graph of alerts and their entities helps analysts identify important connections. MSTICpy provide a feature for graphing and plotting alert information.
alert = SecurityAlert(rel_alert_select.selected_alert)
grph = create_alert_graph(alert)
full_grph = add_related_alerts(related_alerts, grph)
draw_alert_entity_graph(full_grph, width=15)
Logon events are key to understanding any host based activity. We have previously used MSTICpy's timeline feature to display value based data such as our Azure virtual machine metrics, as well as discrete data such as alerts, however we can also use it to display multiple types of discrete data on the same timeline. This is particularly useful for Windows logon events where we plot different logon types (interactive, network, etc.) in different horizontal series.
# Acquire data using a built in query
host_logons = qry_prov.WindowsSecurity.list_host_logons(start=start,end=end, host_name=host_name)
# Display timeline
tooltip_cols = ["TimeGenerated", "Account", "LogonType", 'TimeGenerated']
nbdisplay.display_timeline(data=host_logons, alert=rel_alert_select.selected_alert, title="Host Logons", source_columns = tooltip_cols, group_by = "LogonType", height=200)
When presented with a large number of events such as we have here its useful to cluster these into a more managable number of groups. MSTICpy contains clustering features that can be used against a number of data types. Once clustering is complete we use another widget to let the user select the cluster they want to focus on.
from msticpy.sectools.eventcluster import dbcluster_events, add_process_features, _string_score
logon_features = host_logons.copy()
logon_features["AccountNum"] = host_logons.apply(lambda x: _string_score(x.Account), axis=1)
logon_features["TargetUserNum"] = host_logons.apply(lambda x: _string_score(x.TargetUserName), axis=1)
logon_features["LogonHour"] = host_logons.apply(lambda x: x.TimeGenerated.hour, axis=1)
# run clustering
(clus_logons, _, _) = dbcluster_events(data=logon_features, time_column="TimeGenerated", cluster_columns=["AccountNum", "LogonType", "TargetUserNum"], max_cluster_distance=0.0001)
dist_logons = clus_logons.sort_values("TimeGenerated")[["TargetUserName", "TimeGenerated", "LastEventTime", "LogonType", "ClusterSize"]]
dist_logons = dist_logons.apply(lambda x: (
f"{x.TargetUserName}: "
f"(logontype {x.LogonType}) "
f"timerange: {x.TimeGenerated} - {x.LastEventTime} "
f"count: {x.ClusterSize}"
),
axis=1,
)
dist_logons = {v: k for k, v in dist_logons.to_dict().items()}
def show_logon(idx):
return nbdisplay.format_logon(pd.DataFrame(clus_logons.loc[idx]).T)
logon_wgt = nbwidgets.SelectItem(description="Select logon cluster to examine", item_dict=dist_logons, action=show_logon,height="200px", width="100%", auto_display=True)
VBox(children=(Text(value='', description='Filter:', style=DescriptionStyle(description_width='initial')), Sel…
Account: timvic Account Domain: BlackHatDemoHos Logon Time: 2020-07-10 18:27:49.790000+00:00 Logon type: 10(RemoteInteractive) User Id/SID: S-1-5-21-3334416894-4278249820-3875274378-1006 SID S-1-5-21-3334416894-4278249820-3875274378-1006 is local machine or domain account Subject (source) account: WORKGROUP/BlackHatDemoHos$ Logon process: User32 Authentication: Negotiate Source IpAddress: 174.127.235.80 Source Host: BlackHatDemoHos Logon status: |
# We can reset our timeframe based on the selected cluster.
start = clus_logons.loc[logon_wgt.value]['FirstEventTime']
end = clus_logons.loc[logon_wgt.value]['LastEventTime']
When investigating a host it is valuable to see the processes executed on the host, and the relationship between them. We can use the MSTICpy ProcessTree functionality to build and visualize process trees from both Linux and Windows hosts.
Due to the volume of data potentially involved when looking at process events, it's important to have a focused time frame to look at. We use the MSTICpy widget for selecting a time range.
timescope = nbwidgets.QueryTime(units="hours", origin_time = start, max_before=12, max_after=24, before=0, after=3, auto_display=True)
HTML(value='<h4>Set query time boundaries</h4>')
HBox(children=(DatePicker(value=datetime.date(2020, 7, 10), description='Origin Date'), Text(value='18:27:49.7…
VBox(children=(IntRangeSlider(value=(0, 3), description='Time Range (hour):', layout=Layout(width='80%'), max=…
proc_data = qry_prov.WindowsSecurity.list_host_processes(start=timescope.start,end=timescope.end, host_name=host_name)
p_tree = ptree.build_process_tree(proc_data, show_progress=True)
root_proc_sel = nbwidgets.SelectItem(
description="Select root process to investigate process tree",
item_list=ptree.get_roots(p_tree)['NewProcessName'].to_list(),
height="200px",
width="100%",
auto_display=True)
HBox(children=(IntProgress(value=0, bar_style='info', description='Progress:'), Label(value='0%')))
{'Processes': 1164, 'RootProcesses': 14, 'LeafProcesses': 868, 'BranchProcesses': 282, 'IsolatedProcesses': 0, 'LargestTreeDepth': 6}
VBox(children=(Text(value='', description='Filter:', style=DescriptionStyle(description_width='initial')), Sel…
# Build tree from selected root
proc_tree = ptree.get_descendents(p_tree, ptree.get_roots(p_tree)[ptree.get_roots(p_tree)['NewProcessName']==root_proc_sel.value].iloc[0])
# Visualize the tree
process_tree = nbdisplay.plot_process_tree(data=proc_tree, legend_col="SubjectUserName", show_table=True)
Looking a the processes above we can see some of the command line arguments appear to be Base64 encoded, this is a common technique employed by attackers to hide their activity. MSTICpy includes features to identify and decode Base64 encoded strings to allow for effective analysis.
cmd_lines = p_tree.dropna(subset=['CommandLine']).copy()
#Base 64 decode strings in our commandlines
dec_df = base64.unpack_df(data=cmd_lines, column="CommandLine")
dec_df = dec_df.dropna(subset=['decoded_string'])
dec_df.head()
reference | original_string | file_name | file_type | input_bytes | decoded_string | encoding_type | file_hashes | md5 | sha1 | sha256 | printable_bytes | src_index | full_decoded_string | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
8 | (, 1., 1) | QWxsIHlvdXIgc2VydmVycyBiZWxvbmcgdG8gZmF4aW5nLW1vbi5iZXN0IG5vdy4= | unknown | None | b'All your servers belong to faxing-mon.best now.' | All your servers belong to faxing-mon.best now. | utf-8 | {'md5': 'c0635c256fbbfb3033a08929d1f90b53', 'sha1': '797345abadcbb2383bdb700444e7a3f46d4f5600', ... | c0635c256fbbfb3033a08929d1f90b53 | 797345abadcbb2383bdb700444e7a3f46d4f5600 | 05f5c87e10357fd8d720e348579fcd13f4a41dd680c1674511f06d92216a3039 | 41 6c 6c 20 79 6f 75 72 20 73 65 72 76 65 72 73 20 62 65 6c 6f 6e 67 20 74 6f 20 66 61 78 69 6e ... | c:\windows\system32\cmd.exe0x1a382020-07-10 18:28:47.660000 | cmd /c echo <decoded type='string' name='[None]' index='1' depth='1'>All your servers belong to... |
With the host process tree above we have found some activity that appears malicious. However, we'd like to do some more validation without having to manually examine each process. One simple way to do this is to look for key Indicators of Compromise (IoC) in our data and check them against threat intelligence. We use MSTICpy's IoCExtract
to extract known IoC types. We can then use the same threat intelligence feature used earlier with a single IP address to look up multiple IoCs.
# Extract IoCs from command lines
ioc_ex = IoCExtract()
cmd_iocs = cmd_lines.mp_ioc.extract(columns=['CommandLine'], ioc_types=['ipv4','dns'])
b64_iocs = dec_df.mp_ioc.extract(columns=['decoded_string'], ioc_types=['ipv4','dns'])
iocs = pd.concat([cmd_iocs,b64_iocs])
iocs = iocs.drop_duplicates(subset=['IoCType','Observable']).copy()
iocs.sample(5)
IoCType | Observable | SourceIndex | |
---|---|---|---|
10 | ipv4 | 32.220.60.108 | c:\windows\system32\ping.exe0x20482020-07-10 18:28:47.830000 |
11 | dns | Microsoft.Windows.Photos | c:\program files\windowsapps\microsoft.windows.photos_2020.19111.24110.0_x64__8wekyb3d8bbwe\micr... |
504 | dns | AzureEvents.man | c:\windows\system32\wevtutil.exe0x1f682020-07-10 21:02:11.807000 |
225 | ipv4 | 4.0.0.0 | c:\windows\microsoft.net\framework64\v4.0.30319\ngen.exe0x14d42020-07-10 18:36:30.583000 |
2 | dns | microsoft.com | c:\windows\system32\cmd.exe0x26e42020-07-10 18:28:46.993000 |
Similar to the alert viewer widget used earlier MSTICpy has a viewer for threat intelligence results to make reviewing the output easier.
Note: The full response details from the provider can be see in the collapsible
Raw Results
section
# TI Lookups
ti_resp = ti.lookup_iocs(data=iocs, obs_col="Observable")
select_ti = browse_results(ti_resp, severities=['high','warning'])
select_ti
VBox(children=(Text(value='', description='Filter:', style=DescriptionStyle(description_width='initial')), Sel…
OTX | |
pulse_count | 5 |
names | ['Malware - Malware Domain Feed V2 - June 04 2020', 'Malware - Malware Domain Feed V2 - February 10 2020', 'Cosmic Lynx: The Rise of A Russian BEC Group', 'Cosmic Lynx: The Rise of A Russian BEC Group', 'Cosmic Lynx The Rise of Russian BEC'] |
tags | [[], [], ['BEC', 'Phishing', 'social engineering', 'Russia', 'Email', 'COVID-19'], ['BEC', 'Phishing', 'social engineering', 'Russia', 'Email', 'COVID-19'], ['” “ corporate', '” “ matter', '” “ law', 'march', '” “ potential', '” “ new', '” “ possible', 'january', '“ corporate', '” “ liaise', 'august', 'june', 'april']] |
references | [[], [], ['https://www.agari.com/cyber-intelligence-research/whitepapers/acid-agari-cosmic-lynx.pdf'], ['https://www.agari.com/cyber-intelligence-research/whitepapers/acid-agari-cosmic-lynx.pdf'], ['https://www.agari.com/cyber-intelligence-research/whitepapers/acid-agari-cosmic-lynx.pdf']] |
{'alexa': 'http://www.alexa.com/siteinfo/secure-ssl-sec.com',
'base_indicator': {'access_reason': '',
'access_type': 'public',
'content': '',
'description': '',
'id': 2209997916,
'indicator': 'secure-ssl-sec.com',
'title': '',
'type': 'domain'},
'indicator': 'secure-ssl-sec.com',
'pulse_info': {'count': 5,
'pulses': [{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx.alienvault.com/assets/images/default-avatar.png',
'id': '83138',
'is_following': False,
'is_subscribed': False,
'username': 'otxrobottwo_testing'},
'cloned_from': None,
'comment_count': 0,
'created': '2020-06-04T10:29:56.147000',
'description': 'Command and Control domains for '
'Malware. These domains are '
'extracted from a number of '
'sources, and are suspicious.',
'downvotes_count': 0,
'export_count': 3,
'follower_count': 0,
'groups': [],
'id': '5ed8cd24dea4063ecdd46ff0',
'in_group': False,
'indicator_count': 1436,
'indicator_type_counts': {'domain': 1104,
'hostname': 332},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2020-07-20T02:27:31.705000',
'modified_text': '16 hours ago ',
'name': 'Malware - Malware Domain Feed V2 - June '
'04 2020',
'public': 1,
'pulse_source': 'api',
'references': [],
'related_indicator_is_active': 1,
'related_indicator_type': 'domain',
'subscriber_count': 95,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': False,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx20-web-media.s3.amazonaws.com/media/avatars/user_78495/resized/80/avatar_ba5a8acdbd.png',
'id': '78495',
'is_following': False,
'is_subscribed': False,
'username': 'otxrobottwo'},
'cloned_from': None,
'comment_count': 0,
'created': '2020-02-10T07:12:56.255000',
'description': 'Command and Control domains for '
'Malware. These domains are '
'extracted from a number of '
'sources, and are suspicious.',
'downvotes_count': 0,
'export_count': 3,
'follower_count': 0,
'groups': [],
'id': '5e4102789c1c8aec95a65738',
'in_group': False,
'indicator_count': 1898,
'indicator_type_counts': {'URL': 25,
'domain': 1408,
'hostname': 465},
'industries': [],
'is_author': False,
'is_modified': True,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2020-07-20T01:55:39.683000',
'modified_text': '16 hours ago ',
'name': 'Malware - Malware Domain Feed V2 - '
'February 10 2020',
'public': 1,
'pulse_source': 'api',
'references': [],
'related_indicator_is_active': 1,
'related_indicator_type': 'domain',
'subscriber_count': 265,
'tags': [],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': False,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': 'Cosmic Lynx',
'attack_ids': [],
'author': {'avatar_url': 'https://otx20-web-media.s3.amazonaws.com/media/avatars/user_24260/resized/80/avatar_7b67627076.png',
'id': '24260',
'is_following': False,
'is_subscribed': False,
'username': 'Cyber_Hat'},
'cloned_from': '5f04d03c68918d97811bda03',
'comment_count': 0,
'created': '2020-07-08T09:48:12.031000',
'description': '',
'downvotes_count': 0,
'export_count': 8,
'follower_count': 0,
'groups': [],
'id': '5f05965c766786e334704dd0',
'in_group': False,
'indicator_count': 65,
'indicator_type_counts': {'domain': 65},
'industries': [],
'is_author': False,
'is_modified': False,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2020-07-08T09:48:12.031000',
'modified_text': '12 days ago ',
'name': 'Cosmic Lynx: The Rise of A Russian BEC '
'Group',
'public': 1,
'pulse_source': 'web',
'references': ['https://www.agari.com/cyber-intelligence-research/whitepapers/acid-agari-cosmic-lynx.pdf'],
'related_indicator_is_active': 1,
'related_indicator_type': 'domain',
'subscriber_count': 957,
'tags': ['BEC',
'Phishing',
'social engineering',
'Russia',
'Email',
'COVID-19'],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': False,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': 'Cosmic Lynx',
'attack_ids': [],
'author': {'avatar_url': 'https://otx20-web-media.s3.amazonaws.com/media/avatars/user_2/resized/80/avatar_dacfad0ca8.png',
'id': '2',
'is_following': False,
'is_subscribed': True,
'username': 'AlienVault'},
'cloned_from': None,
'comment_count': 0,
'created': '2020-07-07T19:42:52.567000',
'description': '"We have observed more than 200 '
'BEC campaigns linked to Cosmic '
'Lynx since July 2019, targeting '
'individuals in 46 countries on six '
'continents. Unlike most BEC groups '
'that are relatively target '
'agnostic, Cosmic Lynx has a clear '
'target profile: large, '
'multinational organizations. '
'Nearly all of the organizations '
'Cosmic Lynx has targeted have a '
'significant global presence and '
'many of them are Fortune 500 or '
'Global 2000 companies." -Agari',
'downvotes_count': 0,
'export_count': 66,
'follower_count': 0,
'groups': [],
'id': '5f04d03c68918d97811bda03',
'in_group': False,
'indicator_count': 65,
'indicator_type_counts': {'domain': 65},
'industries': [],
'is_author': False,
'is_modified': False,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2020-07-07T19:42:52.567000',
'modified_text': '12 days ago ',
'name': 'Cosmic Lynx: The Rise of A Russian BEC '
'Group',
'public': 1,
'pulse_source': 'web',
'references': ['https://www.agari.com/cyber-intelligence-research/whitepapers/acid-agari-cosmic-lynx.pdf'],
'related_indicator_is_active': 1,
'related_indicator_type': 'domain',
'subscriber_count': 114944,
'tags': ['BEC',
'Phishing',
'social engineering',
'Russia',
'Email',
'COVID-19'],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': False,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0},
{'TLP': 'white',
'adversary': '',
'attack_ids': [],
'author': {'avatar_url': 'https://otx20-web-media.s3.amazonaws.com/media/avatars/user_94093/resized/80/avatar_281f69b768.png',
'id': '94093',
'is_following': False,
'is_subscribed': False,
'username': 'Sand-Storm'},
'cloned_from': None,
'comment_count': 0,
'created': '2020-07-07T14:53:12.330000',
'description': '',
'downvotes_count': 0,
'export_count': 10,
'follower_count': 0,
'groups': [],
'id': '5f048c58d60cfdb1a2e82d2e',
'in_group': False,
'indicator_count': 126,
'indicator_type_counts': {'IPv4': 61, 'domain': 65},
'industries': [],
'is_author': False,
'is_modified': False,
'is_subscribing': None,
'locked': False,
'malware_families': [],
'modified': '2020-07-07T14:53:12.330000',
'modified_text': '13 days ago ',
'name': 'Cosmic Lynx The Rise of Russian BEC',
'public': 1,
'pulse_source': 'web',
'references': ['https://www.agari.com/cyber-intelligence-research/whitepapers/acid-agari-cosmic-lynx.pdf'],
'related_indicator_is_active': 1,
'related_indicator_type': 'domain',
'subscriber_count': 139,
'tags': ['” “ corporate',
'” “ matter',
'” “ law',
'march',
'” “ potential',
'” “ new',
'” “ possible',
'january',
'“ corporate',
'” “ liaise',
'august',
'june',
'april'],
'targeted_countries': [],
'threat_hunter_has_agents': 1,
'threat_hunter_scannable': True,
'upvotes_count': 0,
'validator_count': 0,
'vote': 0,
'votes_count': 0}],
'references': ['https://www.agari.com/cyber-intelligence-research/whitepapers/acid-agari-cosmic-lynx.pdf']},
'sections': ['general',
'geo',
'url_list',
'passive_dns',
'malware',
'whois',
'http_scans'],
'type': 'domain',
'whois': 'http://whois.domaintools.com/secure-ssl-sec.com'}
We have appear to have identified a malicious domain to go with the IP address identified earlier in our investigation. In order to complete our investigation we want to get some context on this domain in the same way we did with the IP address. Again MSTICpy has a number of tools to help with this, including features to validate a domain and screenshot a URL.
Note: these tools use publicly-available services such as abuse.ch and Browshot
dom = select_ti.value[0]
dom_val = domain_utils.DomainValidator()
md(f"Is {dom} a valid domain? <b>{dom_val.validate_tld(dom)}</b>")
md(f"Is {dom} resolvable? <b>{dom_val.is_resolvable(dom)}</b>")
md(f"Is the TLS cert used by {dom} in abuse.ch's abuse list? <b>{dom_val.in_abuse_list(dom)[0]}</b>")
Is secure-ssl-sec.com a valid domain? True
Is secure-ssl-sec.com resolvable? True
Is the TLS cert used by secure-ssl-sec.com in abuse.ch's abuse list? False
image_data = domain_utils.screenshot("secure-ssl-sec.com")
with open('screenshot.png', 'wb') as f:
f.write(image_data.content)
display(Image(filename='screenshot.png'))
Getting screenshot
IntProgress(value=0, max=40)