Moodle – Cronología de una invasión

En 2019, una institución educativa de la que nos reservamos la identidad, comenzó una nueva etapa al implementar su plataforma de E-Learning basada en Moodle. Poco después sobrevino la pandemia mundial de coronavirus que todos conocemos, y quiso la casualidad que su organización estuviera preparada para atender al alumnado de la manera adecuada justo en el momento en que se dio la necesidad de priorizar las actividades remotas y la sana distancia. Seis años después, con la vida común ya normalizada, la impartición de educación a distancia siguió y sigue siendo una parte importante dentro de su oferta de servicios.

Comienzan los problemas

Dado que la plataforma era usada de manera cotidiana sin contratiempos, nadie cayó en cuenta de que Moodle es un sistema que debe ser atendido y mantenido, sobre todo si tomamos en cuenta que este sistema tiene salida a Internet. Después de un tiempo, la falta de mantenimiento se hizo notar.

Uno de los primeros síntomas fue que, inesperadamente, la plataforma comenzó a mostrarle a los administradores una pantalla de actualización del sistema desde la que no se podía avanzar. Esto, sin embargo, no ocurría con el acceso de los alumnos, quienes podían acceder a sus interfases y recursos de aprendizaje con normalidad. Fue entonces cuando fueron requeridos los servicios de Multimedia Efectiva con el fin de diagnosticar el error y conseguir el funcionamiento correcto de Moodle.

Los primeros pasos

Una vez que contamos con las llaves de la plataforma y del hospedaje, se procedió a revisar la instalación con el fin de levantar un diagnóstico y proponer una solución. Dos de las principales reglas de la administración de sistemas son asegurar el respaldo de la información y mantener el ‘Status Quo‘ del sistema, esto quiere decir dejar las cosas sin cambios mientras se revisan con el fin de no introducir más variables que alteren las condiciones actuales o puedan ofuscar el origen de las desviaciones.

Lo primero que notamos al ingresar es que no había respaldos de ningún tipo, en ningún lugar. Un desastre en potencia latía cada minuto en ese servidor, ya que no había ninguna red de seguridad para el trabajo de años en caso de un incidente catastrófico. Dado que la plataforma estaba instalada en una cuenta de cPanel se procedió a generar y descargar un backup completo desde la cuenta de administración de WHM nada más llegar.

Asegurado el respaldo, el siguiente paso fue replicar el error. Pudimos comprobar que al acceder como alumno no teníamos ningún inconveniente, pero al intentar acceder como administrador se nos presentaba la pantalla de actualización. La pantalla de actualización se presenta cuando Moodle detecta un cambio de versión de alguno de sus componentes o la instalación de un componente nuevo. Al preguntar al personal de la organización sobre los cambios más recientes que hubieran podido aplicar a la plataforma nos confirmaron que ellos de su parte no habían instalado nada.

En conclusión, al parecer nada había cambiado, simplemente un día la pantalla apareció y no dejaba avanzar a los administradores más allá del botón “Continuar”, entonces, ¿porqué está pasando esto? ¿desde cuando? ¿cómo afecta a la información guardada?

El entorno

Siempre es necesario conocer el terreno que vas a pisar con el fin de llevar los zapatos adecuados. Después de revisar la información mediante los accesos disponibles pudimos determinar que el ambiente donde el Moodle de la organización corría tenía las siguientes características:

  • Sistema operativo: Linux Centos
  • Base de datos: Mysql
  • Servidor web: Apache
  • Gestor de hospedaje: cPanel
  • Gestor de PHP: PHP-FPM (FastCGI Process Manager)
  • Versión de Moodle: 3.7.9
  • Versión de PHP: 7.1
  • Versión de base de datos: mysql 5.7
  • Nivel de acceso al servidor: root

Un gran poder

Tener acceso root al sistema quiere decir que podemos hacer todo, sin restricciones. Como diría el clásico, “Un gran poder conlleva una gran responsabilidad“, así que hablamos con el cliente y firmamos un acuerdo de confidencialidad para poder continuar las pesquisas usando su acceso root.

Localizando el problema

