Compare commits

...
Author SHA1 Message Date
David db7091f33b Merge branch 'main' into general-improvements_azure-commands 2024-01-30 11:48:37 -05:00
Bastien Faure 465f08b530 Azure netscan: reduce list of network targets by filtering running vms, included in target subnets 2023-11-08 16:22:53 -08:00
Bastien Faure 8da636e294 Detach a few VM functions from VMs module and make them shared 2023-11-08 16:20:33 -08:00
Bastien Faure 6c7c26a53f Properly display ASG in NSG rules 2023-11-08 16:19:44 -08:00
Bastien Faure 2086a72275 Azure netscan: write scan file into loot directory 2023-11-05 22:32:17 -08:00
Bastien Faure ab6806d641 Added draft Azure netscan command 2023-11-05 22:13:34 -08:00
Bastien Faure 3e3608f774 Merge branch 'general-improvements_azure-commands' of ssh://bf.github.com/BishopFox/cloudfox into general-improvements_azure-commands 2023-11-01 10:27:39 -07:00
Bastien Faure 7941a2c712 Missing panic call in the logger 2023-11-01 10:25:33 -07:00
Bastien Faure 67feb23ad2 Allowed 'expand' param to get passed to NIC getter 2023-11-01 10:24:50 -07:00
Bastien Faure 14bcd43158 Address potential nil pointer deref, wrong module name in NSG rules 2023-11-01 10:24:07 -07:00
Bastien Faure 0d3acc1006 Codespell fix 2023-10-31 21:43:48 -07:00
Bastien Faure f371bc7021 Removed legacy Azure CLI flags and introduced repeatable ones 2023-10-31 19:03:14 -07:00
Bastien Faure 272bc0ad11 Removed additional legacy code from the Azure Storage module 2023-10-31 18:57:34 -07:00
Bastien Faure 3cf38defdd Fixed wrong output directory for Azure whoami module 2023-10-31 18:44:59 -07:00
Bastien Faure 6725aaaf45 Update README with new Azure commands 2023-10-31 18:38:47 -07:00
Bastien Faure b0fe4a290f Migrated all Azure modules to new logging system, disabled legacy subscription and tenant iteration mechanisms 2023-10-31 17:59:02 -07:00
Bastien Faure 20881183d1 Migrated Azure NSG modules to new logger and eliminated legacy tenant and subscription ID manipulation 2023-10-31 17:15:21 -07:00
Bastien Faure 8d989ccaf9 Migrature Azure inventory module to new logger and eliminated legacy tenant and subscription ID manipulation 2023-10-31 16:57:35 -07:00
Bastien Faure a91e0a891e Migrated Azure storage module to use the new logger, as well as the Azure root module 2023-10-31 16:38:05 -07:00
Bastien Faure 386f3cb059 Implemented custom logger usable by any module and cloud provider 2023-10-31 16:37:22 -07:00
Bastien Faure effb6a490e Moved version information to a file and leverage go:embed 2023-10-31 16:36:56 -07:00
Bastien Faure f6f78efd42 Moved version information to its own file 2023-10-30 22:47:29 -07:00
Bastien Faure 924fd3d89e Merge remote-tracking branch 'origin/main' into general-improvements_azure-commands 2023-10-30 21:17:05 -07:00
Bastien Faure 60dbe425a5 Cosmetic fixes in the NSG modules 2023-10-30 21:16:36 -07:00
Bastien Faure bffa980d22 Added the two new Azure nsg-links and nsg-rules commands, added a map[string][]subscription populated prior to run any module 2023-10-30 21:01:08 -07:00
Bastien Faure 6665a9a5f0 Migrated the Azure RBAC command to a fully modular structure 2023-10-30 16:00:18 -07:00
Bastien Faure 50445263d7 Migrated the Azure storage command to a fully modular structure, implemented RG filtering 2023-10-30 15:36:40 -07:00
Bastien Faure ec422dcc6c Migrated the Azure VMs command to a fully modular structure, implemented RG filtering 2023-10-30 15:12:24 -07:00
Bastien Faure 416c93fdf1 Skip PreRun when running Azure Whoami module, but better 2023-10-30 13:34:38 -07:00
Bastien Faure 9bd96cfb66 Skip PreRun when running Azure Whoami module 2023-10-30 13:16:44 -07:00
Bastien Faure b38bf98072 Migrated the Azure Inventory command to a fully modular structure, implemented RG filtering 2023-10-30 12:49:21 -07:00
Bastien Faure 7e209883fb Fix wrong reference to resource ID in Azure RBAC and VMs modules 2023-10-30 12:25:18 -07:00
Bastien Faure 991decf2d9 Code cleanup in Azure modules 2023-10-29 02:08:20 -07:00
Bastien Faure 14132b3958 Migrated Azure storage command to new format 2023-10-29 02:04:47 -07:00
Bastien Faure 325b6ce892 Migrated Azure NSG commands to new format 2023-10-29 02:00:12 -07:00
Bastien Faure b7cca44509 Migrated Azure RBAC subcommand to new format 2023-10-29 01:50:13 -07:00
Bastien Faure c2100cd927 Migrated Azure VMs command to new format and fixed tenant reference bug in Inventory 2023-10-29 01:40:04 -07:00
Bastien Faure 71a60a6e67 Migrated Azure Inventory command to new format 2023-10-29 01:16:42 -07:00
Bastien Faure 04db8765a9 Implemented AzureClient class that autopopulates for all subcommands, moved some code to internal package 2023-10-29 01:12:29 -07:00
Bastien Faure e5de75bd07 Started to prepare new resource selection logic for the Azure subcommand 2023-10-29 00:01:24 -07:00
Bastien Faure 5b5a69a605 Migrated AWS CLI structure into Azure CLI 2023-10-28 18:17:27 -07:00
Bastien Faure 783524ba9c Added skeleton for Azure nsg-rules command 2023-10-28 17:45:00 -07:00
Bastien Faure 7410dab4f6 Added Azure nsg-links command to dump NSG attachments 2023-10-28 17:43:11 -07:00
21 changed files with 1555 additions and 404 deletions

No files matched your search

