如何用Node.js构建漏洞利用检索工具ExploitSearch?

来源:AI智能体作者:卡拉米头衔:草根站长
导读:本期聚焦于卡拉米创作的《如何用Node.js构建漏洞利用检索工具ExploitSearch?》,敬请观看详情。一段只支持单关键词的检索脚本,在本地漏洞库超过五千条后响应时间从 80ms 恶化到 1.2s,主要原因是同步扫描文件加正则全量匹配。要解决这个问题,可以在 Node.js 中拆出抓取、解析、索引三层结构。抓取层用 axios 拉取 ExploitDB 的 CSV 或页面数据,解析层提取 CVE、EDB-ID、平台、类型和描述字段,索引层用 better-sqlite3 建表并对 description 做分词存储。检索时先查索引定位候选 ID,再回表取完整记录,避免全量扫描。文章会给出一个可运行的命令行工具,支持多关键词 AND 查询、CVE 精确匹配和结果高亮。同时加入请求限流与本地缓存,避免频繁抓取被目标站点封禁。全文面向授权安全测试和漏洞研究场景。

在漏洞研究流程中,检索公开利用代码往往比单纯查看漏洞公告更能判断实际风险。拿到一个 CVE 编号后,需要快速确认 ExploitDB 等公开库中是否已存在对应利用脚本。Node.js 凭借异步网络请求和成熟的 SQLite 绑定,很适合实现一个本地化的检索工具 ExploitSearch。下面从项目结构开始拆解,给出可运行的代码。

如何用Node.js构建漏洞利用检索工具ExploitSearch?

一、初始化项目与依赖选择

创建项目目录并初始化 package.json。核心依赖包括 axios 用于 HTTP 请求,better-sqlite3 用于本地索引存储,commander 用于命令行参数解析。better-sqlite3 的优势在于同步 API 简单直观,执行写入和查询都不需要处理回调队列,适合单机小工具。csv-parse 用来解析 ExploitDB 提供的 files.csv 数据文件,如果不想安装 csv-parse,也可以自己写一个简易的 CSV 行解析器,但考虑到描述字段中可能包含逗号和引号,用成熟的解析库更稳妥。

安装命令如下。执行 npm init 后安装四个依赖,better-sqlite3 需要编译原生模块,如果网络较慢可以提前配置 npm 镜像。

mkdir exploit-search && cd exploit-search
npm init -y
npm install axios better-sqlite3 commander csv-parse

安装完成后,在 package.json 的 scripts 中添加一个启动命令,把入口指向 src/index.js。这样后续调试时直接运行 npm run search 即可。目录建议拆分为 src/fetcher.js、src/database.js、src/search.js 和 src/index.js 四个模块,分别负责抓取、存储、检索和命令行交互。

二、数据抓取与解析核心实现

漏洞利用库的数据源选 ExploitDB 的 files.csv,它包含 id、file、description、date、author、type、platform、port 等字段,覆盖了绝大多数公开 Exploit 条目。抓取时用 axios 发起 GET 请求,设置超时和 User-Agent,避免被目标服务器当作脚本拦截。下载完成后将 CSV 字符串交给 csv-parse 处理,得到对象数组。

const axios = require('axios');
const { parse } = require('csv-parse/sync');

const EXPLOITDB_CSV = 'https://www.exploit-db.com/files.csv';

async function fetchExploitDB() {
  const response = await axios.get(EXPLOITDB_CSV, {
    timeout: 15000,
    headers: {
      'User-Agent': 'ExploitSearch/1.0 (+local research tool)'
    }
  });

  const records = parse(response.data, {
    columns: true,
    skip_empty_lines: true,
    relax_column_count: true
  });

  return records.map((row) => ({
    edbId: parseInt(row.id, 10),
    file: row.file || '',
    description: (row.description || '').trim(),
    date: row.date || '',
    author: row.author || '',
    type: row.type || '',
    platform: row.platform || '',
    port: row.port || ''
  }));
}

module.exports = { fetchExploitDB };

解析后的数据写入 SQLite。创建 exploits 表时给 edbId 加上唯一索引,description 字段保留全文检索的原始文本。这里不用单独清理 HTML 实体,因为 files.csv 的描述通常是纯文本,只有少量 HTML 编码。若后续接入网页抓取,再用 he 库统一解码。

const Database = require('better-sqlite3');

function createDatabase(dbPath = './exploits.db') {
  const db = new Database(dbPath);
  db.pragma('journal_mode = WAL');
  db.exec(`
    CREATE TABLE IF NOT EXISTS exploits (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      edb_id INTEGER UNIQUE NOT NULL,
      file TEXT,
      description TEXT,
      date TEXT,
      author TEXT,
      type TEXT,
      platform TEXT,
      port TEXT
    );
  `);

  const insert = db.prepare(`
    INSERT OR REPLACE INTO exploits
      (edb_id, file, description, date, author, type, platform, port)
    VALUES
      (@edbId, @file, @description, @date, @author, @type, @platform, @port)
  `);

  return { db, insert };
}

function saveRecords(db, insert, records) {
  const insertAll = db.transaction((rows) => {
    for (const row of rows) {
      insert.run(row);
    }
  });
  insertAll(records);
}

module.exports = { createDatabase, saveRecords };

