ZAP 扫描知识库(Kb)深入解析:插件间共享扫描结果的机制与用法
2026/9/21 19:32:35 网站建设 项目流程
  • 网络安全
  • 应用安全
  • 开发工具

【免费下载链接】zaproxy

The ZAP by Checkmarx Core project

项目地址:https://gitcode.com/gh_mirrors/za/zaproxy
点击查看免费下载

ZAP(Zed Attack Proxy)核心项目在主动扫描器中内置了一个名为 Kb(Knowledgebase,知识库)的共享存储组件,用于在扫描过程中跨插件记录、共享与传递扫描结果。本文基于 ZAP 核心仓库(zaproxy)中的 docs/Kb.md 文档,结合 Kb.java 等源码实现,系统讲解 Kb 的两种存储作用域、全部公开 API、读写语义、在扫描器中的接入方式,以及当前的 URI 级使用实例,帮助读者理解 ZAP 插件间依赖协作的底层机制,并掌握在自己的扫描插件中使用 Kb 的完整方法。

一、Kb 是什么:扫描过程中的“插件间共享结果池”

ZAP 的主动扫描由多个扫描插件(Plugin)协作完成,单个插件往往只负责探测某类单一漏洞。实际场景中经常出现“依赖关系”:例如先由某个插件探测出后端数据库类型,后续插件才能针对该数据库类型实施精准的注入探测。为此,ZAP 在org.parosproxy.paros.core.scanner包中提供了 Kb 知识库组件。

Kb 的源码 Javadoc 明确了它的设计定位:

Knowledge base records the properties or result found during a scan. It is mainly used to share result among plugin when dependency arise.

也就是说,Kb 的作用是记录扫描过程中发现的性质与结果,并在存在依赖关系的插件之间共享这些结果。它不是一个持久化数据库,也不属于会话数据,而是与一次主机扫描(HostProcess)生命周期绑定的内存缓存。

两种存储作用域(关键设计)

Kb 内部用两个TreeMap分别维护两类数据(见 Kb.java):

作用域存储结构适用场景
Host 级(全局)TreeMap<String, Object> mapKbkey = 名称,value = 结果,对整个主机(Host)全局有效
URI 级(按路径)TreeMap<String, TreeMap<String, Object>> mapURIkey = URL(去掉 query 的路径)+ 名称,value = 结果,仅对特定路径有效

这一设计的意图在于:某些扫描结论是针对整个站点/主机的(如“后端是 MySQL”),而另一些结论则只对某个具体路径成立(如“/admin/login存在 SQL 注入”),两种信息需要区分存储,避免全局结论被局部数据污染。

二、Kb 的完整 API 与读写语义

Kb 类(Kb.java)是线程安全的(所有公开读写方法均为synchronized),所有 API 都以“Host 级 / URI 级”成对出现。

Host 级 API(key = 名称)

方法语义
add(String key, Object value)向知识库写入一个键值对;只有唯一值才会被加入(重复值自动去重)
get(String key)获取该 key 对应列表中的第一个值;不存在或列表为空返回null
getList(String key)获取该 key 对应的全部值列表(Vector<Object>);无历史值返回null
getString(String key)String形式获取第一个值;若值不是String或不存在返回null
getBoolean(String key)boolean形式获取第一个值;若值不是Boolean返回false(注意:与getString的返回语义不同)

URI 级 API(key = URL + 名称)

方法语义
add(URI uri, String key, Object value)为指定 URI 写入键值对,同样只保留唯一值
get(URI uri, String key)获取该 URI 下该 key 的第一个值
getList(URI uri, String key)获取该 URI 下该 key 的全部值列表
getString(URI uri, String key)获取该 URI 下该 key 的第一个值并转String
getBoolean(URI uri, String key)获取该 URI 下该 key 的第一个值并转boolean

三个必须注意的语义细节

  1. “add 一个值”实际上是“追加到一个列表”:底层add(map, key, value)实现中,每个 key 对应一个Vector<Object>,新值追加到向量末尾;get系列方法只返回向量的第一个元素(Kb.java)。因此getList才是取回全部历史值的正确手段。
  2. 唯一性去重add在追加前会检查if (!v.contains(value)),保证同一 key 下不会出现重复值(Kb.java)。
  3. URI 级存储会剥离查询串:写入 URI 级数据时,源码会先uri.clone(),再uri.setQuery(null)去除 query 部分,以“去掉 query 的路径”作为 key(Kb.java)。这意味着/path?a=1/path?a=2会被视为同一个 URI 键;若 URI 不可克隆或去除 query 失败,写入会被静默丢弃并记录日志。

三、扫描器如何接入 Kb:AbstractPlugin 与 HostProcess

Kb 目前通过两个入口暴露给扫描插件使用(这一点在原文档中也有明确说明):

  • AbstractPlugin.getKb():每个扫描插件都可直接调用getKb()获取当前扫描的知识库引用,其实现为return getParent().getKb(),即委托给所属的 HostProcess。
  • HostProcess.getKb():按需懒加载——首次访问时创建new Kb(),之后复用同一个实例,并保证“永不返回 null”。

从源码结构可以推断出 Kb 的完整生命周期:每个 HostProcess 持有一个 Kb 实例(HostProcess.java 声明private Kb kb = null),而一次扫描中运行的所有插件共享该 HostProcess 的 Kb。因此:

  • Kb 的共享范围是同一主机扫描内的全部插件,而不是全局的、跨扫描的;
  • 不同主机(Host)的扫描拥有各自独立的 Kb,互不干扰;
  • 扫描结束、HostProcess 释放后,Kb 中记录的数据随之失效。

在插件中使用 Kb 的典型写法

由于 Kb 同时支持“Host 级”与“URI 级”两种写入,一个插件内典型的数据交换流程如下:

