ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SSRF攻击实验全解析:从可控URL参数到拿下本地管理后台

SSRF攻击实验全解析:从可控URL参数到拿下本地管理后台

做安全测试的朋友对SSRF这个名字应该不陌生,它常年排在主流漏洞类型的前列,危害从内网探测到敏感文件读取,再到某些场景下的命令执行,几乎什么都有可能。但说句实话,很多人第一次完整理解SSRF是怎么一步步被利用的,往往不是从理论文章里学会的,而是从一个个实验里摸出来的。最近我把一个针对本地服务器的SSRF攻击实验从头到尾做了一遍,环境是一个在线购物网站,后台跑着本地服务,攻击者手里能控制的只有一个URL参数。这篇文章不打算念PPT式的理论,就把实验的每一个环节、踩过的坑、以及最后怎么从原理层面理解这件事,全部摊开来讲清楚。不管你是刚入门Web安全的学生,还是工作中需要排查这类漏洞的开发,应该都能从里面找到自己想要的东西。

1. 实验环境准备与需求分析

1.1 这个实验到底在模拟什么场景

实验的标题是“Basic SSRF against the local server”,翻译过来就是“针对本地服务器的简单SSRF攻击”。听起来有点绕,但拆开看其实很直白:

你面前有一个购物网站,商品页面上有一个查看库存的功能。这个功能在服务端实现时,会向一个远程URL发起HTTP请求,然后把结果返回给前端页面。问题在于,这个URL并不是服务端写死的,而是由用户可控的请求参数传递进去的。

整个实验环境由三个角色组成:

  • 前端购物网站:对外开放,攻击者可以正常访问、登录、浏览商品。
  • 本地管理后台:运行在同一台服务器的内网接口上,不对外开放,只允许来自本机的请求访问。
  • 攻击者:手里只有一个浏览器和一个抓包工具,目标是借助购物网站这个“中间人”,访问到本不该访问的内部管理接口,并完成某个敏感操作。

这种场景在真实世界里非常常见。很多企业应用都有类似的功能:管理员在后台填写一个远程回调地址,服务器去请求这个地址;或者用户提交一个图片链接,服务器去抓取这张图片。只要服务端对传入的URL没有做严格的限制,攻击者就可以把地址改成http://127.0.0.1/admin或者http://localhost/admin,让服务器自己去访问自己内部的管理接口。

这个实验要达成的具体目标是:通过修改商品库存查询功能中的URL参数,访问本地管理后台,然后利用后台的删除用户功能,把某个指定用户删掉。

1.2 工具准备与测试思路

做这类实验,工具不需要多,但每一件都有不可替代的用途。我这次准备了三样东西:

工具用途说明
Burp Suite抓包、改包、重放请求核心工具,用来拦截浏览器请求,修改参数后重新发送
浏览器访问购物网站、触发功能请求登录、浏览商品、点击查看库存按钮
curl 命令行辅助验证请求格式需要快速验证请求时可以用,但抓包工具已经够用了

Burp Suite 的配置其实很简单。下载并启动后,在Proxy选项卡里打开拦截开关,把浏览器的代理指向127.0.0.1:8080即可。注意浏览器端要装好对应的证书,否则HTTPS流量会抓不到明文。

实验的测试思路可以概括成三步:

  1. 找到那个可控的URL参数,搞清楚它的正常取值是什么。
  2. 把这个参数的值从合法的外部地址改成内网地址,观察服务端的响应变化。
  3. 顺着响应中暴露的信息,继续构造下一步请求,直达敏感操作。

整个流程并不复杂,但每一步的意图和原理一定要想清楚,否则只是机械地照着操作记录点鼠标,做完就忘,下次遇到实际问题照样不会排查。接下来我从漏洞原理讲起,再逐步进入具体攻击步骤。

2. SSRF漏洞的核心原理

2.1 一句话理解什么是SSRF

