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

Сервис-аккаунт всемогущий
Сервис-аккаунт всемогущий

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.
Ничего не найдено для “roles.update” в документации REST для сервиса IAM в Google Cloud.

Из‑за этого люди реже думают про безопасность, когда они нашли какое‑то решение, которое просто работает для их случая.

Сделайте раздел про эскалацию

На отдельной странице распишите все способы эскалировать привилегии. Моя статья не исчерпывающая, потому что у многих более мелких ресурсов тоже существует разрешение setIamPolicy.

Делайте ссылки на эту страницу со всех страниц, где обсуждаются рискованные методы, роли и разрешения. Делайте красные баннеры.

В справочнике разрешений не только нет отдельной страницы для каждого, но и вообще нет никакого описания. Из-за этого чат-ботам не на чем учиться и они постоянно путают роли и разрешения.
В справочнике разрешений не только нет отдельной страницы для каждого, но и вообще нет никакого описания. Из-за этого чат-ботам не на чем учиться и они постоянно путают роли и разрешения.

Если сделать такую страницу, то чат‑боты смогут на ней учиться и будут точнее отвечать на вопросы о безопасности.

Сделайте неэскалируемые разрешения

Можно будет делать более безопасный CI/CD, если в дополнение к существующим добавить такие разрешения, которые позволяют делиться только теми правами, которые уже есть у пользователя. Можно назвать их вот так:

  • resourcemanager.projects.selfConstrainedSetIamPolicy

  • resourcemanager.organizations.selfConstrainedSetIamPolicy

  • iam.roles.selfConstrainedUpdate

Спасибо.

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

  • На английском: ainkin_com

  • На русском — реже и с задержкой: ainkin_com_ru

Комментарии (2)


  1. amkartashov
    29.03.2024 14:00
    +1

    AWS решает проблему с делегированием управления пермиссиями используя permission boundaries https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html#access_policies_boundaries-delegate

    GCP - c помощью похожего ограничения набора пермиссий, которыми можно управлять: https://cloud.google.com/iam/docs/setting-limits-on-granting-roles


    1. alexeyinkin Автор
      29.03.2024 14:00

      Спасибо. IAM conditions тяжело ревьювить. И на уровень отдельных разрешений так спуститься, кажется, нельзя. То есть разрешить создавать кастомные роли с подмножеством разрешений -- не получится.