在 AppScan 中集成定制脚本,以增强 DAST 扫描

使用 AppScan 将定制脚本集成到您的 DAST 扫描中,以动态处理 HTTP 请求和响应,从而提高安全测试的灵活性和有效性。

在 AppScan 中激活定制脚本

缺省情况下,AppScan 中的定制脚本处于启用状态(CustomScripts.Enable 设置设为 True)。
Note: 一旦激活,这些脚本就会在 AppScan Standard 中的每个 HTTP 请求和响应中执行,同时进行记录和扫描等操作。

要停用定制脚本:

  1. 转至工具 > 选项> 高级选项卡。
  2. CustomScripts.Enable 设置设定为“False”。

实施和管理用于增强型 DAST 扫描的定制脚本

将定制 JavaScript 添加到您的 DAST 扫描中,如下所示:
  1. 在配置设置中的高级部分下,选择定制脚本
  2. 单击 + 添加按钮。
  3. 如有需要,可修改预填的名称字段。
  4. 为脚本选择适当的执行上下文::
    1. 请求前脚本:在发送请求前运行脚本。
    2. 响应后:在收到响应后运行脚本。
    3. 参数:用于包含多个步骤或填写表单的任务。您可以将此上下文与 setResultValue 方法结合使用,以便脚本将结果返回给 AppScan
  5. JavaScript 字段(代码编辑器)中输入JavaScript,并使用 Ctrl+空格键在 AppScan 环境中自动完成建议。

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

    Tip: 代码编辑器会高亮显示语法错误,并通过工具提示帮助识别错误,但这些错误不会阻止脚本执行。

    显示代码编辑器工具提示的图像

    示例 1:可自动将登录详细信息添加到请求中,无需启用登录管理配置。

    // Before each request
    if(request.method == "POST") {
        request.body = JSON.stringify({
            "username": "jsmith",
            "password": "demo1234"
        })
    }

    示例 2:将来自响应的 API 密钥存储在全局变量中。

    // After each response
    try {
            var result = JSON.parse(response.body)
            log(`parsing: ${JSON.parse(response.body).Authorization}`)
            if(result.Authorization) {
                environment.globalVariables.addOrUpdate("Authorization", result.Authorization)
                log(`key = ${result.Authorization}`)
            }
    }
    catch (error) {
            log(error)
    }

    示例 3:将授权 API 密钥(如果存在)附加到标头。

    // Before each request
    var authorization = environment.globalVariables.get("Authorization")
        if(authorization) {
            request.headers.set("Authorization", authorization)
            log(`authorization is ${authorization}`)
        }    
    示例 4:添加另一个具有不同值的标题。
    在每个请求函数之前添加唯一的头部 
    function addUniqueHeader(headerName, value) {
        if(!(request.headers[headerName] != null && request.headers[headerName].includes(value))) {
            request.headers.add(headerName, value)
            }
    }
    addUniqueHeader(“headerName”,”Value”) 
  6. 单击添加以保存您的脚本。该脚本将添加到列表中并缺省启用。
    Note: 在 DAST 扫描期间,对每个 HTTP 请求或响应执行脚本。
    合理使用此功能可以提高扫描的效率和效果。在实时部署之前,在受控环境中测试脚本,以确保它们按预期运行。
  7. 您可以使用启用复选框,从定制脚本列表中选择启用或禁用脚本。
  8. 使用启用复选框,或通过在脚本列表中编辑、删除脚本或重新排序脚本来管理脚本。 选项菜单
  9. 单击开始全面扫描运行扫描。
  10. 结果:将针对每个请求或响应执行启用的脚本。您可以从数据 > 请求选项卡 > 请求/响应查看脚本的详细信息。
    Note: 缺省情况下,定制脚本在导出到模板时会进行加密。要禁用加密,请在扫描配置中将常规:加密敏感数据设置调整为 False

AppScan 的定制脚本示例

AppScan 的定制脚本示例将上载到 GitHub 存储库

在 AppScan 中编写有效 JavaScript 代码的准则

上下文

请求:在将请求数据发送到服务器之前修改请求数据。每个请求对象都包含了 headers、body、path 和 method 等属性,您可以根据需要对这些属性进行修改,以实现 DAST 扫描的定制。
  1. 标头:包含所有请求标头。除缺省标头外,您可以添加、删除或修改任何标头。
    // 如果不存在则添加新头;如果存在则覆盖 
    request.headers.set(key, value)
    // 添加请求头 
    request.headers.add(key, value)
    CAUTION: 由于脚本可能在每次请求中多次执行,建议使用 set方法来添加新头信息,因为它会替换现有头信息并防止重复。仅在需要具有相同名称和不同值的多个头部时使用add方法。add 方法不会添加一个已经存在相同名称和值的头。请小心避免重复标题,因为这可能会导致错误。
    // Remove header
    request.headers.remove(key)
    // Modify header
    request.headers[key]
    // Examples
    request.headers.add("Authorization", "MyCoolApiKey")
    request.headers.set("Authorization", "SecondApiKey")
    request.headers.remove("Authorization")
    request.headers['Authorization'] = "myapikey3"
    缺省标头(不区分大小写):这些是只读的,无法修改。
    • 主机:识别请求将发送到的主机。
    • 内容长度:计算请求的大小,并将该信息发送到服务器。
  2. 正文:包含有关请求正文的信息。正文为“字符串”类型。
    // Access body
    request.body
    // Example
    request.body = "{ "username":"user" }"
  3. 路径:保存有关请求所针对的端点的信息。端点格式不包括基本 URL(例如 /api/v1/test)。该属性是只读的;即使您在运行脚本之前尝试进行更改,它仍然会与首次设置时相同。
    // Access url path
    request.path
    // Example
    if (request.path.startsWith("api/v1/test"))
  4. 查询:保存当前请求的查询字符串和参数(如果有)。您可以在此对象中添加或删除查询参数。每个值都被视为单个对象,如“key=value”。当您使用“getQueryString”时,它会为您提供一个字符串,其中包含用“&”符号连接的所有项目。该字符串是项目的副本,因此如果更改该字符串,也只是更改查询的副本。
    Note: 如果查询参数为空,AppScan 会忽略查询分隔符 (‘?’)。
    // Retrieve the current query string
    request.query.getQueryString()
    
    // Example
    // Add new value (or update existing one)
    request.query.addOrUpdate("appscan","appscanvalue")
    // Check if value exist
    request.query.contains("appscan") // for key only check
    request.query.contains("appscan=appscanvalue") // for key and value check
    // Remove an item
    request.query.remove("appscan") // for key only check
    request.query.remove("appscan=appscanvalue") // for key and value check
    request.query.clear() // remove all values
  5. 方法:请求的方法(例如 GET/POST/等)。该属性是只读的;即使您在运行脚本之前尝试进行更改,它仍然会与首次设置时相同。
    // Access to the method
    request.method
    // Example
    if (request.method == "GET")
