STF Secret of Meow Olympurr: Creating a CTF Challenge with Cat Pictures Part 2

Glenice
CSG @ GovTech
Published in
8 min readSep 5, 2023

The Secret of Meow Olympurr was a challenge created for GovTech’s second edition of STACK the Flags (STF) in 2022. I had a lot of fun creating this challenge and thought it would be good to note down some of my thoughts behind it.

Apart from utilising the cat pictures, there were three design principles in mind when creating the challenge:

  1. To host the challenge in a multi-cloud environment

There are several cloud service providers available in the market, such as Amazon Web Service (AWS) and Microsoft Azure. While there are similarities in the types of cloud computing services provided (e.g., AWS EC2 and Azure Virtual Machine), the behavior and implementation could be relatively different among different cloud environments.

To push the participants, and myself, a little more, I wanted the challenge to span across AWS and Microsoft Azure.

2. To explore a different exploitation path from the CloudyNekos challenge

In 2022, we submitted a challenge to CSIT’s “The InfoSecurity Challenge” (TISC), which consists of horizontal and vertical privilege escalation within the Amazon Web Service (AWS) cloud environment with resource creation permissions.

For STF, I wanted to create something different and leverage mainly read-only permissions for lateral movement.

3. To focus on enumeration

Being stuck in a rabbit hole is frustrating — sometimes it feels like there is no light at the end of the tunnel until we realize that we missed important information that may open a new path for exploration or help eliminate unviable exploits. Since this challenge leverages on read-only privileges, enumeration is key!

While the Secret of Meow Olympurr is no longer live, you may read the rest of the article to experience and learn more about it!

Challenge statement

Jaga reached Meow Olympurr and met some native Meows. While cautious at first, they warmed up and shared that they have recently created a website to promote tourism!

However, the young Meows explained that they are not cy-purr security trained and would like to understand what they might have misconfigured in their environments. The young Meows were trying to get two different environments to work together, but it seemed like something was breaking….

Log a cy-purr security case by invoking the mysterious function and retrieve the secret code!

Technical walkthrough

At the start of the challenge, a CloudFront URL was provided to the participants.

Upon browsing to the error page, a broken image link was shown.

From the page source, it was observed that the developers were attempting to retrieve the image via a Cross-Origin Resource Sharing (CORS) server.

Since CloudFront is an AWS service, the CORS server might be hosted on an AWS EC2.

Server-side Request Forgery (SSRF) attack is a popular method to steal AWS EC2 metadata credentials:

SSRF vulnerabilities allow attackers to make unauthorized requests from web applications. Since these requests come from the application itself, they can be used to access internal resources that the application has access to but that was not intended to be accessible to outsiders.

We can try accessing the AWS EC2 Instance Metadata Service (IMDS) via the IP address: 169.254.169.254.

When interacting with IMDS, it was observed that the iam path was not present.

Another set of credentials can be found at /identity-credentials/ec2/security-credentials/ec2-instance, which was created regardless of the use of instance identity roles. Since the iam path was not present, it was safe to conclude that IAM role was not attached to the EC2.

Moving on to the next clue — the Azure static website URL.

This time, the image on the error page loaded successfully.

A shared access signature (SAS) token was found on the HTML page, which allows access to the source codes.

A shared access signature is a token that is appended to the URI for an Azure Storage resource. The token contains a special set of query parameters that indicate how the resources may be accessed by the client.

We can use Azure Storage Explorer to connect to the storage account.

$web and $log are default containers created by Azure.

A readme.md file was found in the dev container.

Given the instructions and details, there are three key areas to explore:

  1. Interact with the function app.
  2. Authenticate using the service principal credentials and tenant ID.
  3. Extract the function app’s source code using the SAS token. However, we are missing the storage account name to use the SAS token.

Let us explore in sequence — first up, interacting with the function app.

The instructions on how to interact with the function app are included on the home page.

Upon supplying the URL parameter, it was noted that the function app queries and returns the page source.

Here, we may eliminate leveraging SSRF to communicate with Azure IMDS, as it requires an HTTP Request header. On a side note, SSRF may also be eliminated as an attack path in AWS if the EC2 is configured to use only IMDSv2.

Moving on to the next item — authenticating with Azure CLI using the service principal credentials and tenant ID.

We can list all resources in the current subscription using the Get-AzResource command.

From the results, we can identify a second storage account, which has a similar name to the function app.

The blob container SAS URL can be formed using the storage account name, container name and SAS token:

