-
Notifications
You must be signed in to change notification settings - Fork 207
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
base: main
Are you sure you want to change the base?
Changes from 6 commits
d4a2de2
4146020
f35d068
cda9187
b5b7503
37dc8ac
6268f5f
33f611a
7fc6830
50babaa
cf4d25c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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]" | ||
}, | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,182 @@ | ||||||
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.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. | ||||||
|
||||||
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 { | ||||||
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), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
should be enough? |
||||||
}, | ||||||
}, | ||||||
}, | ||||||
}, nil) | ||||||
if err != nil { | ||||||
log.Fatalf("failed to create shareable link: %v", err) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we generally try to return errors here (i.e. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
_, err = poller.PollUntilDone(ctx, nil) | ||||||
if err != nil { | ||||||
log.Fatalf("failed to poll results: %v", err) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
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") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sounds like Sprintf would be a bit cleaner to build this with |
||||||
|
||||||
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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
_, err = poller.PollUntilDone(ctx, nil) | ||||||
if err != nil { | ||||||
log.Fatalf("failed to pull the result: %v", err) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
log.Println("Shareable link deleted") | ||||||
|
||||||
return nil | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
something more action oriented like the below might work better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping "shareable" if we can as this is the feature name, but can use "sharing" if we're up against a character limit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah makes sense, so wdyt of
azure.persistence.create-bastion-shareable-link
?