生产 JVM 的安全调试

2022-09-02 10:54:00

我们有一些应用程序有时会进入不良状态,但仅在生产中(当然!虽然采用堆转储可以帮助收集状态信息,但使用远程调试器通常更容易。设置这个很容易 - 只需要将其添加到他的命令行中:

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=PORT

似乎没有可用的安全机制,因此在生产环境中启用调试将有效地允许任意代码执行(通过热插拔)。

我们有在 Solaris 9 和 Linux(Redhat Enterprise 4)上运行的 1.4.2 和 1.5 Sun JVM 的混合体。如何实现安全调试?还有其他方法可以实现我们的生产服务器检查目标吗?

更新:对于 JDK 1.5+ JVM,可以指定调试器应绑定到的接口和端口。因此,KarlP 关于绑定到环回并仅使用 SSH 隧道到本地开发人员框的建议应该有效,因为 SSH 已在服务器上正确设置。

但是,JDK1.4x 似乎不允许为调试端口指定接口。因此,我们可以阻止对网络中某个位置的调试端口的访问,也可以在操作系统本身中执行一些特定于系统的阻止(Jared建议的IPChains等)?

更新#2:这是一个黑客攻击,可以让我们限制风险,即使在1.4.2 JVM上也是如此:

命令行参数:

-Xdebug
-Xrunjdwp:
    transport=dt_socket,
    server=y,
    suspend=n,
    address=9001,
    onthrow=com.whatever.TurnOnDebuggerException,
    launch=nothing

用于打开调试器的 Java 代码:

try {
    throw new TurnOnDebuggerException();
} catch (TurnOnDebugger td) {
   //Nothing
}

TurnOnDebuggerException可以是任何保证不会在其他任何地方抛出的异常。

我在Windows盒子上对此进行了测试,以证明(1)调试器端口最初不接收连接,并且(2)如上所述引发TurnOnDebugger异常会导致调试器处于活动状态。launch 参数是必需的(至少在 JDK1.4.2 上是这样),但 JVM 可以正常处理垃圾值。

我们计划制作一个小的 servlet,在适当的安全性之后,它可以让我们打开调试器。当然,之后无法将其关闭,调试器一旦打开,仍然会混杂地收听。但是,这些都是我们愿意接受的限制,因为调试生产系统总是会导致之后的重新启动。

更新#3:我最终写了三个类:(1)TurnOnDebuggerException,一个普通的'ol Java异常,(2)DebuggerPoller,一个后台线程,用于检查文件系统上是否存在指定文件,以及(3)DebuggerMainWrapper,一个启动轮询线程然后反射调用另一个指定类的main方法的类。

这是它的使用方式:

  1. 在启动脚本中将“main”类替换为 DebuggerMainWrapper
  2. 添加两个系统 (-D) 参数,一个指定真正的主类,另一个指定文件系统上的文件。
  3. 在命令行上配置调试器,并添加 onthrow=com.whatever.TurnOnDebuggerException 部分
  4. 将包含上述三个类的 jar 添加到类路径中。

现在,当您启动JVM时,除了启动后台轮询器线程之外,一切都是相同的。假设该文件(我们的文件称为 TurnOnDebugger)最初不存在,轮询器每 N 秒检查一次。当轮询者第一次注意到它时,它会抛出并立即抓住TurnOnDebuggerException。然后,代理被踢掉。

您无法将其关闭,并且机器在打开时不是很安全。从好的方面来说,我不认为调试器允许多个同时连接,因此维护调试连接是最好的防御措施。我们之所以选择文件通知方法,是因为它允许我们通过在只有正确使用才具有权限的目录中指定触发器文件来搭载现有的Unix身份验证/作者。您可以轻松构建一个小的war文件,通过套接字连接实现相同的目的。当然,由于我们无法关闭调试器,因此我们只会在杀死生病的应用程序之前使用它来收集数据。如果有人想要这个代码,请让我知道。但是,您只需要几分钟就可以自己将其放在一起。


答案 1

如果使用 SSH,则可以允许通过隧道将端口隧道连接到本地主机。无需开发,全部使用sshd,ssh和/或腻子完成。

Java 服务器上的调试套接字可以在本地接口 127.0.0.1 上进行设置。


答案 2

你是绝对正确的:Java Debugging API本质上是不安全的。但是,您可以将其限制为 UNIX 域套接字,并使用 SSL/SSH 编写代理,以便对经过身份验证和加密的外部连接进行代理,然后将其代理到 UNIX 域套接字中。这至少可以减少您接触到可以将进程导入服务器的人或可以破解您的SSL的人。