Walkthrough Pentesting.cloud first CTF challenge

pentesting.cloud closed :(

but my writeup will live forever I guess

Challenge Start Page
There’s been an influx of AWS/ Cloud CTF posts that have been popping up on my LinkedIn feed. I wanted to take a stab at them and do a write-up of my experience, but with a spin that I’ll be doing it with only PowerShell and not the Console.

linkedin
LinkedIn post that caught my attention.

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

create user
The instructions say to then run the setup.sh but I am on Windows and I don’t feel like setting up WSL 2 or booting up my VM at the moment so we’re going to look at the contents of the file instead and just run the AWS commands manually.

We use the following command to display the contents of the file.

type .\setup.sh

setup sh
Looking at the first block of code, it wants us to give the pentesting-admin full administrative permissions. Usually, I would try to scope down to just the permissions that are needed as a best practice. You can usually figure that out by just adding each permission to your user/role each time it fails and tells you which permission you’re missing.

first block of code
The first block of code to replicate.

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

listing policies
Listing the policies attached to “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

getting policy attached to us
Getting the policy attached to us.

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

getting specific version of policy
Getting a specific version of a policy attached to us.

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

create access key pair

Creating access key pair for our “pentesting-admin” role.

Then add a new profile for ease of use.

aws configure — profile pentesting-admin

Configure new profile
Configuring a new profile for “pentesting-admin” with our new access key pair

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

ensuring we're using correct user
ensuring we're the correct user
Ensuring we’re using the correct user

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

manually setup.sh

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

YAML file inspecting
The YAML file we’re inspecting looks okay and we dare not look further for spoilers.

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…

spoilers
The website gives spoilers to people with large screens :*(

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

creating cf stack
Creating the CloudFormation Stack

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

checking on status of cf stack
Checking on the status of that CloudFormation Stack

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

morelines to look at
More lines to look at from setup.sh

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

getting parameter
Getting that parameter so we know the 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

copy flag
Copying the flag from pentesting.cloud’s s3 bucket to ours.

Cool beans we’re all set up now.

hacker
We’re set up! Way to go, you.

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

create access key pair
Creating an access key pair for a new user we’ll use for the CTF.

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

listing our policies
Listing our policies.

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

contents of policy1
Trying to get the contents of Policy1 and getting denied.

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

running command

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

running enumerate-iam
Running enumerate-iam.

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.

https://expeditedsecurity.com/aws-in-plain-english

List the buckets that exist

aws — profile pentesting-user s3 ls

list buckets in our account
Listing the buckets in our account
list contents of bucket
List the contents of that bucket

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

403 forbidden
Alright. No dice, there’s probably some way we can escalate our privileges. Let’s look at ec2 instances to see if anything is running

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.

https://expeditedsecurity.com/aws-in-plain-english

Let’s try to describe all the instances we can see.

aws — profile pentesting-user ec2 describe-instances — region us-west-2

list instance in uswest2
Listing the instances that are in 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.

https://expeditedsecurity.com/aws-in-plain-english

aws — profile pentesting-user lambda list-functions

checking lambda
Bingo. We got a function in Lambda we can see.

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

get lambda func
Getting the Lambda Function for more specifics.

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.

browser dl
That sweet browser download, I guess I broke the CLI-only rule.

Okay, now we will list the contents of the file

type .\index.py

contents of index.py
The contents of index.py from the Lambda function.

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

invoke lambda func
Invoking the Lambda function.

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

type .\flag.txt

lambda error
Looking at the error we got from the Lambda Function.

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

close look at code
A closer look at the code, and mapping of the variables to where they end up as parameters.

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”))

base64 encoding bucket name
Base64 encoding a bucket name

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)

base64 encoding
Base64 encoding “flag.txt”

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

payload from aws lambda help
— payload from the AWS lambda help command.

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

invoking func to get an error
Invoking a lambda function with a payload, and getting an error.

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

invoke func for diff error
Invoking a lambda function with a payload, and getting a DIFFERENT error.

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 : & \ < > ^ |

https://ss64.com/nt/syntax-esc.html

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

success
Invoking a lambda, and SUCCEEDING!

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

something is still wrong
Checking out the contents of the file and seeing we gave it an invalid bucket name.

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”))

base64 utf8
Base64 encoding, the UTF8 way. It’s different than our previous Unicode way.

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

vague response
1? What does it mean??

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”))

yay
Getting that flag!

We did it.

turning it in
Turning in the flag

Appendix: My Google Searches

One of the things I wanted to try out was documenting my Google rabbit holes and perhaps how my Googling improves over time.

The way I’ve decided to structure it is with the “Goal” I hoped to accomplish by Googling, the different searches I did, and whether I got the information I needed to move forward.

Goal 1: Find the full administrator policy we need to attach to the pentesting-admin user.

  • [Success] Search 1: aws full admin policy

Goal 2: What’s the command for listing the policies a specific user has again?

  • [Success] Search 1: aws list user policies

Goal 3: What’s the command for describing a policy?

  • [Success] Search 1: iam list permissions on policy

Goal 4: What does that bit at the end of the command mean?

  • [Success] Search 1: 2>&1 >/dev/null

Goal 5: How do I list all the instances in an account?

  • Search 1 :aws ec2 list instances
  • [Success] Search 2: aws cli list ec2 instances in all regions

Goal 6: How do I base64 encode using powershell?

  • [Success] Search 1: powershell base64 encode

Goal 7: What the heck is a UTF-8 Middle byte 0x24 and how do I fix it.

  • [Success] Search: Could not parse payload into json: Invalid UTF-8 middle byte 0x24

Goal 8: Why is my base64 not right?

  • Search 1: base64 encode
  • [Success] Search 2: powershell base64 encode

Goal 9: How do I access the logs from a lambda function… damn you print statements

  • Search 1: How to look at print statements in lambda
  • [Success] Search 2: How to look at print statements in lambda on cli

I hope this post was helpful, or at the very least, interesting.