Cómo proteger el panel administrativo de tu servidor de MU Online con autenticación en dos pasos (2FA)
Implementa autenticación en dos pasos (2FA) basada en TOTP en el panel administrativo PHP de tu servidor de MU Online, con generación de secreto, código QR, verificación de código y un plan de recuperación de acceso en caso de que el jugador pierda el dispositivo.
El panel administrativo de un servidor de MU Online es el blanco más valioso para un atacante: una sola cuenta comprometida con nivel de Administrador puede generar ítems, alterar saldos, banear jugadores o derribar la base de datos entera. Proteger ese acceso solo con contraseña — aunque sea una co
El panel administrativo de un servidor de MU Online es el blanco más valioso para un atacante: una sola cuenta comprometida con nivel de Administrador puede generar ítems, alterar saldos, banear jugadores o derribar la base de datos entera. Proteger ese acceso solo con contraseña — aunque sea una contraseña fuerte — deja al servidor vulnerable al phishing, a la reutilización de contraseñas filtradas en otro sitio y a los keyloggers. La autenticación en dos pasos (2FA) basada en TOTP añade una segunda barrera que un atacante no puede reproducir solo con la contraseña robada. Este tutorial muestra cómo implementar el 2FA en el panel PHP legado del servidor, desde el lado de la base de datos hasta la pantalla de verificación del jugador.
Por qué la contraseña sola no es suficiente
Las contraseñas se filtran por varios caminos que no dependen de qué tan fuerte sea la contraseña en sí: reutilización de la misma contraseña en otro sitio que sufrió una filtración, phishing disfrazado de una página de login idéntica, o malware tipo keylogger en la máquina del administrador. En cualquiera de estos casos, el atacante obtiene la contraseña correcta y pasa la autenticación con normalidad. El 2FA rompe ese escenario porque exige un segundo factor — algo que el administrador tiene (el código generado en el celular), no solo algo que sabe (la contraseña).
Cómo funciona el TOTP (Time-based One-Time Password)
El TOTP genera un código numérico de 6 dígitos que cambia cada 30 segundos, calculado a partir de un secreto compartido entre el servidor y la aplicación autenticadora (Google Authenticator, Authy, Microsoft Authenticator) y de la hora actual. El servidor y el celular nunca intercambian ese código por la red en el momento de la generación — ambos calculan el mismo valor de forma independiente, lo que hace que el método sea resistente a la interceptación de red.
| Componente | Rol |
|---|---|
| Secreto compartido (secret) | Generado una vez, almacenado en la base de datos y en la app del usuario |
| Timestamp actual | Sincronizado entre servidor y dispositivo (ventana de 30s) |
| Algoritmo HMAC-SHA1 (RFC 6238) | Combina secreto + timestamp para generar el código de 6 dígitos |
| Ventana de tolerancia | Acepta el código del intervalo anterior/siguiente para tolerar una pequeña diferencia de reloj |
Paso 1 — Preparar la tabla de secretos en la base de datos
Agrega una columna para almacenar el secreto TOTP de cada cuenta administrativa, y una tabla separada para los códigos de recuperación:
ALTER TABLE admin_accounts ADD COLUMN totp_secret VARCHAR(64) NULL;
ALTER TABLE admin_accounts ADD COLUMN totp_enabled TINYINT(1) NOT NULL DEFAULT 0;
CREATE TABLE admin_recovery_codes (
id INT AUTO_INCREMENT PRIMARY KEY,
admin_id INT NOT NULL,
code_hash VARCHAR(255) NOT NULL,
used TINYINT(1) NOT NULL DEFAULT 0,
FOREIGN KEY (admin_id) REFERENCES admin_accounts(id)
);
Nunca almacenes el secreto TOTP en texto plano accesible públicamente, y nunca almacenes los códigos de recuperación sin hash — trátalos como contraseñas de un solo uso.
Paso 2 — Instalar una biblioteca TOTP en PHP
Usa una biblioteca open source consolidada en lugar de implementar el algoritmo desde cero:
composer require spomky-labs/otphp
Esto evita errores sutiles de implementación del RFC 6238 que podrían generar códigos incompatibles con las aplicaciones autenticadoras estándar del mercado.
Paso 3 — Generar el secreto y el código QR de activación
use OTPHP\TOTP;
$totp = TOTP::generate();
$totp->setLabel('PainelAdmin-ViciadosMU');
$totp->setIssuer('ViciadosMU');
$secret = $totp->getSecret();
// Guardar $secret (cifrado) en la columna totp_secret del admin conectado
$qrCodeUrl = $totp->getQrCodeUri(
'https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=[DATA]',
'[DATA]'
);
// Mostrar $qrCodeUrl en pantalla para que el admin lo escanee con la app autenticadora
Muestra el código QR solo una vez, en la pantalla de activación, y nunca registres el secreto en el log de la aplicación — es equivalente a una contraseña maestra del segundo factor.
Paso 4 — Verificar el código en la activación antes de confirmar
Antes de marcar totp_enabled = 1, exige que el administrador ingrese el código generado por la app, confirmando que el código QR se escaneó correctamente:
$codigoDigitado = $_POST['codigo'];
if ($totp->verify($codigoDigitado)) {
// Activar totp_enabled = 1 en la base de datos
// Generar y mostrar los códigos de recuperación (una única vez)
} else {
// Error: código inválido, no activar todavía
}
Este paso evita el escenario en que el administrador cree que activó el 2FA, pero la app no se configuró correctamente, y queda bloqueado en el siguiente inicio de sesión.
Paso 5 — Generar y mostrar códigos de recuperación
En el momento de la activación exitosa, genera de 8 a 10 códigos de un solo uso y muéstralos solo una vez, indicándole al administrador que los guarde fuera de línea:
function gerarCodigosRecuperacao(int $quantidade = 10): array {
$codigos = [];
for ($i = 0; $i < $quantidade; $i++) {
$codigos[] = strtoupper(bin2hex(random_bytes(4))); // ej: A1B2C3D4
}
return $codigos;
}
// Guardar solo el hash de cada código en la tabla admin_recovery_codes
foreach ($codigos as $codigo) {
$hash = password_hash($codigo, PASSWORD_BCRYPT);
// INSERT INTO admin_recovery_codes (admin_id, code_hash) VALUES (?, ?)
}
Paso 6 — Exigir el segundo factor en el inicio de sesión
En el flujo de login, después de validar correctamente la contraseña, muestra la pantalla de código TOTP antes de conceder la sesión administrativa completa:
if ($senhaValida) {
if ($admin['totp_enabled']) {
// Redirigir a la pantalla de verificación de código,
// sin conceder todavía la sesión de nivel administrativo
$_SESSION['pending_2fa_admin_id'] = $admin['id'];
} else {
// Conceder la sesión normalmente (o forzar la activación del 2FA, si es obligatorio)
}
}
// En la pantalla de verificación
$codigo = $_POST['codigo'];
$totp = TOTP::createFromSecret($segredoDoBanco);
if ($totp->verify($codigo)) {
// Conceder sesión administrativa completa
} else {
// Verificar si es un código de recuperación válido (coincidencia de hash y used = 0)
// Caso contrario, negar el acceso y registrar el intento
}
Paso 7 — Definir la política de obligatoriedad por nivel de cuenta
No todas las cuentas necesitan el mismo rigor. Define la política según el riesgo del nivel de acceso:
| Nivel de cuenta | ¿2FA obligatorio? | Justificación |
|---|---|---|
| Owner | Sí | Acceso total, mayor daño posible si se compromete |
| Administrador | Sí | Comandos sensibles (additem, setmoney, ban) |
| Moderador de evento | Recomendado | Acceso limitado, pero aún distribuye ítems |
| Soporte junior | Opcional | Comandos de menor impacto económico |
| Cuenta de jugador común | Opcional/incentivado | Protección de cuenta personal, no administrativa |
Paso 8 — Plan de recuperación sin comprometer la seguridad
Cuando un administrador pierde el dispositivo y no tiene los códigos de recuperación, define un proceso manual y documentado en lugar de simplemente desactivar el 2FA a partir de un pedido informal en Discord:
- Exige verificación de identidad fuera de banda (videollamada con otro administrador de confianza, o confirmación por correo registrado previamente).
- Un segundo administrador (nunca el propio solicitante) resetea
totp_enabled = 0directamente en la base de datos. - Fuerza la reactivación del 2FA de inmediato en el próximo inicio de sesión.
- Registra el incidente en el log interno, incluyendo quién autorizó el reseteo.
Errores comunes y soluciones
| Síntoma | Causa probable | Solución |
|---|---|---|
| El código de la app no coincide con el servidor | Reloj del celular o servidor desincronizado | Sincronizar NTP en el servidor y verificar la hora del celular |
| El admin perdió el acceso y no tiene códigos de recuperación | Códigos no generados o no guardados en la activación | Implementar un proceso manual de reseteo con verificación cruzada |
| Secreto TOTP expuesto en el log de la aplicación | Debug/log registrando una variable sensible | Eliminar el log de variables de secreto y rotar los secretos expuestos |
| El 2FA está activado pero la sesión nunca pide el segundo factor | El flujo de login no verifica totp_enabled correctamente | Revisar la lógica de sesión pendiente antes de conceder acceso completo |
| Reseteo de 2FA hecho sin verificación de identidad | Falta de proceso documentado de recuperación | Formalizar el proceso con un segundo administrador obligatorio |
Lista de verificación de implementación de 2FA en el panel
- Tabla de secreto TOTP y códigos de recuperación creada en la base de datos.
- Biblioteca TOTP confiable integrada (ej.: spomky-labs/otphp).
- Flujo de activación con código QR y verificación de código antes de confirmar.
- Códigos de recuperación generados, con hash y mostrados una única vez.
- Inicio de sesión exigiendo el segundo factor antes de conceder sesión administrativa completa.
- Política de obligatoriedad definida por nivel de cuenta.
- Proceso documentado de recuperación de acceso con verificación cruzada.
Con el panel protegido por 2FA, revisa también los demás controles de acceso del equipo administrativo — la guía de creación de servidor de MU Online cubre la base de infraestructura sobre la que debe construirse esta capa extra de seguridad.
Preguntas frecuentes
¿El 2FA por SMS es lo suficientemente seguro para el panel de admin?
Es mejor que nada, pero el SMS es vulnerable al SIM swap y a la interceptación. Para cuentas de nivel Administrador y Owner, prefiere TOTP (Google Authenticator, Authy) o una llave física (FIDO2/WebAuthn), reservando el SMS como máximo como método secundario de recuperación.
¿Qué pasa si el administrador pierde el celular con el autenticador?
Por eso es esencial generar y almacenar códigos de recuperación (backup codes) en el momento de activar el 2FA, guardados en un lugar seguro fuera de línea. Sin esto, la única salida es un proceso manual de verificación de identidad para resetear el 2FA directamente en la base de datos.
¿El 2FA debe ser obligatorio para todos los niveles de cuenta del panel?
Se recomienda que sea obligatorio para los niveles con comandos administrativos sensibles (Admin, Owner) y opcional, pero incentivado, para cuentas comunes de jugador. Hacerlo obligatorio para todos de una vez puede generar fricción de soporte si la comunidad no está preparada.
¿El TOTP funciona sin internet en el celular del administrador?
Sí. El algoritmo TOTP genera el código localmente a partir del secreto compartido y la hora del dispositivo, sin necesitar conexión a internet en el momento de la generación — solo el reloj del celular necesita estar sincronizado correctamente.
¿Necesito una biblioteca paga para implementar TOTP en PHP?
No. Existen bibliotecas open source como spomky-labs/otphp o implementaciones simples del algoritmo RFC 6238 que pueden integrarse gratuitamente al panel PHP existente, sin depender de un servicio externo pago.