响应:包含响应数据和相关父项请求的对象。您可以检索信息、修改或保存响应数据。每个响应都由一个响应对象表示,该对象具有以下属性:
  1. 正文:保存请求的响应正文。响应为字符串格式,如有必要,您需要手动进行转换(例如,转换为 JSON)。该属性为只读;即使您在运行脚本前尝试修改,它也不会发生变化。
    // Access body
    response.body
    // Example
    let json = JSON.parse(config.response)
  2. 路径:包含有关响应端点的信息。
    // Access to the relative url
    response.path
    // Example
    if (response.path.startsWith("api/v1/test"))
  3. 状态:存储有关响应状态的详细信息。状态是一个具有以下属性的对象:
    // Access status
    response.status.statusCode // A numeric representation of the status
    response.status.statusDescription // The description of the status code
    response.status.statusLine //the whole status line including the http version
    // Example
    if (response.status.statusCode == 200) // check only the number
    if (response.status.statusDescription == "OK") // Check only the description
    if (response.status.statusLine == "HTTP1.1 200 OK") // Full line
  4. 标头:包含所有响应标头。除缺省标头外,您可以添加、删除或修改任何标头。
    // Add header
    response.headers.add(key, value)
    // Override header if exist
    response.headers.set(key, value)
    // Remove header
    response.headers.remove(key)
    // Modify header
    response.headers[key]
    // Examples
    response.headers.add("Authorization", "MyCoolApiKey")
    response.headers.set("Authorization", "SecondApiKey")
    response.headers.remove("Authorization")
    response.headers['Authorization'] = "myapikey3"
  5. 父项请求:包含在响应之前发送的请求的对象。请求数据是只读的。该请求具有先前定义的请求对象的所有属性。
    // Example
    response.request.path //- access to the path from the request

环境数据

短期数据对象会存储重要信息,直到任务结束,例如当您暂停、停止或完成扫描、登录或运行手动测试时。

  1. 全局变量: 全局变量数据是一种键-值数据结构,旨在存储用户特定的数据对。键是唯一的(意味着没有两个元素可以具有相同的键,尽管多个元素可以共享相同的值),这对于在运行中的脚本之间共享数据非常有用。

    每次完成新扫描或重新扫描后,系统都会擦除这些数据。

    // Add data to the globals or update existing one
    environment.globalVariables.addOrUpdate(key, value)
    // Remove key data from the globals
    environment.globalVariables.remove(key)
    // Read data - retrieves the value for the current key (without removing it)
    environment.globalVariables.get(key)
  2. 扫描状态:包含当前扫描的相关信息。
    // determine if the current phase is explore phase
    environment.status.isInExplore()
    // determine if the current phase is test phase
    environment.status.isInTest()

日志记录

在脚本中启用日志记录功能以帮助进行调试和识别问题。“onsole.log”命令在这里不起作用。相反,请使用“log”一词,后跟带引号的消息。此外,如果引擎运行期间出现与 JavaScript 语法或使用无关的错误,该错误也会记录在引擎跟踪日志中。

// Log message to file
log("my cool logging")

// Log message with custom source
log("message", "my-cool-source")

结果

设置定制脚本执行的结果,并与“参数”上下文结合使用。AppScan 必要时将使用此结果。

// Sets the script execution result
setResultValue("result")

约束

  1. 避免在脚本中使用受限方法,以防脚本执行失败或出错。
    1. eval
    2. 函数(构造函数)
    3. setTimeout
    4. setInterval

      使用上述任一方法都会导致脚本执行失败并产生错误。

  2. 遵守脚本执行限制以确保最佳性能并避免资源过度使用。
    1. 5 秒(上限)执行时间
    2. 64 MB 内存分配

      只要超过其中任一限制,脚本就会失败。

错误处理

如果脚本使用任何受限制的函数并遇到错误或发生异常,将停止运行。此故障会暂停当前扫描,并将错误详细信息记录在“UserScript.log”文件中以供进一步检查。

加载配置了脚本的模板

您可以通过加载已启用脚本配置的模板来开始扫描。Program Files (x86)\HCL\AppScan Standard\Templates 中提供了一个示例模板,其中预先配置了 demo.testfire 描述文件的脚本。

AppScan 支持的 JavaScript 版本

目前支持 ES6 (2015) 至 ES15 (2024) 的 ECMAScript 版本,但以下功能除外:
  • ES6 (2015)
    • 生成器
    • 尾调用
  • ES8 (2017)
    • 共享内存和原子操作
  • ES15 (2024)
    • Atomics.waitAsync
    • 正则表达式标志 \v