Con la información en mano, procedimos a cerrar el acceso a la plataforma e investigar usando el gran poder que da ser usuario root mediante La Terminal, ese cuadro negro que sólo admite texto de entrada y devuelve más texto como respuesta, algo así como lo que ven los actores de la películas de la saga de Matrix, pero sin tantas letras verdes. La gran mayoría del contenido que un usuario normal ve en su navegador puede ser manejado desde la terminal de una manera sumamente veloz y eficiente.

Aviso
para el lector

A partir de este punto nos pondremos ‘todavía un poco más técnicos’. Aunque hemos querido simplificar las cosas, no hay manera de abordar el upgrade de un sistema como Moodle sin ensuciarse un poco las manos. Sin embargo, tenemos la esperanza de que esta información pueda ser útil para que otros administradores de sistemas puedan mantener y migrar sus instalaciones de Moodle de manera exitosa en caso de que se enfrenten con un problema parecido.

Versión de Moodle

Sabiendo que la pantalla de actualización de Moodle también puede ser mostrada por una discrepancia entre la versión instalada en el disco duro vs la versión instalada en la base de datos, procedimos a comprobar si las versiones coincidían. A partir de ahora representaremos a La Terminal como una pantalla de fondo negro con la finalidad de mostrar una experiencia lo más cercana a la que tuvimos. Las lineas con el símbolo # y texto de color rosa son comentarios , las lineas con el símbolo $ y texto de color azul son comandos , el texto blanco es la respuesta a los comandos .

Tenga en cuenta: Algunos comandos y salidas de La Terminal se han simplificados con fines de comprensión.

Bueno, la versión de los archivos es igual a la de la base de datos, entonces la pantalla de actualización no fue provocada por una discrepancia de versiones, eso significa que hay que buscar en otro lado.

Examinando la salida

En el navegador, la pantalla de actualización mostraba el botón “Continuar”, lo que nos conducía a una típica pantalla de error “No encontrado”. Las páginas de Internet tienen códigos que le dicen al navegador qué ha sucedido con sus solicitudes. Sabemos que cuando un contenido no es localizado el navegador recibe un código 404. Sin embargo, pese a que veíamos el clásico texto “Not Found”, revisando a fondo la respuesta del navegador notamos que en realidad estaba recibiendo un código 200, que significa que la petición ha sido atendida y el contenido ha sido enviado exitosamente al navegador. Esto no tenía sentido.

La interfase CLI de Moodle

Dato importante: En una computadora o servidor suele haber varios usuarios corriendo diferentes versiones de PHP en diferentes carpetas de su propiedad. Si no queremos que los ajustes que vamos a aplicar rompan más el sistema en lugar de arreglarlo, es necesario que dichos ajustes se apliquen como el “propietario” de Moodle y con la versión de PHP adecuada.

El nombre del usuario propietario de Moodle es ‘moodle_creator’, la versión de PHP que este Moodle utiliza es 7.1, así que debemos correr los comandos de la CLI de Moodle a través de ‘/binario/php71’. Moodle tiene su propia interfase de comandos, o CLI, por lo que usarla nos daría un acceso de cirujano al sistema. Ya que la actualización en el navegador se detenía con un mensaje de “Not Found”, intentamos entonces actualizar la instalación desde la CLI de Moodle. Estábamos listos para pasar al siguiente nivel usando comandos de Moodle en la terminal con el usuario y el comando adecuados.

La salida ‘0’ de la comprobación de errores de PHP indica que la actualización de Moodle mediante el script upgrade.php finalizó con éxito, lo cual no puede ser posible. ¿Cómo ha finalizado con éxito sin hacer nada? Nada tiene sentido…

En este punto nos preguntamos si tal vez convendría parchar el sistema, simplemente sobreescribir los archivos de Moodle con una versión limpia, así la institución podría continuar con sus labores. Pero el saber como, dónde, cuando y porqué se producen las desviaciones son temas que deben conocerse para mantener un sistema sano después de recuperarse de un error. No basta con reinstalar un respaldo o sobreescribir el código para que todo funcione como antes, es importante corregir la desviación y además mitigar las causas que la provocaron. Así que no, aún no. Aquí hay un culpable, debemos hallarlo antes de quemar las naves.

Jugando al escondite

