Documentation / Application Security

Custom Attack

Custom Attacks (CVE)

Custom attacks let you ship your own business-logic checks to PAC. They live in a Git repository, are referenced from the CVE block of the PAC config, and run as part of any pentest. PAC supports two attack engines today:

  • ZAP scripts (JavaScript / Nashorn) — full access to ZAP's request/response pipeline.
  • Custom Bash scripts — standalone shell scripts that emit alerts via the prancer-lib library.

Before you start

  • A Git repository the Prancer scanner can clone (GitHub, GitLab, Bitbucket, self-hosted).
  • A personal access token / deploy key with repo and read:user scopes — see GitHub's PAT guide.
  • A collection in Prancer with Vault access enabled — see Vault.
  • A PAC config file generated via the PAC Wizard.

Attack file layout

Each custom attack is a directory containing the script plus a metadata.yaml describing it:

Git repository
.
├── Alert_on_HTTP_Response_Code_Errors
│   ├── http_sender_attack.js
│   └── metadata.yaml
└── BashHello
    ├── run-hello.sh
    └── metadata.yaml

See Metadata file format for the full schema.

Example 1 — ZAP HTTP sender script

http_sender_attack.js raises an alert for any response with status 4xx (other than 404) or 5xx. See ZAP scripting docs for the API surface.

// A script which raises alerts based on HTTP Response codes.
// Info-level for Client Errors (4xx, excl. 404), Low-level for Server Errors (5xx).

var control, model
if (!control) control = Java.type("org.parosproxy.paros.control.Control").getSingleton()
if (!model) model = Java.type("org.parosproxy.paros.model.Model").getSingleton()

var Pattern = Java.type("java.util.regex.Pattern")

pluginid = 100000   // https://github.com/zaproxy/zaproxy/blob/main/docs/scanners.md

function sendingRequest(msg, initiator, helper) { /* nothing */ }

function logger() {
    print('[' + this['zap.script.name'] + '] ' + arguments[0]);
}

function responseReceived(msg, initiator, helper) {
    var code = msg.getResponseHeader().getStatusCode()
    var extensionAlert = control.getExtensionLoader().getExtension(org.zaproxy.zap.extension.alert.ExtensionAlert.NAME)
    if (extensionAlert != null) {
        if (code < 400 || code >= 600) return
        var risk = 0    // Info
        var title = "A Client Error response code was returned by the server"
        if (code >= 500) {
            risk = 1    // Low
            title = "A Server Error response code was returned by the server"
        }
        var alert = new org.parosproxy.paros.core.scanner.Alert(pluginid, risk, 3, title)
        var ref = msg.getHistoryRef()
        if (ref != null && org.parosproxy.paros.model.HistoryReference.getTemporaryTypes().contains(
                    java.lang.Integer.valueOf(ref.getHistoryType()))) ref = null
        if (ref == null) {
            var type
            switch (initiator) {
                case 1: type = 1; break   // PROXY
                case 2: type = 3; break   // ACTIVE_SCANNER
                case 3: type = 2; break   // SPIDER
                case 4: type = 8; break   // FUZZER
                case 5: type = 15; break  // AUTH
                case 6: type = 15; break  // MANUAL
                case 8: type = 15; break  // BEAN_SHELL
                case 9: type = 13; break  // ACCESS_CONTROL
                default: type = 15; break
            }
            ref = new org.parosproxy.paros.model.HistoryReference(model.getSession(), type, msg)
        }
        alert.setMessage(msg)
        alert.setUri(msg.getRequestHeader().getURI().toString())
        alert.setDescription("A response code of " + code + " was returned by the server.\n" +
            "This may indicate that the application is failing to handle unexpected input correctly.\n" +
            "Raised by the 'Alert on HTTP Response Code Error' script");
        var regex = new RegExp("^HTTP.*" + code)
        alert.setEvidence(msg.getResponseHeader().toString().match(regex))
        alert.setCweId(388)
        alert.setWascId(20)
        extensionAlert.alertFound(alert , ref)
    }
}

metadata.yaml:

Name: Alert On Http Response Code Errors
Type: httpsender
Engine: Oracle Nashorn
Description: A HTTP Sender Script which will raise alerts based on HTTP Response code
Charset: UTF-8

Example 2 — Bash custom attack

run-hello.sh sources the bundled prancer-lib.sh to emit an alert. See the custom Bash script reference for available pr_* helpers.

#!/bin/bash
source shlib/prancer-lib.sh

