diff --git a/docs/attack-techniques/azure/azure.create-bastion-shareable-link.md b/docs/attack-techniques/azure/azure.create-bastion-shareable-link.md
new file mode 100644
index 00000000..96b8ec77
--- /dev/null
+++ b/docs/attack-techniques/azure/azure.create-bastion-shareable-link.md
@@ -0,0 +1,84 @@
+---
+title: Access Virtual Machine using Bastion shareable link
+---
+
+# Access Virtual Machine using 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.
+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/
+
+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.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/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
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/persistence/create-bastion-shareable-link/main.go b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go
new file mode 100644
index 00000000..94b07ac7
--- /dev/null
+++ b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.go
@@ -0,0 +1,201 @@
+package azure
+
+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"
+ "log"
+)
+
+//go:embed main.tf
+var tf []byte
+
+func init() {
+ const codeBlock = "```"
+ stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{
+ ID: "azure.persistence.create-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.
+
+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.Persistence},
+ PrerequisitesTerraformCode: tf,
+ Detonate: detonate,
+ Revert: revert,
+ })
+}
+
+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"]
+ 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()
+ subscriptionID := providers.Azure().SubscriptionID
+ clientOptions := providers.Azure().ClientOptions
+
+ client, err := armnetwork.NewClientFactory(subscriptionID, cred, clientOptions)
+ if err != nil {
+ return fmt.Errorf("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: &vmId}}},
+ }, nil)
+ if err != nil {
+ return fmt.Errorf("failed to create shareable link: %v", err)
+ }
+
+ _, err = poller.PollUntilDone(ctx, nil)
+ if err != nil {
+ return fmt.Errorf("failed to poll results of shareable link request: %v", err)
+ }
+ log.Println("Shareable link created")
+
+ // 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 {
+ return fmt.Errorf("failed to get results page: %v", err)
+ }
+ for _, result := range page.Value {
+ log.Println("Bastion shareable link URL: " + *result.Bsl)
+ }
+ }
+
+ 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{
+ return fmt.Errorf("failed to unmarshal password string: %v", err)
+ }
+ log.Println("Bastion password: " + adminPasswordDecoded)
+
+ 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 {
+ return fmt.Errorf("failed to instantiate ARM Network 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: &vmId}},
+ }}, nil)
+ if err != nil {
+ return fmt.Errorf("failed to delete shareable bastion link: %v", err)
+ }
+ _, err = poller.PollUntilDone(ctx, nil)
+ if err != nil {
+ return fmt.Errorf("failed to poll results of deleting shareable bastion link: %v", err)
+ }
+
+ log.Println("Shareable link deleted")
+
+ 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
new file mode 100644
index 00000000..a1bb6076
--- /dev/null
+++ b/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link/main.tf
@@ -0,0 +1,180 @@
+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 "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
+}
+
+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..f2b1b98c 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/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"