5.3 插件编写指南
前文系统性地阐述了 Yakit 插件系统的顶层设计理念与混合插件调用器(MixPluginCaller)的底层架构和插件生态系统。本章节将从实践应用视角出发,深入剖析 Yakit 平台中至关重要的插件分类体系,并对各类插件的适用场景、技术特性与核心价值进行原理级解读。
掌握不同插件类型的内在机制与开发范式,是安全研究人员高效利用并扩展 Yakit 平台能力的先决条件。本节旨在为开发者构建一个清晰的知识框架,帮助其根据具体安全任务需求,精准选择最合适的插件类型进行开发,从而将 Yakit 真正打造为个性化、自动化的安全研究工作台。
5.3.1 插件类型:定义扩展能力的基石
Yakit 插件系统的强大之处,在于其并非一个单一的、泛化的脚本执行环境,而是构建了一套基于功能特性和执行环境差异的精细化分类体系。这种设计理念深刻体现了对网络安全工作流的深度理解,确保了每种插件类型都能在特定场景下发挥最大效能。开发者在创建插件时做出的第一个选择——“插件类型”,从根本上决定了插件的交互模式、执行上下文以及可调用的平台资源。
要直观地理解这一分类体系,我们可以从插件管理的统一入口——插件仓库——开始。通过点击主界面顶部状态栏的 [插件] 按钮,并选择 [插件仓库],即可进入该功能模块。
如图所示,插件仓库是集插件浏览、管理与创建于一体的综合平台。其左侧的筛选栏中,“插件类型”是进行插件功能定位的首要维度。
图:Yakit插件商店界面展示分类与新建功能
当点击右上角的 [新建插件] 按钮时,系统会引导我们进入插件创建界面,其中“脚本类型”是必须指定的首要参数。这个选择列表完整地展示了 Yakit 的核心插件类别。
图:Yakit 插件创建界面与类型分类
Yakit 插件系统的设计精髓在于其分类体系。它并非一个扁平化的脚本集合,而是为不同安全任务场景量身打造了专用的插件类型。理解每种类型的技术定义、内在机制以及其核心应用场景,是开发者选择正确工具、高效解决问题的基础。本节将对五种核心插件类型进行逐一剖析,将其“是什么”与“用在哪”紧密结合,为读者构建一幅清晰的插件应用全景图。
5.3.1.1 Yak 原生插件:通用性与独立执行
技术定义:Yak 原生插件是 Yakit 平台中最通用、最基础的扩展形式。它直接使用 Yak 语言编写,具备图灵完备的编程能力,可以无限制地访问 Yaklang 的全套标准库。这使得开发者能够调用包括网络通信、文件 I/O、加解密运算、并发控制在内的所有系统级功能,实现任意复杂的业务逻辑,其功能扩展性几乎没有上限。
核心应用场景:
-
独立执行与交互式工具:原生插件最常见的用途是作为一个独立的脚本,从插件列表中直接运行。它可以设计接收用户输入,并在控制台输出结果,非常适合编写临时的、定制化的安全小工具或完整的自动化任务。
-
Yakit 内部功能基石:值得注意的是,Yakit 自身的许多核心功能,例如部分扫描器和自动化工具,其底层就是通过 Yak 原生插件技术封装实现的。这充分证明了原生插件作为平台功能扩展基石的强大能力和稳定性。
5.3.1.2 Yak-MITM 模块:流量Hook与双重上下文
技术定义:此类型插件专为 HTTP/HTTPS 流量的实时分析与干预而设计。其技术核心是基于Hook 机制,开发者在插件中声明的响应函数将会被 MITM 引擎在流量处理流水线的不同阶段自动调用,从而实现对请求和响应的透明检查与修改。
核心应用场景:
-
主应用场景:MITM 代理实时分析:这是 MITM 模块最经典的应用场景。在 Yakit 的 [MITM 代理] 工具中,加载的 MITM 插件可以作为被动插件(加载即生效,自动检测所有流量)或交互插件(需用户手动配置参数触发),对实时捕获的流量进行分析与篡改。
-
扩展应用场景:自动化批量扫描:MITM 插件的能力并不仅限于被动代理。在 [端口/指纹扫描] 、[批量执行] 和 **[基础爬虫] **等自动化任务中,MITM 插件可以被主动调用,对目标列表发起 HTTP 请求并应用其检测逻辑。此时,它的执行流程仍然和MITM代理一致,在自动化场景上复用了其强大的流量分析能力。
在3.2.3章,本书对于MITM模块在MITM代理中的使用进行了详细说明,用户可以重新回顾一下第三章的MITM插件使用内容。
5.3.1.3 扫描类插件:事件驱动与流程自动化
Yakit 将端口扫描的漏洞检测流程也抽象成了插件,它们的核心价值在于与扫描引擎的联动,实现高度的流程自动化和可定制化。
-
端口扫描插件
-
技术定义:此插件采用事件驱动模型,通过一个标准化的函数接口与扫描引擎交互。它的作用并非执行扫描,而是响应扫描结果,并进行进一步研判和漏洞上报。
-
应用场景:在 [端口/指纹扫描] 、[批量执行] 和 **[基础爬虫] **任务中,扫描引擎会将每一个发现的资产(包括 IP、端口、服务指纹等)作为
result参数,自动调用该插件的handle函数。开发者可在此函数内编写逻辑,实现资产信息的深化、自动触发后续漏洞扫描等操作。
-
-
Nuclei Yaml 插件
-
技术定义:此插件是 Yakit 对 Nuclei 社区生态的兼容层。它是一种声明式的插件,内容为符合 Nuclei DSL 规范的 YAML 模板。Yakit 内置的 YakVM 会负责解析和执行这些模板,将其转换为实际的检测请求。
-
应用场景:在 [端口/指纹扫描] 、[批量执行] 和 **[基础爬虫] **任务中,作为核心的漏洞检测逻辑。用户可以加载单个或多个 Nuclei 模板,对目标列表进行高效、标准化的漏洞扫描,是快速响应社区公开漏洞的利器。
-
5.3.1.4 Yak-Codec 插件:数据转换与 UI 深度集成
Codec 插件专注于数据的编解码与格式转换,其实现接口极为简洁:func(i: string) string。它接收一个字符串输入,并返回一个处理后的字符串输出。
核心应用场景:
-
上下文右键联动:Codec 插件的最大价值在于与 Yakit 图形化界面的深度集成。通过插件配置项,它会自动出现在 [Web Fuzz]、**[MITM 数据包] **等核心测试工具的右键菜单中。用户可以选中任意数据,一键调用自定义的编解码逻辑,这在处理私有协议、非标准加密时极为高效。
-
独立工具功能扩展:除了上下文联动,所有 Codec 插件也会在独立的 [Codec] 功能页面中列出,为用户提供一个集中的、可复用的数据处理工作台。这类插件可以在很多位置配合“右键菜单”直接使用,具体联动机制将在 5.3.3 节 进一步详细阐述。
5.3.2 代码即界面:Yakit 的声明式交互与 UI 自动化
Yak插件拥有Yak 强大的语言内置库功能,具备处理应对各种测试情况的能力,但是一个强大的工具不仅需要具备核心功能,更需要拥有灵活、易用的人机交互接口。本节将通过探讨 Yak cli 库,来讲述Yak中的插件输入设计。cli 库不仅仅是实现命令行参数解析的标准库,更是连接Yak脚本与 Yakit 图形化界面的核心桥梁。通过学习本章,您将掌握如何通过声明式参数定义,自动生成功能完备的命令行工具和与之配套的图形化操作界面,极大提升工具的复用性与易用性。
本章将围绕两大核心命题展开:声明式参数的底层解析机制与参数类型驱动的UI自动化生成。我们将从基础的数据类型解析入手,逐步深入到文件处理、结构化数据输入以及富文本UI组件的构建,最终为您揭示cli库在实际安全工具开发中的工程价值。
5.3.2.1 cli 核心机制:从参数声明到 UI 组件的自动映射
在传统的脚本开发中,开发者需要耗费大量精力编写繁琐的命令行参数解析与校验逻辑,这部分代码往往与核心业务逻辑耦合,降低了代码的可读性和可维护性。cli库借鉴了现代命令行框架的设计思想,采用“声明式”范式。开发者仅需在代码中声明期望的参数名称、类型、默认值和帮助信息,库本身便会自动处理所有底层的命令行解析、类型转换和错误校验工作。这一设计的核心优势在于将业务逻辑与参数处理完全解耦,使开发者能更专注于工具核心功能的实现。
更进一步,cli库的设计深度融合了 Yakit 的图形化生态,这构成了其最独特的价值。当一个使用cli库编写的 Yak 代码在 Yakit 平台中被加载时,Yakit 的前端引擎会静态分析或在运行时解析这些参数声明。依据每个cli函数的类型(如 cli.String, cli.File, cli.HTTPPacket),引擎会动态地、自动化地渲染出与之对应的图形化UI组件(如文本框、文件选择器、HTTP报文编辑器等)。
这种“一次定义,双端运行”(命令行CLI与图形GUI)的机制,是cli库区别于其他常规参数解析库的根本特征,也是其工程价值的关键所在,它为脚本工具赋予了媲美原生应用的交互体验。
图:Yakit CLI参数声明到UI组件映射流程
图注:展示 Yak 脚本中的
cli定义,经过 Yakit 引擎解析,最终在前端渲染为动态表单,用户提交表单后,参数被填充并执行脚本的完整流程。
我们可以将上图所示的完整交互分解为两个核心阶段来理解:
-
阶段一:动态表单生成 (步骤 1-7) 当用户在 Yakit GUI 中打开一个包含
cli定义的插件时,前端会向 Yakit Engine 发起请求,以获取该脚本的 UI 描述。Yakit Engine 会对脚本代码进行静态分析或运行时扫描,精准地提取所有cli.*函数的声明(步骤 3-4)。随后,引擎将这些参数定义(类型、名称、帮助文本等)封装成一个标准化的 UI Schema(通常为 JSON 格式),并返回给前端(步骤 5)。Yakit GUI 根据这个 Schema,自动、动态地渲染出一个与之完全对应的交互式表单界面(步骤 6-7),无需开发者编写任何前端代码。 -
阶段二:参数化执行 (步骤 8-13) 用户在图形化表单中填写参数并点击执行后(步骤 8),前端会将用户输入与表单路径一同提交给 Yakit Engine(步骤 9)。引擎接收到请求后,将这些参数注入到即将执行的 Yak 脚本上下文中。此时,脚本中的
cli.Check()函数作为参数的最终获取者和校验器被调用,它从上下文中读取数据,完成最后的合法性校验,并将结果返回给业务逻辑(步骤 10-11)。脚本的核心逻辑执行完毕后,其输出(如日志、结果)被推送回前端界面进行展示(步骤 12-13),完成一次完整的、从 GUI 到脚本的调用闭环。
接下来我们通过对 cli库 中的函数进行分类讨论,依次介绍其对表单UI生成的影响。
cli 库的应用场景与形态演化
cli 库的这种“声明即 UI”的特性,使其在不同类型的插件中扮演着关键角色,甚至能改变插件的交互形态。
场景一:为原生插件赋予图形化界面
在 Yak 原生插件 (5.2.1.1) 中使用 cli 库,是其最直接的应用。它能将一个原本只能在命令行中运行的脚本,无缝转化为一个拥有独立、友好图形化界面的工具。这极大地降低了插件的使用门槛,使得复杂的、需要多参数配置的自动化任务也能被轻松地分发和使用。
图:Yakit插件根据cli生成的参数列表
场景二:驱动 MITM 插件的形态演化
此场景更能体现 cli 库的强大之处。默认情况下,Yak-MITM 插件 (5.2.1.2) 是被动工作的,加载后会对所有流经的流量执行检测逻辑。然而,一旦在 MITM 插件中引入 cli 参数定义,该插件的形态会从“被动插件”演化为“交互插件”。Yakit 会将其识别为需要用户输入才能触发的模式,在 MITM 的插件列表中,点击该插件会弹出一个由 cli 生成的参数配置窗口。
图:Yakit插件系统通过CLI生成交互式参数
更详细的内容和使用方法,用户可以在第三章中找到案例。
这种机制极大地增强了 MITM 插件的灵活性,使其从一个“始终在线”的被动分析器,演变为一个可按需触发、携带复杂上下文(如自定义密钥、特定规则)的“交互式干预工具”。这为实现高度定制化的流量分析与篡改提供了坚实的基础。
5.3.2.2 基础数据类型参数解析
掌握了 cli 库的设计哲学后,我们从最基础也是最常用的数据类型入手,逐一解析其在命令行和图形化界面中的具体实现。这些基础类型构成了所有复杂参数的基石,深刻理解其工作原理是构建健壮工具的第一步。我们将通过具体的函数和案例,展示如何声明并使用字符串、数值及布尔类型的参数。
字符串类型
字符串是人机交互中最普遍的数据载体,无论是URL、文件名还是搜索关键词,其本质都是文本信息。cli.String 和 cli.Text函数是处理文本输入最基础且最常用的接口,分别用于单行文本与多行文本
-
函数定义
函数 (Function) GUI 渲染形式 (GUI Widget) 核心功能与应用场景 cli.String 单行文本框 定义标准的字符串输入,适用于用户名、标识符等简短文本。 cli.Text 多行文本框 定义大段文本输入,适用于自定义 Payload、配置、备注等。 -
实验案例
以下代码演示了如何定义一个单行和一个多行文本输入参数:
cli.String("testString")
cli.Text("testText")
cli.check()
图:Yakit插件系统字符串类型参数配置界面
在上述实验案例的截图中,我们观察到 Yakit 依据代码自动生成的表单中,“额外参数”下的标签(Label)默认采用了 cli.String 函数中定义的参数名(如 testString)。然而,为了提升界面的可读性和用户友好度,我们常常需要一个与程序内部变量名不同的、更具描述性的标签。cli 库为此提供了 cli.setVerboseName() 选项函数,通过 cli.String("testString", cli.setVerboseName("自定义标签名")) 的方式,即可轻松自定义 GUI 中的显示名称。关于此函数及其他通用参数配置的详细用法,请参阅 5.2.2.6 通用选项函数 章节。
数值与布尔类型
在工具开发中,开发者经常需要配置如线程数、超时时间、端口号等数值参数,以及用于控制程序行为的开关式布尔参数。cli 库为此提供了直观的整数、浮点数及布尔类型接口,并能自动渲染为交互友好的UI组件。
-
函数定义
函数 (Function) GUI 渲染形式 (GUI Widget) 核心功能与应用场景 cli.Int / cli.Integer 数字输入框(提供上下箭头方便调节) 定义整数输入,内置格式校验,适用于线程数、ID 等。 cli.Bool 开关(Switch) 定义布尔值,用于表示“是/否”或“启用/禁用”状态的开关。 cli.Float / cli.Double 数字输入框(提供上下箭头方便调节) 定义浮点数输入,适用于需要高精度数值的场景。 -
实验案例
以下代码展示了如何声明整数、布尔和浮点数这三种类型的参数。
cli.Int("testInt")
cli.Bool("testText")
cli.Float("testFloat")
cli.check()
图:Yakit插件数值与布尔参数配置界面
5.3.2.3 结构化数据输入
在上一节中,我们详细探讨了 cli 库如何处理字符串、数值和布尔等基础数据类型,这些是构成命令参数的基础单元。然而,在真实的自动化任务中,我们常常需要处理更为复杂的数据集合,例如一次性输入多个目标,或者加载多个字典。本节则会聚焦集合数据的输入,从两个角度讲解对于此问题的解决方案。
面向安全场景的特定集合输入
Yakit 作为一个专注于网络安全的平台,cli 库内置了一些针对安全测试场景优化的参数输入函数类型。这些函数的渲染形式并没有与字符串有区别,但是对数据进行特定的处理,从字符串中解析出表达的数据集合。
- 函数定义
| 函数 (Function) | GUI 渲染形式 (GUI Widget) | 核心功能与应用场景 |
|---|---|---|
| cli.Host / cli.Hosts / cli.Net / cli.Network | 单行文本框 | 接收主机、IP 或 CIDR 网段。自动解析逗号分隔及 CIDR 格式,返回展开后的 IP 列表。 |
| cli.Port/ cli.Ports | 单行文本框 | 接收端口。支持 80,443(多端口)和 8000-9000(端口范围)等格式,返回整数切片。 |
| cli.Url/ cli.Urls | 单行文本框 | 接收 URL。校验标准 URL 格式,并支持逗号分隔的多个 URL。 |
- 实验案例
host = cli.Host("testHost")
port = cli.Port("testPort")
url = cli.Url("testUrl")
cli.check()
yakit.Output(host)
yakit.Output(port)
yakit.Output(url)
图:Yakit插件特定集合输入参数与终端输出
约束集合输入
当一个任务需要作用于多个对象时,例如对多个IP地址进行端口扫描,使用切片(Slice)类型的参数是标准实践。cli.StringSlice 是对切片类型的良好实现,其不仅支持从命令行接收一个开放的参数列表,更能在图形化界面中通过选项函数(Option Functions)提供约束性、交互性更强的选择器,从而极大地提升用户体验。
为了在 Yakit 的图形化界面中提供更友好、更规范的输入方式,cli 库为 StringSlice 提供了两个重要的配套选项函数:cli.setSelectOption 和 cli.setMultipleSelect。它们将一个简单的输入框升级为功能丰富的下拉选择器。
| 函数 (Function) | 函数介绍 |
|---|---|
| cli.setSelectOption(displayName, value) | 此选项函数用于定义一个下拉选项。它接收两个参数:displayName 是在UI上向用户展示的文本,而 value 是用户选择后,程序实际接收到的字符串值。你可以连续使用多个 setSelectOption 来定义一个完整的选项列表。 |
| cli.setMultipleSelect(bool) | 此选项函数作为一个修饰符,用于决定下拉框是否支持多选。它必须与 setSelectOption 配合使用。当设置为 true 时,UI组件将渲染为多选框;如果省略或设置为 false,则渲染为单选下拉框。 |
案例解析:单选与多选下拉框的实现
以下代码清晰地展示了如何利用这些选项函数来构建不同的UI交互界面。
singleSelect := cli.StringSlice("singleSelect",
cli.setSelectOption("选项一", "select1"),
cli.setSelectOption("选项二", "select2"),
)
multipleSelect := cli.StringSlice("multipleSelect",
cli.setSelectOption("选项一", "select1"),
cli.setSelectOption("选项二", "select2"),
cli.setMultipleSelect(true),
)
cli.check()
println(f"单选结果: %v", singleSelect)
println(f"多选结果: %v", multipleSelect)
上述代码在 Yakit GUI 中会产生两种截然不同的参数输入界面:
- 单选下拉框 (
singleSelect):UI渲染为一个标准的下拉菜单。用户点击后,会看到“选项一”和“选项二”,选择其中一个后,输入框便会显示所选项。
图:Yakit插件参数组下拉选择界面
- 多选下拉框 (
multipleSelect):由于setMultipleSelect(true)的作用,UI组件变为一个支持多选的输入框。用户可以多次点击下拉菜单选择不同的选项,所有已选项会以标签(Tag)的形式清晰地陈列在输入框内。
图:Yakit插件约束集合输入界面
通过这种方式,StringSlice 不再仅仅是一个简单的命令行参数聚合工具,它成为了连接后端逻辑与前端友好交互的桥梁,使得开发者能够构建出既能在自动化流程中被命令行调用,又能在图形化界面中提供直观操作的专业工具。
5.3.2.4 文件与内容处理:实现与外部数据的高效联动
在本节此前的内容中,我们已经掌握了如何通过 cli 库处理从命令行或UI直接输入的各类数据,包括基础类型、集合数据。然而,在真实的安全测试或数据处理任务中,输入源往往更加复杂,例如加载一个包含数千个目标地址的文本文件、读取一个特定的配置文件,或是选择一个目录进行批量处理。手动编写代码来处理文件路径、读取内容和错误处理,会显著增加脚本的复杂性。
本节将引入cli库中用于文件与内容处理的一组高级函数。这组函数的核心设计思想是将文件I/O的复杂性抽象化,并为之匹配专门的UI组件。
单文件内容读取
在诸多安全评估与数据处理任务中,我们面临的往往不是单个目标,而是一个目标列表,例如一批待检测的域名、一组需要验证的 IP 地址或是一系列用户凭证,这些数据往往按行分割存在于文件系统中。
为了解决这一核心痛点,Yakit 的 cli 库提供了 cli.LineDict 函数。这是一个专门为处理多行文本输入而设计的参数类型。其核心机制是将用户在图形界面输入的多行文本,按照换行符 \n 进行切割,最终转换为一个字符串切片([]string)。
这种设计极大地简化了批量数据的接收和处理流程。开发者无需关心数据是通过直接粘贴还是文件上传获得的,只需将 cli.LineDict 返回的变量作为一个标准的字符串列表来使用即可。这种高度的抽象,使得脚本编写者可以专注于数据处理的业务逻辑,从而显著提升开发效率和脚本的可用性。
接下来通过一个简单的例子来使用这个函数:
lines = cli.LineDict("testLineDict")
cli.check()
yakit.Output(lines)
当该脚本在 Yakit 中加载时,cli.LineDict("testLineDict") 会动态生成一个专用的复合输入组件。如下图所示,该组件提供了两种核心的数据输入方式:直接文本粘贴与文件上传。
图:Yakit插件单文件内容读取界面
工作流步骤分解如下:
图:Yakit插件参数组文件读取与内容解析流程
-
用户交互起点:在右侧的参数面板中,用户找到
testLineDict参数。与直接在文本框中输入不同,用户选择了更高效的文件加载方式,点击了文本框下方的“点击此处 上传”链接。 -
调用系统资源:该操作会立即触发并调用操作系统的原生文件选择对话框。这种设计符合用户习惯,提供了稳定、可靠的文件选择体验。在此案例中,用户在
test-data目录下选择了名为test.txt的文件。 -
内容读取与填充:当用户确认选择后,Yakit 的前端逻辑会即刻读取
test.txt文件的内容。假设test.txt包含两行文本:“mytest1”和“mytest2”。Yakit 会将这两行文本原封不动地填充到testLineDict的多行文本输入框中。这是一个关键步骤:文件内容被转化为了文本框中的可见内容。 -
最终数据处理:当用户点击“执行”按钮时,
cli.check()函数被调用。此时,cli.LineDict的后端逻辑会获取testLineDict文本框中当前的所有文本,并以换行符\n为分隔符进行切割,生成一个[]string切片。在这个案例中,lines变量最终被赋值为[]string{"mytest1", "mytest2"}。
而当脚本需要读取单个文件的完整内容时,例如加载一份POC/EXP的原始报文、一个JSON配置文件,cli.File 是最直接的解决方案。cli.File 的核心使命是获取单个文件的完整原始内容。接下来将通过一个具体的实践案例,图解 cli.File 从脚本定义到最终内容输出的全过程。
脚本的意图非常明确:定义一个名为 testFile 的文件参数,其目标是读取用户所选文件的全部内容,并将这些内容存入 filecontent 变量,最后通过 yakit.Output 进行展示。
filecontent = cli.File("testFile")
cli.check()
yakit.Output(filecontent)
脚本在 Yakit GUI 中运行时,cli.File("testFile") 会生成一个专门用于单文件上传的UI组件。与 cli.LineDict 将文件内容填充至文本框不同,cli.File 的交互更为直接,它在后台完成数据绑定。
图:Yakit插件系统单文件内容读取界面
工作流步骤分解如下:
-
用户交互起点:在右侧参数面板,用户面对
testFile参数。UI组件明确提示“可将文件拖入框内或点击此处上传文件”。用户点击链接,触发文件选择操作。 -
调用系统资源:Yakit 随即弹出操作系统的文件管理器,用户在此选择目标文件。在本例中,用户选择了包含文本“mytest”的
test.txt文件。 -
后台数据绑定:这是
cli.File与cli.LineDict的一个关键区别。在用户选择文件后,Yakit 的UI不会将文件内容显示在输入框中,而是在后台直接将文件与该参数进行绑定。UI上仅显示已选择的文件名。 -
后端处理与结果获取:当用户点击“执行”时,
cli.check()函数被调用。cli库的后端逻辑此时会读取文件的完整内容,将其作为一个[]byte切片,并赋值给脚本中定义的filecontent变量。 -
结果输出:脚本继续执行,
yakit.Output(filecontent)将变量中存储的字节流内容输出到结果面板。如图所示,test.txt文件中的“mytest1\nmytest2”被成功读取并完整地显示出来。
图:Yakit插件单文件内容读取界面
文件系统路径输入
在某些场景下,脚本需要的不是文件内容,而是文件或目录的路径本身,以便进行迭代处理、移动或作为其他命令的参数。
文件路径
cli 库提供了 cli.FileNames 函数,它专注于高效、批量地获取文件路径列表。本节将通过一个直观的案例,深入剖析 cli.FileNames 如何将用户在 GUI 上的多文件选择操作,转化为脚本可直接使用的字符串路径列表。
脚本的核心目标是获取用户选择的多个文件的路径,并将这些路径存储在一个字符串切片中,最终进行输出。
fileNames = cli.FileNames("testFileNames")
cli.check()
yakit.Output(fileNames)
图:Yakit插件文件系统路径输入界面
cli.FileNames 在 Yakit GUI 中生成了一个支持多文件选择和路径编辑的复合输入组件,其工作流与 cli.File 存在本质区别:
图:Yakit插件系统文件系统路径输入操作演示
工作流步骤分解如下:
-
用户交互起点:用户在右侧参数面板点击
testFileNames参数的上传链接,调用操作系统的文件管理器。与cli.File不同,文件管理器此时支持多选操作(例如,通过按住Ctrl或Shift键)。在本例中,用户同时选择了test.txt和test2.txt两个文件。 -
路径识别与填充:这是
cli.FileNames的核心交互特性。当用户确认选择后,Yakit 不会读取文件内容。相反,它会获取所有被选中文件的绝对路径,并将这些路径以换行分隔的形式,自动填充到testFileNames参数对应的文本输入框中。如图所示,UI上明确提示“识别到 2 个文件路径”,并且输入框内清晰地显示了两个文件的完整路径。 -
后端处理与结果获取:当用户点击“执行”按钮,
cli.check()函数被调用。cli库的后端逻辑会读取文本框中的多行路径字符串,通过换行符进行分割,并整理成一个规范的字符串切片([]string),然后将其赋值给脚本中定义的fileNames变量。 -
结果输出:脚本继续执行
yakit.Output(fileNames),将这个包含两个文件绝对路径的字符串切片输出到结果面板。从执行结果中可以看到,输出是一个标准的 JSON 数组格式,精确地反映了用户所选择的文件列表。
核心价值与应用场景
cli.FileNames 的设计巧妙地解决了批量文件处理的“入口问题”。它与 cli.File 的关键区别在于:cli.FileNames** 关注的是“文件在哪里”(路径),而 cli.File 关注的是“文件是什么”(内容)**。
开发者在需要编写一个工具来批量操作文件(如批量重命名、批量扫描、批量上传元数据等)时,应优先选用 cli.FileNames。它为最终用户提供了直观的多文件选择体验,同时为脚本开发者提供了干净、可直接迭代的路径列表,避免了手动处理文件路径解析和格式化的复杂工作。
文件夹路径
在完成了对文件级(cli.File, cli.FileNames)的参数交互学习后,自然而然思考到另一类核心需求:操作整个目录。无论是需要对一个网站的本地源码进行目录遍历,还是指定一个统一的输出文件夹来存放扫描结果,直接获取一个目录的路径都是自动化脚本中的基础操作。
为了满足这一场景,cli 库提供了 cli.FolderName 函数。与处理文件不同,它的目标是获取一个单一文件夹的路径。本节将通过一个清晰的实例,演示 cli.FolderName 如何将用户在图形界面中的目录选择行为,无缝转换为脚本中可用的单个路径字符串,从而为目录级操作提供坚实的基础。
脚本的核心任务是接收用户指定的一个文件夹,并将其绝对路径作为单个字符串输出。
FolderName = cli.FolderName("testFolder")
cli.check()
yakit.Output(FolderName)
相较于 cli.FileNames 返回 []string,cli.FolderName 在 cli.check() 执行后,会将一个 string 类型的路径赋值给 FolderName 变量。
图:Yakit插件参数组中的文件夹路径输入界面
cli.FolderName 在 Yakit GUI 中会生成一个专用于选择目录的输入组件,其交互流程精准且高效。
图:Yakit插件参数配置与文件管理器界面
工作流步骤分解如下:
-
用户交互起点:在右侧参数面板中,用户点击与
testFolder参数关联的“上传文件夹”链接。此操作会触发操作系统原生的文件夹选择对话框。当然这里也可以直接输入 -
目录选择:这是与
cli.FileNames的关键区别。该对话框的设计目的就是选择目录,而不是文件。用户在文件系统中导航,并选择目标文件夹(在本例中为test-data),然后确认选择。 -
路径自动填充:一旦用户确认,Yakit 会立即获取所选文件夹的绝对路径,并将其作为单行文本自动填入
testFolder参数的输入框中。整个过程用户无需手动复制粘贴路径,极大地降低了操作失误的风险。 -
后端处理与结果获取:当用户点击“执行”时,
cli.check()函数读取输入框中的路径字符串。由于cli.FolderName预期的是单个目录,它直接将这个字符串赋值给脚本中定义的FolderName变量。 -
结果输出:最后,
yakit.Output(FolderName)将这个字符串变量输出到结果面板。执行结果清晰地显示了所选test-data文件夹的完整绝对路径。
图:Yakit 插件系统显示文件系统路径
核心价值与应用场景
cli.FolderName 的核心价值在于为需要指定作用范围或定义输出位置的脚本提供了一个标准化的、用户友好的交互接口。它与文件操作函数形成了功能互补:
-
输入端应用:当编写需要对整个目录进行递归扫描、文件搜索或分析的工具时(如代码审计工具、配置检查脚本),使用
cli.FolderName来指定根目录是最佳实践。 -
输出端应用:当脚本需要生成多个报告文件、日志或下载资源时,可以通过
cli.FolderName让用户指定一个统一的存放目录,从而保持输出文件的整洁和有序。
通过掌握 cli.FolderName,开发者可以构建出交互逻辑更完整、功能更强大的自动化工具,覆盖从文件到目录的各类操作场景。
5.3.2.5 富文本与专用UI组件:提升图形化界面交互体验
在前面的章节中,我们已经掌握了如何通过 cli 库处理基础数据类型(如字符串、整数)以及文件系统对象(如文件、目录)。然而,在现代化的安全工具中,用户输入的往往不是单一的值,而是结构化的、具有特定语法的复杂数据,例如一个完整的HTTP请求报文、一段待执行的代码或一个复杂的JSON配置对象。若依然使用标准的单行输入框来承载这类信息,将极大地降低可用性并增加出错的概率。
为了解决这一挑战,Yakit的 cli 库引入了一系列专用的UI组件。这些组件的核心设计理念是**“优雅降级”**。在Yakit的图形化界面(GUI)中,它们会被渲染为功能丰富的专用编辑器,提供语法高亮、格式校验等高级功能;而在纯粹的命令行(CLI)环境中,它们则会无缝地退化为标准的字符串参数(cli.String),从而确保了脚本在不同运行环境下的兼容性与可移植性。本章将深入探讨这些高级组件的原理与实践,展示如何通过它们构建出交互体验远超传统命令行工具的专业级脚本。
HTTP报文编辑器
在Web安全测试场景中,直接对HTTP请求报文进行修改、重放和Fuzzing是最为常见的操作之一。一个HTTP请求本身是具有严格语法结构的多行文本,包含了请求行、请求头和请求体。使用普通文本框处理此类数据,不仅缺乏可读性,也无法对语法的正确性提供任何辅助。cli.HTTPPacket 正是为了解决此问题而设计的。它在GUI中会渲染为一个专用的HTTP报文编辑器,能够对报文的不同部分(如请求方法、路径、Host头、普通头、Body等)进行语法高亮,极大地提升了可读性和编辑效率。
以下脚本定义了一个名为 req 的参数,用于接收一个完整的HTTP请求报文。
httpPacket = cli.HTTPPacket("testHTTP", cli.setRequired(true))
cli.check()
yakit.Output(httpPacket)
当脚本运行时,Yakit GUI会解析 cli.HTTPPacket("req") 这行代码,并动态生成对应的UI组件。用户可以在这个编辑器中直接撰写或粘贴一个HTTP请求。编辑器的语法高亮功能会立刻生效,帮助用户快速识别报文结构,避免格式错误。当用户点击“执行”时,编辑器内的完整文本内容将被提取出来,作为一个单一的字符串赋值给 packet 变量,供后续脚本逻辑处理。这种机制确保了从高级UI到基础数据类型的无缝转换。
图:Yakit插件中HTTP报文编辑器参数配置界面
Yak代码编辑器
cli.YakCode 的应用场景更进一步,它主要用于实现“元编程”或构建可扩展的插件化框架。当一个主脚本需要动态接收并执行另一段Yak代码作为其逻辑的一部分时,cli.YakCode 提供了最佳的交互方式。它在Yakit GUI中被渲染为一个功能完备的、内嵌式的代码编辑器,提供语法高亮和智能提示等高级功能,极大地提升了代码编写的效率和准确性。而在底层数据流中,编辑器内的所有内容最终会被无缝转换为一个标准的字符串变量,供主脚本调用和处理。这种设计完美地平衡了高级交互与底层实现的简洁性,是构建可扩展工具的基石。
实验案例
假设我们正在编写一个通用的扫描器,并希望允许用户通过参数传入一段自定义的漏洞验证逻辑(PoC)。
pocScript := cli.YakCode("poc")
cli.check()
yakit.Output("用户传入的PoC脚本内容:\n" + pocScript)
如图左上角所示,cli.YakCode("poc", ...) 这一行代码是向Yakit GUI发出的一个明确指令。Yakit在解析到此指令时,并不会像 cli.String 那样生成一个简单的文本输入框,而是会动态地在参数面板中生成一个功能完备的Yak代码编辑器,并将其与名为 poc 的参数进行绑定。
图:Yak代码编辑器生成与输出流程示意
一旦UI生成,用户便可以在这个专用的编辑器中编写Yak代码。这并非一个简单的文本区域,而是一个小型化的IDE(集成开发环境)。它具备以下核心优势:
-
语法高亮:对Yak语言的关键字(如
func)、字符串、数字等进行着色,代码结构一目了然。 -
智能提示与自动补全:如上图右侧所示,当用户输入
poc.时,编辑器能够智能地提示可用的函数(如poc.Get)及其签名和文档,极大地降低了用户记忆API的负担,并能有效避免拼写错误。
在这个案例中,用户在编辑器内输入了一行代码:poc.Get("``https://yakit.com``")。
Yakit GUI会将代码编辑器中的全部文本内容——poc.Get("``https://yakit.com``")——作为一个单一的字符串,赋值给脚本中定义的 pocScript 变量。此时,从脚本的视角来看,pocScript 与通过 cli.String 获取的普通字符串并无任何区别。
上图左下角的输出结果完美地验证了这一点。yakit.Output 函数成功打印出了 pocScript 变量的内容,证明了从富文本代码编辑器到后台纯字符串数据的无缝转换。后续,主脚本可以对 pocScript 字符串进行进一步处理,例如使用 eval 等函数动态执行这段代码,从而实现“插件化”或“元编程”等高级功能。
高级动态结构化表单
JSON Schema 是一种用于定义JSON数据结构的规范。通过提供一个JSON Schema,我们可以精确描述一个JSON对象应有的字段、每个字段的数据类型(字符串、数字、布尔、数组、对象)、是否为必填项、默认值、枚举值等。
cli.Json会解析这个Schema,并动态生成一个结构化的、用户友好的图形化表单来代替原始的文本编辑器。这种方式将用户从手动编写复杂、易错的JSON字符串中解放出来。
这是 cli 库中最为强大和复杂的UI组件,它解决了终极的复杂参数输入问题:如何优雅地接收一个具有深度嵌套结构、严格类型和约束的配置对象。甚至于在某些专业场景下,开发者不仅需要定义数据的结构,还希望精确控制其在GUI中的视觉表现和布局。为了满足这一终极需求,cli 库引入了 cli.setUISchema 以及 cli.setJsonSchema 选项参数。
cli.Json 表单的核心思想是**“数据与视图分离”(Separation of Data and View)**。
-
JSON Schema 负责定义数据的模型(Model):数据应该包含哪些字段、类型和约束;
-
UI Schema 则负责定义视图(View):这些数据字段应以何种控件(Widget)形式、何种布局方式呈现给用户。
这种分离的设计原则使得开发者能够构建出交互逻辑极其复杂、视觉呈现高度定制化的参数面板,其体验甚至可以媲美原生开发的应用程序界面。
当 cli.Json 同时接收到 JSON Schema 和 UI Schema 时,其渲染逻辑会发生根本性变化。它不再是简单地根据数据类型选择默认控件,而是会严格遵循 UI Schema 的指令进行布局。UI Schema 提供了一系列函数,如 cli.uiFieldWidget 来指定渲染控件(例如,将一个数组渲染成表格 cli.uiWidgetTable),cli.uiTableField 来定义表格的列及其宽度,以及 cli.uiGroup 来组织布局,从而实现对最终UI的像素级控制。
Yakit内置插件: “修改 HTTP 请求 Header” 是此组件的一个范例。插件代码中关于 此组件的部分如下:
args = cli.Json(
"header",
cli.setJsonSchema(
<<<JSON
{
"type": "object",
"properties": {
"header": {
"type": "array",
"title": "Header",
"items": {
"properties": {
"key": {
"type": "string",
"title": "HTTP Header"
},
"value": {
"type": "string",
"title": "Value"
}
},
"require": [
"key",
"value"
]
}
}
}
}
JSON,
cli.setUISchema(
cli.uiGroups(cli.uiGroup(cli.uiField(
"header",
1,
cli.uiFieldWidget(cli.uiWidgetTable),
cli.uiFieldGroups(cli.uiGroup(cli.uiField(
"items",
1,
cli.uiFieldGroups(cli.uiGroup(cli.uiTableField("key", 120), cli.uiTableField("value", 350))),
))),
))),
cli.uiGlobalFieldPosition(cli.uiPosHorizontal),
),
),
cli.setRequired(true),
)
-
数据模型 (
JSON Schema):代码中的cli.setJsonSchema部分定义了数据的内在逻辑。它声明了我们期望得到一个名为header的数组 ("type": "array")。这个数组中的每一项(items)都是一个对象,该对象必须包含key和value两个字符串类型的属性。值得注意的是,properties中定义的title属性(如"title": "HTTP Header")被 Yakit 智能地用作了表格的列标题,这是数据模型影响视图的一个便捷特性。 -
视觉表现 (
UI Schema):这是实现定制化界面的关键。-
cli.uiField("header", ..., cli.uiFieldWidget(cli.uiWidgetTable)): 这是核心指令。它明确告诉 Yakit 的渲染引擎,找到名为header的字段,并使用uiWidgetTable(表格控件)来渲染它,而不是默认的数组输入框。 -
cli.uiTableField("key", 120)和cli.uiTableField("value", 350): 这两条指令进一步深入到表格内部。它们将数据模型中的key和value属性分别映射到表格的两列,并精确地指定了这两列的显示宽度为 120px 和 350px。 -
uiGroup和uiField的嵌套结构是 UI Schema 用来组织和应用规则的语法,使得复杂的布局规则可以被清晰地表达。
-
图:Yakit 高级动态结构化表单界面
如上图所示,Yakit GUI 忠实地执行了这些指令,最终生成了一个功能完备的表格编辑器。用户可以通过“+ 添加一行数据”按钮来增加新的 Header,在对应的输入框中填写键和值。每一行都配有“操作”菜单,允许对单条数据进行保存、取消编辑或删除。
当用户完成编辑并执行脚本时,整个表格的数据将被序列化为一个符合 JSON Schema 规范的 JSON 字符串,并赋值给 args 变量,其格式如下:
{
"header": [
{"key": "myHeader", "value": "value1"},
{"key": "Another-Header", "value": "some-value"}
]
}
通过 cli.Json 结合 JSON Schema 与 UI Schema,Yakit 将命令行脚本的参数定义提升到了一个全新的维度。开发者不再受限于简单的键值对,而是能够为自己的工具量身打造出具有高度可用性和专业性的图形化配置界面。这种“代码即UI”的能力,是连接脚本自动化与人性化交互的桥梁,也是 Yakit 设计哲学中追求极致效率与体验的集中体现。掌握这一高级特性,意味着您已经具备了构建平台级、应用级安全工具的开发能力。
5.3.2.6 通用选项函数
本节将深入探讨 cli 库提供的一组通用选项函数(Option Functions)。这些通用选项函数可以像“修饰符”一样作用于几乎所有的 cli 参数类型,以一种声明式、非侵入的方式来增强参数的功能和表现。这种设计不仅使代码更具可读性和可维护性,也是Yakit实现“代码即UI”哲学的关键所在。
在深入具体函数之前,理解其背后的设计思想至关重要。Yakit cli 库广泛采用了在 Go 语言中备受推崇的函数式选项模式(Functional Options Pattern)。其本质在于,将配置项封装成一个个独立的函数,而不是将所有配置都作为主函数的参数。
核心选项函数
设想我们正在开发一款基础的Web漏洞扫描插件或一个信息收集工具。这类工具的起点,几乎无一例外,都是要求用户提供一个明确的、单一的执行目标,例如一个域名或IP地址。这个目标是工具执行的核心与前提,因此它必须是一个必填项。为了让用户界面更加友好,我们不希望直接展示内部使用的变量名 target,而是需要一个更具描述性的展示名称,如“目标主机地址”。为了方便快速测试或演示,提供一个默认值(例如官方网站)能极大提升便利性。最后,考虑到用户可能输入不规范的格式,提供一段清晰的帮助文本来指引用户正确输入,是保证程序健壮性的关键一环。
将上述所有需求整合起来,我们便能通过 cli 库的函数式选项,以一种极为优雅和声明式的方式来实现。以下代码片段完整地展示了这一过程:
target:= cli.String("target",
// 1. 设置为必选参数, 必选参数会直接在插件执行页面展示,非必须则会收缩到额外参数中
cli.setRequired(true),
// 2. 设置在 GUI 和帮助文档中显示的友好名称
cli.setVerboseName("目标主机地址"),
// 3. 提供详细的帮助信息
cli.setHelp("请输入一个有效的目标,可以是域名(例如 example.com)或 IP 地址(例如 8.8.8.8)。"),
// 4. 设置默认值
cli.setDefault("www.yaklang.com")
)
cli.check()
这种模式的优势是显而易见的:
-
高可读性:每个选项的功能都由其函数名清晰表达,一目了然。
-
高可扩展性:未来增加新的选项时,只需新增一个选项函数即可,无需修改现有函数的签名,保持了API的向后兼容性。
-
灵活性:开发者可以按需组合任意数量的选项,未使用的选项则完全不必出现在代码中
现在,我们将结合下图,逐一解析最核心的几个通用选项函数,并展示它们如何从代码定义精准地转换为用户眼前的GUI组件。
图:Yakit插件核心选项参数配置界面
-
**参数展示名: **
cli.setVerboseName在代码中,参数由其编程标识符(如"target")来引用,但这通常不适合直接展示给最终用户。cli.setVerboseName函数的作用就是为此参数设置一个更友好、更具描述性的“展示名”。-
代码:
cli.setVerboseName("目标主机地址") -
GUI映射: 如图所示,参数的标签(Label)被渲染为“目标主机地址”,而不是内部标识符
"target"。这使得界面对于非开发人员也同样清晰易懂。
-
-
**必选参数约束: **
cli.setRequired在许多场景下,某些参数是程序执行的必要前提。cli.setRequired(true)提供了声明这种约束的机制。-
代码:
cli.setRequired(true) -
GUI映射: Yakit GUI通过两种方式在视觉上强调必选参数。首先,在参数展示名前方添加一个红色的星号
*,这是一个广泛被用户理解的必填标记。其次,Yakit通常会将必选参数默认展示在主界面,而将可选参数收纳于“额外参数”区域中,优化了界面的信息层级。
-
-
**默认值预填: **
cli.setDefault为参数提供一个合理的默认值,可以简化用户的操作,减少输入错误。-
代码:
cli.setDefault("``www.yaklang.com``") -
GUI映射: 对应的输入框在初始状态下会自动填充上设定的默认值,即图中的
"``www.yaklang.com``"。用户可以直接使用该值执行,也可以根据需要进行修改。
-
-
**上下文帮助信息: **
cli.setHelp对于功能复杂或格式有特殊要求的参数,提供即时的帮助信息至关重要。-
代码:
cli.setHelp("请输入一个有效的目标,可以是域名 (例如 ``example.com``) 或 IP 地址 (例如 8.8.8.8) 。") -
GUI映射: 该函数会在参数展示名后生成一个信息图标
ⓘ。当用户的鼠标悬停在此图标上时,一个提示框(Tooltip)会浮现,展示所设置的帮助文本。这是一种优雅且不干扰主界面的信息呈现方式。
-
参数分组选项函数
在设计任何复杂系统时,"分类"和"模块化"都是至关重要的思想。对于命令行工具或其GUI前端而言,参数就是其配置接口的“原子”。当原子数量过多时,就需要将它们按功能、作用域或逻辑关系组合成“分子”,即参数组。例如,一个网络扫描工具可能包含“目标配置”(主机、端口)、“认证配置”(用户名、密码)、“扫描策略”(线程数、超时时间)等多个逻辑分组。
cli.setCliGroup 函数正是这一思想在Yakit cli库中的具体实现。它作为一个通用的选项函数,可以被添加到任何参数定义中。其工作机制非常直观:cli库会将在cli.setCliGroup函数中使用了相同字符串的参数,自动聚合到同一个视觉分组中。这个字符串既是分组的唯一标识符,也会被用作该分组在GUI上的标题。
现在,我们通过一个具体的代码实例和其生成的UI界面,来直观地理解cli.setCliGroup的实际效果。
图:Yakit插件参数分组函数示意图
代码实现解析
请看上图左侧的代码片段。开发者定义了三个参数:host、port和dict。
// 目标信息 参数组
host = cli.Host("host",cli.setVerboseName("目标主机"), cli.setCliGroup("目标信息"))
port = cli.Port("port",cli.setVerboseName("目标端口"), cli.setCliGroup("目标信息"))
在这里,host 和 port 参数在定义时,都调用了 cli.setCliGroup("目标信息")。通过传入完全相同的字符串 "目标信息",我们向cli库声明了这两个参数在逻辑上属于同一组。
// payload 参数组
dict = cli.Host("dict",cli.setVerboseName("爆破字典"), cli.setCliGroup("payload"))
与此不同,dict 参数则通过 cli.setCliGroup("payload") 被划分到了另一个名为 "payload" 的独立分组中。
GUI映射分析 观察上图右侧的“参数预览”界面,我们可以清晰地看到代码定义与视觉呈现之间的一一对应关系:
-
所有被标记为
"目标信息"的参数(目标主机和目标端口)被自动收纳在一个标题为 “参数组: 目标信息” 的可折叠面板中。 -
被标记为
"payload"的参数(爆破字典)则被放入了另一个名为 “参数组: payload” 的独立面板里。
这种从声明式代码到结构化UI的自动转换,是Yakit "代码即UI" 理念的又一体现。开发者无需关心前端布局细节,只需在定义参数时,通过cli.setCliGroup明确其逻辑归属,Yakit框架便会负责生成组织良好、交互友好的用户界面。
5.3.2.7 插件环境变量函数
在之前的内容中,我们的讨论始终围绕着如何通过 cli 库构建用户交互界面,即通过表单让用户在每次运行时输入参数。这种模式对于动态变化的输入非常有效。然而,在专业的工具开发实践中,存在大量不应由用户频繁输入或不应直接暴露在代码中的配置,例如API密钥、认证凭据或固定的服务器地址。将这些敏感或持久化的信息硬编码在脚本中,会带来严重的安全风险和维护性问题。
本章旨在解决这一核心痛点,将引入“配置与代码分离”的工程化思想。我们将深入探讨两大核心命题:“敏感配置的外部化管理” 与 “插件运行时的环境值动态注入”。通过学习 cli.setPluginEnv 选项函数,您将掌握如何让脚本从Yakit平台的全局环境中安全、便捷地获取配置信息,从而编写出更安全、更具可移植性且易于维护的专业级插件。
cli.setPluginEnv 的核心机制在于它改变了参数值的来源。常规的参数定义会促使cli库生成一个对应的UI输入表单,等待用户填充。而一旦为参数添加了 cli.setPluginEnv 选项,cli库的行为将发生根本性转变:它将不再为该参数生成任何UI输入控件,而是转而在插件执行时,从Yakit的“插件环境变量”中查找并读取指定名称的变量值。
这本质上是一种声明式绑定。开发者在代码中声明:“我需要一个名为apiKey的参数,它的值请从一个名为myapiKey的插件环境变量中获取”。这种方式实现了配置的外部化,带来了三大核心价值:
-
安全性:将API密钥等敏感信息从代码中剥离,避免了因代码泄露导致凭据暴露的风险。
-
可维护性:当配置信息(如API密钥更新)需要变更时,用户只需在Yakit的全局配置界面修改一次,所有引用该变量的插件即可自动生效,无需修改任何插件代码。
-
可移植性:插件代码本身不包含任何个人凭据,可以方便地分享给其他用户,接收方只需在自己的Yakit环境中配置好对应的环境变量即可运行。
实践工作流:从全局配置到插件注入
现在,我们通过一个完整的工作流程,结合Yakit界面截图,来展示如何应用 cli.setPluginEnv。
图:Yakit插件配置与执行界面
步骤一:在Yakit平台设置全局环境变量
这是配置外部化的前提。用户需要在Yakit平台中预先定义好将在脚本中使用的环境变量。
图:Yakit插件全局变量配置界面
如上图所示,我们进入Yakit主界面的“配置”模块,在“插件全局变量”页面中,新建了一个名为 myapiKey 的变量,并赋予其一个示例值 test。这个变量现在对平台中所有的插件都可见。
步骤二:在插件代码中声明绑定
接下来,在插件代码中,我们使用 cli.setPluginEnv 来声明对这个全局变量的引用。
key = cli.String("apiKey", cli.setPluginEnv("myapiKey"))
cli.check()
yakit.Output(key)
步骤三:插件运行时的自动注入与验证
当用户准备运行这个插件时,Yakit的界面和行为将直观地反映出这种绑定关系。
图:Yakit插件环境变量配置入口指引
图:Yakit插件全局变量配置界面
从图中可以看出:
-
无UI表单:在插件的“开始执行”主界面,完全看不到为
apiKey生成的输入框。这是cli.setPluginEnv最直接的效果。 -
配置感知:Yakit平台能够识别出此插件依赖于环境变量,并在插件详情页的右侧提供了一个“配置”入口
-
上下文过滤:点击该“配置”入口后,系统会展示一个专属于此插件的环境变量视图。它并非列出所有全局变量,而是精准地显示了该插件代码中通过
cli.setPluginEnv声明需要使用的myapiKey及其当前值。这极大地便利了用户检查和修改当前插件所需的配置。
最终,当插件执行时,cli.check() 会成功从环境中读取到 myapiKey 的值 "test",并将其赋给变量 key。yakit.Output(key) 将会输出 test,证明整个动态注入流程成功完成。
总结
cli.setPluginEnv 是连接插件代码与Yakit平台配置管理系统的桥梁。它提供了一种优雅而安全的方式来处理静态配置和敏感数据,是编写高质量、可维护和可共享插件的核心技能。熟练掌握这一机制,将使您的Yakit脚本开发实践迈向更加工程化和专业的水平。
5.3.3 插件与平台的深度交互
在 5.3.2 节中,我们探讨了 cli 库如何通过“声明即 UI”的模式,为原生插件和 MITM 插件生成标准化的图形输入界面。这种机制解决了插件参数化配置的难题。然而,要实现极致的分析效率,插件功能不应孤立存在,而需无缝融入到用户的高频操作工作流中。
本节将深入探讨 Yakit 平台中另外两种更为精妙的联动机制。第一种是 Codec 插件与核心功能模块 UI 的上下文联动,它将工具嵌入用户的直觉操作路径中;第二种是插件间的直接调用与编排,它将平台内所有功能模块转化为可自由组合的“积木”,用以构建高级自动化测试流程。这两种机制共同构成了 Yakit 插件生态的动态核心。
5.3.3.1 Yak-Codec 插件与右键菜单的上下文集成
Codec 插件的核心价值远不止于独立的数据编解码。Yakit 平台允许开发者将自定义的 Codec 插件动态注册到核心测试模块(如 HTTP History、Web Fuzzer)的右键上下文菜单中。通过这种方式,插件的功能被无缝嵌入到用户的分析工作流中,极大地提升了处理私有协议、非标准加密或执行重复性数据转换时的操作效率。
插件注册:从配置到 UI 适配
将一个 Codec 插件的功能注册到右键菜单,其本质是通过插件的元信息配置,声明其期望挂载的 UI 锚点(UI Anchor)。该流程被设计得直观且易于管理。
首先,在Yakit的“插件”功能区,通过“插件仓库”进入并选择“新建插件”。
图:Yakit插件商店界面及新建入口指引
在新建插件界面,左侧的配置面板是定义联动行为的关键区域。
图:Yakit新建Codec插件的配置与代码编写界面
-
脚本类型 (Script Type): 必须设置为 Yak-Codec。这是插件能够被 UI 联动机制识别并加载的前提。
-
插件名称 (Plugin Name): 应为简洁且能准确概括插件核心功能的描述。
-
插件配置 (Plugin Configuration): 此区域提供了一组布尔开关,用于精确控制插件在特定 UI 上下文中的可见性。每个开关对应一个 UI 钩子,激活后即可将插件功能注入到指定的右键菜单中:
- 用于HTTP数据包变形: 启用后,插件将出现在 HTTP 请求编辑器(如 Web Fuzzer、Repeater)右键菜单的“HTTP数据包变形”子菜单中。此钩子专注于对整个 HTTP 请求报文进行结构化修改。
图:Yakit 插件在 HTTP 数据包右键菜单中的集成展示
- 用于数据包右键: 启用后,插件将作为通用工具出现在任意数据展示区的右键菜单中。它被设计用于处理用户选中的任意文本片段,是实现通用字符串编解码、加解密的首选。若无文本选中,则默认对整个数据包生效。
图:Yakit菜单中展示插件扩展选项
- 用于history右键(单选/多选): 启用后,插件将出现在
HTTP History模块的右键菜单中。此钩子传递的并非流量内容,而是流量在数据库中的唯一标识(ID),专用于对流量进行元数据操作,如标记、分类或触发后续分析。
图:Yakit插件扩展菜单与Codec History注册示例
核心实现:动态 origin 参数与场景化逻辑
所有 Yak-Codec 插件都遵循一个统一的函数签名:handle(origin)。其设计的精妙之处在于,origin 参数的具体含义和数据类型是动态的,它完全取决于用户触发该插件时的 UI 上下文(即上一小节中配置的 UI 钩子)。开发者需根据目标应用场景,在 handle 函数内编写与之匹配的处理逻辑。
handle = func(origin /*string*/) {
// ... 在这里编写你的处理逻辑 ...
return origin
}
关键参数 origin:handle 函数接收一个核心参数 origin。这个参数的值 取决于插件的配置项。
-
当配置为“用于HTTP数据包变形”时:
origin参数接收到的是完整的、原始的 HTTP 请求报文(string类型)。 -
当配置为“用于数据包右键”时:
origin参数接收到的是用户在 UI 上高亮选中的文本(string类型)。若无任何选中,则传递整个数据包内容。 -
当配置为“用于history右键”时:
origin参数接收到的是HTTP History数据库中流量条目的 ID(string类型,多选时以,分隔)。
右键菜单应用案例:场景化实现
理论与实践相结合,本节将通过三个具体的场景案例,深入剖析 Yak-Codec 插件与右键菜单联动的核心应用模式。这三个案例分别对应前文所述的三种 UI 钩子,并各自代表了一种典型的插件设计模式,为开发者提供了可复用的解决方案。
场景一:HTTP 请求体原地转换与美化
此模式的核心在于直接修改传入的数据(如整个 HTTP 请求包),并将修改后的结果返回,以替换原始数据。这在需要对数据包进行格式化、编码转换或轻量级 payload 修改时非常高效。
场景描述: 在 Web Fuzzer 或 Repeater 中,经常遇到经过压缩或编码的 JSON/XML 请求体,可读性极差。本案例旨在创建一个“请求包美化”插件,自动识别并格式化请求体。
实现代码如下:
// origin 参数值为原始 request 请求包
handle = func(origin) {
// Split 切割 HTTP 报文,返回响应头和响应体,其第一个参数是原始 HTTP 报文
_, body = poc.Split(origin)
if len(body) < 0 {
return string(origin)
}
// 先尝试 json 转换
// json.loads 会尝试将一个 JSON 字符串转换为对象,返回转换后的对象
params = json.loads(body)
bodyTyp = ""
if len(params) > 0{
bodyTyp = "json"
} else {
// xml.loads 会尝试将一个 XML 字符串转换为对象,返回转换后的对象
params = xml.loads(body)
bodyTyp = "xml"
}
// 替换body
if len(params) > 0 {
newBody = body
if bodyTyp == "json" {
// 进行 json format,美化 json 字符串
newBody = json.dumps(params, json.withIndent(" "))
} else if bodyTyp == "xml" {
newBody = xml.dumps(params)
}
// ReplaceBody 将原始 HTTP 请求报文中的 body 替换为指定的 body,
// 并指定是否为 chunked,返回新的 HTTP 请求报文
// 此时的 origin 就已经是美化过 body 的 HTTP 请求包了
origin = poc.ReplaceBody(origin, newBody,false)
}
return string(origin)
}
逻辑解析:
-
数据输入: 插件通过
origin参数接收到完整的 HTTP 请求报文字符串。 -
核心处理: 利用
poc库的Split和ReplaceBody函数,实现了对 HTTP 协议的结构化操作。通过json.loads和xml.loads进行智能类型判断,并使用dumps完成美化。 -
结果返回:
handle函数返回了一个全新的请求报文。Yakit 平台捕获此返回值,并用它直接更新UI 编辑器中的内容,实现了“原地”修改。
效果展示
图:Yakit工具请求包美化功能操作演示
场景二:通用字符串处理与结果输出
此模式专注于对用户选中的任意文本进行计算或转换,并将结果显示在独立的输出区域,而不改变原始数据。它适用于哈希计算、编解码查询、信息提取等场景。
场景描述: 在分析数据时,需要快速计算某段字符串(如 token、参数值)的 MD5 哈希值以供后续使用。
实现代码如下:
// origin 参数接收到的值为鼠标选中的字符串,如果没有任何选中,则是整个数据包
handle = func(origin) {
return codec.Md5(origin)
}
-
逻辑解析:
-
数据输入:
origin参数接收到用户高亮选中的文本片段。 -
核心处理: 调用
codec库提供的Md5函数,这是一个纯计算过程,不涉及对 UI 或数据库的任何修改。 -
结果返回: 返回的 MD5 字符串不会替换任何已有内容,而是被 Yakit 自动路由到独立的**“插件输出”**面板中进行展示。
-
-
效果展示:
图:Yakit插件扩展与结果输出界面
场景三:基于 ID 的流量元数据操作
这是最强大的模式,插件接收的不是数据本身,而是一个或多个数据标识符(ID)。插件通过 ID 查询后端数据库,对数据对象进行状态修改(如添加标签、颜色标记),并将其持久化。
场景描述: 在 HTTP History 中,希望快速标记一批重要的流量,以便于后续筛选和审计。本案例实现一个“标记流量”插件,为选中的流量条目添加标签和紫色高亮。
实现代码:
// #.codec.plugin
// origin 接收一个或多个以逗号分隔的流量 ID
handle = func(origin /*string*/) {
// 兼容多选,按逗号分割 ID 字符串
ids = str.Split(origin, ",")
// 通过 ID 查询流量对象
httpflows = db.QueryHTTPFlowsByIDs(ids)
for httpflow = range httpflows {
domain := str.ExtractHost(httpflow.Url)
// 为流量对象添加一个描述性标签
httpflow.AddTag("Important:%v" % domain)
// 调用内置方法将其标记为紫色
httpflow.Purple()
// 将修改后的流量对象保存回数据库
db.SaveHTTPFlowInstance(httpflow)~
}
// 返回 ID 仅用于在插件输出中提示操作完成
return origin
}
逻辑解析:
-
数据输入:
origin参数接收到HTTP History中被选中流量的数据库 ID。 -
核心处理: 使用
db.QueryHTTPFlowsByIDs从数据库中检索出完整的HTTPFlow对象。随后,直接操作对象的属性和方法(AddTag,Purple),最后通过db.SaveHTTPFlowInstance将更改写回数据库,实现状态持久化。~符号表示这是一个异步调用。 -
结果返回:
return origin的主要目的是在“插件输出”中提供一个执行反馈。真正的结果体现在HTTP History列表的UI刷新上(标记的流量变色)。
效果展示:
图:Yakit插件通过流量ID操作元数据
5.3.3.2 Yak 插件编排:构建高级自动化测试流
在前述章节中,我们探讨了 Codec 插件如何通过 UI 钩子与平台上下文进行集成。现在,我们将进入一个更为高级和强大的领域:Yakit 平台允许一个插件直接调用并驱动其他插件。这种“插件编排”机制,允许开发者将多个独立的功能模块(如端口扫描、中间人攻击 MITM、漏洞扫描等)串联成一个有机的整体,构建出高度定制化和自动化的安全测试工作流。
这种模式的本质是定义一个“编排器插件”,它作为主控逻辑,在运行时动态加载并执行一个或多个“工作插件”,从而将单一功能组合成复杂的解决方案。
注意:支持Yak插件编排的插件必须是一个“Yak原声插件”类型。
插件编排模式:声明插件联动UI
实现插件编排的核心流程分为两个关键阶段:设计时声明与运行时注入。
-
设计时声明: 在创建“编排器插件”时,开发者需要预先声明它有权调用的“工作插件”的类型。这相当于定义了一个契约,明确了该编排器能够与哪些类别的功能模块进行交互。
-
运行时选择与注入: 当用户执行该编排器插件时,Yakit 前端会根据设计时的声明,动态地展示出所有符合类型的、可用的“工作插件”列表。用户勾选所需插件后,Yakit 会将这些选择作为参数,注入到“编排器插件”的执行环境中。
图:Yakit插件联动编排界面
实现编排插件:配置与核心代码
创建一个具备编排能力的插件,需要在其配置和源码层面进行特定设计,在配置中打开“启用插件联动UI”开关,并且编写接收代码。
编排器插件的配置
在新建插件界面,通过左侧面板进行以下关键配置:
图:Yakit编排器插件配置界面及源码示例
-
脚本类型 (Script Type): 必须设置为 Yak 原生插件。只有原生插件才能提供执行复杂逻辑、管理子流程以及调用平台核心 API 的能力。
-
启用插件间调用: 必须勾选此选项。它作为激活插件编排功能的总开关,通知 Yakit 引擎为该插件启用动态加载机制。
-
联动插件类型 (Linked Plugin Types): 这是实现编排的核心配置区域。开发者在此声明该编排器可以调用的工作插件类型,例如勾选“端口扫描”和“MITM”。只有在此声明的类型,其下的具体插件才会在运行时作为可选项出现。
核心代码实现
编排器插件的代码遵循“接收参数 -> 加载插件 -> 执行逻辑”的基本模式。
// #.yakit.plugin-file: scriptnames
yakit.AutoInitYakit()
// 1. 定义一个特殊的命令行参数,用于接收前端注入的工作插件列表
// `cli.YakitPlugin` 定义了一个名为 "yakit-plugin-file" 的参数,
// 它将接收到一个包含所选插件名称的文件路径。
// scriptNames 变量将得到一个 `[]string` 类型的值,包含了所有被选中插件的名称。
scriptNames := cli.YakitPlugin()
cli.check() // 检查 CLI 参数是否合理
swg = sync.NewSizedWaitGroup(20)
// 2. 初始化插件管理器
// MixPluginCaller 是用于执行外部插件的核心接口
manager, err := hook.NewMixPluginCaller()
if err != nil {
yakit.Error("build mix plugin caller failed: %v", err)
die(err)
}
// 3. 遍历并加载被选中的工作插件
yakit.Info("Start to load selected plugins: %v", scriptNames)
for _, pluginName := range scriptNames {
// 使用 manager.LoadPlugin 动态加载每一个工作插件
// 加载成功后,该插件的功能就可以在后续逻辑中被调用
err := manager.LoadPlugin(pluginName)
if err != nil {
yakit.Error("Failed to load plugin [%v]: %v", pluginName, err)
continue // 如果某个插件加载失败,跳过并继续加载下一个
}
yakit.Info("Plugin [%v] loaded successfully.", pluginName)
}
// 4. 在此编写核心编排逻辑
// 例如:
// - 接收目标输入
// - 调用已加载的“端口扫描”插件
// - 根据扫描结果,条件性地调用已加载的“漏洞扫描”插件
// ...
/*
manager.HandleServiceScanResult() // 处理端口扫描的结果
manager.MirrorHTTPFlow(...) // 处理镜象流量扫描
*/
-
参数接收与解析: Yakit 的编排机制通过
cli.YakitPlugin()实现参数传递。当用户点击执行时,前端会将勾选的插件列表写入一个临时文件,并将该文件路径作为命令行参数传递给插件后端。cli.YakitPlugin()负责解析此文件,并返回一个包含所有插件名称的字符串切片 ([]string)。 -
插件动态加载: 核心步骤是使用
manager.LoadPlugin(pluginName)。此函数根据插件名称,在 Yakit 的插件库中查找并动态加载该插件的执行体。一旦加载成功,该插件就进入了当前编排器的上下文,可以被后续逻辑调用。 -
数据源触发与执行:这是整个编排流程的核心驱动环节。编排器插件在加载完工作插件后,其主要职责转变为一个数据传递者。它通过调用
manager对外暴露的特定接口(如HandleServiceScanResult或MirrorHTTPFlow),将数据“发布”出去。-
工作机制:
manager接收到数据后,会检查该数据的类型,并将其分发给所有已加载的、且能够处理该数据类型的插件。例如,当调用manager.MirrorHTTPFlow(...)时,“MITM”类型的工作插件和一次性端口扫描类插件都会被触发执行。 -
解耦设计:这种设计实现了“编排器”与“工作插件”的完美解耦。编排器无需关心具体是哪个漏洞插件被加载,它只需要持续地产生并提供标准化的数据(如
HTTPFlow对象)。而工作插件则专注于处理自己感兴趣的数据,实现了功能的高度复用。
-
Yak 插件间的编排机制是 Yakit 平台高级功能的集中体现。它将平台内各个独立的功能模块抽象为可被自由编排和调度的“原子能力”,赋能安全研究人员和开发者,使其能够根据实战需求,构建出超越单个工具限制的、一站式、自动化的解决方案。例如在第二章 2.3.4 小节中提到的基础爬虫模块,其本质就是一个集成了 Web 爬取与漏洞检测能力的编排插件。
应用场景示例:基础爬虫
通过这种插件间的编排机制,我们可以创造出非常强大的自动化工具。例如在第二章的 2.3.4 小节中提到的基础爬虫模块,其本质就是一个典型的编排插件。它的核心逻辑是:
-
作为数据源:执行爬虫逻辑,持续产生
HTTPFlow数据。 -
驱动工作插件:将每一个产生的
HTTPFlow通过manager.MirrorHTTPFlow()接口“喂”给用户在运行时勾选的多个漏洞检测插件(它们都属于 MITM 类型)。
用户可以参考其源码,深入学习和理解这种高级编排插件的完整实现过程。下一小节,我们将介绍 Yakit 插件的输出设计,探讨如何通过代码为插件生成丰富的输出内容。
5.3.4 插件输出与 UI 交互:yakit 库应用
前面的章节深入探讨了 cli 库,阐述了其如何通过声明式参数定义,为 Yakit 插件自动生成强大的命令行接口与图形用户界面。在掌握了如何设计和接收用户输入后,本章我们将焦点从“输入”转向“输出”,从而完成 Yakit 插件中从数据输入到结果反馈的用户交互闭环。
我们将深入研究 yakit 库提供的 UI 组件功能。需要明确的是,本章所探讨的 UI 交互能力主要面向 Yak 原生插件。 这类插件被设计为与 Yakit 客户端深度集成,能够直接调用前端的图形化组件,如日志面板、进度条和数据表格等,提供远超传统命令行工具的丰富交互体验。
实现这种交互的基础是插件后端与前端界面的通信。因此,在原生插件中调用任何 UI 功能前,都必须在代码的起始位置执行 yakit.AutoInitYakit() 来建立这一通信管道。
5.3.4.1 基础信息输出:日志与状态反馈
最基础也是最核心的 UI 交互是向用户反馈插件的运行状态。yakit 库提供了一套与标准日志级别(Info, Warn, Error)对应的输出接口,使开发者能够精细化地控制信息的重要性和视觉表现。
yakit.Info****:标准信息输出
该函数用于向前端 UI 输出常规状态信息,通常用于标识程序正常运行、关键节点完成或提供过程性反馈。在 Yakit 的输出面板中,这类信息通常以默认颜色(如白色或灰色)呈现,表示一种预期的、非告警性的状态。
-
函数定义:
yakit.Info``(message string, args ...``any``) -
应用场景:
-
任务启动与结束标记:
yakit.Info("Target scan task started...") -
关键配置信息展示:
yakit.Info("Current concurrency level: %d", 20) -
中间进度更新反馈:
yakit.Info("Processed 100/500 URLs.") -
成功操作的确认:
yakit.Info("Vulnerability CVE-2023-XXXX verified successfully.")
-
图:Yakit插件基础日志输出示例
yakit.Error****:错误信息输出
该函数专门用于报告程序运行过程中发生的、可能导致任务中断或功能异常的严重问题。通过此函数输出的信息旨在立即引起用户的注意。在 UI 中,错误信息通常以醒目的红色高亮显示,并配有错误图标,强调其紧急性。
-
函数定义:
yakit.Error(message string, args ...``any``) -
应用场景:
-
网络连接失败:
yakit.Error("Failed to connect to target %s: connection refused", target) -
文件或资源访问异常:
yakit.Error("Cannot read config file '%s': permission denied", configFile) -
核心逻辑执行失败:
yakit.Error("Authentication failed with provided credentials.") -
无效的输入参数:
yakit.Error("Invalid URL format provided: %s", rawURL)
-
图:Yakit插件错误日志输出界面
yakit.Warn****:警告信息输出
该函数用于输出潜在问题或非致命异常。警告信息提示用户某些情况值得关注,但程序通常会继续执行。在 UI 中,警告通常以黄色或橙色显示,并配以警告图标,作为一种风险提示而非执行阻断。
-
函数定义:
yakit.Warn(message string, args ...``any``) -
应用场景:
-
使用已弃用的功能或配置:
yakit.Warn("TLSv1.0 is deprecated, consider upgrading.") -
可选资源不可用,程序降级运行:
yakit.Warn("External dictionary not found, falling back to default wordlist.") -
数据处理中的轻微异常:
yakit.Warn("Invalid characters found in response body, attempting to sanitize.") -
非关键性的条件未满足:
yakit.Warn("No custom headers found, proceeding with default User-Agent.")
-
图:Yakit插件日志输出与源码示例
5.3.4.2 可视化任务进度:进度条
在基础的文本日志反馈之外,Yakit 提供了一种更直观的用户交互机制,用于解决长耗时任务(如大规模扫描、批量破解或模糊测试)执行过程中的“黑盒”状态问题:可视化进度条。它将抽象的计算过程转化为用户可感知的进度,极大提升了工具的透明度和用户体验。
yakit 库提供了两种粒度的 API 来实现进度反馈。
全局任务进度:yakit.SetProgress
-
函数定义:
yakit.SetProgress(progress float64) -
参数解析:
progress: 一个float64类型的数值,取值范围为0.0到1.0,分别对应 0% 到 100% 的任务完成度。
-
代码示例与 UI 表现: 以下代码模拟了一个耗时任务,并在循环中持续更新全局进度条。
yakit.AutoInitYakit()
yakit.Info("任务开始执行...")
// 模拟一个需要长时间运行的任务
for i := 0; i <= 100; i++ {
// ... 模拟复杂计算或网络请求
time.Sleep(0.01) // sleep 0.01 秒,就是 10 毫秒
// 更新全局进度条
yakit.SetProgress(float64(i) / 100.0)
}
yakit.Info("任务执行完成。")
我们创建一个Yak原生插件之后,在 Yakit 客户端 UI 中执行,这会渲染出一个名为 main 的主进度条,并根据 yakit.SetProgress 传入的值平滑地从 0% 增长到 100%。
图:Yakit插件任务进度条显示
具名子任务进度:yakit.SetProgressEx()
对于包含多个阶段或并行子任务的复杂工作流,yakit.SetProgressEx() 提供了更精细的控制能力。它允许为不同的子任务创建独立的、带有特定名称的进度条,实现了对复杂任务生命周期的可视化分解。
-
函数定义:
yakit.SetProgressEx(name string, progress float64) -
参数解析:
-
name:string类型,用于唯一标识一个子任务进度条。 -
progress:float64类型,表示该具名子任务的完成进度(0.0 至 1.0)。
-
-
代码示例与 UI 表现: 以下示例演示了一个包含“目录枚举”和“漏洞验证”两个阶段的自动化评估任务。
yakit.AutoInitYakit()
yakit.Info("主任务:启动 Web 应用安全评估")
// 主任务进度推进到 30%
yakit.SetProgress(0.3)
// -- 阶段一:目录枚举 --
yakit.Info("开始执行目录枚举...")
for i := 0; i <= 100; i++ {
// ...
yakit.SetProgressEx("目录枚举", float64(i)/100.0)
}
yakit.Info("目录枚举完成。")
// 主任务进度推进到 70%
yakit.SetProgress(0.7)
// -- 阶段二:漏洞验证 --
yakit.Info("开始执行漏洞验证...")
for i := 0; i <= 20; i++ {
// ...
yakit.SetProgressEx("漏洞验证", float64(i)/20.0)
}
yakit.Info("漏洞验证完成。")
// 主任务完成
yakit.SetProgress(1.0)
yakit.Info("所有评估任务执行完毕。")
在 UI 中,Yakit 会同时渲染出主进度条 (main) 以及两个具名的子任务进度条(“目录枚举”、“漏洞验证”),清晰地展示了整个任务的层级结构和各部分的执行进展。
图:具名子任务进度条显示各阶段完成状态
通过合理运用 yakit.SetProgress() 和 yakit.SetProgressEx(),开发者能够为用户提供清晰、直观的任务状态反馈,这不仅显著提升了工具的可用性和专业性,也为调试和优化复杂任务流程提供了有力的视觉辅助。
5.3.4.3 输出状态卡片与统计数据
在复杂的安全任务执行过程中,实时日志流与进度条虽然提供了过程可见性,但往往缺乏对核心指标的宏观概览。用户在面对海量输出时,难以快速捕获任务的关键状态。为解决这一问题,Yakit 提供了一种结构化的信息聚合与展示机制——状态卡片(Data Card),允许原生插件在客户端界面上渲染清晰、直观的UI组件。
该功能通过 yakit.StatusCard API 实现。此API经过精心设计,支持两种不同的函数签名,以适应从简单的静态信息展示到复杂的动态数据更新等不同场景。理解这两种签名的差异是高效使用该功能的关键。
函数签名与参数解析
yakit.StatusCard 的核心价值在于其灵活性,具体体现在两种调用方式上:
-
**三参数签名 : **
yakit.StatusCard(id string, title string, content string)此签名是构建动态、可交互状态面板的基础。-
id: 必选的唯一标识符。Yakit引擎通过此id来识别卡片。首次调用时创建卡片,后续使用相同id的调用则会更新该卡片的内容,这是实现数据动态更新的核心机制。 -
title: 映射到卡片上醒目的大字体主内容。通常用于展示核心数字或关键状态,例如"11"。 -
content: 映射到卡片下方辅助性的小字体描述文本。用于对主内容进行解释或补充,例如"展示标题内容"。
-
-
**二参数签名: **
yakit.StatusCard(title string, content string)此签名提供了一种便捷方式来快速创建可更新的静态信息卡片。-
该调用方式内部会生成一个
id为索引的卡片统计数据,因此每次调用都会以id为索引更新覆盖旧数据。 -
参数与视觉元素的映射关系与三参数签名完全相反,这一点需要特别注意:
-
title: 映射到卡片下方辅助性的小字体描述文本,例如"数据统计"。 -
content: 映射到卡片上醒目的大字体主内容,例如"abb"。
-
实践案例:组合使用与动态更新
在实际开发中,开发者可以根据需求组合使用这两种签名。例如,在任务结束时,使用二参数签名输出多个最终统计结果;而在任务进行中,使用三参数签名来实时刷新进度。
下方图例清晰地展示了两种签名的渲染效果。右侧两张卡片分别由以下两种不同的调用方式生成,直观地体现了参数映射的差异。
yakit.AutoInitYakit()
// 使用二参数签名:创建一个静态卡片。
// "abb" 是大字体内容, "数据统计" 是小字体标题。
yakit.StatusCard("数据统计", "abb")
// 使用三参数签名:创建一个ID为 "id-for-card" 的可更新卡片。
// "11" 是大字体内容, "展示标题内容" 是小字体描述。
yakit.StatusCard("id-for-card", "11", "展示标题内容")
图:Yakit插件系统数据卡片展示
通过为卡片赋予id,我们可以轻松实现动态更新。yakit.StatusCard 通过提供两种功能互补的签名,赋予了开发者极大的灵活性。正确选择签名方式是构建信息丰富且用户体验友好的Yakit原生插件的关键一步。
5.3.4.4 结构化输出:数据统计表格
前述章节我们探讨了通过日志流和状态卡片进行信息输出的方式,它们分别适用于过程追踪和关键指标聚合。然而,当安全任务产出具有多维属性的列表型数据时,例如漏洞清单、资产列表或端口扫描结果,就需要一种更具结构化的呈现方式。表格,作为信息组织的基础范式,能够清晰地展示数据间的关联,并支持排序、筛选等交互操作,极大提升了分析效率。
Yakit 原生插件通过一套解耦的API,yakit.EnableTable 和 yakit.TableData,为开发者提供了在客户端动态创建和填充表格的能力。这一机制遵循一个经典的两阶段设计模式:首先定义表格的结构(Schema),然后异步地、流式地填充数据。这种设计不仅逻辑清晰,也为处理大规模数据集提供了良好的性能基础。
第一阶段:定义表格结构 - yakit.EnableTable
在向表格填充任何数据之前,必须先对其进行初始化和结构定义。yakit.EnableTable 函数承担此任,其作用是在Yakit前端界面上创建一个新的Tab标签,并定义该标签下表格的列头。
其函数签名为 yakit.EnableTable(tableName string, headers []string):
-
tableName string: 这是一个关键的唯一标识符。它不仅作为前端Tab上显示的名称,更重要的是,它作为后续yakit.TableData调用时绑定数据的目标ID。 -
headers []string: 一个字符串切片,用于定义表格的所有列名及其顺序。这些列名将作为后续填充数据时,数据行(map)中key的依据。
此函数只需在插件执行初期调用一次,即可完成表格的初始化准备。
第二阶段:填充表格数据 - yakit.TableData
一旦表格结构被定义,就可以在任务执行的任何阶段,通过 yakit.TableData 函数向其中添加数据行。此函数可以被反复调用,实现数据的流式追加。
其函数签名为 yakit.TableData(tableName string, rowData map[string]any):
-
tableName string: 目标表格的标识符,必须与yakit.EnableTable中使用的tableName完全一致,以确保数据被正确地路由到对应的表格中。 -
rowData map[string]any: 一条具体的行数据,以map的形式提供。其中,map的key必须与headers数组中定义的列名相对应,value则是该单元格要显示的数据。Yakit会根据key自动将value填充到正确的列。
实践案例:生成MD5哈希对照表
下面的代码片段完整地演示了从定义到填充的全过程。首先,使用 EnableTable 创建一个名为“表格名称”的表格,并定义 key 和 value-md5 两列。随后,进入一个循环,在每次迭代中计算新的MD5值,并立即通过 TableData 将结果作为新的一行推送到前端表格中。
yakit.AutoInitYakit()
*// 1. 定义表格结构:创建一个名为“表格名称”的Tab,包含两列*
yakit.EnableTable("表格名称", ["key", "value-md5"])
*// 2. 循环填充数据:向“表格名称”表格中流式添加10行数据*
for i = 0; i < 10; i++ {
yakit.TableData(
"表格名称",
{"key": i, "value-md5": codec.Md5(i)}, *// 每一行数据是一个map,key必须与headers对应*
)
}
如下图所示,代码执行后,Yakit客户端会生成一个功能完备的交互式表格。用户不仅能清晰地看到结构化的数据,还可以利用表格自带的排序、筛选和导出功能对结果进行深度分析。这种将后端数据计算与前端UI展示解耦并流式对接的方式,是Yakit原生插件开发中处理和呈现复杂数据集的核心技术。
图:Yakit插件生成MD5哈希对照表执行结果
5.3.4.5 富文本输出:基于 Markdown 的报告
前面的章节介绍了状态卡片与数据表格,它们分别解决了关键指标聚合和结构化列表数据的呈现问题。然而,当安全任务需要输出包含详细描述、修复建议和代码示例的综合性报告时,这些固定格式的组件便显得力不-从心。此时,我们需要一种能够承载丰富叙事内容,并具备良好可读性的输出方式,而 Markdown 正是为此而生的理想选择。
Yakit 通过 yakit.Markdown 函数,为原生插件赋予了直接在客户端渲染富文本的能力。这一功能的核心价值在于,它将内容结构定义(由开发者通过Markdown语法完成)与视觉表现(由Yakit客户端的渲染引擎负责)分离开来。这种解耦使得开发者可以专注于信息本身的逻辑层次,而无需关心复杂的UI渲染细节,从而以极低的成本生成专业级的报告和输出。
API原理与调用机制
yakit.Markdown 的API设计极为简洁,其背后是Yakit前后端通信与UI渲染的协同工作。
-
函数签名:
yakit.Markdown(content string) -
工作原理: 当插件调用此函数时,Yakit引擎会将传入的
content字符串标记为Markdown类型,并发送给前端。Yakit客户端的“日志”视图在接收到此类型的数据后,会调用内置的Markdown解析器,将文本标记(如#,*, ````)实时转换为对应的HTML元素,最终呈现为用户所见的富文本格式。与EnableTable创建新Tab不同,Markdown内容是直接嵌入到主日志流中,作为上下文的一部分,丰富了过程记录的细节。
核心应用场景与实践范例
yakit.Markdown 的应用场景广泛,几乎涵盖了所有需要结构化文本展示的场合,例如生成格式化的漏洞报告、输出带有代码高亮的PoC、或提供图文并茂的操作指引。通过组合运用标题、列表、代码块、引用等元素,可以极大地提升信息传达的效率和清晰度。
以下代码演示了如何构建一个包含多种Markdown元素的综合性报告片段,并将其输出到Yakit客户端。
yakit.AutoInitYakit()
# Input your code!
markdownText = `# 这是一个Markdown标题 (H1)
## 这是二级标题 (H2)
### 这是三级标题 (H3)
这是一个水平线,用于分隔内容。
---
这是**粗体文本** 或者 __这也是粗体文本__。这是*斜体文本* 或者 _这也是斜体文本_。
## 列表示例
### 无序列表项
- 第一个列表项
- 第二个列表项
- 嵌套列表项
- 另一个嵌套项
- 第三个列表项
### 有序列表
1. 第一步操作
2. 第二步操作
3. 第三步操作
`
yakit.Markdown(markdownText)
图:Yakit插件日志Markdown渲染效果展示
如上图所示,上述代码执行后,其 report 字符串中的Markdown标记被Yakit客户端的“日志”视图正确解析,渲染为一份结构清晰、元素丰富的报告。标题的层级、文本的强调、列表的缩进以及代码块的语法高亮都得到了精确的呈现。
yakit.Markdown 不仅仅是一个简单的打印函数,它是一种将结构化文本与原生UI无缝集成的强大机制。熟练掌握该功能,能够使您的原生插件超越传统的纯文本输出限制,生成专业、美观且信息层次分明的动态报告,是提升插件最终交付质量和用户体验的关键工具。
5.3.4.6 纯文本输出:独立报告与内联日志
前续章节展示了如何通过Markdown实现富文本输出,它适用于需要复杂版式和视觉强调的场景。然而,在安全工具的实践中,原始、无格式的纯文本输出同样不可或-缺,例如展示原始的HTTP响应体、配置文件、或者一个简洁的Banner信息。针对纯文本,Yakit提供了两种截然不同的输出机制,分别服务于最终报告和过程日志这两种上下文。
这两种机制的设计体现了一种重要的软件工程思想:根据信息的最终用途和重要性,将其呈现在不同的UI区域。是作为一份独立、可供查阅的最终产物,还是作为执行过程中的一个上下文注释,开发者需要做出明确的选择。Yakit通过提供Tab级文本块和日志内联文本两种API,将这一选择权交给了开发者。
Tab级独立文本输出:EnableText 与 TextTabData
当输出内容是一份完整的、独立的报告或数据集合时(如完整的扫描结果、提取的配置文件等),应当使用Tab级文本块。这种方式会在Yakit客户端的主界面创建一个全新的顶级Tab,专门用于承载这块文本内容。
此机制同样遵循**“定义与填充分离”**的两阶段模式,与yakit.EnableTable的设计哲学一致:
-
yakit.EnableText(tabName string): 此函数负责创建UI容器。它在前端界面生成一个名为tabName的新Tab,为后续内容展示预留空间。此函数仅需调用一次。 -
yakit.TextTabData(tabName string, content string): 此函数负责填充内容。它通过tabName将content字符串精准地投递到已创建的Tab中。这个函数可以被多次调用,后续调用将覆盖之前的内容,适用于动态更新最终报告的场景。
这种方式的优点是隔离性强,将最终的关键结果与繁杂的过程日志分离开,便于用户聚焦和导出。
日志流内联文本输出:yakit.Text
与Tab级输出相对,当文本信息仅作为当前任务执行过程中的一个上下文节点时,应使用yakit.Text函数。它会将文本块直接嵌入到“日志”流中,与yakit.Info, yakit.Println等日志函数穿插在一起。
yakit.Text(content string): 这是一个原子化的、低开销的调用。它接收一个字符串,并将其作为一个独立的、带有边框的文本区块渲染在日志时间线上。
这种方式适用于输出简短的提示、阶段性的小结或临时的文本片段。它的优势在于保留了上下文,用户可以清晰地看到该文本是在任务执行的哪个步骤产生的。
实践案例与选型对比
以下代码清晰地展示了两种输出方式的并行使用:
yakit.AutoInitYakit()
// 1. 使用Tab级输出机制,用于呈现最终的、独立的文本报告
yakit.EnableText("Tab级别文本块")
yakit.TextTabData("Tab级别文本块", "Tab 级别文本块输出\n\n这个文本内容可在最高级 Tab 中查看")
// 2. 使用日志内联输出,用于记录过程中的文本信息
yakit.Text("一次性输出的文本块儿,在日志中")
yakit.Text("一次性输出的文本块儿2,在日志中")
代码执行后,Yakit客户端的界面表现如下:
-
一个名为“Tab级别文本块”的新Tab被创建,其中包含了
TextTabData函数发送的多行文本。这适合作为最终的总结性报告。 [图片] -
在默认的“日志”Tab中,可以看到两个由
yakit.Text函数生成的内联文本块,它们按照执行顺序出现在日志流中,清晰地标记了程序的执行过程。 [图片]
若信息是最终成果,应使用 EnableText / TextTabData 将其提升为独立的报告;若信息是过程记录的一部分,则使用 yakit.Text 将其融入日志流。做出恰当的选择,将直接影响插件输出结果的清晰度和可用性。
5.3.4.6 数据统计图渲染:从数据到洞察
在前面的章节中,我们探讨了如何通过 yakit.Output 输出基础的文本和表格数据。然而,在复杂的安全分析场景中,原始数据往往难以直观地揭示其内在规律与关联。本章将承上启下,介绍 Yakit 脚本中更为高级的数据呈现方式——图表渲染。我们将聚焦于如何利用 yakit 库将离散的数据点转化为富有洞察力的可视化图形,核心学习两种关键的图表类型:用于展示趋势变化的线性图和用于解析构成比例的饼状图。掌握这些功能,将极大提升您分析结果的专业性和直观性。
线性图:趋势与时序数据可视化
在安全分析领域,线性图是呈现数据随时间或其他连续变量变化趋势的核心工具。无论是追踪漏洞发现数量的增长曲线、监控网络流量的周期性波动,还是分析应用响应时间的性能变化,线性图都能将枯燥的数字转化为清晰的视觉模式。Yakit 为此提供了 yakit.NewLineGraph 函数,它专为生成专业的线性图表而设计,是进行趋势分析与时序数据可视化的关键接口。
核心函数与使用流程:yakit.NewLineGraph
yakit.NewLineGraph 的使用遵循一个标准化的三步流程:初始化图表、填充数据、渲染输出。这个过程将数据结构化,并最终交由 Yakit 客户端进行图形化渲染。
-
初始化图表对象:
graph = yakit.NewLineGraph(title string)- title:
string类型,定义图表的总标题,它将显示在图表顶部,用于精确概括图表的核心主题。
- title:
-
添加数据点:
graph.Add(key string, value float64)-
key (
string): 数据点的横轴(X轴)标签,通常代表类别、时间序列或标识符。在线性图中,key定义了数据点在横轴上的位置与名称。 -
value (
float64): 数据点对应的纵轴(Y轴)数值,必须为浮点数类型。它决定了数据点在图表中的垂直位置。
-
-
渲染输出图表:
yakit.Output(graph)- 将构建完成的
graph对象作为参数传入yakit.Output,Yakit 将在执行结果中渲染出对应的线性图。
- 将构建完成的
示例:绘制漏洞发现数量趋势图
// 步骤一:确保 Yakit 环境已初始化
yakit.AutoInitYakit()
// 步骤二:创建名为 "漏洞每月发现趋势" 的线性图表对象
graph = yakit.NewLineGraph("漏洞每月发现趋势")
// 步骤三:逐个添加数据点,key为月份,value为漏洞数量
graph.Add("一月", 231)
graph.Add("二月", 123)
graph.Add("三月", 263)
graph.Add("四月", 542)
// 步骤四:将图表对象输出到客户端进行渲染
yakit.Output(graph)
图:Yakit插件系统漏洞发现趋势统计图
上述代码片段演示了从创建图表到渲染输出的完整工作流。通过 graph.Add() 方法,我们为图表填充了一系列代表漏洞发现趋势的数据。当 yakit.Output(graph) 执行后,Yakit 客户端会将这些离散的键值对(key-value)解析为以月份为X轴、漏洞数量为Y轴的坐标点,并用线条连接它们,最终直观地呈现出漏洞数量随时间变化的动态趋势。
yakit.NewLineGraph 的核心价值在于将原始的、序列化的数值数据转化为具有分析价值的视觉信息,是实现安全数据故事化叙事的有力工具。
饼状图:构成与比例分析
在数据分析中,饼状图是展示不同类别数据在总量中所占比例或构成的经典图表。对于网络安全领域中的分类统计数据,例如不同风险等级漏洞的数量占比、Web资产中各类服务器软件(Nginx, Apache, IIS)的分布,或网络流量中各应用协议的构成比例,使用饼状图可以一目了然地呈现各部分对于整体的贡献。Yakit 库为此提供了 yakit.NewPieGraph 函数,允许开发者轻松生成交互式的饼状图,帮助用户快速看懂数据的内部分布结构。
核心函数与使用流程:yakit.NewPieGraph
与线性图类似,yakit.NewPieGraph 的使用也遵循“初始化、填充、渲染”的三步流程。其核心是将一组分类计数值转化为可视化的扇区比例图。
-
初始化图表对象:
graph = yakit.NewPieGraph(title string)- title (
string): 图表的总标题,用于精确描述图表所展示的数据内容,如“Web 服务器软件分布”。
- title (
-
添加数据扇区:
graph.Add(key string, value float64)-
key (
string): 定义数据类别的名称,它将作为饼状图中一个独立扇区的标签。 -
value (
float64): 该类别对应的数值,其大小决定了扇区面积。yakit.NewPieGraph会自动计算所有value的总和,并以此为基准换算出每个扇区所占的百分比。
-
-
渲染输出图表:
yakit.Output(graph)- 将构建完成的
graph对象传入,Yakit 客户端将负责将其渲染为交互式饼状图。
- 将构建完成的
代码示例:绘制不同类型漏洞数量占比
为了更贴近真实场景,我们以统计一次扫描任务中发现的不同类型漏洞数量为例,演示 yakit.NewPieGraph 的具体用法。
// 步骤一:确保 Yakit 环境已初始化
yakit.AutoInitYakit()
// 步骤二:创建名为 "漏洞类型分布" 的饼状图对象
graph = yakit.NewPieGraph("漏洞类型分布")
// 步骤三:添加不同漏洞类型及其对应的数量
graph.Add("SQL 注入", 542)
graph.Add("命令执行", 263)
graph.Add("跨站脚本(XSS)", 231)
graph.Add("文件上传", 123)
// 步骤四:将图表对象输出到客户端进行渲染
yakit.Output(graph)
图:Yakit 插件统计图表展示漏洞类型分布
界面解析与技术价值
上述代码执行后,Yakit 客户端会生成一个专业的、交互式的饼状图。如图所示,该图表不仅用不同颜色的扇区直观展示了各类漏洞的数量占比,还具备以下核心功能:
-
中心汇总: 图表中心区域会显示所有数据项的总和,为用户提供了整体规模的快速参考。
-
图例清晰: 图表下方或侧边会自动生成图例,将类别名称(
key)与颜色对应起来。 -
交互式悬浮提示 (Tooltip): 当用户鼠标悬停在任一扇区时,会弹出信息框,精确显示该类别的名称、具体数值 (
value) 及其在总量中的百分比。
yakit.NewPieGraph 的价值在于,它将数据聚合、比例计算和前端UI渲染等复杂工作完全封装,使开发者仅需关注核心业务数据。通过这种方式,开发者能够快速构建出具备专业数据洞察能力的安全工具和报告,极大地提升了分析结果的可读性与决策支持效率。
词云图:文本数据的高频特征提取
在处理大规模非结构化或半结构化文本数据时,例如分析海量Webshell样本、解析威胁情报报告、或从应用日志中提取异常模式,快速识别核心主题与高频关键词至关重要。词云图(Word Cloud)作为一种强大的视觉分析工具,通过将词汇的频率或重要性映射为其在图中的显示大小,能够帮助分析人员在短时间内抓住文本语料库的焦点。Yakit 提供了 yakit.NewWordCloud 接口,专门用于将词频数据转化为直观的词云图。
核心函数与使用流程:yakit.NewWordCloud
生成词云图的流程同样遵循“初始化、填充、渲染”的标准化模式,其核心在于将词汇-权重对(key-value pair)进行有效的视觉编码。
-
初始化图表对象:
graph = yakit.NewWordCloud(title string)- title (
string): 定义词云图的标题,例如“Webshell 样本高频函数分析”。
- title (
-
添加词汇及其权重:
graph.Add(key string, value float64, group string)-
key (
string): 代表一个独立的词汇或短语,是词云图构成的基本单元。 -
value (
float64): 该词汇的权重,通常是其出现频率或根据TF-IDF等算法计算出的重要性得分。value** 的大小与词汇在图中显示的尺寸成正比**,这是词云图实现视觉聚焦的关键机制。 -
group (
string): 分组标识。该参数目前主要用于内部数据处理,在标准用法下通常传入固定字符串(如"1")。
-
-
注意:当对同一个
key多次调用Add方法时,Yakit 的后端逻辑会自动累加其value值。这一特性对于流式处理或分块处理文本数据并统计词频的场景非常实用。 -
渲染输出图表:
yakit.Output(graph)- 将填充完毕的
graph对象提交给 Yakit 客户端进行最终渲染。
- 将填充完毕的
示例:分析安全报告中的关键词分布
以下示例模拟了对一份年度网络安全报告摘要进行关键词提取与可视化的过程。
// 步骤一:确保 Yakit 环境已初始化
yakit.AutoInitYakit()
// 步骤二:创建名为 "2023年度安全威胁关键词" 的词云图对象
graph = yakit.NewWordCloud("2023年度安全威胁关键词")
// 步骤三:添加从报告中提取的关键词及其出现频次
graph.Add("供应链攻击", 18, "1")
graph.Add("勒索软件", 25, "1")
graph.Add("APT", 15, "1")
graph.Add("零日漏洞", 22, "1")
graph.Add("数据泄露", 19, "1")
graph.Add("身份认证", 12, "1")
// 步骤四:渲染词云图
yakit.Output(graph)
图:Yakit插件生成的2023年度安全威胁关键词词云
上述代码执行后,将在 Yakit 客户端生成一张词云图。其中,“勒索软件”由于其权重值(25)最高,将以最大的字体显示在最突出的位置;而“APT”、“身份认证”等词汇则会相应较小,但仍清晰可见。这种直观的视觉层级使用户无需阅读完整报告,即可快速把握报告所强调的核心威胁。yakit.NewWordCloud 极大地降低了文本探索性分析的认知成本,是进行快速情报研判和日志分析的利器。
5.3.4.7 本章小结:从数据到洞察的UI框架
在本章中,我们系统性地探索了 Yakit 脚本提供的丰富UI组件系统,从基础的 yakit.Warn 到结构化的 yakit.EnableTable,再到具备高级交互能力的 StatusCard、Progress,以及最终实现数据可视化的线性图、饼状图和词云图。这些功能共同构成了 Yakit 插件开发中连接后端逻辑与前端呈现的桥梁。
Yakit 的 UI 框架在设计上遵循两大核心原则:高度抽象与数据驱动。开发者无需掌握复杂的前端技术栈,即可通过调用简洁的 Go 风格 API,构建出专业、交互性强的用户界面。更重要的是,所有 UI 组件都以数据为中心,实现了从“原始输出”到“结构化呈现”,再到“可视化洞察”的逐层深化。这不仅是简单的界面美化,更是为了提升信息传递的效率和深度。
通过熟练运用本章介绍的 API,开发者能够将原本孤立、静态的安全脚本,转化为一个动态、可交互、结果清晰的专业工具。无论是构建一个带实时进度和状态反馈的批量扫描器,还是开发一个能自动生成统计报告的数据分析插件,Yakit 的 UI 系统都提供了坚实的基础。这套框架是 Yakit 赋能安全从业者创造高质量、定制化工具的核心能力之一,它最终的目标是帮助用户更快地从海量数据中发现问题、验证猜想、并形成决策。在后续的章节中,我们将结合具体的实战案例,进一步展示如何将这些UI组件融入完整的安全解决方案中。