# generate a hello alert, pass the target like: "https://brokencrystals.com/"
output=`pr_hello_world_alert "$1"`

if [ $? -ne 0 ]; then
    echo "Failed to generate alert!...."
else
    echo "$output"
fi
exit 0

metadata.yaml:

Name: BashHello
Technology: standalone
Type: active
Engine: bash
Description: Run bash script to generate alert.
Charset: UTF-8
tags:
  Type: Blackbox, Web

Step 1 — Store the Git token in Vault

1. Open Vault in the portal. 2. Add a new key. The Key Name must match the httpsAccessToken field of the connector below; Key Value is the Git PAT.

!Vault upload

Warning: Never commit the access token to the repository or paste it into the connector file. Always reference it by the Vault key name.

Step 2 — Create the Git connector

Create azure_custom_script_connector.json:

{
    "branchName": "main",
    "companyName": "prancer",
    "fileType": "structure",
    "gitProvider": "https://github.com/prancer-io/prancer-pac-sample.git",
    "httpsAccessToken": "secret-git-key",
    "private": true,
    "type": "filesystem"
}

| Field | Description |

| --- | --- |

| gitProvider | Clone URL of the repo. |

| branchName | Branch that holds the attack scripts. |

| httpsAccessToken | Vault key name (not the token itself). |

| private | true if the repo requires authentication. |

| companyName | Tenant name used to namespace the connector. |

Upload the connector file to the collection:

!Upload connector

Step 3 — Reference the attack in the PAC config

1. Open Inventory Management → your application → PAC Configuration. 2. Add a CVE block:

!PAC configuration

```yaml CVE:

Include:

Exclude:

Connector: azure_custom_script_connector ```

  • Path:
  • .*\.js
  • abc.java
  • BashHello
  • .*\.py

| Field | Description |

| --- | --- |

| Connector | Name of the Git connector to clone from. |

| Path.Include | Regex list of attack paths to include. Match folder names (e.g. BashHello) or file patterns (e.g. .*\.js). |

| Path.Exclude | Regex list of paths to skip. |

Tip: Use folder-level entries (BashHello) for Bash attacks and file globs (.*\.js) for ZAP scripts. Combine Include and Exclude to slice the same repo per environment.

Complete PAC example

Collection: azure_remote_test
ConnectionName: azure_remote_test_connector
CloudType: azure
ApplicatioName: Azure Remote Application
RiskLevel: safe
Compliance:
- CSA-CCM
- HIPAA
- ISO 27001
- SOC 2
- HITRUST
- NIST 800
ApplicationType: APIScan
Schedule: onetime
Target: http://prancersampleapp01.eastus2.cloudapp.azure.com:8888
APIScan:
  Type: OpenAPI
  DirectionProvider: git
  Direction: http://prancersampleapp01.eastus2.cloudapp.azure.com:8888/v2/swagger.json
  PostmanRemoteFile: remote_postman/postman_collection.json
  PostmanEnvRemoteFile: remote_postman/postman_environment.json
  Connector: azure_postman_connector
CVE:
- Path:
    Include:
    - .*\.js
    - abc.java
    - BashHello
    Exclude:
    - .*\.py
  Connector: azure_custom_script_connector
Scanner:
  Cloud:
    Platform:
      Azure:
        ContainerInstance:
          AfterRun: delete
          NewContainerInstance:
            External:
              SubscriptionId: 12345678-1234-5678-abcd-1234abcd4567
              ResourceGp: testgroup
              Region: eastus2
              ContainerGroupName: prancer-scanner-group
              ContainerName: prancer-pentest-instance
              ResourceName: prancer-instances-1
AuthenticationMethod: noAuthentication
AddOns:
- accessControl
- ascanrulesBeta
- sqliplugin
- directorylistv2_3
- portscan
- pscanrulesBeta
- websocket
- fuzzdb
- fuzzdboffensive
- fuzz
- graphql
- openapi

Step 4 — Run the pentest

Click Start on the PAC file in Inventory Management.

!Run pentest

Use See Latest Results to jump to findings as they come in:

!See results

Findings from custom attacks appear alongside built-in checks on the Application Security Findings page:

!Custom CVE result !Bash attack result

Note: When multiple runs share a PAC file, filter the findings by run date to isolate the latest results.

Run the pentest from the CLI

Custom attacks run identically when the pentest is launched from the PAC CLI — the only requirement is that the same PAC config and Git connector are available.

Next steps