SSRF,全称是Server-Side Request Forgery,服务端请求伪造。很多人第一次听到这四个字会觉得高深,其实用一句话就能说清楚:

服务端接收了一个攻击者可控的URL,替攻击者去访问了这个URL,然后把访问的结果反馈出来或者执行了对应的操作。

关键在于,发起请求的是服务端,而不是攻击者的浏览器。服务端在服务器上跑着,它发出的请求天然带着“内部人员”的身份。服务器本机自己访问自己的管理端口,往往是畅通无阻的;而攻击者从外网直接访问那个管理端口,则会被防火墙拦下来。

打个比方:攻击者没法直接进入一栋大楼的安保中心,但他可以让大楼内部的保洁员帮忙送一封信进去,再让保洁员把里面的情况转告出来。服务端代码就是这个保洁员,SSRF攻击的本质就是利用这类“内部人员”替自己办事。

2.2 为什么服务端会变成攻击者的代理

你可能会问:正常的服务端代码,怎么会傻到把用户提交的URL当指令去执行呢?

还真会。因为业务场景里确实有太多合理需求了:

  • 用户在个人资料里填写头像的远程图片链接,服务器后台去抓取这个链接并保存到本地。
  • 管理员在后台填写一个Webhook回调地址,服务器向这个地址发送事件通知。
  • 在线文档功能允许用户导入远程文件,服务器去请求文件内容并解析。
  • 商品页面的“检查库存”功能,服务器去请求供应商的库存系统。

这些场景的共同点是:服务端必须要“根据用户提供的参数,向某个URL发起请求”。如果代码在发起请求前,没有对URL的目标地址做合法性校验,攻击者就可以把URL替换成任何他想要的目标。

以本实验为例,商品页面的“查看库存”功能,正常请求是这样的:

POST /product/stock HTTP/1.1 Host: shop.example Content-Type: application/x-www-form-urlencoded Cookie: session=xxxxxxxx stockApi=http://stock.example:8080/product/stock/check

服务端收到stockApi参数后,会向http://stock.example:8080/product/stock/check发起一次HTTP请求,然后把响应内容返回给用户。整个业务流程看起来合情合理,问题就出在:stockApi的值完全由用户控制,服务端完全没有检查这个URL指向哪里。

2.3 SSRF的常见危害层级

SSRF的危害可以从低到高分成几个层级,对这个排查思路很有帮助:

  • 第一层:内网端口探测。攻击者把URL改成http://192.168.x.x:8080,从响应时间或返回内容判断端口是否开放。
  • 第二层:读取本地管理后台页面。就像本实验做的那样,攻击者访问到了http://127.0.0.1/admin,拿到原本不该暴露的页面内容。
  • 第三层:利用管理后台的功能接口。这就更进一步了,不仅看页面,还能通过后台接口执行敏感操作,比如删除用户、修改配置、上传文件。
  • 第四层:在某些极端场景下,SSRF可以配合其他漏洞打穿整个内网,甚至利用file://协议读取服务器上的本地文件。

本实验处在第二层到第三层之间,恰好能完整体验SSRF从发现到利用的全过程,又不会涉及太复杂的绕过技巧,非常适合作为入门练习。

3. 实验攻击与利用步骤

3.1 定位那个可控参数

实验一开始,打开浏览器访问购物网站首页,随便点击一个商品进入详情页,然后找到“查看库存”这个按钮。注意到这里有个细节:这个按钮不像普通页面跳转那样打开一个新页面,而是会向后端发送一个异步请求。正因为如此,我们需要用抓包工具来观察它到底请求了什么。

打开Burp Suite的Proxy拦截功能,在商品详情页点击“查看库存”,会看到Burp拦截到一条看起来非常典型的请求:

POST /product/stock HTTP/1.1 Host: shop.example Cookie: session=xxxxxxxxx Content-Type: application/x-www-form-urlencoded stockApi=http://stock.example:8080/product/stock/check

