# Kernel: Python 3.8 - AzureML
# If you are not sure, you may check what kernels you already have on the compute instance:
!jupyter kernelspec list
# Kernel: Python 3.8 - AzureML
# For Ubuntu 18.04
!wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
!sudo dpkg -i packages-microsoft-prod.deb
# Kernel: Python 3.8 - AzureML
# Install .NET SDK 5.0
!sudo apt-get update; \
sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-5.0
# Kernel: Python 3.8 - AzureML
# Install the dotnet interactive global tool
!dotnet tool install --global Microsoft.dotnet-interactive
# Kernel: Python 3.8 - AzureML
# Create�symlink�between the dotnet interactive and thelocal bin directory
!sudo ln -s /home/azureuser/.dotnet/tools/dotnet-interactive /usr/local/bin/dotnet-interactive
# Kernel: Python 3.8 - AzureML
# Finally install the .NET kernel: C#, F#, and PowerShell
!dotnet interactive jupyter install
##Installs modules necessary to run notebook
Install-Module Az.Compute,Az.Resources,Az.OperationalInsights,powershell-yaml -Force
##Get more details on your PowerShell environment
$PSVersionTable
##Output Markdown
Out-Display "**THIS IS BOLD** _ITALICS_" -MimeType text/markdown
##Set foreground color
$host.UI.RawUI.ForegroundColor = [System.ConsoleColor]::Blue
##Default output colors can be changed
$Host.PrivateData.WarningBackgroundColor = "White"
$Host.PrivateData.WarningForegroundColor = "Black"
##Change output color inline
Write-Host "Lets get started ..." -ForegroundColor Blue -BackgroundColor White
##You can write-host with the -nonewlinke flag
Write-Host "Hello " -NoNewline -ForegroundColor Red
Write-Host "World!" -ForegroundColor Blue
##Output HTML
#!html
Hello in HTML!
#!markdown
Write a **list** ...
* first item
* second item
...or a _table_...
|CallerIP |PrincipalString |
|---------|--------|
|10.1.1.1 |joe@contoso.com |
|10.1.1.2 |joanne@contoso.com |
#!csharp
var x="Hello using C#!";
Console.WriteLine(x);
#!pwsh
$x = "Hello using PowerShell!"
Write-Host $x
##Download IOCs from the internet and use them in your investigation/hunts
$ips = (Invoke-WebRequest 'https://raw.githubusercontent.com/parthdmaniar/coronavirus-covid-19-SARS-CoV-2-IoCs/master/IPs').content
$ips
##You can ask for user input
Write-Host "Don't forget that execution of cells will block on prompts until you submit!"
$name = Read-Host -Prompt "What is the server name you would like to investigate? "
$name
##You can use a progress bar
For ($i=0; $i -le 100; $i++) {
Write-Progress -Id 1 -Activity "Parent work progress" -Status "Current Count: $i" -PercentComplete $i -CurrentOperation "Counting ..."
For ($j=0; $j -le 10; $j++) {
Start-Sleep -Milliseconds 5
Write-Progress -Parent 1 -Id 2 -Activity "Child work progress" -Status "Current Count: $j" -PercentComplete ($j*10) -CurrentOperation "Working ..."
}
if ($i -eq 50) {
Write-Host "Doing the work around here..." -Foreground DarkBlue
"Output goes here..."
}
}
##If you have long running task, that prints output to the screen
##use the -Parallel flag to run them in parallel, vastly improving performance.
##Example below runs one loop sequentially while the second example runs them in parallel
Write-Host "Number of seconds running commands sequentially: " -nonewline
(Measure-Command {
1..5 | ForEach-Object -Process {write-output "This is number $_"; sleep 2}
}).Seconds
Write-Host "Number of seconds running commands in parallel: " -nonewline
(Measure-Command {
1..5 | ForEach-Object -Parallel {write-output "This is number $_"; sleep 2}
}).Seconds
##Get your configuration file settings
$nbcontentpath = "config.json"
if(!(test-path $nbcontentpath)){
write-host "INFO: Your configuration path ($nbcontentpath) could not be located."
write-host "INFO: Attempting to build the file path explicitly. If this continues to be a problem, run 'dir' within the cell to find the current working directory and update the `$nbcontentpath variable accordingly."
$username = read-host "Enter the user name used for the notebook file explorer (the name of the top level folder):"
$nbcontentpath = "users\$username\config.json"
}
##Path fix in case you picked up the cookie cutter configuration file (if you cloned repo from GitHub in terminal)
if(test-path $nbcontentpath){
$content = gc $nbcontentpath | ?{$_ -match "cookiecutter"}
if($content.Length -gt 0) {
$nbcontentpath = "..\" + $nbcontentpath
}
}
try {
$nbconfigcontent = Get-Content $nbcontentpath -ErrorAction Stop
}
catch {
write-host "ERROR: Your configuration path ($nbcontentpath) could not be located. Please fix before continuing further."
}
##Set variables you will use throughout the notebook
$tenantId = ($nbconfigcontent | ConvertFrom-Json).tenant_id
$subscriptionId = ($nbconfigcontent | ConvertFrom-Json).subscription_id
$resourceGroup = ($nbconfigcontent | ConvertFrom-Json).resource_group
$workspaceName = ($nbconfigcontent | ConvertFrom-Json).workspace_name
$workspaceId = ($nbconfigcontent | ConvertFrom-Json).workspace_id
Write-Host "SubscriptionId: " $subscriptionId
Write-Host "TenantId: " $tenantId
Write-Host "WorkspaceId: " $workspaceId
Write-Host "workspaceName: " $workspaceName
#Change the default colors used for PowerShell warnings as they make the Connect-AzAccount output difficult to see
$Host.PrivateData.WarningBackgroundColor = "White"
$Host.PrivateData.WarningForegroundColor = "Black"
##Connect to selected subscription
Connect-AzAccount -UseDeviceAuthentication
Select-AzSubscription -SubscriptionId $subscriptionId -TenantId $tenantId
##Configure the Log Analytics workspace
$workspace = $null
$workspaces = Get-AzOperationalInsightsWorkspace -ResourceGroupName $resourceGroup
if($workspaces.Length -gt 1) {
Write-Host "INFO: Multiple workspaces detected."
foreach($wksp in $workspaces){
if($wksp.Name -eq $workspaceName) {
$workspace = $wksp
}
}
}
else {
$workspace = $workspaces
}
Write-Host "INFO: Ensure that the workspace -- {"$workspace.Name"} is the intended target workspace before continuing to the next cell."
$workspace
##Query your workspace using the savedsearches API
$savedSearchQueries = (Get-AzOperationalInsightsSavedSearch -ResourceGroupName $resourceGroup -WorkspaceName $workspaceName).value
$huntingQueries = $savedSearchQueries | %{$_.properties } | ? {$_.Category -match "Hunting Queries"}
Write-Host "Displaying the first 5 hunting queries..."
0..4 | foreach {Write-Host "Hunting Query Name: " -nonewline;$huntingQueries[$_].DisplayName}
##Build resource id
$worksapceId = "subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.OperationalInsights/workspaces/${workspaceName}"
$incidentsResource = $worksapceId + "/" + "providers/Microsoft.SecurityInsights/incidents"
##Get incidents
$incidents = Get-AzResource -ResourceId $incidentsResource -ApiVersion "2019-01-01-preview"
##Only display a few incidents as the notebook has to translate the PowerShell output into the Jupyter UI
0..4 | foreach {Write-Host "Incident Number {$_}: " -nonewline;$incidents[$_].Properties}
##Retrieve all of your incident counts and day they were created
$incidentsforgraph = $incidents | % {$_.properties} | select Title, Description, Severity, Status, Owner, createdTimeUtc, relatedAnalyticRuleIds, incidentUrl | ? {(get-date $_.createdTimeUtc) -gt (get-date).AddDays(-31d)}
##Add formated property for the date they were created to make it easier to create graphs
foreach($incident in $incidentsforgraph)
{
$incident | Add-Member -MemberType NoteProperty -Name NewDate -Value (get-date $incident.createdTimeUtc -format "yyyy-MM-dd") -Force
}
##Retrieve all of your incident counts and day they were created for the last 30 days
$openIncidents = $incidentsforgraph | ? {$_.Status -ne "Closed"}
$closedIncidents = $incidentsforgraph | ? {$_.Status -eq "Closed"}
Write-Host "Total incidents (last 30 days) : " $incidentsforgraph.count
Write-Host "Total Open Incidents (last 30 days) : " $openIncidents.count
Write-Host "Total Closed Incidents (last 30 days) : " $closedIncidents.count
##At least one of each incident type (open or closed) must exists to run this cell
if(($closedIncidents -eq $null) -or ($openIncidents -eq $null)){
Write-Host "You need at least one instance of each incident type (open or closed) to render the chart"
}
else {
##Create open incident plots
$openSeries = [Graph.Scatter]::new()
$openSeries.name = "Open Incidents"
$openSeries.x = @(($openIncidents | group-object -Property NewDate).Name | % {$_.ToString()})
$openSeries.y = (($openIncidents | group-object -Property NewDate | Select Count | %{$_.Count}))
##Create closed incident plots
$closeSeries = [Graph.Scatter]::new()
$closeSeries.name = "Closed Incidents"
$closeSeries.x = @(($closedIncidents | group-object -Property NewDate).Name | % {$_.ToString()})
$closeSeries.y = (($closedIncidents | group-object -Property NewDate | Select Count | %{$_.Count}))
##Display chart
$chart = @($openSeries, $closeSeries) | New-PlotlyChart -Title "Open vs Closed Incidents"
Out-Display $chart
}
##Add a timeframe variable
$timeframe = $null
do {
$timeframe = Read-Host "How many days back would you like to query the data? (you must enter an integer for number of days):"
$timeframe = $timeframe -as [int]
if ($timeframe -eq $null) { write-host "You must enter a numeric value" }
}
until ($timeframe -ne $null)
write-host "You entered: $timeframe days as the input timespan."
##Query Heartbeat table
$query = "Heartbeat | where TimeGenerated >= ago($timeframe" + "d" + ") | take 10"
Write-Host "Query to run: " $query
##Run query and add results to object. Now you can use object to display data or graph
$queryResults = Invoke-AzOperationalInsightsQuery -Workspace $Workspace -query $query
#0..1 | foreach {Write-Host "Result Number {$_}: ";$queryResults.results[$_] }
$queryResults.Results | select TimeGenerated, ComputerIP, Computer, `
OSType, RemoteIPLongitude, RemoteIPLatitude, RemoteIPCountry, `
Resource, ResourceType, ComputerEnvironment | Format-Table
##Using the example from an earlier cell, collect a list of IOCs and join them with IPs from a query
##Download IOCs from the internet and use them in your investigation/hunts
$ips = ((Invoke-WebRequest 'https://raw.githubusercontent.com/parthdmaniar/coronavirus-covid-19-SARS-CoV-2-IoCs/master/IPs').content).ToString() -Split "`n"
##Query the Log Analytics table
$query = @"
Heartbeat
| where TimeGenerated >= ago(1d)
| summarize RecordCount=count() by ComputerIPs=ComputerIP
"@
##Run query and add results to object. Now you can use object to display data or graph
$queryResults = Invoke-AzOperationalInsightsQuery -Workspace $Workspace -query $query
$computerips=($queryResults.Results | Group-Object ComputerIPs).Group.ComputerIPs
##Compare IOC IPs to IPs from your logs
Write-Host "Example of comparing IPs to IOCs..."
foreach($computerip in ($computerips | select -first 10 )) {
for($i=0;$i -lt $ips.Length;$i++) {
write-host "IOC-IP:" $ips[$i] "does not match IP:" $ips[$i] "from logs!"
if($i -gt 3) {break}
}
}
##Get your configuration file settings
$configFileSuccess=$false
$yamlcontentpath = "msticpyconfig.yaml"
$yaml = $null
if(!(test-path $yamlcontentpath)) {
write-host "INFO: Your configuration path ($yamlcontentpath) could not be located."
write-host "INFO: Attempting to build the file path explicitly. If this continues to be a problem, run 'dir' within the cell to find the current working directory and update the `$nbcontentpath variable accordingly."
$username = read-host "Enter the user name used for the notebook file explorer:"
$yamlcontentpath = "users\$username\msticpyconfig.yaml"
}
##Path fix in case you picked up the cookie cutter configuration file (if you cloned repo from GitHub in terminal)
if(test-path $yamlcontentpath){
$content = gc $yamlcontentpath #| ? {$_ -match "your-workspace-id"}
if($content.Length -gt 0) {
$yamlcontentpath = "..\" + $yamlcontentpath
}
}
##Set Yaml content
try {
$configFileSuccess=$true
$yamlcontent = Get-Content $yamlcontentpath -ErrorAction Stop -Raw
$yaml = ConvertFrom-Yaml $yamlcontent
}
catch {
$configFileSuccess=$false
write-host "ERROR: Your configuration path ($yamlcontentpath) could not be located. Please fix or hardcode the key before continuing further."
}
##Harcode your key here if you haven't configured the yaml configuration file
##$APIKey = "<>"
##Set your API key and you are good to go
$APIKey = $yaml.TIProviders.VirusTotal.Args.AuthKey
if($APIKey -eq $null){
$configFileSuccess = $false
}
else {
$configFileSuccess = $true
}
if($configFileSuccess) {
write-host "INFO: Your VT key was correctly configured. "
$yaml.TIProviders.VirusTotal
}
else {
write-host "ERROR: Your VT key was not correctly configured. Please fix before continuing further."
}
##Input VT URL and Key
##Ideally, it would be better to retrieve the key from msticpyconfig.yaml
##$APIKey = ''
$Resource = Read-Host "Enter the URL you would like to submit (example: support.btcsupports.com):"
##Test URL
##$Resource = 'http://support.btcsupports.com/'
##Setup VT URI
$URI = 'https://www.virustotal.com/vtapi/v2/url/report'
$QueryResources = $Resource -join ','
$OldEAP = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
$Body = @{'resource'= $QueryResources; 'apikey'= $APIKey; 'scan'=$scanurl}
# Start building parameters for REST Method invokation.
$Params = @{}
$Params.add('Body', $Body)
$Params.add('Method', 'Get')
$Params.add('Uri',$URI)
$Params.Add('ErrorVariable', 'RESTError')
$ReportResult = Invoke-RestMethod @Params
$ErrorActionPreference = $OldEAP
if ($RESTError)
{
if ($RESTError.Message.Contains('403'))
{
throw 'API key is not valid.'
}
elseif ($RESTError.Message -like '*204*')
{
throw 'API key rate has been reached.'
}
else
{
throw $RESTError
}
}
foreach ($URLReport in $ReportResult)
{
$URLReport.pstypenames.insert(0,'VirusTotal.URL.Report')
Write-host "Resource:" $URLReport.resource
Write-host "Last Scan:" $URLReport.scan_date
Write-host "VT Link:" $URLReport.permalink
Write-host "Positive Scans:" $URLReport.positives
Write-host "Total Scans:" $URLReport.total
$URLReport
}
##Retrieve watchlist results
$queryResults = $null
$watchlistalias = read-host "Enter your watchlist alias to get the results:"
$query = "_GetWatchlist('$watchlistalias')"
$queryResults = Invoke-AzOperationalInsightsQuery -Workspace $Workspace -query $query
$queryResults.Results | Select Watchlistitem | select -first 100