From 912136eae096f35f86a07d2f70927c16de9e795a Mon Sep 17 00:00:00 2001 From: Katie Knowles Date: Thu, 10 Oct 2024 08:34:06 -0400 Subject: [PATCH 01/12] Add Azure Bastion shareable link technique --- .../azure.execution.bastion-shareable-link.md | 85 ++++++++ v2/go.mod | 3 +- v2/go.sum | 12 +- .../execution/bastion-shareable-link/main.go | 184 ++++++++++++++++++ .../execution/bastion-shareable-link/main.tf | 171 ++++++++++++++++ v2/internal/attacktechniques/main.go | 1 + 6 files changed, 453 insertions(+), 3 deletions(-) create mode 100644 docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md create mode 100644 v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.go create mode 100644 v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.tf diff --git a/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md b/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md new file mode 100644 index 00000000..20f02319 --- /dev/null +++ b/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md @@ -0,0 +1,85 @@ +--- +title: Access Virtual Machine using Bastion shareable link +--- + +# Access Virtual Machine using Bastion shareable link + + slow + + +Platform: Azure + +## MITRE ATT&CK Tactics + + +- Execution + +## Description + + +By utilizing the 'shareable link' feature on Bastions where it is enabled, an attacker can create a link to allow access to a virtual machine (VM) from untrusted networks. Public links generated for an Azure Bastion can allow VM network access to anyone with the generated URL. + +References: + +- https://blog.karims.cloud/2022/11/26/yet-another-azure-vm-persistence.html +- https://learn.microsoft.com/en-us/azure/bastion/shareable-link +- https://microsoft.github.io/Azure-Threat-Research-Matrix/Persistence/AZT509/AZT509/ + +Warm-up: + +- Create a VM and VNet +- Create an Azure Bastion host with access to the VM, and shareable links enabled +NOTE: Warm-up and cleanup can each take 10-15 minutes to create and destroy the Azure Bastion instance + +Detonation: + +- Create an Azure Bastion shareable link with access to the VM + + +## Instructions + +```bash title="Detonate with Stratus Red Team" +stratus detonate azure.execution.bastion-shareable-link +``` +## Detection + + +Identify Azure events of type Microsoft.Network/bastionHosts/createshareablelinks/action and Microsoft.Network/bastionHosts/getShareablelinks/action. A sample of createshareablelinks is shown below (redacted for clarity). + +```json hl_lines="7" + { + "category": { + "value": "Administrative", + "localizedValue": "Administrative" + }, + "level": "Informational", + "operationName": { + "value": "Microsoft.Network/bastionHosts/createshareablelinks/action", + "localizedValue": "Creates shareable urls for the VMs under a bastion and returns the urls" + }, + "resourceGroupName": "stratus-red-team-shareable-link-rg-tz6o", + "resourceProviderName": { + "value": "Microsoft.Network", + "localizedValue": "Microsoft.Network" + }, + "resourceType": { + "value": "Microsoft.Network/bastionHosts", + "localizedValue": "Microsoft.Network/bastionHosts" + }, + "resourceId": "[removed]/resourceGroups/stratus-red-team-shareable-link-rg-tz6o/providers/Microsoft.Network/bastionHosts/stratus-red-team-shareable-link-bastion-tz6o", + "status": { + "value": "Succeeded", + "localizedValue": "Succeeded" + }, + "subStatus": { + "value": "", + "localizedValue": "" + }, + "properties": { + "eventCategory": "Administrative", + "entity": "[removed]/resourceGroups/stratus-red-team-shareable-link-rg-tz6o/providers/Microsoft.Network/bastionHosts/stratus-red-team-shareable-link-bastion-tz6o", + "message": "Microsoft.Network/bastionHosts/createshareablelinks/action", + "hierarchy": "[removed]" + }, + } +``` \ No newline at end of file diff --git a/v2/go.mod b/v2/go.mod index 464ea7f4..2ec5cd7c 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -9,7 +9,8 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.1.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 github.com/aws/aws-sdk-go-v2 v1.31.0 github.com/aws/aws-sdk-go-v2/config v1.25.11 github.com/aws/aws-sdk-go-v2/credentials v1.16.9 diff --git a/v2/go.sum b/v2/go.sum index 686151ff..35c5f8ac 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -13,10 +13,18 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0/go.mod h1:gM3K25LQlsET3QR+4V74zxCsFAy0r6xMNN9n80SZn+4= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.0.0 h1:lMW1lD/17LUA5z1XTURo7LcVG2ICBPlyMHjIUrcFZNQ= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.0.0/go.mod h1:ceIuwmxDWptoW3eCqSXlnPsZFKh4X+R38dWPv7GS9Vs= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0/go.mod h1:AW8VEadnhw9xox+VaVd9sP7NjzOAnaZBLRH6Tq3cJ38= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0/go.mod h1:mLfWfj8v3jfWKsL9G4eoBoXVcsqcIUTapmdKy7uGOp0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0 h1:nBy98uKOIfun5z6wx6jwWLrULcM0+cjBalBFZlEZ7CA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0/go.mod h1:243D9iHbcQXoFUtgHJwL7gl2zx1aDuDMjvBZVGr2uW0= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.1.0 h1:Fd+iaEa+JBwzYo6OTWYSNqyvlPSLciMGsmsnYCKcXM0= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.1.0/go.mod h1:ulHyBFJOI0ONiRL4vcJTmS7rx18jQQlEPmAgo80cRdM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= diff --git a/v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.go b/v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.go new file mode 100644 index 00000000..e547fadd --- /dev/null +++ b/v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.go @@ -0,0 +1,184 @@ +package azure + +import ( + "context" + _ "embed" + "github.com/datadog/stratus-red-team/v2/pkg/stratus" + "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" + "log" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" + "fmt" +) + +//go:embed main.tf +var tf []byte + +func init() { + const codeBlock = "```" + stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ + ID: "azure.execution.bastion-shareable-link", + FriendlyName: "Access Virtual Machine using Bastion shareable link", + Description: ` +By utilizing the 'shareable link' feature on Bastions where it is enabled, an attacker can create a link to allow access to a virtual machine (VM) from untrusted networks. Public links generated for an Azure Bastion can allow VM network access to anyone with the generated URL. + +References: + +- https://blog.karims.cloud/2022/11/26/yet-another-azure-vm-persistence.html +- https://learn.microsoft.com/en-us/azure/bastion/shareable-link +- https://microsoft.github.io/Azure-Threat-Research-Matrix/Persistence/AZT509/AZT509/ + +Warm-up: + +- Create a VM and VNet +- Create an Azure Bastion host with access to the VM, and shareable links enabled +NOTE: Warm-up and cleanup can each take 10-15 minutes to create and destroy the Azure Bastion instance + +Detonation: + +- Create an Azure Bastion shareable link with access to the VM +`, + Detection: ` +Identify Azure events of type Microsoft.Network/bastionHosts/createshareablelinks/action and Microsoft.Network/bastionHosts/getShareablelinks/action. A sample of createshareablelinks is shown below (redacted for clarity). + +` + codeBlock + `json hl_lines="7" +{ + { + "category": { + "value": "Administrative", + "localizedValue": "Administrative" + }, + "level": "Informational", + "operationName": { + "value": "Microsoft.Network/bastionHosts/createshareablelinks/action", + "localizedValue": "Creates shareable urls for the VMs under a bastion and returns the urls" + }, + "resourceGroupName": "stratus-red-team-shareable-link-rg-tz6o", + "resourceProviderName": { + "value": "Microsoft.Network", + "localizedValue": "Microsoft.Network" + }, + "resourceType": { + "value": "Microsoft.Network/bastionHosts", + "localizedValue": "Microsoft.Network/bastionHosts" + }, + "resourceId": "[removed]/resourceGroups/stratus-red-team-shareable-link-rg-tz6o/providers/Microsoft.Network/bastionHosts/stratus-red-team-shareable-link-bastion-tz6o", + "status": { + "value": "Succeeded", + "localizedValue": "Succeeded" + }, + "subStatus": { + "value": "", + "localizedValue": "" + }, + "properties": { + "eventCategory": "Administrative", + "entity": "[removed]/resourceGroups/stratus-red-team-shareable-link-rg-tz6o/providers/Microsoft.Network/bastionHosts/stratus-red-team-shareable-link-bastion-tz6o", + "message": "Microsoft.Network/bastionHosts/createshareablelinks/action", + "hierarchy": "[removed]" + }, +} +` + codeBlock + ` +`, + Platform: stratus.Azure, + IsSlow: true, + IsIdempotent: false, + MitreAttackTactics: []mitreattack.Tactic{mitreattack.Execution}, + PrerequisitesTerraformCode: tf, + Detonate: detonate, + Revert: revert, + }) +} + +const ExtensionName = "CustomScriptExtension-StratusRedTeam-Example" + +func detonate(params map[string]string, providers stratus.CloudProviders) error { + bastionName := params["bastion_name"] + resourceGroup := params["resource_group_name"] + vmId := params["vm_id"] + vmName := params["vm_name"] + tenantId := params["tenant_id"] + + ctx := context.Background() + cred := providers.Azure().GetCredentials() + subscriptionID := providers.Azure().SubscriptionID + clientOptions := providers.Azure().ClientOptions + + client, err := armnetwork.NewClientFactory(subscriptionID, cred, clientOptions) + if err != nil { + log.Fatalf("failed to create client: %v", err) + } + + // Create Bastion shareable link + // Reference method: https://learn.microsoft.com/en-us/rest/api/virtualnetwork/put-bastion-shareable-link/put-bastion-shareable-link + log.Println("Getting Bastion shareable link for VM " +vmName) + + poller, err := client.NewManagementClient().BeginPutBastionShareableLink(ctx, resourceGroup, bastionName, armnetwork.BastionShareableLinkListRequest{ + VMs: []*armnetwork.BastionShareableLink{ + { + VM: &armnetwork.VM{ + ID: to.Ptr(vmId), + }, + }, + }, + }, nil) + if err != nil { + log.Fatalf("failed to create shareable link: %v", err) + } + + _, err = poller.PollUntilDone(ctx, nil) + if err != nil { + log.Fatalf("failed to poll results: %v", err) + } + log.Println("Shareable link created") + + // Provide URL to access Bastion shareable link + // NOTE: Response via Go SDK methods does not return any page contents, so we'll supply a Portal URL to fetch the link for now. (The example cited in reference link above is not clear on how to resolve this.) + url := fmt.Sprintln("https://portal.azure.com/#@" + tenantId + "/resource/subscriptions/" + subscriptionID + "/resourceGroups/" + resourceGroup + "/providers/Microsoft.Network/bastionHosts/" + bastionName + "/shareablelinks") + + log.Println("You can view and fetch the shareable link URL here: " + url) + + return nil +} + +func revert(params map[string]string, providers stratus.CloudProviders) error { + // Reference method: https://learn.microsoft.com/en-us/rest/api/virtualnetwork/delete-bastion-shareable-link/delete-bastion-shareable-link?view=rest-virtualnetwork-2024-03-01&tabs=Go + bastionName := params["bastion_name"] + resourceGroup := params["resource_group_name"] + vmId := params["vm_id"] + vmName := params["vm_name"] + + ctx := context.Background() + cred := providers.Azure().GetCredentials() + subscriptionID := providers.Azure().SubscriptionID + clientOptions := providers.Azure().ClientOptions + + client, err := armnetwork.NewClientFactory(subscriptionID, cred, clientOptions) + if err != nil { + log.Fatalf("failed to create client: %v", err) + } + + // Delete shareable link that was previously created + log.Println("Deleting shareable Bastion link to VM " + vmName) + + poller, err := client.NewManagementClient().BeginDeleteBastionShareableLink(ctx, resourceGroup, bastionName, armnetwork.BastionShareableLinkListRequest{ + VMs: []*armnetwork.BastionShareableLink{ + { + VM: &armnetwork.VM{ + ID: to.Ptr(vmId), + }, + }, + }, + }, nil) + if err != nil { + log.Fatalf("failed to finish the request: %v", err) + } + _, err = poller.PollUntilDone(ctx, nil) + if err != nil { + log.Fatalf("failed to pull the result: %v", err) + } + + log.Println("Shareable link deleted") + + return nil +} \ No newline at end of file diff --git a/v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.tf b/v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.tf new file mode 100644 index 00000000..16ceff2f --- /dev/null +++ b/v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.tf @@ -0,0 +1,171 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "3.8.0" + } + } +} + +provider "azurerm" { + features {} +} + +locals { + resource_prefix = "stratus-red-team-shareable-link" +} + +data "azurerm_client_config" "current" { +} + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Random +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +resource "random_string" "lab_name" { + length = 4 + special = false + upper = false +} + +resource "random_password" "password" { + length = 64 + special = true + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Resource Group +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +resource "azurerm_resource_group" "lab_environment" { + name = "${local.resource_prefix}-rg-${random_string.lab_name.result}" + location = "West US" +} + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Networking Resources +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +resource "azurerm_virtual_network" "lab_vnet" { + name = "${local.resource_prefix}-vnet-${random_string.lab_name.result}" + address_space = ["10.0.0.0/24"] + location = azurerm_resource_group.lab_environment.location + resource_group_name = azurerm_resource_group.lab_environment.name +} + +resource "azurerm_subnet" "bastion_subnet" { + # Required naming for deployment of Azure Bastion + name = "AzureBastionSubnet" + resource_group_name = azurerm_resource_group.lab_environment.name + virtual_network_name = azurerm_virtual_network.lab_vnet.name + address_prefixes = ["10.0.0.0/27"] +} + +resource "azurerm_subnet" "lab_subnet" { + name = "${local.resource_prefix}-subnet-${random_string.lab_name.result}" + resource_group_name = azurerm_resource_group.lab_environment.name + virtual_network_name = azurerm_virtual_network.lab_vnet.name + address_prefixes = ["10.0.0.32/27"] +} + +resource "azurerm_public_ip" "lab_pip" { + name = "${local.resource_prefix}-pip-${random_string.lab_name.result}" + location = azurerm_resource_group.lab_environment.location + resource_group_name = azurerm_resource_group.lab_environment.name + allocation_method = "Static" + sku = "Standard" +} + +resource "azurerm_network_interface" "lab_nic" { + name = "${local.resource_prefix}-nic-${random_string.lab_name.result}" + location = azurerm_resource_group.lab_environment.location + resource_group_name = azurerm_resource_group.lab_environment.name + + ip_configuration { + name = "${local.resource_prefix}-ip-${random_string.lab_name.result}" + subnet_id = azurerm_subnet.lab_subnet.id + private_ip_address_allocation = "Dynamic" + } +} + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Virtual Machine Resources +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +resource "azurerm_windows_virtual_machine" "lab_windows_vm" { + name = "srt-vm-bsl" # 15 character limit: stratus red team - vm - bastion shareable link + resource_group_name = azurerm_resource_group.lab_environment.name + location = azurerm_resource_group.lab_environment.location + size = "Standard_F2" + admin_username = "local_admin_user" + admin_password = random_password.password.result + user_data = base64encode(random_string.lab_name.result) + + network_interface_ids = [ + azurerm_network_interface.lab_nic.id, + ] + + os_disk { + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + } + + source_image_reference { + publisher = "MicrosoftWindowsServer" + offer = "WindowsServer" + sku = "2016-Datacenter" + version = "latest" + } +} + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Bastion Resource +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +# Note: Creation/destruction of a Bastion can take 10 minutes each, see https://learn.microsoft.com/en-us/azure/bastion/tutorial-create-host-portal +resource "azurerm_bastion_host" "bastion" { + name = "${local.resource_prefix}-bastion-${random_string.lab_name.result}" + location = azurerm_resource_group.lab_environment.location + resource_group_name = azurerm_resource_group.lab_environment.name + # Required for shareable link feature + sku = "Standard" + shareable_link_enabled = "true" + + ip_configuration { + name = "${local.resource_prefix}-ipconfig-${random_string.lab_name.result}" + subnet_id = azurerm_subnet.bastion_subnet.id + public_ip_address_id = azurerm_public_ip.lab_pip.id + } +} + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Outputs +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +output "resource_group_name" { + value = azurerm_resource_group.lab_environment.name +} + +output "bastion_name" { + value = azurerm_bastion_host.bastion.name +} + +output "vm_id"{ + value = azurerm_windows_virtual_machine.lab_windows_vm.id +} + +output "vm_name"{ + value = azurerm_windows_virtual_machine.lab_windows_vm.name +} + +output tenant_id{ + value = data.azurerm_client_config.current.tenant_id +} + +output "display" { + value = format( + "Bastion %s ready in resource group %s, with access to VM %s.", + azurerm_windows_virtual_machine.lab_windows_vm.name, + azurerm_resource_group.lab_environment.name, + azurerm_windows_virtual_machine.lab_windows_vm.name + ) +} \ No newline at end of file diff --git a/v2/internal/attacktechniques/main.go b/v2/internal/attacktechniques/main.go index 29cd3996..678ad314 100644 --- a/v2/internal/attacktechniques/main.go +++ b/v2/internal/attacktechniques/main.go @@ -43,6 +43,7 @@ import ( _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/privilege-escalation/change-iam-user-password" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/vm-custom-script-extension" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/vm-run-command" + _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/bastion-shareable-link" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/exfiltration/disk-export" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/eks/lateral-movement/create-access-entry" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/eks/persistence/backdoor-aws-auth-configmap" From 4fa6aab1c0e972c33e4e6788e4f5266ca56b42c7 Mon Sep 17 00:00:00 2001 From: Katie Knowles Date: Thu, 10 Oct 2024 09:02:27 -0400 Subject: [PATCH 02/12] Add delay note for Bastion technique --- .../azure.execution.bastion-shareable-link.md | 76 +++++++------------ 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md b/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md index 20f02319..f4c35687 100644 --- a/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md +++ b/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md @@ -1,10 +1,10 @@ --- -title: Access Virtual Machine using Bastion shareable link +title: Execute Command on Virtual Machine using Custom Script Extension --- -# Access Virtual Machine using Bastion shareable link +# Execute Command on Virtual Machine using Custom Script Extension - slow + slow Platform: Azure @@ -17,69 +17,49 @@ Platform: Azure ## Description -By utilizing the 'shareable link' feature on Bastions where it is enabled, an attacker can create a link to allow access to a virtual machine (VM) from untrusted networks. Public links generated for an Azure Bastion can allow VM network access to anyone with the generated URL. +By utilizing the 'CustomScriptExtension' extension on a Virtual Machine, an attacker can pass PowerShell commands to the VM as SYSTEM. +NOTE: This technique will take 10-15 minutes to warmup, and 10-15 minutes to cleanup. This is due to the time to deploy an Azure Bastion. References: -- https://blog.karims.cloud/2022/11/26/yet-another-azure-vm-persistence.html -- https://learn.microsoft.com/en-us/azure/bastion/shareable-link -- https://microsoft.github.io/Azure-Threat-Research-Matrix/Persistence/AZT509/AZT509/ +- https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/custom-script-windows +- https://microsoft.github.io/Azure-Threat-Research-Matrix/Execution/AZT301/AZT301-2/ Warm-up: -- Create a VM and VNet -- Create an Azure Bastion host with access to the VM, and shareable links enabled -NOTE: Warm-up and cleanup can each take 10-15 minutes to create and destroy the Azure Bastion instance +- Create a virtual machine Detonation: -- Create an Azure Bastion shareable link with access to the VM +- Configure a custom script extension for the virtual machine ## Instructions ```bash title="Detonate with Stratus Red Team" -stratus detonate azure.execution.bastion-shareable-link +stratus detonate azure.execution.vm-custom-script-extension ``` ## Detection -Identify Azure events of type Microsoft.Network/bastionHosts/createshareablelinks/action and Microsoft.Network/bastionHosts/getShareablelinks/action. A sample of createshareablelinks is shown below (redacted for clarity). +Identify Azure events of type Microsoft.Compute/virtualMachines/extensions/write. Sample below (redacted for clarity). ```json hl_lines="7" - { - "category": { - "value": "Administrative", - "localizedValue": "Administrative" - }, - "level": "Informational", - "operationName": { - "value": "Microsoft.Network/bastionHosts/createshareablelinks/action", - "localizedValue": "Creates shareable urls for the VMs under a bastion and returns the urls" - }, - "resourceGroupName": "stratus-red-team-shareable-link-rg-tz6o", - "resourceProviderName": { - "value": "Microsoft.Network", - "localizedValue": "Microsoft.Network" - }, - "resourceType": { - "value": "Microsoft.Network/bastionHosts", - "localizedValue": "Microsoft.Network/bastionHosts" - }, - "resourceId": "[removed]/resourceGroups/stratus-red-team-shareable-link-rg-tz6o/providers/Microsoft.Network/bastionHosts/stratus-red-team-shareable-link-bastion-tz6o", - "status": { - "value": "Succeeded", - "localizedValue": "Succeeded" - }, - "subStatus": { - "value": "", - "localizedValue": "" - }, - "properties": { - "eventCategory": "Administrative", - "entity": "[removed]/resourceGroups/stratus-red-team-shareable-link-rg-tz6o/providers/Microsoft.Network/bastionHosts/stratus-red-team-shareable-link-bastion-tz6o", - "message": "Microsoft.Network/bastionHosts/createshareablelinks/action", - "hierarchy": "[removed]" - }, - } +{ + "duration": 0, + "resourceId": "/SUBSCRIPTIONS//RESOURCEGROUPS/RG-HAT6H48Q/PROVIDERS/MICROSOFT.COMPUTE/VIRTUALMACHINES/VM-HAT6H48Q/EXTENSIONS/CUSTOMSCRIPTEXTENSION-STRATUS-EXAMPLE", + "evt": { + "category": "Administrative", + "outcome": "Start", + "name": "MICROSOFT.COMPUTE/VIRTUALMACHINES/EXTENSIONS/WRITE" + }, + "resource_name": "customscriptextension-stratus-example", + "time": "2022-06-18T19:57:27.8617215Z", + "properties": { + "hierarchy": "ecc2b97b-844b-414e-8123-b925dddf87ed/", + "message": "Microsoft.Compute/virtualMachines/extensions/write", + "eventCategory": "Administrative", + "entity": "/subscriptions//resourceGroups/rg-hat6h48q/providers/Microsoft.Compute/virtualMachines/vm-hat6h48q/extensions/CustomScriptExtension-Stratus-Example" + }, +} ``` \ No newline at end of file From d04686ce8bbcd790376247fb0113678876534c6a Mon Sep 17 00:00:00 2001 From: Katie Knowles Date: Thu, 10 Oct 2024 09:07:49 -0400 Subject: [PATCH 03/12] Add techniqe documentation --- .../azure.execution.bastion-shareable-link.md | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md b/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md index f4c35687..14df1f98 100644 --- a/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md +++ b/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md @@ -1,8 +1,8 @@ --- -title: Execute Command on Virtual Machine using Custom Script Extension +title: Access Virtual Machine using Bastion shareable link --- -# Execute Command on Virtual Machine using Custom Script Extension +# Access Virtual Machine using Bastion shareable link slow @@ -17,49 +17,68 @@ Platform: Azure ## Description -By utilizing the 'CustomScriptExtension' extension on a Virtual Machine, an attacker can pass PowerShell commands to the VM as SYSTEM. +By utilizing the 'shareable link' feature on Bastions where it is enabled, an attacker can create a link to allow access to a virtual machine (VM) from untrusted networks. Public links generated for an Azure Bastion can allow VM network access to anyone with the generated URL. NOTE: This technique will take 10-15 minutes to warmup, and 10-15 minutes to cleanup. This is due to the time to deploy an Azure Bastion. References: -- https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/custom-script-windows -- https://microsoft.github.io/Azure-Threat-Research-Matrix/Execution/AZT301/AZT301-2/ +- https://blog.karims.cloud/2022/11/26/yet-another-azure-vm-persistence.html +- https://learn.microsoft.com/en-us/azure/bastion/shareable-link +- https://microsoft.github.io/Azure-Threat-Research-Matrix/Persistence/AZT509/AZT509/ Warm-up: -- Create a virtual machine +- Create a VM and VNet +- Create an Azure Bastion host with access to the VM, and shareable links enabled +NOTE: Warm-up and cleanup can each take 10-15 minutes to create and destroy the Azure Bastion instance Detonation: -- Configure a custom script extension for the virtual machine - +- Create an Azure Bastion shareable link with access to the VM ## Instructions ```bash title="Detonate with Stratus Red Team" -stratus detonate azure.execution.vm-custom-script-extension +stratus detonate azure.execution.bastion-shareable-link ``` ## Detection - -Identify Azure events of type Microsoft.Compute/virtualMachines/extensions/write. Sample below (redacted for clarity). +Identify Azure events of type Microsoft.Network/bastionHosts/createshareablelinks/action and Microsoft.Network/bastionHosts/getShareablelinks/action. A sample of createshareablelinks is shown below (redacted for clarity). ```json hl_lines="7" -{ - "duration": 0, - "resourceId": "/SUBSCRIPTIONS//RESOURCEGROUPS/RG-HAT6H48Q/PROVIDERS/MICROSOFT.COMPUTE/VIRTUALMACHINES/VM-HAT6H48Q/EXTENSIONS/CUSTOMSCRIPTEXTENSION-STRATUS-EXAMPLE", - "evt": { - "category": "Administrative", - "outcome": "Start", - "name": "MICROSOFT.COMPUTE/VIRTUALMACHINES/EXTENSIONS/WRITE" - }, - "resource_name": "customscriptextension-stratus-example", - "time": "2022-06-18T19:57:27.8617215Z", - "properties": { - "hierarchy": "ecc2b97b-844b-414e-8123-b925dddf87ed/", - "message": "Microsoft.Compute/virtualMachines/extensions/write", - "eventCategory": "Administrative", - "entity": "/subscriptions//resourceGroups/rg-hat6h48q/providers/Microsoft.Compute/virtualMachines/vm-hat6h48q/extensions/CustomScriptExtension-Stratus-Example" - }, + { + "category": { + "value": "Administrative", + "localizedValue": "Administrative" + }, + "level": "Informational", + "operationName": { + "value": "Microsoft.Network/bastionHosts/createshareablelinks/action", + "localizedValue": "Creates shareable urls for the VMs under a bastion and returns the urls" + }, + "resourceGroupName": "stratus-red-team-shareable-link-rg-tz6o", + "resourceProviderName": { + "value": "Microsoft.Network", + "localizedValue": "Microsoft.Network" + }, + "resourceType": { + "value": "Microsoft.Network/bastionHosts", + "localizedValue": "Microsoft.Network/bastionHosts" + }, + "resourceId": "[removed]/resourceGroups/stratus-red-team-shareable-link-rg-tz6o/providers/Microsoft.Network/bastionHosts/stratus-red-team-shareable-link-bastion-tz6o", + "status": { + "value": "Succeeded", + "localizedValue": "Succeeded" + }, + "subStatus": { + "value": "", + "localizedValue": "" + }, + "properties": { + "eventCategory": "Administrative", + "entity": "[removed]/resourceGroups/stratus-red-team-shareable-link-rg-tz6o/providers/Microsoft.Network/bastionHosts/stratus-red-team-shareable-link-bastion-tz6o", + "message": "Microsoft.Network/bastionHosts/createshareablelinks/action", + "hierarchy": "[removed]" + }, } ``` \ No newline at end of file From 121707a6c85e40527cdcb64185dbc487ede920a8 Mon Sep 17 00:00:00 2001 From: Katie Knowles Date: Thu, 10 Oct 2024 09:19:43 -0400 Subject: [PATCH 04/12] Change category to persistence --- ...-link.md => azure.persistence.bastion-shareable-link.md} | 4 ++-- .../bastion-shareable-link/main.go | 6 ++---- .../bastion-shareable-link/main.tf | 0 v2/internal/attacktechniques/main.go | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) rename docs/attack-techniques/azure/{azure.execution.bastion-shareable-link.md => azure.persistence.bastion-shareable-link.md} (97%) rename v2/internal/attacktechniques/azure/{execution => persistence}/bastion-shareable-link/main.go (97%) rename v2/internal/attacktechniques/azure/{execution => persistence}/bastion-shareable-link/main.tf (100%) diff --git a/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md b/docs/attack-techniques/azure/azure.persistence.bastion-shareable-link.md similarity index 97% rename from docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md rename to docs/attack-techniques/azure/azure.persistence.bastion-shareable-link.md index 14df1f98..96b8ec77 100644 --- a/docs/attack-techniques/azure/azure.execution.bastion-shareable-link.md +++ b/docs/attack-techniques/azure/azure.persistence.bastion-shareable-link.md @@ -12,7 +12,7 @@ Platform: Azure ## MITRE ATT&CK Tactics -- Execution +- Persistence ## Description @@ -39,7 +39,7 @@ NOTE: Warm-up and cleanup can each take 10-15 minutes to create and destroy the ## Instructions ```bash title="Detonate with Stratus Red Team" -stratus detonate azure.execution.bastion-shareable-link +stratus detonate azure.persistence.bastion-shareable-link ``` ## Detection diff --git a/v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.go b/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.go similarity index 97% rename from v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.go rename to v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.go index e547fadd..a7b7988e 100644 --- a/v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.go +++ b/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.go @@ -17,7 +17,7 @@ var tf []byte func init() { const codeBlock = "```" stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ - ID: "azure.execution.bastion-shareable-link", + ID: "azure.persistence.bastion-shareable-link", FriendlyName: "Access Virtual Machine using Bastion shareable link", Description: ` By utilizing the 'shareable link' feature on Bastions where it is enabled, an attacker can create a link to allow access to a virtual machine (VM) from untrusted networks. Public links generated for an Azure Bastion can allow VM network access to anyone with the generated URL. @@ -83,15 +83,13 @@ Identify Azure events of type Microsoft.Network/bastionHosts/createshareab Platform: stratus.Azure, IsSlow: true, IsIdempotent: false, - MitreAttackTactics: []mitreattack.Tactic{mitreattack.Execution}, + MitreAttackTactics: []mitreattack.Tactic{mitreattack.Persistence}, PrerequisitesTerraformCode: tf, Detonate: detonate, Revert: revert, }) } -const ExtensionName = "CustomScriptExtension-StratusRedTeam-Example" - func detonate(params map[string]string, providers stratus.CloudProviders) error { bastionName := params["bastion_name"] resourceGroup := params["resource_group_name"] diff --git a/v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.tf b/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.tf similarity index 100% rename from v2/internal/attacktechniques/azure/execution/bastion-shareable-link/main.tf rename to v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.tf diff --git a/v2/internal/attacktechniques/main.go b/v2/internal/attacktechniques/main.go index 678ad314..604dfeac 100644 --- a/v2/internal/attacktechniques/main.go +++ b/v2/internal/attacktechniques/main.go @@ -43,7 +43,7 @@ import ( _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/privilege-escalation/change-iam-user-password" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/vm-custom-script-extension" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/vm-run-command" - _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/bastion-shareable-link" + _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/exfiltration/disk-export" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/eks/lateral-movement/create-access-entry" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/eks/persistence/backdoor-aws-auth-configmap" From 047d555c9853d0520db7e969cd03d0ac374c4d6b Mon Sep 17 00:00:00 2001 From: Katie Knowles Date: Thu, 10 Oct 2024 09:26:28 -0400 Subject: [PATCH 05/12] Fix tf formatting --- .../persistence/bastion-shareable-link/main.tf | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.tf b/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.tf index 16ceff2f..5fcd8f49 100644 --- a/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.tf +++ b/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.tf @@ -124,12 +124,12 @@ resource "azurerm_windows_virtual_machine" "lab_windows_vm" { # Note: Creation/destruction of a Bastion can take 10 minutes each, see https://learn.microsoft.com/en-us/azure/bastion/tutorial-create-host-portal resource "azurerm_bastion_host" "bastion" { - name = "${local.resource_prefix}-bastion-${random_string.lab_name.result}" - location = azurerm_resource_group.lab_environment.location - resource_group_name = azurerm_resource_group.lab_environment.name + name = "${local.resource_prefix}-bastion-${random_string.lab_name.result}" + location = azurerm_resource_group.lab_environment.location + resource_group_name = azurerm_resource_group.lab_environment.name # Required for shareable link feature - sku = "Standard" - shareable_link_enabled = "true" + sku = "Standard" + shareable_link_enabled = "true" ip_configuration { name = "${local.resource_prefix}-ipconfig-${random_string.lab_name.result}" @@ -149,15 +149,15 @@ output "bastion_name" { value = azurerm_bastion_host.bastion.name } -output "vm_id"{ +output "vm_id" { value = azurerm_windows_virtual_machine.lab_windows_vm.id } -output "vm_name"{ +output "vm_name" { value = azurerm_windows_virtual_machine.lab_windows_vm.name } -output tenant_id{ +output "tenant_id" { value = data.azurerm_client_config.current.tenant_id } From cf05cee4a1936473bb823f41a5ec3642d0676555 Mon Sep 17 00:00:00 2001 From: Katie Knowles Date: Thu, 10 Oct 2024 17:01:39 -0400 Subject: [PATCH 06/12] Error and string handling + technique rename --- ...d => azure.create-bastion-shareable-link.md} | 0 .../main.go | 17 ++++++++--------- .../main.tf | 2 +- v2/internal/attacktechniques/main.go | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) rename docs/attack-techniques/azure/{azure.persistence.bastion-shareable-link.md => azure.create-bastion-shareable-link.md} (100%) rename v2/internal/attacktechniques/azure/persistence/{bastion-shareable-link => create-bastion-shareable-link}/main.go (90%) rename v2/internal/attacktechniques/azure/persistence/{bastion-shareable-link => create-bastion-shareable-link}/main.tf (99%) diff --git a/docs/attack-techniques/azure/azure.persistence.bastion-shareable-link.md b/docs/attack-techniques/azure/azure.create-bastion-shareable-link.md similarity index 100% rename from docs/attack-techniques/azure/azure.persistence.bastion-shareable-link.md rename to docs/attack-techniques/azure/azure.create-bastion-shareable-link.md diff --git a/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.go b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go similarity index 90% rename from v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.go rename to v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go index a7b7988e..394e1b33 100644 --- a/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.go +++ b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go @@ -6,7 +6,6 @@ import ( "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" "fmt" ) @@ -17,7 +16,7 @@ var tf []byte func init() { const codeBlock = "```" stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ - ID: "azure.persistence.bastion-shareable-link", + ID: "azure.persistence.create-bastion-shareable-link", FriendlyName: "Access Virtual Machine using Bastion shareable link", Description: ` By utilizing the 'shareable link' feature on Bastions where it is enabled, an attacker can create a link to allow access to a virtual machine (VM) from untrusted networks. Public links generated for an Azure Bastion can allow VM network access to anyone with the generated URL. @@ -115,24 +114,24 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error VMs: []*armnetwork.BastionShareableLink{ { VM: &armnetwork.VM{ - ID: to.Ptr(vmId), + ID: &vmId, }, }, }, }, nil) if err != nil { - log.Fatalf("failed to create shareable link: %v", err) + return fmt.Errorf("failed to create shareable link: %v", err) } _, err = poller.PollUntilDone(ctx, nil) if err != nil { - log.Fatalf("failed to poll results: %v", err) + return fmt.Errorf("failed to poll results of shareable link request: %v", err) } log.Println("Shareable link created") // Provide URL to access Bastion shareable link // NOTE: Response via Go SDK methods does not return any page contents, so we'll supply a Portal URL to fetch the link for now. (The example cited in reference link above is not clear on how to resolve this.) - url := fmt.Sprintln("https://portal.azure.com/#@" + tenantId + "/resource/subscriptions/" + subscriptionID + "/resourceGroups/" + resourceGroup + "/providers/Microsoft.Network/bastionHosts/" + bastionName + "/shareablelinks") + url := fmt.Sprintf("https://portal.azure.com/#@%s/resource/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/bastionHosts/%s/shareablelinks", tenantId, subscriptionID, resourceGroup, bastionName) log.Println("You can view and fetch the shareable link URL here: " + url) @@ -163,17 +162,17 @@ func revert(params map[string]string, providers stratus.CloudProviders) error { VMs: []*armnetwork.BastionShareableLink{ { VM: &armnetwork.VM{ - ID: to.Ptr(vmId), + ID: &vmId, }, }, }, }, nil) if err != nil { - log.Fatalf("failed to finish the request: %v", err) + return fmt.Errorf("failed to delete shareable bastion link: %v", err) } _, err = poller.PollUntilDone(ctx, nil) if err != nil { - log.Fatalf("failed to pull the result: %v", err) + return fmt.Errorf("failed to poll results of deleting shareable bastion link: %v", err) } log.Println("Shareable link deleted") diff --git a/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.tf b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.tf similarity index 99% rename from v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.tf rename to v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.tf index 5fcd8f49..e39fe5b3 100644 --- a/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link/main.tf +++ b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.tf @@ -129,7 +129,7 @@ resource "azurerm_bastion_host" "bastion" { resource_group_name = azurerm_resource_group.lab_environment.name # Required for shareable link feature sku = "Standard" - shareable_link_enabled = "true" + shareable_link_enabled = true ip_configuration { name = "${local.resource_prefix}-ipconfig-${random_string.lab_name.result}" diff --git a/v2/internal/attacktechniques/main.go b/v2/internal/attacktechniques/main.go index 604dfeac..f2b1b98c 100644 --- a/v2/internal/attacktechniques/main.go +++ b/v2/internal/attacktechniques/main.go @@ -43,7 +43,7 @@ import ( _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/privilege-escalation/change-iam-user-password" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/vm-custom-script-extension" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/vm-run-command" - _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/persistence/bastion-shareable-link" + _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/exfiltration/disk-export" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/eks/lateral-movement/create-access-entry" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/eks/persistence/backdoor-aws-auth-configmap" From 145bb02302896ce23e6b035be4e5a3cb23af57ef Mon Sep 17 00:00:00 2001 From: Katie Knowles Date: Fri, 11 Oct 2024 11:27:59 -0400 Subject: [PATCH 07/12] Update v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go Co-authored-by: Christophe Tafani-Dereeper --- .../azure/persistence/create-bastion-shareable-link/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go index 394e1b33..9a82509c 100644 --- a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go +++ b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go @@ -103,7 +103,7 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error client, err := armnetwork.NewClientFactory(subscriptionID, cred, clientOptions) if err != nil { - log.Fatalf("failed to create client: %v", err) + return fmt.Errorf("failed to create client: %v", err) } // Create Bastion shareable link From 9f13975011562deb103643a5266ad26451da8095 Mon Sep 17 00:00:00 2001 From: Katie Knowles Date: Fri, 11 Oct 2024 11:28:07 -0400 Subject: [PATCH 08/12] Update v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go Co-authored-by: Christophe Tafani-Dereeper --- .../azure/persistence/create-bastion-shareable-link/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go index 9a82509c..72b4f967 100644 --- a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go +++ b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go @@ -152,7 +152,7 @@ func revert(params map[string]string, providers stratus.CloudProviders) error { client, err := armnetwork.NewClientFactory(subscriptionID, cred, clientOptions) if err != nil { - log.Fatalf("failed to create client: %v", err) + return fmt.Errorf("failed to instantiate ARM Network client: %v", err) } // Delete shareable link that was previously created From 86ac34005cdf9678b09c945d446fd0c1b3153434 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Thu, 17 Oct 2024 23:00:20 +0200 Subject: [PATCH 09/12] Cosmetic changes --- .../create-bastion-shareable-link/main.go | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go index 72b4f967..52e9c49e 100644 --- a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go +++ b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go @@ -3,11 +3,11 @@ package azure import ( "context" _ "embed" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" - "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" - "fmt" ) //go:embed main.tf @@ -17,7 +17,7 @@ func init() { const codeBlock = "```" stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ ID: "azure.persistence.create-bastion-shareable-link", - FriendlyName: "Access Virtual Machine using Bastion shareable link", + FriendlyName: "Create Azure VM Bastion shareable link", Description: ` By utilizing the 'shareable link' feature on Bastions where it is enabled, an attacker can create a link to allow access to a virtual machine (VM) from untrusted networks. Public links generated for an Azure Bastion can allow VM network access to anyone with the generated URL. @@ -31,6 +31,7 @@ Warm-up: - Create a VM and VNet - Create an Azure Bastion host with access to the VM, and shareable links enabled + NOTE: Warm-up and cleanup can each take 10-15 minutes to create and destroy the Azure Bastion instance Detonation: @@ -108,16 +109,10 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error // Create Bastion shareable link // Reference method: https://learn.microsoft.com/en-us/rest/api/virtualnetwork/put-bastion-shareable-link/put-bastion-shareable-link - log.Println("Getting Bastion shareable link for VM " +vmName) + log.Println("Getting Bastion shareable link for VM " + vmName) poller, err := client.NewManagementClient().BeginPutBastionShareableLink(ctx, resourceGroup, bastionName, armnetwork.BastionShareableLinkListRequest{ - VMs: []*armnetwork.BastionShareableLink{ - { - VM: &armnetwork.VM{ - ID: &vmId, - }, - }, - }, + VMs: []*armnetwork.BastionShareableLink{{VM: &armnetwork.VM{ID: &vmId}}}, }, nil) if err != nil { return fmt.Errorf("failed to create shareable link: %v", err) @@ -159,14 +154,9 @@ func revert(params map[string]string, providers stratus.CloudProviders) error { log.Println("Deleting shareable Bastion link to VM " + vmName) poller, err := client.NewManagementClient().BeginDeleteBastionShareableLink(ctx, resourceGroup, bastionName, armnetwork.BastionShareableLinkListRequest{ - VMs: []*armnetwork.BastionShareableLink{ - { - VM: &armnetwork.VM{ - ID: &vmId, - }, - }, - }, - }, nil) + VMs: []*armnetwork.BastionShareableLink{{ + VM: &armnetwork.VM{ID: &vmId}}, + }}, nil) if err != nil { return fmt.Errorf("failed to delete shareable bastion link: %v", err) } @@ -178,4 +168,4 @@ func revert(params map[string]string, providers stratus.CloudProviders) error { log.Println("Shareable link deleted") return nil -} \ No newline at end of file +} From e951d433fb89a3c114cbd1f81a717f48b8944114 Mon Sep 17 00:00:00 2001 From: Katie Knowles Date: Fri, 18 Oct 2024 14:00:46 -0400 Subject: [PATCH 10/12] Add shareable link + credentials to output --- .../create-bastion-shareable-link/main.go | 40 ++++++++++++++++--- .../create-bastion-shareable-link/main.tf | 9 +++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go index 52e9c49e..f10feec2 100644 --- a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go +++ b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go @@ -4,6 +4,7 @@ import ( "context" _ "embed" "fmt" + "encoding/json" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" @@ -95,7 +96,9 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error resourceGroup := params["resource_group_name"] vmId := params["vm_id"] vmName := params["vm_name"] - tenantId := params["tenant_id"] + adminUsername := params["admin_username"] + // String requires extra quotations for unmarshaling, see below for more on this + adminPassword := fmt.Sprintf(`"%s"`, params["admin_password"]) ctx := context.Background() cred := providers.Azure().GetCredentials() @@ -124,11 +127,38 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error } log.Println("Shareable link created") - // Provide URL to access Bastion shareable link - // NOTE: Response via Go SDK methods does not return any page contents, so we'll supply a Portal URL to fetch the link for now. (The example cited in reference link above is not clear on how to resolve this.) - url := fmt.Sprintf("https://portal.azure.com/#@%s/resource/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/bastionHosts/%s/shareablelinks", tenantId, subscriptionID, resourceGroup, bastionName) + // Get Bastion shareable link + // Reference method: https://learn.microsoft.com/en-us/rest/api/virtualnetwork/get-bastion-shareable-link/get-bastion-shareable-link + // No error is returned by this method + pager := client.NewManagementClient().NewGetBastionShareableLinkPager(resourceGroup, bastionName, armnetwork.BastionShareableLinkListRequest{ + VMs: []*armnetwork.BastionShareableLink{ + { + VM: &armnetwork.VM{ + ID: &vmId, + }, + }, + }, + }, nil) + + for pager.More() { + page, err := pager.NextPage(ctx) + if err != nil { + fmt.Errorf("failed to get results page: %v", err) + } + for _, result := range page.Value { + log.Println("Bastion shareable link URL: " + *result.Bsl) + } + } - log.Println("You can view and fetch the shareable link URL here: " + url) + log.Println("Bastion username: " + adminUsername) + + // Password needs to be unmarshaled, as the resulting string from Terraform has json.HTMLEscape applied. Unmarshal is the correct operation, but string needs to be correctly formatted to work (see above). + var adminPasswordDecoded string + err = json.Unmarshal([]byte(adminPassword), &adminPasswordDecoded) + if err != nil{ + fmt.Errorf("failed to unmarshal password string: %v", err) + } + log.Println("Bastion password: " + adminPasswordDecoded) return nil } diff --git a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.tf b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.tf index e39fe5b3..a1bb6076 100644 --- a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.tf +++ b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.tf @@ -157,6 +157,15 @@ output "vm_name" { value = azurerm_windows_virtual_machine.lab_windows_vm.name } +output "admin_username" { + value = azurerm_windows_virtual_machine.lab_windows_vm.admin_username +} + +output "admin_password" { + sensitive = true + value = random_password.password.result +} + output "tenant_id" { value = data.azurerm_client_config.current.tenant_id } From 61d003abe06856c4539eeb3d1be6c40cd8696fae Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Mon, 21 Oct 2024 22:07:19 +0200 Subject: [PATCH 11/12] Fix error handling --- .../azure/persistence/create-bastion-shareable-link/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go index f10feec2..94b07ac7 100644 --- a/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go +++ b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go @@ -143,7 +143,7 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error for pager.More() { page, err := pager.NextPage(ctx) if err != nil { - fmt.Errorf("failed to get results page: %v", err) + return fmt.Errorf("failed to get results page: %v", err) } for _, result := range page.Value { log.Println("Bastion shareable link URL: " + *result.Bsl) @@ -156,7 +156,7 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error var adminPasswordDecoded string err = json.Unmarshal([]byte(adminPassword), &adminPasswordDecoded) if err != nil{ - fmt.Errorf("failed to unmarshal password string: %v", err) + return fmt.Errorf("failed to unmarshal password string: %v", err) } log.Println("Bastion password: " + adminPasswordDecoded) From 6d8c75e3656dfe0c03cabb2e1041aff4fcef55ce Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Mon, 21 Oct 2024 22:08:51 +0200 Subject: [PATCH 12/12] autogen docs --- ...rsistence.create-bastion-shareable-link.md | 89 +++++++++++++++++++ docs/attack-techniques/azure/index.md | 5 ++ docs/attack-techniques/list.md | 1 + docs/index.yaml | 8 ++ 4 files changed, 103 insertions(+) create mode 100755 docs/attack-techniques/azure/azure.persistence.create-bastion-shareable-link.md diff --git a/docs/attack-techniques/azure/azure.persistence.create-bastion-shareable-link.md b/docs/attack-techniques/azure/azure.persistence.create-bastion-shareable-link.md new file mode 100755 index 00000000..81af9747 --- /dev/null +++ b/docs/attack-techniques/azure/azure.persistence.create-bastion-shareable-link.md @@ -0,0 +1,89 @@ +--- +title: Create Azure VM Bastion shareable link +--- + +# Create Azure VM Bastion shareable link + + slow + + +Platform: Azure + +## MITRE ATT&CK Tactics + + +- Persistence + +## Description + + +By utilizing the 'shareable link' feature on Bastions where it is enabled, an attacker can create a link to allow access to a virtual machine (VM) from untrusted networks. Public links generated for an Azure Bastion can allow VM network access to anyone with the generated URL. + +References: + +- https://blog.karims.cloud/2022/11/26/yet-another-azure-vm-persistence.html +- https://learn.microsoft.com/en-us/azure/bastion/shareable-link +- https://microsoft.github.io/Azure-Threat-Research-Matrix/Persistence/AZT509/AZT509/ + +Warm-up: + +- Create a VM and VNet +- Create an Azure Bastion host with access to the VM, and shareable links enabled + +NOTE: Warm-up and cleanup can each take 10-15 minutes to create and destroy the Azure Bastion instance + +Detonation: + +- Create an Azure Bastion shareable link with access to the VM + + +## Instructions + +```bash title="Detonate with Stratus Red Team" +stratus detonate azure.persistence.create-bastion-shareable-link +``` +## Detection + + +Identify Azure events of type Microsoft.Network/bastionHosts/createshareablelinks/action and Microsoft.Network/bastionHosts/getShareablelinks/action. A sample of createshareablelinks is shown below (redacted for clarity). + +```json hl_lines="7" +{ + { + "category": { + "value": "Administrative", + "localizedValue": "Administrative" + }, + "level": "Informational", + "operationName": { + "value": "Microsoft.Network/bastionHosts/createshareablelinks/action", + "localizedValue": "Creates shareable urls for the VMs under a bastion and returns the urls" + }, + "resourceGroupName": "stratus-red-team-shareable-link-rg-tz6o", + "resourceProviderName": { + "value": "Microsoft.Network", + "localizedValue": "Microsoft.Network" + }, + "resourceType": { + "value": "Microsoft.Network/bastionHosts", + "localizedValue": "Microsoft.Network/bastionHosts" + }, + "resourceId": "[removed]/resourceGroups/stratus-red-team-shareable-link-rg-tz6o/providers/Microsoft.Network/bastionHosts/stratus-red-team-shareable-link-bastion-tz6o", + "status": { + "value": "Succeeded", + "localizedValue": "Succeeded" + }, + "subStatus": { + "value": "", + "localizedValue": "" + }, + "properties": { + "eventCategory": "Administrative", + "entity": "[removed]/resourceGroups/stratus-red-team-shareable-link-rg-tz6o/providers/Microsoft.Network/bastionHosts/stratus-red-team-shareable-link-bastion-tz6o", + "message": "Microsoft.Network/bastionHosts/createshareablelinks/action", + "hierarchy": "[removed]" + }, +} +``` + + diff --git a/docs/attack-techniques/azure/index.md b/docs/attack-techniques/azure/index.md index f5b5789a..bcf637e4 100755 --- a/docs/attack-techniques/azure/index.md +++ b/docs/attack-techniques/azure/index.md @@ -15,3 +15,8 @@ Note that some Stratus attack techniques may correspond to more than a single AT - [Export Disk Through SAS URL](./azure.exfiltration.disk-export.md) + +## Persistence + +- [Create Azure VM Bastion shareable link](./azure.persistence.create-bastion-shareable-link.md) + diff --git a/docs/attack-techniques/list.md b/docs/attack-techniques/list.md index 91cc55b1..be75c57b 100755 --- a/docs/attack-techniques/list.md +++ b/docs/attack-techniques/list.md @@ -52,6 +52,7 @@ This page contains the list of all Stratus Attack Techniques. | [Execute Command on Virtual Machine using Custom Script Extension](./azure/azure.execution.vm-custom-script-extension.md) | [Azure](./azure/index.md) | Execution | | [Execute Commands on Virtual Machine using Run Command](./azure/azure.execution.vm-run-command.md) | [Azure](./azure/index.md) | Execution | | [Export Disk Through SAS URL](./azure/azure.exfiltration.disk-export.md) | [Azure](./azure/index.md) | Exfiltration | +| [Create Azure VM Bastion shareable link](./azure/azure.persistence.create-bastion-shareable-link.md) | [Azure](./azure/index.md) | Persistence | | [Create Admin EKS Access Entry](./EKS/eks.lateral-movement.create-access-entry.md) | [EKS](./EKS/index.md) | Lateral Movement | | [Backdoor aws-auth EKS ConfigMap](./EKS/eks.persistence.backdoor-aws-auth-configmap.md) | [EKS](./EKS/index.md) | Persistence, Privilege Escalation | | [Backdoor Entra ID application through service principal](./entra-id/entra-id.persistence.backdoor-application-sp.md) | [Entra ID](./entra-id/index.md) | Persistence, Privilege Escalation | diff --git a/docs/index.yaml b/docs/index.yaml index dc3d7c6b..e87c594a 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -472,6 +472,14 @@ Azure: - Exfiltration platform: Azure isIdempotent: true + Persistence: + - id: azure.persistence.create-bastion-shareable-link + name: Create Azure VM Bastion shareable link + isSlow: true + mitreAttackTactics: + - Persistence + platform: Azure + isIdempotent: false Entra ID: Persistence: - id: entra-id.persistence.backdoor-application-sp