Muchas veces nos es requerido analizar el comportamiento de nuestras aplicaciones funcionando en ambientes finales de despliegue, ya sea como una etapa más en el proceso de prueba, por restricción de los recursos existentes o a fin de poder adquirir rápidamente acceso remoto al dispositivo y así identificar y solucionar eventuales problemas.
Sin embargo, el proceso de depuración crea un punto de acceso a nuestras aplicaciones que debe ser cuidadosamente administrado y monitoreado para evitar vulnerabilidades.
Vulnerando la depuración remota en Java
Esta característica es proporcionada por la misma especificación de Java, y se basa en un mecanismo de escucha y enlace. El concepto es bastante sencillo: la aplicación a ser depurada adjunta un socket a sí misma y queda a la espera de comandos en ese puerto, mientras que el depurador se enlaza posteriormente a dicho socket y comienza el envío de instrucciones. El protocolo que permite esta transacción es denominado JDWP, del inglés Java Debug Wire Protocol, enmarcado en la arquitectura JPDA o Java Platform Debug Architecture, construida sobre la JVMTI o JVM Tool Interface.
A fin de permitir el debugging remoto, la aplicación debe ser inicializada de manera particular, proveyendo ciertos argumentos específicos. Supongamos que deseamos iniciar Apache Tomcat en modo de depuración remota; para ello podemos incluir los siguientes comandos al lanzar la aplicación por consola:Una vez hecho esto, vemos que tenemos una escucha al puerto 8000. Si no especificamos un puerto diferente, la aplicación utilizará este puerto por defecto. El parámetro server nos permite indicar el comportamiento de la JVM en el proceso de depuración: y -yes- comanda a la JVM a tomar el rol de servidor y esperar conexiones del depurador, mientras que n –no, opción por defecto- hará que la JVM actúe como cliente, enlazándose al socket del depurador.
Cabe mencionar que, para JVMs 1.5 o superiores, es posible reemplazar los parámetros -Xdebug -Xrunjdwp por -agentlib:jdwp, y en el caso de Apache Tomcat podemos configurar estas opciones de inicio en el archivo setenv.sh:
Lo interesante de este proceso es que el enlace se realiza mediante una cadena en texto plano, “JDWP-Handshake”, y el paso inicial puede tomarlo la máquina depuradora legítima, o bien un atacante.
Como es mostrado en este artículo, un atacante puede utilizar un exploit para vulnerar la conexión de depuración y configurar unbreakpoint en algún método comúnmente utilizado, determinando incluso comandos maliciosos a ejecutarse en la máquina depurada:En este caso, el payload creará un proceso de escucha en la máquina depurada, en el puerto 6667, al cual podremos luego conectarnos y acceder a la máquina remota con privilegios del usuario local para tal vez dar un paseo por el archivo de contraseñas.
A continuación tenemos una vista de la máquina depurada:Y debajo el depurador:
Entonces, ¿qué medidas de seguridad tomamos al respecto?
En primer lugar, mantener una aplicación ejecutándose en modo de depuración remota no es una muy buena idea, y debiese ser evitada en la medida de lo posible. Una aplicación inicializada sin agentes habilitados y con los manejadores de seguridad instalados es el mejor escenario. Esto último podemos lograrlo mediante el comando:
${RUTA_JDK}/bin/java -Djava.security.manager ApplicationName
Sin embargo, a veces esto simplemente no es una opción. En esos casos es importante tomar algunas precauciones. Como puede suponerse, es necesario utilizar un puerto confiable y no aquel por defecto, con el objeto de evadir procesos de escaneo masivo de red.
Además, al establecer los argumentos iniciales, es importante indicar a la JVM que se comporte como cliente, mediante el argumento server=n.
Finalmente, debemos ser cuidadosos de no otorgar permisos que no son necesarios para la aplicación. En particular, permisos de socket a hosts arbitrarios; omitir el permiso java.net.SocketPermission "*", "connect,accept” es una buena práctica.