[Перевод] 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:

3f20253c32880e8220edfe7025cff5d3.png

Чат-боты говорят, что всё безопасно

Сейчас люди изучают всё, задавая вопросы чат-ботам. Для многих задач это нормально, но не для вопросов безопасности. На момент написания многие чат-боты уверяли, что эскалировать эти привилегии нельзя, в том числе Gemini от Google!

59f4eb07b98e47449b32e287848d8586.png132b147e3d187db1b2f241ed69200e58.png2307d3fea77889976cb23d0513385bf3.png

Только бот Microsoft предупредил об опасности:

c425bc6405fc006a10b43c29a63351e4.png

Другие облачные провайдеры работают так же

Это не специфичная проблема 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:

5e57754b26fa04bed04f4ac3c52b4b66.png

Например, вы хотите создавать стейдж при каждом пул-реквесте, чтобы запускать тесты. На верхнем уровне будет главный сервис-аккаунт, который может создавать проекты: 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

Спасибо.

Не пропускайте мои статьи, подпишитесь здесь и добавляйтесь в Телеграм‑каналы:

© Habrahabr.ru