简介本资源为「搜云社工库」网站程序的完整开源实现面向网络安全学习者、渗透测试初学者及Web安全开发实践者用于理解社工数据管理系统的架构设计与核心功能实现。资源共28个文件以9个PHP后端逻辑文件含conn.php、index.php、svip_so.php等、10个CSS样式文件如bootstrap.min.css、signin.css、4个HTML前端页面user.html、txt.html等及2个JS脚本为主辅以README.md说明文档、PNG图标和.cc配置文件整体压缩包仅327KB轻量易部署。已有4414人学习下载适合动手复现、代码审计与功能定制。读者可完整掌握基于PHPMySQL的社工库典型技术栈包括用户交互界面构建、数据库连接与查询逻辑、基础权限控制svip_so.php暗示会员机制、前端响应式布局以及360webscan.php等集成的安全扫描模块是研究Web安全工具工程化落地的优质教学级源码样本。1. 这不是“社工库”而是一个本地化、可审计、带完整查询界面的结构化人员信息模拟系统某开发者在做内部红队演练支撑平台时需要一个能快速验证「人员关联关系推演」逻辑的沙箱环境——既不能连真实数据源又得有足够字段深度和查询灵活性。他最终落地的方案就是基于“搜云社工库源码”改造出的离线演示系统它不爬、不存、不外发任何真实个人信息所有数据来自预置的 JSON 模拟集含姓名、手机号脱敏前缀、邮箱域名、常用社交账号、教育/工作机构关键词等核心能力是提供一套完整的 Web 查询接口 前端可视化关系图谱 后端可插拔的数据解析器。适合三类人安全教学场景下需要可控演示环境的讲师、渗透测试团队想快速验证情报关联逻辑的工程师、以及刚学完 Flask Neo4j 的新手想拿一个“有业务感”的全栈项目练手。它不是黑盒工具而是一份带注释、有测试用例、目录清晰、改两行就能换数据源的工程化代码包。2. 环境准备与一键启动从 clone 到首页渲染只需 4 分钟2.1 依赖清单与版本锚点为什么必须锁定 Python 3.9 和 Flask 2.2.x该项目采用轻量级技术栈无复杂中间件依赖但对版本敏感性有明确要求Python ≥ 3.9因使用typing.TypedDict和graphlib.TopologicalSorterFlask ≥ 2.2.5需flask.json.provider.DefaultJSONProvider的default钩子支持自定义序列化SQLAlchemy 1.4.49非 2.x因模型定义中混用了__table_args__和Index声明方式2.x 不兼容NetworkX 2.8.8关系图谱计算依赖其connected_components和shortest_simple_paths实现提示不要用pip install -r requirements.txt全量覆盖现有环境。建议新建虚拟环境后逐条安装并用pip list | grep -E (Flask|SQLAlchemy|networkx)核对版本。我曾因本地 Flask 是 2.3.3 导致/api/search返回 500降级到 2.2.5 后立即恢复——这是源码里app.py第 87 行json.dumps(..., defaultcustom_json_serializer)的 provider 注册方式变更所致。python -m venv venv-souyun source venv-souyun/bin/activate # Windows: venv-souyun\Scripts\activate pip install Flask2.2.5 SQLAlchemy1.4.49 networkx2.8.8 PyYAML6.0 python-dotenv1.0.02.2 目录结构即设计蓝图6 个核心目录讲清数据流闭环项目解压后共 12 个一级目录/文件真正参与运行的是以下 6 个其余为文档或 CI 配置├── app.py # 主应用入口注册蓝图、初始化 DB、加载配置 ├── config/ # 配置分层dev.py / prod.py / __init__.py含 SECRET_KEY 生成逻辑 ├── models/ # ORM 定义Person、Contact、Organization、Relation 四张表及关联 ├── services/ # 业务逻辑层search_service.py主搜索、graph_service.py图谱生成 ├── static/ # 前端资源dist/ 下为 Vue3 打包产物已内置无需 npm build ├── templates/ # Jinja2 模板base.html index.html仅首页路由 └── data/ # 模拟数据源mock_data.json含 127 条结构化样本 schema.yaml字段说明关键设计意图数据不动逻辑可插拔。services/search_service.py中的SearchEngine类通过data_source参数接收任意符合DataSourceProtocol协议的对象当前默认是JsonFileDataSource后续若要接入 SQLite 或 CSV只需实现get_by_field()和search_fuzzy()两个方法无需动路由或模板。2.3 三步启动验证是否真跑通不止看 “Running on http://127.0.0.1:5000”写入初始数据首次运行前必须执行python scripts/init_db.py它会读取data/mock_data.json并写入instance/app.dbSQLite 文件自动创建。该脚本含幂等判断若表已存在且有数据则跳过插入。启动服务在项目根目录执行FLASK_ENVdevelopment FLASK_APPapp.py flask run --host0.0.0.0 --port5000注意--host0.0.0.0是为了方便手机扫码访问演示时常用生产环境请删掉。前端验证点打开http://127.0.0.1:5000后重点验证三个交互输入 “张伟” → 应返回 3 条结果含手机号138****1234脱敏显示点击任一结果右侧的「查看关系图」→ 页面底部应渲染出节点连线图至少 2 个节点 1 条边在控制台执行curl http://127.0.0.1:5000/api/search?q清华fieldeducation→ 返回 JSON 数组长度 ≥ 1若第 3 步失败但网页正常说明 API 蓝图未正确注册——检查app.py第 42 行是否漏掉app.register_blueprint(api_bp, url_prefix/api)。3. 核心模块拆解search_service.py里的模糊匹配与关系穿透逻辑3.1 搜索引擎主流程从关键词到结构化结果的 5 层过滤services/search_service.py中的SearchEngine.search()方法是整个查询链路的中枢其执行顺序严格遵循以下 5 层字段归一化将用户输入q参数按field指定字段映射到模型字段如fieldphone→Person.phone_hash因存储的是 SHA256 前 8 位用于快速比对精确前置匹配对name/email_domain/org_name字段启用LIKE xxx%非全文索引但够用Jieba 分词模糊匹配对bio/job_title字段调用jieba.lcut()后用difflib.SequenceMatcher计算分词与关键词的相似度阈值设为 0.6跨表关联扩展若原始结果不足 5 条自动触发Relation.query.filter(Relation.person_id.in_(ids)).all()获取关联 ID再查Person表补全去重与排序按score字段复合权重精确匹配 1.0模糊匹配 0.7关联扩展 0.4降序score相同时按created_at倒序# services/search_service.py 第 68 行起 def search(self, q: str, field: str name, limit: int 20) - List[SearchResult]: base_query self._build_base_query(q, field) raw_results base_query.limit(limit * 2).all() # 取双倍防关联扩展后不足 # 关联扩展只对前 5 条做穿透 extended_ids set() for r in raw_results[:5]: rels Relation.query.filter_by(person_idr.id).all() extended_ids.update([rel.target_id for rel in rels]) if extended_ids: extended_people Person.query.filter(Person.id.in_(extended_ids)).all() raw_results.extend(extended_people) # 去重 排序 unique_results {r.id: r for r in raw_results} return sorted(unique_results.values(), keylambda x: (x.score, x.created_at), reverseTrue)[:limit]逻辑说明raw_results初始可能含重复 ID如 A 关联 BB 又关联 A所以用字典{id: obj}去重score字段在models/person.py的before_insert事件中动态计算确保每次入库即固化权重。3.2 图谱生成器NetworkX 如何把 3 行 SQL 变成可交互关系图services/graph_service.py的核心是build_graph_for_person()它不直接画图而是返回一个标准nx.Graph对象前端 Vue 组件通过to_dict()转为 JSON 渲染。关键在于边的构建策略节点固定为Person实体ID name avatar_color边仅包含两类关系Relation(typecolleague, weight0.9)→ 边权值 0.9Relation(typeschoolmate, weight0.7)→ 边权值 0.7其余类型如friend被过滤因演示场景需突出强关联# services/graph_service.py 第 41 行 def build_graph_for_person(self, person_id: int, max_depth: int 2) - nx.Graph: G nx.Graph() # 添加中心节点 center Person.query.get(person_id) G.add_node(center.id, labelcenter.name, colorself._get_avatar_color(center.name)) # BFS 遍历关系限制 depth2 防爆炸 queue deque([(person_id, 0)]) visited {person_id} while queue and len(G.nodes()) 50: # 节点数上限防卡死 curr_id, depth queue.popleft() if depth max_depth: continue # 查当前人的所有关系 relations Relation.query.filter( (Relation.person_id curr_id) | (Relation.target_id curr_id) ).all() for rel in relations: other_id rel.target_id if rel.person_id curr_id else rel.person_id if other_id not in visited and len(G.nodes()) 50: other Person.query.get(other_id) if other: # 确保 person 存在 G.add_node(other_id, labelother.name, colorself._get_avatar_color(other.name)) # 边权 关系类型权重 × (1/depth1) 衰减 weight self.RELATION_WEIGHTS.get(rel.type, 0.3) * (1 / (depth 1)) G.add_edge(curr_id, other_id, weightweight, labelrel.type) visited.add(other_id) queue.append((other_id, depth 1)) return G参数说明max_depth2是血泪经验——设为 3 时某“校长”节点会拉出全校教职工图谱渲染超时len(G.nodes()) 50是硬性熔断避免前端cytoscape.js内存溢出。4. 避坑指南5 个真实翻车现场与对应解法4.1 现象首页空白浏览器控制台报Failed to load resource: the server responded with a status of 404 (NOT FOUND)路径为/static/js/app.123abc.js原因static/dist/目录缺失或路径错误。源码包中static/下应有dist/子目录但部分下载渠道解压后dist/被误放在根目录。解决确认static/dist/是否存在。若只有static/js/和static/css/则将js/css/index.html全部移入static/dist/并修改templates/index.html第 8 行script src/static/js/app.123abc.js为script src/static/dist/js/app.123abc.js。4.2 现象搜索返回空数组但mock_data.json明确含 “李四” 数据原因init_db.py未执行或执行后instance/app.db被手动删除导致 SQLite 表为空。search_service默认从 DB 查询不直读 JSON。解决重新运行python scripts/init_db.py然后ls -lh instance/app.db确认文件大小 10KB。若仍无效检查models/__init__.py第 12 行db SQLAlchemy(app, session_options{expire_on_commit: False})是否被注释——此行缺失会导致 ORM 会话异常。4.3 现象关系图谱只显示单个节点无连线原因Relation表无数据。init_db.py脚本虽插入Person但Relation数据需单独初始化原作者将关联数据放在data/relations.json但init_db.py默认不加载。解决编辑scripts/init_db.py在insert_persons()后添加def insert_relations(): with open(data/relations.json, r, encodingutf-8) as f: relations json.load(f) for r in relations: db.session.add(Relation(**r)) db.session.commit()并在主函数末尾调用insert_relations()。4.4 现象curl http://127.0.0.1:5000/api/search?q张返回 404原因API 蓝图未注册。检查app.py是否漏掉from api import api_bp导入语句或app.register_blueprint()调用被注释。解决确认app.py有且仅有如下三行位置在db.init_app(app)之后from api import api_bp app.register_blueprint(api_bp, url_prefix/api)4.5 现象启动时报错sqlite3.OperationalError: unable to open database file原因instance/目录不存在且 Flask 默认不自动创建。SQLite 路径instance/app.db中instance/是相对路径需手动创建。解决在项目根目录执行mkdir -p instance再启动服务。Windows 用户用mkdir instance。5. 二次开发实战如何把模拟数据源换成自己的 CSV并增加「邮箱域名聚合」统计页5.1 替换数据源3 个文件 2 小时完成从 JSON 到 CSV 的平滑迁移目标将data/mock_data.json替换为公司内部导出的staff_export.csv字段name, email, dept, manager_email, hire_date并保留全部搜索与图谱功能。步骤 1定义 CSV 数据源协议类在services/下新建csv_data_source.py# services/csv_data_source.py import csv from typing import List, Dict, Optional from models import Person class CsvDataSource: def __init__(self, filepath: str): self.filepath filepath self._cache None def _load_cache(self): if self._cache is None: self._cache [] with open(self.filepath, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: # 字段映射CSV 列名 → Person 模型字段 p Person( namerow[name], emailrow[email], email_domainrow[email].split()[-1] if in row[email] else , departmentrow[dept], manager_emailrow[manager_email], hire_daterow[hire_date] ) self._cache.append(p) return self._cache def get_by_field(self, field: str, value: str) - List[Person]: cache self._load_cache() if field email_domain: return [p for p in cache if p.email_domain value] elif field department: return [p for p in cache if p.department value] else: return [p for p in cache if getattr(p, field, ).lower().find(value.lower()) 0] def search_fuzzy(self, keyword: str) - List[Person]: cache self._load_cache() results [] for p in cache: text f{p.name} {p.email} {p.department}.lower() if keyword.lower() in text: results.append(p) return results步骤 2注入新数据源到搜索引擎修改app.py第 35 行search_engine SearchEngine()为from services.csv_data_source import CsvDataSource search_engine SearchEngine(data_sourceCsvDataSource(data/staff_export.csv))步骤 3适配 CSV 字段到模型打开models/person.py在Person类中补充department和manager_email字段声明SQLAlchemy 列# models/person.py 第 22 行追加 department db.Column(db.String(100), indexTrue) manager_email db.Column(db.String(120), indexTrue)然后重新运行python scripts/init_db.py—— 它会自动检测新字段并ALTER TABLESQLAlchemy 1.4 支持简单列添加。5.2 新增统计页用原生 Flask 实现「邮箱域名 TOP 10」聚合需求在首页增加一个/stats/domain路由返回各邮箱域名出现次数按降序排列。步骤 1编写聚合逻辑在services/下新建stats_service.py# services/stats_service.py from models import Person from collections import Counter def get_email_domain_stats(limit: int 10) - List[Dict]: domains [p.email_domain for p in Person.query.all() if p.email_domain] counter Counter(domains) return [{domain: d, count: c} for d, c in counter.most_common(limit)]步骤 2注册新路由在api/__init__.py中或新建api/stats_api.py添加# api/stats_api.py from flask import Blueprint, jsonify from services.stats_service import get_email_domain_stats stats_bp Blueprint(stats, __name__) stats_bp.route(/stats/domain, methods[GET]) def domain_stats(): data get_email_domain_stats() return jsonify({code: 0, data: data})再在app.py中注册app.register_blueprint(stats_bp, url_prefix/api)。步骤 3前端调用可选修改templates/index.html在搜索框下方加div iddomain-stats classmt-4 h3邮箱域名 TOP 10/h3 ul iddomain-list/ul /div script fetch(/api/stats/domain) .then(r r.json()) .then(d { const ul document.getElementById(domain-list); d.data.forEach(item { const li document.createElement(li); li.textContent ${item.domain}: ${item.count} 人; ul.appendChild(li); }); }); /script5.3 我的血泪习惯每次改数据源必做的三件事从那以后我每次替换数据源都强制走一遍这三步验证字段对齐检查用python -c import pandas as pd; print(pd.read_csv(data/staff_export.csv).columns.tolist())输出 CSV 列名对照models/person.py的__table__.columns.keys()确保所有业务字段都有映射空值容忍测试在 CSV 中故意将 1 行email设为空启动后执行curl http://127.0.0.1:5000/api/search?qtestfieldemail_domain确认不崩溃且返回合理空数组性能基线记录对 5000 行 CSV 执行time curl -s http://127.0.0.1:5000/api/search?qtech | wc -l记下耗时我的机器是 1.2s后续优化才有参照。希望帮到你。本文还有配套的精品资源点击获取