[Перевод] 3 разрешения в Google Cloud, которые можно эскалировать, чтобы делать что угодно
Эскалация привилегий — это когда вам запрещено какое-то действие, но вы можете увеличить собственные привилегии и смочь его выполнить.
Сервис-аккаунт всемогущий
resourcemanager.projects.setIamPolicy: Станьте владельцем проекта
Разрешение resourcemanager.projects.setIamPolicy
позволяет назначать роли на проекте. Можно подумать, что оно должно позволять только поделиться теми правами, которые у вас уже есть, то есть назначать только те роли, в которых у вас есть каждое разрешение на уровне проекта.
Это не так. Можно давать любые роли кому угодно, в том числе самому себе. Вот пример, который это показывает.
Создайте сервис-аккаунт и назначьте ему кастомную роль, у которой есть только разрешения читать и устанавливать политику:
$ gcloud iam service-accounts create "test-1"
Created service account [test-1].
$ gcloud iam service-accounts keys create key.json \
--iam-account=test-1@$PROJECT.iam.gserviceaccount.com
created key [0e61d692b3f12d8a5b2051f2a75a8db25accc765] of type [json] as
[key.json] for [test-1@project-id.iam.gserviceaccount.com]
$ gcloud iam roles create Test1 \
--permissions=resourcemanager.projects.setIamPolicy,resourcemanager.projects.getIamPolicy \
--stage=GA
Created role [Test1].
etag: BwYTic5Oc5s=
includedPermissions:
- resourcemanager.projects.getIamPolicy
- resourcemanager.projects.setIamPolicy
name: projects/project-id/roles/Test1
stage: GA
title: Test1
$ gcloud projects add-iam-policy-binding $PROJECT \
--member="serviceAccount:test-1@$PROJECT.iam.gserviceaccount.com" \
--role="projects/$PROJECT/roles/Test1"
Updated IAM policy for project [project-id].
bindings:
- members:
- serviceAccount:test-1@project-id.iam.gserviceaccount.com
role: projects/project-id/roles/Test1
etag: BwYTidGhDQY=
version: 1
$ gcloud auth revoke --all
Залогиньтесь с этим сервис-аккаунтом и попробуйте создать Cloud Storage bucket. Не получится, потому что не хватает прав:
$ gcloud auth activate-service-account --key-file=key.json
Activated service account credentials for:
[test-1@project-id.iam.gserviceaccount.com]
$ gcloud storage buckets create "gs://$PROJECT-test1"
Creating gs://project-id-test1/...
ERROR: (gcloud.storage.buckets.create) HTTPError 403:
test-1@project-id-test1.iam.gserviceaccount.com does not have
storage.buckets.create access to the Google Cloud project.
Permission 'storage.buckets.create' denied on resource (or it may not exist).
Но эти же права позволяют вам стать владельцем проекта:
$ gcloud projects add-iam-policy-binding $PROJECT \
--member="serviceAccount:test-1@$PROJECT.iam.gserviceaccount.com" \
--role='roles/owner'
Updated IAM policy for project [project-id].
bindings:
- members:
- serviceAccount:test-1@project-id.iam.gserviceaccount.com
role: projects/project-id/roles/Test1
- members:
- serviceAccount:test-1@project-id.iam.gserviceaccount.com
role: roles/owner
etag: BwYTikPia9U=
version: 1
Теперь можно создать bucket:
$ gcloud storage buckets create "gs://$PROJECT-test1"
Creating gs://project-id-test1/...
В этом примере мы использовали два разрешения на чтение и запись политики. Это потому, что gcloud
требует разрешение на чтение, прежде чем внести изменения. Но для прямого REST-запроса на изменение политики достаточно и одного разрешения.
resourcemanager.organizations.setIamPolicy: Станьте владельцем организации
Да, вы отгадали. Разрешение resourcemanager.organizations.setIamPolicy
— это самое мощное оружие, с ним можно таким же образом захватить всю организацию.
iam.roles.update: Добавьте любые разрешения в свою кастомную роль
Разрешение iam.roles.update
позволяет менять кастомные роли. Можно подумать, что оно должна позволять добавить в них только те разрешения, которые у вас самих есть (на уровне проекта для роли проекта или на уровне организации для роли организации).
Это не так. Можно добавить любые разрешения, включая те, которых у вас нет, в роль, которая у вас есть.
Чтобы это проверить, сделайте сервис-аккаунт и дайте ему кастомную роль с двумя разрешениями на чтение и обновление ролей:
$ gcloud iam service-accounts create "test-2"
Created service account [test-2].
$ gcloud iam service-accounts keys create key.json \
--iam-account=test-2@$PROJECT.iam.gserviceaccount.com
created key [37fcfa449049e4ab44e40e1a9beab00c3d26276a] of type [json] as
[key2.json] for [test-2@project-id.iam.gserviceaccount.com]
$ gcloud iam roles create Test2 \
--permissions=iam.roles.get,iam.roles.update \
--stage=GA
Created role [Test2].
etag: BwYTm0OCSu0=
includedPermissions:
- iam.roles.get
- iam.roles.update
name: projects/project-id/roles/Test2
stage: GA
title: Test2
$ gcloud projects add-iam-policy-binding $PROJECT \
--member="serviceAccount:test-2@$PROJECT.iam.gserviceaccount.com" \
--role="projects/$PROJECT/roles/Test2"
Updated IAM policy for project [project-id].
bindings:
- members:
- serviceAccount:test-2@project-id.iam.gserviceaccount.com
role: projects/project-id/roles/Test2
etag: BwYTm0_Mbsk=
version: 1
$ gcloud auth revoke --all
Залогиньтесь с этим сервис-аккаунтом и попробуйте создать Cloud Storage bucket. Не получится, потому что не хватит прав:
$ gcloud auth activate-service-account --key-file=key.json
Activated service account credentials for:
[test-2@project-id.iam.gserviceaccount.com]
$ gcloud storage buckets create "gs://$PROJECT-test2"
Creating gs://project-id-test2/...
ERROR: (gcloud.storage.buckets.create) HTTPError 403:
test-2@project-id.iam.gserviceaccount.com does not have
storage.buckets.create access to the Google Cloud project.
Permission 'storage.buckets.create' denied on resource (or it may not exist).
Но эти же права позволяют вам добавить разрешение в собственную роль:
$ gcloud iam roles update Test2 --add-permissions=storage.buckets.create
etag: BwYTm35Gekg=
includedPermissions:
- iam.roles.get
- iam.roles.update
- storage.buckets.create
name: projects/project-id/roles/Test2
stage: GA
title: Test2
И теперь можно создавать bucket:
$ gcloud storage buckets create "gs://$PROJECT-test2"
Creating gs://project-id-test2/...
В этом примере нам были нужны два разрешения для чтения и обновления ролей. Это потому, что gcloud
требует разрешение на чтение, прежде чем вносить изменения. Но для прямого REST-запроса достаточно одного разрешения на редактирование.
Почему это плохо
Не интуитивно
Если бы вы создавали систему управления правами с нуля, вы не разрешили бы это.
Плохая документация
Я нашёл только одно не очень явное упоминание в документации REST:
Чат-боты говорят, что всё безопасно
Сейчас люди изучают всё, задавая вопросы чат-ботам. Для многих задач это нормально, но не для вопросов безопасности. На момент написания многие чат-боты уверяли, что эскалировать эти привилегии нельзя, в том числе Gemini от Google!
Только бот Microsoft предупредил об опасности:
Другие облачные провайдеры работают так же
Это не специфичная проблема Google Cloud. Amazon Web Services работает так же.
Чтобы это проверить, создайте JSON-файл с политикой:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:CreatePolicyVersion"
],
"Resource": "*"
}
]
}
Создайте политику и назначьте её новому пользователю:
$ aws iam create-policy --policy-name Test2Policy \
--policy-document file://policy.json
{
"Policy": {
"PolicyName": "Test2Policy",
"PolicyId": "policy-id",
"Arn": "arn:aws:iam::aws-account-id:policy/Test2Policy",
"Path": "/",
"DefaultVersionId": "v1",
"AttachmentCount": 0,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"CreateDate": "2024-03-15T04:42:54+00:00",
"UpdateDate": "2024-03-15T04:42:54+00:00"
}
}
$ aws iam create-user --user-name test-2
{
"User": {
"Path": "/",
"UserName": "test-2",
"UserId": "user-id",
"Arn": "arn:aws:iam::aws-account-id:user/test-2",
"CreateDate": "2024-03-15T04:39:54+00:00"
}
}
$ aws iam create-access-key --user-name test-2
{
"AccessKey": {
"UserName": "test-2",
"AccessKeyId": "access-key-id",
"Status": "Active",
"SecretAccessKey": "secret-access-key",
"CreateDate": "2024-03-15T04:40:30+00:00"
}
}
$ aws iam attach-user-policy \
--policy-arn arn:aws:iam::aws-account-id:policy/Test2Policy \
--user-name test-2
Залогиньтесь от имени этого пользователя и попробуйте создать bucket. Не получится, потому что не хватает прав:
$ aws configure
AWS Access Key ID [********************]: access-key-id
AWS Secret Access Key [********************]: secret-access-key
Default region name [us-east-1]:
Default output format [json]:
$ aws s3 mb s3://my-bucket-name
make_bucket failed: s3://my-bucket-name An error occurred
(AccessDenied) when calling the CreateBucket operation: Access Denied
Добавьте недостающее действие в собственную политику:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:CreatePolicyVersion",
"s3:CreateBucket"
],
"Resource": "*"
}
]
}
И обновите её:
$ aws iam create-policy-version \
--policy-arn arn:aws:iam::aws-account-id:policy/Test2Policy \
--policy-document file://policy.json --set-as-default
{
"PolicyVersion": {
"VersionId": "v2",
"IsDefaultVersion": true,
"CreateDate": "2024-03-15T05:00:38+00:00"
}
}
Теперь вы можете создать bucket:
$ aws s3 mb s3://my-bucket-name
make_bucket: my-bucket-name
Практические последствия
Вот типовая модель трёх уровней ролей в CI/CD:
Например, вы хотите создавать стейдж при каждом пул-реквесте, чтобы запускать тесты. На верхнем уровне будет главный сервис-аккаунт, который может создавать проекты: project-creator
. На каждом пул-реквесте он создаёт проект. Это мощный аккаунт, и его нужно использовать как можно меньше.
Поэтому для деплоя конкретного проекта после его создания вы создаёте аккаунт попроще: deploy
. Он уже настраивает все сервисы и ресурсы в проекте. И он создаёт самые слабые роли и аккаунты, которые используют конкретные раннеры: поды, cloud-функции и т.п.
У каждого раннера должны быть минимальные права, нужные ему для работы, вплоть до конкретных таблиц и очередей.
Если бы не проблема эскалации привилегий, то аккаунту deploy
можно было дать сумму привилегий раннеров. Тогда, если злоумышленник получит доступ к аккаунту deploy
, будут хоть какие-то ограничения на то, что он может сделать с проектом.
Но поскольку deploy
должен создавать и назначать роли раннерам, у него есть всё для того, чтобы дать себе любые права, если его ключ утечёт. Поэтому нет смысла делать его чем-то меньшим, чем Owner
на проекте. Вот вам и принцип минимальных привилегий.
Что нужно сделать облачным провайдерам
Опишите привилегии
В AWS в политике напрямую прописываются разрешённые действия, и у каждого действия есть отдельная вот такая страница. Это хорошо.
В Google Cloud есть дополнительная надстройка — разрешение (permission). В большинстве случаев одно разрешение соответствует одному REST-вызову, но всё равно это лишняя единица, которую нужно искать, и это непросто:
Ничего не найдено для «roles.update» в документации REST для сервиса IAM в Google Cloud.
Из-за этого люди реже думают про безопасность, когда они нашли какое-то решение, которое просто работает для их случая.
Сделайте раздел про эскалацию
На отдельной странице распишите все способы эскалировать привилегии. Моя статья не исчерпывающая, потому что у многих более мелких ресурсов тоже существует разрешение setIamPolicy
.
Делайте ссылки на эту страницу со всех страниц, где обсуждаются рискованные методы, роли и разрешения. Делайте красные баннеры.
В справочнике разрешений не только нет отдельной страницы для каждого, но и вообще нет никакого описания. Из-за этого чат-ботам не на чем учиться и они постоянно путают роли и разрешения.
Если сделать такую страницу, то чат-боты смогут на ней учиться и будут точнее отвечать на вопросы о безопасности.
Сделайте неэскалируемые разрешения
Можно будет делать более безопасный CI/CD, если в дополнение к существующим добавить такие разрешения, которые позволяют делиться только теми правами, которые уже есть у пользователя. Можно назвать их вот так:
resourcemanager.projects.selfConstrainedSetIamPolicy
resourcemanager.organizations.selfConstrainedSetIamPolicy
iam.roles.selfConstrainedUpdate
Спасибо.
Не пропускайте мои статьи, подпишитесь здесь и добавляйтесь в Телеграм‑каналы: