Skip to content

Commit

Permalink
Merge pull request #67 from BishopFox/seth-dev
Browse files Browse the repository at this point in the history
v1.13.0
  • Loading branch information
sethsec-bf authored Dec 26, 2023
2 parents 26cb997 + f1b6eae commit 7520999
Show file tree
Hide file tree
Showing 16 changed files with 1,138 additions and 754 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/codespell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ jobs:
steps:
- uses: actions/checkout@v4
- run: pip install --user codespell
- run: codespell --ignore-words-list="aks" # --skip="*.css,*.js,*.lock,*.po"
- run: codespell --ignore-words-list="aks" --skip="*.sum"
2 changes: 1 addition & 1 deletion aws/buckets.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ func (m *BucketsModule) isPublicAccessBlocked(bucketName string, r string) bool
if err != nil {
return false
}
return publicAccessBlock.IgnorePublicAcls
return aws.ToBool(publicAccessBlock.IgnorePublicAcls) && aws.ToBool(publicAccessBlock.BlockPublicPolicy) && aws.ToBool(publicAccessBlock.RestrictPublicBuckets)

}

Expand Down
8 changes: 4 additions & 4 deletions aws/buckets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ func (m *MockedS3Client) GetBucketLocation(ctx context.Context, params *s3.GetBu
func (m *MockedS3Client) GetPublicAccessBlock(ctx context.Context, params *s3.GetPublicAccessBlockInput, optFns ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) {
output := &s3.GetPublicAccessBlockOutput{
PublicAccessBlockConfiguration: &types.PublicAccessBlockConfiguration{
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: false,
RestrictPublicBuckets: true,
BlockPublicAcls: aws.Bool(true),
BlockPublicPolicy: aws.Bool(true),
IgnorePublicAcls: aws.Bool(false),
RestrictPublicBuckets: aws.Bool(true),
},
}
return output, nil
Expand Down
87 changes: 75 additions & 12 deletions aws/env-vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type EnvironmentVariable struct {
region string
environmentVarName string
environmentVarValue string
interesting bool
}

func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
Expand Down Expand Up @@ -161,19 +162,35 @@ func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
}
}

m.determineIfInteresting()
//Table rows
for _, envVar := range m.EnvironmentVariables {
m.output.Body = append(
m.output.Body, []string{
aws.ToString(m.Caller.Account),
envVar.service,
envVar.region,
envVar.name,
envVar.environmentVarName,
envVar.environmentVarValue,
},
)

if envVar.interesting {
m.output.Body = append(
m.output.Body, []string{
aws.ToString(m.Caller.Account),
envVar.service,
envVar.region,
envVar.name,
magenta(envVar.environmentVarName),
magenta(envVar.environmentVarValue),
},
)
} else {
m.output.Body = append(
m.output.Body, []string{
aws.ToString(m.Caller.Account),
envVar.service,
envVar.region,
envVar.name,
envVar.environmentVarName,
envVar.environmentVarValue,
},
)
}
}

if len(m.output.Body) > 0 {

m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
Expand All @@ -185,14 +202,27 @@ func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
Wrap: m.WrapTable,
},
}

o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))

o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))

// Create another table file that only contains the environment variables that seem to be secrets.
body := m.interestingEnvVarsOnly()
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
Header: m.output.Headers,
Body: body,
TableCols: tableCols,
Name: fmt.Sprintf("%s-interesting", m.output.CallingModule),
SkipPrintToScreen: true,
})

o.WriteFullOutput(o.Table.TableFiles, nil)
fmt.Printf("[%s][%s] %s environment variables found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))

Expand All @@ -202,6 +232,20 @@ func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
}

func (m *EnvsModule) determineIfInteresting() {
for i, envVar := range m.EnvironmentVariables {
// If the environment variable name contains the word secret, key, token, or password, we'll make it magenta to make it stand out.
lowerEnvVar := strings.ToLower(envVar.environmentVarName)
if (strings.Contains(lowerEnvVar, "secret") && !strings.Contains(lowerEnvVar, "arn")) ||
strings.Contains(lowerEnvVar, "key") ||
strings.Contains(lowerEnvVar, "token") ||
strings.Contains(lowerEnvVar, "user") ||
strings.Contains(lowerEnvVar, "pass") {
m.EnvironmentVariables[i].interesting = true
}
}
}

func EnvVarsContains(element EnvironmentVariable, array []EnvironmentVariable) bool {
for _, v := range array {
if v == element {
Expand Down Expand Up @@ -786,3 +830,22 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn

}
}

func (m *EnvsModule) interestingEnvVarsOnly() [][]string {
var interestingBody [][]string
for _, envVar := range m.EnvironmentVariables {
if envVar.interesting {
interestingBody = append(
m.output.Body, []string{
aws.ToString(m.Caller.Account),
envVar.service,
envVar.region,
envVar.name,
magenta(envVar.environmentVarName),
magenta(envVar.environmentVarValue),
},
)
}
}
return interestingBody
}
44 changes: 35 additions & 9 deletions aws/iam-simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
"module": m.output.CallingModule,
})
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
var filename string
var actionList []string
var pmapperCommands []string
var pmapperOutFileName string
var inputArn string

