Sorakurai/karasu
GitHub: Sorakurai/karasu
基于 LLM 的威胁情报自动化平台,从 URL 中提取结构化情报并发布到 MISP,通过人工审核保障情报质量。
Stars: 1 | Forks: 0
# Karasu
一个威胁情报自动化平台,能够导入 URL,使用 LLM 提取结构化情报,支持分析师审核,并将结果作为 MISP 事件发布。
## 目录
- [概述](#overview)
- [架构](#architecture)
- [入门指南](#getting-started)
- [开发(不使用 Docker)](#development-without-docker)
- [添加自定义 LLM 提供商](#adding-a-custom-llm-provider)
- [设计决策](#design-decisions)
## 概述
分析师提交一个 URL(威胁报告、博客文章、PDF)。平台获取内容,通过 LLM 提取结构化威胁情报,并将结果显示在编辑器中供审核。满意后,分析师将事件直接推送到 MISP 实例。
### 流水线
```
URL submitted → Fetch content → LLM extraction → Analyst review → Push to MISP
```
### 提取的情报
| 类别 | 详情 |
|---|---|
| 摘要 | 威胁的简短描述 |
| 威胁行为者 | 具名组织或个人 |
| 目标行业 | 受到攻击的行业或部门 |
| 目标国家 | 受到攻击的国家 |
| IoCs | IP、域名、URL、MD5 / SHA1 / SHA256 哈希值(带有 `to_ids` 标志) |
| TTPs | MITRE ATT&CK 技术 ID、名称和上下文 |
| 检测规则 | 引擎(Sigma、KQL 等)和查询语句 |
| 威胁狩猎假设 | 标题、假设、方法和可见性 |
### MISP 事件结构
| 情报 | MISP 表示 |
|---|---|
| IoCs | 带类型的属性(`ip-dst`、`domain`、`url`、`md5`、`sha1`、`sha256`) |
| 威胁行为者 | `threat-actor` 属性 |
| 目标行业 | `target-org` 属性 |
| 目标国家 | `target-location` 属性 |
| TTPs | Galaxy 标签(`misp-galaxy:mitre-attack-pattern`)+ `attack-pattern` 对象 |
| 检测规则 | 带引擎注释的 `text` 属性 |
| 威胁狩猎假设 | 事件报告(Markdown,标记为 `Threat Hunting Hypothesis`) |
## 架构
```
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ Frontend │────▶│ Backend │────▶│ PostgreSQL │
│ React/Vite │ │ FastAPI │ └──────────────┘
│ (nginx) │ │ (Uvicorn) │ ┌──────────────┐
└─────────────┘ └──────┬──────┘────▶│ Redis │
│ └──────────────┘
┌──────▼──────────────────────────────┐
│ Celery Workers │
│ ┌─────────┐ ┌─────────┐ ┌──────┐ │
│ │ fetch │ │ extract │ │ misp │ │
│ └────┬────┘ └────┬────┘ └──┬───┘ │
└───────┼───────────┼──────────┼──────┘
│ │ │
fetch page LLM provider MISP
content (pluggable) galaxy
│ resolution
▼ │
┌──────────┐ ▼
│ Azure AI │ ┌─────────┐
│ Foundry │ │ MISP │
└──────────┘ └─────────┘
```
**处理流水线**
```
URL submitted → [fetch] → [extract] → Analyst review → [misp] → MISP event published
```
**服务**
| 服务 | 角色 |
|---|---|
| `frontend` | 由 nginx 提供服务的 React SPA,将 `/api` 代理到后端 |
| `backend` | FastAPI REST API,JWT 认证,业务逻辑 |
| `celery` | 消费 fetch、extract 和 misp 队列的后台工作进程 |
| `postgres` | URL、原始内容和提取情报的主数据存储 |
| `redis` | Celery 代理和结果后端 |
**Celery 队列**
| 队列 | 任务 | 描述 |
|---|---|---|
| `fetch` | `fetch_url_task` | 下载 URL 内容(HTML、PDF)并存储原始文本 |
| `extract` | `extract_llm_task` | 运行 LLM 提取;在达到 token 限制时回退到拆分提取 |
| `misp` | `push_to_misp_task` | 解析 ATT&CK galaxy 标签并将事件发布到 MISP |
**LLM 抽象**
LLM 提供商可通过环境中的 `LLM_PROVIDER` 进行插拔式配置。当前生效的提供商在运行时由 `backend/app/services/llm/factory.py` 中的工厂进行选择。有关实现细节,请参阅[添加自定义 LLM 提供商](#adding-a-custom-llm-provider)。
## 入门指南
### 前置条件
- Docker 和 Docker Compose
- 已导入 MITRE ATT&CK galaxy 的运行中的 MISP 实例
- LLM 提供商(默认:使用 Mistral Small 2503 的 Azure AI Foundry)
### 1. 配置环境
复制示例文件并填入相应的值:
```
cp backend/.env.example backend/.env
```
所有必需的值都列在 `.env.example` 中,并在适用处提供了生成说明。关键值如下:
| 变量 | 描述 | 如何生成 |
|---|---|---|
| `POSTGRES_PASSWORD` | 数据库密码 | 选择一个强密码 |
| `SECRET_KEY` | 应用程序密钥 | `python -c "import secrets; print(secrets.token_hex(32))"` |
| `JWT_SECRET_KEY` | JWT 签名密钥 | `python -c "import secrets; print(secrets.token_hex(32))"` |
| `MISP_TOKEN_ENCRYPTION_KEY` | 用于静态加密 MISP token 的 Fernet 密钥 | `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` |
| `AZURE_API_KEY` | Azure AI Foundry API 密钥 | Azure 门户 |
| `AZURE_INFERENCE_ENDPOINT` | Azure AI Foundry 端点 URL | Azure 门户 |
| `MISP_URL` | 您的 MISP 实例的基础 URL | 例如 `https://misp.example.com` |
| `CORS_ORIGINS` | 逗号分隔的允许来源 | 例如 `https://your-host` |
### 2. 构建并运行
```
docker compose up --build -d
```
前端可通过 `https://your-host`(端口 443)访问。端口 80 上的 HTTP 流量会自动重定向到 HTTPS。自签名的 TLS 证书会在首次访问时触发浏览器警告——添加一个浏览器例外以继续访问。
### 3. 创建首个管理员用户
容器运行后,创建初始管理员账户:
```
docker compose exec backend python -m app.scripts.create_admin
```
可以通过 UI 中的用户管理页面创建和管理其他用户。
### 4. 设置您的 MISP API token
每位用户必须配置其个人的 MISP API token,然后才能推送事件:
1. 登录并点击顶部栏中的您的用户名
2. 输入您的 MISP API token 并点击 **Save token**
token 在静态存储时已加密,并且绝不会在 API 响应中暴露。推送到 MISP 的事件归属于 token 所有者,从而保持审计追踪的完整性。
## 开发(不使用 Docker)
### 后端
```
cd backend
python -m venv .venv && source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install -r requirements.txt
cp .env.example .env # fill in values
uvicorn app.main:app --reload
```
单独启动 Celery 工作进程:
```
celery -A app.workers.celery_app worker --loglevel=info -Q fetch,extract,misp
```
### 前端
```
cd frontend
npm install
npm run dev
```
## 添加自定义 LLM 提供商
Karasu 使用了一个抽象层,可以轻松替换不同的 LLM 提供商,而无需触及应用程序的其余部分。
### 1. 实现基类
在 `backend/app/services/llm/` 中创建一个新文件并实现 `BaseLLMService`:
```
from app.services.llm.base import BaseLLMService
from app.services.llm.schemas import LLMRequest, LLMResponse, LLMTokenLimitExceeded
class MyLLMService(BaseLLMService):
async def extract(self, request: LLMRequest) -> LLMResponse:
# Make a single request to your LLM using MISP_EXTRACTION_PROMPT
# Raise LLMTokenLimitExceeded if the model hits its output limit
# Return an LLMResponse with extracted_data, token counts, and model name
...
async def extract_split(self, request: LLMRequest) -> LLMResponse:
# Make two parallel requests using MISP_IOC_TTP_PROMPT and MISP_ANALYSIS_PROMPT
# Merge the results and return a single LLMResponse
...
```
提示词定义在 `backend/app/services/llm/prompts.py` 中。提取的 JSON 必须符合流水线其余部分所期望的 schema——请参考现有的 `AzureFoundryLLMService` 实现作为指南。
当模型的输出被截断时,必须引发 `LLMTokenLimitExceeded`(而不是被静默捕获)——这正是触发工作进程中拆分提取回退机制的条件。
### 2. 在工厂中注册提供商
在 `backend/app/services/llm/factory.py` 中,添加您的提供商:
```
from app.services.llm.my_provider import MyLLMService
def get_llm_client() -> BaseLLMService:
if settings.LLM_PROVIDER == "azure_foundry":
return AzureFoundryLLMService()
if settings.LLM_PROVIDER == "my_provider":
return MyLLMService()
raise ValueError(f"Unsupported LLM provider: {settings.LLM_PROVIDER}")
```
### 3. 在您的环境中设置提供商
```
LLM_PROVIDER=my_provider
```
## 设计决策
### MISP 发布前的人工审核
LLM 的提取结果不被视为绝对真实。提取完成后,结果会呈现在分析师使用的编辑器中,其中每个字段(IoCs、TTPs、检测规则、威胁狩猎假设)都可以在发送到 MISP 之前进行检查、纠正或删除。推送到 MISP 始终是一个谨慎的手动操作。这让人参与到整个流程中,防止 LLM 的幻觉或错误分类自动污染威胁情报平台。
### 每用户的 MISP API token
每位分析师使用其自己的个人 API token 而不是共享的服务账户向 MISP 进行身份验证。这保留了 MISP 审计日志中的归属——不同分析师推送的事件会记录在各自的账户下。token 使用 Fernet 对称加密进行静态存储,并且绝不会在 API 响应中暴露。
### 大型文档的拆分提取
LLM 模型具有有限的输出限制。冗长的文档可能会导致模型在输出 JSON 中途截断其响应,从而产生不可用的输出。为了处理这个问题,Karasu 采用了两次请求的回退策略:首次尝试在一个请求中提取所有内容,输出上限设为 10,000 个 token。如果模型达到此限制,任务会自动将工作拆分为两个并行请求——一个用于 IoCs 和 TTPs,另一个用于检测规则和威胁狩猎假设——每个请求都有自己的 10,000 个 token 上限。因此,每次提取尝试最多可以消耗 30,000 个输出 token。加上针对瞬时故障最多 3 次的重试尝试,单个文档在最坏的情况下可能会消耗多达 120,000 个输出 token。这避免了静默生成不完整的提取结果,而无需分析师重新提交。
标签:AI安全, Chat Copilot, Cloudflare, DLL 劫持, FTP漏洞扫描, IoC提取, IP 地址批量处理, LLM, MITRE ATT&CK, Sigma规则, TTP提取, Unmanaged PE, URL解析, 大语言模型, 威胁分析, 威胁情报自动化, 威胁组织, 安全编排, 实时处理, 情报协同, 情报收集, 指标提取, 搜索引擎查询, 检测规则生成, 测试用例, 漏洞研究, 目标导入, 结构化数据提取, 网络安全, 自动化侦查工具, 自动化平台, 请求拦截, 逆向工具, 隐私保护