核心信息在请求体里:stockApi参数的值,就是服务端即将去访问的URL。浏览器端的JavaScript把这个值硬编码进了请求里,发给服务端。服务端看到这个参数就直接去请求,完全没有任何过滤。这行代码就是整个漏洞的根源,而stockApi这个参数名也暗示了它的用途:stock API,库存接口地址。

找到了这个参数,实验就成功了一大半。现在把请求转发到Repeater(重放器)里,准备开始修改参数值。

3.2 第一次利用:访问本地管理后台

把请求体中的stockApi参数值从正常的外部库存地址改成指向本机。这里有两个常用的地址:http://127.0.0.1和http://localhost。在这个实验里用http://127.0.0.1/admin是一个很自然的尝试:

POST /product/stock HTTP/1.1 Host: shop.example Cookie: session=xxxxxxxxx Content-Type: application/x-www-form-urlencoded stockApi=http://127.0.0.1/admin

点击Send按钮,观察响应。如果漏洞存在,你会在响应中看到管理后台的HTML内容。关键信息往往不是完整的样式页面,而是页面里那些接口地址。管理后台的长相大概会包含:一个“Admin panel”的标题,一个删除用户的功能区域,以及可以直接点击或请求的用户管理接口链接,比如/admin/delete?username=carlos。

为什么http://127.0.0.1/admin能访问成功?因为购物网站的服务端和本地管理后台跑在同一台机器上。服务端代码发起请求时,网络栈会直接访问本机端口。防火墙规则通常允许本机回环地址(127.0.0.1)的自由通信,所以这个请求畅通无阻。

这里有一点非常值得注意:响应中返回的管理后台页面内容,通常是完整HTML,里面藏着很多接口信息。在做SSRF测试时,不要只看HTTP状态码是不是200,一定要把响应体内容翻一遍,尤其是那些带/admin前缀的链接,它们就是下一步攻击的目标。

3.3 提取接口信息并构造删除请求

在管理后台的响应HTML里,你会看到一个值得记录的链接。它的形式大概是这样:

<a href="/admin/delete?username=carlos">Delete</a>

这意味着一件事:本地管理后台提供了一个删除用户的接口,参数是username,值是carlos。正常情况下,这个接口只有管理员在管理后台页面里点击才能触发。但现在我们已经拿到了完整的URL路径。

接下来要做的事情就顺理成章了:让购物网站的服务端替我们去请求这个删除接口。还是刚才那个stockApi参数,把值改成完整地址:

POST /product/stock HTTP/1.1 Host: shop.example Cookie: session=xxxxxxxxx Content-Type: application/x-www-form-urlencoded stockApi=http://127.0.0.1/admin/delete?username=carlos

等等,这里有一个细节需要处理:stockApi参数的值里包含问号和等号,分别是对参数做URL编码,这是很关键也很容易出现格式问题的点。

正确做法是:在Burp Suite的Repeater里,直接把stockApi的值设置为:

http://127.0.0.1/admin/delete?username=carlos

这样整个请求体看起来是:

POST /product/stock HTTP/1.1 Host: shop.example Cookie: session=xxxxxxxxx Content-Type: application/x-www-form-urlencoded stockApi=http://127.0.0.1/admin/delete?username=carlos

实操过几次后会发现,Burp Suite的Repeater在发送时会对请求体里stockApi=...后面的值原样发送。如果遇到格式错误,可以在原始值上做一次URL编码再放进去,但大部分情况下不需要。

点击Send,这次不需要关注响应的内容,重点看结果。服务端会去请求http://127.0.0.1/admin/delete?username=carlos,管理后台执行删除操作。删除成功后,购物网站的页面会显示carlos用户已不存在,实验目标达成。

3.4 实验成功的关键判断标准

