WordPress Attack Detection

Detección de Ataques en WordPress

Un domingo al mediodía, recibí un alerta sobre una anomalía. Era 19 de marzo de 2023. Esta historia es sobre lo que sucedió.

Antecedentes

El sitio web de Blue Core Research usa WordPress (sistema libre y de código abierto de gestión de contenidos). WordPress usualmente usa MySQL como base de datos de backend, y nuestra instalación no es diferente.

Mientras nuestro WordPress no contiene información sensible, aún lo protegemos con Core Audit. Parcialmente, lo hacemos porque producimos Core Audit, y las licencias no nos cuestan nada. Pero para contextos más grandes, protegemos esta base de datos porque nuestro sitio web es la cara de la compañía, y aún sin información sensible, una filtración dañaría nuestra reputación.

Como verás en este trabajo, protegiendo la base de datos proteges la aplicación (WordPress) también. Y eso defiende nuestro servidor de ataques en Internet.

La Anomalía

Nuestra instalación de Core Audit cuenta con una configuración relativamente simple con alertas diarias de detección de anomalías. Recibimos algunos falsos positivos cada tanto, pero una mañana de domingo del 19 de marzo de 2023, recibimos un alerta de un nuevo error.

Access denied for user ''@'' (using password: YES)

Eso inmendiatamente me levantó una bandera roja. Esto es porque las anomalías solo alertan sobre algo diferente, como un cambio en la actividad del perfil de la aplicación. Pero un cambio que causa un acceso denegado parece sospechoso.

La Investigación

Las anomalías de Core Audit descansan en el repositorio de seguridad de Core Audit. Y esta anomalía particular está basada en la reducción de la porción de SQL de ese repositorio. El repositorio reducido SQL contiene cada construcción SQL que la base de datos ejecuta con la resolución de 5 minutos.

Una vez que recibí el alerta, me logué en Core Audit y revisé la vista relevante forense. Rápidamente encontré sobre qué fui alertado:

Date:     Sat 2023/03/18
Time:     18:55 - 19:00
Count:    1 per 5 minutes
Rows:     0
Command:  ERROR TEXT
Activity: Access denied for user ''@'' (using password: YES)

Esto es un error interno MySQL resultante de un log in fallido. El usuario y anfitrión en este error están entre comillas simples (‘) y son, por lo tanto, despojados desde el repositorio reducido de SQL. La división de literales es parte de cómo opera el repositorio reducido de SQL.

Mi próxima parada fue para buscar la sesión fallida que la causó. Al alternar a la sesión de vista forence, encontré esta sesión:

Start:    2023/03/18 18:55:59
End:      2023/03/18 18:55:59
Type:     No Login
Username: username_here
Machine:  localhost

Esto luce inusual desde que no contamos con un usuario en la base de datos llamado username_here Esto es, obviamente, un usuario inválido. Mi próxima parada fue mirar los logs del web server para ver quién o qué disparó este login inusual.

Aquí hay un extractro del log de Apache:

[18:55:36] GET /.wp-config.php_copy
[18:55:38] GET /.wp-config.php.rar
[18:55:39] GET /.wp-config.php.7z
[18:55:41] GET /.wp-config.php.tmp
[18:55:42] GET /.wp-config.php_tmp
[18:55:44] GET /.wp-config.php.old
[18:55:46] GET /.wp-config.php.0
[18:55:47] GET /.wp-config.php.1
[18:55:49] GET /.wp-config.php.2
[18:55:50] GET /.wp-config.php.zip
[18:55:52] GET /.wp-config.php.gz
[18:55:53] GET /.wp-config.php~
[18:55:55] GET /wp-config.php.templ
[18:55:56] GET /wp-config.php1
[18:55:58] GET /wp-config.php2
[18:55:59] GET /wp-config-sample.php
[18:56:00] GET /wp-config-backup.txt
[18:56:02] GET /wp-config.php.orig
[18:56:03] GET /wp-config.php.original
[18:56:05] GET /wp-license.php?file=..%2F..
[18:56:06] GET /wp-config.save
[18:56:07] GET /wp-config.txt
[18:56:09] GET /wp-config.dist
[18:56:10] GET /.wp-config.php.swo
[18:56:12] GET /wp-config%20copy.php
[18:56:12] GET /wp-config_good
[18:56:14] GET /wp-config-backup
[18:56:15] GET /wp-config-backup.php
[18:56:16] GET /wp-config-backup1.txt
[18:56:18] GET /wp-config-good
[18:56:19] GET /wp-config-sample.php.bak
[18:56:21] GET /wp-config-sample.php~
[18:56:22] GET /wp-config.backup

El culpable es claramente wp-config-sample.php, y mirando dentro de él, podemos encontrar estas líneas:

define( 'DB_USER', 'username_here' );
define( 'DB_PASSWORD', 'password_here' );
…
require_once ABSPATH . 'wp-settings.php';

Ahora entendemos qué pasó: alguien escaneó el sitio web buscando vulnerabilidades y llamó wp-config-sample.php. Ese script PHP contiene un usuario y contraseña inválido que disparó una falsa conexión a la base de datos. Core Audit detectó este inusual comportamiento y elevó una alerta.

