Custom scripts

Custom scripts can make your DAST scan more dynamic. You can add the JavaScripts to manipulate HTTP requests or responses during a scan. This can be done before a request is sent to the server or after a response is received.

How to enable custom scripts for AppScan

Custom scripts are enabled by default (CustomScripts.Enable setting is set to True).

To disable the scripts:

  1. Go to Tools > Options > Advanced tab.
  2. Set the CustomScripts.Enable setting to "False".

How to add and enable custom scripts for DAST scans

You can add custom JavaScripts to your DAST scan in the following way:
  1. Navigate to Custom scripts under the Advanced section of the configuration settings.
  2. Click the + Add button.
  3. The Name field is prepopulated with the date and time of script creation. You can change the name if needed.
  4. In the Execution context dropdown, select either Before request or After response depending on when you want the script to run.
  5. In the JavaScript field, enter your JavaScript code. To receive suggestions for JavaScript code within the AppScan environment, press Ctrl+Space.

    Ensure that your code is free of syntax errors and is compatible with the version of JavaScript supported by AppScan. For more information about the rules and how to write the code, see JavaScript code.

    Example 1: Add login details to your requests automatically (without using Login Management configuration).

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

    Example 2: Check if the response includes an API key and store it in the global variables.

    // 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)
    }

    Example 3: Set the authorization API key to the header if it exists.

    // Before each request
    var authorization = environment.globalVariables.get("Authorization")
        if(authorization) {
            request.headers.set("Authorization", authorization)
            log(`authorization is ${authorization}`)
        }    
  6. Click Add to save your script. The script is added to the list and enabled by default.
    Note: The scripts you add are executed for each HTTP request or response during the DAST scan.
    Use this feature wisely to enhance the efficiency and effectiveness of your scans. Always test your scripts in a controlled environment before deploying them in a live scan to avoid any unexpected behavior or results.
  7. You can choose to enable or disable a script from the list of custom scripts using the Enable checkbox.
  8. You can modify, delete or change the order of execution of the scripts. On the right-hand corner of the script, click Three-dot-menu-vertical and select the option as follows:
    • Edit
    • Delete
    • Move up
    • Move down
  9. Click Start full scan to run the scan.
  10. Result: The enabled scripts are executed for each request or response. You can view the details of the scripts from Data > Requests tab > Request/Response.
    Note: When you export custom scripts in a template, they are encrypted by default. To manually disable this encryption, from the scan configuration settings, go to Advanced Configuration and set the General: Encrypt sensitive data to False.

JavaScript code

Context

Request: You can read or modify the request data before it is sent to the server. Each request is represented by a request object, which has the following properties:
  1. Headers: Contains all the request headers. You can add, remove or modify any header except the default headers.
    // Add header
    request.headers.add(key, value)
    // Override header if exist
    request.headers.set(key, value)
    Note: Since the scripts might be executed multiple times per request, it is advisable to use the set method to add new headers, as it replaces existing headers and prevents duplicates. The add method should only be used when multiple headers with the same name are necessary, and it will not add a header if one with the same name and value already exists. Care should be taken to avoid header duplication, which can lead to errors.
    // 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"
    Default headers (not case-sensitive): These are read-only and cannot be modified.
    • Host: Identifies the host that the request is being sent to.
    • Content-Length: Calculates the size of the request and sends this information to the server.
  2. Body: Contains information about the body of the request. The body is of type 'string'.
    // Access body
    request.body
    // Example
    request.body = "{ "username":"user" }"
  3. Path: Holds the information about the endpoint to which the request is intended. The endpoint format excludes the base-url (e.g. /api/v1/test). This property is read-only; it remains the same as when it was first set, even if you attempt to change it before running the script.
    // Access url path
    request.path
    // Example
    if (request.path.startsWith("api/v1/test"))
  4. Query: Holds the query string and parameters (if there are any) for the current request. You can add or remove query parameters from this object. Each value is treated as a single object, like 'key=value'. When you use 'getQueryString', it gives you a string that has all the items concatenated with an '&' sign. This string is a copy of the items, so if you change the string, you're only changing a copy of the query.
    Note: If the query parameter is empty, AppScan won’t send the query delimiter (‘?’)
    // 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. Method: Method of the request (e.g. GET/POST/etc). This property is read-only; it remains the same as when it was first set, even if you attempt to change it before running the script.
    // Access to the method
    request.method
    // Example
    if (request.method == "GET")
Response: An object that contains the response data and a related parent request. You can retrieve information, modify or save the response data. Each response is represented by a response object, which has the following properties:
  1. Body: Holds the response body from the request. The response is in string format, so you need to convert it manually if needed (to JSON for example).
    // Access body
    response.body
    // Example
    let json = JSON.parse(config.response)
  2. Path: Contains the information about the response endpoint.
    // Access to the relative url
    response.path
    // Example
    if (response.path.startsWith("api/v1/test"))
  3. Status: Stores details about the response status. The status is an object with the following properties:
    // 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. Headers: Contains all the response headers. You can add, remove or modify any header except the default headers.
    // 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. ParentRequest: An object that contains the request that was sent prior to the response. The request data is read-only. The request has all the properties as the previously defined request object.
    // Example
    response.request.path //- access to the path from the request

Environment Data

A short-term data object stores important information until the task ends, like when you pause, stop, or complete a scan, log in, or run a manual test.

  1. Global variables: The global variables data is a key-value data structure designed to store user-specific data pairs. The keys are unique (meaning no two elements can have the same key, though multiple elements can share the same value), which is useful for sharing data between running scripts.

    This data is erased every time a new scan or rescan is done.

    // 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. Scan status: Contains information about the current scan.
    // determine if the current phase is explore phase
    environment.status.isInExplore()
    // determine if the current phase is test phase
    environment.status.isInTest()

Logging

Enable logging features in the script to help with debugging and identifying problems. The command 'console.log' does not function here. Instead, use the word 'log' followed by a message in quotes. Also, if there's an error during the engine's operation that is not related to JavaScript syntax or usage, this error is recorded in the engine trace log.

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

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

Constraints

Do not use the following methods in the script context:
  • eval
  • Function (constructor)
  • setTimeout
  • setInterval
Using any of these methods will cause the script to fail and produce an error

Error handling

If the script uses any restricted functions and encounters an error or exception, it stops working. This failure pauses the current scan and the error details are recorded in the ‘UserScript.log’ file for further review.

Loading template configured with scripts

You can start a scan by loading a template configured with scripts enabled. A sample template, pre-configured with scripts for the demo.testfire description file, is available at Program Files (x86)\HCL\AppScan Standard\Templates.

JavaScript versions supported by AppScan

Currently, ECMAScript versions from ES6 (2015) to ES15 (2024) are supported, except for the following features:
  • ES6 (2015)
    • Generators
    • Tail calls
  • ES8 (2017)
    • Shared memory and atomics
  • ES15 (2024)
    • Atomics.waitAsync
    • Regular expression flag \v