定制脚本

定制脚本可以使您的 DAST 扫描更加灵活。您可以添加 JavaScript,以便在扫描过程中操纵 HTTP 请求和响应,无论是在向服务器发送请求之前还是在收到响应之后。

概述

定制脚本在 AppScan 中运行,并对内存中的请求和响应模型进行操作,使您可以通过编程方式调整请求和检查响应,而无需构建扩展或使用代理服务器。

概览

请求前:在 HTTP 请求发送到您的应用程序之前执行脚本以修改该请求(添加/删除标头、加密数据、处理令牌等)。

响应后:在从您的应用程序收到 HTTP 响应之后对其进行处理(解密内容、提取值等)。

参数:仅使用 JavaScript 为多步骤操作和表单填充器生成值。

合理使用此功能可以提高扫描的效率和效果。在实时部署之前,在受控环境中测试脚本,以确保它们按预期运行。

示例模板路径:C:\Program Files (x86)\HCL\AppScan Standard\Templates。

自定义脚本示例:GitHub 存储库。
  • 身份验证脚本 - 动态计算、生成和附加所需的安全凭据或令牌:
    • 基本
      • 基本 HTTP 身份验证 - 将用户名和密码编码为 Base64 字符串并将其注入请求标头。
    • 凭证
      • 添加凭据 - 在 POST 请求的正文中包含格式化为 JSON 对象的用户名和密码。
      • 注入标头 - 手动将 "Authorization" 字段插入 HTTP 请求中。
      • 解析响应 - 检查服务器的响应正文以提取授权令牌。
  • 加密 - 对请求有效负载或标头应用加密或哈希算法。
    • HMAC
      • HMACSHA512 - 使用 SHA-512 生成密钥哈希消息认证码。
    • SHA
      • SHA256_RSA - 通过使用 SHA-256 对数据进行哈希处理并使用 RSA 私钥对其进行加密来创建数字签名。
      • SHA512 - 生成高度安全的 512 位固定长度加密哈希。
      • SHA256 - 生成标准的 256 位哈希。

添加定制脚本

将定制 JavaScript 添加到您的 DAST 扫描中,如下所示:
  1. 在配置设置中的高级部分下,选择定制脚本。
  2. 单击 + 添加按钮。
  3. 如有需要,可修改预填的名称字段。
  4. 为脚本选择适当的执行上下文::
    1. 请求前脚本:在发送请求前运行脚本。
    2. 响应后:在收到响应后运行脚本。
    3. 参数:用于包含多个步骤或填写表单的任务。您可以将此上下文与 setResultValue 方法结合使用,以便脚本将结果返回给 AppScan。
  5. 在 JavaScript 字段(代码编辑器)中输入JavaScript,并使用 Ctrl+空格键在 AppScan 环境中自动完成建议。
  6. 单击添加以保存您的脚本。该脚本将添加到列表中并缺省启用。
  7. 您可以使用启用复选框,从定制脚本列表中选择启用或禁用脚本。
  8. 使用启用复选框,或通过在脚本列表中编辑、删除脚本或重新排序脚本来管理脚本。选项菜单
  9. 单击开始全面扫描运行扫描。
  10. 结果:将针对每个请求或响应执行启用的脚本。您可以从数据 > 请求选项卡 > 请求/响应查看脚本的详细信息。

    请验证脚本的语法是否正确,并确认其与 AppScan 支持的 JavaScript 版本兼容。如需详细指导,请参阅 JavaScript 代码。

    注:
    • 定制脚本保存在扫描配置文件中,可使用模板导出。它们不包括在扫描结果中。
    • 缺省情况下,在模板中导出定制脚本时,将对其进行加密。

激活定制脚本

定制脚本缺省情况下处于启用状态,并在录制和扫描过程中对每个 HTTP 请求和响应执行。

要禁用脚本执行,请转到工具 > 选项 > 高级选项卡,然后将 CustomScripts.Enable 设置为“False”。

