上周五晚上十点多,一个平时只用来做图床代理的接口突然被人刷了三千多次请求。运维群里的告警先响,值班同事看了一眼日志,发现这些请求的 URL 长得都很正常每一个都是指向我们自己 CDN 域名的一张图片。但真正让我后背发凉的不是流量,而是我们在其中一台云主机上抓到的东西:进程的文件描述符里,出现了一个本不该属于它的、有效期两小时的 AWS 临时访问密钥。
攻击者提交的并不是什么花哨的 payload,而是一个图片地址。我们的服务在后端去「拉取」这个地址,返回的不是图片,而是一段 JSON,里面装着这台机器的 IAM 角色凭据。这就是服务端请求伪造(SSRF)最经典的杀伤路径:不是打你的应用,而是让你的应用替你去打你的云。
如果你也在做带图片抓取、URL 预览、PDF 渲染、webhook 回连这类功能,或者你的服务跑在云主机上并挂着角色凭据,可以先看看这几家的报价和技术说明:joysol(香港/东京/新加坡高防云服务器与裸金属)、linkcli(海外高防服务器 抗DDoS 云主机)高防护的基础设施能在源头挡掉大量来源异常的扫描流量,为后面要说的加固手段争取时间窗口。

一、169.254.169.254 到底是什么,为什么它这么值钱
几乎所有公有云都有一个「元数据服务」,国内外的地址基本统一在 169.254.169.254。它的作用是让机器上的程序在不需要写死任何密钥的情况下,向平台询问「我这块云服务器的身份凭据是什么」。
在 AWS 上,用这个地址加上不同后缀能拿到的东西包括:/latest/meta-data/iam/security-credentials/<角色名> 拿到临时 AK/SK;/latest/meta-data/local-ipv4 拿到内网地址;/latest/user-data 直接拿到启动时执行的脚本,很多人的数据库密码就写在那上面。国内的云厂商路径略有不同,但同样不需要任何认证,只要能发出请求就能拿到。
关键点在于:这个地址是 169.254.0.0/16 这个链路本地网段,它只存在于机器自己这张网卡上,不走任何路由,也不经过安全组。所以即使你把安全组入站规则配得再严,只要服务端进程能主动发包,请求照样能出去。攻击者要的从来不是「访问你的服务器」,而是「让你的服务器去访问它」。

二、三条路径:从外网到密钥的三种走法
我们复盘时发现,同一个 SSRF 漏洞,实际被利用时的路径至少有三类,修法也完全不同。
路径一:直接请求。 最朴素的一种。应用提供一个「填个 URL 我帮你抓图」的接口,攻击者直接把地址填成 http://169.254.169.254/latest/meta-data/iam/security-credentials/。如果应用对返回内容不做类型校验、不限制目标地址,这份 JSON 就会原封不动地回到攻击者手里。我们那台出事的机器,走的就是这条路。
路径二:重定向跳转。 这是最容易被忽略的一种,因为第一道防线看起来是有效的应用确实校验了 URL 域名,只允许自家的域名。但攻击者在自己服务器上返回 302,跳转到 169.254.169.254,而大多数 HTTP 客户端默认会跟随重定向。如果你的白名单校验发生在「发起请求之前」,那它拦的其实只是第一跳。这一点我们复现了三次,全中。
路径三:协议与解析器差异。 同一个目标地址有很多种写法:http://127.0.0.1:80/、http://0x7f000001/、http://017700000001/、http://[::1]/,还有 IPv4 映射的 IPv6 写法 http://[::ffff:169.254.169.254]/。更麻烦的是 DNS rebinding:攻击者的域名第一次解析到公网 IP 通过校验,第二次解析到 169.254.169.254,前后两次都是同一个域名,域名白名单形同虚设。还有一种是用 gopher://、dict://、file:// 这类协议去读本地文件,某些解析器还会把 http://a.com\@169.254.169.254/ 这种带反斜杠或百分号编码的地址解析成完全不同的主机。

