1. 项目概述:为什么我们要深入调试Ofbiz?
如果你是一名Java开发者,或者对开源企业级应用框架感兴趣,那么Apache Ofbiz这个名字你一定不陌生。它是一个功能极其庞大的开源ERP(企业资源计划)和电子商务套件,涵盖了从产品目录、订单管理、库存、会计到人力资源等几乎所有企业运营模块。正因为其功能复杂、历史悠久,代码库也相当庞大,动辄几十万行代码。对于开发者来说,无论是想学习其架构设计、进行二次开发,还是进行安全研究(比如分析历史漏洞),直接阅读源码往往让人望而生畏,效率低下。
这时,一个强大的集成开发环境(IDE)和顺畅的调试环境就成了刚需。IntelliJ IDEA作为目前Java生态中最受欢迎的IDE之一,以其智能的代码提示、强大的重构能力和优秀的调试器而闻名。而Gradle则是现代Java项目构建的事实标准之一,相比传统的Ant和Maven,它脚本更灵活,依赖管理更直观。将这三者——IDEA、Gradle和Ofbiz——结合起来,搭建一个本地可调试的开发环境,就相当于为你配备了一台高倍显微镜和一套精密的手术刀,让你可以深入到Ofbiz这个庞然大物的内部,清晰地观察其运行脉络,甚至定位到某个特定细胞(漏洞)的位置。
本次我们以Apache Ofbiz 16.11.01这个特定版本为例。选择这个版本并非偶然,一方面,它是一个相对稳定且资料较多的历史版本;另一方面,它也是著名漏洞CVE-2020-9496(一个XML外部实体注入漏洞)影响的版本。通过搭建调试环境并复现这个漏洞,我们不仅能掌握通用的Ofbiz项目调试方法,更能获得一套“以漏洞为线索,逆向追踪代码执行流”的实战分析技巧。这对于安全研究人员、代码审计人员以及任何希望深入理解大型Java应用内部机制的开发者来说,价值巨大。
2. 环境准备与项目导入
工欲善其事,必先利其器。在开始调试之前,我们需要确保本地环境一切就绪。这个过程看似繁琐,但每一步都关系到后续调试的顺畅度,值得耐心对待。
2.1 基础软件安装与配置
首先,你需要准备以下基础软件,并建议使用指定或更高版本以保证兼容性:
-
Java Development Kit (JDK) :Ofbiz 16.11.01官方推荐使用JDK 8。虽然更高版本的JDK(如11)也可能运行,但为了避免因版本差异导致的不可预知问题,强烈建议安装 JDK 8 。你可以从Oracle官网或AdoptOpenJDK等渠道下载。安装后,请确保正确配置
JAVA_HOME环境变量,并将%JAVA_HOME%\bin(Windows)或$JAVA_HOME/bin(Linux/macOS)添加到系统的PATH中。在命令行输入java -version和javac -version验证安装。 -
IntelliJ IDEA :推荐使用 IntelliJ IDEA Ultimate 版本,因为它对Java EE、Web开发等企业级功能支持更完善,社区版虽然也能用,但可能会缺少一些便利的插件或功能。确保安装的IDEA版本相对较新(如2021.3及以上),以更好地支持Gradle。
-
Gradle :虽然IDEA内置了Gradle Wrapper,但为了在命令行也能方便地执行构建任务,建议单独安装Gradle。Ofbiz 16.11.01项目根目录下的
gradle/wrapper/gradle-wrapper.properties文件通常指定了所需的Gradle版本(例如distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip)。你可以下载对应版本的Gradle二进制包,解压后设置GRADLE_HOME环境变量并添加到PATH。同样,使用gradle -v命令验证。
注意 :在Windows系统上,路径中避免使用中文和空格,这能减少很多潜在的诡异问题。JDK和Gradle的安装路径如
C:\Dev\Java\jdk1.8.0_301和C:\Dev\gradle-4.10.2是比较好的选择。
2.2 获取并准备Ofbiz源代码
Apache Ofbiz的源代码托管在Apache的SVN仓库和Git镜像中。对于16.11.01版本,最直接的方式是从Apache官网的发布页面下载源代码压缩包。
-
下载源码
:访问Apache Ofbiz官网的发布目录,找到16.11.01版本的
source压缩包(通常是ofbiz-16.11.01.zip或.tar.gz)并下载到本地。 -
解压源码
:将压缩包解压到一个合适的目录,例如
D:\Projects\ofbiz-16.11.01。这个目录将作为我们的项目根目录。 - (可选)初始化数据库配置 :Ofbiz默认使用内嵌的Derby数据库。如果你想使用MySQL或PostgreSQL,需要提前修改配置文件。不过对于调试和漏洞分析,使用默认的Derby足以满足需求,可以跳过此步。
2.3 使用IntelliJ IDEA导入Gradle项目
这是最关键的一步,正确的导入方式能避免后续大量的配置麻烦。
-
启动IDEA,选择“Open”
:不要选择“New Project”。在文件选择器中,导航到你解压的Ofbiz项目根目录(即包含
build.gradle、gradlew文件的目录),选中它,点击“OK”。 - 信任项目 :IDEA可能会提示这是一个Gradle项目,询问是否信任。选择信任。
-
Gradle导入设置
:IDEA会自动检测到
build.gradle文件并弹出导入设置窗口。这里有几个关键选项需要留意:-
Use auto-import
:勾选此项。这样当
build.gradle或settings.gradle文件发生变化时,IDEA会自动同步项目。 -
Use Gradle from
:选择
‘gradle-wrapper.properties’ file
。这是最推荐的方式,IDEA会使用项目自带的Gradle Wrapper(即
gradlew或gradlew.bat)来构建项目,确保构建环境与项目要求完全一致,避免了本地安装的Gradle版本不兼容的问题。 - Gradle JVM :选择你安装的JDK 8。确保这里和后续项目SDK的JDK版本一致。
-
Use auto-import
:勾选此项。这样当
- 等待项目构建 :点击“OK”后,IDEA会开始导入项目并下载所有依赖。这是一个漫长的过程,因为Ofbiz的依赖非常多,首次导入可能需要十几分钟到半小时,取决于你的网络速度。请耐心等待底部的进度条完成。
-
解决可能的构建警告
:构建完成后,你可能会在“Build”工具窗口看到一些警告,例如“Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0”。
这是完全正常的
,因为Ofbiz 16.11.01使用的Gradle版本(如4.10)较老,一些特性在新版Gradle中已被废弃。只要构建最终显示
BUILD SUCCESSFUL,就可以忽略这些警告,不影响我们的调试。
实操心得 :在导入过程中,如果遇到依赖下载失败(特别是从Maven中央仓库),可以尝试配置Gradle使用国内镜像。在用户主目录下的
.gradle文件夹中创建init.gradle文件,添加阿里云等镜像仓库配置,可以极大加速下载过程。另外,确保你的网络环境可以正常访问Maven中央仓库。
3. 项目结构与运行配置解析
成功导入项目后,我们先不急着调试,花点时间理解一下Ofbiz的项目结构和如何配置运行,这对后续的调试定位至关重要。
3.1 Ofbiz项目目录结构浅析
在IDEA的项目工具窗口中,你会看到一个非常庞大的目录树。对于调试而言,我们主要关注以下几个核心部分:
-
/framework:这是Ofbiz的核心框架层。包含了大量的基础服务、实体引擎、服务引擎、工作流引擎、安全认证等通用组件。如果你想研究Ofbiz的底层机制,比如实体CRUD如何运作、服务如何调度,这里就是宝库。 -
/applications:这是业务应用层。我们常见的功能模块如订单管理(order)、产品目录(catalog)、会计(accounting)等都位于此。每个子目录通常对应一个相对独立的功能组件。 -
/specialpurpose:存放一些特殊用途的组件。 -
/themes:前端主题和页面模板。 -
build.gradle:项目根构建脚本,定义了全局的插件、依赖和任务。 -
/gradle:包含Gradle Wrapper的文件。 -
ofbiz.jar: 注意 ,在源码项目中你不会直接看到这个文件,它是在构建过程中生成的。但很多启动脚本(如gradlew ofbiz)最终会调用这个jar包。
理解这个结构有助于你在调试时快速定位代码。例如,当你看到一个URL路径包含
/ordermgr
,你就能很快想到对应的代码可能在
/applications/order
目录下。
3.2 配置IntelliJ IDEA运行/调试配置
要让Ofbiz在IDEA中跑起来并支持调试,我们需要创建一个“Gradle”运行配置。
- 打开运行配置窗口 :点击IDEA右上角运行配置下拉框,选择“Edit Configurations...”。
-
添加Gradle配置
:点击左上角
+号,选择“Gradle”。 -
配置关键参数
:
- Name :可以命名为“Ofbiz Run”或“Ofbiz Debug”。
- Gradle project :选择你导入的Ofbiz项目。
-
Tasks
:这是核心。输入
ofbiz。这是Ofbiz的Gradle构建脚本中定义的一个任务,它会执行一系列操作最终启动Ofbiz服务。 -
Arguments
:可以留空,或者根据需要添加。例如,
-ofbiz参数(虽然ofbiz任务本身已包含)。更常见的可能是传递系统属性,例如-Djava.awt.headless=true用于无头模式运行。
-
配置调试参数(关键)
:要让这个配置支持调试,我们需要让Gradle任务在调试模式下启动JVM。
-
在运行配置窗口的“Before launch”区域,点击
+,选择“Run Gradle task”。 -
在弹出的窗口中,同样选择你的Ofbiz项目,在Tasks里输入
--debug-jvm。注意,这里有两个横线。这个参数会告诉Gradle,在执行后续任务(即ofbiz)时,以调试模式启动JVM。 - 确保这个“Run Gradle task”的顺序在“Gradle task ‘ofbiz’” 之前 。
-
在运行配置窗口的“Before launch”区域,点击
-
最终配置顺序
:“Before launch”中应该有两个步骤:第一步是“Run Gradle task”执行
--debug-jvm,第二步是“Gradle task”执行ofbiz。
现在,当你选择这个配置并点击“Debug”按钮(绿色的虫子图标)时,IDEA会先触发调试模式,然后启动Ofbiz。你会在“Run”工具窗口中看到Gradle开始构建并最终输出类似“Listening for transport dt_socket at address: 5005”的信息,这表明Ofbiz的JVM已经以调试模式启动,并在5005端口等待调试器连接。IDEA会自动连接上去。
注意事项 :首次以调试模式启动可能会比较慢,因为Gradle需要执行完整的构建。启动成功后,你可以通过浏览器访问
http://localhost:8080/来打开Ofbiz的首页。默认的管理员用户名密码是admin/ofbiz。
4. 核心调试技巧与漏洞定位实战
环境跑通后,我们就拥有了一个“活的”、可交互的Ofbiz实例。接下来,我们将结合CVE-2020-9496这个具体的漏洞,来演示如何利用IDEA的调试功能进行代码追踪和漏洞分析。这不仅是一个漏洞复现过程,更是一套通用的安全研究/代码理解方法论。
4.1 CVE-2020-9496漏洞背景与原理简述
在开始调试前,有必要先理解我们要找什么。CVE-2020-9496是一个存在于Apache Ofbiz XML RPC接口中的XML外部实体(XXE)注入漏洞。
- 什么是XXE? 简单来说,当应用程序解析来自用户的可控XML数据时,如果没有禁用外部实体引用,攻击者就可以在XML文件中构造特殊的实体,导致应用程序读取服务器上的任意文件、发起内部网络请求,甚至可能造成拒绝服务。
- 漏洞入口 :在Ofbiz中,存在一个基于XML-RPC的接口。当这个接口接收到XML请求时,会调用相应的解析器进行处理。
- 漏洞本质 :在解析过程中,使用的XML解析器(如Apache Xerces)的配置不够安全,没有禁用DTD(文档类型定义)或外部实体加载,从而导致XXE攻击成为可能。
我们的调试目标就是:找到处理XML-RPC请求的代码点,跟踪XML解析的流程,并定位到那个不安全的解析器配置位置。
4.2 利用调试器进行动态追踪
静态阅读代码如同大海捞针,而调试器给了我们“动态跟踪执行流”的能力。
-
设置断点
:我们首先需要猜测漏洞可能发生的范围。根据漏洞描述(XML RPC),我们可以从Ofbiz中处理RPC请求的组件入手。在IDEA中,使用快捷键
Ctrl+Shift+F(Windows/Linux)或Cmd+Shift+F(macOS)进行全局搜索。尝试搜索关键词如 “xmlrpc”、“XmlRpc”、“RPCService” 等。-
例如,你可能会在
/framework/webtools下的某个类中发现XmlRpcService相关的代码。在疑似处理请求入口的方法上(比如service方法)单击左侧行号栏,设置一个断点(红色圆点)。
-
例如,你可能会在
-
触发请求
:我们需要一个能触发漏洞的请求。网上可以找到CVE-2020-9496的公开漏洞利用(PoC)代码或数据包。通常是一个构造了恶意DTD的HTTP POST请求,发送到Ofbiz的XML-RPC端点(例如
/webtools/control/xmlrpc)。你可以使用Burp Suite、Postman甚至简单的curl命令来发送这个请求。-
请求示例(概念性)
:
POST /webtools/control/xmlrpc HTTP/1.1 Host: localhost:8080 Content-Type: text/xml <?xml version="1.0"?> <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <methodCall> <methodName>&xxe;</methodName> </methodCall>
-
请求示例(概念性)
:
-
开始调试并跟踪
:
- 确保你的Ofbiz正在以调试模式运行(上一节配置的)。
- 发送上述恶意请求。
- 此时,IDEA的调试器会立即在之前设置的断点处暂停。整个Ofbiz应用的执行线程在此刻“冻结”,等待你的指令。
- 在IDEA的“Debug”工具窗口,你可以看到完整的调用栈(Frames)。这个调用栈展示了当前执行点是如何被一层层调用过来的,这是 逆向追踪 的黄金路径。你可以点击调用栈中上一级的方法,跳转到调用当前方法的地方,从而理解整个调用链。
-
使用调试器的步进功能:
- Step Over (F8) :执行当前行,如果当前行是一个方法调用,则不会进入该方法内部,直接得到其结果。
-
Step Into (F7)
:进入当前行所调用的方法内部。这是我们
深入追踪
的关键。当执行到像
Document doc = parser.parse(inputStream);这样的XML解析语句时,果断按F7跟进去。 - Step Out (Shift+F8) :快速执行完当前方法,返回到调用它的地方。
- Run to Cursor (Alt+F9) :直接运行到光标所在的行,用于跳过不感兴趣的代码段。
4.3 关键代码定位与漏洞点分析
通过一步步跟踪(Step Into),你会逐渐深入到XML解析的核心库。在Ofbiz 16.11.01中,最终很可能会跟踪到Apache Xerces或类似XML解析器的
parse
方法。
-
定位解析器创建点
:比执行
parse更关键的是 解析器是如何被创建和配置的 。你需要回溯调用栈,找到创建XMLReader、DocumentBuilder或SAXParser的代码位置。通常这里会有一个setFeature或setX的方法调用来配置解析器。 -
寻找安全配置缺失
:安全的XML解析应该至少禁用以下特性:
-
http://apache.org/xml/features/disallow-doctype-decl(设置为 true) -
http://xml.org/sax/features/external-general-entities(设置为 false) -
http://xml.org/sax/features/external-parameter-entities(设置为 false) -
或者使用
XMLConstants.FEATURE_SECURE_PROCESSING在调试器中,你可以使用“Evaluate Expression”(Alt+F8)功能,查看解析器实例的这些Feature值是否被正确设置为安全状态。在漏洞版本中,你很可能发现这些配置是缺失的,或者被设置为不安全的值(如允许外部实体)。
-
-
确定漏洞根因
:通过调试,你最终可以将漏洞根因定位到某个具体的类和方法。例如,可能是在
/framework/webtools/src/main/java/org/apache/ofbiz/webapp/event/XmlRpcEventHandler.java的某个方法中,直接使用了未经验安全配置的DocumentBuilderFactory.newInstance().newDocumentBuilder()。
实操心得 :在跟踪过程中,会遇到大量框架代码和第三方库代码。不要试图理解每一行,要像侦探一样,抓住“XML”、“parse”、“解析”、“请求体”这些关键词相关的线索。善用IDEA的“Find Usages”(Alt+F7)功能,选中一个方法或变量,可以快速找到它在项目中的所有被调用或使用的地方,这能帮你理清数据流向。
4.4 高级调试功能应用
除了基本的步进,IDEA调试器还有一些强大功能可以助你一臂之力:
-
条件断点
:右键点击断点,可以设置条件。例如,你只想在XML请求体包含特定字符串(如
<!ENTITY)时才中断,这样可以避免被大量正常请求干扰。 - 日志断点 :同样右键点击断点,选择“More”或直接创建“Log Breakpoint”。这种断点不会暂停程序,而是输出你指定的日志信息(如变量值、调用方法名)。非常适合在不中断流程的情况下观察程序的执行路径和关键数据。
- 观察点(Watch) :在“Variables”或“Watches”窗口,你可以添加对某个变量或表达式的监视。当它的值发生变化时,调试器会高亮显示,帮助你跟踪关键数据的流转。
- 内存查看 :对于复杂的对象,可以使用“Evaluate Expression”功能,直接执行一段代码来查看其内部状态,或者调用它的某个getter方法。
通过组合运用这些技巧,你可以高效地在数十万行代码中定位到那个关键的、不安全的XML解析配置点,从而完成对CVE-2020-9496漏洞的根因分析。
5. 常见问题与排查技巧实录
在实际操作中,你几乎一定会遇到各种问题。下面是我在多次搭建和调试Ofbiz环境中踩过的一些坑以及解决方案,希望能帮你节省大量时间。
5.1 环境与构建类问题
问题1:Gradle构建失败,提示依赖下载超时或找不到。
-
现象
:在IDEA导入项目或执行
gradlew ofbiz命令时,卡在下载依赖,最终失败。 - 排查 :这通常是网络问题,特别是连接Maven中央仓库不稳定。
-
解决
:
- 检查网络连通性。
-
配置国内镜像
:在用户主目录下的
.gradle文件夹中创建或修改init.gradle文件,添加以下内容:allprojects { repositories { // 优先使用阿里云镜像 maven { url 'https://maven.aliyun.com/repository/public/' } maven { url 'https://maven.aliyun.com/repository/central/' } // 保留原始仓库作为备用 mavenCentral() } } -
清理Gradle缓存:有时缓存损坏也会导致问题。可以删除
~/.gradle/caches目录(注意这会清空所有项目的缓存,下次构建需要重新下载),或者更精确地删除~/.gradle/caches/modules-2/files-2.1下与失败依赖相关的目录。
问题2:启动Ofbiz时,提示端口被占用(如8080端口)。
- 现象 :运行配置启动失败,日志显示地址已在使用。
-
解决
:
-
找到占用端口的进程并关闭:在命令行使用
netstat -ano | findstr :8080(Windows) 或lsof -i :8080(Linux/macOS)。 -
修改Ofbiz的默认端口:在Ofbiz项目目录下的
/framework/start/src/main/config/start.config文件中,找到port.http.port属性,将其修改为其他未使用的端口,如8090。
-
找到占用端口的进程并关闭:在命令行使用
问题3:IDEA调试器无法连接,提示“Unable to open debugger port”。
- 现象 :启动调试配置后,控制台显示监听5005端口,但IDEA显示连接失败。
-
排查
:
-
首先确认Ofbiz进程是否真的在5005端口监听。可以用
netstat -ano | findstr :5005检查。 -
检查IDEA运行配置中“Before launch”的
--debug-jvm任务是否成功添加并位于ofbiz任务之前。 - 检查是否有防火墙阻止了本地回环地址(127.0.0.1)上5005端口的连接。
-
首先确认Ofbiz进程是否真的在5005端口监听。可以用
-
解决
:确保配置顺序正确。如果问题依旧,可以尝试在运行配置的“Gradle task”的Arguments中直接添加JVM调试参数,例如:在Tasks栏填写
ofbiz -Dorg.gradle.jvmargs="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"。但这不如使用--debug-jvm优雅。
5.2 调试与代码分析类问题
问题4:断点打上了,但发送请求后始终不中断。
- 现象 :在疑似入口类打了断点,但请求正常处理,调试器无反应。
-
排查
:
- 确认调试会话已连接 :查看IDEA底部“Debug”工具窗口,确保显示已连接到进程,并且没有断开。
-
确认断点位置正确
:你可能打错了类或方法。通过日志或请求响应,确认你的请求是否真的走了你猜测的代码路径。可以在代码中添加一些简单的
System.out.println日志来辅助判断。 - 检查断点是否被禁用 :断点图标上如果有斜线,表示被禁用。
- 类是否被加载 :有时代码被多个ClassLoader加载,断点可能打在了未被实际使用的类上。尝试在断点处添加日志(见上文“日志断点”),看是否有输出。
-
解决
:使用更全局的拦截点。例如,在Java Web应用中,可以在Servlet的
service方法或Filter的doFilter方法上打断点,这是所有请求的必经之路,然后再一步步跟踪到具体处理器。
问题5:跟踪过程中,步进(Step Into)进了JDK或第三方库的源码,陷入无关细节。
-
现象
:按F7后,进入了
ArrayList.get()或String.equals()等JDK内部方法,或者进入了像Xerces这样的第三方库深水区。 - 解决 :IDEA提供了“Force Step Into” (Alt+Shift+F7) 和更重要的 “Step Into My Code” 功能。你可以在调试设置中(File -> Settings -> Build, Execution, Deployment -> Debugger -> Stepping)勾选“Skip non-project classes”,这样在步进时会自动跳过所有非本项目(即JDK和第三方库)的代码,直接进入你自己的业务逻辑中。这是提高调试效率的神器。
问题6:变量窗口显示
null
或看不到想看的变量值。
-
现象
:在断点处暂停后,Variables窗口里某些局部变量显示为
null,或者根本看不到。 -
排查
:
-
编译优化
:如果代码在编译时被优化(如使用较高优化级别的JIT),局部变量信息可能丢失。确保是以调试模式运行(我们之前配置的
--debug-jvm就是为了这个)。 - 作用域 :确认当前栈帧(Frame)选中的是你感兴趣的方法。变量窗口显示的是当前选中栈帧的局部变量。
- Lambda表达式/匿名内部类 :这些结构中的变量捕获有时在调试器中显示不直观。
-
编译优化
:如果代码在编译时被优化(如使用较高优化级别的JIT),局部变量信息可能丢失。确保是以调试模式运行(我们之前配置的
-
解决
:使用“Evaluate Expression” (Alt+F8) 功能。你可以在弹出的计算器中,直接输入一段代码来获取你想要的值,例如
request.getParameter("xml")或者documentBuilderFactory.getFeature("http://xml.org/sax/features/external-general-entities")。这是调试中最常用的功能之一。
5.3 漏洞复现与验证类问题
问题7:按照PoC发送请求,但没有成功读取到文件内容。
- 现象 :请求返回了错误,或者返回了正常响应但没有包含预期的文件内容。
-
排查
:
- PoC是否正确 :确认你的PoC XML格式完全正确,特别是DTD声明和实体引用部分。一个字符错误都可能导致解析失败。
-
目标文件是否存在
:PoC中指定的文件路径(如
file:///etc/passwd)在服务器上是否存在。在Windows上,可以尝试file:///C:/Windows/win.ini。 - 权限问题 :Ofbiz进程是否有权限读取目标文件。
-
输出位置
:XXE漏洞读取的文件内容不一定直接体现在HTTP响应中。它可能被记录到日志、存储到数据库变量,或者导致错误信息泄露。你需要结合调试,观察在解析过程中,实体
&xxe;被替换后的值流向了哪里。在调试器中,跟踪解析后得到的Document对象或相关字符串变量。
-
解决
:在调试模式下,在XML解析后的代码行设置断点,然后检查解析结果对象(如
Document、Node)的内容,看看文件内容是否被成功注入。这是验证漏洞是否存在的直接证据。
搭建和调试一个像Apache Ofbiz这样的大型历史项目,本身就是对耐心和技能的双重考验。过程中遇到的每一个错误信息、每一个不生效的断点,都是你更深入理解这个系统以及Java调试技术的机会。记住,调试的核心思想是“大胆假设,小心求证”——根据漏洞描述和代码结构猜测可能的位置,然后用调试器这个显微镜去仔细观察和验证你的猜想。当你最终通过自己的调试跟踪,在浩瀚代码中精准定位到那一行不安全的
setFeature
调用时,那种成就感是无与伦比的。这套方法不仅适用于CVE-2020-9496,也适用于任何你想深入分析的Java应用漏洞或复杂业务逻辑。

358

被折叠的 条评论
为什么被折叠?