怎么确认实验是不是真的成功了?有几个判断标准可以对照:

  • 第二次请求发送后,服务端返回的响应里没有报错信息,说明服务端成功请求到了本地接口。
  • 重新访问管理后台页面,看到用户列表里carlos已经消失。
  • 在实验平台的界面上,出现了“实验已完成”或“恭喜”的提示。

另外要注意,整个利用过程只是修改了stockApi这一个参数的值。没有绕过身份认证,没有用任何暴力破解手段,服务端的cookie和会话完全正常。这说明漏洞就单纯出在“服务端请求URL”这个环节缺少校验。

这个实验的完整攻击链可以总结成一张顺序表,思考逻辑非常清晰:

步骤操作目的
1定位 stockApi 参数找到服务端可控的URL入口
2改为 http://127.0.0.1/admin探测本地管理后台是否可访问
3读取响应中的 /admin/delete 链接提取管理接口信息
4改为完整删除接口地址利用服务端身份执行敏感操作

4. SSRF防御思路与安全建设

实验做完了,但如果只是停留在“打通实验就完事”的层面,价值就少了一半。真正重要的是把这个漏洞的成因吃透,并且知道在实际开发中怎么防。

4.1 针对参数输入的校验

最直接的防御手段,就是在代码层面对用户传入的URL做校验。这个校验必须是服务端代码的逻辑,而不是前端。因为前端逻辑可以被绕过。

校验可以从几个维度展开:

  • 协议白名单:只允许http://和https://,禁止file://、gopher://、dict://等危险协议。
  • 域名白名单:如果业务场景只需要请求固定的域名,直接维护一个域名白名单,不在白名单里的地址一律拒绝。
  • 端口限制:限制只能访问特定的端口,比如80和443,避免探测内网任意端口。
  • IP地址校验:把目标域名解析成IP后,判断IP是否属于内网地址段。如果属于内网地址段就直接拒绝。

这里有一个很容易被忽视的问题:DNS解析结果会和实际请求时不一致。攻击者可以利用这个时间差绕过IP校验。防御做法是,自己完成URL解析,强行走一遍规定流程,而不是用系统库函数直接用URL发起请求。

4.2 对服务端出口请求的管控

第二层防御思路,是从服务端网络出口入手,把服务端发出的所有外部请求都收敛到一个统一的代理服务中。

这个代理服务可以做成这样:

  • 所有业务代码要发起远程HTTP请求,必须通过这个代理。
  • 代理层统一管控目标地址和协议。
  • 代理层记录所有请求日志,便于审计和分析。
  • 代理层把响应结果再返回给业务代码。

这种做法的好处是,即便某个业务模块的代码疏漏了校验,攻击者也没办法直接把请求打到内网资源,因为出口代理已经把所有内网地址都屏蔽了。从运维角度说,对所有出站流量做统一限制也是比较成熟的方案。

4.3 网络架构层面的隔离

代码层面的防御始终存在被绕过的可能,所以网络层面的隔离才是兜底方案。

核心思路是:让服务端无法访问到不该访问的东西。

  • 内网服务不要绑定在通配地址上,尽量只绑定本地回环地址或内网专用地址。
  • 管理后台不要和业务服务放在同一台机器上,至少做网络隔离。
  • 管理后台的认证不要只依赖IP限制,必须要求强认证。
  • 敏感操作接口要有独立的权限控制,不能仅凭一个URL就能执行。

如果管理后台本身就没有暴露给本机之外的服务,即便SSRF发生了,攻击者也拿不到什么有价值的东西。所谓纵深防御,就是即便某一层被攻破,其他层还能拦住攻击路径。

4.4 安全建设和检测

开发侧的防御到位了,安全侧还需要持续投入。这里说的不是简单指出漏洞存在,而是能解释清楚漏洞可能造成的后果,才能让人信服。

实际操作中,我发现一个很实用的方法:在代码评审时,主动去找“服务端发请求”的函数调用点,然后用SSRF的思路去审查上游数据是否可控。这种排查方式比随机找漏洞效率高得多。