Hablando de arquitectura de software, Moodle tiene un enfoque modular que separa los datos, los archivos y las funcionalidades. Los datos se colocan en una base de datos, los archivos en una carpeta y las funcionalidades en otra. De esta manera, los datos de un alumno (nombre, correo, contraseña) están guardados en la base de datos, las tareas que el alumno ha enviado en formato PDF a la plataforma estarían almacenadas en la carpeta de archivos llamada ‘moodledata/’, y los programas que hacen que todo interactúe, se conecte y se visualice se guarda en la carpeta principal, que en este caso hemos nombrado como ‘moodle/’.

De toda esta información, sólo la carpeta moodle/ no cambia con el uso. Si tienes dos instalaciones de Moodle en lugares diferentes, pero con la misma versión y los mismos agregados como temas o actividades, la carpeta principal debería ser igual en ambas instalaciones. Teniendo en cuenta lo anterior, bajamos a una computadora el respaldo del sitio web en problemas y una copia limpia de Moodle desde la web oficial, luego comparamos las carpetas donde se guardan los programas, a ver qué hallamos.

Un ¡Eureka! que no emociona

Los archivos que son parte de Moodle comienzan con un encabezado que los identifica y suele decir algo como “This file is part of Moodle”. El archivo anon2.php que hemos examinado con el comando head no tiene ese encabezado, y de hecho se pone un poco más feo: Si juntamos las palabras sueltas que deberían formar el título se forma la frase ‘AnonSec Shell’, una búsqueda rápida en Google nos dice que eso no es nada bueno. La revisión de las otras carpetas y sus archivos ya no deja lugar a dudas, la instalación de Moodle ha sido comprometida.

Cerrando el cerco

El usuario reportó que no pudo entrar el 12 de mayo. Antes de esa fecha entraba sin problemas. Sabemos que no instaló nada, si acaso habrá subido contenido, pero eso alteraría la carpeta moodledata o la base de datos, no la instalación principal de Moodle. Vayamos un poco atrás, al punto donde intentábamos ingresar como administradores y se presentaba la pantalla de actualización del sistema.

Cuando le dimos click en “Continuar” desde el navegador, obtenemos un texto “Not found” pero un código de éxito 200. Además, si intentamos actualizar desde la CLI de Moodle obtuvimos de PHP un estado de error cero (0) pero sin generar ninguna otra salida, lo que quiere decir que el proceso terminó de forma limpia y exitosa. Si hubiera un error en la actualización de Moodle, su CLI nos indicaría un error.

Tenemos dos banderas (el código 200 del navegador y el valor cero de PHP) que nos dejan bien en claro que el código terminó bien… ¿Y si el texto “Not Found” no es generado por un error en el navegador, sino por algo dentro de Moodle que si termina bien?

Aquí está el resultado exitoso con cara de error. En la linea 155 el script malicioso detecta si el visitante es el robot de un buscador (Google, Yandex, etc.), entonces se esconde generando un error 404 y detiene la ejecución con la orden exit. En la linea 512 genera una falsa pantalla de error 404, pero al hacerlo el código que nos envía es 200. Ahora todo tiene sentido.

Conforme a la documentación oficial para la versión 3.7(1), cada inicio de Moodle mapea el entorno físico y la base de datos en busca de cambios. Durante este mapeo llegó hasta donde estaban los intrusos en busca de los datos para verificar si deberían ser actualizados. Dado que algunos de los exploits introducidos no están hechos para ser integrados con Moodle (afortunadamente), el mapeo llega a la parte del código que dispara el código 200 y ‘muere’.

El proceso de actualización llama al archivo comprometido lock.php, y ese mismo archivo detiene el flujo de actualización con el falso 404.

Un examen a las otras carpetas y sus contenidos nos dieron más pistas, ahora sabemos que todos los archivos comprometidos se crearon a la misma hora del 7 de mayo. Sobra decir que todas las carpetas encontradas como diferencia entre la instalación local y una copia limpia de Moodle resultaron ser contenido infectado.

Encontrando la puerta con la ayuda de la IA

Antes del advenimiento de la IA, un administrador de sistemas podría empezar a auditar la instalación de Moodle usando todos los elementos disponibles para cruzar información buscando archivos y registros creados cerca de la fecha de la infección, pero como estamos en pleno auge de la IA agéntica, por supuesto que contamos con un agente que nos puede ayudar a realizar un diagnóstico. En este caso, contamos con una instancia de Gemin CLI corriendo en el equipo local donde está guardado el respaldo. Esto nos garantiza que el Moodle en producción conservará su Status Quo sin que se le toque un pelo.

