0xdad0/telegram-credential-harvester
GitHub: 0xdad0/telegram-credential-harvester
用于监控 Telegram 频道、自动提取泄露凭证并通过 Web 界面集中管理的安全研究与威胁情报工具。
Stars: 1 | Forks: 1
# Telegram 凭证收集器
用于监控 Telegram 频道、提取泄露凭证并通过 Web 界面进行管理的自动化系统。专为渗透测试、威胁情报和安全研究而构建。
## 免责声明
**仅适用于:**
- 授权的渗透测试活动
- 安全研究与威胁情报
- 针对自有或客户授权账号的数据泄露验证
- 具有书面授权的红队演练
滥用可能违反隐私和计算机安全法律。用户需对其使用承担全部责任。
## 架构
```
Telegram API
│
▼
TelegramDownloader
├─ scan loop (concurrent channels, incremental via last_message_id)
│ └─ pushes {channel_id, message_id, file_name} → Redis Queue
└─ worker pool (N workers, configurable)
└─ pops tasks → download → parse (Rust/Python) → PostgreSQL
Redis
├─ tch:file_queue — persistent download task queue (FIFO)
├─ tch:processed — set of channel_id:message_id already done
└─ tch:dead_letter — failed tasks (capped 500, retry via API)
Flask web app
├─ channel management
├─ credential search / export
├─ queue monitoring tab
└─ REST API
```
**技术栈:** Python 3.12 · Telethon · Flask · PostgreSQL 15 · Redis 7 · Rust(可选解析器)
## 功能特性
- 持久化 Redis 队列 — 支持重启恢复;正在处理的任务会自动重新排队
- 增量频道扫描 — 仅获取比上次扫描更新的消息(`last_message_id`)
- 并发频道扫描 — 并行扫描所有活跃频道
- 可配置的凭证模式 — 分隔符、URL 前缀窃取器格式、任意附加正则表达式
- Rust 解析器 — 原生速度的凭证提取(启用自定义模式时回退至 Python)
- 域名定位 — 仅保存匹配特定域名的凭证
- 文件大小限制 — 跳过过大的文件(默认限制为 10 GB)
- 消息文本提取 — 可选从消息正文中提取凭证(不仅限于附件)
- `FloodWait` 处理 — 在触发 Telegram 速率限制时自动休眠,并重新排队任务
- 死信队列 — 失败的任务可通过 Web UI 进行检查和重试
- 强制重新下载 — 清空数据库中的 `processed_files` 可在下次启动时触发全面重新扫描
- 批量频道导入 — 通过 POST 提交 JSON 格式的频道列表
- 统计缓存 — 后台预计算,在插入新凭证时使其失效
- Web 界面 — 搜索、导出 CSV、频道管理、队列监控
## 前置条件
- Docker & Docker Compose
- Telegram API 凭证 — https://my.telegram.org/apps
- 活跃的 Telegram 账号
## 安装说明
### 1. 克隆
```
git clone
cd telegram-credential-harvester
```
### 2. 配置
```
cp .env.prod .env
nano .env
```
最低必填字段:
```
TELEGRAM_API_ID=your_api_id
TELEGRAM_API_HASH=your_api_hash
TELEGRAM_PHONE=+1xxxxxxxxxx
POSTGRES_PASSWORD=change_this
```
### 3. 启动
```
docker-compose up -d
```
### 4. 认证
打开 http://localhost:5000 → Authentication(认证)标签页 → Send Verification Code(发送验证码) → 输入在 Telegram 上收到的验证码。
## 配置参考
所有设置均通过环境变量(`.env` 文件或 Docker Compose)进行。
### Telegram
| 变量 | 默认值 | 描述 |
|---|---|---|
| `TELEGRAM_API_ID` | — | 必填。来自 my.telegram.org |
| `TELEGRAM_API_HASH` | — | 必填。来自 my.telegram.org |
| `TELEGRAM_PHONE` | — | 必填。国际格式 |
### 数据库
| 变量 | 默认值 | 描述 |
|---|---|---|
| `POSTGRES_HOST` | `localhost` | PostgreSQL 主机 |
| `POSTGRES_PORT` | `5432` | PostgreSQL 端口 |
| `POSTGRES_DB` | `credentials_db` | 数据库名称 |
| `POSTGRES_USER` | `admin` | 数据库用户 |
| `POSTGRES_PASSWORD` | `changeme` | **请修改此项** |
### Redis
| 变量 | 默认值 | 描述 |
|---|---|---|
| `REDIS_HOST` | `localhost` | Redis 主机(在 Docker 中为 `redis`) |
| `REDIS_PORT` | `6379` | Redis 端口 |
| `REDIS_DB` | `0` | Redis 数据库索引 |
### 扫描
| 变量 | 默认值 | 描述 |
|---|---|---|
| `SCAN_INTERVAL` | `300` | 完整扫描周期之间的间隔秒数 |
| `WORKER_CONCURRENCY` | `2` | 并行下载 worker 数量 |
| `DELETE_AFTER_PROCESSING` | `true` | 解析后删除本地文件 |
| `MAX_FILE_SIZE_MB` | `10240` | 跳过大于此大小的文件(MB)。`0` = 不限制 |
### 凭证提取
| 变量 | 默认值 | 描述 |
|---|---|---|
| `CREDENTIAL_SEPARATORS` | `[":", "\|", ";", "\t", ","]` | 用于 `emailpassword` 模式的分隔符 JSON 数组 |
| `ENABLE_URL_PREFIX_PATTERN` | `false` | 启用窃取器日志格式:`https://url:email:password` |
| `EXTRACT_FROM_MESSAGE_TEXT` | `false` | 同时扫描消息正文文本(不仅限于文件附件) |
| `EXTRA_CREDENTIAL_PATTERNS` | `[]` | 原始 Python 正则表达式字符串的 JSON 数组;组 1 = 邮箱,组 2 = 密码 |
**自定义模式示例:**
```
EXTRA_CREDENTIAL_PATTERNS=["([\\w.+-]+@[\\w-]+\\.[\\w.]+)\\s*=>\\s*(.+)"]
```
### 过滤
| 变量 | 默认值 | 描述 |
|---|---|---|
| `TARGET_DOMAINS` | `` | 逗号分隔的要保存的域名(留空 = 全部)。示例:`gmail.com,company.it` |
### 应用程序
| 变量 | 默认值 | 描述 |
|---|---|---|
| `WEB_PORT` | `5000` | Web 界面端口 |
| `DOWNLOAD_PATH` | `./downloads` | 下载文件的临时目录 |
## 支持的凭证格式
默认模式(均可通过 `CREDENTIAL_SEPARATORS` 配置):
| 格式 | 示例 |
|---|---|
| `email:password` | `user@example.com:P@ssw0rd` |
| `email\|password` | `user@example.com\|P@ssw0rd` |
| `email;password` | `user@example.com;P@ssw0rd` |
| `emailpassword` | `user@example.com P@ssw0rd` |
| `email,password` | `user@example.com,P@ssw0rd` |
可选模式(通过环境变量启用):
| 格式 | 环境变量 |
|---|---|
| `https://url:email:password` | `ENABLE_URL_PREFIX_PATTERN=true` |
| `https://url \| email \| password` | `ENABLE_URL_PREFIX_PATTERN=true` |
| 消息正文文本 | `EXTRACT_FROM_MESSAGE_TEXT=true` |
编码回退链:UTF-8 → Latin-1 → CP1252 → ISO-8859-1。
## Web 界面
| 标签页 | 描述 |
|---|---|
| Authentication | Telegram 登录流程 |
| Search | 全文 + 正则表达式 + 域名搜索,批量删除,导出 CSV |
| Channels | 添加/删除/切换频道,从 JSON 批量导入 |
| Discover Channels | 列出该账号可访问的所有 Telegram 对话 |
| Queue | 实时队列监控 — 待处理文件、死信队列、自动刷新 |
| Statistics | 热门域名、各频道计数、数据库大小 |
| Settings | API 配置、数据库限制、批量删除操作 |
### 队列标签页
显示 Redis 处理 pipeline 的实时状态:
- **Files in Queue** — 等待下载的任务
- **Processed** — 自首次运行以来处理的总文件数
- **Dead-letter** — 失败的任务;点击 **Retry All** 重新排队
- **Redis Connection** — 实时连接状态
- 自动刷新开关(每 5 秒一次)
## API 参考
### 频道
| 方法 | Endpoint | 描述 |
|---|---|---|
| `GET` | `/api/channels` | 列出受监控的频道 |
| `POST` | `/api/channels` | 添加频道 `{"channel_identifier": "@name"}` |
| `DELETE` | `/api/channels/` | 移除频道 |
| `POST` | `/api/channels//toggle` | 切换活跃状态 |
| `POST` | `/api/channels//scan` | 手动扫描(将新文件加入队列) |
| `POST` | `/api/channels/bulk` | 批量导入 `[{"identifier": "...", "name": "..."}]` |
### 队列
| 方法 | Endpoint | 描述 |
|---|---|---|
| `GET` | `/api/queue/status` | `{queue_length, processed_count, dead_letter_count, redis_connected}` |
| `GET` | `/api/queue/items?limit=200` | 查看待处理任务(按时间最早的优先) |
| `GET` | `/api/queue/dead-letter?limit=50` | 检查失败的任务 |
| `POST` | `/api/queue/dead-letter/retry` | 将所有死信任务重新排队 |
### 凭证
| 方法 | Endpoint | 描述 |
|---|---|---|
| `GET` | `/api/credentials/search` | 使用 `?q=&domain=®ex=&page=&per_page=` 进行搜索 |
| `GET` | `/api/credentials/export` | 将过滤后的结果导出为 CSV |
| `DELETE` | `/api/credentials/` | 删除单个凭证 |
| `POST` | `/api/credentials/bulk-delete` | 按域名 / 频道 / 日期删除 |
### 统计
| 方法 | Endpoint | 描述 |
|---|---|---|
| `GET` | `/api/stats` | 完整统计信息(使用缓存) |
| `POST` | `/api/stats/refresh` | 强制重新计算 |
| `POST` | `/api/stats/invalidate` | 使缓存失效 |
## 批量频道导入
将通过 OSINT 发现的频道填充到 `channels_seed.json` 中,然后导入:
```
curl -X POST http://localhost:5000/api/channels/bulk \
-H "Content-Type: application/json" \
-d '[{"identifier": "-1001234567890", "name": "Label"}, {"identifier": "@channelname"}]'
```
频道发现来源:tgscan.io, telemetr.io, 威胁情报源(KELA, Hudson Rock, Flashpoint),Telegram 关键词搜索(`combo`, `logs`, `fullbase`, `stealer`, `leaks`)。
## 项目结构
```
telegram-credential-harvester/
├── docker-compose.yml # PostgreSQL + Redis + app
├── docker-compose.dev.yml # Dev stack (no app container)
├── Dockerfile
├── requirements.txt
├── channels_seed.json # Template for bulk channel import
├── .env.prod # Production config template
├── app/
│ ├── config.py # All settings with defaults
│ ├── database.py # SQLAlchemy + connection pool
│ ├── models.py # credentials, processed_files, telegram_channels, stats_cache
│ ├── queue_manager.py # Redis queue + processed-file set + dead-letter
│ ├── parser.py # Rust/Python credential parser, configurable patterns
│ ├── telegram_client.py # Telethon client, scan loop, worker pool, FloodWait
│ ├── web_app.py # Flask app, all API routes
│ ├── stats_manager.py # Stats cache management
│ ├── background_task.py # Background stats refresh (only when cache invalid)
│ ├── logger.py # Colored terminal logging
│ └── templates/
│ └── index.html # Web UI
├── lib/
│ └── credential_parser_rs/ # Rust parser (optional, ~10x faster on defaults)
├── downloads/ # Temp download directory (auto-created)
└── session/ # Telegram session files (auto-created)
```
## Docker 命令
```
# 启动
docker-compose up -d
# 日志
docker-compose logs -f app
docker-compose logs -f
# 停止
docker-compose down
# 代码更改后重新构建
docker-compose up -d --build
# Shell 访问
docker-compose exec app bash
# PostgreSQL 访问
docker-compose exec postgres psql -U admin -d credentials_db
# Redis CLI
docker-compose exec redis redis-cli
```
## 数据库
### Schema
**`credentials`** — 提取出的凭证对;基于 SHA256(`email:password`) 保证唯一性
**`processed_files`** — 每个下载的文件对应一行记录;`channel_id` + `message_id` 的唯一性防止重复处理
**`telegram_channels`** — 受管理的频道;`last_message_id` 驱动增量扫描
**`stats_cache`** — 预计算的 JSON 数据块,在插入新凭证时失效
### 实用查询
```
-- Top domains
SELECT domain, COUNT(*) AS count
FROM credentials
GROUP BY domain ORDER BY count DESC LIMIT 20;
-- Files processed per channel
SELECT channel_id, COUNT(*) AS files, SUM(credentials_found) AS creds
FROM processed_files
GROUP BY channel_id ORDER BY creds DESC;
-- Credentials discovered today
SELECT email, domain, source_channel, discovered_at
FROM credentials
WHERE discovered_at >= NOW() - INTERVAL '24 hours'
ORDER BY discovered_at DESC;
-- Duplicate check
SELECT credential_hash, COUNT(*) FROM credentials GROUP BY credential_hash HAVING COUNT(*) > 1;
```
## 强制重新下载
要从头重新处理所有文件:
```
# 选项 1:清空 processed_files 表(下次启动时触发重新下载)
docker-compose exec postgres psql -U admin -d credentials_db \
-c "TRUNCATE processed_files;"
# 选项 2:同时清空凭据
docker-compose exec postgres psql -U admin -d credentials_db \
-c "TRUNCATE credentials, processed_files RESTART IDENTITY;"
# 然后重启应用 — Redis processed 集合从空 DB 同步
docker-compose restart app
```
在启动时,应用程序会将 `processed_files` 同步到 Redis。如果表为空,Redis 集合将被清空,并且所有频道文件都会重新排队。
## 性能
| 特性 | 详情 |
|---|---|
| Rust 解析器 | 在标准模式下比 Python 快约 10 倍;当默认配置未更改时自动启用 |
| 增量扫描 | 每个频道单独追踪 `last_message_id`;每个周期仅获取新消息 |
| 并发频道 | 使用 `asyncio.gather` 并行扫描所有活跃频道 |
| CPU 卸载 | `parse_file` 和 `calculate_file_hash` 通过 `asyncio.to_thread` 在线程池中运行 |
| 批量 upsert | PostgreSQL `INSERT … ON CONFLICT DO NOTHING` — 每个文件单条语句 |
| 文件内去重 | Python 解析器在插入数据库之前对文件内的内容进行去重 |
| Redis 已处理集合 | 在每次下载前进行 O(1) 成员检查(避免在热路径上查询数据库) |
| 连接池 | `pool_size=10, max_overflow=20`(总共 30 个连接) |
| 统计缓存 | 预计算的 JSON,仅在凭证插入导致失效时才重新计算 |
## 故障排除
### 容器无法启动
```
docker-compose logs app
docker-compose logs postgres
docker-compose logs redis
```
### Telegram 连接错误
- 检查 `.env` 中的 `API_ID` 和 `API_HASH`
- 手机号格式必须包含国家代码:`+1xxxxxxxxxx`
- 删除过期的会话:`rm -rf session/*`,然后执行 `docker-compose restart app`
### Redis 连接错误
```
# 检查 Redis 是否正在运行
docker-compose exec redis redis-cli ping
# 应返回:PONG
# 查看 queue 状态
docker-compose exec redis redis-cli llen tch:file_queue
docker-compose exec redis redis-cli scard tch:processed
docker-compose exec redis redis-cli llen tch:dead_letter
```
### 数据库无法连接
```
docker-compose exec postgres pg_isready -U admin
# 核弹级选项:擦除并重新创建
docker-compose down -v
docker-compose up -d
```
### 文件未被处理
1. 确认该频道在 Channels 标签页中处于**活跃**状态
2. 检查 `/api/queue/status` 或 Queue 标签页中的队列深度
3. 检查 `/api/queue/dead-letter` 中的死信队列以获取错误消息
. 检查日志:`docker-compose logs -f app`
5. 验证文件的 MIME 类型是否为 `text/*` 或 `application/octet-stream`
### FloodWait / 速率限制
自动处理 — 客户端会休眠所需的时间并重新排队任务。无需人工干预;通过日志进行监控。
## 备份与恢复
```
# 备份
docker-compose exec postgres pg_dump -U admin credentials_db \
| gzip > backup_$(date +%Y%m%d_%H%M).sql.gz
# 恢复
gunzip -c backup_20250615_1200.sql.gz \
| docker-compose exec -T postgres psql -U admin credentials_db
```
## 安全建议
1. 更改 `POSTGRES_PASSWORD` — 切勿将其保留为 `changeme`
2. 在未经身份验证的情况下,请勿将 5000 端口暴露在互联网上
3. 使用带有 HTTP Basic Auth 或 mTLS 的反向代理(NGINX)进行远程访问
4. 使用防火墙规则限制 Docker 网络访问
5. 切勿将 `TELEGRAM_API_ID` / `TELEGRAM_API_HASH` 提交到版本控制系统中
6. Redis 默认没有密码保护 — 请绑定到 localhost 或在生产环境中使用 `requirepass`
## 许可证
按“原样”提供,不提供任何担保。请负责任地使用,并遵守当地法律。
标签:ESC4, Flask, OSINT, Python, Redis, Rust, StruQ, Telegram, 凭据收集, 可视化界面, 威胁情报, 开发者工具, 搜索引擎查询, 数据泄露, 无后门, 测试用例, 网络流量审计, 网络测绘, 请求拦截, 逆向工具, 通知系统