这段代码使用事务批量写入,SQLite 在单事务内连续插入数万条记录也能在几秒内完成。WAL 模式可以提升读写并发,命令行查询时不会因为未提交事务而锁库。主键自增与 edb_id 唯一索引并存,便于后续扩展多数据源时用自增主键做关联。

三、检索索引与查询优化

如果只对 description 字段执行 LIKE 模糊查询,当记录超过五千条时每次检索都要全表扫描,响应时间会明显上升。SQLite 自带的 FTS5 全文索引可以解决这个问题。FTS5 会按词元切分文本,对英文描述和 CVE 编号有较好的匹配效果。建表时使用 content 参数指向 exploits 表,FTS5 只保存索引,原始数据仍然留在主表。

function createFtsIndex(db) {
  db.exec(`
    CREATE VIRTUAL TABLE IF NOT EXISTS exploits_fts
    USING fts5(
      description,
      content='exploits',
      content_rowid='id',
      tokenize='unicode61 remove_diacritics 2'
    );
  `);

  db.exec(`
    INSERT INTO exploits_fts(exploits_fts)
    VALUES('rebuild');
  `);
}

function searchByKeywords(db, keywords) {
  const terms = keywords
    .split(/\s+/)
    .filter(Boolean)
    .map((term) => {
      const safe = term.replace(/"/g, '""');
      return '"' + safe + '"';
    })
    .join(' AND ');

  const rows = db.prepare(`
    SELECT
      e.edb_id,
      e.file,
      e.description,
      e.date,
      e.author,
      e.type,
      e.platform,
      highlight(exploits_fts, 0, '[[', ']]') AS snippet
    FROM exploits_fts
    JOIN exploits e ON e.id = exploits_fts.rowid
    WHERE exploits_fts MATCH ?
    ORDER BY e.date DESC
    LIMIT 50
  `).all(terms);

  return rows;
}

module.exports = { createFtsIndex, searchByKeywords };

上面的查询把多个关键词用 AND 连接,要求结果同时包含所有词。highlight 函数会在匹配片段前后加上占位符,方便命令行输出时替换成 ANSI 颜色。CVE 编号的精确检索可以直接查 edb_id 或再用一个普通索引,不必依赖 FTS 的模糊行为。对于中文关键词,FTS5 默认 unicode61 分词会把连续汉字视为一个词元,所以需要确保查询词与描述中写法一致。

如果希望支持 CVE 编号的部分匹配,比如只输入 CVE-2023 而不是完整编号,可以在代码里先检测输入是否匹配 CVE 格式,再决定走 FTS 还是 LIKE。LIKE 查询范围可以利用日期过滤,减少扫描数据量。实际测试中,五千条数据用 FTS5 检索常见关键词如 remote code execution,耗时通常低于 5ms。

四、命令行封装与安全使用

命令行入口用 commander 定义三个参数:--query 接收关键词,--cve 接收 CVE 编号,--limit 控制返回条数。程序启动时检查 SQLite 文件是否存在,如果不存在则先抓取数据并建索引。抓取过程默认强制执行,也可以加一个 --update 参数手动刷新本地库。

const { Command } = require('commander');
const { fetchExploitDB } = require('./fetcher');
const { createDatabase, saveRecords } = require('./database');
const { createFtsIndex, searchByKeywords } = require('./search');
const fs = require('fs');

const program = new Command();
program
  .option('-q, --query <keywords>', 'search keywords')
  .option('-c, --cve <cve>', 'exact CVE identifier')
  .option('-l, --limit <number>', 'result limit', '20');

program.parse(process.argv);
const options = program.opts();

(async () => {
  const dbPath = './exploits.db';
  const needFetch = !fs.existsSync(dbPath) || options.update;

  const { db, insert } = createDatabase(dbPath);

  if (needFetch) {
    const records = await fetchExploitDB();
    saveRecords(db, insert, records);
    createFtsIndex(db);
    console.log(`已导入 ${records.length} 条记录`);
  }

  let results;
  if (options.cve) {
    results = db.prepare(`
      SELECT edb_id, file, description, date, author, type, platform
      FROM exploits
      WHERE description LIKE ?
      ORDER BY date DESC
      LIMIT ?
    `).all(`%${options.cve}%`, Number(options.limit));
  } else if (options.query) {
    results = searchByKeywords(db, options.query);
  } else {
    console.log('请提供 --query 或 --cve 参数');
    process.exit(1);
  }

  for (const row of results.slice(0, Number(options.limit))) {
    console.log(`[${row.edb_id}] ${row.file}`);
    console.log(`类型: ${row.type}  平台: ${row.platform}`);
    console.log(row.snippet || row.description);
    console.log('');
  }
})();

代码里注意异步主函数需要用 IIFE 包裹,因为 commander 的 parse 是同步的。LIKE 查询中的百分号在 SQL 参数中不需要转义,但描述字段如果包含用户输入的特殊字符,使用参数绑定可以防止 SQL 注入。

ExploitSearch 仅用于授权测试和内部漏洞研究。公开 Exploit 代码通常带有攻击利用性质,运行前必须在隔离环境中检查代码逻辑,避免对未授权系统执行。抓取 ExploitDB 时应降低请求频率,并遵守目标站点的 robots 与服务条款。本地缓存可以减少对外部服务的依赖,也方便离线检索。

Node.jsExploitSearch漏洞利用检索修改时间:2026-10-04 12:18:19

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1004/65551.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。