TTP是Threat、Techniques and Procedures三个词的组合,中文通常译为战术、技术和程序。它是MITRE ATT&CK框架的骨架:战术表示攻击者想达到的目标,技术表示达成目标的手段,程序则描述具体攻击组织或工具如何实施这些手段。安全运营团队在分析事件时,经常需要回答诸如“APT29用过哪些持久化技术”、“T1055进程注入有哪些变体”这类问题,如果靠人工翻阅ATT&CK矩阵效率极低。本文将介绍如何用Node.js搭建一套TTP Search检索服务,实现快速的模糊查询、精确匹配和多维度过滤。
一、ATT&CK数据解析与建模
MITRE官方提供STIX 2.1格式的enterprise-attack.json数据集,里面包含了attack-pattern(技术)、intrusion-set(威胁组织)、malware(恶意软件)、course-of-action(缓解措施)等对象。第一步是用Node.js把这个JSON文件解析成适合检索的扁平化结构。
原始STIX数据是嵌套的,一个attack-pattern对象里有external_references字段指向ATT&CK技术ID,kill_chain_phases字段表示所属战术阶段,x_mitre_data_sources表示数据来源。我们需要把这些字段提取出来,构建一个统一的文档模型:每个技术点包含ID、名称、描述、战术阶段、关联组织、关联工具、数据源等字段。
const fs = require('fs');
const stix = JSON.parse(fs.readFileSync('enterprise-attack.json', 'utf8'));
const techniques = stix.objects.filter(o => o.type === 'attack-pattern');
// 构建技术文档模型
const docs = techniques.map(t => {
const extRef = t.external_references?.find(r => r.source_name === 'mitre-attack');
return {
techniqueId: extRef ? extRef.external_id : '',
name: t.name,
description: t.description || '',
tactics: (t.kill_chain_phases || []).map(p => p.phase_name),
platforms: t.x_mitre_platforms || [],
dataSources: t.x_mitre_data_sources || []
};
});
console.log(`共解析出 ${docs.length} 条技术条目`);构建文档模型时要注意两点:一是external_references可能为空,需要做空值保护,否则解析老版本数据集时会抛出TypeError;二是x_开头的自定义字段是MITRE扩展的,不是STIX标准字段,升级STIX库版本时字段名可能变化,建议集中在一个函数里处理解析逻辑,方便后续维护。
二、两种索引方案:内存索引与Elasticsearch
检索层有两种典型方案。对于数据量不大、并发不高的场景,直接在内存中构建倒排索引最简单:ATT&CK企业版总共只有六百多个技术点,全文构建索引后内存占用不过几十MB,Node.js单进程完全可以承载。
内存索引的核心是分词。中文场景需要借助结巴分词或Intl.Segmenter做分词,英文场景可以简单按空格和标点切分后做小写归一化。以下是一个轻量的倒排索引实现:
class InvertedIndex {
constructor() {
this.index = new Map(); // 词 -> 文档ID集合
}
tokenize(text) {
return text.toLowerCase().match(/[a-z0-9\u4e00-\u9fa5]+/g) || [];
}
add(id, text) {
for (const token of new Set(this.tokenize(text))) {
if (!this.index.has(token)) this.index.set(token, new Set());
this.index.get(token).add(id);
}
}
search(query) {
const tokens = this.tokenize(query);
if (tokens.length === 0) return new Set();
// 求多词交集,实现AND语义
let result = this.index.get(tokens[0]) || new Set();
for (let i = 1; i < tokens.length; i++) {
const next = this.index.get(tokens[i]) || new Set();
result = new Set([...result].filter(id => next.has(id)));
}
return result;
}
}这套内存索引方案的优势是零依赖、响应快、部署简单,缺点是重启后需要重建索引,且无法水平扩展。如果平台需要支持多用户高并发检索,或者数据源除了ATT&CK还包括自研情报库、历史事件库,那么建议引入Elasticsearch。ES提供了现成的分词器、相关度评分、高亮和聚合能力,通过官方的@elastic/elasticsearch客户端批量写入即可。
接入ES时一个常见坑是mapping设置。技术名称和描述建议设为text类型并配置IK或standard分词器,而techniqueId这类需要精确匹配的字段要设为keyword类型。如果全部交给ES自动推断,techniqueId可能被识别成text,导致按ID精确查询时查不到结果。这一点在初始化索引时务必显式声明mapping。
三、实现RESTful检索API
索引层就绪后,用Express封装查询接口。一个实用的TTP Search API至少要支持四种查询方式:按技术ID精确查询、按关键词全文检索、按战术阶段过滤、按威胁组织反查技术。下面是核心路由的实现示例:
const express = require('express');
const app = express();
app.use(express.json());
// 按ID精确查询
app.get('/api/ttp/:techniqueId', (req, res) => {
const doc = docs.find(d => d.techniqueId === req.params.techniqueId.toUpperCase());
if (!doc) return res.status(404).json({ error: '未找到该技术' });
res.json(doc);
});
// 关键词模糊检索,支持战术过滤
app.get('/api/ttp/search', (req, res) => {
const { q, tactic, page = 1, size = 20 } = req.query;
let ids = index.search(q || '');
let results = docs.filter(d => ids.has(d.techniqueId));
if (tactic) {
results = results.filter(d => d.tactics.includes(tactic));
}
const start = (page - 1) * size;
res.json({
total: results.length,
items: results.slice(start, start + Number(size))
});
});
app.listen(3000, () => console.log('TTP Search服务已启动'));代码中有几个细节值得注意。第一,用户输入的技术ID大小写不统一,要统一转大写再匹配;第二,分页参数要做数值校验并限制size上限,防止恶意请求一次拉取全部数据;第三,全文检索结果建议按匹配词数量做简单排序,让命中最多的条目排在前面,提升检索体验。
如果要做组织反查功能,需要在解析阶段建立组织与技术的关联关系。STIX数据中通过relationship对象描述intrusion-set与attack-pattern之间的uses关系,把这些关系解析成邻接表后,即可通过组织名查询其常用的技术清单,甚至统计出每个组织的技术偏好,为威胁狩猎提供线索。
四、性能优化与工程化建议
在真实生产环境中,TTP Search还需要考虑几方面的优化。首先是缓存:ATT&CK数据更新频率很低,通常跟随官方版本每季度更新一次,因此可以把解析后的文档模型序列化为单独的JSON文件,服务启动时直接加载,省去每次解析STIX的过程,冷启动时间可以从数秒降到几十毫秒。
其次是模糊匹配体验。用户往往记不清技术的完整名称,只记得“注入”或“横向移动”这类片段。倒排索引只能匹配完整词条,可以做两层兜底:先走倒排索引快速召回,再对结果集内的名称字段做一次includes子串匹配,把“凭据转储”这类未分词成功的长词也召回回来。数据量大时则交给ES的wildcard或ngram分词器处理。
最后是数据的定时更新。可以写一个定时任务脚本,从MITRE官方GitHub仓库拉取最新的STIX数据,重新构建索引后原子替换旧数据。整个流程建议容器化,把数据卷挂载到容器中,配合健康检查接口验证索引完整性,确保更新失败时服务仍能使用旧版本数据,不影响线上检索。
总结来说,用Node.js实现TTP Search并不复杂:核心工作在于STIX数据解析建模、索引方案选型和查询接口设计。小型场景用内存倒排索引加Express即可快速落地,数据规模增长后再平滑迁移到Elasticsearch,这套渐进式架构能满足威胁情报平台从起步到成熟各阶段的检索需求。
Node.jsTTP SearchATT&CK框架修改时间:2026-08-31 06:26:47