https://meowvellousappstorage.blob.core.windows.net/scm-releases?sv=2018-11-09&sr=c&st=2022-09-01T00%3A00%3A00Z&se=2022-12-12T00%3A00%3A00Z&sp=rl&spr=https&sig=jENgCFTrC1mYM1ZNo%2F8pq1Hg9BO1VLbXIk%2FpABrK4Eo%3D

Inspect the zip file and note that it stores the source codes of the function app.

It is time for some source code analysis!

Boto3 is the AWS Software Development Kit (SDK) for Python and provides an API for AWS infrastructure services. The AWS credential was retrieved from the Azure key vault upon each API call to invoke the AWS Lambda function.

Since the service principal has the same privileges as the function app, we can retrieve the AWS secret access key using the vault name (in the vault_url) and key name.

We have gained access to AWS!

With that, it is time to enumerate our current access in AWS.

The IAM user entity is attached with two policies and a permission boundary. The two policies are:

  1. azure-policy
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"iam:GetPolicy",
"iam:GetPolicyVersion"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Effect": "Allow",
"Action": [
"iam:ListAttachedUserPolicies"
],
"Resource": "arn:aws:iam::<ACCOUNT_ID>:user/azure_user"
},
{
"Action": [
"lambda:Invoke*",
"lambda:ListFunctions",
"lambda:CreateFunction",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"lambda:UpdateFunctionCode"
],
"Resource": "*",
"Effect": "Allow"
}
]
}

2. azure-policy-extended

{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:AddUserToGroup",
"iam:AttachUserPolicy",
"iam:CreateRole",
"iam:AttachRolePolicy",
"iam:PassRole"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Effect": "Allow",
"Action": [
"iam:ListAttachedUserPolicies",
"iam:GetUser"
],
"Resource": "arn:aws:iam::<ACCOUNT_ID>:user/azure_user"
},
{
"Action": [
"lambda:Invoke*",
"lambda:ListFunctions",
"lambda:CreateFunction",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:GetLogEvents",
"lambda:UpdateFunctionCode"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"cloudformation:CreateStack"
],
"Resource": "*",
"Effect": "Allow"
}
]
}

Interestingly, the permissions seemed to allow privilege escalation; for example, using the iam:CreateRole, lambda:CreateFunction, iam:PassRole and lambda: InvokeFunction permissions.

However, these actions were denied — because of the permission boundary:

{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"iam:*",
"lambda:*"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"lambda:*"
],
"Resource": "arn:aws:lambda:ap-southeast-1:<ACCOUNT_ID>:function:amplify-*",
"Effect": "Deny"
},
{
"Action": [
"lambda:InvokeAsync",
"iam:Generate*",
"iam:Add*",
"iam:Create*",
"iam:Put*",
"lambda:InvokeFunctionUrl",
"iam:Delete*",
"logs:Put*",
"iam:Deactivate*",
"iam:Enable*",
"logs:Create*",
"iam:Pass*",
"lambda:List*",
"iam:Attach*",
"lambda:Create*",
"lambda:Update*",
"cloudformation:*"
],
"Resource": "*",
"Effect": "Deny"
},
{
"Action": [
"logs:Describe*"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"logs:*"
],
"Resource": "arn:aws:logs:ap-southeast-1:<ACCOUNT_ID>:log-group:/aws/lambda/*-webservice:*",
"Effect": "Allow"
}
]
}

When evaluating IAM policy logic, deny statements take precedence. The effective permissions works out to be:

{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"iam:GetPolicy",
"iam:GetPolicyVersion"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Effect": "Allow",
"Action": [
"iam:ListAttachedUserPolicies",
"iam:GetUser"
],
"Resource": "arn:aws:iam::<ACCOUNT_ID>:user/azure_user"
},
{
"Action": [
"lambda:InvokeFunction",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:GetLogEvents",

],
"Resource": "*",
"Effect": "Allow"
}
]
}

Going back to the challenge description, the objective was to invoke a secret function. While lambda:ListFunction is not allowed, we have logs:DescribeLogGroups!

Lambda automatically integrates with CloudWatch Logs and pushes all logs from your code to a CloudWatch Logs group associated with a Lambda function, which is named /aws/lambda/<function name>.

Bingo — we can infer the function name from the log group name!

Invoke the Lambda function and the flag is returned in the response!

Conclusion

As we move to the cloud and/or hybrid environment, it is important to note that cloud service providers adopt a shared responsibility model.

Customers are responsible for ensuring that the permissions assigned follow the principle of least privileges. This challenge also demonstrates the ability to gain meaningful insights and lateral moves between cloud environments with read-only privileges!

If you are looking for more Cloud challenges and adorable cat pictures, fret not, TISC is back for its next run. Who knows, we might meet there again ;)

--

--