Java 内存马(九):WildFly、WebLogic、WebSphere 与 Resin

企业应用服务器与嵌入式 Tomcat 最大的区别,不是类名更多,而是配置和运行时分布在多个层级。一个 WAR 完全正常,服务器级 Filter、Deployment Plan、共享库、节点配置或启动 Agent 仍然可以改变请求链。

所以不能只问“包里有没有恶意类”,而要同时回答:

1
2
3
4
管理面配置了什么
节点实际运行什么
类从哪里加载
集群各节点是否一致

本文只使用只读清点和诊断命令,不提供运行时植入代码。命令必须使用目标产品自带、版本匹配的管理工具执行。

一、先建立四个证据面

不同产品名称不同,但证据始终可以分成四层:

证据面 要回答的问题 常见来源
管理配置 控制面声明了什么 Domain、Cell、Profile、Deployment Plan、server.xml
节点运行时 JVM 里实际装配了什么 Deployment、Web Context、Handler、Servlet 注册表
类加载来源 对象的代码从哪里来 Module、Shared Library、应用 ClassLoader、Agent
集群一致性 哪些节点发生偏移 Server Group、Managed Server、Node、Server

四类服务器的主要差异:

产品 Web 核心 配置层级 典型盲区
WildFly Undertow Domain/Profile/Server Group/Deployment 只查 WAR,漏掉 Undertow 子系统和 JBoss Module
WebLogic WebApp Runtime Domain/Managed Server/Plan/Shared Library 只查 AdminServer,漏掉 Managed Server 当前状态
WebSphere Traditional Web Container Cell/Node/Server/Application 配置正确,但节点尚未同步
Liberty Feature 与 OSGi 组件 server.xml/Feature/Application/Library 把 Liberty 当成 Traditional WAS
Resin WebApp resin.xml/resin-web.xml/web.xml 老旧节点缺少可信运行基线

应用内部可以先导出 Servlet 规范层,但这只是第一层视图:

1
2
3
4
5
6
7
8
9
10
11
12
ServletContext context = request.getServletContext();

context.getServletRegistrations().forEach((name, item) ->
System.out.println("servlet=" + name
+ ", class=" + item.getClassName()
+ ", mappings=" + item.getMappings()));

context.getFilterRegistrations().forEach((name, item) ->
System.out.println("filter=" + name
+ ", class=" + item.getClassName()
+ ", urls=" + item.getUrlPatternMappings()
+ ", servlets=" + item.getServletNameMappings()));

这段代码看不到服务器原生 Handler、共享库、管理模型和 Agent,也未必能取得实际实例。不要将它暴露为公网诊断接口;应在隔离副本、受控管理任务或启动基线采集器中执行。

二、WildFly 与 WebLogic 的只读核对

2.1 WildFly:管理模型必须对齐 Undertow 运行时

WildFly 请求链可以粗略表示为:

1
2
3
4
Undertow 子系统配置
→ Server / Host / Filter 引用
→ Deployment Handler
→ Servlet Filter / Servlet

Standalone 模式下,先导出 Undertow 子系统:

1
2
jboss-cli.sh --connect \
--commands="/subsystem=undertow:read-resource(recursive=true,include-runtime=true)"

再读取部署资源:

1
2
jboss-cli.sh --connect \
--commands="/deployment=app.war:read-resource(recursive=true,include-runtime=true)"

重点核对:

  • custom-filter、expression-filter 及其 Host 引用;
  • Deployment 与实际 Handler 链是否一致;
  • 自定义类属于哪个 JBoss Module;
  • JVM 参数是否加载 Agent 或额外模块;
  • 管理审计日志中何时发生配置变更。

Domain 模式不能只连任意一台 Server。还要把 Profile、Server Group、Host Controller 和具体 Server 的结果对齐;同一个 Server Group 中只有一个节点出现额外 Handler,通常比全组一致的配置更可疑。

2.2 WebLogic:不要停在 AdminServer

WebLogic 的最终运行状态可能由以下内容共同决定:

1
2
3
4
5
6
WAR / EAR
+ web.xml / weblogic.xml
+ Deployment Plan
+ Domain 配置
+ Shared Library
+ Managed Server 启动参数

WLST 进入 Domain Runtime 树后,可以只读枚举所有 Server 与应用运行时:

1
2
3
4
5
6
7
8
9
10
11
connect(
userConfigFile='/secure/wlst-monitor.config',
userKeyFile='/secure/wlst-monitor.key',
url='t3://admin-host:7001'
)
domainRuntime()

for server in cmo.getServerRuntimes():
print('server=' + server.getName() + ', state=' + server.getState())
for app in server.getApplicationRuntimes():
print(' app=' + app.getName())

配置文件应只授予监控账号读取权限,并限制操作系统访问权限。不要把管理密码写入脚本、命令行参数或终端历史。随后逐节点比较:

  • 应用部署目标、版本和当前状态;
  • 原始制品与 Deployment Plan;
  • Shared Library 和 Domain lib;
  • Server Start 参数、ClassPath 与 Java Options;
  • 每个 Managed Server 的 Servlet、Filter、Listener 和类来源。

AdminServer 显示“已部署”只代表控制面状态,不能证明每个 Managed Server 的当前对象一致。未取证前直接重新部署会改变运行时引用和时间线,不适合作为第一步。

三、WebSphere、Liberty 与 Resin 的关键差异

3.1 WebSphere Traditional

Traditional WebSphere 的层级是 Cell、Node、Server。先列出已安装应用和运行中的应用 MBean:

1
2
print(AdminApp.list())
print(AdminControl.queryNames('type=Application,*'))

再保存以下状态:

  • Cell 主配置库与应用部署元数据;
  • Node Sync 状态和同步时间;
  • 共享库、类加载顺序及模块覆盖规则;
  • wsadmin 变更记录与安全审计日志;
  • 每个 Server JVM 的启动参数和 Agent。

Cell 中配置正确,不等于 Node 已同步;应用制品相同,也不等于父优先或父最后的类加载结果相同。实际类的 ClassLoader 和 CodeSource 必须在对应 JVM 中确认。

3.2 Liberty

Liberty 由 Feature、OSGi Bundle 和配置片段装配运行时。首先核对:

  • server.xml 及其 include 文件;
  • 启用的 Servlet 或 Jakarta EE Feature;
  • apps、dropins 和外部应用路径;
  • commonLibraryRef、privateLibraryRef 与全局库;
  • server.env、bootstrap.properties、jvm.options。

可用产品自带命令生成诊断归档:

1
server dump myserver --archive=myserver-dump.zip

归档中的配置、已部署应用、OSGi Bundle、SCR 组件和运行环境可用于节点对比。Traditional 与 Liberty 内部对象不同,不能复用私有字段脚本;能复用的是“配置、运行时、类来源”三份清单。

3.3 Resin

Resin 应同时检查:

1
2
3
4
5
resin.xml
resin-web.xml
WEB-INF/web.xml
全局 WebApp 默认配置
应用 lib 与全局 lib

历史系统经常没有正常基线。此时应选择同版本、同补丁、同配置的干净节点,比较 WebApp、ContextPath、Servlet/Filter 映射、Filter 顺序、启动类路径和类来源,不能拿其他 Resin 版本的字段名直接套用。

四、按节点生成差异矩阵并恢复

每个节点至少生成一条记录:

1
2
3
4
5
6
7
8
节点、产品与补丁版本
JDK 完整版本和启动参数
应用制品哈希
Deployment Plan 或服务器配置哈希
Servlet / Filter / Listener 清单
容器原生 Handler 清单
共享库、模块和 Agent
异常类的 ClassLoader 与 CodeSource

按应用和集群分组后,优先调查以下差异:

  • 只有一个节点出现额外 Handler;
  • 名称相同,实际类或 CodeSource 不同;
  • 管理面显示未部署,节点内却存在运行对象;
  • 应用哈希一致,启动参数、Plan 或共享库不同;
  • 边界代理有请求,应用访问日志没有记录。

处置前还要判断异常落在哪个变更面:

变更面 只重启 JVM 只重部署应用
纯运行时 Servlet/Handler 通常消失 可能消失
Domain/Cell/Profile 配置 会重新装配 通常不受影响
Deployment Plan 会重新生效 会重新生效
Shared Library/Module 文件仍在就会加载 通常仍存在
启动 Agent 重启时再次加载 不受影响

完整恢复顺序是:保存管理配置和审计历史,隔离异常节点,枚举实际对象及类来源,修复控制面与初始入口,从可信制品重建全部受影响节点,再用同一份清单复测。控制台显示正常、单个 WAR 哈希一致或某个节点重启成功,都不能单独构成恢复结论。

上一篇
下一篇