+5 -2
View File
@@ -48,8 +48,8 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
| Provider| CloudFox Commands |
| - | - |
| AWS | 33 |
| Azure | 4 |
| AWS | 30 |
| Azure | 6 |
| GCP | Support Planned |
| Kubernetes | Support Planned |
@@ -149,11 +149,14 @@ Additional policy notes (as of 09/2022):
| Azure | [inventory](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#inventory) | Display an inventory table of all resources per location. |
| Azure | [rbac](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#rbac) | Lists Azure RBAC role assignments at subscription or tenant level |
| Azure | [storage](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#storage) | The storage command is still under development. Currently it only displays limited data about the storage accounts |
| Azure | [nsg-links](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#nsg-links) | Lists subnets and network interfaces attached to Network Security Groups |
| Azure | [nsg-rules](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#nsg-rules) | Lists rules in Network Security Groups |
| Azure | [vms](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#vms) | Enumerates useful information for Compute instances in all available resource groups and subscriptions |
# Authors
* [Carlos Vendramini](https://github.com/carlosvendramini-bf)
* [Seth Art (@sethsec](https://twitter.com/sethsec))
* [Bastien Faure](https://github.com/BastienFaure)
# Contributing
[Wiki - How to Contribute](https://github.com/BishopFox/cloudfox/wiki#how-to-contribute)
+78 -74
View File
@@ -7,107 +7,101 @@ import (
"sort"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
"github.com/BishopFox/cloudfox/globals"
"github.com/BishopFox/cloudfox/internal"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/aws/smithy-go/ptr"
"github.com/fatih/color"
"github.com/kyokomi/emoji"
)
func AzInventoryCommand(AzTenantID, AzSubscriptionID, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
type AzInventoryModule struct {
AzClient *internal.AzureClient
Log *internal.Logger
}
func (m *AzInventoryModule) AzInventoryCommand() error {
o := internal.OutputClient{
Verbosity: AzVerbosity,
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
Verbosity: m.AzClient.AzVerbosity,
CallingModule: "inventory",
Table: internal.TableClient{
Wrap: AzWrapTable,
Wrap: m.AzClient.AzWrapTable,
},
}
if AzTenantID != "" && AzSubscriptionID == "" {
if len(m.AzClient.AzTenants) > 0 {
// cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN]
tenantInfo := populateTenant(AzTenantID)
for _, AzTenant := range m.AzClient.AzTenants {
if AzMergedTable {
// set up table vars
var header []string
var body [][]string
if m.AzClient.AzMergedTable {
// set up table vars
var header []string
var body [][]string
o := internal.OutputClient{
Verbosity: AzVerbosity,
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
Table: internal.TableClient{
Wrap: AzWrapTable,
},
o := internal.OutputClient{
Verbosity: m.AzClient.AzVerbosity,
CallingModule: "inventory",
Table: internal.TableClient{
Wrap: m.AzClient.AzWrapTable,
},
}
m.Log.Infof(nil, "Gathering inventory for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))
o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain)
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level")
//populate the table data
header, body, err := m.getInventoryInfoPerTenant(ptr.ToString(AzTenant.TenantID))
if err != nil {
return err
}
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: fmt.Sprintf(o.CallingModule)})
if body != nil {
o.WriteFullOutput(o.Table.TableFiles, nil)
}
} else {
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
m.runInventoryCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription)
}
}
fmt.Printf(
"[%s][%s] Gathering inventory for subscription %s\n",
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule),
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
//populate the table data
header, body, err := getInventoryInfoPerTenant(ptr.ToString(tenantInfo.ID))
if err != nil {
return err
}
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: fmt.Sprintf(o.CallingModule)})
if body != nil {
o.WriteFullOutput(o.Table.TableFiles, nil)
}
} else {
for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) {
runInventoryCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
}
} else {
// ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
for _, AzSubscription := range AzSubscriptions {
m.runInventoryCommandForSingleSubscription(tenantSlug, AzSubscription)
}
}
} else if AzTenantID == "" && AzSubscriptionID != "" {
// ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
runInventoryCommandForSingleSubscription(AzSubscriptionID, AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
} else {
// Error: please make a valid flag selection
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
}
o.WriteFullOutput(o.Table.TableFiles, nil)
return nil
}
func runInventoryCommandForSingleSubscription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error {
func (m *AzInventoryModule) runInventoryCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
// set up table vars
var header []string
var body [][]string
var err error
o := internal.OutputClient{
Verbosity: AzVerbosity,
Verbosity: m.AzClient.AzVerbosity,
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
Table: internal.TableClient{
Wrap: AzWrapTable,
Wrap: m.AzClient.AzWrapTable,
},
}
var AzSubscriptionInfo SubsriptionInfo
tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription))
tenantInfo := populateTenant(tenantID)
AzSubscriptionInfo = PopulateSubsriptionType(AzSubscription)
o.PrefixIdentifier = AzSubscriptionInfo.Name
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
o.PrefixIdentifier = *AzSubscription.DisplayName
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName)
fmt.Printf(
"[%s][%s] Gathering inventory for subscription %s\n",
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule),
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
m.Log.Infof(nil, "Gathering inventory for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)
// populate the table data
header, body, err = getInventoryInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID)
header, body, err = m.getInventoryInfoPerSubscription(tenantSlug, AzSubscription)
if err != nil {
return err
}
@@ -126,8 +120,8 @@ func runInventoryCommandForSingleSubscription(AzSubscription string, AzOutputDir
return nil
}
func getInventoryInfoPerSubscription(tenantID, subscriptionID string) ([]string, [][]string, error) {
resources, err := getResources(tenantID, subscriptionID)
func (m *AzInventoryModule) getInventoryInfoPerSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) ([]string, [][]string, error) {
resources, err := m.getResources(*AzSubscription.TenantID, *AzSubscription.SubscriptionID)
if err != nil {
return nil, nil, err
}
@@ -139,14 +133,24 @@ func getInventoryInfoPerSubscription(tenantID, subscriptionID string) ([]string,
for _, resource := range resources {
resourceType := ptr.ToString(resource.Type)
resourceLocation := ptr.ToString(resource.Location)
_, ok := inventory[resourceType]
if len(m.AzClient.AzRGs) > 0 {
for _, AzRG := range m.AzClient.AzRGs {
metaResource, _ := azure.ParseResourceID(*resource.ID)
if metaResource.ResourceGroup == *AzRG.Name {
goto ADD_RESOURCE
}
}
goto SKIP_RESOURCE
}
ADD_RESOURCE:
if !ok {
inventory[resourceType] = make(map[string]int)
}
inventory[resourceType][resourceLocation]++
resourceTypes[resourceType] = true
resourceLocations[resourceLocation] = true
SKIP_RESOURCE:
}
header := []string{"Resource Type"}
@@ -173,14 +177,14 @@ func getInventoryInfoPerSubscription(tenantID, subscriptionID string) ([]string,
return header, body, nil
}
func getInventoryInfoPerTenant(tenantID string) ([]string, [][]string, error) {
func (m *AzInventoryModule) getInventoryInfoPerTenant(tenantID string) ([]string, [][]string, error) {
inventory := make(map[string]map[string]int)
resourceTypes := make(map[string]bool)
resourceLocations := make(map[string]bool)
for _, s := range GetSubscriptionsPerTenantID(tenantID) {
resources, err := getResources(tenantID, ptr.ToString(s.SubscriptionID))
resources, err := m.getResources(tenantID, ptr.ToString(s.SubscriptionID))
if err != nil {
return nil, nil, err
}
@@ -223,7 +227,7 @@ func getInventoryInfoPerTenant(tenantID string) ([]string, [][]string, error) {
return header, body, nil
}
func getResources(tenantID, subscriptionID string) ([]*armresources.GenericResourceExpanded, error) {
func (m *AzInventoryModule) getResources(tenantID, subscriptionID string) ([]*armresources.GenericResourceExpanded, error) {
client := internal.GetARMresourcesClient(tenantID, subscriptionID)
var resources []*armresources.GenericResourceExpanded
+248
View File
@@ -0,0 +1,248 @@
package azure
// https://learn.microsoft.com/en-us/azure/virtual-network/network-security-group-how-it-works
// https://learn.microsoft.com/en-us/azure/virtual-network/application-security-groups
// https://learn.microsoft.com/en-us/azure/network-watcher/diagnose-vm-network-traffic-filtering-problem?toc=%2Fazure%2Fvirtual-network%2Ftoc.json
import (
"github.com/BishopFox/cloudfox/internal"
"github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
"github.com/Azure/go-autorest/autorest/azure"
"strings"
"github.com/aws/smithy-go/ptr"
"context"
"path/filepath"
"fmt"
"net"
"os"
)
type AzNetScanModule struct {
AzClient *internal.AzureClient
Log *internal.Logger
}
func (m *AzNetScanModule) AzNetScanCommand(sourceIP string) error {
o := internal.OutputClient{
Verbosity: m.AzClient.AzVerbosity,
CallingModule: "netscan",
Table: internal.TableClient{
Wrap: m.AzClient.AzWrapTable,
},
}
if len(m.AzClient.AzResources) == 0 && sourceIP == "" {
return fmt.Errorf("No resource ID or source IP address supplied")
}
if sourceIP != "" {
m.Log.Warnf(nil, "Searching resources attached to IP %s, this might take a while", sourceIP)
} else {
for _, AzResource := range m.AzClient.AzResources {
if AzResource.ResourceType == "virtualMachines" {
m.Log.Infof(nil, "Starting network analysis for Virtual Machine %s", AzResource.ResourceName)
m.processVM(AzResource.SubscriptionID, AzResource.ResourceGroup, AzResource.ResourceName)
} else {
m.Log.Errorf(nil, "Resource type %s / %s not implemented", AzResource.Provider, AzResource.ResourceType)
continue
}
}
}
o.WriteFullOutput(o.Table.TableFiles, nil)
return nil
}
func (m *AzNetScanModule) processVM(subscriptionID string, resourceGroupName string, VMName string) {
computeClient := internal.GetVirtualMachinesClient(subscriptionID)
vm, err := computeClient.Get(context.TODO(), resourceGroupName, VMName, "")
targetSubnets := []string{}
targetSubnetsAlt := make(map[string][]string)
vmSubscriptionIDs := make(map[string]bool)
vmSubscriptionIDs[subscriptionID] = true
if err != nil {
m.Log.Errorf([]string{VMName}, "Could not fetch virtual machine details")
return
} else {
if vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces != nil {
for _, nicReference := range *vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces {
nic, err := getNICdetails(subscriptionID, resourceGroupName, nicReference, "networkSecurityGroup")
if err != nil {
m.Log.Warnf([]string{VMName}, "Could not get details of network interface %s", *nicReference.ID)
continue
}
if nic.InterfacePropertiesFormat.IPConfigurations == nil {
m.Log.Warn([]string{VMName, *nic.Name}, "Interface has no IP address configured")
continue
}
if nic.InterfacePropertiesFormat.NetworkSecurityGroup != nil {
m.Log.Infof([]string{VMName, *nic.Name}, "Linked to NSG %s", *nic.InterfacePropertiesFormat.NetworkSecurityGroup.Name)
}
//var subnet *network.Subnet
for _, ip := range *nic.InterfacePropertiesFormat.IPConfigurations {
if ip.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddress == nil {
m.Log.Warn([]string{VMName, *nic.Name}, "Empty IP configuration")
continue
}
ipConfig := ip.InterfaceIPConfigurationPropertiesFormat
m.Log.Infof([]string{VMName, *nic.Name}, "Interface assigned with address %s", *ipConfig.PrivateIPAddress)
subnet := *ipConfig.Subnet
subnetRGName := strings.Split(*subnet.ID, "/")[4]
VNETName := strings.Split(*subnet.ID, "/")[8]
subnetName := strings.Split(*subnet.ID, "/")[10]
subnet, err := getSubnetDetails(subscriptionID, subnetRGName, VNETName, subnetName, "networkSecurityGroup")
if err != nil {
m.Log.Warnf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Unable to get subnet %s details", subnetName)
continue
}
if subnet.SubnetPropertiesFormat.NetworkSecurityGroup != nil {
m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Linked to NSG %s through subnet %s", *subnet.SubnetPropertiesFormat.NetworkSecurityGroup.Name, *subnet.Name)
}
m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Subnet %s, in Virtual Network %s", subnetName, VNETName)
vnet, err := getVNET(subscriptionID, subnetRGName, VNETName, "")
if err != nil {
m.Log.Warnf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress, *subnet.Name}, "Unable to get Virtual Network %s details", VNETName)
continue
}
m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Virtual Network %s has %s subnets as potential targets", VNETName, len(*vnet.VirtualNetworkPropertiesFormat.Subnets))
for _, subnet := range *vnet.VirtualNetworkPropertiesFormat.Subnets {
m.Log.Infof([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress}, "Potential target : %s", *subnet.SubnetPropertiesFormat.AddressPrefix)
targetSubnets = append(targetSubnets, *subnet.SubnetPropertiesFormat.AddressPrefix)
targetSubnetsAlt[*subnet.SubnetPropertiesFormat.AddressPrefix] = []string{}
}
for _, peering := range *vnet.VirtualNetworkPropertiesFormat.VirtualNetworkPeerings {
remoteVnetID := *peering.VirtualNetworkPeeringPropertiesFormat.RemoteVirtualNetwork.ID
remoteVnetResource, _ := azure.ParseResourceID(remoteVnetID)
vmSubscriptionIDs[remoteVnetResource.SubscriptionID] = true
if !*peering.VirtualNetworkPeeringPropertiesFormat.AllowForwardedTraffic {
m.Log.Warnf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress, *subnet.Name}, "Peering to Virtual Network %s is disabled", remoteVnetResource.ResourceName)
} else {
m.Log.Successf([]string{VMName, *nic.Name, *ipConfig.PrivateIPAddress, *subnet.Name}, "Peering to Virtual Network %s is active : %s",
remoteVnetResource.ResourceName, stringAndArrayToString(nil, peering.VirtualNetworkPeeringPropertiesFormat.RemoteAddressSpace.AddressPrefixes, " "))
for _, addressPrefix := range *peering.VirtualNetworkPeeringPropertiesFormat.RemoteAddressSpace.AddressPrefixes {
targetSubnets = append(targetSubnets, addressPrefix)
targetSubnetsAlt[addressPrefix] = []string{}
}
}
}
}
}
m.Log.Info([]string{VMName}, "Narrowing down potential target subnets with running compute instances...")
var vms []compute.VirtualMachine
for subscriptionID, _ := range vmSubscriptionIDs {
subscriptionVMs, err := getComputeVMsPerSubscription(subscriptionID)
if err != nil {
m.Log.Warnf([]string{VMName}, "Unable to enumerate VMs in subscription %s", subscriptionID)
continue
}
vms = append(vms, subscriptionVMs...)
}
for i, vm := range vms {
fmt.Printf("Processing machine %d%%\r", 100*i/len(vms))
vmResource, _ := azure.ParseResourceID(*vm.ID)
vmDetails, err := getComputeVmInstanceView(subscriptionID, vmResource.ResourceGroup, ptr.ToString(vm.Name))
if err != nil {
m.Log.Warnf([]string{VMName}, "Unable to fetch VM %s details", ptr.ToString(vm.Name))
continue
}
if vmDetails.VirtualMachineProperties != nil && vmDetails.VirtualMachineProperties.InstanceView != nil {
for _, status := range *vmDetails.VirtualMachineProperties.InstanceView.Statuses {
if *status.Code == "PowerState/running" {
privateIPs, _ := getIPs(subscriptionID, vmResource.ResourceGroup, vm)
for targetSubnet, _ := range targetSubnetsAlt {
_, ipnet, _ := net.ParseCIDR(targetSubnet)
for _, privateIP := range privateIPs {
ip := net.ParseIP(privateIP)
if ipnet.Contains(ip) {
targetSubnetsAlt[targetSubnet] = append(targetSubnetsAlt[targetSubnet], privateIP)
}
}
}
break
}
}
}
}
for targetSubnet, targets := range targetSubnetsAlt {
m.Log.Infof([]string{VMName}, "%s subnet has %s addresses", targetSubnet, len(targets))
}
m.writeScanFile(VMName, targetSubnetsAlt)
} else {
m.Log.Errorf(nil, "Virtual Machine %s has no network interface", VMName)
return
}
}
}
func (m *AzNetScanModule) filterVM(vm compute.VirtualMachine) {
}
func getComputeVMsPerSubscription(subscriptionID string) ([]compute.VirtualMachine, error) {
computeClient := internal.GetVirtualMachinesClient(subscriptionID)
var vms []compute.VirtualMachine
for _, rg := range GetResourceGroups(subscriptionID) {
for page, err := computeClient.List(context.TODO(), ptr.ToString(rg.Name), ""); page.NotDone(); page.Next() {
if err != nil {
return nil, fmt.Errorf("could not enumerate resource group %s. %s", rg, err)
} else {
vms = append(vms, page.Values()...)
}
}
}
return vms, nil
}
func (m *AzNetScanModule) writeScanFile(vm string, targetSubnetsAlt map[string][]string) error {
lootDirectory := filepath.Join(m.AzClient.AzOutputDirectory, "loot")
lootFilePath := filepath.Join(lootDirectory, fmt.Sprintf("scan-from-%s.txt", vm))
err := os.MkdirAll(lootDirectory, os.ModePerm)
if err != nil {
return err
}
file, err := os.Create(lootFilePath)
if err != nil {
return err
}
defer file.Close()
for targetSubnet, targets := range targetSubnetsAlt {
cleanSubnet := strings.Replace(targetSubnet, "/", "_", -1)
_, err := file.WriteString(fmt.Sprintf("nmap -p- --max-retries 1 -T4 -Pn -sV -sC -oA %s -iL %s.lst\n", cleanSubnet, cleanSubnet))
if err != nil {
return err
}
targetsFile, err := os.Create(filepath.Join(lootDirectory, fmt.Sprintf("%s.lst", cleanSubnet)))
if err != nil {
return err
}
defer targetsFile.Close()
for _, target := range targets {
targetsFile.WriteString(fmt.Sprintf("%s\n", target))
}
}
m.Log.Successf(nil, "Scan file written to %s", lootFilePath)
return nil
}
func getSubnetDetails(subscriptionID, resourceGroup, VNETName, subnetName, expand string) (network.Subnet, error) {
client := internal.GetSubnetsClient(subscriptionID)
subnet, err := client.Get(context.TODO(), resourceGroup, VNETName, subnetName, expand)
if err != nil {
return network.Subnet{}, err
}
return subnet, nil
}
func getVNET(subscriptionID, resourceGroup, VNETName, expand string) (network.VirtualNetwork, error) {
client := internal.GetVNETsClient(subscriptionID)
subnet, err := client.Get(context.TODO(), resourceGroup, VNETName, expand)
if err != nil {
return network.VirtualNetwork{}, err
}
return subnet, nil
}
+51
View File
@@ -0,0 +1,51 @@
package azure
import (
"fmt"
"context"
"github.com/BishopFox/cloudfox/internal"
"github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network"
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
)
type AzNSGModule struct {
AzClient *internal.AzureClient
getNSGData func(string, *subscriptions.Subscription) error
Log *internal.Logger
}
func (m *AzNSGModule) AzNSGCommand(data string) error {
if data == "links" {
m.AzNSGLinksCommand()
} else if data == "rules" {
m.AzNSGRulesCommand()
} else {
return fmt.Errorf("Invalid data selection for NSG")
}
return nil
}
func (m *AzNSGModule) getNSGInfoPerSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
var err error
err = m.getNSGData(tenantSlug, AzSubscription)
if err != nil {
return err
}
return nil
}
func (m *AzNSGModule) getNSG(subscriptionID string) (*[]network.SecurityGroup, error) {
nsgClient := internal.GetNSGClient(subscriptionID)
var networkSecurityGroups []network.SecurityGroup
for page, err := nsgClient.ListAll(context.TODO()); page.NotDone(); page.Next() {
if err != nil {
return nil, fmt.Errorf("could not get network security groups for subscription")
}
networkSecurityGroups = append(networkSecurityGroups, page.Values()...)
}
return &networkSecurityGroups, nil
}
+175
View File
@@ -0,0 +1,175 @@
package azure
import (
"context"
"strings"
"fmt"
"path/filepath"
"github.com/BishopFox/cloudfox/globals"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/smithy-go/ptr"
"github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network"
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
"github.com/Azure/go-autorest/autorest/azure"
)
func (m *AzNSGModule) AzNSGLinksCommand() error {
m.getNSGData = m.getNSGLinksData
if len(m.AzClient.AzTenants) > 0 {
for _, AzTenant := range m.AzClient.AzTenants {
m.Log.Infof([]string{"links"}, "Enumerating Network Security Group links for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))
for _, AzTenant := range m.AzClient.AzTenants {
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
m.runNSGLinksCommandForSingleSubcription(*AzTenant.DefaultDomain, &AzSubscription)
}
}
}
} else {
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
for _, AzSubscription := range AzSubscriptions {
m.runNSGLinksCommandForSingleSubcription(tenantSlug, AzSubscription)
}
}
}
return nil
}
func (m *AzNSGModule) runNSGLinksCommandForSingleSubcription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
var err error
m.Log.Infof([]string{"links"}, "Enumerating Network Security Groups links for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)
err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription)
if err != nil {
return err
}
return nil
}
func (m *AzNSGModule) getNSGLinksData(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
tableHeader := []string{"Network Security Group", "Link Type", "Linked Name", "Link Target"}
var tableBody [][]string
networkSecurityGroups, err := m.getNSG(*AzSubscription.SubscriptionID)
nsgClient := internal.GetNSGClient(*AzSubscription.SubscriptionID)
//subnetsClient := internal.GetSubnetsClient(subscriptionID)
if err != nil {
return err
}
for _, networkSecurityGroup := range *networkSecurityGroups {
tableBody = nil
// setup logging client
o := internal.OutputClient{
Verbosity: m.AzClient.AzVerbosity,
CallingModule: globals.AZ_NSG_LINKS_MODULE_NAME,
Table: internal.TableClient{
Wrap: m.AzClient.AzWrapTable,
},
}
var resource azure.Resource
resource, err = azure.ParseResourceID(*networkSecurityGroup.ID)
if err != nil {
continue
}
networkSecurityGroup, err = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "Subnets,NetworkInterfaces")
if err != nil {
m.Log.Warnf([]string{"links"}, "Failed to enumerate links for NSG %s", *networkSecurityGroup.Name)
continue
} else {
m.Log.Infof([]string{"links"}, "Enumerating rules for NSG %s", *networkSecurityGroup.Name)
}
if networkSecurityGroup.Subnets != nil {
for _, subnet := range *networkSecurityGroup.Subnets {
var addressPrefixes []string
if subnet.AddressPrefixes != nil {
for _, prefix := range *subnet.AddressPrefixes {
addressPrefixes = append(addressPrefixes, prefix)
}
}
if subnet.AddressPrefix != nil {
addressPrefixes = append(addressPrefixes, *subnet.AddressPrefix)
}
tableBody = append(tableBody,
[]string{
*networkSecurityGroup.Name,
"Subnet",
ptr.ToString(subnet.Name),
strings.Join(addressPrefixes[:], "\n"),
},
)
}
}
if networkSecurityGroup.NetworkInterfaces != nil {
for _, networkInterface := range *networkSecurityGroup.NetworkInterfaces {
tableBody = append(tableBody,
[]string{
*networkSecurityGroup.Name,
"NIC",
ptr.ToString(networkInterface.Name),
strings.Join(internal.GetIPAddressesFromInterface(&networkInterface)[:], "\n"),
},
)
}
}
// set up table vars
o.PrefixIdentifier = fmt.Sprintf("%s", *AzSubscription.DisplayName)
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName)
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: tableHeader,
Body: tableBody,
Name: fmt.Sprintf("%s-%s", globals.AZ_NSG_LINKS_MODULE_NAME, *networkSecurityGroup.Name)})
if tableBody != nil {
o.WriteFullOutput(o.Table.TableFiles, nil)
}
}
return nil
}
func stringAndArrayToString(value *string, values *[]string, separator string) string {
var final string
if value != nil {
final = *value
} else {
final = ""
}
if len(*values) > 0 {
if len(final) > 0 {
final = fmt.Sprintf("%s%s%s", final, separator, strings.Join((*values)[:], separator))
} else {
final = strings.Join((*values)[:], separator)
}
}
return final
}
func getSourceFromSecurityGroupRule(rule *network.SecurityRule) string {
var final string
final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.SourceAddressPrefix, rule.SecurityRulePropertiesFormat.SourceAddressPrefixes, "\n")
if rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups != nil {
for _, app := range *rule.SecurityRulePropertiesFormat.SourceApplicationSecurityGroups {
resource, _ := azure.ParseResourceID(ptr.ToString(app.ID))
final = fmt.Sprintf("%s\n%s", final, resource.ResourceName)
}
}
return final
}
func getDestinationFromSecurityGroupRule(rule *network.SecurityRule) string {
var final string
final = stringAndArrayToString(rule.SecurityRulePropertiesFormat.DestinationAddressPrefix, rule.SecurityRulePropertiesFormat.DestinationAddressPrefixes, "\n")
if rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups != nil {
for _, app := range *rule.SecurityRulePropertiesFormat.DestinationApplicationSecurityGroups {
resource, _ := azure.ParseResourceID(ptr.ToString(app.ID))
final = fmt.Sprintf("%s\n%s", final, resource.ResourceName)
}
}
return final
}
+125
View File
@@ -0,0 +1,125 @@
package azure
import (
"fmt"
"path/filepath"
"context"
"github.com/BishopFox/cloudfox/globals"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/smithy-go/ptr"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
)
func (m *AzNSGModule) AzNSGRulesCommand() error {
m.getNSGData = m.getNSGRulesData
if len(m.AzClient.AzTenants) > 0 {
for _, AzTenant := range m.AzClient.AzTenants {
m.Log.Infof([]string{"rules"}, "Enumerating Network Security Group rules for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))
for _, AzTenant := range m.AzClient.AzTenants {
// cloudfox azure nsg-rules --tenant [TENANT_ID | PRIMARY_DOMAIN]
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
m.runNSGRulesCommandForSingleSubcription(*AzTenant.DefaultDomain, &AzSubscription)
}
}
}
} else {
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
for _, AzSubscription := range AzSubscriptions {
m.runNSGRulesCommandForSingleSubcription(tenantSlug, AzSubscription)
}
}
}
return nil
}
func (m *AzNSGModule) runNSGRulesCommandForSingleSubcription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
var err error
m.Log.Infof([]string{"rules"}, "Enumerating Network Security Groups rules for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)
err = m.getNSGInfoPerSubscription(tenantSlug, AzSubscription)
if err != nil {
return err
}
return nil
}
func (m *AzNSGModule) getNSGRulesData(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
tableHeader := []string{"Name", "Rule Type", "Protocol", "Source", "Destination", "Destination port", "Action", "Description"}
var tableBody [][]string
networkSecurityGroups, err := m.getNSG(*AzSubscription.SubscriptionID)
nsgClient := internal.GetNSGClient(*AzSubscription.SubscriptionID)
//subnetsClient := internal.GetSubnetsClient(subscriptionID)
if err != nil {
return err
}
for _, networkSecurityGroup := range *networkSecurityGroups {
tableBody = nil
// setup logging client
o := internal.OutputClient{
Verbosity: m.AzClient.AzVerbosity,
CallingModule: globals.AZ_NSG_RULES_MODULE_NAME,
Table: internal.TableClient{
Wrap: m.AzClient.AzWrapTable,
},
}
var resource azure.Resource
resource, err = azure.ParseResourceID(*networkSecurityGroup.ID)
if err != nil {
continue
}
networkSecurityGroup, err = nsgClient.Get(context.TODO(), resource.ResourceGroup, resource.ResourceName, "")
if err != nil {
m.Log.Warnf([]string{"rules"}, "Failed to enumerate rules for NSG %s", *networkSecurityGroup.Name)
continue
} else {
m.Log.Infof([]string{"rules"}, "Enumerating rules for NSG %s", *networkSecurityGroup.Name)
}
for _, securityRule := range *networkSecurityGroup.SecurityRules {
tableBody = append(tableBody,
[]string{
*securityRule.Name,
fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Direction),
fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Protocol),
getSourceFromSecurityGroupRule(&securityRule),
getDestinationFromSecurityGroupRule(&securityRule),
stringAndArrayToString(securityRule.SecurityRulePropertiesFormat.DestinationPortRange,
securityRule.SecurityRulePropertiesFormat.DestinationPortRanges, "\n"),
m.colorRule(fmt.Sprintf("%v", securityRule.SecurityRulePropertiesFormat.Access)),
ptr.ToString(securityRule.Description),
},
)
}
// set up table vars
o.PrefixIdentifier = fmt.Sprintf("%s", *AzSubscription.DisplayName)
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName)
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: tableHeader,
Body: tableBody,
Name: fmt.Sprintf("%s-%s", globals.AZ_NSG_RULES_MODULE_NAME, *networkSecurityGroup.Name)})
if tableBody != nil {
o.WriteFullOutput(o.Table.TableFiles, nil)
}
}
return nil
}
func (m *AzNSGModule) colorRule(action string) string {
if action == "Allow" {
return m.Log.Green(action)
} else if action == "Deny" {
return m.Log.Red(action)
} else {
return action
}
}
+74 -69
View File
@@ -16,82 +16,80 @@ import (
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/smithy-go/ptr"
"github.com/fatih/color"
"github.com/kyokomi/emoji"
)
func AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
type AzRBACModule struct {
AzClient *internal.AzureClient
Log *internal.Logger
}
func (m *AzRBACModule) AzRBACCommand() error {
// setup logging client
o := internal.OutputClient{
Verbosity: AzVerbosity,
Verbosity: m.AzClient.AzVerbosity,
CallingModule: globals.AZ_RBAC_MODULE_NAME,
Table: internal.TableClient{
Wrap: AzWrapTable,
Wrap: m.AzClient.AzWrapTable,
},
}
// initiate command specific client
var c CloudFoxRBACclient
c.RBACModule = m
// set up table vars
var header []string
var body [][]string
var AzSubscriptionInfo SubsriptionInfo
if AzTenantID != "" && AzSubscription == "" {
if len(m.AzClient.AzTenants) > 0 {
// cloudfox azure rbac --tenant [TENANT_ID | PRIMARY_DOMAIN]
for _, AzTenant := range m.AzClient.AzTenants {
var err error
if err != nil {
return err
}
o.PrefixIdentifier = *AzTenant.DefaultDomain
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, *AzTenant.DefaultDomain, "1-tenant-level")
m.Log.Infof(nil, "Enumerating RBAC permissions for tenant %s (%s)", *AzTenant.DefaultDomain, *AzTenant.TenantID)
header, body, err = m.getRBACperTenant(ptr.ToString(AzTenant.TenantID), c)
if err != nil {
return err
}
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)})
var err error
tenantInfo := populateTenant(AzTenantID)
if err != nil {
return err
}
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
fmt.Printf("[%s][%s] Enumerating RBAC permissions for tenant %s\n",
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME),
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
header, body, err = getRBACperTenant(ptr.ToString(tenantInfo.ID), c)
if err != nil {
return err
}
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)})
} else if AzTenantID == "" && AzSubscription != "" {
// cloudfox azure rbac --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription))
tenantInfo := populateTenant(tenantID)
AzSubscriptionInfo = PopulateSubsriptionType(AzSubscription)
o.PrefixIdentifier = AzSubscriptionInfo.Name
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
fmt.Printf("[%s][%s] Enumerating RBAC permissions for subscription %s\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME),
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
header, body = getRBACperSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID, c)
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)})
} else {
// Error: please make a valid flag selection
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
// cloudfox azure rbac --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
for _, AzSubscription := range m.AzClient.AzSubscriptions {
tenantInfo := populateTenant(*AzSubscription.TenantID)
o.PrefixIdentifier = ptr.ToString(AzSubscription.DisplayName)
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), *AzSubscription.DisplayName)
m.Log.Infof(nil, "Enumerating RBAC permissions for subscription %s (%s)", ptr.ToString(AzSubscription.DisplayName), *AzSubscription.SubscriptionID)
header, body = m.getRBACperSubscription(ptr.ToString(tenantInfo.ID), *AzSubscription.SubscriptionID, c)
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: fmt.Sprintf(globals.AZ_RBAC_MODULE_NAME)})
}
}
if body != nil {
//internal.OutputSelector(AzVerbosity, AzOutputFormat, header, body, outputDirectory, fileNameWithoutExtension, globals.AZ_RBAC_MODULE_NAME, AzWrapTable, controlMessagePrefix)
//internal.OutputSelector(m.AzClient.AzVerbosity, AzOutputFormat, header, body, outputDirectory, fileNameWithoutExtension, globals.AZ_RBAC_MODULE_NAME, AzWrapTable, controlMessagePrefix)
o.WriteFullOutput(o.Table.TableFiles, nil)
}
return nil
}
func getRBACperTenant(AzTenantID string, c CloudFoxRBACclient) ([]string, [][]string, error) {
func (m *AzRBACModule) getRBACperTenant(AzTenantID string, c CloudFoxRBACclient) ([]string, [][]string, error) {
var selectedSubs, resultsHeader []string
var resultsBody, b [][]string
for _, s := range GetSubscriptions() {
@@ -110,7 +108,7 @@ func getRBACperTenant(AzTenantID string, c CloudFoxRBACclient) ([]string, [][]st
return resultsHeader, resultsBody, nil
}
func getRBACperSubscription(AzTenantID, AzSubscriptionID string, c CloudFoxRBACclient) ([]string, [][]string) {
func (m *AzRBACModule) getRBACperSubscription(AzTenantID, AzSubscriptionID string, c CloudFoxRBACclient) ([]string, [][]string) {
var resultsHeader []string
var resultsBody [][]string
for _, s := range GetSubscriptions() {
@@ -126,6 +124,7 @@ type CloudFoxRBACclient struct {
roleAssignments []authorization.RoleAssignment
roleDefinitions []authorization.RoleDefinition
AADUsers []graphrbac.User
RBACModule *AzRBACModule
}
func (c *CloudFoxRBACclient) initialize(tenantID string, subscriptionIDs []string) error {
@@ -134,19 +133,19 @@ func (c *CloudFoxRBACclient) initialize(tenantID string, subscriptionIDs []strin
c.roleAssignments = nil
c.roleDefinitions = nil
c.AADUsers, err = getAzureADUsers(tenantID)
c.AADUsers, err = c.RBACModule.getAzureADUsers(tenantID)
if err != nil {
return fmt.Errorf("[%s] failed to get users for tenant %s: %s", color.New(color.FgCyan).Sprint(globals.AZ_RBAC_MODULE_NAME), tenantID, err)
}
for _, subID := range subscriptionIDs {
rd, err := getRoleDefinitions(subID)
rd, err := c.RBACModule.getRoleDefinitions(subID)
if err != nil {
fmt.Printf("[%s] failed to get role definitions for subscription %s: %s. Skipping it.\n", color.New(color.FgCyan).Sprint(globals.AZ_RBAC_MODULE_NAME), subID, err)
}
c.roleDefinitions = append(c.roleDefinitions, rd...)
ra, err := getRoleAssignments(subID)
ra, err := c.RBACModule.getRoleAssignments(subID)
if err != nil {
fmt.Printf("[%s] failed to get role assignments for subscription %s: %s. Skipping it.\n", color.New(color.FgCyan).Sprint(globals.AZ_RBAC_MODULE_NAME), subID, err)
}
@@ -171,7 +170,7 @@ func (c *CloudFoxRBACclient) GetRelevantRBACData(tenantID, subscriptionID string
}
// Sort the results by userDisplayName using slice.Sort
sortedResults := results
sort.Slice(sortedResults, func(i, j int) bool {
sort.Slice(sortedResults, func (i, j int) bool {
return sortedResults[i].userDisplayName < sortedResults[j].userDisplayName
})
@@ -216,9 +215,11 @@ type RoleAssignmentRelevantData struct {
roleName string
}
var getAzureADUsers = getAzureADUsersOriginal
func (m *AzRBACModule) getAzureADUsers(tenantID string) ([]graphrbac.User, error) {
return m.getAzureADUsersOriginal(tenantID)
}
func getAzureADUsersOriginal(tenantID string) ([]graphrbac.User, error) {
func (m *AzRBACModule) getAzureADUsersOriginal(tenantID string) ([]graphrbac.User, error) {
var users []graphrbac.User
client := internal.GetAADUsersClient(tenantID)
for page, err := client.List(context.TODO(), "", ""); page.NotDone(); page.Next() {
@@ -234,7 +235,7 @@ func getAzureADUsersOriginal(tenantID string) ([]graphrbac.User, error) {
return users, nil
}
func mockedGetAzureADUsers(tenantID string) ([]graphrbac.User, error) {
func (m *AzRBACModule) mockedGetAzureADUsers(tenantID string) ([]graphrbac.User, error) {
var users AzureADUsersTestFile
file, err := os.ReadFile(globals.AAD_USERS_TEST_FILE)
@@ -248,10 +249,10 @@ func mockedGetAzureADUsers(tenantID string) ([]graphrbac.User, error) {
return users.AzureADUsers, nil
}
func generateAzureADUsersTestFIle(tenantID string) {
func (m *AzRBACModule) generateAzureADUsersTestFIle(tenantID string) {
// The READ-ONLY ObjectID attribute needs to be included manually in the test file
// ObjectID *string `json:"objectId,omitempty"`
users, err := getAzureADUsers(tenantID)
users, err := m.getAzureADUsers(tenantID)
if err != nil {
log.Fatalf("could not enumerate users for tenant %s", tenantID)
}
@@ -269,9 +270,11 @@ type AzureADUsersTestFile struct {
AzureADUsers []graphrbac.User `json:"azureADUsers"`
}
var getRoleDefinitions = getRoleDefinitionsOriginal
func (m *AzRBACModule) getRoleDefinitions(subscriptionID string) ([]authorization.RoleDefinition, error) {
return m.getRoleDefinitionsOriginal(subscriptionID)
}
func getRoleDefinitionsOriginal(subscriptionID string) ([]authorization.RoleDefinition, error) {
func (m *AzRBACModule) getRoleDefinitionsOriginal(subscriptionID string) ([]authorization.RoleDefinition, error) {
client := internal.GetRoleDefinitionsClient(subscriptionID)
var roleDefinitions []authorization.RoleDefinition
for page, err := client.List(context.TODO(), "", ""); page.NotDone(); page.Next() {
@@ -287,7 +290,7 @@ func getRoleDefinitionsOriginal(subscriptionID string) ([]authorization.RoleDefi
return roleDefinitions, nil
}
func mockedGetRoleDefinitions(subscriptionID string) ([]authorization.RoleDefinition, error) {
func (m *AzRBACModule) mockedGetRoleDefinitions(subscriptionID string) ([]authorization.RoleDefinition, error) {
var roleDefinitions RoleDefinitionTestFile
file, err := os.ReadFile(globals.ROLE_DEFINITIONS_TEST_FILE)
if err != nil {
@@ -300,16 +303,16 @@ func mockedGetRoleDefinitions(subscriptionID string) ([]authorization.RoleDefini
return roleDefinitions.RoleDefinitions, nil
}
func generateRoleDefinitionsTestFile(subscriptionID string) {
func (m *AzRBACModule) generateRoleDefinitionsTestFile(subscriptionID string) {
// The READ-ONLY ID attribute needs to be included manually in the test file.
// This attribute is the unique identifier for the role.
// ID *string `json:"id,omitempty"`.
roleDefinitions, err := getRoleDefinitions(subscriptionID)
roleDefinitions, err := m.getRoleDefinitions(subscriptionID)
if err != nil {
log.Fatal(err)
}
roleAssignments, err := getRoleAssignments(subscriptionID)
roleAssignments, err := m.getRoleAssignments(subscriptionID)
if err != nil {
log.Fatal(err)
}
@@ -346,9 +349,11 @@ type RoleDefinitionTestFile struct {
RoleDefinitions []authorization.RoleDefinition `json:"roleDefinitions"`
}
var getRoleAssignments = getRoleAssignmentsOriginal
func (m *AzRBACModule) getRoleAssignments(subscriptionID string) ([]authorization.RoleAssignment, error) {
return m.getRoleAssignmentsOriginal(subscriptionID)
}
func getRoleAssignmentsOriginal(subscriptionID string) ([]authorization.RoleAssignment, error) {
func (m *AzRBACModule) getRoleAssignmentsOriginal(subscriptionID string) ([]authorization.RoleAssignment, error) {
var roleAssignments []authorization.RoleAssignment
client := internal.GetRoleAssignmentsClient(subscriptionID)
for page, err := client.List(context.TODO(), ""); page.NotDone(); page.Next() {
@@ -363,7 +368,7 @@ func getRoleAssignmentsOriginal(subscriptionID string) ([]authorization.RoleAssi
return roleAssignments, nil
}
func mockedGetRoleAssignments(subscriptionID string) ([]authorization.RoleAssignment, error) {
func (m *AzRBACModule) mockedGetRoleAssignments(subscriptionID string) ([]authorization.RoleAssignment, error) {
var allRoleAssignments, roleAssignmentsResults []authorization.RoleAssignment
file, err := os.ReadFile(globals.ROLE_ASSIGNMENTS_TEST_FILE)
if err != nil {
@@ -382,8 +387,8 @@ func mockedGetRoleAssignments(subscriptionID string) ([]authorization.RoleAssign
return roleAssignmentsResults, nil
}
func generateRoleAssignmentsTestFile(subscriptionID string) {
ra, err := getRoleAssignments(subscriptionID)
func (m *AzRBACModule) generateRoleAssignmentsTestFile(subscriptionID string) {
ra, err := m.getRoleAssignments(subscriptionID)
if err != nil {
log.Fatalf("could not generate role assignments for subscription %s", subscriptionID)
}
+1 -1
View File
@@ -116,7 +116,7 @@ func GetDefaultDomainFromTenantID(tenantID string) (string, error) {
func populateTenant(tenantID string) TenantInfo {
for _, t := range getTenants() {
for _, t := range GetTenants() {
if ptr.ToString(t.TenantID) == tenantID || ptr.ToString(t.DefaultDomain) == tenantID {
var subscriptions []SubsriptionInfo
for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(t.ID)) {
+95 -85
View File
@@ -9,98 +9,99 @@ import (
"path/filepath"
"strings"
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
"github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
"github.com/BishopFox/cloudfox/globals"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/smithy-go/ptr"
"github.com/fatih/color"
"github.com/kyokomi/emoji"
)
// Color functions
var cyan = color.New(color.FgCyan).SprintFunc()
func AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
type AzStorageModule struct {
AzClient *internal.AzureClient
Log *internal.Logger
}
func (m *AzStorageModule) AzStorageCommand() error {
var publicBlobURLs []string
if AzTenantID != "" && AzSubscription == "" {
if len(m.AzClient.AzTenants) > 0 {
// cloudfox azure storage --tenant [TENANT_ID | PRIMARY_DOMAIN]
tenantInfo := populateTenant(AzTenantID)
for _, AzTenant := range m.AzClient.AzTenants {
if AzMergedTable {
if m.AzClient.AzMergedTable {
// set up table vars
var header []string
var body [][]string
// setup logging client
o := internal.OutputClient{
Verbosity: AzVerbosity,
CallingModule: globals.AZ_STORAGE_MODULE_NAME,
Table: internal.TableClient{
Wrap: AzWrapTable,
},
}
// set up table vars
var header []string
var body [][]string
// setup logging client
o := internal.OutputClient{
Verbosity: m.AzClient.AzVerbosity,
CallingModule: globals.AZ_STORAGE_MODULE_NAME,
Table: internal.TableClient{
Wrap: m.AzClient.AzWrapTable,
},
}
var err error
var err error
fmt.Printf("[%s][%s] Enumerating storage accounts for tenant %s\n",
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME),
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
m.Log.Infof([]string{}, "Enumerating storage accounts for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain)
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level")
header, body, publicBlobURLs, err = getStorageInfoPerTenant(ptr.ToString(tenantInfo.ID))
header, body, publicBlobURLs, err = m.getStorageInfoPerTenant(ptr.ToString(AzTenant.TenantID))
if err != nil {
return err
}
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: fmt.Sprintf(globals.AZ_STORAGE_MODULE_NAME)})
if body != nil {
o.WriteFullOutput(o.Table.TableFiles, nil)
}
if publicBlobURLs != nil {
err := writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs)
if err != nil {
return err
}
}
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: fmt.Sprintf(globals.AZ_STORAGE_MODULE_NAME)})
} else {
if body != nil {
o.WriteFullOutput(o.Table.TableFiles, nil)
}
if publicBlobURLs != nil {
err := m.writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs)
if err != nil {
return err
}
}
for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) {
runStorageCommandForSingleSubcription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
} else {
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
m.runStorageCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription)
}
}
}
} else if AzTenantID == "" && AzSubscription != "" {
//cloudfox azure storage --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
runStorageCommandForSingleSubcription(AzSubscription, AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
} else {
// Error: please make a valid flag selection
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
for _, AzSubscription := range AzSubscriptions {
m.runStorageCommandForSingleSubscription(tenantSlug, AzSubscription)
}
}
}
return nil
}
func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error {
func (m *AzStorageModule) runStorageCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
var err error
// setup logging client
o := internal.OutputClient{
Verbosity: AzVerbosity,
Verbosity: m.AzClient.AzVerbosity,
CallingModule: globals.AZ_STORAGE_MODULE_NAME,
Table: internal.TableClient{
Wrap: AzWrapTable,
Wrap: m.AzClient.AzWrapTable,
},
}
@@ -109,19 +110,11 @@ func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirect
var body [][]string
var publicBlobURLs []string
tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription))
tenantInfo := populateTenant(tenantID)
AzSubscriptionInfo := PopulateSubsriptionType(AzSubscription)
o.PrefixIdentifier = AzSubscriptionInfo.Name
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
o.PrefixIdentifier = *AzSubscription.DisplayName
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName)
fmt.Printf(
"[%s][%s] Enumerating storage accounts for subscription %s\n",
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)),
color.CyanString(globals.AZ_STORAGE_MODULE_NAME),
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
//AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription))
header, body, publicBlobURLs, err = getStorageInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID)
m.Log.Infof([]string{}, "Enumerating storage accounts for subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)
header, body, publicBlobURLs, err = m.getStorageInfoPerSubscription(*AzSubscription.TenantID, *AzSubscription.SubscriptionID)
if err != nil {
return err
}
@@ -136,7 +129,7 @@ func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirect
}
if publicBlobURLs != nil {
err := writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs)
err := m.writeBlobURLslootFile(globals.AZ_STORAGE_MODULE_NAME, o.PrefixIdentifier, o.Table.DirectoryName, publicBlobURLs)
if err != nil {
return err
}
@@ -145,7 +138,7 @@ func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirect
}
func writeBlobURLslootFile(callingModule, controlMessagePrefix, outputDirectory string, publicBlobURLs []string) error {
func (m *AzStorageModule) writeBlobURLslootFile(callingModule, controlMessagePrefix, outputDirectory string, publicBlobURLs []string) error {
lootDirectory := filepath.Join(outputDirectory, "loot")
lootFilePath := filepath.Join(lootDirectory, "public-blob-urls.txt")
@@ -171,14 +164,14 @@ func writeBlobURLslootFile(callingModule, controlMessagePrefix, outputDirectory
return nil
}
func getStorageInfoPerTenant(AzTenantID string) ([]string, [][]string, []string, error) {
func (m *AzStorageModule) getStorageInfoPerTenant(AzTenantID string) ([]string, [][]string, []string, error) {
var err error
var header []string
var body, b [][]string
var publicBlobURLs []string
for _, s := range GetSubscriptionsPerTenantID(AzTenantID) {
header, b, publicBlobURLs, err = getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID))
header, b, publicBlobURLs, err = m.getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID))
if err != nil {
return nil, nil, nil, err
} else {
@@ -188,7 +181,7 @@ func getStorageInfoPerTenant(AzTenantID string) ([]string, [][]string, []string,
return header, body, publicBlobURLs, nil
}
func getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]string, [][]string, []string, error) {
func (m *AzStorageModule) getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]string, [][]string, []string, error) {
var err error
var header []string
var body [][]string
@@ -196,7 +189,7 @@ func getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]strin
for _, s := range GetSubscriptions() {
if ptr.ToString(s.SubscriptionID) == AzSubscriptionID {
header, body, publicBlobURLs, err = getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID))
header, body, publicBlobURLs, err = m.getRelevantStorageAccountData(AzTenantID, ptr.ToString(s.SubscriptionID))
if err != nil {
return nil, nil, nil, err
}
@@ -205,20 +198,34 @@ func getStorageInfoPerSubscription(AzTenantID, AzSubscriptionID string) ([]strin
return header, body, publicBlobURLs, nil
}
func getRelevantStorageAccountData(tenantID, subscriptionID string) ([]string, [][]string, []string, error) {
func (m *AzStorageModule) getRelevantStorageAccountData(tenantID, subscriptionID string) ([]string, [][]string, []string, error) {
tableHeader := []string{"Subscription Name", "Storage Account Name", "Container Name", "Access Status"}
var tableBody [][]string
var publicBlobURLs []string
storageAccounts, err := getStorageAccounts(subscriptionID)
var urls []string
var containers map[string]string
storageAccounts, err := m.getStorageAccounts(subscriptionID)
if err != nil {
return nil, nil, nil, err
}
for _, sa := range storageAccounts {
blobClient, err := internal.GetStorageAccountBlobClient(tenantID, ptr.ToString(sa.Name))
// if resource group were submitted, do filtering
if len(m.AzClient.AzRGs) > 0 {
fmt.Println(*sa.ID)
for _, AzRG := range m.AzClient.AzRGs {
metaResource, _ := azure.ParseResourceID(*sa.ID)
if metaResource.ResourceGroup == *AzRG.Name {
goto ADD_RESOURCE
}
}
goto SKIP_RESOURCE
}
ADD_RESOURCE:
if err != nil {
return nil, nil, nil, err
}
containers, err := getStorageAccountContainers(blobClient)
containers, err = m.getStorageAccountContainers(blobClient)
if err != nil {
// rather than return an error, we'll just add a row to the table highlighting the storage account name and that we couldn't get the containers
@@ -240,20 +247,23 @@ func getRelevantStorageAccountData(tenantID, subscriptionID string) ([]string, [
containerName,
accessType})
}
urls, err := getPublicBlobURLs(blobClient, ptr.ToString(sa.Name), containers)
urls, err = m.getPublicBlobURLs(blobClient, ptr.ToString(sa.Name), containers)
if err == nil {
continue
//return nil, nil, nil, err
}
publicBlobURLs = append(publicBlobURLs, urls...)
SKIP_RESOURCE:
}
return tableHeader, tableBody, publicBlobURLs, nil
}
var getStorageAccounts = getStorageAccountsOriginal
func (m *AzStorageModule) getStorageAccounts(subscriptionID string) ([]storage.Account, error) {
return m.getStorageAccountsOriginal(subscriptionID)
}
func getStorageAccountsOriginal(subscriptionID string) ([]storage.Account, error) {
func (m *AzStorageModule) getStorageAccountsOriginal(subscriptionID string) ([]storage.Account, error) {
storageClient := internal.GetStorageClient(subscriptionID)
var storageAccounts []storage.Account
for page, err := storageClient.List(context.TODO()); page.NotDone(); page.Next() {
@@ -265,7 +275,7 @@ func getStorageAccountsOriginal(subscriptionID string) ([]storage.Account, error
return storageAccounts, nil
}
func mockedGetStorageAccounts(subscriptionID string) ([]storage.Account, error) {
func (m *AzStorageModule) mockedGetStorageAccounts(subscriptionID string) ([]storage.Account, error) {
testFile, err := os.ReadFile(globals.STORAGE_ACCOUNTS_TEST_FILE)
if err != nil {
return nil, fmt.Errorf("could not open storage accounts test file %s", globals.STORAGE_ACCOUNTS_TEST_FILE)
@@ -284,7 +294,7 @@ func mockedGetStorageAccounts(subscriptionID string) ([]storage.Account, error)
return storageAccountsResults, nil
}
func getStorageAccountContainers(client *azblob.Client) (map[string]string, error) {
func (m *AzStorageModule) getStorageAccountContainers(client *azblob.Client) (map[string]string, error) {
containers := make(map[string]string)
pager := client.NewListContainersPager(&azblob.ListContainersOptions{
Include: azblob.ListContainersInclude{Metadata: true, Deleted: true},
@@ -305,11 +315,11 @@ func getStorageAccountContainers(client *azblob.Client) (map[string]string, erro
return containers, nil
}
func getPublicBlobURLs(client *azblob.Client, storageAccountName string, containers map[string]string) ([]string, error) {
func (m *AzStorageModule) getPublicBlobURLs(client *azblob.Client, storageAccountName string, containers map[string]string) ([]string, error) {
var publicBlobURLs []string
for containerName, accessType := range containers {
if accessType == "public" {
url, err := getPublicBlobURLsForContainer(client, storageAccountName, containerName)
url, err := m.getPublicBlobURLsForContainer(client, storageAccountName, containerName)
if err != nil {
//return nil, err
continue
@@ -320,19 +330,19 @@ func getPublicBlobURLs(client *azblob.Client, storageAccountName string, contain
return publicBlobURLs, nil
}
func getPublicBlobURLsForContainer(client *azblob.Client, storageAccountName, containerName string) ([]string, error) {
blobNames, err := getAllBlobsForContainer(client, containerName)
func (m *AzStorageModule) getPublicBlobURLsForContainer(client *azblob.Client, storageAccountName, containerName string) ([]string, error) {
blobNames, err := m.getAllBlobsForContainer(client, containerName)
if err != nil {
return nil, err
}
publicBlobURLs, err := validatePublicBlobURLs(storageAccountName, containerName, blobNames)
publicBlobURLs, err := m.validatePublicBlobURLs(storageAccountName, containerName, blobNames)
if err != nil {
return nil, err
}
return publicBlobURLs, nil
}
func getAllBlobsForContainer(blobClient *azblob.Client, containerName string) ([]string, error) {
func (m *AzStorageModule) getAllBlobsForContainer(blobClient *azblob.Client, containerName string) ([]string, error) {
var blobNames []string
pager := blobClient.NewListBlobsFlatPager(containerName, &azblob.ListBlobsFlatOptions{
@@ -352,7 +362,7 @@ func getAllBlobsForContainer(blobClient *azblob.Client, containerName string) ([
return blobNames, nil
}
func validatePublicBlobURLs(storageAccountName, containerName string, blobNames []string) ([]string, error) {
func (m *AzStorageModule) validatePublicBlobURLs(storageAccountName, containerName string, blobNames []string) ([]string, error) {
var publicBlobURLs []string
if blobNames == nil {
+2 -2
View File
@@ -69,9 +69,9 @@ func TestAzStorageCommand(t *testing.T) {
}
internal.MockFileSystem(true)
// Mocked functions to simulate Azure calls and responses
getTenants = mockedGetTenants
GetTenants = mockedGetTenants
GetSubscriptions = mockedGetSubscriptions
getResourceGroups = mockedGetResourceGroups
GetResourceGroups = mockedGetResourceGroups
getStorageAccounts = mockedGetStorageAccounts
for _, s := range subtests {
+130 -107
View File
@@ -16,106 +16,108 @@ import (
"github.com/BishopFox/cloudfox/globals"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/smithy-go/ptr"
"github.com/fatih/color"
"github.com/kyokomi/emoji"
)
func AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
type AzVMsModule struct {
AzClient *internal.AzureClient
Log *internal.Logger
}
if AzTenantID != "" && AzSubscription == "" {
// cloudfox azure vms --tenant [TENANT_ID | PRIMARY_DOMAIN]
tenantInfo := populateTenant(AzTenantID)
if AzMergedTable {
// set up table vars
var header []string
var body [][]string
var userData string
func (m *AzVMsModule) AzVMsCommand() error {
o := internal.OutputClient{
Verbosity: AzVerbosity,
CallingModule: globals.AZ_VMS_MODULE_NAME,
Table: internal.TableClient{
Wrap: AzWrapTable,
},
}
fmt.Printf("[%s][%s] Enumerating VMs for tenant %s\n",
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_VMS_MODULE_NAME),
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
// populate the table data
header, body, userData = getVMsPerTenantID(ptr.ToString(tenantInfo.ID))
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: fmt.Sprintf(globals.AZ_VMS_MODULE_NAME)})
if body != nil {
if userData != "" {
o.Loot.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "loot")
o.Loot.LootFiles = append(o.Loot.LootFiles,
internal.LootFile{
Contents: userData,
Name: "virtualmachines-user-data"})
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
fmt.Println()
} else {
o.WriteFullOutput(o.Table.TableFiles, nil)
fmt.Println()
}
}
} else {
for _, s := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) {
runVMsCommandForSingleSubscription(ptr.ToString(s.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
}
}
} else if AzTenantID == "" && AzSubscription != "" {
// cloudfox azure vms --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
runVMsCommandForSingleSubscription(AzSubscription, AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
} else {
// Error: please make a valid flag selection
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
o := internal.OutputClient{
Verbosity: m.AzClient.AzVerbosity,
CallingModule: globals.AZ_VMS_MODULE_NAME,
Table: internal.TableClient{
Wrap: m.AzClient.AzWrapTable,
},
}
if len(m.AzClient.AzTenants) > 0 {
// cloudfox azure inventory --tenant [TENANT_ID | PRIMARY_DOMAIN]
for _, AzTenant := range m.AzClient.AzTenants {
if m.AzClient.AzMergedTable {
// set up table vars
var header []string
var body [][]string
var userData string
o := internal.OutputClient{
Verbosity: m.AzClient.AzVerbosity,
CallingModule: globals.AZ_INVENTORY_MODULE_NAME,
Table: internal.TableClient{
Wrap: m.AzClient.AzWrapTable,
},
}
m.Log.Infof(nil, "Enumerating VMs for tenant %s (%s)", ptr.ToString(AzTenant.DefaultDomain), ptr.ToString(AzTenant.TenantID))
o.PrefixIdentifier = ptr.ToString(AzTenant.DefaultDomain)
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "1-tenant-level")
//populate the table data
header, body, userData = m.getVMsPerTenantID(ptr.ToString(AzTenant.TenantID))
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
Body: body,
Name: fmt.Sprintf(globals.AZ_VMS_MODULE_NAME)})
if body != nil {
if userData != "" {
o.Loot.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(AzTenant.DefaultDomain), "loot")
o.Loot.LootFiles = append(o.Loot.LootFiles,
internal.LootFile{
Contents: userData,
Name: "virtualmachines-user-data"})
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
fmt.Println()
} else {
o.WriteFullOutput(o.Table.TableFiles, nil)
fmt.Println()
}
}
} else {
for _, AzSubscription := range GetSubscriptionsPerTenantID(ptr.ToString(AzTenant.TenantID)) {
m.runVMsCommandForSingleSubscription(*AzTenant.DefaultDomain, &AzSubscription)
}
}
}
} else {
// ./cloudfox azure inventory --subscription [SUBSCRIPTION_ID | SUBSCRIPTION_NAME]
for tenantSlug, AzSubscriptions := range m.AzClient.AzSubscriptionsAlt {
for _, AzSubscription := range AzSubscriptions {
m.runVMsCommandForSingleSubscription(tenantSlug, AzSubscription)
}
}
}
o.WriteFullOutput(o.Table.TableFiles, nil)
return nil
}
func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error {
func (m *AzVMsModule) runVMsCommandForSingleSubscription(tenantSlug string, AzSubscription *subscriptions.Subscription) error {
// set up table vars
var header []string
var body [][]string
var userData string
o := internal.OutputClient{
Verbosity: AzVerbosity,
Verbosity: m.AzClient.AzVerbosity,
CallingModule: globals.AZ_VMS_MODULE_NAME,
Table: internal.TableClient{
Wrap: AzWrapTable,
Wrap: m.AzClient.AzWrapTable,
},
}
var AzSubscriptionInfo SubsriptionInfo
tenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription))
tenantInfo := populateTenant(tenantID)
AzSubscriptionInfo = PopulateSubsriptionType(AzSubscription)
o.PrefixIdentifier = AzSubscriptionInfo.Name
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
o.PrefixIdentifier = *AzSubscription.DisplayName
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName)
fmt.Printf("[%s][%s] Enumerating VMs for subscription %s\n",
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_VMS_MODULE_NAME),
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
m.Log.Infof(nil, "Enumerating VMs in subscription %s (%s)", *AzSubscription.DisplayName, *AzSubscription.SubscriptionID)
// populate the table data
header, body, userData = getVMsPerSubscriptionID(AzSubscriptionInfo.ID)
header, body, userData = m.getVMsPerSubscriptionID(tenantSlug, AzSubscription)
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
@@ -125,7 +127,7 @@ func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory
if body != nil {
if userData != "" {
o.Loot.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name, "loot")
o.Loot.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantSlug, *AzSubscription.DisplayName, "loot")
o.Loot.LootFiles = append(o.Loot.LootFiles,
internal.LootFile{
Contents: userData,
@@ -143,59 +145,65 @@ func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory
return nil
}
func getVMsPerTenantID(AzTenantID string) ([]string, [][]string, string) {
func (m *AzVMsModule) getVMsPerTenantID(AzTenantID string) ([]string, [][]string, string) {
var resultsHeader []string
var resultsBody, b [][]string
var userDataCombined, userData string
var err error
for _, s := range GetSubscriptionsPerTenantID(AzTenantID) {
for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) {
resultsHeader, b, userData, err = getComputeRelevantData(s, rg)
for _, rg := range GetResourceGroups(ptr.ToString(s.SubscriptionID)) {
resultsHeader, b, userData, err = m.getComputeRelevantData(s, rg)
if err != nil {
fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID))
m.Log.Warnf(nil, "Could not enumerate VMs for resource group %s in subscription %s", ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID))
} else {
resultsBody = append(resultsBody, b...)
userDataCombined += userData
}
}
}
return resultsHeader, resultsBody, userDataCombined
}
func getVMsPerSubscriptionID(AzSubscriptionID string) ([]string, [][]string, string) {
func (m *AzVMsModule) getVMsPerSubscriptionID(tenantSlug string, AzSubscription *subscriptions.Subscription) ([]string, [][]string, string) {
var resultsHeader []string
var resultsBody, b [][]string
var userDataCombined, userData string
var err error
for _, s := range GetSubscriptions() {
if ptr.ToString(s.SubscriptionID) == AzSubscriptionID {
for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) {
resultsHeader, b, userData, err = getComputeRelevantData(s, rg)
if err != nil {
fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID))
} else {
resultsBody = append(resultsBody, b...)
userDataCombined += userData
}
}
for _, rg := range GetResourceGroups(ptr.ToString(AzSubscription.SubscriptionID)) {
resultsHeader, b, userData, err = m.getComputeRelevantData(*AzSubscription, rg)
if err != nil {
m.Log.Warnf(nil, "Could not enumerate VMs for resource group %s in subscription %s", ptr.ToString(rg.Name), ptr.ToString(AzSubscription.SubscriptionID))
} else {
resultsBody = append(resultsBody, b...)
userDataCombined += userData
}
}
return resultsHeader, resultsBody, userDataCombined
}
func getComputeRelevantData(sub subscriptions.Subscription, rg resources.Group) ([]string, [][]string, string, error) {
func (m *AzVMsModule) getComputeRelevantData(sub subscriptions.Subscription, rg resources.Group) ([]string, [][]string, string, error) {
header := []string{"Subscription Name", "VM Name", "VM Location", "Private IPs", "Public IPs", "Admin Username", "Resource Group Name"}
var body [][]string
var userDataString string
// User has requested specific resource groups, filtering
if len(m.AzClient.AzRGs) > 0 {
for _, AzRG := range m.AzClient.AzRGs {
if *rg.Name == *AzRG.Name {
goto ADD_RESOURCE
}
}
return header, body, userDataString, nil
}
ADD_RESOURCE:
subscriptionID := ptr.ToString(sub.SubscriptionID)
subscriptionName := ptr.ToString(sub.DisplayName)
resourceGroupName := ptr.ToString(rg.Name)
vms, err := getComputeVMsPerResourceGroup(subscriptionID, resourceGroupName)
vms, err := m.getComputeVMsPerResourceGroup(subscriptionID, resourceGroupName)
if err != nil {
return nil, nil, "", fmt.Errorf("error fetching vms for resource group %s: %s", resourceGroupName, err)
}
@@ -250,9 +258,12 @@ func getComputeRelevantData(sub subscriptions.Subscription, rg resources.Group)
return header, body, userDataString, nil
}
var getComputeVMsPerResourceGroup = getComputeVMsPerResourceGroupOriginal
func getComputeVMsPerResourceGroupOriginal(subscriptionID string, resourceGroup string) ([]compute.VirtualMachine, error) {
func (m *AzVMsModule) getComputeVMsPerResourceGroup(subscriptionID string, resourceGroup string) ([]compute.VirtualMachine, error) {
return m.getComputeVMsPerResourceGroupOriginal(subscriptionID, resourceGroup)
}
func (m *AzVMsModule) getComputeVMsPerResourceGroupOriginal(subscriptionID string, resourceGroup string) ([]compute.VirtualMachine, error) {
computeClient := internal.GetVirtualMachinesClient(subscriptionID)
var vms []compute.VirtualMachine
@@ -277,8 +288,16 @@ func getComputeVmInfo(subscriptionID string, resourceGroup string, vmName string
}
return vm, nil
}
func getComputeVmInstanceView(subscriptionID string, resourceGroup string, vmName string) (compute.VirtualMachine, error) {
computeClient := internal.GetVirtualMachinesClient(subscriptionID)
vm, err := computeClient.Get(context.Background(), resourceGroup, vmName, compute.InstanceViewTypesInstanceView)
if err != nil {
return compute.VirtualMachine{}, fmt.Errorf("could not get vm %s. %s", vmName, err)
}
return vm, nil
}
func mockedGetComputeVMsPerResourceGroup(subscriptionID, resourceGroup string) ([]compute.VirtualMachine, error) {
func (m *AzVMsModule) mockedGetComputeVMsPerResourceGroup(subscriptionID, resourceGroup string) ([]compute.VirtualMachine, error) {
testFile, err := os.ReadFile(globals.VMS_TEST_FILE)
if err != nil {
return nil, fmt.Errorf("could not read file %s", globals.VMS_TEST_FILE)
@@ -306,7 +325,7 @@ func getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachi
if vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces != nil {
for _, nicReference := range *vm.VirtualMachineProperties.NetworkProfile.NetworkInterfaces {
nic, err := getNICdetails(subscriptionID, resourceGroup, nicReference)
nic, err := getNICdetails(subscriptionID, resourceGroup, nicReference, "")
if err != nil {
return []string{err.Error()}, []string{err.Error()}
}
@@ -330,13 +349,15 @@ func getIPs(subscriptionID string, resourceGroup string, vm compute.VirtualMachi
return privateIPs, publicIPs
}
var getNICdetails = getNICdetailsOriginal
func getNICdetails(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference, expand string) (network.Interface, error) {
return getNICdetailsOriginal(subscriptionID, resourceGroup, nicReference, expand)
}
func getNICdetailsOriginal(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) {
func getNICdetailsOriginal(subscriptionID string, resourceGroup string, nicReference compute.NetworkInterfaceReference, expand string) (network.Interface, error) {
client := internal.GetNICClient(subscriptionID)
NICName := strings.Split(ptr.ToString(nicReference.ID), "/")[len(strings.Split(ptr.ToString(nicReference.ID), "/"))-1]
nic, err := client.Get(context.TODO(), resourceGroup, NICName, "")
nic, err := client.Get(context.TODO(), resourceGroup, NICName, expand)
if err != nil {
return network.Interface{}, fmt.Errorf("NICnotFound_%s", NICName)
}
@@ -344,7 +365,7 @@ func getNICdetailsOriginal(subscriptionID string, resourceGroup string, nicRefer
return nic, nil
}
func mockedGetNICdetails(subscriptionID, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) {
func (m *AzVMsModule) mockedGetNICdetails(subscriptionID, resourceGroup string, nicReference compute.NetworkInterfaceReference) (network.Interface, error) {
testFile, err := os.ReadFile(globals.NICS_TEST_FILE)
if err != nil {
return network.Interface{}, fmt.Errorf("NICnotFound_%s", globals.NICS_TEST_FILE)
@@ -364,7 +385,9 @@ func mockedGetNICdetails(subscriptionID, resourceGroup string, nicReference comp
return network.Interface{}, fmt.Errorf("NICnotFound_%s", ptr.ToString(nicReference.ID))
}
var getPublicIP = getPublicIPOriginal
func getPublicIP(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) {
return getPublicIPOriginal(subscriptionID, resourceGroup, ip)
}
func getPublicIPOriginal(subscriptionID string, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) {
client := internal.GetPublicIPClient(subscriptionID)
@@ -380,7 +403,7 @@ func getPublicIPOriginal(subscriptionID string, resourceGroup string, ip network
return publicIPExpanded.PublicIPAddressPropertiesFormat.IPAddress, nil
}
func mockedGetPublicIP(subscriptionID, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) {
func (m *AzVMsModule) mockedGetPublicIP(subscriptionID, resourceGroup string, ip network.InterfaceIPConfiguration) (*string, error) {
f, err := os.ReadFile(globals.PUBLIC_IPS_TEST_FILE)
if err != nil {
return nil, fmt.Errorf("IPNotFound_%s", globals.PUBLIC_IPS_TEST_FILE)
+1 -1
View File
@@ -77,7 +77,7 @@ func TestAzVMsCommand(t *testing.T) {
// Mocked functions to simulate Azure calls and responses
GetSubscriptions = mockedGetSubscriptions
getResourceGroups = mockedGetResourceGroups
GetResourceGroups = mockedGetResourceGroups
getComputeVMsPerResourceGroup = mockedGetComputeVMsPerResourceGroup
getNICdetails = mockedGetNICdetails
getPublicIP = mockedGetPublicIP
+19 -15
View File
@@ -15,27 +15,31 @@ import (
"github.com/BishopFox/cloudfox/globals"
"github.com/BishopFox/cloudfox/internal"
"github.com/aws/smithy-go/ptr"
"github.com/fatih/color"
"github.com/kyokomi/emoji"
)
func AzWhoamiCommand(AzOutputDirectory, version string, AzWrapTable bool, AzVerbosity int, AzWhoamiListRGsAlso bool) error {
type AzWhoamiModule struct {
AzClient *internal.AzureClient
Log *internal.Logger
}
func (m *AzWhoamiModule) AzWhoamiCommand(AzWhoamiListRGsAlso bool) error {
o := internal.OutputClient{
Verbosity: AzVerbosity,
Verbosity: m.AzClient.AzVerbosity,
CallingModule: globals.AZ_WHOAMI_MODULE_NAME,
Table: internal.TableClient{
Wrap: AzWrapTable,
Wrap: m.AzClient.AzWrapTable,
},
}
fmt.Printf("[%s][%s] Enumerating Azure CLI sessions...\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", version)), color.CyanString(globals.AZ_WHOAMI_MODULE_NAME))
m.Log.Info(nil, "Enumerating Azure CLI sessions...")
var header []string
var body [][]string
o.PrefixIdentifier = "N/A"
if !AzWhoamiListRGsAlso {
// cloudfox azure whoami
header, body = getWhoamiRelevantDataSubsOnly()
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data")
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data")
// append timestamp to filename (time from epoch)
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
@@ -46,7 +50,7 @@ func AzWhoamiCommand(AzOutputDirectory, version string, AzWrapTable bool, AzVerb
} else {
// cloudfox azure whoami --list-rgs
header, body = getWhoamiRelevantDataPerRG()
o.Table.DirectoryName = filepath.Join(ptr.ToString(internal.GetLogDirPath()), globals.AZ_DIR_BASE, "whoami-data")
o.Table.DirectoryName = filepath.Join(m.AzClient.AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data")
o.Table.TableFiles = append(o.Table.TableFiles,
internal.TableFile{
Header: header,
@@ -64,10 +68,10 @@ func getWhoamiRelevantDataPerRG() ([]string, [][]string) {
tableHead := []string{"Tenant ID", "Tentant Primary Domain", "Subscription ID", "Subscription Name", "RG Name", "Region"}
var tableBody [][]string
for _, t := range getTenants() {
for _, t := range GetTenants() {
for _, s := range GetSubscriptions() {
if ptr.ToString(t.TenantID) == ptr.ToString(s.TenantID) {
for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) {
for _, rg := range GetResourceGroups(ptr.ToString(s.SubscriptionID)) {
tableBody = append(
tableBody,
[]string{
@@ -90,7 +94,7 @@ func getWhoamiRelevantDataSubsOnly() ([]string, [][]string) {
tableHead := []string{"Tenant ID", "Tenant Primary Domain", "Subscription ID", "Subscription Name"}
var tableBody [][]string
for _, t := range getTenants() {
for _, t := range GetTenants() {
for _, s := range GetSubscriptions() {
if ptr.ToString(t.TenantID) == ptr.ToString(s.TenantID) {
tableBody = append(
@@ -108,9 +112,9 @@ func getWhoamiRelevantDataSubsOnly() ([]string, [][]string) {
return tableHead, tableBody
}
var getTenants = getTenantsOriginal
var GetTenants = GetTenantsOriginal
func getTenantsOriginal() []subscriptions.TenantIDDescription {
func GetTenantsOriginal() []subscriptions.TenantIDDescription {
tenantsClient := internal.GetTenantsClient()
var results []subscriptions.TenantIDDescription
for page, err := tenantsClient.List(context.TODO()); page.NotDone(); err = page.Next() {
@@ -163,9 +167,9 @@ func mockedGetSubscriptions() []subscriptions.Subscription {
return results
}
var getResourceGroups = getResourceGroupsOriginal
var GetResourceGroups = GetResourceGroupsOriginal
func getResourceGroupsOriginal(subscriptionID string) []resources.Group {
func GetResourceGroupsOriginal(subscriptionID string) []resources.Group {
var results []resources.Group
rgClient := internal.GetResourceGroupsClient(subscriptionID)
+2 -2
View File
@@ -14,9 +14,9 @@ func TestAzWhoamiCommand(t *testing.T) {
fmt.Println("[test case] Azure Whoami Command")
// Mocked functions to simulate Azure calls and responses
getTenants = mockedGetTenants
GetTenants = mockedGetTenants
GetSubscriptions = mockedGetSubscriptions
getResourceGroups = mockedGetResourceGroups
GetResourceGroups = mockedGetResourceGroups
// Test case parameters
internal.MockFileSystem(true)
+203 -45
View File
@@ -1,27 +1,46 @@
package cli
import (
"log"
"github.com/BishopFox/cloudfox/azure"
"github.com/BishopFox/cloudfox/internal"
az "github.com/Azure/go-autorest/autorest/azure"
"github.com/spf13/cobra"
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/resources"
)
var (
AzTenantID string
AzSubscription string
AzRGName string
AzOutputFormat string
AzOutputDirectory string
AzVerbosity int
AzWrapTable bool
AzMergedTable bool
// output options
AzOutputFormat string
AzOutputDirectory string
AzVerbosity int
AzWrapTable bool
AzMergedTable bool
// resource selectors
AzTenantRefs []string
AzSubscriptionRefs []string
AzRGRefs []string
AzResourceRefs []string
// Shared resources for all modules
AzClient *internal.AzureClient
AzTenants []*subscriptions.TenantIDDescription
AzSubscriptions []*subscriptions.Subscription
AzRGs []*resources.Group
AzResources []*az.Resource
// command specific variables
// netscan ipv4
AzSourceIPv4 string
AzCommands = &cobra.Command{
Use: "azure",
Aliases: []string{"az"},
Long: `See "Available Commands" for Azure Modules below`,
Short: "See \"Available Commands\" for Azure Modules below",
PersistentPreRun: azurePreRun,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
@@ -34,12 +53,8 @@ var (
Long: `
Display Available Azure CLI Sessions:
./cloudfox az whoami`,
Run: func(cmd *cobra.Command, args []string) {
err := azure.AzWhoamiCommand(AzOutputDirectory, cmd.Root().Version, AzWrapTable, AzVerbosity, AzWhoamiListRGsAlso)
if err != nil {
log.Fatal(err)
}
},
Run: runAzWhoamiCommand,
PersistentPreRun: func (cmd *cobra.Command, args []string) {},
}
AzInventoryCommand = &cobra.Command{
Use: "inventory",
@@ -52,12 +67,7 @@ Enumerate inventory for a specific tenant:
Enumerate inventory for a specific subscription:
./cloudfox az inventory --subscription SUBSCRIPTION_ID
`,
Run: func(cmd *cobra.Command, args []string) {
err := azure.AzInventoryCommand(AzTenantID, AzSubscription, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
if err != nil {
log.Fatal(err)
}
},
Run: runAzInventoryCommand,
}
AzRBACCommand = &cobra.Command{
Use: "rbac",
@@ -70,13 +80,7 @@ Enumerate role assignments for a specific tenant:
Enumerate role assignments for a specific subscription:
./cloudfox az rbac --subscription SUBSCRIPTION_ID
`,
Run: func(cmd *cobra.Command, args []string) {
err := azure.AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
if err != nil {
log.Fatal(err)
}
},
Run: runAzRBACCommand,
}
AzVMsCommand = &cobra.Command{
Use: "vms",
@@ -88,12 +92,7 @@ Enumerate VMs for a specific tenant:
Enumerate VMs for a specific subscription:
./cloudfox az vms --subscription SUBSCRIPTION_ID`,
Run: func(cmd *cobra.Command, args []string) {
err := azure.AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
if err != nil {
log.Fatal(err)
}
},
Run: runAzVMsCommand,
}
AzStorageCommand = &cobra.Command{
Use: "storage",
@@ -106,26 +105,182 @@ Enumerate storage accounts for a specific tenant:
Enumerate storage accounts for a specific subscription:
./cloudfox az storage --subscription SUBSCRIPTION_ID
`,
Run: func(cmd *cobra.Command, args []string) {
err := azure.AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
if err != nil {
log.Fatal(err)
}
},
Run: runAzStorageCommand,
}
AzNSGRulesCommand = &cobra.Command{
Use: "nsg-rules",
Aliases: []string{},
Short: "Enumerates azure Network Security Group rules",
Long: `
Enumerate Network Security Groups rules for a specific tenant:
./cloudfox az nsg-rukes --tenant TENANT_ID
Enumerate Network Security Groups rules for a specific subscription:
./cloudfox az nsg-rules --subscription SUBSCRIPTION_ID
Enumerate rules for a specific Network Security Group:
./cloudfox az nsg-rules --nsg NSG_ID
`,
Run: runAzNSGRulesCommand,
}
AzNSGLinksCommand = &cobra.Command{
Use: "nsg-links",
Aliases: []string{},
Short: "Enumerates azure Network Security Groups links",
Long: `
Enumerate Network Security Groups links for a specific tenant:
./cloudfox az nsg-links --tenant TENANT_ID
Enumerate Network Security Groups links for a specific subscription:
./cloudfox az nsg-links --subscription SUBSCRIPTION_ID
Enumerate links for a specific Network Security Group:
./cloudfox az nsg-links --nsg NSG_ID
`,
Run: runAzNSGLinksCommand,
}
AzNetScanCommand = &cobra.Command{
Use: "netscan",
Aliases: []string{},
Short: "Enumerates internal network targets",
Long: `
Enumerate targets with a specific resource as origin:
./cloudfox az nsg-links --resource-id /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}
Enumerate targets with a specific IP address as origin
./cloudfox az nsg-links --ipv4 A.B.C.D
`,
Run: runAzNetScanCommand,
}
)
func runAzWhoamiCommand (cmd *cobra.Command, args []string) {
AzClient = new(internal.AzureClient)
AzClient.Log = internal.NewLogger("azure")
AzClient.Log.Announce(nil, "Analyzing local Azure credentials")
AzClient.Version = cmd.Root().Version
AzClient.AzWrapTable = AzWrapTable
AzClient.AzMergedTable = AzMergedTable
AzClient.AzVerbosity = AzVerbosity
AzClient.AzOutputFormat = AzOutputFormat
AzClient.AzOutputDirectory = AzOutputDirectory
m := azure.AzWhoamiModule{
AzClient: AzClient,
Log: internal.NewLogger("whoami"),
}
err := m.AzWhoamiCommand(AzWhoamiListRGsAlso)
if err != nil {
m.Log.Fatal(nil, err.Error())
}
}
func runAzInventoryCommand (cmd *cobra.Command, args []string) {
m := azure.AzInventoryModule{
AzClient: AzClient,
Log: internal.NewLogger("inventory"),
}
err := m.AzInventoryCommand()
if err != nil {
m.Log.Fatal(nil, err.Error())
}
}
func runAzRBACCommand (cmd *cobra.Command, args []string) {
m := azure.AzRBACModule{
AzClient: AzClient,
Log: internal.NewLogger("rbac"),
}
err := m.AzRBACCommand()
if err != nil {
m.Log.Fatal(nil, err.Error())
}
}
func runAzVMsCommand (cmd *cobra.Command, args []string) {
m := azure.AzVMsModule{
AzClient: AzClient,
Log: internal.NewLogger("vms"),
}
err := m.AzVMsCommand()
if err != nil {
m.Log.Fatal(nil, err.Error())
}
}
func runAzStorageCommand (cmd *cobra.Command, args []string) {
m := azure.AzStorageModule{
AzClient: AzClient,
Log: internal.NewLogger("storage"),
}
err := m.AzStorageCommand()
if err != nil {
m.Log.Fatal(nil, err.Error())
}
}
func runAzNSGRulesCommand(cmd *cobra.Command, args []string) {
m := azure.AzNSGModule{
AzClient: AzClient,
Log: internal.NewLogger("nsg"),
}
err := m.AzNSGCommand("rules")
if err != nil {
m.Log.Fatal([]string{"rules"}, err.Error())
}
}
func runAzNSGLinksCommand(cmd *cobra.Command, args []string) {
m := azure.AzNSGModule{
AzClient: AzClient,
Log: internal.NewLogger("nsg"),
}
err := m.AzNSGCommand("links")
if err != nil {
m.Log.Fatal([]string{"links"}, err.Error())
}
}
func runAzNetScanCommand(cmd *cobra.Command, args []string) {
m := azure.AzNetScanModule{
AzClient: AzClient,
Log: internal.NewLogger("netscan"),
}
err := m.AzNetScanCommand(AzSourceIPv4)
if err != nil {
m.Log.Fatal(nil, err.Error())
}
}
func azurePreRun(cmd *cobra.Command, args []string) {
AzClient = internal.NewAzureClient(AzVerbosity, AzWrapTable, AzMergedTable, AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs, cmd, AzOutputFormat, AzOutputDirectory)
nTenants := len(AzClient.AzTenants)
nSubscriptions := len(AzClient.AzSubscriptions)
nRGs := len(AzClient.AzRGs)
nResources := len(AzClient.AzResources)
nTotal := nTenants + nSubscriptions + nRGs + nResources
if nTotal == 0 {
AzClient.Log.Fatal(nil, "No valid target supplied, stopping")
}
}
func init() {
AzWhoamiCommand.Flags().BoolVarP(&AzWhoamiListRGsAlso, "list-rgs", "l", false, "Drill down to the resource group level")
AzNetScanCommand.Flags().StringVar(&AzSourceIPv4, "ipv4", "", "The Source IP address to run the analysis from")
// Global flags
AzCommands.PersistentFlags().StringVarP(&AzOutputFormat, "output", "o", "all", "[\"table\" | \"csv\" | \"all\" ]")
AzCommands.PersistentFlags().StringVar(&AzOutputDirectory, "outdir", defaultOutputDir, "Output Directory ")
AzCommands.PersistentFlags().IntVarP(&AzVerbosity, "verbosity", "v", 2, "1 = Print control messages only\n2 = Print control messages, module output\n3 = Print control messages, module output, and loot file output\n")
AzCommands.PersistentFlags().StringVarP(&AzTenantID, "tenant", "t", "", "Tenant name")
AzCommands.PersistentFlags().StringVarP(&AzSubscription, "subscription", "s", "", "Subscription ID or Name")
AzCommands.PersistentFlags().StringVarP(&AzRGName, "resource-group", "g", "", "Resource Group name")
AzCommands.PersistentFlags().StringSliceVarP(&AzTenantRefs, "tenant", "t", []string{}, "Tenant ID or name, repeatable")
AzCommands.PersistentFlags().StringSliceVarP(&AzSubscriptionRefs, "subscription", "s", []string{}, "Subscription ID or name, repeatable")
AzCommands.PersistentFlags().StringSliceVarP(&AzRGRefs, "resource-group", "r", []string{}, "Resource Group name or ID, repeatable")
AzCommands.PersistentFlags().StringSliceVarP(&AzResourceRefs, "resource-id", "i", []string{}, "Resource ID (/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}), repeatable")
AzCommands.PersistentFlags().BoolVarP(&AzWrapTable, "wrap", "w", false, "Wrap table to fit in terminal (complicates grepping)")
AzCommands.PersistentFlags().BoolVarP(&AzMergedTable, "merged-table", "m", false, "Writes a single table for all subscriptions in the tenant. Default writes a table per subscription.")
@@ -134,6 +289,9 @@ func init() {
AzRBACCommand,
AzVMsCommand,
AzStorageCommand,
AzInventoryCommand)
AzNSGRulesCommand,
AzNSGLinksCommand,
AzInventoryCommand,
AzNetScanCommand)
}
+1
View File
@@ -0,0 +1 @@
1.13.0-prerelease
+2
View File
@@ -21,6 +21,8 @@ const AZ_INVENTORY_MODULE_NAME = "inventory"
const AZ_VMS_MODULE_NAME = "vms"
const AZ_RBAC_MODULE_NAME = "rbac"
const AZ_STORAGE_MODULE_NAME = "storage"
const AZ_NSG_LINKS_MODULE_NAME = "nsg-links"
const AZ_NSG_RULES_MODULE_NAME = "nsg-rules"
// Microsoft endpoints
const AZ_RESOURCE_MANAGER_ENDPOINT = "https://management.azure.com/"
+8
View File
@@ -1,6 +1,14 @@
package globals
import (
_ "embed"
"strings"
)
const CLOUDFOX_USER_AGENT = "cloudfox"
const CLOUDFOX_LOG_FILE_DIR_NAME = ".cloudfox"
const CLOUDFOX_BASE_DIRECTORY = "cloudfox-output"
const LOOT_DIRECTORY_NAME = "loot"
var CLOUDFOX_VERSION string = strings.TrimSpace(version)
//go:embed VERSION
var version string
+224
View File
@@ -3,6 +3,7 @@ package internal
import (
"fmt"
"log"
"context"
"github.com/Azure/azure-sdk-for-go/profiles/latest/authorization/mgmt/authorization"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
@@ -15,10 +16,146 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/azure/auth"
"github.com/BishopFox/cloudfox/globals"
"github.com/spf13/cobra"
)
type AzureClient struct {
AzVerbosity int
AzWrapTable bool
AzMergedTable bool
Version string
AzOutputFormat string
AzOutputDirectory string
AzTenants []*subscriptions.TenantIDDescription
AzSubscriptions []*subscriptions.Subscription
AzSubscriptionsAlt map[string][]*subscriptions.Subscription
AzRGs []*resources.Group
AzResources []*azure.Resource
Log *Logger
}
func (a *AzureClient) init (AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string){
availableSubscriptions := GetSubscriptions()
availableTenants := GetTenants()
// resource identifiers were submitted on the CLI, running modules on them only
if len(AzResourceRefs) > 0 {
a.Log.Info(nil, "Azure resource identifiers submitted, skipping submitted tenants and subscriptions")
// remove any other resource scope filter
a.AzTenants = nil
a.AzSubscriptions = nil
a.AzRGs = nil
var (
err error
resource azure.Resource
)
// check if the submitted resource can be reached with existing credentials
// this does not guarantees that the resource will be found since only the prefix of the resource ID
// is checked against the available ones
for _, azResourceRef := range AzResourceRefs {
resource, err = azure.ParseResourceID(azResourceRef)
if err != nil {
a.Log.Errorf(nil, "Invalid resource identifier : %s", azResourceRef)
continue
}
for _, subscription := range availableSubscriptions {
if resource.SubscriptionID == *subscription.SubscriptionID {
// add the resource to the final list of targets
a.AzResources = append(a.AzResources, &resource)
// also add the associated subscription since you cannot query a resource alone
a.AzSubscriptions = append(a.AzSubscriptions, &subscription)
goto FOUND_RESOURCE
}
}
a.Log.Errorf(nil, "No active credentials valid for resource %s, removing from target list", azResourceRef)
FOUND_RESOURCE:
}
} else if len(AzRGRefs) > 0 {
// resource groups were submitted on the CLI, running modules on them only
a.Log.Info(nil, "Azure subscriptions submitted, skipping submitted tenants")
a.AzTenants = nil
a.AzSubscriptions = nil
for _, AzRGRef := range AzRGRefs {
for _, subscription := range availableSubscriptions {
for _, rg := range GetResourceGroups(*subscription.SubscriptionID) {
if *rg.ID == AzRGRef {
a.AzRGs = append(a.AzRGs, &rg)
a.AzSubscriptions = append(a.AzSubscriptions, &subscription)
goto FOUND_RG
} else if *rg.Name == AzRGRef {
a.AzRGs = append(a.AzRGs, &rg)
a.AzSubscriptions = append(a.AzSubscriptions, &subscription)
goto FOUND_RG
}
}
}
a.Log.Warnf(nil, "Resource Group %s not accessible with active CLI credentials, removing from targets", AzRGRef)
FOUND_RG:
}
} else if len(AzSubscriptionRefs) > 0 {
// subscriptions were submitted on the CLI, running modules on them only
a.Log.Info(nil, "Azure subscriptions submitted, skipping submitted tenants")
// remove any other resource scope filter
a.AzTenants = nil
a.AzSubscriptionsAlt = make(map[string][]*subscriptions.Subscription, 0)
for _, AzSubscriptionRef := range AzSubscriptionRefs {
for _, subscription := range availableSubscriptions {
if (*subscription.SubscriptionID == AzSubscriptionRef) || (*subscription.DisplayName == AzSubscriptionRef) {
a.AzSubscriptions = append(a.AzSubscriptions, &subscription)
for _, AzTenant := range availableTenants {
if *subscription.TenantID == *AzTenant.TenantID {
a.AzSubscriptionsAlt[*AzTenant.DefaultDomain] = append(a.AzSubscriptionsAlt[*AzTenant.DefaultDomain], &subscription)
break
}
}
goto FOUND_SUB
}
}
a.Log.Warnf(nil, "Subscription %s not accessible with active CLI credentials, removing from targets", AzSubscriptionRef)
FOUND_SUB:
}
} else if len(AzTenantRefs) > 0 {
// tenants were submitted on the CLI, running modules on them only
for _, AzTenantRef := range AzTenantRefs {
for _, tenant := range availableTenants {
if *tenant.ID == AzTenantRef {
a.AzTenants = append(a.AzTenants, &tenant)
goto FOUND_TENANT
} else if *tenant.DefaultDomain == AzTenantRef {
a.AzTenants = append(a.AzTenants, &tenant)
goto FOUND_TENANT
}
}
a.Log.Warnf(nil, "Tenant %s not accessible with active CLI credentials, removing from targets", AzTenantRef)
FOUND_TENANT:
}
}
}
func NewAzureClient(AzVerbosity int, AzWrapTable, AzMergedTable bool, AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs []string, cmd *cobra.Command, AzOutputFormat, AzOutputDirectory string) *AzureClient {
client := new(AzureClient)
client.Log = NewLogger("azure")
client.Log.Announce(nil, "Searching existing credentials and validating submitted targets...")
client.Version = cmd.Root().Version
client.AzWrapTable = AzWrapTable
client.AzMergedTable = AzMergedTable
client.AzVerbosity = AzVerbosity
client.AzOutputFormat = AzOutputFormat
client.AzOutputDirectory = AzOutputDirectory
client.init(AzTenantRefs, AzSubscriptionRefs, AzRGRefs, AzResourceRefs)
return client
}
func getAuthorizer(endpoint string) (autorest.Authorizer, error) {
auth, err := auth.NewAuthorizerFromCLIWithResource(endpoint)
if err != nil {
@@ -126,6 +263,39 @@ func GetNICClient(subscriptionID string) network.InterfacesClient {
return client
}
func GetNSGClient(subscriptionID string) network.SecurityGroupsClient {
client := network.NewSecurityGroupsClient(subscriptionID)
authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT)
if err != nil {
log.Fatalf("failed to get nsg client: %s", err)
}
client.Authorizer = authorizer
client.AddToUserAgent(globals.CLOUDFOX_USER_AGENT)
return client
}
func GetSubnetsClient(subscriptionID string) network.SubnetsClient {
client := network.NewSubnetsClient(subscriptionID)
authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT)
if err != nil {
log.Fatalf("failed to get subnets client: %s", err)
}
client.Authorizer = authorizer
client.AddToUserAgent(globals.CLOUDFOX_USER_AGENT)
return client
}
func GetVNETsClient(subscriptionID string) network.VirtualNetworksClient {
client := network.NewVirtualNetworksClient(subscriptionID)
authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT)
if err != nil {
log.Fatalf("failed to get subnets client: %s", err)
}
client.Authorizer = authorizer
client.AddToUserAgent(globals.CLOUDFOX_USER_AGENT)
return client
}
func GetPublicIPClient(subscriptionID string) network.PublicIPAddressesClient {
client := network.NewPublicIPAddressesClient(subscriptionID)
authorizer, err := getAuthorizer(globals.AZ_RESOURCE_MANAGER_ENDPOINT)
@@ -171,3 +341,57 @@ func GetARMresourcesClient(tenantID, subscriptionID string) *armresources.Client
}
return client
}
func GetIPAddressesFromInterface(iface *network.Interface) []string{
var ipAddresses []string
for _, ipConfiguration := range *iface.InterfacePropertiesFormat.IPConfigurations {
if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddress != nil {
ipAddresses = append(ipAddresses, *ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PrivateIPAddress)
}
if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress != nil {
if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress.PublicIPAddressPropertiesFormat != nil {
if ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress.PublicIPAddressPropertiesFormat.IPAddress != nil {
ipAddresses = append(ipAddresses, *ipConfiguration.InterfaceIPConfigurationPropertiesFormat.PublicIPAddress.PublicIPAddressPropertiesFormat.IPAddress)
}
}
}
}
return ipAddresses
}
func GetSubscriptions() []subscriptions.Subscription {
var results []subscriptions.Subscription
subsClient := GetSubscriptionsClient()
for page, err := subsClient.List(context.TODO()); page.NotDone(); err = page.Next() {
if err != nil {
log.Fatal("could not get subscriptions for active session")
}
results = append(results, page.Values()...)
}
return results
}
func GetTenants() []subscriptions.TenantIDDescription {
tenantsClient := GetTenantsClient()
var results []subscriptions.TenantIDDescription
for page, err := tenantsClient.List(context.TODO()); page.NotDone(); err = page.Next() {
if err != nil {
log.Fatal("could not get tenants for active session")
}
results = append(results, page.Values()...)
}
return results
}
func GetResourceGroups(subscriptionID string) []resources.Group {
var results []resources.Group
rgClient := GetResourceGroupsClient(subscriptionID)
for page, err := rgClient.List(context.TODO(), "", nil); page.NotDone(); err = page.Next() {
if err != nil {
log.Fatalf("error reading resource groups for subscription %s", subscriptionID)
}
results = append(results, page.Values()...)
}
return results
}
+109
View File
@@ -2,6 +2,7 @@ package internal
import (
"log"
"fmt"
"os"
"os/user"
"path/filepath"
@@ -9,6 +10,9 @@ import (
"github.com/BishopFox/cloudfox/globals"
"github.com/aws/smithy-go/ptr"
"github.com/jedib0t/go-pretty/text"
"github.com/kyokomi/emoji"
"github.com/fatih/color"
"github.com/sirupsen/logrus"
)
func init() {
@@ -30,3 +34,108 @@ func GetLogDirPath() *string {
}
return ptr.String(dir)
}
type Logger struct {
version string
module string
txtLog *logrus.Logger
Cyan func(...interface{}) string
Red func(...interface{}) string
Green func(...interface{}) string
Yellow func(...interface{}) string
}
func NewLogger(module string) *Logger {
lootLogger := logrus.New()
logDirPath := GetLogDirPath()
logFile, err := os.OpenFile(filepath.Join(*logDirPath, "cloudfox.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic("Could not initiate logger")
}
lootLogger.Out = logFile
lootLogger.SetLevel(logrus.InfoLevel)
var logger = Logger{
version: globals.CLOUDFOX_VERSION,
txtLog: lootLogger,
module: module,
Cyan: color.New(color.FgCyan).SprintFunc(),
Red: color.New(color.FgRed).SprintFunc(),
Green:color.New(color.FgGreen).SprintFunc(),
Yellow: color.New(color.FgYellow).SprintFunc(),
}
return &logger
}
func (l *Logger) Print(categories []string, color func(...interface{}) string, text string) {
formatString := text
formatArgs := []any{}
l.Printf(categories, color, formatString, formatArgs...)
}
func (l *Logger) Printf(categories []string, color func(...interface{}) string, format string, params ...any) {
formatString := ""
formatArgs := []any{}
blocks := []string{}
blocks = append(blocks, categories...)
for _, block := range blocks {
formatString += "[%s]"
formatArgs = append(formatArgs, color(block))
}
for _, param := range params {
formatArgs = append(formatArgs, color(param))
}
formatString += " " + format + "\n"
fmt.Printf(formatString, formatArgs...)
}
func (l *Logger) Announce(categories []string, text string) {
l.Print(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Cyan, text)
}
func (l *Logger) Announcef(categories []string, format string, params ...any) {
l.Printf(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Cyan, format, params...)
}
func (l *Logger) Info(categories []string, text string) {
l.Print(append([]string{l.module}, categories...), l.Cyan, text)
}
func (l *Logger) Infof(categories []string, format string, params ...any) {
l.Printf(append([]string{l.module}, categories...), l.Cyan, format, params...)
}
func (l *Logger) Success(categories []string, text string) {
l.Print(append([]string{l.module}, categories...), l.Green, text)
}
func (l *Logger) Successf(categories []string, format string, params ...any) {
l.Printf(append([]string{l.module}, categories...), l.Green, format, params...)
}
func (l *Logger) Warn(categories []string, text string) {
l.Print(append([]string{l.module}, categories...), l.Yellow, text)
}
func (l *Logger) Warnf(categories []string, format string, params ...any) {
l.Printf(append([]string{l.module}, categories...), l.Yellow, format, params...)
}
func (l *Logger) Error(categories []string, text string) {
l.Print(append([]string{l.module}, categories...), l.Red, text)
}
func (l *Logger) Errorf(categories []string, format string, params ...any) {
l.Printf(append([]string{l.module}, categories...), l.Red, format, params...)
}
func (l *Logger) Fatal(categories []string, text string) {
l.Print(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Red, text)
panic(nil)
}
func (l *Logger) Fatalf(categories []string, format string, params ...any) {
l.Printf(append([]string{emoji.Sprintf(":fox:cloudfox v%s :fox:", l.version), l.module}, categories...), l.Red, format, params...)
panic(nil)
}
+2 -1
View File
@@ -4,13 +4,14 @@ import (
"os"
"github.com/BishopFox/cloudfox/cli"
"github.com/BishopFox/cloudfox/globals"
"github.com/spf13/cobra"
)
var (
rootCmd = &cobra.Command{
Use: os.Args[0],
Version: "1.13.1-prerelease",
Version: globals.CLOUDFOX_VERSION,
}
)