定制脚本

定制脚本可以使您的 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")