Tenemos los archivos de la infección identificados, la fecha y hora exacta en la que se produjo, un respaldo completo generado antes de comenzar a buscar el origen del error y una copia limpia de un Moodle sin instalar. Sabemos que el respaldo incluye la base de datos de Moodle, y que Moodle guarda en la base de datos un registro detallado de los eventos que suceden durante su funcionamiento. Vamos a dejar disponibles la base de datos y una lista de archivos con las fechas y horas intactas para que el agente de IA Gemini-CLI las pueda revisar.

Como ya tenemos el respaldo completo en la carpeta ‘respaldo/’, estamos listos para convocar a Gemini CLI.

En resumen, la contaminación se produjo porque el atacante pudo ingresar al sistema usando las credenciales de un alumno. Dado que Moodle no se había actualizado en 5 años pudo explotar una vulnerabilidad conocida para extraer la lista de usuarios y sus contraseñas (hashes para los puristas) y descifrarlas (con un diccionario, para los puristas también). Luego probó las contraseñas que tenía contra la lista de usuarios y encontró uno con privilegios de administrador. A partir de ahí todo fue coser y cantar para el hacker, usando su recién estrenado acceso con nivel de administrador le bastaron menos de dos segundos para subir malware de tipo ‘puerta trasera’ en Moodle.

En nuestro campo de visión estaban los registros de actividad de Moodle, pero algo que no nos esperábamos es que Gemini también usó los registros del servidor web (Apache) para su análisis. Con toda esta información Gemini CLI no sólo identificó al alumno y al administrador con las cuentas comprometidas en menos de 5 minutos, también localizó los archivos exactos que había subido y hasta un intento de vinculación con Google Search Console. Al revisar las cuentas comprometidas mediante el panel de administración de Moodle tuvimos que revisar también las políticas de contraseñas y, creo que al final, encontramos al culpable:

La seguridad de las contraseñas estaba configurada para aceptar mínimo 5 caracteres, en cualquier orden y combinación, sin obligación de usar minúsculas, mayúsculas, números o signos.

Es decir, en este Moodle, contraseñas como ‘12345’, ‘qwerty’, ‘abcde’, ‘77777’ eran permitidas. Era hora de “quemar las naves”.

Ajustes previos

Ahora que el panorama estaba completo, y que entendíamos cómo fue que se produjo la contaminación, estábamos listos para mitigarla. Estos fueron los siguientes pasos que se tomaron:

  • Se cerró el acceso a Moodle desde la terminal con admin/cli/maintenance.php –enable.
  • Se retiró la vinculación del hacker a Google Search Console.
  • En el config.php se habilitó el valor $CFG->disableupdateautodeploy = true.
  • Se localizó y eliminó a un usuario administrador no autorizado.
  • Se localizaron y borraron 42 usuarios no autorizados con rol de alumno.
  • En las políticas de contraseñas de Moodle se subió el requerimiento a mínimo 12 caracteres, con obligación de usar al menos un número, una minúscula, una mayúscula y un símbolo. Se deja como deuda técnica implementar la autorización de dos factores (2FA)*.
  • Se les cambió el password a todos los usuarios para que recuperen su acceso legítimo por medio de la función de recuperación por correo de contraseña.

* Sobre la autorización de dos factores (2FA)

Es la acción de obtener acceso a un sistema mediante dos elementos: algo que sabes (la contraseña) y algo que tienes (por ejemplo: un token numérico recibido en tu correo).

Migrando a las siguientes ramas de Moodle

Siguiendo las directrices que la documentación oficial de Moodle proporciona(2), el plan para alcanzar la rama deseada en esta instalación de Moodle constó de los siguientes pasos:

Fase 1 – Recuperar la versión actual de Moodle

