一个用于对手模拟的设备代码钓鱼服务器。通过设备授权许可流程捕获 Microsoft 365 OAuth 令牌。灵感来源于 GraphSpy。
警告——仅限授权使用。 本工具面向在明确书面授权下操作的渗透测试人员和红队成员。滥用可能导致违反计算机欺诈法律。
microsoft.com/devicelogin 进行身份验证。Python 后端(每个活动一个端口)+ nginx 前端 + SQLite 持久化。
main_server.py ← 统一入口点(运行此文件)
db.py ← SQLite 持久化层
html_enroll.py ← 受害者注册页面构建器
html_search.py ← 受害者认证后搜索页面构建器
css_shared.py ← 操作员界面共享 CSS
nginx_helper.py ← 自动生成并重新加载 nginx 反向代理配置
nginx.conf ← 基础 nginx 配置(包含 /etc/nginx/conf.d/*.conf)
当创建活动时设置了域名,nginx_helper.py 会自动向 NGINX_CONF(默认为 /etc/nginx/conf.d/easytokens.conf)写入一个基于域名的 server 块,并重新加载 nginx。针对该域名到达端口 80 的受害者流量会被代理到正确的 Python 服务器实例。
nginx.conf 会丢弃 Host 头不匹配已知活动的任何请求(return 444),防止意外暴露 Python 后端。NGINX_CONF='' 或 NGINX_RELOAD_CMD='' 设置为空可禁用自动 nginx 管理。docker compose up --build
Docker 暴露的端口:
| 端口 | 用途 |
|---|---|
80 | nginx——基于域名的受害者流量 |
3000 | Python——操作员界面 + 直接受害者访问 |
http://localhost:3000/http://localhost:3000/apphttp://<campaign-domain>/(通过端口 80 的 nginx 路由)捕获的令牌和日志持久化到 Docker 卷:
| 卷 | 容器内路径 | 用途 |
|---|---|---|
easytokens-data | /data/easytokens.db | SQLite 数据库 |
./logs | /logs/ | 每次请求的日志文件 |
要求: Python 3.12+,无需第三方包。
python main_server.py
php-relay/ 目录包含一个独立的 Apache/PHP 容器,作为面向受害者的分布式节点。它提供与主服务器相同的设备代码注册页面,直接轮询 Microsoft,并通过 POST /webhook 将捕获的令牌转发到主面板。
checkin.php 向主面板的 POST /relay/checkin 发送 POST 请求,使用 RELAY_SECRET 进行身份验证。面板注册该节点并返回一个 node_id。index.php),该页面发起一个新的设备代码流程。poll.php。捕获到令牌时,poll.php 将其转发到主面板的 POST /webhook,并附带 node_id 标记。在 php-relay/.env 中配置:
同时启动两个容器(推荐):
# 在 EasyTokens/ 目录下
docker compose -f docker-compose.yml -f php-relay/docker-compose.yml up --build
使用单个 -f 合并启动会使它们处于同一 Docker 网络,因此 MAIN_SERVER_URL=http://easytokens:3000 能正确解析。
访问:
| URL | 用途 |
|---|---|
http://localhost:8082/ | 中继节点受害者注册页面 |
http://localhost:3000/app/nodes | 操作员查看已注册中继节点 |
仅启动中继节点(远程面板):
cd php-relay
# 在 .env 中将 MAIN_SERVER_URL 设置为公共面板 URL
docker compose up --build
openssl rand -hex 32EasyTokens/.env 中设置 RELAY_SECRET=<secret>(通过 docker-compose.yml 被主面板读取)php-relay/.env 中设置相同的值SQLite 数据库中的两个表:
tokens —— 所有捕获的访问/刷新令牌(UPN、访问令牌、刷新令牌、ID 令牌、作用域、来源、捕获时间戳)device_codes —— 操作员发起的活动(用户代码、设备代码、验证 URL、状态、关联令牌 ID、端口、域名)MIT —— 版权所有(c)2026 Casey Smith
| 方法 | 路径 | 受众 | 描述 |
|---|
GET | / | 受害者 | 设备代码注册页面(Dunder Mifflin 主题) |
POST | /poll | 受害者(JS) | 轮询 MS 令牌端点;成功时存储令牌 |
GET | /search?s=<id> | 受害者 | 认证后的 OneDrive/邮件搜索页面 |
GET | /app | 操作员 | 仪表板——统计信息与最近捕获内容 |
GET | /app/device-codes | 操作员 | 所有活动及实时轮询状态 |
POST | /app/device-codes/new | 操作员 | 创建新的设备代码活动 |
GET | /app/tokens | 操作员 | 所有捕获的令牌 |
GET | /app/search?id=<id> | 操作员 | 针对特定令牌的 OneDrive + 邮件搜索 |
POST | /webhook | 中继节点 | 从已验证的中继节点接收令牌 |
POST | /relay/checkin | 中继节点 | 注册中继节点;返回 node_id |
| 变量 | 默认值 | 描述 |
|---|
PORT | 3000 | Python 服务器监听端口 |
PORT_MAX | 3010 | Docker 暴露的端口范围上限 |
DB_PATH | /data/easytokens.db | SQLite 数据库路径 |
LOG_DIR | /logs | 每次请求的日志目录 |
NGINX_CONF | /etc/nginx/conf.d/easytokens.conf | nginx_helper 写入生成配置的路径;设为 '' 以禁用 |
NGINX_RELOAD_CMD | nginx -s reload | 配置更改后重新加载 nginx 的 Shell 命令;设为 '' 以禁用 |
RELAY_SECRET | (未设置) | 中继节点身份验证的共享密钥。使用 openssl rand -hex 32 生成。必须与 php-relay/.env 中的 RELAY_SECRET 匹配。留空以禁用中继支持。 |
| 变量 | 默认值 | 描述 |
|---|
MAIN_SERVER_URL | http://easytokens:3000 | 主面板 URL,从中继容器内部可访问 |
RELAY_SECRET | (必需) | 必须与主面板的 RELAY_SECRET 匹配 |
RELAY_PORT | 8082 | 暴露中继节点的主机端口 |
RELAY_LABEL | 主机名 | 面板上为该节点显示的人类可读名称 |
CHECKIN_RETRIES | 10 | 放弃前的签到重试次数 |
CHECKIN_DELAY | 5 | 签到重试间隔秒数 |
NODE_ID_FILE | /tmp/relay_node_id | 签到后缓存 node_id 的路径 |
| 字段 | 值 |
|---|
| 客户端 ID | d3590ed6-52b3-4102-aeff-aad2292ab01c (Microsoft Office) |
| 资源 | https://graph.microsoft.com |
| 作用域 | openid profile email offline_access Mail.Read Files.Read |
| 设备代码 URL | https://login.microsoftonline.com/common/oauth2/devicecode |
| 令牌 URL | https://login.microsoftonline.com/Common/oauth2/token |