Integrating custom scripts in AppScan for enhanced DAST scanning

Integrate custom scripts into your DAST scans with AppScan to manipulate HTTP requests and responses dynamically, enhancing the flexibility and effectiveness of your security testing.

Activating custom scripts in AppScan

Custom scripts in AppScan are activated by default (CustomScripts.Enable setting is set to True).
Note: Once activated, these scripts are executed with every HTTP request and response within AppScan Standard, during operations such as recording and scanning.

To deactivate custom scripts:

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

Implementing and managing custom scripts for enhanced DAST scans

Add custom JavaScripts to your DAST scans as follows:
  1. Select Custom scripts from the Advanced section in the configuration settings.
  2. Click the + Add button.
  3. Edit the prepopulated Name field as necessary.
  4. Choose the appropriate Execution context for your script: :
    1. Before requestfor scripts to run before sending requests.
    2. After response for scripts to run after receiving responses.
    3. Parameter for tasks that involve multiple-steps or filling out forms. You can use this context in conjuction with the setResultValue method to allow scripts to return results to AppScan.
  5. Enter your JavaScript in the JavaScript field (code-editor) and use Ctrl+Space for auto-complete suggestions within the AppScan environment.

    Verify your script for syntax accuracy and compatibility with the supported JavaScript version in AppScan. For detailed guidelines, refer to JavaScript code.

    Tip: Syntax errors are highlighted within the code editor and can be identified by tooltips, although they do not prevent script execution.

    Image showing the tool tip of code editor

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

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

    Example 2: Store API keys from responses in 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: Append 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}`)
        }    
    Example 4: Add another header with a different value.
    // Before each request
    function addUniqueHeader(headerName, value) {
        if(!(request.headers[headerName] != null && request.headers[headerName].includes(value))) {
            request.headers.add(headerName, value)
            }
    }
    addUniqueHeader(“headerName”,”Value”)   
  6. Click Add to save your script. The script is added to the list and enabled by default.
    Note: These scripts 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. Test your scripts in a controlled environment before live deployment to ensure they function as expected.
  7. You can choose to enable or disable a script from the list of custom scripts using the Enable checkbox.
  8. Manage your scripts using the Enable checkbox or by editing, deleting, or reordering them in the script list. Options menu
  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: Custom scripts are encrypted by default when exported in a template. To disable encryption, adjust the General: Encrypt sensitive data setting to False in the scan configuration.

Examples of custom scripts for AppScan

Examples of custom scripts for AppScan are uploaded to the GitHub repository.

Guidelines for writing effective JavaScript code in AppScan

Context

Request: Modify request data before it is sent to the server. Each request object includes properties such as headers, body, path, and method, which you can manipulate to customize your DAST scans.
  1. Headers: Contains all the request headers. You can add, remove or modify any header except the default headers.
    // Adds new header if absent; overrides if present
    request.headers.set(key, value)
    // Adds header
    request.headers.add(key, value)
    CAUTION: Since the scripts might execute multiple times per request, it is advisable to use the set method to add new headers, as it replaces existing headers and prevents duplicates. Use the add method only when you need multiple headers with the same name and different values. The add method will not add a header if one with the same name and value already exists. Be careful to avoid duplicating headers, as this can cause 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: AppScan omits the query delimiter (‘?’) if the query parameter is empty.
    // 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 response. The response is in string format, so you need to convert it manually if needed (to JSON for example). This property is read-only; it remains the same even if you attempt to change it before running the script.
    // 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")

Results

Sets the result of the custom script execution and works in conjunction with the 'Parameter' context. AppScan will use this result when necessary.

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

Constraints

  1. Avoid using restricted methods in your scripts to prevent execution failures and errors.
    1. eval
    2. Function (constructor)
    3. setTimeout
    4. setInterval

      Using any of these methods will cause the script to fail and produce an error.

  2. Adhere to script execution limits to ensure optimal performance and avoid resource overuse.
    1. 5 seconds (max) of execution time
    2. 64 MB of memory allocation

      If at least one of these limits is exceeded, the script will fail.

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