2095 字
约 6 分钟
14
S05-A27 · JMX 端口:为什么"线上 9010 暴露了所有内部状态"
2024-12-31
2026-11-11

一次安全扫描报出高危:某服务的 9010 端口"无认证可读全部 MBean"——里面有"连接的完整 JDBC URL(含密码)"、"线程池队列里的任务名"、以及"最近的 SQL 语句"。而这个端口是"为了监控"随手开的:-Dcom.sun.management.jmxremote.port=9010,其余全是默认——而默认就是"无认证、无 SSL、绑 0.0.0.0".

JMX 是"JVM 的自省接口",而它的默认配置是"为'本地调试'设计的"——**一旦"监听在对外地址",它就等于"把 JVM 内部状态挂到公网上".

"JMX 是什么"值得一句话说清:它是"JVM 按名字暴露的对象树"——你给它"一个 MBean 名",它返回"属性与方法"。于是"它暴露什么",取决于"装了哪些 MBean":标准 MBean 给"内存/线程/GC",而"连接池、缓存、业务组件"往往会注册"自己的 MBean",而"那些"才是"带敏感信息的部分"。

1. JMX 端口能读到什么

内容 敏感度
JVM 内存/线程/GC 低
线程栈(Thread.print) 中高(含业务方法名、参数)
数据源配置(URL/用户名) 高
缓存内容/队列任务 高
动态操作(invoke) 最高(可调参、可触发)

第五项是"最容易被忽略"的:JMX 不只是"读",很多 MBean 还暴露"写操作"——**如"改日志级别""清缓存""触发 GC",而"某些自定义 MBean"甚至能"执行任意方法".

"最小改动"的启动参数只有两行,而它"关掉了对外可达":

// 两行"只绑本地"(推荐首选)
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.local.only=true
// 需要远程时:绑内网 + 认证 + SSL
-Dcom.sun.management.jmxremote.host=10.0.0.12
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.password.file=/etc/jmx/passwd
-Dcom.sun.management.jmxremote.access.file=/etc/jmx/access

"认证开了却连不上"这个坑,值得单独记:-Dcom.sun.management.jmxremote.authenticate=true 是默认,但"没有 password 文件"时,"所有远程连接都会被拒"——而"运维的第一反应"通常是"把 authenticate 设成 false",于是"从'连不上'变成'谁都能连'"。而"正解"只有一步:"生成 password.file(chmod 600)"。

2. "默认配置"的三个危险项

项 默认值
jmxremote.authenticate true(但没有密码文件 → 实际"不放行")
jmxremote.ssl false
绑定地址 0.0.0.0(所有网卡)

第一项"最坑":"打开认证但没提供 password.file"时,连接会被拒——于是"运维为了让它通",就把 authenticate=false 加上——**而那一步,"刚好"把"认证"关掉.

"access.file"要"按 MBean 授权",而它"只有两列":

// 一份"只读"access 文件(两行)
monitor readonly
monitor readonly java.lang.*
// 只给"读",不给"写/invoke"——于是"能看内存,不能改日志级别"
// 判据:access 里"没有 write/create/unregister"

3. "三种安全配置"的选择

方式 代价
只绑 127.0.0.1 + 本地 jconsole/SSH 隧道 最简单(推荐)
绑定内网 + 认证 + SSL 要"证书 + 密码文件"
关掉 JMX 远程,改用"指标导出"(Micrometer/Prometheus) 最推荐(长期)

第一项是"最小改动":-Dcom.sun.management.jmxremote.local.only=true(或绑 -Djava.rmi.server.hostname=127.0.0.1)——**于是"只有本机能连",外部"扫不到".

"指标导出"是"长期正确"的替代,而它"仍然可以基于 JMX":

// jmx_exporter 的最小配置(三行)
rules:
  - pattern: "java.lang<type=Memory><HeapMemoryUsage>used"
    name: jvm_heap_used_bytes
// 收益:Prometheus 抓 HTTP,不再暴露 RMI 端口

"jmx_exporter 的思路值得理解,因为它"把'远程 JMX'这件事取消了":它作为"进程内的 agent(或 sidecar)"读本地 MBean,再把结果"用 HTTP 暴露成 /metrics"——于是"Prometheus 抓的是'白名单里的指标'",而不是"整个 MBean 树"。而"白名单"这个设计,恰好"把'密码、任务名'这类东西挡在门外"。

4. "为什么监控要 JMX"的三个误解