对于已有系统,可以在安全测试中专门设计针对SSRF的测试用例,字段包括:

  • 可控URL参数是否区分内部地址。
  • 是否可以通过重定向绕过校验。
  • 是否能从响应内容中读到内部接口信息。
  • 是否能结合DNS重绑定技术绕过IP白名单。

5. 常见问题与排查技巧实录

做实验和实际排查问题都会遇到很多细碎的坎。这里整理出几类高频问题,都是我实际操作中验证过的。

5.1 Burp Suite 抓不到商品库存请求

如果开了拦截却抓不到请求,先检查几个地方:

问题排查步骤
浏览器代理没设置确认浏览器代理指向 127.0.0.1:8080
HTTP/HTTPS 过滤设置检查 Proxy 设置里的 Intercept 是否开启
证书未安装访问 http://burp 下载证书并导入浏览器信任列表
请求被缓存使用隐私窗口请求,避免浏览器缓存

首选方案是把全部流量代理到Burp,再在最左侧标签里把目标站点筛选单独打开,这样能有效减少干扰。

5.2 修改参数后响应返回 403 或空白页面

出现这种情况,绝大多数时候是URL格式不对。我踩过的一个坑是:把stockApi改成http://127.0.0.1/admin/delete?username=carlos后,返回的响应是空白,后来排查发现是Burp把?后面的内容当成了另一个参数,导致请求体格式错乱。

解决办法:

  • 确保整个stockApi的值放在一个完整参数位里。
  • 在Burp的Repeater界面里,检查请求体的参数分割,确认stockApi=之后只有一个值。
  • 如果还不行,把整个值做URL编码再发送。

5.3 用 localhost 和 127.0.0.1 的区别

初做实验时可能会困惑到底用哪个。在大部分系统里,localhost会优先被解析成IPv6地址::1,而127.0.0.1一定是IPv4的回环地址。如果服务端只监听了IPv4的端口,用localhost可能会连接失败,而127.0.0.1则稳得多。

实验教程里经常用localhost写法,但对靶场来说,是一回事。实际工作中如果是排查SSRF,用127.0.0.1往往更稳妥一点。

5.4 实验重置后怎么快速恢复

实验平台如果支持重置,重来一遍的成本其实很低。但我发现一个提高效率的技巧:把第一次攻击成功后获取到的删除接口地址,直接保存在本地笔记里。第二次重置后,不需要再走一遍“管理后台页面提取链接”的步骤,直接构造删除请求即可完成。

做安全实验要养成记录的习惯:每次请求的参数、响应里提取到的有价值信息、踩过的坑,都值得留档。这些记录在写漏洞报告时能发挥很大作用。

6. 实验做完之后的一点心得

这个实验虽然叫“简单”,但它几乎把SSRF利用中最重要的逻辑浓缩进来了:可控参数、服务端请求、本地接口、权限绕过。做完这个实验之后,我在实际代码评审里再看到URL参数拼进服务端请求的写法,都会忍不住多问一句:这个参数从哪来?服务端会用这个URL去请求什么?响应会回流给用户吗?如果这三个问题的答案都默认,那就可以确认是一个非常值得警惕的漏洞形态。

另外,在实际安全建设里,我发现很多团队对SSRF的重视程度是明显不够的。相比SQL注入和XSS,SSRF的利用链往往需要多走几步,危害不容易被直观感知。但恰恰因为这种隐蔽性,它才更容易在真实攻击中被用作内网突破口。把这个基础实验做透,其实是建立对这类漏洞敏感度最低成本的一条路。

最后再分享一个小技巧:做这类实验时,每次修改参数之前,先记录下原始请求的完整内容。这不仅是习惯问题,更是纪律问题。你永远不知道哪一步把流量改坏了,回头对照原始数据是排查问题最可靠的办法。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进