三、为什么我怀疑「安全组配了也没用」
当时我们第一反应是去查安全组。查完发现入站规则其实相当干净,只放开了 443 和 SSH 白名单,169.254 相关的地址一条都没有。这恰恰是很多人会掉进去的坑:安全组管的是「别人能不能连我」,而 SSRF 是「我主动去连别人」。前者拦不住后者,加再多安全组规则也没用。真正要动的是三处:应用层的出网策略、系统层的路由限制、以及云平台上角色权限本身的收敛。
顺带说一个反直觉的结论:给这台机器配一个公网 IP 的安全组策略,或者干脆不绑定公网 IP 并用 SSM 之类的带外通道管理,反而能缩小攻击面。很多云平台的元数据服务在开启 IMDSv2 之后,会要求请求带上带签名的会话令牌,并且把跳转到元数据地址的请求直接拒绝掉这个开关是免费的,应该第一时间打开。

四、我的加固清单(按优先级排)
下面这几条是我们事后逐条补上的,每一条都对应上面某个具体的利用路径。
1. 关掉不需要的元数据能力,开启 IMDSv2。 把元数据服务改成 session 模式(HttpTokens=required、HttpPutResponseHopLimit=1),一跳重定向直接失效。这条是所有加固里性价比最高的,因为它同时干掉了「重定向跳转」和「DNS rebinding」两条路径。
2. 应用层做 IP 级校验,而不是域名级。 把 URL 解析成 IP 之后再判断是否落在私有网段、链路本地网段、回环网段里,命中就直接拒绝。注意要在解析之后、重定向之后各校验一次,并且禁止自动跟随重定向或者自己实现一个只允许跳一次、跳完重新校验的跟随逻辑。DNS rebinding 的根治办法是「解析一次,锁定 IP,请求时直接连这个 IP 并带上正确的 Host 头」。
3. 出网走白名单。 在应用所在的容器或主机上做 egress 限制,只允许访问真正需要的外部域名和端口,其余一律丢弃。这是从网络层面兜底,比在代码里判断可靠得多,因为代码路径总有遗漏的那一条。
4. 收敛云上角色权限。 最关键的一条。临时密钥之所以一读就能用,是因为绑定的角色权限太大。把角色拆细,只给它真正需要的操作;能用实例角色代替长期密钥就用实例角色;能不给权限就不给。同时开 CloudTrail 或对应平台的审计日志,对元数据接口的访问量做异常告警正常业务不会在两秒内读几十次凭据。
5. 响应体做类型和大小限制。 抓图接口就只接受图片类型的内容,限制响应大小和超时。元数据返回的 JSON 会被你的图片解码器直接报错拒绝,这是最后一道兜底。别为了「灵活」就把任意 URL 的任意内容原样返回给前端。
6. 密钥定期轮转并留痕。 一旦确认泄露,第一件事是禁用对应凭据而不是改代码。ECS 角色的密钥一般十分钟到两小时就会自动轮换,但如果你用的是自己写在配置文件里的长期 AK/SK,泄露窗口就是无限期,只能手动换。
五、怎么确认自己是不是中招了
如果你想自查,不用真去攻击。直接在服务器上执行一次元数据请求,看返回内容是不是你的凭据;在应用日志里搜索 169.254、169.254.169.254、metadata、iam/security-credentials 这些关键字,看有没有异常的读取记录;再检查一下角色的 AccessKey 列表里有没有你完全不认识的条目、有没有从未用过的凭据。最后把凭据禁用一遍,观察业务是否报错如果禁用后没有任何业务受影响,那说明这个凭据本来就是多余的。
说到底,SSRF 之所以能造成这么大的影响,是因为它把「应用有权限」这件事直接等价成了「攻击者有权限」。而应用几乎总是拿着比它实际需要的更大的一把钥匙既能读文件,又能连数据库,还顺手揣着一份云平台凭据。真正有效的防守不在于堵住每一个可能的绕过姿势,而在于把权限本身收窄到最小,让一次成功的 SSRF 最多只能拿到一段无害的文本。



下载 
下载 
下载 
下载
下载 
下载
喜欢
顶
难过
囧
围观
无聊




