package aws import ( "fmt" "net/url" "path/filepath" "strconv" "strings" "time" "github.com/BishopFox/cloudfox/aws/sdk" "github.com/BishopFox/cloudfox/internal" "github.com/BishopFox/cloudfox/internal/aws/policy" "github.com/BishopFox/cloudfox/internal/common" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/iam/types" "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/sirupsen/logrus" ) type IamPermissionsModule struct { // General configuration data IAMClient sdk.AWSIAMClientInterface Caller sts.GetCallerIdentityOutput AWSRegions []string AWSOutputType string AWSTableCols string Goroutines int AWSProfile string WrapTable bool // Main module data Policies []GAADPolicy Users []GAADUser Roles []GAADRole Groups []GAADGroup Rows []common.PermissionsRow CommandCounter internal.CommandCounter // Used to store output data for pretty printing output internal.OutputData2 modLog *logrus.Entry } type GAADPolicy struct { Name string Arn string PolicyVersionList []types.PolicyVersion } type GAADUser struct { Name string Arn string AttachedPolicies []types.AttachedPolicy InlinePolicies []types.PolicyDetail GroupList []string } type GAADRole struct { Arn string Name string AttachedPolicies []types.AttachedPolicy InlinePolicies []types.PolicyDetail } type GAADGroup struct { Arn string Name string AttachedPolicies []types.AttachedPolicy InlinePolicies []types.PolicyDetail } func (m *IamPermissionsModule) PrintIamPermissions(outputDirectory string, verbosity int, principal string) { // These struct values are used by the output module m.output.Verbosity = verbosity m.output.Directory = outputDirectory m.output.CallingModule = "permissions" filename := m.output.CallingModule m.modLog = internal.TxtLog.WithFields(logrus.Fields{ "module": m.output.CallingModule, }) if m.AWSProfile == "" { m.AWSProfile = internal.BuildAWSPath(m.Caller) } m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account))) 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 != "" { filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10))) } m.GetGAAD() m.ParsePermissions(principal) m.output.Headers = []string{ "Account", "Type", "Name", "Arn", "Policy", "Policy Name", "Policy Arn", "Effect", "Action", "Resource", "Condition", } //Table rows for i := range m.Rows { m.output.Body = append( m.output.Body, []string{ aws.ToString(m.Caller.Account), m.Rows[i].Type, m.Rows[i].Name, m.Rows[i].Arn, m.Rows[i].PolicyType, m.Rows[i].PolicyName, m.Rows[i].PolicyArn, m.Rows[i].Effect, m.Rows[i].Action, m.Rows[i].Resource, m.Rows[i].Condition, }, ) } 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))) o := internal.OutputClient{ Verbosity: verbosity, CallingModule: m.output.CallingModule, Table: internal.TableClient{ Wrap: m.WrapTable, }, } // If the user specified table columns, use those. // If the user specified -o wide, use the wide default cols for this module. // Otherwise, use the hardcoded default cols for this module. var tableCols []string // If the user specified table columns, use those. if m.AWSTableCols != "" { tableCols = strings.Split(m.AWSTableCols, ",") // If the user specified wide as the output format, use these columns. } else if m.AWSOutputType == "wide" { tableCols = []string{ "Account", "Type", //"Name", "Arn", "Policy", "Policy Name", "Policy Arn", "Effect", "Action", "Resource", "Condition"} // Otherwise, use the default columns for this module (brief) } else { tableCols = []string{ "Type", "Name", //"Arn", "Policy", "Policy Name", "Effect", "Action", "Resource", "Condition", } } o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{ Header: m.output.Headers, TableCols: tableCols, Body: m.output.Body, 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))) o.WriteFullOutput(o.Table.TableFiles, nil) //m.writeLoot(o.Table.DirectoryName, verbosity) fmt.Printf("[%s][%s] %s unique permissions identified.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body))) } else { fmt.Printf("[%s][%s] No IAM permissions found. skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile)) } 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 *IamPermissionsModule) GetGAAD() { GAAD, err := sdk.CachedIAMGetAccountAuthorizationDetails(m.IAMClient, aws.ToString(m.Caller.Account)) if err != nil { TxtLogger.Error(err.Error()) m.CommandCounter.Error++ return } // // if user supplied a principal name without the arn, try to create the arn // if !strings.Contains(principal, "arn:") { // principal = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal) // } for _, policy := range GAAD.Policies { //var IAMtype = "Role" arn := aws.ToString(policy.Arn) name := aws.ToString(policy.PolicyName) m.Policies = append(m.Policies, GAADPolicy{ Arn: arn, Name: name, PolicyVersionList: policy.PolicyVersionList, }) } for _, role := range GAAD.RoleDetailList { arn := aws.ToString(role.Arn) name := aws.ToString(role.RoleName) m.Roles = append(m.Roles, GAADRole{ Arn: arn, Name: name, AttachedPolicies: role.AttachedManagedPolicies, InlinePolicies: role.RolePolicyList, }) } for _, user := range GAAD.UserDetailList { //var IAMtype = "User" arn := aws.ToString(user.Arn) name := aws.ToString(user.UserName) groupList := user.GroupList m.Users = append(m.Users, GAADUser{ Arn: arn, Name: name, AttachedPolicies: user.AttachedManagedPolicies, InlinePolicies: user.UserPolicyList, GroupList: groupList, }) } for _, group := range GAAD.GroupDetailList { arn := aws.ToString(group.Arn) name := aws.ToString(group.GroupName) m.Groups = append(m.Groups, GAADGroup{ Arn: arn, Name: name, AttachedPolicies: group.AttachedManagedPolicies, InlinePolicies: group.GroupPolicyList, }) } } // create a function that will take a principal name and try to see if it is a role, user, or group and return the arn of the principal func (m *IamPermissionsModule) getPrincipalArn(principal string) string { var arn string for _, role := range m.Roles { if role.Name == principal { arn = role.Arn } } for _, user := range m.Users { if user.Name == principal { arn = user.Arn } } for _, group := range m.Groups { if group.Name == principal { arn = group.Arn } } return arn } func (m *IamPermissionsModule) ParsePermissions(principal string) { var inputArn string for i := range m.Roles { if principal == "" { for _, attachedPolicy := range m.Roles[i].AttachedPolicies { m.getPermissionsFromAttachedPolicy(m.Roles[i].Arn, attachedPolicy, "Role", m.Roles[i].Name) } for _, inlinePolicy := range m.Roles[i].InlinePolicies { m.getPermissionsFromInlinePolicy(m.Roles[i].Arn, inlinePolicy, "Role", m.Roles[i].Name) } } else { // if user supplied a principal name without the arn, try to create the arn if !strings.Contains(principal, "arn:") { inputArn = fmt.Sprintf("arn:aws:iam::%s:role/%s", aws.ToString(m.Caller.Account), principal) } else { inputArn = principal } if strings.ToLower(m.Roles[i].Arn) == strings.ToLower(inputArn) { for _, attachedPolicy := range m.Roles[i].AttachedPolicies { m.getPermissionsFromAttachedPolicy(m.Roles[i].Arn, attachedPolicy, "Role", m.Roles[i].Name) } for _, inlinePolicy := range m.Roles[i].InlinePolicies { m.getPermissionsFromInlinePolicy(m.Roles[i].Arn, inlinePolicy, "Role", m.Roles[i].Name) } } } } for i := range m.Users { if principal == "" { for _, attachedPolicy := range m.Users[i].AttachedPolicies { m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name) } for _, inlinePolicy := range m.Users[i].InlinePolicies { m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name) } } else { // if user supplied a principal name without the arn, try to create the arn if !strings.Contains(principal, "arn:") { inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal) } else { inputArn = principal } if strings.ToLower(m.Users[i].Arn) == strings.ToLower(inputArn) { for _, attachedPolicy := range m.Users[i].AttachedPolicies { m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name) } for _, inlinePolicy := range m.Users[i].InlinePolicies { m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name) } } } // for each group in the user's group list, get the attached and inline policy names, and then get the permissions from those policies for g := range m.Users[i].GroupList { if principal == "" { for _, group := range m.Groups { if group.Name == m.Users[i].GroupList[g] { for _, attachedPolicy := range group.AttachedPolicies { m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name) } for _, inlinePolicy := range group.InlinePolicies { m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name) } } } } else { // if user supplied a principal name without the arn, try to create the arn if !strings.Contains(principal, "arn:") { inputArn = fmt.Sprintf("arn:aws:iam::%s:user/%s", aws.ToString(m.Caller.Account), principal) } else { inputArn = principal } if strings.ToLower(m.Users[i].Arn) == strings.ToLower(inputArn) { for _, group := range m.Groups { if group.Name == m.Users[i].GroupList[g] { for _, attachedPolicy := range group.AttachedPolicies { m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name) } for _, inlinePolicy := range group.InlinePolicies { m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name) } } } } } } // for group := range m.Users[i].GroupList { // for _, gaadGroup := range m.Groups { // if gaadGroup.Name == group { // for _, attachedPolicy := range m.Groups[j].AttachedPolicies { // m.getPermissionsFromAttachedPolicy(m.Users[i].Arn, attachedPolicy, "User", m.Users[i].Name) // } // for _, inlinePolicy := range m.Groups[j].InlinePolicies { // m.getPermissionsFromInlinePolicy(m.Users[i].Arn, inlinePolicy, "User", m.Users[i].Name) // } // } // } // } } } func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, attachedPolicy types.AttachedPolicy, IAMtype string, name string) { //var policies []types.ManagedPolicyDetail var s policy.PolicyStatement var AWSService = "IAM" var hasConditions string for _, p := range m.Policies { if p.Name == aws.ToString(attachedPolicy.PolicyName) { for _, d := range p.PolicyVersionList { if d.IsDefaultVersion { //parsedPolicyDocument, _ := parsePolicyDocument(d.Document) document, _ := url.QueryUnescape(aws.ToString(d.Document)) parsedPolicyDocument, _ := policy.ParseJSONPolicy([]byte(document)) // hasStsAssumeRole := parsedPolicyDocument.DoesPolicyHaveMatchingStatement("Allow", "sts:AssumeRole", "*") // fmt.Println(hasStsAssumeRole) for _, s = range parsedPolicyDocument.Statement { //version := parsedPolicyDocument.Version effect := s.Effect if s.Action != nil { for _, action := range s.Action { for _, resource := range s.Resource { if s.Condition != nil { hasConditions = "Yes" } else { hasConditions = "No" } m.Rows = append( m.Rows, common.PermissionsRow{ AWSService: AWSService, Arn: arn, Name: name, Type: IAMtype, PolicyType: "Managed", PolicyName: p.Name, PolicyArn: p.Arn, Effect: effect, Action: action, Resource: resource, Condition: hasConditions, }) } } } if s.NotAction != nil { for _, action := range s.NotAction { for _, resource := range s.Resource { if s.Condition != nil { hasConditions = "Yes" } else { hasConditions = "No" } m.Rows = append( m.Rows, common.PermissionsRow{ AWSService: AWSService, Arn: arn, Name: name, Type: IAMtype, PolicyType: "Managed", PolicyName: p.Name, PolicyArn: p.Arn, Effect: effect, Action: "[NotAction] " + action, Resource: resource, Condition: hasConditions, }) } } } } } } } } } func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inlinePolicy types.PolicyDetail, IAMtype string, name string) { //var policies []types.ManagedPolicyDetail var s policy.PolicyStatement var AWSService = "IAM" var hasConditions string //parsedPolicyDocument, _ := parsePolicyDocument(inlinePolicy.PolicyDocument) document, _ := url.QueryUnescape(aws.ToString(inlinePolicy.PolicyDocument)) parsedPolicyDocument, _ := policy.ParseJSONPolicy([]byte(document)) for _, s = range parsedPolicyDocument.Statement { effect := s.Effect if s.Action != nil { for _, action := range s.Action { for _, resource := range s.Resource { if s.Condition != nil { hasConditions = "Yes" } else { hasConditions = "No" } m.Rows = append( m.Rows, common.PermissionsRow{ AWSService: AWSService, Arn: arn, Name: name, Type: IAMtype, PolicyType: "Inline", PolicyName: aws.ToString(inlinePolicy.PolicyName), PolicyArn: aws.ToString(inlinePolicy.PolicyName), Effect: effect, Action: action, Resource: resource, Condition: hasConditions, }) } } } if s.NotAction != nil { for _, action := range s.NotAction { for _, resource := range s.Resource { if s.Condition != nil { hasConditions = "Yes" } else { hasConditions = "No" } m.Rows = append( m.Rows, common.PermissionsRow{ AWSService: AWSService, Arn: arn, Name: name, Type: IAMtype, PolicyType: "Inline", PolicyName: aws.ToString(inlinePolicy.PolicyName), PolicyArn: aws.ToString(inlinePolicy.PolicyName), Effect: effect, Action: "[NotAction] " + action, Resource: resource, Condition: hasConditions, }) } } } } }