if m.AWSProfile == "" {
m.AWSProfile = internal.BuildAWSPath(m.Caller)
Expand All @@ -103,35 +105,59 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
if action != "" {
// The user specified a specific --principal and a specific --action
fmt.Printf("[%s][%s] Checking to see if %s can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), principal, action)
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
actionList = append(actionList, action)
m.getPolicySimulatorResult((&principal), actionList, resource, dataReceiver)
// if user supplied a principal name without the arn, try to create the arn as a user and as a role and run both
if !strings.Contains(principal, "arn:") {
// try as a role
inputArn = fmt.Sprintf("arn:aws:iam::%s:role/%s", aws.ToString(m.Caller.Account), principal)
m.getPolicySimulatorResult((&inputArn), actionList, resource, dataReceiver)
// try as a user
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
m.getPolicySimulatorResult((&inputArn), actionList, resource, dataReceiver)
} else {
// the arn was supplied so just run it
m.getPolicySimulatorResult((&principal), actionList, resource, dataReceiver)
}

} else {
// The user specified a specific --principal, but --action was empty
fmt.Printf("[%s][%s] Checking to see if %s can do any actions of interest.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), principal)
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
m.getPolicySimulatorResult((&principal), defaultActionNames, resource, dataReceiver)
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))

// if user supplied a principal name without the arn, try to create the arn as a user and as a role and run both
if !strings.Contains(principal, "arn:") {
// try as a role
inputArn = fmt.Sprintf("arn:aws:iam::%s:role/%s", aws.ToString(m.Caller.Account), principal)
m.getPolicySimulatorResult((&inputArn), defaultActionNames, resource, dataReceiver)
// try as a user
inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal)
m.getPolicySimulatorResult((&inputArn), defaultActionNames, resource, dataReceiver)
} else {
// the arn was supplied so just run it
m.getPolicySimulatorResult((&principal), defaultActionNames, resource, dataReceiver)
}

}
} else {
if action != "" {
// The did not specify a specific --principal, but they did specify an --action
fmt.Printf("[%s][%s] Checking to see if any principal can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), action)
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
actionList = append(actionList, action)
wg.Add(1)
m.getIAMUsers(wg, actionList, resource, dataReceiver)
wg.Add(1)
m.getIAMRoles(wg, actionList, resource, dataReceiver)
pmapperOutFileName = filepath.Join(m.output.FullFilename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
pmapperOutFileName = filepath.Join(filename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfile, action, resource, pmapperOutFileName))
} else {
// Both --principal and --action are empty. Run in default mode!
fmt.Printf("[%s][%s] Running multiple iam-simulator queries for account %s. (This command can be pretty slow, FYI)\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
m.output.FullFilename = m.output.CallingModule
filename = m.output.CallingModule
m.executeChecks(wg, resource, dataReceiver)
for _, action := range defaultActionNames {
pmapperOutFileName = filepath.Join(m.output.FullFilename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
pmapperOutFileName = filepath.Join(filename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfile, action, resource, pmapperOutFileName))
}

Expand Down Expand Up @@ -212,7 +238,7 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
Header: m.output.Headers,
Body: m.output.Body,
TableCols: tableCols,
Name: m.output.CallingModule,
Name: filename,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
Expand Down
53 changes: 27 additions & 26 deletions aws/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/base64"
"fmt"
"log"
"os"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -490,9 +491,8 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin

var profile string
var externalIP string
var name string = ""
var adminRole string = ""
var roleArn string = ""
var profileArn, profileName, name string

// The name is in a tag so we have to do this to grab the value from the right tag
for _, tag := range instance.Tags {
Expand All @@ -513,33 +513,34 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin
if instance.IamInstanceProfile == nil {
profile = "NoInstanceProfile"
} else {
// This returns only the role name without the preceding forward slash.
profileARN := aws.ToString(instance.IamInstanceProfile.Arn)
profileID := aws.ToString(instance.IamInstanceProfile.Id)
profile = strings.Split(profileARN, "/")[len(strings.Split(profileARN, "/"))-1]

if roles, ok := m.InstanceProfileToRolesMap[profileID]; ok {
for _, role := range roles {
roleArn = aws.ToString(role.Arn)
}
profileArn = aws.ToString(instance.IamInstanceProfile.Arn)

// Extracting instance profile name from ARN
profileName = strings.Split(profileArn, "/")[len(strings.Split(profileArn, "/"))-1]

// Describe the IAM instance profile
profileOutput, err := sdk.CachedIamGetInstanceProfile(m.IAMClient, aws.ToString(m.Caller.Account), profileName)
if err != nil {
log.Printf("failed to get instance profile for %s, %v", profileArn, err)
}

for _, role := range profileOutput.Roles {
dataReceiver <- MappedInstance{
ID: aws.ToString(instance.InstanceId),
Name: aws.ToString(&name),
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
State: string(instance.State.Name),
ExternalIP: externalIP,
PrivateIP: aws.ToString(instance.PrivateIpAddress),
Profile: profile,
Role: aws.ToString(role.Arn),
Region: region,
Admin: adminRole,
CanPrivEsc: "",
}
}
}
dataReceiver <- MappedInstance{
ID: aws.ToString(instance.InstanceId),
Name: aws.ToString(&name),
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
State: string(instance.State.Name),
ExternalIP: externalIP,
PrivateIP: aws.ToString(instance.PrivateIpAddress),
Profile: profile,
Role: roleArn,
Region: region,
Admin: adminRole,
CanPrivEsc: "",
}

}

func (m *InstancesModule) getRolesFromInstanceProfiles() {
Expand Down
6 changes: 3 additions & 3 deletions aws/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputDirectory string, verbo
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "permissions"
m.output.FullFilename = m.output.CallingModule
filename := m.output.CallingModule
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
Expand All @@ -101,7 +101,7 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputDirectory string, verbo
fmt.Printf("[%s][%s] Enumerating IAM permissions for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))

if principal != "" {
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
}

m.getGAAD()
Expand Down Expand Up @@ -192,7 +192,7 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputDirectory string, verbo
Header: m.output.Headers,
TableCols: tableCols,
Body: m.output.Body,
Name: m.output.CallingModule,
Name: filename,
})
o.PrefixIdentifier = m.AWSProfile
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
Expand Down
Loading

0 comments on commit 7520999

Please sign in to comment.