执行上下文

请求前

用途:修改传出的 HTTP 请求。

数据:只有请求和环境对象可用,其他对象将为 null。
// 从存储的全局变量中附加 Authorization
var token = environment.globalVariables.get("Authorization");
if (token) request.headers.set("Authorization", token);

响应后

用途:检查响应和持续值。

数据:只有响应和环境数据对象可用。其他对象将为 null。
// 从 JSON 响应中捕获令牌并存储以供重用
try {
		var result = JSON.parse(response.body);
		if (result && result.Authorization) {
			environment.globalVariables.addOrUpdate("Authorization", result.Authorization);
		}
} catch(e) { log(e); }

参数

用途:为多步骤操作和表单填充器生成值。
  • 此上下文中没有可用的数据对象(请求或响应);使用它们将返回 null。
  • 通过 setResultValue(value) 返回值。

在表单填充器中,当您选择值源 > 定制脚本时,列表仅显示其执行上下文为参数的脚本。

在多步骤操作中,您还可以使用参数脚本提供动态值。
// 示例:为注册流程返回唯一的电子邮件
function random(n) { return Math.random().toString(36).slice(2, 2+n); }
var email = "test_" + Date.now() + "_" + random(6) + "@example.test";
setResultValue(email);

API 参考文档

请求对象(请求上下文之前)- 完整 API
// 示例:
// 处理标头
request.headers.set("Authorization", "Bearer");
request.headers.add("X-Trace", "abc");