La idea en esta fase es recrear la carpeta de programas de Moodle en su versión actual 3.7 a partir de archivos totalmente limpios obtenidos desde los repositorios oficiales. Se realizaron las siguientes acciones:

  • Se ubicaron los componentes que fueron integrados a la plataforma de manera legítima.
  • Se creó la carpeta “moodle-safe-3-7” y se descargaron en ella, desde las fuentes oficiales, el núcleo de Moodle 3.7 y los componentes legítimos.
  • Se copió el archivo config.php desde la carpeta “moodle” a “moodle-safe-3-7”.
  • Se renombró la carpeta en funciones “moodle” a “moodle-compromised” y se retiró del acceso público.
  • Se renombró la carpeta “moodle-safe-3-7” a “moodle” y se trasladó al acceso público.
  • Se subió la versión de PHP desde la actual 7.1 a la máxima soportada por Moodle 3.7, que es 7.3.
  • Se ejecutó la actualización de la plataforma desde la terminal con admin/cli/upgrade.php
  • Se limpiaron los cachés desde la terminal con admin/cli/purge_caches.php.
  • Se reactivó el acceso desde la terminal con admin/cli/maintenance.php –disable.
  • Se accedió a la plataforma desde el front-end.
    Se volvió a presentar la pantalla de actualización, pero esta vez permitió continuar hasta presentar la pantalla de inicio normal.
  • Se verificó el comportamiento como usuario administrador.
  • Una vez verificado de nuestra parte el comportamiento correcto como administrador, se le pidió al usuario encargado de la plataforma que verificara el funcionamiento de Moodle, tanto para administradores, profesores y alumnos.
  • ¡Éxito!. El encargado reportó que administradores, profesores y alumnos podían utilizar la plataforma sin desviaciones.
    Se ha recuperado la instalación de Moodle en la versión 3.7, que es la que tenía originalmente.

Fase 2 – Escalar a las siguientes ramas estables de Moodle

Un principio básico de las migraciones de Moodle hacia ramas superiores es que no puede ser mayor que la siguiente versión de soporte extendido (LTS o Long-term Support Release). Dado que nuestra versión de Moodle es 3.7, las siguientes versiones LTS a las que debemos escalar son 3.9 LTS, 4.1 LTS y 4.5 LTS en ese orden(3).

Moodle 3.7 a 3.9 y subsecuentes

  • Se cerró el acceso a Moodle desde la terminal con admin/cli/maintenance.php –enable.
  • Se creo un respaldo de la base de datos y de las carpetas moodledata y moodle en su estado actual (Moodle versión 3.7 y contenidos).
  • Se creó la carpeta “moodle-3-9” y se descargaron en ella, desde las fuentes oficiales, el núcleo de Moodle 3.9 y los componentes.
  • Se renombró la carpeta en funciones “moodle” a “moodle-3-7-sanitized” y se retiró del acceso público.
  • Se renombró la carpeta “moodle-3-9” a “moodle” y se trasladó al acceso público.
  • Se subió la versión de PHP desde la actual 7.3 a la máxima soportada por Moodle 3.9, que es 7.4.
  • Se ejecutó la actualización de la plataforma desde la terminal con admin/cli/upgrade.php
  • Se limpiaron los cachés desde la terminal con admin/cli/purge_caches.php.
  • Se reactivó el acceso desde la terminal con admin/cli/maintenance.php –disable.
  • Se accedió a la plataforma desde el front-end.
  • Se realizaron todas las comprobaciones para asegurar un upgrade exitoso.

A partir de aquí, seguir subiendo hasta alcanzar la versión 4.5 LTS siguió el mismo camino resumido en la lista anterior. Hubo algunos cambios relacionados con la plantilla gráfica (theme) de Moodle, dado que en la versión 4.1 LTS la plantilla sobre el que se desarrolló la identidad gráfica del sitio había cambiado, por lo que hubo que hacer ajustes en la plantilla hija. También se tuvieron que migrar los contenidos de clase H5T, dado que este formato ya es soportado de manera nativa por Moodle 4.1 LTS.

Conclusión

Haber tenido esta experiencia nos dejó un aprendizaje que agradecemos y que quisimos compartir. A la vista de los eventos y la forma en que los abordó nuestro equipo, tal vez otro enfoque, otra manera de comenzar, hubieran dado resultados más rápidamente o diferentes, no lo sabemos.

Fuentes consultadas:

1 – Moodle Docs. (24 junio 2022), “Upgrade API”, https://docs.moodle.org/dev/Upgrade_API

2 – Moodle Docs. (27 febrero 2025), “Upgrading”, https://docs.moodle.org/405/en/Upgrading

3 – Moodle Developer Resources (05 de junio de 2026), “Releases”, https://moodledev.io/general/releases

Etiquetas de esta entrada: