Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Technique: Access Virtual Machine using Bastion shareable link #583

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: Access Virtual Machine using Bastion shareable link
---

# Access Virtual Machine using Bastion shareable link

<span class="smallcaps w3-badge w3-orange w3-round w3-text-sand" title="This attack technique is slow to warm up and cleanup">slow</span>


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/

<span style="font-variant: small-caps;">Warm-up</span>:

- 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

<span style="font-variant: small-caps;">Detonation</span>:

- 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 <code>Microsoft.Network/bastionHosts/createshareablelinks/action</code> and <code>Microsoft.Network/bastionHosts/getShareablelinks/action</code>. A sample of <code>createshareablelinks</code> 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]"
},
}
```
3 changes: 2 additions & 1 deletion v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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/resources/armresources v1.2.0
github.com/aws/aws-sdk-go-v2 v1.30.3
github.com/aws/aws-sdk-go-v2/config v1.25.11
github.com/aws/aws-sdk-go-v2/credentials v1.16.9
Expand Down Expand Up @@ -47,6 +47,7 @@ require (

require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.1.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/Microsoft/go-winio v0.5.0 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions v2/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ 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/network/armnetwork v1.0.0 h1:nBy98uKOIfun5z6wx6jwWLrULcM0+cjBalBFZlEZ7CA=
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.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/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=
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
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/resourcemanager/network/armnetwork/v6"
"fmt"
)

//go:embed main.tf
var tf []byte

func init() {
const codeBlock = "```"
stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{
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.

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 <code>Microsoft.Network/bastionHosts/createshareablelinks/action</code> and <code>Microsoft.Network/bastionHosts/getShareablelinks/action</code>. A sample of <code>createshareablelinks</code> 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"]
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 {
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")

// 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)

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 {
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
}
Loading
Loading