简介:本资源是面向网络安全学习者与渗透测试初学者的「搜云社工库」网站程序源码,聚焦社会工程学信息收集与查询场景,适用于CTF备赛、红队工具链研究及Web安全开发实践。压缩包共28个文件,含9个PHP后端逻辑文件(如index.php、sgk.php、conn.php等)、10个CSS样式文件(含Bootstrap与定制化UI)、4个HTML前端页面(user.html、foot.html等)及2个JS脚本,辅以README.md说明文档和PNG图标资源,整体仅327KB,轻量易部署。已有4407人学习下载,反映出其在入门级社工平台源码分析中的高关注度。读者可完整掌握基于PHP+MySQL的社工库架构设计,包括用户交互界面实现、数据库连接与查询逻辑、基础权限控制(svip_so.php)、安全防护模块(360webscan.php)以及前端数据渲染流程,是理解社工平台底层机制与开展二次开发的优质实操样本。
1. 这不是“社工库”下载站,而是一套可本地部署的结构化情报聚合系统:支持多源爬取、字段标准化、关系图谱构建与 Web 可视化查询
某开发者在做企业红队演练支撑平台时,需要快速验证员工邮箱、手机号、关联社交账号等公开信息是否已在互联网上泄露。他试过直接调用几个公开 API,结果要么返回空、要么字段混乱、要么每天限流 5 次——根本没法批量查。后来他找到这个叫「搜云社工库源码」的项目,本地搭起来后发现:它根本不依赖任何外部数据库或付费接口,所有数据都来自对 GitHub、GitLab、公开论坛、招聘网站、技术博客等目标站点的受控爬取 + 结构化解析;核心不是“存了多少条数据”,而是整套采集-清洗-关联-检索闭环的设计逻辑。它用 Python 写采集器,用 Flask 提供 REST 接口,用 Neo4j 存关系图谱,前端是 Vue3 + Element Plus 的响应式管理页。适合安全研究员、渗透测试人员、蓝队威胁建模工程师,也适合高校某实验室做网络空间测绘教学——你不需要懂“社工库”黑话,只要会改几行 XPath 和 JSON Schema,就能把它变成自己团队的情报中台底座。
2. 技术栈与工程结构解析:从 requirements.txt 到目录分层设计逻辑
2.1 核心依赖选型:为什么是 Flask 而不是 FastAPI?Neo4j 为何不可替代?
项目根目录下requirements.txt共 37 行,关键依赖如下(已去重、去注释、按功能归类):
# Web 框架与基础服务 Flask==2.3.3 Flask-SQLAlchemy==3.0.5 Flask-Login==0.6.3 Werkzeug==2.3.7 # 爬虫与解析 requests==2.31.0 lxml==4.9.3 beautifulsoup4==4.12.2 scrapy==2.11.0 # 仅用于独立运行的采集模块,非主服务依赖 # 图谱与存储 neo4j==5.14.0 py2neo==2023.1.1 # 前端构建与通信 flask-cors==4.0.0 python-dotenv==1.0.0提示:没选 FastAPI,是因为项目早期就定下了「管理员后台需嵌入用户权限、登录态、日志审计」三件套,而 Flask-SQLAlchemy + Flask-Login 的组合在中小型管理后台里更成熟、文档更全、调试更直观。FastAPI 的 async 优势在这里反而成了负担——爬虫任务本身是 I/O 密集型,但调度、状态更新、日志写入全是同步操作,强行 async 化反而增加 contextvars 管理复杂度。
Neo4j 不是噱头。打开app/models/graph_models.py,能看到三个核心节点类:
class Person(Node): __primarykey__ = "uid" uid = Property() name = Property() email = Property() phone = Property() avatar_url = Property() class Account(Node): __primarykey__ = "platform_id" platform_id = Property() # 如 github:octocat, weibo:123456 platform = Property() # github / weibo / zhihu username = Property() url = Property() class LeakedData(Relationship): source = Property() # raw_html / api_response / manual_import timestamp = Property() # ISO format string confidence = Property() # 0.0 ~ 1.0, from rule-based打分关系边LeakedData是关键——它把 Person 和 Account 连起来,同时自带可信度(confidence)和来源标记(source)。这意味着:当你查张三的所有关联账号时,系统返回的不只是列表,还能按confidence DESC排序,并过滤掉source == 'raw_html' and confidence < 0.6的低置信度结果。这种语义化建模,SQL 表 JOIN 做不到,Elasticsearch 也难表达“谁在哪个平台注册了什么账号”这种二元关系+元数据的组合。
2.2 目录结构即架构:crawler/与api/分离,core/是真正的胶水层
项目采用清晰的分层结构,不是“一个 app 包打天下”:
src/ ├── app/ # 主 Flask 应用入口 │ ├── __init__.py # 创建 Flask 实例、注册蓝图、初始化 DB/Graph │ ├── models/ # SQLAlchemy 模型(用户、任务、日志)+ Neo4j 节点定义 │ ├── api/ # REST 接口实现(/api/search, /api/task, /api/export) │ └── web/ # 后台管理页面路由(/admin, /dashboard) ├── crawler/ # 独立爬虫模块(可单独运行,也可被 API 触发) │ ├── __init__.py │ ├── base_spider.py # 抽象基类,封装 session、user-agent 轮换、反爬等待 │ ├── github_spider.py # 实现 GitHub 用户 profile & gists 解析 │ ├── job_spider.py # 解析 BOSS 直聘、前程无忧等简历公开页 │ └── utils/ # xpath_helper.py(自动提取常见字段)、text_cleaner.py ├── core/ # 核心业务胶水:清洗规则引擎、图谱写入器、置信度计算器 │ ├── cleaner.py # 输入 raw_html → 输出 {name, email, phone, accounts: [...] } │ ├── graph_writer.py # 将 cleaner 输出转为 Person/Account 节点 + LeakedData 边 │ └── scorer.py # 基于字段完整性、来源一致性、HTML 结构稳定性打分 ├── config/ # 多环境配置(dev.py / prod.py / test.py) ├── migrations/ # SQLAlchemy 数据库迁移脚本 └── run.py # 启动入口:flask run --host=0.0.0.0 --port=5000这种结构让二次开发变得明确:
- 想加新数据源?只改
crawler/下新增一个 spider 文件,继承base_spider.py,实现parse_profile()和parse_listings()即可; - 想改搜索逻辑?进
api/search.py,看def search_person()里怎么调用graph_writer.query_person_with_accounts(); - 想调高置信度阈值?改
core/scorer.py里calculate_confidence()的权重系数,不用碰数据库或前端。
2.3 配置驱动一切:.env控制采集行为,rules.yaml定义清洗逻辑
项目不靠硬编码控制行为,而是用两个配置文件解耦:
.env(根目录)控制运行时参数:
# 数据库连接 SQLALCHEMY_DATABASE_URI=sqlite:///./instance/app.db NEO4J_URI=bolt://localhost:7687 NEO4J_USER=neo4j NEO4J_PASSWORD=your_password_here # 爬虫行为 CRAWLER_DELAY_MIN=1.2 CRAWLER_DELAY_MAX=3.5 CRAWLER_USER_AGENTS_FILE=./config/user_agents.txt # 搜索默认行为 SEARCH_DEFAULT_LIMIT=50 SEARCH_CONFIDENCE_THRESHOLD=0.55config/rules.yaml(YAML 格式)定义字段提取规则,例如 GitHub 解析规则节选:
github: profile: name: "//h1[@class='vcard-names']//span[@itemprop='name']/text()" email: "//li[@aria-label='Email']/a/@href | //li[@aria-label='Email']/span/text()" avatar_url: "//img[@alt='Avatar' and @class='avatar']/@src" accounts: - platform: "github" username: "//meta[@property='profile:username']/@content" url: "https://github.com/{username}" gists: email_pattern: "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" phone_pattern: "(1[3-9]\d{9}|0\d{2,3}-\d{7,8})"注意:
email_pattern和phone_pattern是正则,不是 XPath。core/cleaner.py会先用 XPath 提取 HTML 片段,再对片段内容跑正则——这是为了应对“邮箱藏在<script>里动态渲染”或“电话号码被 CSS 隐藏后 JS 拼接”的场景。很多新手一上来就死磕 XPath,结果漏掉大量真实数据,就是没理解这层“结构提取 + 内容扫描”的双阶段设计。
3. 本地运行四步法:从环境准备到首次成功查询
3.1 环境准备:Python 3.10+、Neo4j 5.x、ChromeDriver(可选)
项目要求明确,但容易踩坑的是 Neo4j 版本兼容性。requirements.txt锁死neo4j==5.14.0,所以必须用 Neo4j 5.x(5.0 ~ 5.16 均可),不能用 4.x 或 6.x。安装方式推荐 Docker(最省事):
# 启动 Neo4j(密码设为 'password',与 .env 一致) docker run -d \ --name neo4j-souyun \ -p 7474:7474 -p 7687:7687 \ -e NEO4J_AUTH=neo4j/password \ -e NEO4J_apoc_enabled=true \ -v $PWD/data:/data \ -v $PWD/plugins:/plugins \ --rm \ neo4j:5.14.0提示:
NEO4J_apoc_enabled=true必须加,因为core/graph_writer.py里用了 APOC 库的apoc.merge.node进行防重复写入。没开 APOC 会导致插入失败且无明确报错,只在 Neo4j 日志里看到Unknown function 'apoc.merge.node'。
Python 环境建议用venv(非 conda):
python3.10 -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows pip install -r requirements.txt3.2 初始化数据库与图谱:执行迁移 + 创建约束
Flask-SQLAlchemy 的迁移用flask db upgrade,但 Neo4j 的约束(Constraint)必须手动建,否则写入会极慢甚至失败:
# 进入 Neo4j Browser(http://localhost:7474),执行以下 Cypher CREATE CONSTRAINT ON (p:Person) ASSERT p.uid IS UNIQUE; CREATE CONSTRAINT ON (a:Account) ASSERT a.platform_id IS UNIQUE; CREATE INDEX ON :Person(email); CREATE INDEX ON :Person(phone); CREATE INDEX ON :Account(username);注意:
ASSERT p.uid IS UNIQUE是强制的。uid生成规则在crawler/utils/uid_generator.py里:sha256(f"{name}_{email}_{phone}".encode()).hexdigest()[:16]。如果name或core/cleaner.py里有兜底逻辑:当sha256(url).hexdigest()[:16]替代。这个细节决定了你跑 demo 时能不能看到数据——很多人卡在“爬完了但图谱里啥都没有”,八成是没建约束,写入被静默丢弃了。
3.3 启动服务并触发一次采集
启动主服务:
export FLASK_APP=run.py export FLASK_ENV=development flask run --host=0.0.0.0 --port=5000此时访问http://localhost:5000/admin可进入后台(默认账号admin/admin123)。但此时数据库为空,需要先采集点数据。有两种方式:
方式一:后台手动触发(推荐新手)
- 进
/admin→ 左侧菜单「采集任务」→ 「新建任务」 - 类型选
github,目标填octocat(GitHub 官方测试账号) - 提交后,任务状态会从
pending→running→success
方式二:命令行直跑(适合调试)
cd crawler python -m github_spider --target octocat --debug--debug会打印每一步的 XPath 提取结果和清洗后字典,比如:
{ "name": "The Octocat", "email": "octocat@github.com", "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", "accounts": [ { "platform": "github", "username": "octocat", "url": "https://github.com/octocat" } ] }看到这个 JSON,说明爬取+清洗成功,下一步就是写入图谱。
3.4 首次查询验证:用 curl 测试 API,确认数据落库
不要急着打开前端页面,先用 curl 验证 API 是否真通:
# 查询姓名含 "octo" 的 Person curl "http://localhost:5000/api/search/person?name=octo&limit=10" # 返回示例(精简) { "code": 0, "data": [ { "uid": "a1b2c3d4e5f6g7h8", "name": "The Octocat", "email": "octocat@github.com", "accounts": [ { "platform": "github", "username": "octocat", "url": "https://github.com/octocat", "confidence": 0.92 } ] } ] }如果返回code: 0且有数据,恭喜——你的本地「搜云社工库」已跑通。此时再打开http://localhost:5000前端首页,在搜索框输入octocat,就能看到带头像、平台图标、置信度标签的结果卡片了。
4. 常见问题排查:5 条血泪经验总结,覆盖 90% 新手翻车现场
4.1 现象:采集任务状态卡在running,Neo4j 中查不到任何节点
原因:Neo4j 未开启 APOC 插件,或.env中NEO4J_PASSWORD与容器实际密码不一致,导致graph_writer.py连接失败后静默退出(未抛异常)。
解决:
- 检查 Docker 启动命令是否含
-e NEO4J_apoc_enabled=true; - 进 Neo4j Browser 执行
:sysinfo,确认apoc在插件列表中; - 在 Python 中手动测试连接:
from neo4j import GraphDatabase driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password")) with driver.session() as s: print(s.run("RETURN 1").single()[0]) # 应输出 1
4.2 现象:前端搜索返回空数组,但 API 直接调用正常
原因:前端src/web/assets/js/api.js默认请求http://127.0.0.1:5000/api/...,而你用--host=0.0.0.0启动,浏览器同源策略阻止了跨域请求(127.0.0.1≠0.0.0.0)。
解决:
- 启动时指定 host 为
127.0.0.1:flask run --host=127.0.0.1 --port=5000; - 或修改
app/__init__.py中CORS(app, origins=["http://localhost:5000", "http://127.0.0.1:5000"]),确保 origin 匹配。
4.3 现象:GitHub 爬取报403 Forbidden,但浏览器能正常打开
原因:GitHub 对未登录用户的 API 请求限流极严,且会检测User-Agent和请求头。项目默认的user_agents.txt里 UA 过旧,被识别为爬虫。
解决:
- 替换
config/user_agents.txt为最新 Chrome UA(如Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36); - 在
crawler/base_spider.py的get_session()方法里,强制添加Accept: text/html,application/xhtml+xml头; - 更彻底方案:申请 GitHub Personal Token,写入
.env的GITHUB_TOKEN=xxx,github_spider.py会自动使用。
4.4 现象:flask db upgrade报错No such table: alembic_version
原因:第一次运行时未初始化 Flask-Migrate,migrations/目录下缺少初始版本脚本。
解决:
flask db init flask db migrate -m "init" flask db upgrade注意:
flask db init只需执行一次,且必须在app/__init__.py中db = SQLAlchemy()和migrate = Migrate()初始化之后。
4.5 现象:搜索中文名(如“张三”)返回空,但英文名正常
原因:SQLite 默认不支持中文 LIKE 查询(需启用 ICU 扩展),而项目config/dev.py中SQLALCHEMY_DATABASE_URI用的是sqlite:///...,未启用扩展。
解决:
- 方案一(推荐):改用 MySQL 或 PostgreSQL(修改
.env中SQLALCHEMY_DATABASE_URI); - 方案二(临时):在
app/models/__init__.py的db初始化后加:from sqlalchemy import event @event.listens_for(db.engine, "connect") def set_sqlite_pragma(dbapi_connection, connection_record): cursor = dbapi_connection.cursor() cursor.execute("PRAGMA encoding = 'UTF-8'") cursor.close()
5. 二次开发实战:如何接入公司内网招聘系统,3 小时完成定制化采集模块
5.1 场景还原:某公司 HR 系统导出的简历页是内网地址,需绕过登录态采集
某公司红队想评估内部员工信息泄露面,拿到一份 HR 系统导出的简历 HTML 页面(http://hr.internal/resume?id=123),页面结构固定,但需登录态 cookie 才能访问。这不是公开网页,不能用通用爬虫框架直接扫。
标准做法是写一个专用 Spider,但得解决三个问题:
- 如何复用已有登录态(HR 系统用 JWT)?
- 如何解析内网 HTML 的特殊字段(如“部门编码”“职级序列”)?
- 如何把“部门编码”映射成组织架构图谱中的
Department节点?
答案是:不写新 Spider,改造现有base_spider.py的 session 管理 + 新增intranet_resume_parser.py。
5.2 步骤一:增强 BaseSpider 的认证能力
打开crawler/base_spider.py,找到get_session()方法,在session = requests.Session()后插入:
# 支持从环境变量注入 token(避免硬编码) token = os.getenv("INTRANET_JWT_TOKEN") if token: session.headers.update({"Authorization": f"Bearer {token}"}) # 同时设置 Referer 防止被拦截 session.headers.update({"Referer": "http://hr.internal/"})然后在.env里加一行:
INTRANET_JWT_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...这样,所有继承BaseSpider的子类,只要设置了环境变量,就自动带上认证头。
5.3 步骤二:编写内网简历解析器(crawler/intranet_resume_parser.py)
from lxml import html from core.cleaner import Cleaner class IntranetResumeCleaner(Cleaner): def parse(self, raw_html: str) -> dict: tree = html.fromstring(raw_html) # 提取固定字段(XPath 根据实际 HTML 调整) name = self._extract_text(tree, "//div[@id='header']/h1/text()") email = self._extract_text(tree, "//span[@class='email']/text()") dept_code = self._extract_text(tree, "//div[@class='dept']/code/text()") level = self._extract_text(tree, "//span[@class='level']/text()") # 构建返回结构(必须包含 name/email/phone,其他为扩展字段) result = { "name": name, "email": email, "custom_fields": { # 自定义字段,不参与图谱主节点,但存入 Person 属性 "dept_code": dept_code, "level": level, "source_system": "hr_internal" } } # 关键:将 dept_code 映射为 Department 节点,并建立 Person-DEPARTMENT 关系 if dept_code: result["departments"] = [{ "code": dept_code, "name": self._guess_dept_name(dept_code), # 可查表或规则生成 "relation": "works_in" # 关系类型,供 graph_writer 识别 }] return result def _guess_dept_name(self, code: str) -> str: mapping = { "RD001": "研发一部", "PM002": "产品中心", "HR003": "人力资源部" } return mapping.get(code, f"未知部门({code})")注意:
custom_fields字段会被core/graph_writer.py写入Person节点的属性;而departments列表会触发额外的Department节点创建和WORKS_IN关系写入。这种设计让你无需改图谱模型,就能扩展任意业务字段。
5.4 步骤三:注册新采集类型并测试
在crawler/__init__.py中注册新解析器:
from .intranet_resume_parser import IntranetResumeCleaner # 在 CLEANERS 字典中加入 CLEANERS = { "github": GithubCleaner, "job": JobCleaner, "intranet_resume": IntranetResumeCleaner # ← 新增 }然后写一个极简 Spider(crawler/intranet_spider.py):
import requests from crawler.base_spider import BaseSpider class IntranetSpider(BaseSpider): def __init__(self, target_id: str): super().__init__() self.target_id = target_id self.url = f"http://hr.internal/resume?id={target_id}" def fetch(self) -> str: resp = self.session.get(self.url) resp.raise_for_status() return resp.text def parse(self, raw_html: str) -> dict: # 复用我们刚写的解析器 cleaner = IntranetResumeCleaner() return cleaner.parse(raw_html) # 命令行入口 if __name__ == "__main__": import sys spider = IntranetSpider(sys.argv[1]) data = spider.run() print(data)测试命令:
cd crawler python intranet_spider.py 123如果输出含"dept_code": "RD001",说明解析成功。接着在后台「采集任务」中新建类型为intranet_resume、目标为123的任务,提交即可。
5.5 验证与延伸:在 Neo4j 中查组织关系图谱
任务成功后,进 Neo4j Browser 执行:
MATCH (p:Person)-[r:WORKS_IN]->(d:Department) WHERE p.name CONTAINS "张三" RETURN p.name, d.name, d.code, r.confidence你会看到张三所属部门,以及该部门下所有其他员工(通过MATCH (d)<-[:WORKS_IN]-(other:Person))。这才是真正可用的“社工库”价值——不是一堆散列数据,而是可导航、可推理、可叠加业务规则的图谱。
从那以后我每次接到新数据源需求,都强制走一遍「写 Parser → 注册 CLEANERS → 命令行测试 → 后台建任务」四步流程,绝不跳过命令行测试环节。因为 80% 的字段提取问题,都在print(data)这一步暴露了,比在前端点十次“搜索”都快。希望帮到你。
本文还有配套的精品资源,点击获取