生产环境JVM的安全调试

20
我们有一些应用程序在生产环境中偶尔会进入不良状态。虽然进行堆转储可以帮助收集状态信息,但使用远程调试器通常更容易。设置这个非常简单——只需要在命令行中添加以下内容:
-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:这是一个hack,即使在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异常会使调试器启动。至少在JDK1.4.2上需要启动参数,但JVM可以优雅地处理垃圾值。

我们计划制作一个小servlet,通过适当的安全措施,让我们能够打开调试器。当然,一旦打开就无法关闭,而且调试器仍然会一直监听。但这些都是我们愿意接受的限制,因为对生产系统进行调试总是会导致重启。

更新#3: 我编写了三个类:(1)TurnOnDebuggerException,一个普通的Java异常,(2)DebuggerPoller,一个检查文件系统上指定文件存在性的后台线程,和(3)DebuggerMainWrapper,一个类,它启动轮询线程并反射调用另一个指定类的main方法。

用法如下:

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

现在,当你启动JVM时,除了启动一个后台轮询线程外,其余都是相同的。假设文件(我们的文件名为TurnOnDebugger)最初不存在,则轮询器每隔N秒检查一次。当轮询器首次注意到它时,它抛出并立即捕获TurnOnDebuggerException。然后,代理启动。

你无法再将其关闭,并且当其处于打开状态时,机器并不是非常安全。好处是,我认为调试器不允许多个同时连接,因此维护调试连接是您最好的防御。我们选择文件通知方法,因为它允许我们通过指定目录中的触发文件来依附于我们现有的Unix身份验证/授权。您可以轻松地构建一个小型war文件,通过套接字连接实现相同的目的。当然,由于我们无法关闭调试器,所以只会在关闭病态应用程序之前使用它来收集数据。如果有人需要这段代码,请告诉我。不过,自己编写这些代码只需要几分钟时间。

4个回答

8
如果您使用SSH,可以允许隧道并将端口隧道传输到本地主机。无需开发,只需使用sshd、ssh和/或putty即可完成所有操作。
您的Java服务器上的调试套接字可以在本地接口127.0.0.1上设置。

如果这个(现在正在测试)有效,似乎对我们来说是最好的选择。虽然我们不经常进行调试,但我们希望能够捕获 JVM 处于异常状态的情况。 - ShabbyDoo
1
我认为这仅适用于JDK 1.5及以上版本:http://java.sun.com/j2se/1.5.0/docs/guide/jpda/enhancements.html请参见上面链接中的“dt_socket传输已经被修改,在服务器模式下采用本地地址”的说明。 - ShabbyDoo
@Shabby - 是的 - 看起来这个方案可以在1.5+版本中使用,并且是一个很好的解决方案。另一种选择是通过防火墙(软件或硬件)锁定调试端口。也许可以查看一下针对你的Linux主机的ipchains?(http://tldp.org/HOWTO/IPCHAINS-HOWTO.html) - Jared
我们的基础设施人员指出,绑定到回环会限制访问权限仅限于具有相关机器权限的人 - 对于某些应用程序来说可能有点宽泛。然而,在许多情况下,这可能已经足够了。 - ShabbyDoo

2

你说得没错:Java调试API本质上是不安全的。但是,你可以将其限制为UNIX域套接字,并编写一个代理程序,使用SSL / SSH进行身份验证和加密外部连接,然后将其代理到UNIX域套接字中。这至少减少了暴露给可以将进程放入服务器或破解SSL的人的风险。


可以将默认接口上的端口映射到域套接字吗?我遇到的问题(在最初的帖子之后发现)是1.4.x Sun JVM只能绑定到默认接口。因此,需要进行一些神奇的映射,以便此端口不会在VM外公开。 - ShabbyDoo

0
将信息/服务导出到JMX,然后使用RMI+SSL远程访问它。您的情况是JMX设计的用途(M代表管理)。

我认为常见的度量应该通过JMX公开。实际上,我们正在使用轻量级生产分析器(Wily),但它在捕获状态信息方面表现不佳,并且只能在限制为粗略的跟踪时表现良好。另一个问题是,其中一些应用程序是(部分)第三方的,因此最多只能使用反编译源代码进行调试。 - ShabbyDoo
1
我仍然坚持认为将调试器连接到生产应用程序是一个不好的主意。当您遇到断点并花费几分钟在内存中搜索时,用户将完全不知道发生了什么。我建议您尝试找出代码中存在问题的地方,并通过JMX公开当前状态,并保留详细的审计日志以记录正在发生的事情。 - Kevin
2
在调试之前,我们实际上会将表现不佳的实例从负载平衡集群中移除。我同意,在应用程序有活跃用户的情况下进行调试是一个非常糟糕的想法。 - ShabbyDoo

0

好问题。

我不知道是否有内置的能力来加密调试端口的连接。

可能有一个更好/更简单的解决方案,但我会执行以下步骤:

  1. 将生产机器放在阻止对调试端口进行访问的防火墙后面。
  2. 在主机上运行代理进程,连接端口并加密套接字的输入和输出。
  3. 在调试工作站上运行代理客户端,也加密/解密输入。与它之间的通信将被加密。
  4. 将您的调试器连接到代理客户端。

顺便提一下:我们的生产服务器在防火墙后面,但是它们暴露给了一些内部网络段。 - ShabbyDoo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接