Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions aws/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var yellow = color.New(color.FgRed).SprintFunc()
var blue = color.New(color.FgBlue).SprintFunc()
var magenta = color.New(color.FgMagenta).SprintFunc()
var green = color.New(color.FgGreen).SprintFunc()
var AWSRegions = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "af-south-1", "ap-east-1", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-south-1", "eu-west-3", "eu-north-1", "me-south-1", "sa-east-1"}
var AWSRegions = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "af-south-1", "ap-east-1", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-south-1", "eu-west-3", "eu-north-1", "sa-east-1"}
var sharedLogger = internal.TxtLogger()

func GetIamSimResult(SkipAdminCheck bool, roleArnPtr *string, iamSimulatorMod IamSimulatorModule, localAdminMap map[string]bool) (string, string) {
Expand Down Expand Up @@ -54,12 +54,6 @@ func GetIamSimResult(SkipAdminCheck bool, roleArnPtr *string, iamSimulatorMod Ia
}
}

if isRoleAdminBool {
adminRole = "YES"
} else {
adminRole = "No"
}

} else {
adminRole = "Skipped"
canRolePrivEsc = "Skipped"
Expand Down
9 changes: 7 additions & 2 deletions cli/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ var (
AWSUseCache bool
AWSMFAToken string

Goroutines int
Verbosity int
Goroutines int
Verbosity int
AWSMaxRetries int

AWSCommands = &cobra.Command{
Use: "aws",
Expand Down Expand Up @@ -546,6 +547,9 @@ type OrgAccounts struct {
func awsPreRun(cmd *cobra.Command, args []string) {
gob.Register(&types.Organization{})

// Set the max retries before any AWS config loading happens
internal.MaxRetries = AWSMaxRetries

// if multiple profiles were used, ensure the management account is first
// if AWSProfilesList != "" || AWSAllProfiles {
// AWSProfiles = FindOrgMgmtAccountAndReorderAccounts(AWSProfiles, cmd.Root().Version, AWSMFAToken)
Expand Down Expand Up @@ -2553,6 +2557,7 @@ func init() {
AWSCommands.PersistentFlags().BoolVarP(&AWSUseCache, "cached", "c", false, "Load cached data from disk. Faster, but if changes have been recently made you'll miss them")
AWSCommands.PersistentFlags().StringVarP(&AWSTableCols, "cols", "t", "", "Comma separated list of columns to display in table output")
AWSCommands.PersistentFlags().StringVar(&AWSMFAToken, "mfa-token", "", "MFA Token")
AWSCommands.PersistentFlags().IntVar(&AWSMaxRetries, "max-retries", 3, "Maximum number of AWS API retry attempts. Set to 0 for no retries (faster for black-box/limited-permission enumeration)")
AWSCommands.PersistentFlags().StringVar(&PmapperDataBasePath, "pmapper-data-basepath", "", "Supply the base path for the pmapper data files (useful if you have copied them from another machine)\nPoint to the parent directory that contains all of the pmapper data by account numbers. \n\tExample: /path/to/com.nccgroup.principalmapper/\n\tExample: ./pmapperdata/")

AWSCommands.AddCommand(
Expand Down
2 changes: 1 addition & 1 deletion globals/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ const CLOUDFOX_USER_AGENT = "cloudfox"
const CLOUDFOX_LOG_FILE_DIR_NAME = ".cloudfox"
const CLOUDFOX_BASE_DIRECTORY = "cloudfox-output"
const LOOT_DIRECTORY_NAME = "loot"
const CLOUDFOX_VERSION = "2.0.5"
const CLOUDFOX_VERSION = "2.0.6"
80 changes: 77 additions & 3 deletions internal/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (
"encoding/json"
"fmt"
"io"
"net"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"sync/atomic"
"time"

Expand All @@ -34,6 +36,23 @@ var (
UtilsFs = afero.NewOsFs()
credsMap = map[string]aws.Credentials{}
ConfigMap = map[string]aws.Config{}

// MaxRetries controls the maximum number of retry attempts for AWS API calls.
// The default AWS SDK behavior is 3 attempts. Set to 0 for no retries, which
// is useful when running with limited-permission credentials to avoid slow
// timeouts on access-denied errors.
MaxRetries = 3

// unreachableRegions tracks regions that failed a TCP connectivity check so
// they can be skipped for the remainder of the run. Protected by a mutex
// since multiple goroutines may probe regions concurrently.
unreachableRegions = make(map[string]bool)
unreachableRegionsMu sync.RWMutex

// RegionReachabilityTimeout controls how long the TCP probe waits before
// declaring a region unreachable. Default 3 seconds is enough for any
// healthy AWS endpoint.
RegionReachabilityTimeout = 3 * time.Second
)

type CloudFoxRunData struct {
Expand Down Expand Up @@ -132,7 +151,7 @@ func AWSConfigFileLoader(AWSProfile string, version string, AwsMfaToken string)
if AwsMfaToken != "" {
cfg, err = config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(AWSProfile), config.WithDefaultRegion("us-east-1"), config.WithRetryer(
func() aws.Retryer {
return retry.AddWithMaxAttempts(retry.NewStandard(), 3)
return retry.AddWithMaxAttempts(retry.NewStandard(), MaxRetries)
}), config.WithAssumeRoleCredentialOptions(func(options *stscreds.AssumeRoleOptions) {
options.TokenProvider = func() (string, error) {
return AwsMfaToken, nil
Expand All @@ -142,7 +161,7 @@ func AWSConfigFileLoader(AWSProfile string, version string, AwsMfaToken string)
} else {
cfg, err = config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(AWSProfile), config.WithDefaultRegion("us-east-1"), config.WithRetryer(
func() aws.Retryer {
return retry.AddWithMaxAttempts(retry.NewStandard(), 3)
return retry.AddWithMaxAttempts(retry.NewStandard(), MaxRetries)
}), config.WithAssumeRoleCredentialOptions(func(options *stscreds.AssumeRoleOptions) {
options.TokenProvider = stscreds.StdinTokenProvider
}),
Expand Down Expand Up @@ -210,6 +229,58 @@ func AWSWhoami(awsProfile string, version string, AwsMfaToken string) (*sts.GetC
return CallerIdentity, err
}

// isRegionReachable does a fast TCP dial to the STS endpoint in the given region.
// STS is available in every region and is a lightweight endpoint to probe. If the
// connection succeeds, the region is reachable. Results are cached so each region
// is only probed once per run.
func isRegionReachable(region string) bool {
unreachableRegionsMu.RLock()
alreadyUnreachable := unreachableRegions[region]
unreachableRegionsMu.RUnlock()
if alreadyUnreachable {
return false
}

endpoint := fmt.Sprintf("sts.%s.amazonaws.com:443", region)
conn, err := net.DialTimeout("tcp", endpoint, RegionReachabilityTimeout)
if err != nil {
unreachableRegionsMu.Lock()
unreachableRegions[region] = true
unreachableRegionsMu.Unlock()
TxtLog.Warnf("Region %s is unreachable (probe to %s failed: %v) - skipping for this run", region, endpoint, err)
return false
}
conn.Close()
return true
}

// FilterReachableRegions takes a list of regions and returns only the ones that
// pass a TCP connectivity probe. Unreachable regions are probed in parallel and
// cached so subsequent calls return immediately.
func FilterReachableRegions(regions []string) []string {
// Probe all regions concurrently for speed
type probeResult struct {
region string
reachable bool
}
resultsChan := make(chan probeResult, len(regions))

for _, region := range regions {
go func(r string) {
resultsChan <- probeResult{region: r, reachable: isRegionReachable(r)}
}(region)
}

var reachableRegions []string
for range regions {
result := <-resultsChan
if result.reachable {
reachableRegions = append(reachableRegions, result.region)
}
}
return reachableRegions
}

func GetEnabledRegions(awsProfile string, version string, AwsMfaToken string) []string {
cacheKey := fmt.Sprintf("GetEnabledRegions-%s", awsProfile)
cached, found := Cache.Get(cacheKey)
Expand All @@ -234,12 +305,15 @@ func GetEnabledRegions(awsProfile string, version string, AwsMfaToken string) []
if err != nil {
TxtLog.Println(err)
}
return AWSRegions
reachableRegions := FilterReachableRegions(AWSRegions)
Cache.Set(cacheKey, reachableRegions, cache.DefaultExpiration)
return reachableRegions
}

for _, region := range regions.Regions {
enabledRegions = append(enabledRegions, *region.RegionName)
}
enabledRegions = FilterReachableRegions(enabledRegions)
Cache.Set(cacheKey, enabledRegions, cache.DefaultExpiration)
return enabledRegions

Expand Down