[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-93014":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":14,"stars30d":15,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":16,"rankGlobal":9,"rankLanguage":9,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":22,"readmeContent":23,"aiSummary":24,"trendingCount":14,"starSnapshotCount":14,"syncStatus":15,"lastSyncTime":25,"discoverSource":26},93014,"EasyTokens","secdev02\u002FEasyTokens","secdev02","Kali365 - EvilTokens Replica",null,"Python",57,8,53,0,2,43.06,"MIT License",false,"main",true,[],"2026-07-22 04:02:07","# EasyTokens\n\nA device-code phishing server for adversary emulation. Captures Microsoft 365 OAuth tokens via the [Device Authorization Grant](https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Factive-directory\u002Fdevelop\u002Fv2-oauth2-device-code) flow. Inspired by [GraphSpy](https:\u002F\u002Fgithub.com\u002FRedByte1337\u002FGraphSpy).\n\n> **Warning — Authorized Use Only.** This tool is intended for penetration testers and red teamers operating under explicit written authorization. Misuse may violate computer fraud laws.\n\n---\n\n## How It Works\n\n1. Operator creates a **device code campaign** via the operator UI.\n2. A victim-facing enrollment page (Dunder Mifflin themed) displays the user code and prompts the target to authenticate at `microsoft.com\u002Fdevicelogin`.\n3. A background poller detects when the target completes authentication and stores the captured access\u002Frefresh token in SQLite.\n4. The victim's browser is redirected to a post-auth search page.\n5. The operator can search the victim's OneDrive and email via Microsoft Graph using the captured token.\n\n---\n\n## Architecture\n\nPython backend (one port per campaign) + nginx front-end + SQLite persistence.\n\n```\nmain_server.py          ← unified entry point (run this)\ndb.py                   ← SQLite persistence layer\nhtml_enroll.py          ← victim enrollment page builder\nhtml_search.py          ← victim post-auth search page builder\ncss_shared.py           ← shared CSS for operator UI\nnginx_helper.py         ← auto-generates and reloads nginx reverse-proxy config\nnginx.conf              ← base nginx config (includes \u002Fetc\u002Fnginx\u002Fconf.d\u002F*.conf)\n```\n\n### Routes\n\n| Method | Path | Audience | Description |\n|--------|------|----------|-------------|\n| `GET` | `\u002F` | Victim | Device-code enrollment page (Dunder Mifflin theme) |\n| `POST` | `\u002Fpoll` | Victim (JS) | Polls MS token endpoint; stores token on success |\n| `GET` | `\u002Fsearch?s=\u003Cid>` | Victim | Post-auth OneDrive\u002Femail search page |\n| `GET` | `\u002Fapp` | Operator | Dashboard — stats + recent captures |\n| `GET` | `\u002Fapp\u002Fdevice-codes` | Operator | All campaigns with live poll status |\n| `POST` | `\u002Fapp\u002Fdevice-codes\u002Fnew` | Operator | Create a new device code campaign |\n| `GET` | `\u002Fapp\u002Ftokens` | Operator | All captured tokens |\n| `GET` | `\u002Fapp\u002Fsearch?id=\u003Cid>` | Operator | OneDrive + email search for a specific token |\n| `POST` | `\u002Fwebhook` | Relay node | Ingest a token from an authenticated relay node |\n| `POST` | `\u002Frelay\u002Fcheckin` | Relay node | Register a relay node; returns `node_id` |\n\n---\n\n## Nginx — Domain Campaigns\n\nWhen a campaign is created with a **domain** set, `nginx_helper.py` automatically writes a per-domain server block to `NGINX_CONF` (`\u002Fetc\u002Fnginx\u002Fconf.d\u002Feasytokens.conf` by default) and reloads nginx. Victim traffic arriving on port 80 for that domain is proxied to the correct Python server instance.\n\n- Campaigns **without** a domain are served directly on the Python port (default 3000).\n- Campaigns **with** a domain are served through nginx on port 80.\n- The base `nginx.conf` drops any request whose `Host` header does not match a known campaign (`return 444`), preventing accidental exposure of the Python backend.\n- Set `NGINX_CONF=''` or `NGINX_RELOAD_CMD=''` to disable automatic nginx management.\n\n---\n\n## Quick Start (Docker)\n\n```bash\ndocker compose up --build\n```\n\nPorts exposed by Docker:\n\n| Port | Purpose |\n|------|---------|\n| `80` | nginx — domain-based victim traffic |\n| `3000` | Python — operator UI + direct victim access |\n\n- Victim page (direct): `http:\u002F\u002Flocalhost:3000\u002F`\n- Operator UI: `http:\u002F\u002Flocalhost:3000\u002Fapp`\n- Domain campaigns: `http:\u002F\u002F\u003Ccampaign-domain>\u002F` (routed through nginx on port 80)\n\nCaptured tokens and logs are persisted to Docker volumes:\n\n| Volume | Path in container | Purpose |\n|--------|------------------|---------|\n| `easytokens-data` | `\u002Fdata\u002Feasytokens.db` | SQLite database |\n| `.\u002Flogs` | `\u002Flogs\u002F` | Per-request log files |\n\n---\n\n## Manual Setup\n\n**Requirements:** Python 3.12+, no third-party packages.\n\n```bash\npython main_server.py\n```\n\n---\n\n## Environment Variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `PORT` | `3000` | Listening port for the Python server |\n| `PORT_MAX` | `3010` | Upper bound of port range exposed by Docker |\n| `DB_PATH` | `\u002Fdata\u002Feasytokens.db` | SQLite database path |\n| `LOG_DIR` | `\u002Flogs` | Per-request log directory |\n| `NGINX_CONF` | `\u002Fetc\u002Fnginx\u002Fconf.d\u002Feasytokens.conf` | Path nginx_helper writes generated config to; set to `''` to disable |\n| `NGINX_RELOAD_CMD` | `nginx -s reload` | Shell command to reload nginx after config changes; set to `''` to disable |\n| `RELAY_SECRET` | *(unset)* | Shared secret for relay node authentication. Generate with `openssl rand -hex 32`. Must match `RELAY_SECRET` in `php-relay\u002F.env`. Leave unset to disable relay support. |\n\n---\n\n## PHP Relay Node\n\nThe `php-relay\u002F` directory contains a standalone Apache\u002FPHP container that acts as a distributed victim-facing node. It serves the same device-code enrollment page as the main server, polls Microsoft directly, and forwards captured tokens to the main panel via `POST \u002Fwebhook`.\n\n### How It Works\n\n1. On startup, `checkin.php` POSTs to `POST \u002Frelay\u002Fcheckin` on the main panel, authenticating with `RELAY_SECRET`. The panel registers the node and returns a `node_id`.\n2. Victims browse to the relay's enrollment page (`index.php`), which initiates a fresh device-code flow.\n3. Browser-side JS polls `poll.php`. When a token is captured, `poll.php` forwards it to `POST \u002Fwebhook` on the main panel, tagged with the `node_id`.\n\n### Relay Environment Variables\n\nConfigure in `php-relay\u002F.env`:\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `MAIN_SERVER_URL` | `http:\u002F\u002Feasytokens:3000` | URL of the main panel reachable from inside the relay container |\n| `RELAY_SECRET` | *(required)* | Must match `RELAY_SECRET` on the main panel |\n| `RELAY_PORT` | `8082` | Host port to expose the relay on |\n| `RELAY_LABEL` | hostname | Human-readable name shown in the panel for this node |\n| `CHECKIN_RETRIES` | `10` | Check-in retry attempts before giving up |\n| `CHECKIN_DELAY` | `5` | Seconds between check-in retries |\n| `NODE_ID_FILE` | `\u002Ftmp\u002Frelay_node_id` | Path where the node_id is cached after check-in |\n\n### Quick Start\n\n**Both containers simultaneously (recommended):**\n\n```bash\n# from EasyTokens\u002F\ndocker compose -f docker-compose.yml -f php-relay\u002Fdocker-compose.yml up --build\n```\n\nRunning both with a single `-f` merge puts them on the same Docker network, so `MAIN_SERVER_URL=http:\u002F\u002Feasytokens:3000` resolves correctly.\n\n**Access:**\n\n| URL | Purpose |\n|-----|---------|\n| `http:\u002F\u002Flocalhost:8082\u002F` | Relay victim enrollment page |\n| `http:\u002F\u002Flocalhost:3000\u002Fapp\u002Fnodes` | Operator view of registered relay nodes |\n\n**Relay only (remote panel):**\n\n```bash\ncd php-relay\n# Set MAIN_SERVER_URL to the public panel URL in .env\ndocker compose up --build\n```\n\n### Shared Secret Setup\n\n1. Generate a secret: `openssl rand -hex 32`\n2. Set `RELAY_SECRET=\u003Csecret>` in `EasyTokens\u002F.env` (picked up by the main panel via `docker-compose.yml`)\n3. Set the same value in `php-relay\u002F.env`\n\n---\n\n## Database\n\nTwo tables in the SQLite database:\n\n- **`tokens`** — every captured access\u002Frefresh token (UPN, access token, refresh token, id token, scope, source, capture timestamp)\n- **`device_codes`** — operator-initiated campaigns (user code, device code, verification URL, status, linked token ID, port, domain)\n\n---\n\n## Microsoft OAuth Details\n\n| Field | Value |\n|-------|-------|\n| Client ID | `d3590ed6-52b3-4102-aeff-aad2292ab01c` (Microsoft Office) |\n| Resource | `https:\u002F\u002Fgraph.microsoft.com` |\n| Scopes | `openid profile email offline_access Mail.Read Files.Read` |\n| Device code URL | `https:\u002F\u002Flogin.microsoftonline.com\u002Fcommon\u002Foauth2\u002Fdevicecode` |\n| Token URL | `https:\u002F\u002Flogin.microsoftonline.com\u002FCommon\u002Foauth2\u002Ftoken` |\n\n---\n\n## License\n\nMIT — Copyright (c) 2026 Casey Smith\n","EasyTokens 是一个用于红队对抗演练的设备码钓鱼服务器，通过模拟 Microsoft 365 设备授权流程（Device Authorization Grant）诱使目标用户在 microsoft.com\u002Fdevicelogin 输入设备代码，从而捕获其 OAuth 访问令牌与刷新令牌。项目采用 Python 后端 + nginx 反向代理 + SQLite 存储架构，支持多域名分 Campaign 部署、实时轮询检测认证完成、以及基于捕获令牌调用 Microsoft Graph 查询 OneDrive 和邮件数据。适用于经授权的渗透测试与红队演练场景，尤其适合评估组织对设备码钓鱼攻击的防御能力。","2026-07-11 02:30:40","CREATED_QUERY"]