Skip to content

Commit b2c450e

Browse files
authored
Merge pull request #73 from BishopFox/seth-dev
Release PR for 1.13.1
2 parents 8e99347 + 997c13d commit b2c450e

File tree

16 files changed

+463
-135
lines changed

16 files changed

+463
-135
lines changed

aws/api-gws.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ import (
1212
"github.com/BishopFox/cloudfox/aws/sdk"
1313
"github.com/BishopFox/cloudfox/internal"
1414
"github.com/aws/aws-sdk-go-v2/aws"
15-
"github.com/aws/aws-sdk-go-v2/service/apigateway"
1615
apigatewayTypes "github.com/aws/aws-sdk-go-v2/service/apigateway/types"
17-
"github.com/aws/aws-sdk-go-v2/service/apigatewayv2"
1816
apigatewayV2Types "github.com/aws/aws-sdk-go-v2/service/apigatewayv2/types"
1917
"github.com/aws/aws-sdk-go-v2/service/sts"
2018
"github.com/bishopfox/awsservicemap"
@@ -25,8 +23,8 @@ var CURL_COMMAND string = "curl -X %s %s"
2523

2624
type ApiGwModule struct {
2725
// General configuration data
28-
APIGatewayClient *apigateway.Client
29-
APIGatewayv2Client *apigatewayv2.Client
26+
APIGatewayClient sdk.APIGatewayClientInterface
27+
APIGatewayv2Client sdk.APIGatewayv2ClientInterface
3028

3129
Caller sts.GetCallerIdentityOutput
3230
AWSRegions []string
@@ -127,13 +125,17 @@ func (m *ApiGwModule) PrintApiGws(outputDirectory string, verbosity int) {
127125

128126
}
129127
if len(m.output.Body) > 0 {
130-
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
128+
filepath := filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
131129

132130
o := internal.OutputClient{
133131
Verbosity: verbosity,
134132
CallingModule: m.output.CallingModule,
135133
Table: internal.TableClient{
136-
Wrap: m.WrapTable,
134+
Wrap: m.WrapTable,
135+
DirectoryName: filepath,
136+
},
137+
Loot: internal.LootClient{
138+
DirectoryName: filepath,
137139
},
138140
}
139141
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
@@ -142,9 +144,13 @@ func (m *ApiGwModule) PrintApiGws(outputDirectory string, verbosity int) {
142144
Name: m.output.CallingModule,
143145
})
144146
o.PrefixIdentifier = m.AWSProfile
145-
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
146-
o.WriteFullOutput(o.Table.TableFiles, nil)
147-
m.writeLoot(o.Table.DirectoryName, verbosity)
147+
loot := m.writeLoot(o.Table.DirectoryName, verbosity)
148+
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{
149+
Name: m.output.CallingModule,
150+
Contents: loot,
151+
})
152+
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
153+
148154
fmt.Printf("[%s][%s] %s API gateways found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
149155
} else {
150156
fmt.Printf("[%s][%s] No API gateways found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
@@ -199,7 +205,7 @@ func (m *ApiGwModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan
199205
}
200206
}
201207

202-
func (m *ApiGwModule) writeLoot(outputDirectory string, verbosity int) {
208+
func (m *ApiGwModule) writeLoot(outputDirectory string, verbosity int) string {
203209
path := filepath.Join(outputDirectory, "loot")
204210
err := os.MkdirAll(path, os.ModePerm)
205211
if err != nil {
@@ -237,12 +243,12 @@ func (m *ApiGwModule) writeLoot(outputDirectory string, verbosity int) {
237243
out += line + "\n"
238244
}
239245

240-
err = os.WriteFile(f, []byte(out), 0644)
241-
if err != nil {
242-
m.modLog.Error(err.Error())
243-
m.CommandCounter.Error++
244-
panic(err.Error())
245-
}
246+
// err = os.WriteFile(f, []byte(out), 0644)
247+
// if err != nil {
248+
// m.modLog.Error(err.Error())
249+
// m.CommandCounter.Error++
250+
// panic(err.Error())
251+
// }
246252

247253
if verbosity > 2 {
248254
fmt.Println()
@@ -253,6 +259,8 @@ func (m *ApiGwModule) writeLoot(outputDirectory string, verbosity int) {
253259

254260
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
255261

262+
return out
263+
256264
}
257265

258266
func (m *ApiGwModule) getAPIGatewayAPIsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {

aws/api-gws_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package aws
2+
3+
import (
4+
"path/filepath"
5+
"strings"
6+
"testing"
7+
8+
"github.com/BishopFox/cloudfox/aws/sdk"
9+
"github.com/BishopFox/cloudfox/internal"
10+
"github.com/aws/aws-sdk-go-v2/aws"
11+
"github.com/aws/aws-sdk-go-v2/service/sts"
12+
"github.com/spf13/afero"
13+
)
14+
15+
func TestApiGw(t *testing.T) {
16+
17+
m := ApiGwModule{
18+
AWSProfile: "unittesting",
19+
AWSRegions: []string{"us-east-1"},
20+
Caller: sts.GetCallerIdentityOutput{
21+
Arn: aws.String("arn:aws:iam::123456789012:user/Alice"),
22+
Account: aws.String("123456789012"),
23+
},
24+
Goroutines: 3,
25+
WrapTable: false,
26+
APIGatewayClient: &sdk.MockedAWSAPIGatewayClient{},
27+
APIGatewayv2Client: &sdk.MockedAWSAPIGatewayv2Client{},
28+
}
29+
30+
fs := internal.MockFileSystem(true)
31+
defer internal.MockFileSystem(false)
32+
tmpDir := "~/.cloudfox/"
33+
34+
m.PrintApiGws(tmpDir, 2)
35+
36+
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/api-gw.txt")
37+
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
38+
if err != nil {
39+
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
40+
}
41+
//print the results file to the screen
42+
//fmt.Println(string(resultsFile))
43+
44+
// I want a test that runs the main function and checks the output to see if the following items are in the output: "https://qwerty.execute-api.us-east-1.amazonaws.com/stage1/path1", "https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage1/route2"
45+
46+
expectedResults := []string{
47+
"https://qwerty.execute-api.us-east-1.amazonaws.com/stage1/path1",
48+
"https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage1/route2",
49+
"https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage2/route1",
50+
"23oieuwefo3rfs",
51+
}
52+
53+
for _, expected := range expectedResults {
54+
if !strings.Contains(string(resultsFile), expected) {
55+
t.Errorf("Expected %s to be in the results file", expected)
56+
}
57+
}
58+
}

aws/client-initializers.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import (
2020
func initIAMSimClient(iamSimPPClient sdk.AWSIAMClientInterface, caller sts.GetCallerIdentityOutput, AWSProfile string, Goroutines int) IamSimulatorModule {
2121

2222
iamSimMod := IamSimulatorModule{
23-
IAMClient: iamSimPPClient,
24-
Caller: caller,
25-
AWSProfile: AWSProfile,
26-
Goroutines: Goroutines,
23+
IAMClient: iamSimPPClient,
24+
Caller: caller,
25+
AWSProfileProvided: AWSProfile,
26+
Goroutines: Goroutines,
2727
}
2828

2929
return iamSimMod

aws/iam-simulator.go

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ type IamSimulatorModule struct {
2727
AWSOutputType string
2828
AWSTableCols string
2929

30-
Goroutines int
31-
AWSProfile string
32-
WrapTable bool
30+
Goroutines int
31+
AWSProfileProvided string
32+
AWSProfileStub string
33+
WrapTable bool
3334

3435
// Main module data
3536
SimulatorResults []SimulatorResult
@@ -76,16 +77,19 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
7677
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
7778
"module": m.output.CallingModule,
7879
})
79-
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
80+
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileStub, aws.ToString(m.Caller.Account)))
8081
var filename string
8182
var actionList []string
8283
var pmapperCommands []string
8384
var pmapperOutFileName string
8485
var inputArn string
8586

86-
if m.AWSProfile == "" {
87-
m.AWSProfile = internal.BuildAWSPath(m.Caller)
87+
if m.AWSProfileProvided == "" {
88+
m.AWSProfileStub = internal.BuildAWSPath(m.Caller)
89+
} else {
90+
m.AWSProfileStub = m.AWSProfileProvided
8891
}
92+
8993
wg := new(sync.WaitGroup)
9094
// Create a channel to signal the spinner aka task status goroutine to finish
9195
spinnerDone := make(chan bool)
@@ -104,7 +108,7 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
104108
if principal != "" {
105109
if action != "" {
106110
// The user specified a specific --principal and a specific --action
107-
fmt.Printf("[%s][%s] Checking to see if %s can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), principal, action)
111+
fmt.Printf("[%s][%s] Checking to see if %s can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), principal, action)
108112
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
109113
actionList = append(actionList, action)
110114
// if user supplied a principal name without the arn, try to create the arn as a user and as a role and run both
@@ -122,7 +126,7 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
122126

123127
} else {
124128
// The user specified a specific --principal, but --action was empty
125-
fmt.Printf("[%s][%s] Checking to see if %s can do any actions of interest.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), principal)
129+
fmt.Printf("[%s][%s] Checking to see if %s can do any actions of interest.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), principal)
126130
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
127131

128132
// if user supplied a principal name without the arn, try to create the arn as a user and as a role and run both
@@ -142,23 +146,31 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
142146
} else {
143147
if action != "" {
144148
// The did not specify a specific --principal, but they did specify an --action
145-
fmt.Printf("[%s][%s] Checking to see if any principal can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), action)
149+
fmt.Printf("[%s][%s] Checking to see if any principal can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), action)
146150
filename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
147151
actionList = append(actionList, action)
148152
wg.Add(1)
149153
m.getIAMUsers(wg, actionList, resource, dataReceiver)
150154
wg.Add(1)
151155
m.getIAMRoles(wg, actionList, resource, dataReceiver)
152156
pmapperOutFileName = filepath.Join(filename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
153-
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfile, action, resource, pmapperOutFileName))
157+
if m.AWSProfileProvided != "" {
158+
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfileProvided, action, resource, pmapperOutFileName))
159+
} else {
160+
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper query \"who can do %s with %s\" | tee %s\n", action, resource, pmapperOutFileName))
161+
}
154162
} else {
155163
// Both --principal and --action are empty. Run in default mode!
156-
fmt.Printf("[%s][%s] Running multiple iam-simulator queries for account %s. (This command can be pretty slow, FYI)\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
164+
fmt.Printf("[%s][%s] Running multiple iam-simulator queries for account %s. (This command can be pretty slow, FYI)\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), aws.ToString(m.Caller.Account))
157165
filename = m.output.CallingModule
158166
m.executeChecks(wg, resource, dataReceiver)
159167
for _, action := range defaultActionNames {
160168
pmapperOutFileName = filepath.Join(filename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
161-
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfile, action, resource, pmapperOutFileName))
169+
if m.AWSProfileProvided != "" {
170+
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfileProvided, action, resource, pmapperOutFileName))
171+
} else {
172+
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper query \"who can do %s with %s\" | tee %s\n", action, resource, pmapperOutFileName))
173+
}
162174
}
163175

164176
}
@@ -225,7 +237,7 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
225237

226238
}
227239
if len(m.output.Body) > 0 {
228-
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
240+
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileStub, aws.ToString(m.Caller.Account)))
229241

230242
o := internal.OutputClient{
231243
Verbosity: verbosity,
@@ -240,20 +252,20 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
240252
TableCols: tableCols,
241253
Name: filename,
242254
})
243-
o.PrefixIdentifier = m.AWSProfile
244-
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
255+
o.PrefixIdentifier = m.AWSProfileStub
256+
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfileStub, aws.ToString(m.Caller.Account)))
245257
o.WriteFullOutput(o.Table.TableFiles, nil)
246-
fmt.Printf("[%s][%s] We suggest running the pmapper commands in the loot file to get the same information but taking privesc paths into account.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
258+
fmt.Printf("[%s][%s] We suggest running the pmapper commands in the loot file to get the same information but taking privesc paths into account.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
247259
// fmt.Printf("[%s]\t\tpmapper --profile %s graph create\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.AWSProfile)
248260
// for _, line := range pmapperCommands {
249261
// fmt.Printf("[%s]\t\t%s", cyan(m.output.CallingModule), cyan(m.AWSProfile), line)
250262
// }
251263
m.writeLoot(o.Table.DirectoryName, verbosity, pmapperCommands)
252264

253265
} else if principal != "" || action != "" {
254-
fmt.Printf("[%s][%s] No allowed permissions identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
266+
fmt.Printf("[%s][%s] No allowed permissions identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
255267
}
256-
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
268+
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), m.output.CallingModule)
257269
}
258270

259271
func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pmapperCommands []string) {
@@ -266,7 +278,11 @@ func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pm
266278

267279
outFile := filepath.Join(path, "iam-simulator-pmapper-commands.txt")
268280
var out string
269-
out = out + fmt.Sprintf("pmapper --profile %s graph create\n", m.AWSProfile)
281+
if m.AWSProfileProvided != "" {
282+
out = out + fmt.Sprintf("pmapper --profile %s graph create\n", m.AWSProfileProvided)
283+
} else {
284+
out = out + fmt.Sprintf("pmapper graph create\n")
285+
}
270286
for _, line := range pmapperCommands {
271287
out = out + line
272288
}
@@ -278,12 +294,12 @@ func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pm
278294

279295
if verbosity > 2 {
280296
fmt.Println()
281-
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("We suggest running these pmapper commands in the loot file to get the same information but taking privesc paths into account."))
297+
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), green("We suggest running these pmapper commands in the loot file to get the same information but taking privesc paths into account."))
282298
fmt.Print(out)
283-
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
299+
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), green("End of loot file."))
284300
}
285301

286-
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), outFile)
302+
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), outFile)
287303

288304
}
289305

aws/instances.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,21 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin
512512

513513
if instance.IamInstanceProfile == nil {
514514
profile = "NoInstanceProfile"
515+
dataReceiver <- MappedInstance{
516+
ID: aws.ToString(instance.InstanceId),
517+
Name: aws.ToString(&name),
518+
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
519+
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
520+
State: string(instance.State.Name),
521+
ExternalIP: externalIP,
522+
PrivateIP: aws.ToString(instance.PrivateIpAddress),
523+
Profile: profile,
524+
Role: "",
525+
Region: region,
526+
Admin: adminRole,
527+
CanPrivEsc: "",
528+
}
529+
515530
} else {
516531
profileArn = aws.ToString(instance.IamInstanceProfile.Arn)
517532

0 commit comments

Comments
 (0)