基于 Python Flask 的 Web 应用,用于解析和可视化 fscan 内网扫描器的输出结果。支持拖拽上传、粘贴文本、自动去重合并、两种浏览模式。
| 层级 | 技术 | 说明 |
|---|---|---|
| 后端 | Flask 3.1 | Web 框架 |
| ORM | SQLAlchemy 3.1 | 数据库操作 |
| 数据库 | SQLite(文件型) | 单文件存储,instance/app.db |
| 认证 | Flask-Login 0.6 | 会话管理 + RBAC |
| 迁移 | Flask-Migrate 4.0 | 数据库版本管理 |
| 前端 | Jinja2 + 原生 JS | 无构建工具,零依赖 |
| 测试 | Pytest 8.x | 单元测试 |
为什么用 SQLite? 无需安装 MySQL/PostgreSQL,开箱即用。数据库是单个文件 instance/app.db,备份只需复制这个文件。SQLAlchemy 抽象了引擎层,后续如需迁移到 PostgreSQL,只改一行连接字符串。
fscan_parse/
├── app.py # 入口
├── config.py # 配置类(开发 / 测试)
├── requirements.txt # 5个依赖
├── result.txt # fscan 扫描样本(用于测试)
│
├── fscan_parse/ # 主包
│ ├── __init__.py # create_app() 工厂函数
│ ├── auth/ # 认证蓝图
│ │ ├── routes.py # 登录 / 登出 / 注册
│ │ └── decorators.py # admin_required 装饰器
│ ├── projects/ # 项目蓝图
│ │ └── routes.py # CRUD + 上传 + 视图 + API
│ ├── admin/ # 管理蓝图
│ │ └── routes.py # 用户管理 + 全部项目
│ ├── parser/ # fscan 解析器
│ │ └── __init__.py # parse_text() + 10种分类正则
│ ├── models/ # 数据模型
│ │ ├── user.py # 用户(UserMixin)
│ │ ├── project.py # 项目
│ │ ├── upload.py # 上传记录
│ │ └── scan_result.py # 扫描结果(含 unique_key 去重)
│ ├── templates/ # Jinja2 模板
│ │ ├── base.html # 基础布局 + 导航栏
│ │ ├── auth/ # 登录 / 注册
│ │ ├── projects/ # 列表 / 创建 / 详情 / 上传
│ │ ├── dashboard/ # 模式一:分类卡片网格
│ │ ├── explorer/ # 模式二:左侧导航 + 数据表格
│ │ └── admin/ # 用户管理 / 全部项目
│ ├── static/
│ │ ├── css/style.css # 暗色主题(GitHub 风格)
│ │ └── js/
│ │ ├── upload.js # 拖拽上传 + 文件列表管理
│ │ └── explorer.js # 侧栏导航 + 复制IP + 前往URL
│ └── utils/helpers.py # 工具函数
│
└── tests/
├── conftest.py # Pytest 夹具
└── test_parser.py # 解析器单元测试(12条)
# 1. 安装依赖
pip install -r requirements.txt
# 2. 初始化数据库 + 创建管理员
export FLASK_APP=app.py
flask init-admin
# 3. 启动
flask run --host=0.0.0.0打开 http://127.0.0.1:5000,默认管理员:
| 用户名 | 密码 |
|---|---|
admin |
admin123 |
管理员登录后可创建普通用户账号。
- 管理员:可创建/编辑/删除用户,查看所有用户的项目和数据
- 普通用户:只能管理自己的项目
- 首页未登录自动跳转到登录页
- 创建项目(名称 + 描述)
- 上传扫描结果,支持两种方式:
- 拖拽上传:支持多选
.txt文件,拖到上传区域即可 - 粘贴文本:直接将 fscan 输出粘贴到文本框
- 拖拽上传:支持多选
- 删除项目(级联删除所有关联数据)
- 项目详情页显示统计:总结果数、独立主机数、上传次数、分类数
- 文件级:SHA-256 校验 — 完全相同的文件内容不会重复解析
- 结果级:每条结果有
unique_key,数据库层面 UNIQUE 约束 - 合并逻辑:同一项目上传第二个扫描文件 → 已有结果跳过,新主机/新端口/新漏洞自动补入
/projects/<id>/dashboard
- 全屏分类卡片网格布局,每种扫描结果类型一个卡片
- 卡片显示前 N 条预览,超出可滚动
- 颜色编码:
- 🔴 红色 — 漏洞类(MS17-010、DoublePulsar、SmbGhost)
- 🟡 黄色 — 凭据类(MySQL、MSSQL 等弱口令)
- 🟢 绿色 — Web 类(网站标题)
- 🔵 蓝色 — 信息类(主机信息、网卡信息)
- 🟣 紫色 — 开放端口
/projects/<id>/explorer
- 左侧导航栏(260px)— 动态生成有数据的分类列表,每项显示数量标签
- 右侧主内容区 — 点击分类加载对应数据表格
- 表格根据分类类型显示不同列
- 复制功能:
- 点击 IP / URL → 复制到剪贴板
- 「一键复制全部」按钮 → 复制当前分类所有 IP
- 前往按钮 — 网站标题类型每行显示"前往 →"按钮,新窗口打开 URL
User (1) ──→ (N) Project (1) ──→ (N) Upload
│
└──→ (N) ScanResult
users(用户表)
| 列 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | 主键 |
| username | VARCHAR(64) UNIQUE | 用户名 |
| password_hash | VARCHAR(256) | Werkzeug 加密哈希 |
| role | VARCHAR(16) | admin / user |
| is_active | BOOLEAN | 是否启用 |
| created_at | DATETIME | 创建时间 |
projects(项目表)
| 列 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | 主键 |
| name | VARCHAR(128) | 项目名 |
| description | TEXT | 描述 |
| user_id | INTEGER FK | 所属用户 |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
uploads(上传记录表)
| 列 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | 主键 |
| project_id | INTEGER FK | 所属项目 |
| filename | VARCHAR(256) | 原始文件名 |
| raw_content | TEXT | 原始文本 |
| content_checksum | VARCHAR(64) | SHA-256 校验 |
| uploaded_at | DATETIME | 上传时间 |
scan_results(扫描结果表)
| 列 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | 主键 |
| project_id | INTEGER FK | 所属项目 |
| category | VARCHAR(32) INDEX | 分类名 |
| ip_address | VARCHAR(45) INDEX | IP 地址 |
| port | INTEGER | 端口号 |
| data_json | TEXT | JSON 数据 |
| unique_key | VARCHAR(256) | 去重键(project_id + unique_key 唯一约束) |
| created_at | DATETIME | 创建时间 |
data_json 按分类存储不同结构:
// open_port
{"ip": "192.168.5.26", "port": 445}
// ms17_010
{"ip": "192.168.5.11", "os_version": "Windows Server 2008 R2 Standard 7601 SP1"}
// web_title
{"url": "http://192.168.5.11", "code": 200, "len": 7, "title": "XMAM HD"}
// mysql_cred
{"protocol": "mysql", "ip": "192.168.5.11", "port": 3306, "username": "root", "password": ""}
// net_info
{"ip": "192.168.5.26", "interfaces": ["MDC-2", "10.10.10.2", "192.168.5.25", ...]}| 分类 | fscan 输出格式 | 去重键 |
|---|---|---|
open_port |
IP:PORT open |
open_port|{ip}|{port} |
ms17_010 |
[+] IP MS17-010 (OS版本) |
ms17_010|{ip} |
doublepulsar |
[+] IP has DOUBLEPULSAR SMB IMPLANT |
doublepulsar|{ip} |
smbghost |
[+] IP CVE-2020-0796 SmbGhost Vulnerable |
smbghost|{ip} |
web_title |
[*] WebTitle:URL code:N len:N title:X |
web_title|{url} |
host_info |
[*] IP WORKGROUP\HOST OS版本 |
host_info|{ip} |
net_info |
NetInfo: 多行块 |
net_info|{ip} |
mysql_cred |
[+] mysql:IP:PORT:user pass |
mysql_cred|{ip}|{port} |
mssql_cred |
[+] mssql:IP:PORT:user pass |
mssql_cred|{ip}|{port} |
*_cred |
[+] 协议:IP:PORT:user pass |
{proto}_cred|{ip}|{port} |
最后一项是通用凭据解析,自动匹配 ssh、ftp、redis 等任何 [+] 开头的凭据行。
pytest tests/ -v
# 12 passed测试数据使用项目根目录的 result.txt(真实的 fscan 输出)。
| 路由 | 方法 | 说明 |
|---|---|---|
/auth/login |
GET,POST | 登录 |
/auth/logout |
GET | 登出 |
/auth/register |
GET,POST | 创建用户(管理员) |
/projects/ |
GET | 项目列表 |
/projects/create |
GET,POST | 创建项目 |
/projects/<id>/ |
GET | 项目详情 |
/projects/<id>/upload |
GET,POST | 上传文件/粘贴文本 |
/projects/<id>/delete |
POST | 删除项目 |
/projects/<id>/dashboard |
GET | 模式一视图 |
/projects/<id>/explorer |
GET | 模式二视图 |
/projects/<id>/api/category/<name> |
GET | AJAX:获取分类数据 (JSON) |
/projects/<id>/api/summary |
GET | AJAX:获取分类统计 (JSON) |
/admin/users |
GET | 用户列表 |
/admin/users/create |
GET,POST | 创建用户 |
/admin/users/<id>/edit |
GET,POST | 编辑用户 |
/admin/users/<id>/delete |
POST | 删除用户 |
/admin/projects |
GET | 所有项目 |
flask init-admin # 创建默认管理员 (admin/admin123)
flask db migrate -m "xxx" # 生成数据库迁移
flask db upgrade # 执行迁移SQLite 数据库是单文件,直接复制即可:
cp instance/app.db instance/app.db.bak.$(date +%Y%m%d)- 开发环境:
flask run自带热重载,适合本地使用 - 生产环境:用
gunicorn或waitress(Windows)替代 Flask 开发服务器 - 更换数据库:修改
config.py中SQLALCHEMY_DATABASE_URI,指向 MySQL/PostgreSQL 连接字符串即可 - SECRET_KEY:生产环境务必通过环境变量设置随机密钥