Invoke-Monkey365.ps1
# Monkey365 - the PowerShell Cloud Security Tool for Azure and Microsoft 365 (copyright 2022) by Juan Garrido # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. Function Invoke-Monkey365{ <# .SYNOPSIS Monkey365 is a multi-threaded collector-based tool to help assess the security of both Azure Cloud and Microsoft 365 environment configurations. This module will not change any asset deployed in cloud. Only GET and POST HTTP requests are made to the API endpoints. .DESCRIPTION The main features included in this version are: Return a number of attributes on computers, users, configurations from Microsoft Entra ID Review of Microsoft Entra ID configuration Search for High level accounts in both Azure and Microsoft 365, including Microsoft Entra ID, classic administrators and Directory Roles (RBAC) Multi-Threading support Collector Support The following Azure services are supported by Monkey365: Azure SQL Databases Azure MySQL Databases Azure PostgreSQL Databases Microsoft Entra ID Storage Accounts Classic Virtual Machines Virtual Machines V2 Security Status Security Policies Role Assignments (RBAC) Security Patches Security Baseline Microsoft Defender for Cloud Network Security Groups Classic Endpoints Azure Security Alerts Azure Web Application Firewall Azure Application services The following Microsoft 365 applications are supported by Monkey365: Exchange Online Microsoft Teams SharePoint Online OneDrive for Business With Monkey365, there is also support for exporting data driven to popular formats like CSV, CLIXML or JSON. .NOTES Author : Juan Garrido Twitter : @tr1ana File Name : Invoke-Monkey365.ps1 Version : 0.8.5-beta .LINK https://github.com/silverhack/monkey365 .EXAMPLE $assets = Invoke-Monkey365 -ExportTo CSV -PromptBehavior SelectAccount -IncludeEntraID -Instance Microsoft365 -Collect SharePointOnline This example will collect information of both Azure AD and SharePoint Online and will save results into a CSV file. If credentials are not supplied, Monkey365 will prompt for credentials. .EXAMPLE Invoke-Monkey365 -PromptBehavior SelectAccount -Instance Azure -Collect All -subscriptions 00000000-0000-0000-0000-000000000000 -TenantID 00000000-0000-0000-0000-000000000000 -ExportTo CLIXML This example will collect information of an Azure subscription and will export data to a XML-based file. If credentials are not supplied, Monkey365 will prompt for credentials. .EXAMPLE Invoke-Monkey365 -ClientId 00000000-0000-0000-0000-000000000000 -ClientSecret ("MySuperClientSecret" | ConvertTo-SecureString -AsPlainText -Force) -Instance Azure -Collect All -subscriptions 00000000-0000-0000-0000-000000000000 -TenantID 00000000-0000-0000-0000-000000000000 -ExportTo CLIXML,CSV,JSON,HTML This example retrieves information of an Azure subscription and will export data driven to CSV, JSON, HTML, XML and Excel format into monkey-reports folder. The script will connect to Azure using the client credential flow. .EXAMPLE Invoke-Monkey365 -certificate C:\monkey365\testapp.pfx -ClientId 00000000-0000-0000-0000-000000000000 -CertFilePassword ("MySuperCertSecret" | ConvertTo-SecureString -AsPlainText -Force) -Instance Microsoft365 -Collect SharePointOnline -TenantID 00000000-0000-0000-0000-000000000000 -ExportTo CLIXML,CSV,JSON,HTML This example retrieves information of an Microsoft 365 subscription and will export data driven to CSV, JSON, HTML, XML and Excel format into monkey-reports folder. The script will connect to Azure using the certificate credential flow. .EXAMPLE Invoke-Monkey365 -PromptBehavior SelectAccount -Instance Azure -Collect All -TenantID 00000000-0000-0000-0000-000000000000 -ExportTo HTML This example retrieves information of an Azure subscription and will export data driven to HTML format into monkey-reports folder. If credentials are not supplied, Monkey365 will prompt for credentials. .PARAMETER Environment Select an Environment of Azure services. Valid options are AzureCloud, Preproduction, China, AzureUSGovernment. Default value is AzureCloud .PARAMETER Instance Select the instance to scan. Valid options are Azure or Microsoft365 .PARAMETER Collect Collect data from specified assets. Depending of what instance was selected, the following values are accepted: Value Description ActiveDirectory Retrieves information from Azure Active Directory, including users, groups, contacts, policies, reports, administrative users, etc.. SharePointOnline Retrieves information from SharePoint Online, including lists, users, groups, orphaned users, etc.. ExchangeOnline Retrieves information from Exchange Online Databases Retrieves information from Azure SQL, including databases, Transparent Data Encryption or Threat Detection Policy VirtualMachines Retrieves information from virtual machines deployed on both classic mode and resource manager. SecurityAlerts Get Security Alerts from Microsoft Azure. SecurityCenter Get information about Microsoft Defender for Cloud RoleAssignments Retrieves information about RBAC Users and Groups StorageAccounts Retrieves information about storage accounts deployed on Classic mode and resource manager All Extract all information about an Azure subscription .PARAMETER ExportTo Export data driven to specific formats. Accepted values are CSV, JSON, XML, HTML. .PARAMETER ExcludedResources Exclude unwanted azure resources from being scanned .PARAMETER ExcludeCollector Exclude collectors from being executed .PARAMETER Threads Change the threads settings. By default, a large number of requests will be made with two threads .PARAMETER ForceAuth Force Monkey365 to Authenticate. Only valid for legacy user & password authentication .PARAMETER UserCredentials pscredential of the user requesting the token .PARAMETER ClearCache Clear Token Cache. Only valid if module is using ADAL library .PARAMETER WriteLog Write events to a log file .PARAMETER TenantID Force to authenticate to specific tenant .PARAMETER AuditorName Auditor Name. Used in Excel File .PARAMETER ClientId Service Principal Application ID. Used in Certificate and Client credentials authentication flow .PARAMETER ClientSecret Secret password. Used in Client credentials authentication flow .PARAMETER Certificate PFX certificate file. Used in Certificate authentication flow .PARAMETER CertFilePassword PFX certificate password. Used in Certificate authentication flow .PARAMETER DeviceCode Authenticate by using device code authentication flow #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Scope="Function")] [CmdletBinding(DefaultParameterSetName = 'Implicit', HelpUri='https://silverhack.github.io/monkey365/')] Param ( # pscredential of the user requesting the token [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-InputObject')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-PublicApplication')] [Alias('user_credentials')] [System.Management.Automation.PSCredential]$UserCredentials, [parameter(Mandatory= $false, ParameterSetName = 'Implicit', HelpMessage= "User for access to the O365 services")] [String]$UserPrincipalName, # Tenant identifier of the authority to issue token. [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret-App')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret-InputObject')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientAssertionCertificate')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientAssertionCertificate-File')] [string] $TenantId, [parameter(Mandatory= $false, HelpMessage= "Select an instance of Azure services")] [ValidateSet("AzurePublic","AzureGermany","AzureChina","AzureUSGovernment")] [String]$Environment= "AzurePublic", [Parameter(Mandatory=$false)] [ValidateSet('Azure','Microsoft365')] [String]$Instance, [Parameter(Mandatory=$false, HelpMessage="Include Azure AD")] [Switch]$IncludeEntraID, [Parameter(HelpMessage="Save entire project")] [Switch]$SaveProject, [Parameter(HelpMessage="Import Monkey 365 Job")] [Switch]$ImportJob, [Parameter(Mandatory=$false, HelpMessage="Collectors to exclude")] [string[]]$ExcludeCollector, [parameter(Mandatory= $false, HelpMessage= "Export data to multiple formats")] [ValidateSet("CSV","JSON","CLIXML","HTML")] [Array]$ExportTo=@(), [Parameter(HelpMessage="Compress Monkey365 output to a ZIP file")] [Switch]$Compress, [Parameter(Mandatory= $false, HelpMessage = 'Please specify folder to export results')] [System.IO.DirectoryInfo]$OutDir, [Parameter(Mandatory=$false, HelpMessage="Change the threads settings. Default is 2")] [int32]$Threads = 2, [Parameter(Mandatory=$false, HelpMessage="Clear token cache")] [Switch]$ClearCache, [Parameter(Mandatory= $false, HelpMessage="Auditor Name. Used in Excel File")] [String] $AuditorName = $env:username, [Parameter(Mandatory=$false, HelpMessage="Write Log file")] [Switch]$WriteLog, # Identifier of the client requesting the token. [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret-App')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientAssertionCertificate')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientAssertionCertificate-File')] [string]$ClientId, # Secure secret of the client requesting the token. [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret-App')] [securestring]$ClientSecret, # Secure secret of the client requesting the token. [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret-InputObject', HelpMessage = 'PsCredential')] [Alias('client_credentials')] [System.Management.Automation.PSCredential]$ClientCredentials, # Client assertion certificate of the client requesting the token. [Parameter(Mandatory = $true, ParameterSetName = 'ClientAssertionCertificate')] [System.Security.Cryptography.X509Certificates.X509Certificate2] $ClientAssertionCertificate, # ClientAssertionCertificate of the application requesting the token [Parameter(Mandatory = $false,ParameterSetName = 'ClientAssertionCertificate-File', HelpMessage = 'Certificate')] [parameter(Mandatory= $false, HelpMessage= "pfx certificate file")] [ValidateScript( { if( -Not ($_ | Test-Path) ){ throw ("The cert file does not exist in {0}" -f (Split-Path -Path $_)) } if(-Not ($_ | Test-Path -PathType Leaf) ){ throw "The argument must be a PFX file. Folder paths are not allowed." } if($_ -notmatch "(\.pfx)"){ throw "The certificate specified argument must be of type pfx" } return $true })] [System.IO.FileInfo]$Certificate, # Secure password of the certificate [Parameter(Mandatory = $false,ParameterSetName = 'ClientAssertionCertificate-File', HelpMessage = 'Certificate password')] [Security.SecureString]$CertFilePassword, [parameter(Mandatory= $false, HelpMessage= "json file with all rules")] [ValidateScript({ if( -Not (Test-Path -Path $_) ){ throw ("The ruleset does not exist in {0}" -f (Split-Path -Path $_)) } if(-Not (Test-Path -Path $_ -PathType Leaf) ){ throw "The ruleSet argument must be a json file. Folder paths are not allowed." } if($_ -notmatch "(\.json)"){ throw "The file specified in the ruleset argument must be of type json" } return $true })] [System.IO.FileInfo]$RuleSet, [parameter(Mandatory= $false, HelpMessage= "Directory with all rules")] [ValidateScript({ if( -Not (Test-Path -Path $_) ){ throw ("The directory does not exist in {0}" -f (Split-Path -Path $_)) } if(-Not (Test-Path -Path $_ -PathType Container) ){ throw "The RulesPath argument must be a directory. Files are not allowed." } return $true })] [System.IO.DirectoryInfo]$RulesPath, # location where the authorization server will sends the user once is authenticated. [Parameter(Mandatory = $false)] [uri]$RedirectUri, # Indicates whether AcquireToken should automatically prompt only if necessary or whether it should prompt regardless of whether there is a cached token. [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [ValidateSet("SelectAccount", "NoPrompt", "Never", "ForceLogin")] [String]$PromptBehavior, [Parameter(Mandatory=$false, ParameterSetName = 'Implicit', HelpMessage="Force Authentication Context. Only valid for user&password auth method")] [Switch]$ForceAuth, [Parameter(Mandatory=$false, HelpMessage="Force silent authentication")] [Switch]$Silent, [Parameter(Mandatory=$false, ParameterSetName = 'Implicit', HelpMessage="Device code authentication")] [Switch]$DeviceCode, [Parameter(Mandatory=$false, HelpMessage="Force to load MSAL Desktop PowerShell Core on Windows")] [Switch]$ForceMSALDesktop, [Parameter(Mandatory=$false, HelpMessage="List available collectors")] [Switch]$ListCollector ) dynamicparam{ # Set available instance class $instance_class = @{ Azure = $Script:azure_plugins Microsoft365 = $Script:m365_plugins } # set a new dynamic parameter $paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary # check to see whether the user already chose an instance if(Get-Variable -Name Instance -ErrorAction Ignore){ $attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] # define a new parameter attribute $analysis_attr_name = New-Object System.Management.Automation.ParameterAttribute $analysis_attr_name.Mandatory = $false $attributeCollection.Add($analysis_attr_name) # set the ValidateSet attribute $token_attr_name = New-Object System.Management.Automation.ValidateSetAttribute($instance_class.Item($Instance)) $attributeCollection.Add($token_attr_name) # create the dynamic -Collect parameter $analysis_pname = 'Collect' $analysis_type_dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($analysis_pname, [Array], $attributeCollection) $paramDictionary.Add($analysis_pname, $analysis_type_dynParam) } #Add parameters for Azure instance if($null -ne (Get-Variable -Name Instance -ErrorAction Ignore) -and $Instance -eq 'Azure'){ #Create the -AllSubscriptions switch parameter $attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] # define a new parameter attribute $all_sbs_attr_name = New-Object System.Management.Automation.ParameterAttribute $all_sbs_attr_name.Mandatory = $false $attributeCollection.Add($all_sbs_attr_name) #Create alias for -AllSubscriptions switch param $allsbs_alias = New-Object System.Management.Automation.AliasAttribute -ArgumentList 'all_subscriptions' $attributeCollection.Add($allsbs_alias) $sbs_pname = 'AllSubscriptions' $analysis_type_dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($sbs_pname, [switch], $attributeCollection) $paramDictionary.Add($sbs_pname, $analysis_type_dynParam) #Create the -Subscriptions string parameter $attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] # define a new parameter attribute $sbs_attr_name = New-Object System.Management.Automation.ParameterAttribute $sbs_attr_name.Mandatory = $false $attributeCollection.Add($sbs_attr_name) $sbs_pname = 'Subscriptions' $analysis_type_dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($sbs_pname, [string], $attributeCollection) $paramDictionary.Add($sbs_pname, $analysis_type_dynParam) #Create the -ResourceGroups string parameter $attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] # define a new parameter attribute $rg_attr_name = New-Object System.Management.Automation.ParameterAttribute $rg_attr_name.Mandatory = $false $attributeCollection.Add($rg_attr_name) $rg_pname = 'ResourceGroups' $rg_type_dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($rg_pname, [String[]], $attributeCollection) $paramDictionary.Add($rg_pname, $rg_type_dynParam) #Create the -ExcludeResources File parameter $attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] # define a new parameter attribute $exclude_rsrc_attr_name = New-Object System.Management.Automation.ParameterAttribute $exclude_rsrc_attr_name.Mandatory = $false $attributeCollection.Add($exclude_rsrc_attr_name) $rsrc_pname = 'ExcludedResources' $rsrc_type_dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($rsrc_pname, [System.IO.FileInfo], $attributeCollection) $paramDictionary.Add($rsrc_pname, $rsrc_type_dynParam) } #Add parameters for Microsoft365 instance if($null -ne (Get-Variable -Name Instance -ErrorAction Ignore) -and $Instance -eq 'Microsoft365'){ #Create the -ScanSites string parameter $attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] # define a new parameter attribute $rg_attr_name = New-Object System.Management.Automation.ParameterAttribute $rg_attr_name.Mandatory = $false $attributeCollection.Add($rg_attr_name) $rg_pname = 'ScanSites' $rg_type_dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($rg_pname, [string[]], $attributeCollection) $paramDictionary.Add($rg_pname, $rg_type_dynParam) } # return the collection of dynamic parameters $paramDictionary } Begin{ #Set Window name $Host.UI.RawUI.WindowTitle = "Monkey365 Cloud Security Scanner" #Start Time $starttimer = Get-Date #####Get Default parameters ######## $MyParams = $PSBoundParameters #Create O365 object New-O365Object #Set timer $O365Object.startDate = $starttimer Update-PsObject #Initialize Logger Initialize-MonkeyLogger #Check if import job If($PSBoundParameters.ContainsKey('ImportJob') -and $PSBoundParameters.ImportJob){ Import-MonkeyJob return } #Check if list collectors If($PSBoundParameters.ContainsKey('ListCollector') -and $PSBoundParameters['ListCollector'].IsPresent){ #Get command Metadata $MetaData = New-Object -TypeName "System.Management.Automation.CommandMetaData" (Get-Command -Name "Get-MonkeyCollector") $newPsboundParams = [ordered]@{} if($null -ne $MetaData){ $param = $MetaData.Parameters.Keys foreach($p in $param.GetEnumerator()){ if($PSBoundParameters.ContainsKey($p)){ $newPsboundParams.Add($p,$PSBoundParameters[$p]) } } #Add verbose, debug $newPsboundParams.Add('Verbose',$O365Object.verbose) $newPsboundParams.Add('Debug',$O365Object.debug) $newPsboundParams.Add('InformationAction',$O365Object.InformationAction) #Add services if exists If($null -ne $O365Object.initParams.Collect -and $O365Object.initParams.Collect.Count -gt 0){ #Remove all option $collect = $O365Object.initParams.Collect.Where({$_.ToLower() -ne 'all'}) [void]$newPsboundParams.Add('Service',$collect); } #Add pretty print [void]$newPsboundParams.Add('Pretty',$true); #Add Provider If($PSBoundParameters.ContainsKey('Instance') -and $PSBoundParameters['Instance']){ [void]$newPsboundParams.Add('Provider',$PSBoundParameters['Instance']); } #Execute command Get-MonkeyCollector @newPsboundParams } return } #Check for mandatory params Test-MandatoryParameter #Import MSAL module $msg = @{ MessageData = "Importing MSAL authentication library"; callStack = (Get-PSCallStack | Select-Object -First 1); logLevel = 'info'; InformationAction = $O365Object.InformationAction; Tags = @('Monkey365LoadMSAL'); } Write-Information @msg $MSAL = ("{0}{1}core/modules/monkeymsal" -f $O365Object.Localpath,[System.IO.Path]::DirectorySeparatorChar) Import-Module $MSAL -Scope Global -Force -ArgumentList $O365Object.forceMSALDesktop ################### End Validate parameters ##################### #Initialize authentication parameters Initialize-AuthenticationParam #Connect Connect-MonkeyCloud #Start Watcher If($null -ne (Get-Command -Name "Watch-AccessToken" -ErrorAction ignore)){ Watch-AccessToken } } Process{ if(($O365Object.onlineServices.GetEnumerator() | Where-Object {$_.Value -eq $true})){ switch ($O365Object.Instance.ToLower()){ 'azure'{ Invoke-AzureScanner } 'microsoft365'{ Invoke-M365Scanner } 'entraid'{ Invoke-EntraIDScanner } default{ throw ("{0}" -f "Unable to recognize {0} environment",$O365Object.Instance.ToLower()) } } } } End{ try{ #Stop timer $stoptimer = Get-Date $elapsedTime = [math]::round(($stoptimer - $starttimer).TotalMinutes , 2) $msg = @{ MessageData = ($message.TimeElapsedScript -f $elapsedTime); callStack = (Get-PSCallStack | Select-Object -First 1); logLevel = 'info'; InformationAction = $script:InformationAction; Tags = @('Monkey365FinishedJobs'); } Write-Information @msg #Stop Watcher if($null -ne (Get-Command -Name "Watch-AccessToken" -ErrorAction ignore)){ Watch-AccessToken -Stop } #Stop loggers Stop-Logger } catch{ Write-Error $_ } Finally{ #Remove OutFolder variable Remove-Variable -Name OutFolder -Scope Script -Force -ErrorAction Ignore #Remove returnData variable Remove-Variable -Name returnData -Scope Script -Force -ErrorAction Ignore #Remove LogQueue Remove-Variable -Name MonkeyLogQueue -Scope Global -ErrorAction Ignore -Force #collect garbage [System.GC]::GetTotalMemory($true) | out-null } } } |