跑过几年渗透和攻防的人应该都有这种感觉:子域名、API 端点和技术栈识别这类信息收集工作,占了整个项目前期至少一半的时间,而且做起来非常“笨”。传统思路无非是拉字典跑爆破、挂 few 工具扒 JS、再扔进 whatweb 里看一眼指纹,最后导出几百条结果自己慢慢筛。大多数时候,程序跑完不是终点,而是人工过滤的开始。后来我开始把 LLM 接进这条链路,让模型去读 JS 里的隐藏接口、推断字典里没有的路径、把指纹信息汇总成结构化的技术栈清单,整个流程从“人工筛结果”变成了“让模型给结论,人只负责确认”。这篇文章就把我实践下来的一套自动化侦察方案完整拆开讲,内容包括整体架构、模型选型、提示词设计、工具联动,以及我踩过的坑和最终的落地代码,适合正在做安全研究、资产梳理或漏洞挖掘的工程师参考。
1. 内容整体设计与思路拆解
1.1 传统信息收集的痛点在哪里
先把问题说透。子域名发现、API 端点提取、技术栈识别这三件事,本质上都是信息收集,但它们的难点不一样。
子域名发现的传统做法是爬证书透明度日志(比如 crt.sh)、用 subfinder 汇总被动数据源、再用自己的字典跑一遍暴力枚举。这一套组合拳打下来,能拿到几万甚至几十万条候选记录,但真正能解析、有业务价值的子域往往只占很小比例。大量记录是泛解析、历史 DNS 记录、废弃项目遗留的域名,需要人逐个去判断。
API 端点提取就更头疼了。现在的前端项目几乎都是 SPA,所有业务逻辑都藏在几百个 JS 文件里,常见的做法是把 JS 下载下来,用正则提取路径字符串,再配一些类似“/api/”“/v1/”的过滤器。但实际项目里接口路径往往是拼接出来的,比如const base = '/user'、url = base + '/info',纯正则根本拼不出来,而 LLM 能读懂这种拼接逻辑。
技术栈识别相对成熟,whatweb、wappalyzer、fingerprintjs 这些工具能识别出 CMS、框架、服务器类型。但它们的输出是标签式的,比如“PHP 7.4”和“jQuery 3.6”堆在一起,没有层次,也不告诉你哪个组件对应哪个安全风险,仍然需要人脑去做二次关联分析。
说白了,传统工具擅长“收集”,不擅长“理解”。它们能给你一堆原材料,但把原材料加工成情报的动作,最终都落在了人身上。
1.2 LLM 在这条链路里扮演什么角色
我设计这套方案的核心理念是:LLM 不替代任何现有工具,它只做三件事——筛选、推断、总结。
筛选的意思是,把 subfinder、httpx 这些工具的输出整理成结构化列表交给模型,让模型根据你给定的目标特征(比如“只关心能访问的、内容看起来像业务系统的域名”)返回一个优先级排序的子列表,直接把数万条结果压缩成几十条需要人工关注的。
推断的意思是,模型在阅读 JS 代码、HTML 片段、Swagger 文档的时候,能结合上下文推测出没有被显式引用的 API 路径。比如我看到代码里有一处deleteUser(id)函数的定义,那么实际接口大概率就是DELETE /api/users/{id},模型能直接把这条路径补出来。
总结的意思是,把 whatweb 或 wappalyzer 输出的散乱标签输入模型,让它整理成一个技术栈报告,包含框架、服务端语言、数据库、CDN 或 WAF 信息,以及每个组件可能的已知问题。这一步省掉了很多查阅资料的功夫。
1.3 整体架构与信息流
这套系统的完整链路我建议这样设计:
- 被动收集层。用 subfinder、crt.sh 拉取子域名候选列表,用 gau、waybackurls 拉取历史 URL,用 katana 爬取目标站点 JS 文件。
- 存活探测层。用 httpx 做存活检测和基础指纹采集,输出“存活域名 + 响应头 + title 关键词”的结构化结果。
- LLM 分析层。把前两步的输出整理成上下文窗口内的文本,配合设计好的提示词,让模型分别输出子域名优先级、隐藏 API 端点、技术栈报告。
- 结果汇聚层。把模型的输出解析成 JSON(或 CSV),输入到统一的表格里,方便后续用 Burp、nuclei 或者人工去验证。
需要特别说明的是,LLM 分析层和工具层之间不需要做成复杂的 Agent 循环。第一版我只做了单向管道:工具收集 -> 数据清洗 -> LLM 分析 -> 结果输出。这么做的好处是每一步都可控、可审计,模型给出的每个结论都能追溯到原始数据。后面如果你想升级,可以在这个基础上加“模型发现新线索后自动调用工具去验证”的闭环,但第一版没必要,因为模型调用外部工具出错的概率不低,盲目自动化反而会让结果可信度下降。
2. 模型选型与本地化部署的关键细节
2.1 为什么优先考虑本地模型
做侦察的时候会有大量域名、URL、接口片段这类敏感数据,直接通过 API 发给云端模型,我心理上不踏实,而且很多目标环境(内网测试、涉密演练)也不允许数据出网。所以我第一版方案用的是本地模型,通过 Ollama 这类推理框架跑开源权重模型。
本地模型的好处有三点:
一是数据闭环。所有输入输出都在自己机器上,不需要担心第三方接口记录你的查询行为。二是零成本试错。云端模型的 API 是按 token 计费的,一轮侦察下来可能要消耗几十万 token,虽然单价不高,但频繁调试提示词的成本也够喝一壶的,本地模型跑多少次都不花钱。三是不受网络条件限制。内网目标、隔离网段都能用,不依赖外部服务。
当然,本地模型在智商上通常比不过顶级云端模型,但对“筛选、推断、总结”这类任务来说,7B~14B 量级的模型完全够用。如果你用的是 70B 以上的大模型,效果会明显提升,但显存和推理延迟也是实打实的。
2.2 开源模型怎么选
Open LLM Leaderboard 这个榜单可以当作第一道筛选器,但我更关注的是实际体验。在侦察场景里,我最看重模型的三个能力:
- 指令遵循能力。模型能不能严格按照 JSON 格式输出,不夹带解释性文字。
- 长上下文理解。模型能不能在几千 token 的 URL 列表里准确识别出值得注意的条目。
- 代码/Javascript 解析能力。模型能不能从一段混淆 JS 里读出接口拼接逻辑。
我实测比较好的组合是 Qwen2.5 14B 和 Llama 3.1 8B。Qwen 系列在中文场景下表现稳,对结构化输出理解很好;Llama 8B 在英文技术内容上更扎实——虽然代码里没有中文注释,但 JS 路径分离这类任务 Act 上更聪明一些。如果你机器显存只有 8GB,可以退到 Qwen2.5 7B 用 Q4 量化版本,效果打个折,但能用。
另外,别忽视 GGUF 格式。Ollama 里面跑的都是 GGUF 量化模型,这种格式的好处是推理时内存占用可控,而且很多工具都支持直接加载,包括我之前在安卓设备上折腾过的 llama.cpp 方案。虽然移动端跑 7B 模型速度慢到怀疑人生,但局域网内架一台纯 CPU 推理的服务器用来处理离线批量分析,是可行的。
2.3 部署过程中的实际参数建议
以 Ollama 为例,部署起来很简单,但有几个参数值得注意:
num_ctx(上下文窗口)默认只有 2048,如果不调大,长 URL 列表根本放不下。我建议根据显存情况调到 8192 或 16384。temperature(温度)在结构化输出任务里要调低。侦察分析不是创意写作,我们希望模型稳定输出,不希望它“发挥想象力”,所以温度我通常设在 0.2 以下。如果模型频繁输出格式错误,可以把温度调到 0,确定性最高。repeat_penalty(重复惩罚)可以用默认值,但如果你发现模型把一条路径翻来覆去重复输出,可以考虑提高到 1.2。
Ollama 服务跑起来以后,通过ollama run qwen2.5:14b验证一下是否正常,再用 8000+ 长度的 URL 列表测试上下文能力,确认不报错再继续做工具联动。
3. 工具联动与自动化收集的真实操作流程
3.1 第一步:子域名候选与存活探测
子域名收集我用的主力工具是 subfinder,它聚合了证书透明日志、DNS 数据库等多个被动数据源。命令很简单:
subfinder -d example.com -all -silent -o subs_raw.txt如果你想扩大覆盖面,还可以把 crt.sh 的证书追溯结果也拉进去,用 curl 直接抓取就行:
curl -s "https://crt.sh/?q=%25.example.com&output=json" | jq -r '.[].name_value' | sed 's/\*\.//g' | sort -u >> subs_raw.txt注意,-silent参数只是让 subfinder 不输出 banner 和进度信息,不影响结果。-all会启用全部数据源,速度会慢一些,但信息更全面。
拿到几万条原始记录后,先做一个粗过滤。泛解析问题的简单处理方法是:批量解析一批子域名,泛解析的域名往往所有子域都指向同一个 IP,或者指向一组固定 IP。用dig +short批量获取每个域名的解析结果,统计 IP 出现的频次,如果一个 IP 对应了上百个域名,那大概率是泛解析或者 CDN 的通用节点,可以先标记出来。
然后做存活探测。这里我用 httpx,它能批量请求并返回状态码、响应头、标题、内容摘要等信息。命令参考:
cat subs_cleaned.txt | httpx -silent -status-code -title -tech-detect -json -o httpx_result.json-tech-detect这个参数很关键,它会顺便把技术栈标签带出来,喂给 LLM 的时候省了我们自己写指纹识别的步骤。
3.2 第二步:JS 文件抓取与静态资源收集
API 端点挖掘的半壁江山在 JS 里。要抓到目标站点的 JS 文件,我推荐用 katana 爬虫。它比传统工具更擅长处理 SPA 站点的动态渲染,能直接嗅探页面加载过程中发起的网络请求,间接拿到一批接口返回路径。
基本用法:
katana -u https://admin.example.com -jc -d 3 -o katana_urls.txt-jc表示仅抓取 JS 文件。-d 3是爬取深度,对大多数站点来说 3 层足够,太深了既浪费时间又容易跑到外站去。抓到的 JS 文件 URL 列表保存下来,下一步下载到本地。
下载 JS 文件可以简单粗暴一点:
cat katana_urls.txt | while read url; do filename=$(echo "$url" | md5sum | cut -d' ' -f1) curl -s -m 30 -o "js/$filename.js" "$url" done文件名用哈希而不是直接用原始路径,是为了避免路径里的特殊字符影响文件系统。下载完之后,把所有文件拼成几个大文件,控制在 1MB~2MB 以内,方便后续按文件块输入给模型。
除了 JS 文件,还有两个来源不要漏。一是gau拉取历史 URL,里面经常能碰到已经下线的测试接口,这些接口往往没有鉴权逻辑,价值极高。二是目标站点根目录下的robots.txt、sitemap.xml、.well-known/路径,这些位置常常有隐藏入口,把内容拼进上下文让 LLM 判断有没有高价值路径。
3.3 第三步:把文件块交给 LLM 做分析
这一步是整个系统最核心的环节。JS 文件下载完、历史 URL 收集完,接下来就轮到模型上场了。
先用一个简单脚本把 JS 文件按行数或大小切块,每块不超过 6000 个 token 预估量。然后把块内容塞进提示词模板里,要求模型定位出所有可能的 API 端点、动态拼接的路径、硬编码的密钥或内部接口。这里用 Qwen 做一次演示:
from openai import OpenAI client = OpenAI( base_url="http://localhost:11434/v1", api_key="ollama" ) def analyze_js_block(block_content): prompt = f""" 你是一名资深 Web 安全研究员。下面是一段 JavaScript 代码片段, 请从中提取所有潜在 API 端点(含拼接路径)、内部接口名、以及硬编码的敏感信息。 要求: 1. 输出 JSON 数组,每个元素包含 path, method, confidence, reason 四个字段。 2. path 要补全拼接逻辑,比如 '/user' + '/info' 要写成 /user/info。 3. confidence 取值 low/medium/high。 4. 只输出 JSON,不要额外解释。 代码片段: {block_content} """ resp = client.chat.completions.create( model="qwen2.5:14b", messages=[{"role": "user", "content": prompt}], temperature=0.1, max_tokens=2048 ) return resp.choices[0].message.content # 读取 js/app_1.js 文件进行分析 with open("js/app_1.js", "r", encoding="utf-8", errors="ignore") as f: code = f.read() print(analyze_js_block(code[:6000]))这里我用的是 OpenAI 兼容接口,因为 Ollama 天然暴露了一个v1接口,所以代码里可以无缝沿用常用的openaiSDK。不需要额外引入别的库,这对跑惯了脚本的人来说很友好。
如果你不想写 Python,直接用 curl 也能调,只是响应解析繁琐一些:
curl -s http://localhost:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2.5:14b", "messages": [ {"role": "user", "content": "请分析以下 JS 片段中所有 API 端点:\n" + "$(cat js/app_1.js | head -c 4000)"} ], "temperature": 0.1 }'经验之谈:一次喂给模型的 JS 文件块不要太大,超过 8000 token 后模型开始“偷懒”,它只会认真读前面和后面的部分,中间的内容经常被它忽略。这可能和注意力机制的分布有关,别把模型当成能精确处理无限上下文的机器。
3.4 第四步:技术栈识别与报告输出
技术栈这块,httpx 的-tech-detect已经能给出初步标签。但标签之间是割裂的,比如它同时输出vue.js、nginx、php、mysql的时候,你并不知道它们是前端框架配后端语言的关系,还是旧系统遗留的静态资源。
这里就是 LLM 的强项了。把 http 响应头(Server、X-Powered-By、Set-Cookie)、首页 HTML 特征片段、以及 httpx 给出的全部指纹标签拼在一起,让模型判断:
- 前端框架到底是什么
- 后端语言或框架是什么
- 是否有明显的中间件(Nginx、Apache、IIS)
- 有没有 WAF(从响应头特征推测,不是通过绕过手段验证)
- 各组件之间的大致版本范围
- 可能存在的常见问题组件,对应哪类攻击面
提示词大致是这样的:
你是 Web 技术栈分析师。下面是目标站点的 HTTP 响应头、HTML 特征和指纹工具的标签输出。 请综合这些信息,输出一份技术栈清单。 要求字段: - frontend - backend - server - middleware - database - waf - cms - risk_items(数组,每项包括 component, reason) 只输出 JSON,不要解释。这个环节我建议用llm as judge的思路做一层复核:第一次让模型输出技术栈清单,第二次用一个独立 prompt 去检查这份清单是否和原始数据矛盾,比如模型说“没有 WAF”,但原始响应头里明明有cf-ray之类的标识,第二次就会指出来。虽然多花一倍推理时间,但准确率提升非常明显,值得。
4. 提示词工程与结构化输出的设计实践
4.1 三段式提示词的核心套路
这一节我详细讲讲提示词该怎么写。侦察场景的提示词和普通问答完全不同,它要求模型输出稳定、格式严格、可审计。我总结了一套三段式结构:
角色段。告诉模型“你是谁”。这里的“你”不只是安全研究员,还要明确边界:“你只做分析和推测,不负责实际请求任何目标系统”。这一句在逻辑上防止模型拒绝回答某些看似危险的问题,也防止模型越权去“想象”自己已经发起了攻击。
任务段。把任务拆得足够细。模糊的任务比如“分析这个 JS”,效果通常很差。要改成“从 JS 中提取所有字符串常量、API 路径模板、拼接变量组合”。把“期望模型发现的规律”说清楚,它才能朝着你要的方向去。
约束段。包括“只输出 JSON”“不要给分析过程”“未知信息输出 null”“不要编造不存在的端点”。尤其是“不要编造”这一条,我几乎每个 prompt 都会写上。模型在信息不足时倾向于编造内容填补空白,这在侦察场景里是致命的,因为它会给你一堆根本不存在的路径,浪费你的验证时间。
4.2 从一段真实 JS 看模型怎么“推断”
上面聊的是框架,下面给一个物理示例。假设有一段 JS 代码是这样的:
const api = '/api/v2'; const userPart = '/users'; const userId = getParam('id'); fetch(api + userPart + '/' + userId + '/profile')传统正则提取到的内容只有/api/v2、/users这些零散片段。但如果让 LLM 分析,它不仅能提取出/api/v2/users/{id}/profile这个完整路径,还能根据fetch默认为 GET 方法的特性,标注这个 endpoint 是 GET 类型,并且注明{id}是在 URL 参数里传入的。类似地,代码里如果出现axios.post('/login', {username, password}),模型能推断出/login是 POST 端点,参数格式是 JSON。这个能力让后续手动验证的步骤大大简化。
在实际执行时,为了控制上下文长度,我通常会告诉模型“只分析片段中出现的拼接变量,禁止推测片段之外的定义”。否则模型可能会根据某个变量名adminApi瞎猜一个/admin_api/...路径,误导性很强。
4.3 Token 理解中的 Key/Query/Value 思维
之前逛社区时,有人用“key、query、value”这三个概念来理解 Token 在推理过程中的角色——Key是模型知识里存的东西,Query是当前任务在找什么,Value是当前实际提供的信息。这个类比在提示词工程里非常管用。
你写 prompt 的时候,本质上是在把任务目标(query)、已知背景(value)和期望答案(key)对齐。具体到侦察场景:
- Query 是“找出所有端点和技术栈”
- Value 是 JS 代码、HTTP 头、URL 列表这些实际素材
- Key 是“模型知识里沉淀的常见框架路径、API 设计模式、指纹特征”
所以提示词里不仅要给出素材,还要提示模型去调用它知识库里的对应模式,比如:
如果你在代码中发现类似 csrf token 的处理逻辑,注意检查是否引用了某些常见安全接口,比如 /csrf、/captcha、/logout。这种“给知识锚点”的方式能明显提高模型发现敏感端点的概率。换句话说,模型的知识库(key)和你的素材(value)之间,需要用 prompt(query)搭桥。
4.4 输出解析与异常处理
用 LLM 做结构化输出,最大的痛点是模型偶尔会返回格式不标准的 JSON。比如请求使用json {...}包裹,或者因为 max_tokens 不够而截断成半截 JSON。这种情况必须在代码层面兜底。
我的处理策略是三层容错。第一层是字符串清洗,把```json前缀和```后缀剥离掉,把注释符号和尾逗号去掉。第二层是用一个简单的状态机从文本中截取第一对完整的花括号,假设模型整体输出没有跑偏,这样能拿到一个能解析的 JSON 片段。第三是如果解析失败,就用一个轻量级的“修复式提问”让模型重新生成一次,比如 “直接输出合法 JSON,不要带任何前后缀”。
import json, re def extract_json(text): if "```" in text: text = text.split("```")[1] if len(text.split("```")) > 1 else text text = text.replace("json", "", 1) match = re.search(r"\{.*\}", text, re.DOTALL) if not match: raise ValueError("No JSON in output") raw = match.group(0) try: return json.loads(raw) except json.JSONDecodeError: fixed = raw.replace(",\n}", "\n}").replace(",]", "]") return json.loads(fixed)这个函数是我项目里最关键的工具函数之一,几乎所有模型输出都要过一遍。调用模型之前也要预估max_tokens,我一般预留 4096,宁可让它输出完自己截断,也不要把正常内容拦掉。
5. 常见问题与排查技巧实录
5.1 模型开始“胡说八道”怎么办
最常见的现象是模型输出了大量不存在的路径,比如它根据config.js里的一个环境变量名PAYMENT_URL就写出了/api/payment/xxxx,但实际项目里根本没有这个路径。查证下来浪费了一大把时间。
这个问题的根因有两个。一是 prompt 里没有明确禁止“基于变量名臆测路径”,二是上下文素材太少,模型只能用知识库里的常见模式硬套。
解法也很明确。首先在 prompt 里显式加一句:“如果片段中没有出现具体的路由注册、请求调用、或字符串拼接,不要输出路径,用 null 代替。”其次,尽量把整段相关代码都给到模型,而不要只给一行。比如你给模型一个工具函数片段,它根本看不出这个函数被谁调用了,自然无法正确推断。代码块要至少包含“定义到实际调用”的上下文链。
另外,对置信度做分级输出也非常有用。我让模型对每条路径打confidence分,low 置信度的结果在后续验证时可以优先基于低优先级验证,或者干脆先看 high 和 medium。高置信度的结果再进入人工复核队列。只要保证低置信度的输出不会自动进入验证工具里,误报的影响就可控。
5.2 上下文窗口不够用,怎么处理长列表
当你喂给模型一份 5000 条子域的列表时,任何本地模型都会因为上下文不够而出现漏读或截断。这里的处理方式不能只靠调大num_ctx,而是要做信息压缩。
Httpx 的技术检测结果往往几百行,子域记录更多,第一轮先让模型做“粗筛”,比如只给 1000 条,让模型根据域名关键词的模式(是否看起来像业务系统、是否包含 admin/test/staging 等字样)挑出“最好奇”的前 50 条,第二轮再把这个结果展开,让模型精读 50 条的响应头和标题。
简单说,核心技巧是“两阶段筛选”。先用宽上下文低精度扫一遍,再用窄上下文高精度看一遍。这里的“窄”不是指窗口窄,而是指候选数量窄,要保证每一条数据都能分到足够的 token 让模型精读。
5.3 本地推理太慢,批量任务如何加速
14B 量化模型在消费级显卡上生成速度大约是 20 token/秒,分析一段 6000 token 的 JS 可能要耗时半分多钟。如果一次要分析几十个 JS 文件,总耗时奔着半小时就去了。这个时候并行是唯一的出路。
策略是在一台高配机器上起多个 Ollama 实例,或者干脆用 llama.cpp 直接跑多个进程,每个进程绑定不同的 GPU 显存块。最省事的方式是用 Ollama 的OLLAMA_NUM_PARALLEL环境变量设置并行度。
OLLAMA_NUM_PARALLEL=4 ollama serve这个变量让 Ollama 同时处理 4 个推理请求,理论上能把利用率拉满。如果你的 GPU 显存规模不够,跑不动 4 路并行,那就退回到 2 路。还有一个折中办法,就是把 JS 文件合并成大文件,减少模型加载次数。每次调用模型时,模型参数已经在显存里了,真正花的时间在 prompt 处理和生成阶段,而不是加载模型。所以批量任务的优化核心是提高单次请求的信息密度,减少请求次数。
5.4 模型输出格式解析失败时的兜底方案
就算是最稳的模型,也有概率在一个长分析任务里突然输出一些解释性文字,把你的 JSON 解析器给卡死。如果发生这种情况,最有效的处理办法不是去优化“解析器”,而是重新调整提示词,把“只输出 JSON”改写成“你输出的第一个字符必须是{,最后一个字符必须是},中间不允许有任何换行解释”。
这样模型就算犯错,也只会错在 JSON 内容上,不会错在格式前缀上。解析失败后,代码层面做了三层兜底,我在 4.4 节里写了一个extract_json函数,它能把绝大多数异常输出恢复正常。如果三层兜底都失败了,那就原样保存模型输出到日志文件,下次分析完统一排查。不需要每次失败都打断整个流程。
6. 从单次分析到可以长期使用的工具化改造
6.1 从脚本到工具的思路
很多人在这一步就停了:跑完脚本,拿到结果,手动整理,项目结束。但侦察工作真正有价值的地方在于“持续积累”。目标站点会更新 JS 文件,子域会新增,技术栈会变化,所以这个系统不应该是一次性脚本,而应该是一个可重复运行、增量更新的侦察工具。
我建议把整个流程抽象成三个模块:collector(收集)、analyzer(分析)、reporter(输出)。collector负责调用 subfinder、httpx、katana 等工具,把结果统一转换为 JSON 行格式。analyzer负责调用 LLM 做分析,生成结构化报告。reporter负责把报告写入数据库或 Excel/CSV 文件,供人工查阅。
我用 SQLite 做存储就已经足够了。核心表结构大概是:
CREATE TABLE subdomains ( id INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT NOT NULL, source TEXT, confidence TEXT, discovered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE endpoints ( id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT, method TEXT, confidence TEXT, source TEXT, discovered_at TIMESTAMP ); CREATE TABLE techstack ( id INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT, component TEXT, category TEXT, risk_reason TEXT );有了这个结构,后续想看“有哪些新增子域”“哪些接口是最近才出现的”就非常简单了。每周跑一轮收集和分析,把新结果和旧记录做差分对比,就能看到目标的攻击面变化趋势。这在长期值守类项目里特别有用。
6.2 增量采集与差分对比的思路
增量更新的核心难点在于“去重判断”。比如本周的 JS 文件和上周的文件内容几乎一致,只有某个版本号变了,导致所有端点都被重新写入数据库。这时候既要避免重复插入,又要能感知变化。
比较粗糙但有效的做法是把每个 JS 文件的哈希值存下来。本轮抓取后先算一次哈希,如果和上次记录的哈希相同,就跳过该文件的 LLM 分析,从而节省大量推理时间。如果哈希不一致,再分析新内容,并把变化的部分标记为“近期变更”。这个思路同样适用于子域名列表:先比对域名集合,只有新增的域名才进入存活探测和后续分析。
6.3 Agent 化扩展的可行方向
如果你不满足于当前的单向管道,可以往 Agent 方向扩展一层。比较务实的方案是引入“模型提出验证任务 -> 调工具验证 -> 反馈结果 -> 模型更新判断”的循环。举例来说,模型在分析 JS 时发现了疑似接口/api/user/info,它如果只是一个“文本分析器”,它并不知道这个接口是否真实存在。但如果我们在分析模块后面接一个轻量级的验证模块,用curl发一个不带鉴权的 OPTIONS 请求,看一下返回的响应码是否是 404,不是 404 就记录为“疑似有效”,再反馈给模型去修改置信度,那这个系统的价值就大幅提升了。
这里我想提醒一句,Agent 化扩展虽然听起来很“前沿”,但工程复杂度和出错概率是指数级上升的,因为模型在循环里产生的任何一步错误都会进入下一轮循环,导致误差被放大。我的建议是先把单向管道跑稳了,然后把验证模块做成独立工具,最后再考虑做闭环。不要一上来就整 Agent 编排,否则你会花大量时间在调试“模型为什么反复调用错误参数”上面。
就我个人实践而言,这套方案在几个授权测试项目中已经帮我把前期信息收集时间缩短了大概 60%,尤其是 API 端点的挖掘环节,原来需要人肉翻 JS 一小时的工作,现在几分钟就结束了,而且模型的置信度分级还能帮我决定先看哪条路径。不过也要泼一盆冷水:LLM 在这条链路里更适合当“聪明的助理”,不适合当“唯一的决策者”。它给出的结论必须能被追溯到原始数据源,否则就失去了传统工具可审计性的优势。真要把它推到生产环境,建议保留全部原始日志,模型的所有判断都附带依据字段。这样系统出错的时候,你至少知道自己错在哪一步,而不是面对一团模糊的黑箱输出。
最后分享一个小技巧:本地模型跑稳之后,可以把历史上分析过的代码块和最终确认的基础真值整理成样本集,用整理的样本微调一个小模型。微调出来的模型在你自己常见的目标特征上,效果会比通用模型好不少,而且显存占用更低,推理更快。不过这属于进阶玩法,前提是你已经积累了一大堆经过人工验证的高质量结果。当前这套自动化和人工确认配合的流程,已经足够应付绝大多数侦察场景了。