// 操作正文
request.body = JSON.stringify( { user:"admin" });
if(request.body.includes("admin") { ... }

// 读取路径
if (request.path.startsWith("/api/v1")) { ... }

// 编辑查询
request.query.addOrUpdate("lang","en")
request.query.remove("debug")
request.query.getQueryString()

// 读取方法
if (request.method == "GET") { ... }
警告:
由于脚本可能在每个请求中执行多次,建议使用 set 方法添加新标头,因为该方法会替换现有标头,从而避免重复。仅当需要多个名称相同但值不同的标头时,才使用 add 方法。如果已存在名称和值都相同的标头,add 方法将不会添加新标头。请注意避免重复标头,因为这可能会导致错误。
响应对象(响应上下文之后)- 完整 API
// 示例:
// 处理标头
response.headers.set("Authorization", "Bearer");
response.headers.add("X-Trace", "abc");

// 读取正文(因为它是只读的)
var j = JSON.parse(response.body);

// 读取路径
if (response.path.startsWith("/api/v1")) { ... }

// 读取响应
if (response.status.statusCode == 200) { ... }

// 读取父请求
if (response.request.path.startsWith("/api/v1")) { ... }
参数上下文 - 完整 API
// 将结果更新回 AppScan
setResultValue("ABC-" + Date.now());

环境数据

短期数据仅在任务结束前可用 - 任务结束的场景包括暂停、停止或完成扫描,以及登录或结束手动测试。每次启动新扫描或重新扫描时,短期数据都会被清除。

  1. 全局变量:全局变量数据是一种键-值数据结构,旨在存储用户特定的数据对。键是唯一的(意味着没有两个元素可以具有相同的键,尽管多个元素可以共享相同的值),这对于在运行中的脚本之间共享数据非常有用。
  2. 扫描状态:包含当前扫描的相关信息。
    // 检查扫描状态
    environment.status.isInExplore();
    environment.status.isInTest();

日志记录 - 完整 API

在脚本中启用日志记录功能以帮助进行调试和故障诊断。此环境中不支持标准 console.log 命令。相反,应改用 log 函数并传入带引号的消息,例如:log("Your message here")。如果在引擎操作期间发生与 JavaScript 语法或用法无关的运行时错误,则会将错误记录在引擎跟踪日志中,以便进一步分析。

日志将写入 AppScan 日志文件夹中的 UserScript.log 文件。

缺省情况下,该文件夹为 [UserDataFolder]\AppData\Roaming\HCL\AppScan Standard\logs。

约束和执行限制

  • 受限函数:eval、Function(构造函数)、setTimeout、setlnterval。如果使用了其中任何一个函数,脚本将失败。
  • 限制:最大执行时间:5 秒;内存限制:64 MB。超过这些限制会导致脚本失败。

    失败时扫描将暂停,详细信息将记录在 UserScript.log 中。

支持的 JavaScript 版本

ECMAScript ES6 (2015) 至 ES15 (2024),例外:
  • ES6:生成器、尾调用
  • ES8:共享内存和原子操作
  • ES15:Atomics.waitAsync、正则表达式标志 \v

自动完成中可用的对象

以下对象会显示在脚本编辑器的自动完成建议中,便于查找和使用:

  • Keyword
  • Array
  • 日期
  • Object
  • String
  • RegExp
  • JSON
  • Map
  • Math
受支持但不在自动完成中

脚本引擎完全支持以下对象,但它们不会显示在自动完成列表中。您仍然可以通过在编辑器中直接键入其名称来使用它们:

  • ArrayBuffer
  • BigInt
  • Boolean
  • DataView
  • 错误
  • Iterator
  • 编号
  • Promise
  • 代理
  • Reflect
  • SharedArrayBuffer
  • Symbol
  • TypedArray

  • WeakMap

  • WeakRef

  • WeakSet

    注:
    “不在自动完成中”表示编辑器不会自动建议这些项目;但是,这些对象的实现方式和行为与它们在标准 JavaScript 中完全相同。

故障诊断

常见问题和解决方案:
  1. 脚本失败:请查看日志文件夹(默认:[UserDataFolder]\AppData\Roaming\HCL\AppScan Standard\logs)以获取有关错误的详细信息。如果遇到未经处理的异常,请将代码封装在 try...catch 块中,以便正常处理。
  2. 脚本未执行:确保已在配置设置中启用脚本。

    如果问题与登录/授权相关,请考虑从配置 → 高级配置 → 安全包顺序中移除协商值,以防止 AppScan 自动处理这些身份验证。

请求完整 API

表 1. 请求对象 - 属性
成员 类型 描述 示例
headers 对象 一个封装当前 HTTP 请求标头的对象,提供动态添加、更新、检索或移除标头的方法,可实现对请求元数据的精准控制。 request.headers.set("Authorization","Bearer ...")
body 字符串 当前 HTTP 请求的主体。 request.body = JSON.stringify({ user:"jsmith" })
path 字符串

只读🔒

当前 HTTP 请求的相对路径。

if (request.path.startsWith("/api/v1/")) { ... }
query 对象 包含多种方法的对象,可用于在当前 HTTP 请求中添加、更新或移除查询参数,支持对查询字符串进行动态操控。 request.query.addOrUpdate("lang","en") request.query.remove("debug")
method 字符串

只读🔒

当前 HTTP 请求所使用的方法。

if (request.method == "GET") {{ ... }}
表 2. 查询对象 - 方法
成员 类型 描述 示例

addOrUpdate

(name: string,value: string)

空 将新值添加到查询字符串或更新现有值。 request.query.addOrUpdate("appscan","appscanvalue")
getQueryString() 字符串

返回当前 HTTP 请求的完整查询字符串,包括所有参数及其值。

request.query.getQueryString()

包含

(name: string)

布尔值

检查当前 HTTP 请求中是否存在特定查询参数。

如果参数存在,则返回 true,否则返回 false。

request.query.contains("lang")

除去

(name: string)

布尔值

从当前 HTTP 请求中移除特定查询参数。

如果参数已移除,则返回 true,否则返回 false。

request.query.remove("lang")
clear() 空

从当前 HTTP 请求中移除所有查询参数。

request.query.clear()

响应完整 API

表 3. 响应 objectStatus 对象
成员 类型 描述 示例
headers 对象 一个封装当前 HTTP 响应标头的对象,提供动态添加、更新、检索或移除标头的方法,可实现对响应元数据的精准控制。 response.headers.set("Authorization","Bearer ...")
body 字符串 当前 HTTP 响应的主体。 response.body = JSON.stringify({ user:"jsmith" })
path 字符串

只读🔒

当前 HTTP 响应的相对路径。

if (response.path.startsWith("/api/v1/")) { ... }
状态 对象 表示当前 HTTP 响应状态的对象,包括状态代码和状态消息属性。 if (response.status.code == 200) { ... }
request 对象 表示启动响应的请求的对象。请求数据为只读,包括在前面描述的请求对象中定义的所有属性。 if (response.request.path) { ... }
表 4. 属性
成员 类型 描述 示例
statusCode 数字

只读🔒

当前响应的 HTTP 状态代码。

response.status.statusCode = 200
statusDescription 字符串

只读🔒

当前响应的 HTTP 状态消息。

response.status.statusDescription = "OK"
statusLine 字符串

只读🔒

当前响应的 HTTP 状态行。

response.status.statusLine = "HTTP/1.1 200 OK"

参数完整 API

表 5. 参数对象
成员 类型 描述 示例

setResultValue

(value: string)

空 设置当前参数上下文脚本的结果值。 setResultValue("ABC-" + Date.now())

通用对象

表 6. 标头对象 - 属性
成员 类型 描述 示例
items 数组

只读🔒

当前 HTTP 请求中所有标头的数组。

if (request.headers.items.length > 0) { ... }

if (response.headers.items.length > 0) { ... }

表 7. 标头对象 - 方法
成员 类型 描述 示例
添加

(name: string,value: string)

空

向当前 HTTP 请求添加新标头。如果已存在具有相同名称和值的标头,则不会再次添加。

request.headers.add("Authorization", "MyCoolApiKey")

response.headers.add("Authorization",MyCoolApiKey")

集合

(name: string,value: string)

空 设置当前 HTTP 请求的标头。如果标头已存在,则替换其值。

request.headers.set("Authorization", "SecondApiKey")

response.headers.set("Authorization","SecondApiKey")

除去

(name: string)

空 从当前 HTTP 请求中移除标头。 request.headers.remove("Authorization")response.headers.remove("Authorization")
[name: string] 字符串 按名称检索特定标头的值。如果标头不存在,则返回 null。

request.headers['Authorization'] = "myapikey3"

response.headers['Authorization'] = "myapikey3"

环境数据

表 8. 环境对象 - 属性
成员 类型 描述 示例
globalVariables 对象 用于存储用户特定数据对的键值数据结构。键必须是唯一的,确保没有重复项,而多个键可以使用相同的值。此结构有助于在运行的脚本之间共享数据。 environment.globalVariables.addOrUpdate(key, value)
状态 对象 有关当前扫描状态的信息。 environment.status.isInExplore()
表 9. 全局变量对象 - 方法
成员 类型 描述 示例

addOrUpdate

(key: string,value:

string)

空 向全局变量添加新的键值对,或更新现有键的值。 environment.globalVariables.addOrUpdate("mycoolkey", "testValue")

除去

(key: string)

布尔值 从全局变量中移除键值对。如果找到并移除了键,则返回 true,否则返回 false。 environment.globalVariables.remove("mycoolkey")
get

(key: string)

字符串 从全局变量检索特定键的值。如果键不存在,则返回空字符串。 environment.globalVariables.get("mycoolkey")
表 10. 状态对象 - 方法
方法 类型 描述 示例
isInExplore() 布尔值 检查扫描当前是否处于探索阶段。 environment.status.isInExplore()
isInTest() 布尔值 检查扫描当前是否处于测试阶段。 environment.status.isInTest()

日志记录

表 11. 方法
成员 类型 描述 示例
log(message: string) log(message: string, source: string) 空 添加具有指定消息和可选来源的新日志条目。 log("This is a log message")

log("This is a log message", "mySource")