Проблема
Стандартный чек-лист аудита звучит как «проверить, что у пользователей нет AdministratorAccess». На практике почти никто не выдаёт AdministratorAccess напрямую — а путь до тех же прав всё равно есть, просто он собран из нескольких формально безопасных разрешений.
Известно больше двадцати задокументированных техник эскалации привилегий в AWS IAM. Самые частые в реальных инфраструктурах: iam:PassRole в сочетании с сервисом, который выполняет код от имени роли (Lambda, EC2, CloudFormation, Glue); iam:CreatePolicyVersion и iam:SetDefaultPolicyVersion, позволяющие принципалу переписать собственные эффективные права; sts:AssumeRole на роль с trust policy без ограничивающего Condition; забытые access key без ротации у сервисных аккаунтов с широкими правами.
Каждое из этих разрешений по отдельности почти всегда проходит ручной review — оно нужно для легитимной задачи (CI/CD должен уметь деплоить Lambda, сервис должен уметь ассюмить роль). Проблема появляется на пересечении: у кого есть путь от «может задеплоить функцию» до «может стать той ролью, которую задеплоил».
Решение
Аудит должен строить граф «кто может стать кем», а не таблицу «у кого какие политики привязаны». Для этого не нужно писать анализатор с нуля — AWS IAM Access Analyzer нативно ищет unintended access, а open-source инструменты вроде PMapper, Prowler и ScoutSuite целенаправленно ищут пути эскалации, а не отдельные misconfig.
Для каждого privileged-принципала фиксируется: какие роли достижимы через PassRole/AssumeRole, есть ли MFA, возраст access key, есть ли wildcard в Action или Resource. Дальше пути приоритизируются по длине от низкодоверенной точки входа (например, CI/CD service account, у которого есть только доступ на чтение репозитория) до admin-эквивалентных прав.
Что проверить руками
- Принципалы с
iam:PassRole+ правом вызывать Lambda/EC2/CloudFormation/Glue. - Trust policy ролей без
Condition(aws:SourceArn,sts:ExternalId) — кто угодно с правом assume может ей воспользоваться. - Wildcard
ActionилиResourceв inline и managed policies, особенно на service-ролях. - MFA на IAM-пользователях с доступом в консоль и на root-аккаунте.
- Access key старше 90 дней без ротации, особенно у сервисных аккаунтов.
- CloudTrail: включён ли на все регионы, защищён ли лог от удаления самим же скомпрометированным принципалом.
Безопасный пример кода
Read-only аудит через boto3: ищет principals без MFA при наличии console access, старые access key и wildcard-разрешения в managed policies. Скрипт ничего не меняет и не эксплуатирует — только собирает сигналы для дальнейшего ручного review.
import boto3
from datetime import datetime, timezone
iam = boto3.client("iam")
MAX_KEY_AGE_DAYS = 90
def audit_users():
findings = []
for user in iam.list_users()["Users"]:
name = user["UserName"]
mfa = iam.list_mfa_devices(UserName=name)["MFADevices"]
has_console_access = True
try:
iam.get_login_profile(UserName=name)
except iam.exceptions.NoSuchEntityException:
has_console_access = False
if has_console_access and not mfa:
findings.append((name, "console access without MFA"))
for key in iam.list_access_keys(UserName=name)["AccessKeyMetadata"]:
age_days = (datetime.now(timezone.utc) - key["CreateDate"]).days
if age_days > MAX_KEY_AGE_DAYS:
findings.append((name, f"access key {key['AccessKeyId']} is {age_days}d old"))
return findings
def audit_wildcard_policies():
findings = []
for policy in iam.list_policies(Scope="Local")["Policies"]:
doc = iam.get_policy_version(
PolicyArn=policy["Arn"], VersionId=policy["DefaultVersionId"]
)["PolicyVersion"]["Document"]
for stmt in doc.get("Statement", []):
if stmt.get("Effect") != "Allow":
continue
actions = stmt.get("Action", [])
actions = [actions] if isinstance(actions, str) else actions
if "*" in actions:
findings.append((policy["PolicyName"], "wildcard Action in Allow statement"))
return findings
for name, issue in audit_users() + audit_wildcard_policies():
print(name, "-", issue)
Как это должно попасть в отчёт
- Signals: отсутствие MFA на привилегированных принципалах, wildcard-политики, устаревшие access key, trust policy без Condition.
- Controls: обязательный MFA для консольного доступа, автоматическая ротация ключей, least-privilege review вместо ручного, SCP-guardrails на уровне организации.
- Retest: те же запросы после исправления не должны находить сигналы — путь от низкодоверенного принципала до admin должен быть разорван, а не просто задокументирован.