mirror of
https://github.com/BishopFox/cloudfox.git
synced 2026-11-09 17:07:17 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7d1cbcdb6 | ||
|
|
b3c25bb5d0 | ||
|
|
9076beb111 | ||
|
|
878d7ec0c4 | ||
|
|
ebf9985d40 | ||
|
|
8e1e8d0c0a | ||
|
|
a88e98aedd | ||
|
|
85b9b11798 | ||
|
|
6b0970ebd7 | ||
|
|
e0dc71b414 | ||
|
|
b31f36746b | ||
|
|
0a95240b7f | ||
|
|
785b69316a |
No files matched your search
+1
-1
@@ -301,7 +301,7 @@ func preloadData(filePaths []string) (*AllAccountData, error) {
|
||||
|
||||
for _, filePath := range filePaths {
|
||||
fileRecords, err := loadFileRecords(filePath)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appData.Files[filePath] = fileRecords
|
||||
|
||||
+73
-5
@@ -1,7 +1,9 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -200,7 +202,12 @@ func (m *CapeCommand) findPathsToThisDestination(allGlobalNodes map[string]map[s
|
||||
s, sourceVertexWithProperties, _ := m.GlobalGraph.VertexWithProperties(source)
|
||||
//for the source vertex, we only want to deal with the ones that are NOT in this account
|
||||
if sourceVertexWithProperties.Attributes["AccountID"] != aws.ToString(m.Caller.Account) {
|
||||
// skip if the source Name contains AWSSSO-
|
||||
if strings.Contains(sourceVertexWithProperties.Attributes["Name"], "AWSSSO-") {
|
||||
continue
|
||||
}
|
||||
// now let's see if there is a path from this source to our destination
|
||||
|
||||
path, _ := graph.ShortestPath(m.GlobalGraph, s, d)
|
||||
// if we have a path, then lets document this source as having a path to our destination
|
||||
if path != nil {
|
||||
@@ -235,7 +242,7 @@ func (m *CapeCommand) findPathsToThisDestination(allGlobalNodes map[string]map[s
|
||||
privescPathsBody = append(privescPathsBody, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
s,
|
||||
magenta(d),
|
||||
d,
|
||||
magenta(destinationVertexWithProperties.Attributes["IsAdminString"]),
|
||||
paths})
|
||||
} else {
|
||||
@@ -289,7 +296,7 @@ func ConvertIAMRoleToNode(role types.Role, vendors *knownawsaccountslookup.Vendo
|
||||
|
||||
TrustedPrincipals = append(TrustedPrincipals, TrustedPrincipal{
|
||||
TrustedPrincipal: principal,
|
||||
ExternalID: statement.Condition.StringEquals.StsExternalID,
|
||||
ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"),
|
||||
VendorName: vendorName,
|
||||
//IsAdmin: false,
|
||||
//CanPrivEscToAdmin: false,
|
||||
@@ -650,6 +657,48 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) {
|
||||
}
|
||||
}
|
||||
|
||||
// if the role trusts a principal in another account explicitly, then the principal can assume the role
|
||||
if thisAccount != trustedPrincipalAccount {
|
||||
// make a CAN_ASSUME relationship between the trusted principal and this role
|
||||
|
||||
err := GlobalGraph.AddEdge(
|
||||
TrustedPrincipal.TrustedPrincipal,
|
||||
a.Arn,
|
||||
//graph.EdgeAttribute("AssumeRole", "Cross account explicit trust"),
|
||||
graph.EdgeAttribute("AssumeRole", "can assume (because of an explicit cross account trust) "),
|
||||
)
|
||||
if err != nil {
|
||||
//fmt.Println(err)
|
||||
//fmt.Println(TrustedPrincipal.TrustedPrincipal + a.Arn + "Cross account explicit trust")
|
||||
if err == graph.ErrEdgeAlreadyExists {
|
||||
// update the edge by copying the existing graph.Edge with attributes and add the new attributes
|
||||
//fmt.Println("Edge already exists")
|
||||
|
||||
// get the existing edge
|
||||
existingEdge, _ := GlobalGraph.Edge(TrustedPrincipal.TrustedPrincipal, a.Arn)
|
||||
// get the map of attributes
|
||||
existingProperties := existingEdge.Properties
|
||||
// add the new attributes to attributes map within the properties struct
|
||||
// Check if the Attributes map is initialized, if not, initialize it
|
||||
if existingProperties.Attributes == nil {
|
||||
existingProperties.Attributes = make(map[string]string)
|
||||
}
|
||||
|
||||
// Add or update the attribute
|
||||
existingProperties.Attributes["AssumeRole"] = "can assume (because of an explicit cross account trust) "
|
||||
err = GlobalGraph.UpdateEdge(
|
||||
TrustedPrincipal.TrustedPrincipal,
|
||||
a.Arn,
|
||||
graph.EdgeAttributes(existingProperties.Attributes),
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// If the role trusts a principal in this account or another account using the :root notation, then we need to iterate over all of the rows in AllPermissionsRows to find the principals that have sts:AssumeRole permissions on this role
|
||||
// if the role we are looking at trusts root in it's own account
|
||||
|
||||
@@ -667,6 +716,7 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) {
|
||||
if PermissionsRowAccount == thisAccount {
|
||||
// lets only look for rows that have sts:AssumeRole permissions
|
||||
if policy.MatchesAfterExpansion(PermissionsRow.Action, "sts:AssumeRole") {
|
||||
|
||||
// lets only focus on rows that have an effect of Allow
|
||||
if strings.EqualFold(PermissionsRow.Effect, "Allow") {
|
||||
// if the resource is * or the resource is this role arn, then this principal can assume this role
|
||||
@@ -820,10 +870,10 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) {
|
||||
fmt.Sprintf("Could not get account number from this PermissionsRow%s", PermissionsRow.Arn)
|
||||
}
|
||||
if PermissionsRowAccount == trustedPrincipalAccount {
|
||||
// lets only look for rows that have sts:AssumeRole permissions
|
||||
if policy.MatchesAfterExpansion(PermissionsRow.Action, "sts:AssumeRole") {
|
||||
// lets only look for rows that have sts:AssumeRole permis sions
|
||||
if policy.MatchesAfterExpansion("sts:AssumeRole", PermissionsRow.Action) {
|
||||
// if strings.EqualFold(PermissionsRow.Action, "sts:AssumeRole") ||
|
||||
// strings.EqualFold(PermissionsRow.Action, "*") ||
|
||||
// strings.EqualFold(PermissionsRow.Action, "*") {
|
||||
// strings.EqualFold(PermissionsRow.Action, "sts:Assume*") ||
|
||||
// strings.EqualFold(PermissionsRow.Action, "sts:*") {
|
||||
// lets only focus on rows that have an effect of Allow
|
||||
@@ -979,3 +1029,21 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// function to read file specified in CapeArnIgnoreList which is separated by newlines, and convert it to a slice of strings with each line as an entry in the slice.
|
||||
// the function accepts a string with the filename
|
||||
|
||||
func ReadArnIgnoreListFile(filename string) ([]string, error) {
|
||||
var arnIgnoreList []string
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return arnIgnoreList, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
arnIgnoreList = append(arnIgnoreList, scanner.Text())
|
||||
}
|
||||
return arnIgnoreList, scanner.Err()
|
||||
}
|
||||
+1
-1
@@ -331,7 +331,7 @@ func (m *GraphCommand) collectRoleDataForGraph() []models.Role {
|
||||
|
||||
TrustedPrincipals = append(TrustedPrincipals, models.TrustedPrincipal{
|
||||
TrustedPrincipal: principal,
|
||||
ExternalID: statement.Condition.StringEquals.StsExternalID,
|
||||
ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"),
|
||||
VendorName: vendorName,
|
||||
//IsAdmin: false,
|
||||
//CanPrivEscToAdmin: false,
|
||||
|
||||
+15
-5
@@ -33,8 +33,9 @@ type IamSimulatorModule struct {
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
SimulatorResults []SimulatorResult
|
||||
CommandCounter internal.CommandCounter
|
||||
SimulatorResults []SimulatorResult
|
||||
CommandCounter internal.CommandCounter
|
||||
IamSimulatorAdminCheckOnly bool
|
||||
// Used to store output data for pretty printing
|
||||
output internal.OutputData2
|
||||
modLog *logrus.Entry
|
||||
@@ -104,6 +105,11 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
|
||||
go m.Receiver(dataReceiver, receiverDone)
|
||||
|
||||
if m.IamSimulatorAdminCheckOnly {
|
||||
// set defaultActionNames to an empty slice
|
||||
defaultActionNames = []string{}
|
||||
}
|
||||
|
||||
// This double if/else section is here to handle the cases where --principal or --action (or both) are specified.
|
||||
if principal != "" {
|
||||
if action != "" {
|
||||
@@ -354,7 +360,9 @@ func (m *IamSimulatorModule) getIAMUsers(wg *sync.WaitGroup, actions []string, r
|
||||
Decision: "",
|
||||
}
|
||||
} else {
|
||||
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
|
||||
if !m.IamSimulatorAdminCheckOnly {
|
||||
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -394,7 +402,9 @@ func (m *IamSimulatorModule) getIAMRoles(wg *sync.WaitGroup, actions []string, r
|
||||
Decision: "",
|
||||
}
|
||||
} else {
|
||||
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
|
||||
if !m.IamSimulatorAdminCheckOnly {
|
||||
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -443,7 +453,7 @@ func (m *IamSimulatorModule) isPrincipalAnAdmin(principal *string) bool {
|
||||
"iam:PutRolePolicy",
|
||||
"iam:AttachRolePolicy",
|
||||
"secretsmanager:GetSecretValue",
|
||||
"ssm:GetDocument",
|
||||
"ssm:GetParameters",
|
||||
}
|
||||
for {
|
||||
SimulatePrincipalPolicy, err := m.IAMClient.SimulatePrincipalPolicy(
|
||||
|
||||
@@ -26,6 +26,12 @@ type IamPrincipalsModule struct {
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
SkipAdminCheck bool
|
||||
iamSimClient IamSimulatorModule
|
||||
pmapperMod PmapperModule
|
||||
pmapperError error
|
||||
PmapperDataBasePath string
|
||||
|
||||
// Main module data
|
||||
Users []User
|
||||
Roles []Role
|
||||
@@ -43,6 +49,8 @@ type User struct {
|
||||
Name string
|
||||
AttachedPolicies []string
|
||||
InlinePolicies []string
|
||||
Admin string
|
||||
CanPrivEsc string
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
@@ -62,6 +70,8 @@ type Role struct {
|
||||
Name string
|
||||
AttachedPolicies []string
|
||||
InlinePolicies []string
|
||||
Admin string
|
||||
CanPrivEsc string
|
||||
}
|
||||
|
||||
func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosity int) {
|
||||
@@ -69,6 +79,7 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "principals"
|
||||
localAdminMap := make(map[string]bool)
|
||||
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
@@ -78,6 +89,9 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating IAM Users and Roles for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
m.pmapperMod, m.pmapperError = InitPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines, m.PmapperDataBasePath)
|
||||
m.iamSimClient = InitIamCommandClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
|
||||
|
||||
// wg := new(sync.WaitGroup)
|
||||
|
||||
// done := make(chan bool)
|
||||
@@ -101,6 +115,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
|
||||
"Arn",
|
||||
"AttachedPolicies",
|
||||
"InlinePolicies",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
@@ -122,6 +138,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
|
||||
"Arn",
|
||||
//"AttachedPolicies",
|
||||
//"InlinePolicies",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// Otherwise, use the default columns.
|
||||
@@ -132,11 +150,25 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
|
||||
"Arn",
|
||||
// "AttachedPolicies",
|
||||
// "InlinePolicies",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the pmapper row if there is no pmapper data
|
||||
if m.pmapperError != nil {
|
||||
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
|
||||
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
|
||||
}
|
||||
|
||||
//Table rows
|
||||
for i := range m.Users {
|
||||
if m.pmapperError == nil {
|
||||
m.Users[i].Admin, m.Users[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.Users[i].Arn)
|
||||
} else {
|
||||
m.Users[i].Admin, m.Users[i].CanPrivEsc = GetIamSimResult(m.SkipAdminCheck, &m.Users[i].Arn, m.iamSimClient, localAdminMap)
|
||||
}
|
||||
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
@@ -146,12 +178,19 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
|
||||
m.Users[i].Arn,
|
||||
strings.Join(m.Users[i].AttachedPolicies, " , "),
|
||||
strings.Join(m.Users[i].InlinePolicies, " , "),
|
||||
m.Users[i].Admin,
|
||||
m.Users[i].CanPrivEsc,
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
for i := range m.Roles {
|
||||
if m.pmapperError == nil {
|
||||
m.Roles[i].Admin, m.Roles[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.Roles[i].Arn)
|
||||
} else {
|
||||
m.Roles[i].Admin, m.Roles[i].CanPrivEsc = GetIamSimResult(m.SkipAdminCheck, &m.Roles[i].Arn, m.iamSimClient, localAdminMap)
|
||||
}
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
@@ -161,6 +200,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
|
||||
m.Roles[i].Arn,
|
||||
strings.Join(m.Roles[i].AttachedPolicies, " , "),
|
||||
strings.Join(m.Roles[i].InlinePolicies, " , "),
|
||||
m.Roles[i].Admin,
|
||||
m.Roles[i].CanPrivEsc,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
+6
-5
@@ -219,9 +219,10 @@ func (m *RoleTrustsModule) printPrincipalTrusts(outputDirectory string) ([]strin
|
||||
RoleARN: aws.ToString(role.roleARN),
|
||||
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
|
||||
TrustedPrincipal: principal,
|
||||
ExternalID: statement.Condition.StringEquals.StsExternalID,
|
||||
IsAdmin: role.Admin,
|
||||
CanPrivEsc: role.CanPrivEsc,
|
||||
// if there is more than one externalID concat them using newlines
|
||||
ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"),
|
||||
IsAdmin: role.Admin,
|
||||
CanPrivEsc: role.CanPrivEsc,
|
||||
}
|
||||
body = append(body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
@@ -281,7 +282,7 @@ func (m *RoleTrustsModule) printPrincipalTrustsRootOnly(outputDirectory string)
|
||||
for _, role := range m.AnalyzedRoles {
|
||||
for _, statement := range role.trustsDoc.Statement {
|
||||
for _, principal := range statement.Principal.AWS {
|
||||
if strings.Contains(principal, ":root") && statement.Condition.StringEquals.StsExternalID == "" {
|
||||
if strings.Contains(principal, ":root") && statement.Condition.StringEquals.StsExternalID == nil {
|
||||
accountID := strings.Split(principal, ":")[4]
|
||||
vendorName := m.vendors.GetVendorNameFromAccountID(accountID)
|
||||
if vendorName != "" {
|
||||
@@ -292,7 +293,7 @@ func (m *RoleTrustsModule) printPrincipalTrustsRootOnly(outputDirectory string)
|
||||
RoleARN: aws.ToString(role.roleARN),
|
||||
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
|
||||
TrustedPrincipal: principal,
|
||||
ExternalID: statement.Condition.StringEquals.StsExternalID,
|
||||
ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"),
|
||||
IsAdmin: role.Admin,
|
||||
CanPrivEsc: role.CanPrivEsc,
|
||||
}
|
||||
|
||||
+54
-25
@@ -138,9 +138,10 @@ var (
|
||||
PostRun: awsPostRun,
|
||||
}
|
||||
|
||||
CapeAdminOnly bool
|
||||
CapeJobName string
|
||||
CapeCommand = &cobra.Command{
|
||||
CapeAdminOnly bool
|
||||
CapeArnIgnoreList string
|
||||
CapeJobName string
|
||||
CapeCommand = &cobra.Command{
|
||||
Use: "cape",
|
||||
Aliases: []string{"CAPE"},
|
||||
Short: "Cross-Account Privilege Escalation Route finder. Needs to be run with multiple profiles using -l or -a flag. Needs pmapper data to be present",
|
||||
@@ -263,10 +264,11 @@ var (
|
||||
PostRun: awsPostRun,
|
||||
}
|
||||
|
||||
SimulatorResource string
|
||||
SimulatorAction string
|
||||
SimulatorPrincipal string
|
||||
IamSimulatorCommand = &cobra.Command{
|
||||
SimulatorResource string
|
||||
SimulatorAction string
|
||||
SimulatorPrincipal string
|
||||
IamSimulatorAdminCheckOnly bool
|
||||
IamSimulatorCommand = &cobra.Command{
|
||||
Use: "iam-simulator",
|
||||
Aliases: []string{"iamsimulator", "simulator"},
|
||||
Short: "Wrapper around the AWS IAM Simulate Principal Policy command",
|
||||
@@ -1172,6 +1174,20 @@ func runCapeCommand(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// if the CapeArnIgnoreList arg is not empty, read the file and add the arns to the CapeArnIgnoreList
|
||||
if CapeArnIgnoreList != "" {
|
||||
// call ReadArnIgnoreListFile and add the arns to the CapeArnIgnoreList
|
||||
arnsToIgnore, err := aws.ReadArnIgnoreListFile(CapeArnIgnoreList)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading the arn ignore list file: " + err.Error())
|
||||
}
|
||||
|
||||
// remove nodes that are in the CapeArnIgnoreList from the graph
|
||||
for _, arn := range arnsToIgnore {
|
||||
GlobalGraph.RemoveVertex(arn)
|
||||
}
|
||||
}
|
||||
|
||||
// make pmapper edges
|
||||
//you can update edges, so we can just merge attributes as needed
|
||||
// first we add the edges that already exist in pmapper, then later we will make more edges based on the cloudfox role trusts logic
|
||||
@@ -1318,10 +1334,20 @@ func runCapeTUICommand(cmd *cobra.Command, args []string) {
|
||||
if _, err := os.Stat(filepath.Join(cloudfoxRunData.OutputLocation, "json", fileName)); os.IsNotExist(err) {
|
||||
fmt.Printf("[%s] Could not retrieve CAPE data for profile %s.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), profile)
|
||||
//remove the profile from the list of profiles to analyze
|
||||
AWSProfiles = append(AWSProfiles[:i], AWSProfiles[i+1:]...)
|
||||
if len(AWSProfiles) > 1 {
|
||||
AWSProfiles = append(AWSProfiles[:i], AWSProfiles[i+1:]...)
|
||||
} else {
|
||||
if CapeAdminOnly {
|
||||
fmt.Printf("[%s] Could not retrieve cape data. Did you run cape without the --admin-only flag? You'll need to run cape with --admin-only to use the tui with --admin-only\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)))
|
||||
} else {
|
||||
fmt.Printf("[%s] Did you run cape with the --admin-only flag? You'll need to run cape without --admin-only to use the tui without --admin-only\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(capeOutputFileLocations) == 0 {
|
||||
fmt.Printf("[%s] Could not retrieve CAPE data.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)))
|
||||
os.Exit(1)
|
||||
@@ -1338,13 +1364,14 @@ func runIamSimulatorCommand(cmd *cobra.Command, args []string) {
|
||||
continue
|
||||
}
|
||||
m := aws.IamSimulatorModule{
|
||||
IAMClient: iam.NewFromConfig(AWSConfig),
|
||||
Caller: *caller,
|
||||
AWSProfileProvided: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
IAMClient: iam.NewFromConfig(AWSConfig),
|
||||
Caller: *caller,
|
||||
AWSProfileProvided: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
IamSimulatorAdminCheckOnly: IamSimulatorAdminCheckOnly,
|
||||
}
|
||||
m.PrintIamSimulator(SimulatorPrincipal, SimulatorAction, SimulatorResource, AWSOutputDirectory, Verbosity)
|
||||
}
|
||||
@@ -1547,13 +1574,15 @@ func runPrincipalsCommand(cmd *cobra.Command, args []string) {
|
||||
continue
|
||||
}
|
||||
m := aws.IamPrincipalsModule{
|
||||
IAMClient: iam.NewFromConfig(AWSConfig),
|
||||
Caller: *caller,
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
IAMClient: iam.NewFromConfig(AWSConfig),
|
||||
Caller: *caller,
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
SkipAdminCheck: AWSSkipAdminCheck,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
PmapperDataBasePath: PmapperDataBasePath,
|
||||
}
|
||||
m.PrintIamPrincipals(AWSOutputDirectory, Verbosity)
|
||||
}
|
||||
@@ -2375,9 +2404,6 @@ func init() {
|
||||
// Map Access Keys Module Flags
|
||||
AccessKeysCommand.Flags().StringVarP(&AccessKeysFilter, "filter", "f", "none", "Access key ID to search for")
|
||||
|
||||
// IAM Simulator Module Flags
|
||||
//IamSimulatorCommand.Flags().StringVarP(&IamSimulatorFilter, "filter", "f", "none", "Access key ID to search for")
|
||||
|
||||
// Instances Map Module Flags
|
||||
InstancesCommand.Flags().StringVarP(&InstancesFilter, "filter", "f", "all", "[InstanceID | InstanceIDsFile]")
|
||||
InstancesCommand.Flags().BoolVarP(&InstanceMapUserDataAttributesOnly, "userdata", "u", false, "Use this flag to retrieve only the userData attribute from EC2 instances.")
|
||||
@@ -2395,6 +2421,7 @@ func init() {
|
||||
IamSimulatorCommand.Flags().StringVar(&SimulatorPrincipal, "principal", "", "Principal Arn")
|
||||
IamSimulatorCommand.Flags().StringVar(&SimulatorAction, "action", "", "Action")
|
||||
IamSimulatorCommand.Flags().StringVar(&SimulatorResource, "resource", "*", "Resource")
|
||||
IamSimulatorCommand.Flags().BoolVar(&IamSimulatorAdminCheckOnly, "admin-check-only", false, "Only check check if principals are admin")
|
||||
|
||||
// iam-simulator module flags
|
||||
PermissionsCommand.Flags().StringVar(&PermissionsPrincipal, "principal", "", "Principal Arn")
|
||||
@@ -2408,6 +2435,8 @@ func init() {
|
||||
// cape command flags
|
||||
CapeCommand.Flags().BoolVar(&CapeAdminOnly, "admin-only", false, "Only return paths that lead to an admin role - much faster")
|
||||
//CapeCommand.Flags().StringVar(&CapeJobName, "job-name", "", "Name of the cape job")
|
||||
// flag that accepts a list of arns to ignore
|
||||
CapeCommand.Flags().StringVar(&CapeArnIgnoreList, "arn-ignore-list", "", "File containing a list of ARNs to ignore separated by newlines")
|
||||
|
||||
// cape tui command flags
|
||||
CapeTuiCmd.Flags().BoolVar(&CapeAdminOnly, "admin-only", false, "Only return paths that lead to an admin role - much faster")
|
||||
|
||||
+1
-2
@@ -4,5 +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 = "1.14.2"
|
||||
|
||||
const CLOUDFOX_VERSION = "1.15.0"
|
||||
@@ -109,6 +109,9 @@ func MatchesAfterExpansion(stringFromPolicyToCheck, stringToCheckAgainst string)
|
||||
return pattern.MatchString(stringFromPolicyToCheck)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
func (p *Policy) DoesPolicyHaveMatchingStatement(effect string, actionToCheck string, resourceToCheck string) bool {
|
||||
|
||||
for _, statement := range p.Statement {
|
||||
|
||||
@@ -222,3 +222,51 @@ func TestDoesPolicyHaveMatchingStatement(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoesPermissionExpansionMatch(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
actionToExpand string
|
||||
actionToCheckAgainst string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
actionToExpand: "sts:AssumeRole",
|
||||
actionToCheckAgainst: "sts:AssumeRole",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
actionToExpand: "*",
|
||||
actionToCheckAgainst: "sts:AssumeRole",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
actionToExpand: "sts:Assume*",
|
||||
actionToCheckAgainst: "sts:AssumeRole",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
actionToExpand: "sts:*",
|
||||
actionToCheckAgainst: "sts:AssumeRole",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
actionToExpand: "s3:GetObject",
|
||||
actionToCheckAgainst: "sts:AssumeRole",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
actionToExpand: "s3:*",
|
||||
actionToCheckAgainst: "sts:AssumeRole",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
actual := MatchesAfterExpansion(tt.actionToCheckAgainst, tt.actionToExpand)
|
||||
if tt.want != actual {
|
||||
//fmt.Printf("DoesPermissionHaveMatchingStatement(%s, %s) is %v but should be %v", tt.actionToExpand, tt.actionToCheckAgainst, actual, tt.want)
|
||||
t.Errorf("DoesPermissionHaveMatchingStatement(%s, %s) is %v but should be %v", tt.actionToExpand, tt.actionToCheckAgainst, actual, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ type RoleTrustStatementEntry struct {
|
||||
Action string `json:"Action"`
|
||||
Condition struct {
|
||||
StringEquals struct {
|
||||
StsExternalID string `json:"sts:ExternalId"`
|
||||
StsExternalID ListOfPrincipals `json:"sts:ExternalId"`
|
||||
SAMLAud string `json:"SAML:aud"`
|
||||
TokenActionsGithubusercontentComSub ListOfPrincipals `json:"token.actions.githubusercontent.com:sub"`
|
||||
TokenActionsGithubusercontentComAud string `json:"token.actions.githubusercontent.com:aud"`
|
||||
|
||||
Reference in new issue
Block a user