// 写入:将扫描中发现的结论存入知识库(供后续依赖插件读取) Kb kb = getKb(); kb.add("some/host/level/key", discoveredValue); // Host 级,对整个主机生效 kb.add(msg.getRequestHeader().getURI(), "path/key", value); // URI 级,仅对该路径生效 // 读取:从知识库获取前置插件写入的结论 String rdbms = kb.getString("sql/rdbms_name"); if (rdbms != null) { // 已确认数据库类型,执行针对性的探测 } // 批量读取 java.util.Vector<Object> values = kb.getList("some/key");

注意:Kb 的 key 当前采用“命名空间/具体键名”的分层字符串约定(如sql/<rdbms_name>),写入与读取方必须约定一致的 key,才能完成跨插件协作。

四、当前 URI 级使用实例:SqlInjectionScanRule 与 sql/ 键

原文档记录的、当前代码库中唯一明确的 URI 级 Kb 使用场景来自org.zaproxy.zap.extension.ascanrules.SqlInjectionScanRule(SQL 注入扫描规则插件),它使用 key 跟踪:

  • sql/<rdbms_name>

即“sql/+ 具体数据库名(rdbms name)”形式的键。可以推断其协作模式为:某个探测插件在扫描到某一路径时,若识别出后端数据库类型(例如 MySQL、PostgreSQL、Oracle 等),就调用kb.add(uri, "sql/" + rdbmsName, ...)将结论按 URI 级写入;后续针对该路径做 SQL 注入检测的插件再通过kb.getString(uri, "sql/<rdbms_name>")读取,从而避免重复探测、并针对已知数据库类型选择更精准的注入语法。

需要说明的是:SqlInjectionScanRule属于独立的 zap-extensions 仓库(ascanrules 扩展),在当前 zaproxy 核心仓库中并无其源码;本文所述仅为 docs/Kb.md 中记录的事实与其在核心 Kb 实现下的必然交互方式,属合理推断。

五、单元测试对 Kb 语义的验证

核心仓库的 KbUnitTest.java 直接印证了前文描述的 API 语义:

测试用例验证的行为
shouldStoreValueForGivenKeyadd(key, value)get(key)能取回原值
shouldRetrieveStoredObjectsForGivenKey同一 key 写入两个不同对象,getList返回大小为 2 的列表且保持写入顺序contains断言)
shouldRetrieveStoredBooleanForGivenKey写入Boolean.TRUEgetBoolean返回true
shouldRetrieveStoredStringForGivenKey写入字符串后getString返回原字符串
shouldReturnNullWhenGivenKeyHasNoStoredValue未写入的 key,get返回null
shouldReturnFalseWhenRetrievingNonBooleanValueAsBoolean存入非 Boolean 对象后用getBoolean读取,返回false而非抛异常
shouldReturnNullWhenRetrievingNonStringValueAsString存入非 String 对象后用getString读取,返回null而非抛异常

值得注意的是,测试文件中 URI 级相关的 4 个用例(shouldStoreValueForGivenUriAndKeyshouldRetrieveStoredObjectsForGivenUriAndKeyshouldRetrieveStoredBooleanForGivenUriAndKeyshouldRetrieveStoredStringForGivenUriAndKey)目前均标注@Disabled(“Not yet implemented”),即 URI 级 API 在核心仓库中尚无自动化测试覆盖。这提示开发者:若要在 zap-extensions 中依赖 URI 级 Kb 行为,需要自行保证测试覆盖。

六、在扩展插件中正确使用 Kb 的实践建议

综合原文档、核心实现与测试,为在 ZAP 扫描扩展中使用 Kb 的开发实践给出以下要点:

  1. 明确作用域:整站结论用 Host 级(add(String key, Object value)),局部结论用 URI 级(add(URI uri, String key, Object value)),不要混用,避免数据污染。
  2. 约定 key 命名空间:沿用以斜杠分层的命名空间(如sql/<rdbms_name>),并在文档/代码注释中登记已使用的 key,防止不同插件间的 key 冲突。
  3. 写入时只写结论,读取时容忍缺失:读取方务必处理null返回值——依赖的前置插件可能未运行或未写入对应 key。
  4. 注意类型语义getBoolean对非 Boolean 值返回falsegetString对非 String 值返回null,读取前应确认写入值的类型。
  5. 记住 URI 级去 query 的键规约:按路径维度共享数据时,query 参数不同不影响键匹配;需要区分 query 的场景不应使用 Kb 的 URI 级存储。
  6. 共享范围即单次主机扫描:Kb 数据随 HostProcess 生命周期存续,跨扫描或跨主机需要的数据应改由 ZAP 会话或其他持久化机制承载。

七、小结

Kb 是 ZAP 主动扫描引擎中承担“插件间结果共享与依赖协作”职责的轻量级内存组件,其核心价值在于让不同扫描插件围绕同一主机/路径共享探测结论,从而减少重复探测、实现针对性的漏洞检测。其两级作用域(Host 级 / URI 级)、唯一值去重、getList/get的列表语义,以及“去 query 的 URI 键”等设计,均可在 Kb.java 与 KbUnitTest.java 中得到直接验证。开发者若需在扫描扩展中实现插件间数据依赖,可直接通过getKb()接入,并遵循本文的键规约与读写语义进行开发。

  • 网络安全
  • 应用安全
  • 开发工具

【免费下载链接】zaproxy

The ZAP by Checkmarx Core project

项目地址:https://gitcode.com/gh_mirrors/za/zaproxy
点击查看免费下载
上一篇:如何让老旧Mac焕发新生:OpenCore Legacy Patcher完整指南
下一篇:Mybatis Common Mapper类型转换器:EnumTypeHandler实战

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询