Secure Debugging for Production JVMs

2020-05-19 00:45发布

问题:

We have some applications that sometimes get into a bad state, but only in production (of course!). While taking a heap dump can help to gather state information, it's often easier to use a remote debugger. Setting this up is easy -- one need only add this to his command line:

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

There seems to be no available security mechanism, so turning on debugging in production would effectively allow arbitrary code execution (via hotswap).

We have a mix of 1.4.2 and 1.5 Sun JVMs running on Solaris 9 and Linux (Redhat Enterprise 4). How can we enable secure debugging? Any other ways to achieve our goal of production server inspection?

Update: For JDK 1.5+ JVMs, one can specify an interface and port to which the debugger should bind. So, KarlP's suggestion of binding to loopback and just using a SSH tunnel to a local developer box should work given SSH is set up properly on the servers.

However, it seems that JDK1.4x does not allow an interface to be specified for the debug port. So, we can either block access to the debug port somewhere in the network or do some system-specific blocking in the OS itself (IPChains as Jared suggested, etc.)?

Update #2: This is a hack that will let us limit our risk, even on 1.4.2 JVMs:

Command line params:

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

Java Code to turn on debugger:

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

TurnOnDebuggerException can be any exception guaranteed not to be thrown anywhere else.

I tested this on a Windows box to prove that (1) the debugger port does not receive connections initially, and (2) throwing the TurnOnDebugger exception as shown above causes the debugger to come alive. The launch parameter was required (at least on JDK1.4.2), but a garbage value was handled gracefully by the JVM.

We're planning on making a small servlet that, behind appropriate security, can allow us to turn on the debugger. Of course, one can't turn it off afterward, and the debugger still listens promiscuously once its on. But, these are limitations we're willing to accept as debugging of a production system will always result in a restart afterward.

Update #3: I ended up writing three classes: (1) TurnOnDebuggerException, a plain 'ol Java exception, (2) DebuggerPoller, a background thread the checks for the existence of a specified file on the filesystem, and (3) DebuggerMainWrapper, a class that kicks off the polling thread and then reflectively calls the main method of another specified class.

This is how its used:

  1. Replace your "main" class with DebuggerMainWrapper in your start-up scripts
  2. Add two system (-D) params, one specifying the real main class, and the other specifying a file on the filesystem.
  3. Configure the debugger on the command line with the onthrow=com.whatever.TurnOnDebuggerException part added
  4. Add a jar with the three classes mentioned above to the classpath.

Now, when you start up your JVM everything is the same except that a background poller thread is started. Presuming that the file (ours is called TurnOnDebugger) doesn't initially exist, the poller checks for it every N seconds. When the poller first notices it, it throws and immediately catches the TurnOnDebuggerException. Then, the agent is kicked off.

You can't turn it back off, and the machine is not terribly secure when its on. On the upside, I don't think the debugger allows for multiple simultaneous connections, so maintaining a debugging connection is your best defense. We chose the file notification method because it allowed us to piggyback off of our existing Unix authen/author by specifying the trigger file in a directory where only the proper uses have rights. You could easily build a little war file that achieved the same purpose via a socket connection. Of course, since we can't turn off the debugger, we'll only use it to gather data before killing off a sick application. If anyone wants this code, please let me know. However, it will only take you a few minutes to throw it together yourself.

回答1:

If you use SSH you can allow tunneling and tunnel a port to your local host. No development required, all done using sshd, ssh and/or putty.

The debug socket on your java server can be set up on the local interface 127.0.0.1.



回答2:

You're absolutely right: the Java Debugging API is inherently insecure. You can, however, limit it to UNIX domain sockets, and write a proxy with SSL/SSH to let you have authenticated and encrypted external connections that are then proxied into the UNIX domain socket. That at least reduces your exposure to someone who can get a process into the server, or someone who can crack your SSL.



回答3:

Export information/services into JMX and then use RMI+SSL to access it remotely. Your situation is what JMX is designed for (the M stands for Management).



回答4:

Good question.

I'm not aware of any built-in ability to encrypt connections to the debugging port.

There may be a much better/easier solution, but I would do the following:

  1. Put the production machine behind a firewall that blocks access to the debugging port(s).
  2. Run a proxy process on the host itself that connects to the port, and encrypts the input and output from the socket.
  3. Run a proxy client on the debugging workstation that also encrypts/decrypts the input. Have this connect to the server proxy. Communication between them would be encrypted.
  4. Connect your debugger to the proxy client.