资讯动态

IDEA+Gradle调试Ofbiz:CVE-2020-9496漏洞定位

发布时间:2026/10/20 5:53:02 来源:尧图企业网站定制
“手把手教你用IntelliJ IDEA和Gradle调试Apache Ofbiz 16.11.01附CVE-2020-9496漏洞定位技巧”——这标题看着长其实就是把三件事拧在一起怎么把一个老掉牙的Gradle工程在IDEA里跑起来怎么断点调试它以及怎么顺着CVE-2020-9496这条真实漏洞线索反向摸清代码路径。我去年折腾这玩意断断续续花了一周时间踩了不少坑今天把整个过程梳理出来希望能帮你少走弯路。这篇内容适合两类人一类是给Ofbiz做二次开发的每天面对一堆XML配置和自研框架想用IDEA调试却不知道从哪下手另一类是做Java安全研究的想拿Ofbiz这个典型老项目练手反序列化漏洞定位。无论你是哪类建议先把环境装好跟着我的步骤走一遍后面分析漏洞链路时会轻松很多。1. 调试前必做的三件事1.1 版本选型绝对不能含糊Apache Ofbiz 16.11.01 是2017年左右的版本整套代码是基于 Java 8 写的。虽然Java 11只是多了模块化限制但实测下来用JDK 8跑最稳JDK 11在加载某些Bean时会报模块访问错误IDEA内部触发反射的时候也容易出权限问题。所以我建议直接用 JDK 8比如8u202版本不要想着用新JDK在这类老框架上“新”就是给自己找麻烦。Gradle方面其实不用太纠结因为项目自带gradlew wrapper版本是3.5或4.x。IDEA 2020之后的版本对Gradle 3.5的同步支持不太好会直接提示最低版本4.x之类的我的处理办法是在IDEA的 Settings-Gradle 里面选 “Use Gradle from: gradle-wrapper.properties”让IDEA跟着wrapper走。如果sync还是报错就去 gradle/wrapper/gradle-wrapper.properties 里把 distributionUrl 改成IDEA支持的版本比如4.10.3改完后重新加载成功概率很高。这里有个很重要的坑IDEA版本如果太新比如2023.1以上Gradle 4.10.3这个老版本依然可能适配有问题。这时候有个更稳的思路——直接把项目作为普通工程导入不走Gradle然后把lib目录下需要的依赖JAR全加进去。这个方案适合你只想分析代码、不依赖Gradle task跑构建的情况实测下来用来看漏洞链路完全够用。1.2 导入IDEA的过程和坑导入时不要选Ofbiz的根目录而是选中根目录下的 build.gradle 文件让IDEA识别为Gradle工程。如果你选根目录IDEA会认为整个目录是普通文件夹不会触发Gradle sync后面所有依赖都糊了。导入完成后等Gradle sync跑完这个过程会挂几分钟甚至更久主要取决于你的网络能不能正常访问Maven中央仓库。如果一直卡着去根目录的 build.gradle 里确认仓库配置——官方仓库默认是 mavenCentral 和 apache snapshot所有依赖都会下载到 ~/.gradle/caches 目录。国内网络环境下直接在 settings.gradle 或者全局的 init.gradle 里配好镜像仓库能把sync时间从半小时压缩到五分钟左右这个优化非常值得做。有一点不需要提前配置数据目录。首次用 gradlew ofbiz 启动时会在项目根目录自动创建 runtime 下的数据库文件和日志IDEA导入后只需要确认两件事Project SDK是1.8Java compiler的target也是1.8其他默认即可。1.3 两种调试启动方式的取舍官方文档推荐 gradlew ofbiz 直接启动但我个人更推荐第二种直接把主类跑在IDEA里断点全部生效调试体验最好。方式一命令行跑 gradlew ofbiz然后配置远程调试端口。需要在gradle里加上运行参数比如-Dofbiz.debug.port5005 -Djava.rmi.server.hostname127.0.0.1然后IDEA里新建 Remote JVM Debug 配置attach 到 127.0.0.1:5005。好处是配套的Gradle任务如 loadDefault、cleanAll都很方便坏处是远程attach模式下第一次断点命中会慢半拍而且改Gradle配置后要重启整个进程有点笨重。方式二直接调试 org.apache.ofbiz.base.start.Start 的main方法。运行配置的VM options里写-Dofbiz.home/你的路径/apache-ofbiz-16.11.01Working directory设成项目根目录然后直接运行控制台就会输出初始化日志浏览器访问127.0.0.1:8443就能进管理后台。这种方式是本地调试断点命中率和响应速度都是最好的唯一的坑是classpath里必须包含所有运行时依赖而IDEA的Gradle sync已经帮你算出来了基本不会缺。两个方式各有场景如果你只是要追踪漏洞触发链路方式二最简单如果你还要跑Ofbiz的初始化数据或租户任务方式一更顺手。我平时会把两个配置都存好按需切换。2. 先把请求链路彻底捋清楚2.1 找到xmlrpc入口CVE-2020-9496的入口是 /webtools/control/xmlrpc这个端点是在web应用的 web.xml 里配置的。打开Ofbiz源码后不要急着全项目搜索CVE而是先打开 web.xml找到 servlet-mapping 定位到xmlrpc再顺着映射找到对应的servlet类通常是 org.apache.ofbiz.extensions.xmlrpc.XmlRpcDispatcher。这个类实现了 doPost / doGet内部接收请求后解析XML-RPC消息。这里有个容易忽略的知识点Ofbiz自身封装了一套认证机制但webtools这个应用里的xmlrpc端点对未认证用户是开放的这也是漏洞能被直接利用的前提。调试时直接用脚本发POST不需要走任何登录流程省事很多。我第一次调试时在这个入口花费了不少时间因为Ofbiz的web应用结构比较特殊它不是标准的Spring MVC而是自研的ControlServlet框架各种URL映射分散在多个配置文件里。如果你也找不到入口最快的办法是全局搜索“xmlrpc”字符串先定位到URL映射配置再倒推到Java类。2.2 核心转换类ObjectType在Ofbiz的内部机制里XML-RPC的参数会被映射成一组Java对象。为了让字符串能变成任意类型Ofbiz使用了 org.apache.ofbiz.base.util.ObjectType 这个工具类。它里面有个 fromString 方法会根据类名去创建对象。攻击者构造的payload里类名是可控的这就等于打开了一个“字符串到任意类实例”的万能通道非常危险。从调试角度来说fromString 和它的兄弟方法 resolve 是我最喜欢打断点的地方。原因很简单一次正常业务请求不会走到“把字符串转换成任意类”这个分支里但只要出现了反序列化攻击就一定会从这个方法经过。看着这个断点命中再顺着调用栈往上翻入口就非常清晰。提醒一下源码里这个方法内部逻辑稍微有点绕因为它要处理基本类型、数组、集合、Map、枚举等一堆情况。你不需要逐行理解只需要知道当类名以特定前缀出现时它会走反射创建对象的逻辑这正是攻击链的关键跳板。2.3 反序列化到底发生在哪一步当攻击者提交的XML-RPC请求中包含base64编码的Java序列化字节流时XmlRpcDispatcher在处理参数时会用 java.io.ObjectInputStream 的 readObject() 方法恢复对象。这个过程在Ofbiz内部被封装得比较隐蔽你直接全局搜索ObjectInputStream会发现很多正常的使用不太好区分。这里我给一个实用技巧直接在JDK的 ObjectInputStream.readObject() 方法里打断点。不管Ofbiz怎么封装最终反序列化一定会走到这个方法。这是调试反序列化链路的“总闸门”。至于哪条调用链是攻击者可控的看调用栈就能认出来——正常业务请求的反序列化栈很浅层次分明攻击请求的栈往往从 XmlRpcDispatcher 一路反射调用出来栈帧数量和复杂程度明显异常。3. CVE-2020-9496 的定位实战3.1 搭建最小复现环境复现环境只需要三样东西Ofbiz 16.11.01源码、JDK 8、一套IDE。不需要登录不需要初始化业务数据因为默认启动流程会创建内嵌的Derby数据库。为了不影响调试建议用一台独立的虚拟机或容器跑靶机把8443端口映射出来本地IDEA通过远程调试连接到容器内这样断点、日志、抓包都不会互相干扰。启动成功后用requests或者Burp构造一个POST请求Content-Type一定要是 text/xmlbody是标准XML-RPC methodCall 结构。攻击者真正控制的地方是param里的参数值。你可以先手工拼一个简单的字符串参数确认请求能被正常处理并返回响应说明环境通了。这时我们在 XmlRpcDispatcher 入口处打上断点用这个正常请求验证断点能命中。3.2 payload构造思路与落点CVE-2020-9496利用的是Java原生反序列化理论上标准Gadget都可以尝试。最常被提到的链子是CommonsBeanutils1因为Ofbiz自带的依赖里就有 commons-beanutils 1.8.3。用ysoserial生成payload的命令类似这样java -jar ysoserial.jar CommonsBeanutils1 touch /tmp/pwned payload.bin然后把生成的二进制文件做base64编码嵌入XML-RPC请求的param值里。这一步要非常严肃地说如果你做的是安全研究务必在隔离环境、自己搭的靶机上测试绝对不要在生产或未授权环境做类似操作。这是底线问题。我自己的体验是第一次直接打过去断点会瞬间停在 readObject() 方法然后一路飙到栈底部整个过程非常直观。你会看到变量面板里ObjectInputStream正在解析的类名如果仔细看能发现它正在读取的是CommonsBeanutils1链里的类。3.3 断点命中后怎么看栈以IDEA为例断点停在 ObjectInputStream.readObject() 后打开Debugger窗口的 Frames 面板。正常业务请求的栈会看到一串tomcat-http、xmlrpc、framework框架代码攻击请求则有明显不同的特征。重点观察两个区域上半部分谁发起了这次反序列化。看方法名里带 XmlRpcDispatcher 的那几帧顺着往下走。下半部分readObject 内部最终构造了哪个类。打开variables面板查看 ObjectInputStream 正在读取的类描述符或者直接看栈中是否出现 org.apache.ofbiz.base.util.ObjectType.resolve 或 fromString 相关帧。如果你看到栈里有 ObjectType.resolve 或 fromString 的踪迹那么CVE-2020-9496的核心脉络已经被你踩实了继续往下走分析到类的构造函数被执行时的参数来源漏洞定位就完成了。我习惯把这套栈记录保存下来截图或复制到笔记里。因为后续写分析报告或者做二次利用验证时这个栈就是最可靠的证据比你口述“这里能反序列化”有说服力得多。3.4 条件断点与表达式求值的实战技巧反序列化调试时很多类都会经过readObject方法每次遇到都停下会把耐心磨光。我给一个条件断点的写法在断点处右键设置Conditionthis.getClass().getName().contains(BeanComparator)或者更广泛一点this.getClass().getName().startsWith(org.apache.commons)这样IDEA只在解析出 commons 包下的类时才暂停其他常规反序列化直接跳过。这个技巧在处理一次会话里几十上百个反序列化调用时特别管用能让你从噪声里快速锁定目标。另外当断点命中后可以用Evaluate Expression表达式求值功能查看当前 ObjectInputStream 的 filter 是否为空。CVE-2020-9496能成功本质上就是因为目标版本没有可靠的ObjectInputFilter或反序列化黑名单。你可以在表达式框里执行((java.io.ObjectInputStream)this).getObjectInputFilter()如果返回null说明目标版本没有开启过滤这就是漏洞存在的直接证据之一。这个操作比单纯看代码更直观很适合在写漏洞分析时给同事或客户展示。4. 调试过程中我踩过的坑和排查清单4.1 端口占用和数据库文件锁8443、8080同时被占启动时日志会报Address already in use。此时先别急着杀进程去 runtime/logs/ofbiz.log 看看到底是不是权限问题。如果你之前用sudo跑过一次Ofbizruntime目录里的Derby数据文件会被root持有普通用户再启动就会报文件锁相关的错误。解决办法简单粗暴把runtime目录删掉重新跑反正是本地数据无伤大雅。4.2 Gradle sync卡死或下载失败依赖源问题是重灾区。我建议在 ~/.gradle/init.gradle 里统一配置镜像仓库把mavenCentral和google都镜像到国内源。其次是Gradle版本过老IDEA新版本直接不兼容这时可以退回“普通工程导入”的模式绕过。还有一个小技巧把IDEA的 Build and run using 从Gradle改成IntelliJ IDEA减少Gradle反复接管启动参数导致的调试混乱。4.3 断点永远不命中的排查思路先确认当前运行的进程是Debug模式IDEA右上角显示的是小虫子图标而不是运行箭头。其次要警惕类加载器不一致的问题。Ofbiz里有多个ClassLoader如果目标类是被容器加载器加载的你在IDEA里下的断点可能因为ClassLoader不同而无法命中IDEA会提示“Class not found”。解决办法是把断点打在接口方法上或者干脆把检查点放在JDK类上比如ObjectInputStream至少能保证命中一次再从那次命中向外扩展断点。4.4 日志级别调整和抓包对比调整 runtime/conf/log4j.xml 里 org.apache.ofbiz 的日志级别到TRACE可以看到请求从入口到出口的完整调用链。日志里如果出现WARN或ERROR再把对应代码拉起来回看效率比瞎猜高得多。再补充一个终极大招抓包。先用抓包工具把正常请求完整录下来再把攻击请求也录下来用文本对比工具看XML结构差异。这一步能直观告诉你请求里多了哪个字段、字段值变了什么从而快速判断代码分支会走向哪里。调试这类老框架反复对比正常请求和异常请求的差异永远是最笨但最可靠的方法。最后分享一个小习惯这个项目调试完整套流程后我最深的体会是固定几个关键断点不动入口servlet、ObjectType、ObjectInputStream.readObject把请求一点点改异常观察异常被捕获的位置很快就能摸清一套老框架的脾气。如果一次断点没停下不要怀疑人生先检查是不是类加载器不一致再检查是不是IDEA把当前工程识别成了普通目录。多试几次你也能像我一样花一个周末把一个老漏洞的来龙去脉摸得明明白白。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