subashjaganathan/hawk-breach-check
GitHub: subashjaganathan/hawk-breach-check
一款基于 Solr 与 FastAPI 的自托管本地全文搜索引擎,附带离线哈希标识符泄露检查功能,用于检索个人安全研究记录。
Stars: 0 | Forks: 0
# HAWK - 自托管搜索与泄露检查
一个小型、自托管的全文搜索引擎,用于检索**您自己的记录** - 日志、
分析师笔记、威胁情报文档、合成数据集。它**不是**公共搜索服务,也并非为此设计。
```
docker-compose.yml Solr + FastAPI proxy, wired together
app/
Dockerfile python:3.12-slim, uvicorn on :3000
requirements.txt fastapi, uvicorn[standard], httpx
main.py API + serves the UI
static/index.html search UI (vanilla JS, no build step)
static/check.html breach-check UI (offline single-analyst use)
convert.py turns Excel/CSV/txt/JSON into JSONL (runs on the host)
ingest.py stdlib-only JSONL loader (+ identifier hashing), runs on the host
sample.jsonl 15 synthetic DFIR records to get started
sample_breach.jsonl 10 synthetic breach records for the breach-check demo
README.md
```
## 架构
```
browser ──▶ FastAPI proxy (:3000) ──▶ Solr core "BigData" (:8983, localhost-only)
/api/search _default configset
/api/fields schemaless field-guessing
/api/health _text_ catch-all copyField
/ (static UI)
```
浏览器**绝不**直接与 Solr 通信。代理是 UI 唯一使用的接口面,您后续也可以在此添加身份验证、速率限制或查询日志记录。
## 快速开始
```
docker compose up --build
```
等待 Solr 健康检查通过(UI 中的 **solr** 圆点变为绿色),然后打开:
```
http://localhost:3000
```
全新启动时索引是空的 - UI 会显示*"没有匹配的记录。"*
加载示例数据(见下文)并重新搜索。
## 数据导入
`ingest.py` 仅使用标准库(无需 `pip install`)且运行在**宿主机**上,而不是在容器中:
```
python3 ingest.py sample.jsonl
```
其他选项:
```
python3 ingest.py data.jsonl --core BigData --batch 500
python3 ingest.py events.jsonl --id-field event_id # use an existing field as the id
python3 ingest.py data.jsonl --solr http://localhost:8983/solr
```
- **输入格式:** JSONL - 每行一个 JSON 对象。每个对象即为一条记录/文档;其键将成为 Solr 字段。
- **ID:** 每个文档都需要一个唯一的 `id`。如果指定了 `--id-field`,则该字段的值将被复制到 `id`;否则,如果不存在 `id`,则会生成一个 (UUID)。重新导入具有相同 `id` 的文档将覆盖原记录。
- **错误行**将被跳过并报告 - 格式错误的行绝不会中止运行。
- 数据分批提交(默认为 1000 条),因此可立即进行搜索。
- 从 **stdin** 读取:传入 `-` 作为文件名,以导入管道传输的 JSONL(见下文)。
### 导入 Excel / CSV / 文本 / 其他格式
索引仅接受 JSONL,因此 `convert.py` 会首先将常见格式转换为 JSONL。
将其指向**单个文件或整个目录**(递归遍历):
| 来源 | 处理方式 |
|--------|----------|
| `.csv` | 自动检测分隔符和表头;表头行 → 字段名。 |
| `.tsv` / `.tab` | 制表符分隔;表头行 → 字段名。 |
| `.xlsx` | 每个工作表的行 → 记录;第一行为表头。需要 `pip install openpyxl`。(不支持 `.xls` - 请重新保存为 `.xlsx`/CSV。) |
| `.txt` / 组合列表 | 每行一条记录。自动检测分隔符;如果是 2 列数据则假定为 `email:password`,否则请传入 `--columns`。 |
| `.json` | 由对象组成的 JSON 数组(或单个对象)。 |
| `.jsonl` / `.ndjson` | 直接透传(并进行校验)。 |
默认启用两项便捷功能:标识符列会被**标准化**(`mail`, `e-mail`, `login`, `tel`, `mobile`, … → `email`/`username`/`phone`),从而实现一条导入命令适配所有文件;同时每条记录都会添加一个 **`source_file`** 字段用于溯源。
```
# 整个混合文件的文件夹 -> 在一个 pipe 中 hashed 并 loaded
python convert.py "C:/leaks" | python ingest.py - --email-field email --username-field username --phone-field phone
# 无 header 的 combolist 使用自定义 layout
python convert.py combo.txt --delimiter : --columns email,password,ip | python ingest.py - --email-field email
```
实用的 `convert.py` 参数:`--out FILE`, `--delimiter`, `--columns a,b,c`,
`--no-header`, `--sheet NAME` (Excel), `--no-canonical`, `--no-source-file`。
### 记录 / JSONL 格式
记录格式自由。Solr 处于 schemaless(无模式)模式,因此字段类型会在首次出现时进行推断;代理确保存在 `* -> _text_` 的全局 copyField(见上文说明),因此全文搜索在首次导入时即可生效。示例行:
```
{"id": "note-0002", "kind": "analyst_note", "title": "Beaconing host 10.0.7.88", "body": "Regular 60s callouts to cdn-update[.]example over 443...", "severity": "high", "tags": ["c2", "beacon"]}
{"id": "feed-0001", "kind": "threat_feed", "indicator": "cdn-update.example", "indicator_type": "domain", "confidence": "high"}
```
UI 会自动适应您加载的任何字段(通过 `/api/fields`),并将每条记录渲染为 `字段 → 值` 的卡片行。
### 搜索
搜索框直接映射到 Solr 的 edismax 查询解析器,并将 `_text_` 作为默认字段:
- `cobaltstrike` - 任何提及它的记录
- `severity:high` - 限定字段范围
- `c2 AND powershell` - 布尔逻辑
- 空白框 - 列出所有记录 (`*:*`)
匹配的术语会被高亮显示。读数界面会显示总命中数、往返延迟、当前页以及 Solr 健康状态圆点。
## API
| 路由 | 用途 |
|-------|---------|
| `GET /api/search?q=&start=&rows=&sort=` | 代理至 Solr `select` (edismax, `df=_text_`, 开启高亮)。空的 `q` → `*:*`。`rows` 限制在 `MAX_ROWS` 以内。返回 `{total, start, rows, query, docs, highlighting}`。Solr 返回 4xx → 清理为 `400` 并附带 Solr 消息;Solr 不可达 → `503`。 |
| `GET /api/fields` | 非下划线开头的 schema 字段,以便 UI 适应您的数据。 |
| `GET /api/check?email=&username=&phone=` | **泄露检查(仅限离线)。** 对提供的每个标识符进行哈希处理(标准化值的 SHA-256),并与 `email_h`/`username_h`/`phone_h` 进行精确匹配,返回**完整**的匹配记录。返回 `{found, count, checked, docs}`。参见泄露检查章节。 |
| `GET /api/health` | Ping Solr,返回 `{ok, core}`。 |
通过环境变量配置(在 `docker-compose.yml` 中设置):`SOLR_URL`, `SOLR_CORE`,
`MAX_ROWS`。
## 泄露检查(离线,仅供单名分析师使用)
第二个页面 - **`/check.html`**(导航栏中的 "Breach check") - 允许您输入电子邮件、用户名或电话号码,并查看其是否出现在已索引的泄露记录中。
**工作原理**
- 标识符在**导入时进行哈希处理**,不以明文形式存储。将标识符字段传递给 `ingest.py`:
python3 ingest.py breach.jsonl \
--email-field email --username-field username --phone-field phone
每个值都会被标准化(电子邮件/用户名 → 去除首尾空格并转为小写;电话号码 → 仅保留数字),计算 SHA-256 哈希值,并写入 `email_h` / `username_h` / `phone_h`。原始的明文标识符字段将被**丢弃**(使用 `--keep-plaintext` 可保留)。非标识符字段(密码、来源、日期等)保持原样,因此完整记录依然可用。
- `/api/check` 会对您输入的内容应用**相同**的标准化和哈希处理,并对哈希值进行精确匹配,因此索引永远不需要(也从未保存)明文标识符。匹配对电子邮件/用户名不区分大小写,对电话号码则不区分格式(仅比较数字,因此国家代码的差异可能会导致漏报)。
- 使用内置的合成数据试一试:
python3 ingest.py sample_breach.jsonl --email-field email --username-field username --phone-field phone
# 然后打开 /check.html 并检查 alice@example.com(大小写混用也可以)
## 安全
此技术栈专为在**您**控制的机器上索引**您自己的**记录而构建。请保持这一原则:
- **8983 端口仅限 localhost 访问。** Solr 的管理 UI **没有任何身份验证**,绝不可暴露在网络中。compose 文件将其绑定到 `127.0.0.1:8983`;在不首先添加身份验证的情况下,请勿将其更改为 `0.0.0.0` 或通过反向代理发布。任何能够直接访问 Solr 的人都可以读取、修改或删除您的整个索引 - 在某些配置下甚至可以执行代码。
- **代理是唯一旨在公开的接口面。** `app/main.py` 是添加**身份验证、速率限制和查询日志记录**的唯一契合点。它除了三个 `/api/*` 路由和静态 UI 外不暴露任何内容,并禁用了浏览器对 Solr 原始管理/更新端点的访问。代理端口默认*也*绑定到 localhost - 如果您将其暴露(例如在局域网中或通过隧道),请务必先在代理处添加身份验证。
- **一旦字段确定,请摆脱 schemaless 模式。** `_default` 配置集便于入门,但字段类型猜测非常脆弱(一个类型异常的值就可能将字段锁定为错误的类型),并且索引体积也会大于实际需求。一旦确定了您的字段,请定义一个**显式 schema**:
选择 `string`(精确匹配 / 分面 / 排序 - 例如 `id`, `severity`,
`indicator_type`,IP)或 `text_general`(分词全文搜索 - 例如 `body`,
`title`, `event`),谨慎设置 `indexed`/`stored`,并且只将您实际需要搜索的字段复制到 `_text_` 中。这样做的好处是更好的相关性以及更小、更快的索引。
## 注意事项
- Solr 数据持久化存储在命名的 Docker 卷 `solr_data` 中。`docker compose
down` 会保留数据;`docker compose down -v` 会清除索引。
- 若要在不删除卷的情况下重置索引:
`python3 ingest.py` 不会清除它 - 请通过
`curl 'http://localhost:8983/solr/BigData/update?commit=true' -H 'Content-Type: application/json' -d '{"delete":{"query":"*:*"}}'` 删除。
标签:AV绕过, Docker Compose, FastAPI, Solr, 代码示例, 全文检索, 后端开发, 幻觉缓解, 数据分析, 版权保护, 自托管, 逆向工具