误解 现实
"只有 JMX 能给 JVM 指标" 有 JMX Exporter/Micrometer
"Prometheus 抓不到 JVM" jmx_exporter 是标准做法
"关掉 JMX 就没法诊断" jcmd/jstack/jfr 都能"本地"

第三项是"关键":"诊断"需要的是"能进机器",而不是"端口对外"——**而"能进机器"本来就该"走 SSH/堡垒机".

"RMI 两个端口"是"连不上"的经典原因,而它"需要两个都固定":

// 反例:只固定注册端口 9010,数据端口"随机"→ 防火墙拦住
-Dcom.sun.management.jmxremote.port=9010
// 正解:同时固定"数据端口"
-Dcom.sun.management.jmxremote.rmi.port=9011
-Djava.rmi.server.hostname=10.0.0.12
// 判据:防火墙"两个端口都放行"

5. "开了 JMX 之后"的三件事

事 做法
改"绑定地址" 只绑本地/内网
加"认证" 提供 password.file + access.file(只读)
限"可操作项" access.file 里"只给 read"

第三项是"最有效"的:access.file 支持"按 MBean 名"授权——**于是"读内存可以、invoke 不行".

"密码出现在 MBean 里"这件事,要"按 MBean"关掉,而不是"整体关":

// 三种"处理"
1 关掉"数据源 MBean"(不暴露 URL 与 username)
2 打开"脱敏"(如 HikariCP 的 register-mbeans=false)
3 只给"read"权限(密码仍可见,但"不能改")
// 判据:先 1(不暴露),再 3(只读)

"扫描报高危"之后"怎么补",有个"顺序"建议:先"关掉对外可达"(绑本地/删端口映射),再"补认证",最后"决定要不要保留"——**因为"前两步"是"分钟级"的,"第三步"往往要"改监控链路",而"暴露面"不该"等改造完成才关"。

6. 排错:五种"JMX 暴露问题"

现象 成因 处理方向
扫描报"无认证" authenticate=false 改回 + 密码文件
外部能连、内部连不上 绑了 127.0.0.1 走隧道/绑内网
认证开了却连不上 缺 password.file 提供文件
RMI 端口随机(连不上) 只固定了"注册端口" 固定"数据端口"
密码出现在 URL 里 数据源 MBean 脱敏/关该 MBean

"只绑本地"时,"诊断"怎么走"隧道"很实用:

// 三步"SSH 隧道"(不暴露任何端口)
1 ssh -L 9010:127.0.0.1:9010 user@host
2 本地 jconsole 连 localhost:9010
3 用完关闭隧道
// 判据:公网上"没有任何 JMX 端口"

7. 边界:什么情况"可以不关 JMX"

场景 为什么 做法
只绑 127.0.0.1 外部不可达 可留
内网 + 堡垒机 + 认证 有"多层防护" 可留
已用"指标导出"替代 不再需要远程 JMX 关掉远程
开发机/单机 无"外部" 随意

判断口径一句话:"这个端口,从'公网/办公网'能不能连到"。能,就必须"认证 + SSL + 只读 access 文件"(或干脆关掉);不能(只绑本地/仅堡垒机可达),它只是"内部工具"——而"为了监控方便"而"开在 0.0.0.0",是这类事故的唯一成因.

8. 自测

  1. JMX 端口能读到哪五类内容?为什么"动态操作"最危险?
  2. "默认配置"的三个危险项里,哪一项"最坑"?"为了让它通"会发生什么?
  3. "只绑本地"为什么是"最小改动"?它解决了什么?
  4. "监控必须用 JMX"为什么是误解?替代方案是什么?
  5. "access.file 只给 read"为什么"最有效"?
  6. 判断"能不能不关 JMX"的那个问题是什么?

补一句"责任"视角:JMX 端口通常"不是应用开发者开的",而是"部署/运维在启动参数里加的"——于是"谁开的"与"谁负责"常常错位。而"清理"这类暴露的可行做法,是"把它纳入'启动参数审计'":任何"监听 0.0.0.0 的调试端口",都应当在"发布检查单"里被问一句"为什么"。

9. 一句话总结

JMX 的默认配置是"为本地调试"设计的,而它的默认绑定是"所有网卡"——两者结合,就是"把 JVM 内部挂到网络上"。稳妥做法只有两条:要么"只绑本地 + SSH 隧道",要么"内网 + 认证 + 只读 access 文件";而"用指标导出替代远程 JMX",是"既安全又够用"的长期选择。

S05-A27 · JMX 端口:为什么"线上 9010 暴露了所有内部状态"
https://www.clxhxhhr.top/posts/6718/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。