
Kali365 - Réplica de EvilTokens
Un servidor de phishing mediante código de dispositivo para emulación de adversarios. Captura tokens OAuth de Microsoft 365 a través del flujo de Concesión de Autorización de Dispositivo. Inspirado en GraphSpy.
Advertencia — Solo uso autorizado. Esta herramienta está destinada a testers de penetración y red teamers que operan bajo autorización escrita explícita. El uso indebido puede violar las leyes contra el fraude informático.
microsoft.com/devicelogin.Backend en Python (un puerto por campaña) + front-end nginx + persistencia SQLite.
main_server.py ← punto de entrada unificado (ejecutar este)
db.py ← capa de persistencia SQLite
html_enroll.py ← generador de página de inscripción para víctimas
html_search.py ← generador de página de búsqueda posterior a la autenticación para víctimas
css_shared.py ← CSS compartido para la interfaz de operador
nginx_helper.py ← genera y recarga automáticamente la configuración del proxy inverso nginx
nginx.conf ← configuración base de nginx (incluye /etc/nginx/conf.d/*.conf)
Cuando se crea una campaña con un dominio establecido, nginx_helper.py escribe automáticamente un bloque de servidor por dominio en NGINX_CONF (por defecto /etc/nginx/conf.d/easytokens.conf) y recarga nginx. El tráfico de víctimas que llega al puerto 80 para ese dominio es redirigido a la instancia correcta del servidor Python.
nginx.conf base rechaza cualquier solicitud cuyo encabezado Host no coincida con una campaña conocida (return 444), evitando la exposición accidental del backend Python.NGINX_CONF='' o NGINX_RELOAD_CMD='' para deshabilitar la gestión automática de nginx.docker compose up --build
Puertos expuestos por Docker:
| Puerto | Propósito |
|---|---|
80 | nginx — tráfico de víctimas basado en dominio |
3000 | Python — interfaz de operador + acceso directo de víctimas |
http://localhost:3000/http://localhost:3000/apphttp://<dominio-campaña>/ (enrutado a través de nginx en el puerto 80)Los tokens capturados y los registros se persisten en volúmenes Docker:
| Volumen | Ruta en el contenedor | Propósito |
|---|---|---|
easytokens-data | /data/easytokens.db | Base de datos SQLite |
./logs | /logs/ | Archivos de registro por solicitud |
Requisitos: Python 3.12+, sin paquetes de terceros.
python main_server.py
El directorio php-relay/ contiene un contenedor Apache/PHP independiente que actúa como un nodo distribuido orientado a víctimas. Sirve la misma página de inscripción con código de dispositivo que el servidor principal, sondea Microsoft directamente y reenvía los tokens capturados al panel principal mediante POST /webhook.
checkin.php realiza un POST a POST /relay/checkin en el panel principal, autenticándose con RELAY_SECRET. El panel registra el nodo y devuelve un node_id.index.php), que inicia un flujo de código de dispositivo nuevo.poll.php. Cuando se captura un token, poll.php lo reenvía a POST /webhook en el panel principal, etiquetado con el node_id.Configurar en php-relay/.env:
Ambos contenedores simultáneamente (recomendado):
# desde EasyTokens/
docker compose -f docker-compose.yml -f php-relay/docker-compose.yml up --build
Ejecutar ambos con una sola fusión de -f los coloca en la misma red Docker, por lo que MAIN_SERVER_URL=http://easytokens:3000 se resuelve correctamente.
Acceso:
| URL | Propósito |
|---|---|
http://localhost:8082/ | Página de inscripción de víctimas del relay |
http://localhost:3000/app/nodes | Vista del operador de los nodos relay registrados |
Solo relay (panel remoto):
cd php-relay
# Establecer MAIN_SERVER_URL a la URL pública del panel en .env
docker compose up --build
openssl rand -hex 32RELAY_SECRET=<secreto> en EasyTokens/.env (lo recoge el panel principal a través de docker-compose.yml)php-relay/.envDos tablas en la base de datos SQLite:
tokens — cada token de acceso/actualización capturado (UPN, token de acceso, token de actualización, token de ID, alcance, origen, marca de tiempo de captura)device_codes — campañas iniciadas por el operador (código de usuario, código de dispositivo, URL de verificación, estado, ID del token vinculado, puerto, dominio)MIT — Copyright (c) 2026 Casey Smith
| Método | Ruta | Audiencia | Descripción |
|---|
GET | / | Víctima | Página de inscripción con código de dispositivo (tema Dunder Mifflin) |
POST | /poll | Víctima (JS) | Sondea el endpoint de tokens de Microsoft; almacena el token en caso de éxito |
GET | /search?s=<id> | Víctima | Página de búsqueda posterior a la autenticación para OneDrive/correo |
GET | /app | Operador | Panel de control — estadísticas + capturas recientes |
GET | /app/device-codes | Operador | Todas las campañas con estado de sondeo en vivo |
POST | /app/device-codes/new | Operador | Crear una nueva campaña de código de dispositivo |
GET | /app/tokens | Operador | Todos los tokens capturados |
GET | /app/search?id=<id> | Operador | Búsqueda en OneDrive + correo para un token específico |
POST | /webhook | Nodo relay | Ingresar un token desde un nodo relay autenticado |
POST | /relay/checkin | Nodo relay | Registrar un nodo relay; devuelve node_id |
| Variable | Valor por defecto | Descripción |
|---|
PORT | 3000 | Puerto de escucha del servidor Python |
PORT_MAX | 3010 | Límite superior del rango de puertos expuestos por Docker |
DB_PATH | /data/easytokens.db | Ruta de la base de datos SQLite |
LOG_DIR | /logs | Directorio de registros por solicitud |
NGINX_CONF | /etc/nginx/conf.d/easytokens.conf | Ruta donde nginx_helper escribe la configuración generada; establecer a '' para deshabilitar |
NGINX_RELOAD_CMD | nginx -s reload | Comando shell para recargar nginx después de cambios en la configuración; establecer a '' para deshabilitar |
RELAY_SECRET | (sin establecer) | Secreto compartido para la autenticación del nodo relay. Generar con openssl rand -hex 32. Debe coincidir con RELAY_SECRET en php-relay/.env. Dejar sin establecer para deshabilitar el soporte de relay. |
| Variable | Valor por defecto | Descripción |
|---|
MAIN_SERVER_URL | http://easytokens:3000 | URL del panel principal accesible desde dentro del contenedor relay |
RELAY_SECRET | (requerido) | Debe coincidir con RELAY_SECRET en el panel principal |
RELAY_PORT | 8082 | Puerto del host para exponer el relay |
RELAY_LABEL | nombre del host | Nombre legible para humanos que se muestra en el panel para este nodo |
CHECKIN_RETRIES | 10 | Intentos de registro antes de rendirse |
CHECKIN_DELAY | 5 | Segundos entre reintentos de registro |
NODE_ID_FILE | /tmp/relay_node_id | Ruta donde se almacena en caché el node_id después del registro |
| Campo | Valor |
|---|
| ID de cliente | d3590ed6-52b3-4102-aeff-aad2292ab01c (Microsoft Office) |
| Recurso | https://graph.microsoft.com |
| Ámbitos | openid profile email offline_access Mail.Read Files.Read |
| URL de código de dispositivo | https://login.microsoftonline.com/common/oauth2/devicecode |
| URL de token | https://login.microsoftonline.com/Common/oauth2/token |