Resolución

En WordPress, wp-config-sample.php es parte del paquete de instalación de WordPress. El proceso de instalación de WordPress copia este archivo en wp-config.php y define algunos parámetros como el usuario y contraseña de la base de datos.

Por lo tanto, wp-config-sample.php es esperado que exista en una instalación de WordPress. Y si el equipo de desarrollo de WordPress ha hecho bien su trabajo, es probable que no signifique una filtración de WordPress al llamarlo así.

Pero como todos sabemos, los errores existen, y ataques 0-dia pueden siempre ocurrir. Teniendo este archivo alrededor parece preguntar por problemas. Este archivo no tiene otro propósito en WordPress que no sea como plantilla para wp-config.php, entonces ¿no deberías eliminarlo?

Esto nos lleva a otro comportamiento de WordPress: su actualización. Cuando WordPress actualiza, sobre escribe todos los archivos que son parte del paquete de WordPress. Por lo tanto, borrando wp-config-sample.php será desecho tan pronto como WordPress automáticamente actualice.

Otra opción es dejar el archivo en el lugar pero cambiar los permisos del archivo así Apache no puede acceder ahí. Esto prevendría este script PHP de ejecutarse. Como sea, esto tal vez cause errores cuando WordPress intenta actualizar porque no será capaz de sobre escribir el archivo. Entonces la actualización requiere intervención manual.

Por ejemplo, puedes eliminar permisos ejecutando:

chown root:root wp-config-sample.php
chmod 000 wp-config-sample.php

Más allá

Como expliqué previamente, wp-config-sample.php es usado para crear wp-config.php durante la instalación. Esto significa que wp-config.php puede ser también vulnerable al mismo ataque u otro similar ataque 0-dia.

Más problemático es si una actualización de WordPress arregla una vulnerabilidad en wp-config-sample.php, este arreglo no actualizará wp-config.php desde que ese copy nunca es modificado.

wp-config.php está pensado para ser incluido por otros archivos de WordPress como index.php. Luego de definir los parámetros de la conexión de la base de datos, el script llama wp-settings.php para configurar el ambiente WordPress, incluyendo la conexión a la base de datos.

En otras palabras, wp-config.php nunca quiere ser ejecutado directamente. Para protegerlo contra un ataque ejecutado directamente, podemos agregar una línea en la parte de superior de wp-config.php para detener el proceso.

if (get_included_files()[0] == '/path_to_site/wp-config.php') exit();

¿WordPress?

Mientras muchos no van a considerar WordPress un buen ejemplo de seguridad de aplicación, pensamos que aporta valor por estas razones:

3ra Parte & Cadena de suministro

Primero y más importante, esto es una aplicación 100% de terceros. Nosotros no la codeamos, no revisamos el código fuente, y no tenemos casi conocimiento del código, el modelo de datos, y ningún otro aspecto de la aplicación.

WordPress es un software de código abierto expuesto tanto para vulnerabilidades descubiertas por hackers como ataques a la cadena de suministro.

Para hacerlo peor, los sitios de WordPress casi siempre usan plug-ins. Mucho del poder de una plataforma de WordPress está en su extensibilidad por las decenas de miles de plugins que existen. Los plugins significativamente expanden la superficie del área de la tercera parte del software y ataques a la cadena de suministro. Plug-ins significan un montón de código adicional desconocido de vendors o desarrolladores múltiples, diferentes estándares de código, mejoras al modelo de datos con más tablas, y más.

SQL dinámico

WordPress lleva el concepto de SQL dinámico a un extremo. No solo todos los SQLs embebidos literales, sino que los SQLs a menudo se generan dinámicamente con cláusulas creadas sobre la marcha en función de la entrada del usuario.

El desafío no es solo los dinámicos ataques SQL pero también el gran vocabulario SQL donde es imposible predecir todas las combinaciones SQL que WordPress puede generar.

De nuevo, plug-ins empeoran el problema debido a un todavía más grande y desconocido vocabulario SQL, y más potenciales vulnerabilidades en el código que genera SQLs dinámicos.

Popular & De cara a Internet

WordPress es muy popular y es una aplicación orientada a Internet. Hackers son, por lo tanto, altamente incentivados para encontrar vulnerabilidades. Explotar estas vulnerabilidades puede ser también fácil desde que Internet está lleno de sitios en WordPress con poca o no adicional seguridad.

Ideas finales

A pesar de estos desafíos, los análisis de anomalía de Core Audit proveen seguridad efectiva así como tiene un razonable nivel de falsos positivos y, en este caso, ha sido alertado de un ataque contra la aplicación.

Es importante notar que Core Audit no tiene un soporte especial para PHP o WordPress. Tampoco miramos ninguna vulnerabilidad en particular en la base de datos o la aplicación. Solo teníamos un objetivo general de detección de anomalía que nos alertó tan pronto como la aplicación se comportó diferente. Esto fue suficiente para identificar un ataque.

Como has visto, monitorear cambios en el perfil de la actividad de la base de datos de la aplicación nos permite detectar muchos cambios en el comportamiento de la aplicación. Mientras algunos cambios ocurren naturalmente, otros indican un problema de seguridad o un ataque.