Active Scanning Scripts
Active Script Type Details
Usage Overview
Active scripting in HawkScan enables you to create custom vulnerability detection rules that actively test your application’s endpoints. Unlike passive scanning which analyzes existing traffic, active scripts generate and send crafted requests to probe for security vulnerabilities, business logic flaws, or application-specific security issues not covered by HawkScan’s built-in rules.
Active scripts give you the power to:
- Create custom parameter fuzzing logic with dynamic payload generation
- Test for business-specific vulnerabilities (e.g., multi-tenancy violations, privilege escalation)
- Implement specialized injection testing patterns tailored to your application
- Detect application-specific error conditions that indicate security weaknesses
- Build custom security checks for unique application behaviors
Key Capabilities:
- Send multiple crafted requests per endpoint parameter
- Modify request parameters, headers, and bodies programmatically
- Analyze responses for vulnerability indicators
- Raise alerts with custom risk levels, descriptions, and remediation guidance
- Integrate with HawkScan’s scanning workflow and alerting system
When to Use Active Scripts vs Built-in Rules
HawkScan includes comprehensive built-in active scanning rules covering OWASP Top 10 vulnerabilities like SQL injection, XSS, command injection, and more. Active scripts become valuable when you need to:
Use Active Scripts When:
- Testing for business logic vulnerabilities specific to your application
- Implementing custom fuzzing strategies for unique parameter handling
- Checking multi-tenancy or authorization boundary violations
- Testing application-specific error handling behaviors
- Validating custom security controls or data validation logic
- Creating domain-specific vulnerability checks (e.g., financial calculations, access control)
- Generating test data dynamically based on application context
Use Built-in Active Rules When:
- Testing for common injection vulnerabilities (SQL, XSS, XXE, command injection)
- Checking for security misconfigurations (CORS, CSP, HSTS)
- Scanning for path traversal, SSRF, or deserialization vulnerabilities
- Detecting common framework-specific vulnerabilities
- Standard OWASP Top 10 coverage is sufficient
Usage Scenarios
1. Parameter Fuzzing with Dynamic Payload Generation
One of the most common uses for active scripts is intelligent parameter fuzzing—sending a variety of crafted values to parameters to detect unhandled input scenarios that cause server errors or unexpected behavior.
Example: Testing how an application handles unexpectedly long strings, special characters, or type mismatches:
- Generate random strings of varying lengths (1-100 characters)
- Test with special data patterns (Unicode, control characters, SQL fragments)
- Monitor for 5xx errors, excessive response times, or error messages in responses
Use Case: An API endpoint accepts a name
parameter. Your fuzzer sends 10 different values including very long strings and special characters. If the server returns a 500 error with a stack trace, you’ve found a vulnerability.
Reference: fuzzer.kts example
2. Multi-Tenancy and Authorization Boundary Testing
Business logic vulnerabilities often involve authorization checks. Active scripts can test whether users can access data belonging to other tenants or users.
Example: Testing if changing a user ID in a request allows access to another user’s data:
- Original request:
GET /api/users/user123
- Modified request:
GET /api/users/user456
- Check if response contains data from user456 when authenticated as user123
Use Case: A multi-tenant SaaS application should enforce tenant boundaries. Your script modifies user/tenant IDs in requests and checks if unauthorized data is returned.
Reference: tenancy-check.kts example
3. Custom Injection Pattern Testing
While HawkScan includes SQL injection detection, you may need to test for application-specific injection vulnerabilities or use custom payloads tuned to your technology stack.
Example: Testing for NoSQL injection in MongoDB-backed APIs:
- Inject MongoDB query operators:
{"$gt": ""}
,{"$ne": null}
- Test JSON payload manipulation:
{"username": {"$regex": ".*"}}
- Monitor for authentication bypass or data disclosure
Use Case: Your API uses MongoDB and accepts JSON query parameters. Standard SQL injection tests won’t find NoSQL-specific issues. Your custom active script tests MongoDB operator injection.
Simple Script/Template Example and Description
Consider the following basic active scanning template, active-template.kts
:
import org.apache.log4j.LogManager
import org.parosproxy.paros.network.HttpMessage
import com.stackhawk.hste.extension.scripts.scanrules.ScriptsActiveScanner
val logger = LogManager.getLogger("active-template")
// Entry point for node-level scanning (tests entire endpoints)
fun scanNode(activeScanner: ScriptsActiveScanner, origMessage: HttpMessage) {
logger.debug("scanNode: ${origMessage.requestHeader.uri}")
// Example: Test the entire endpoint without parameter modification
// val msg = origMessage.cloneRequest()
// activeScanner.sendAndReceive(msg, false, false)
// Analyze msg.responseHeader and msg.responseBody
}
// Entry point for parameter-level scanning (tests individual parameters)
fun scan(activeScanner: ScriptsActiveScanner, origMessage: HttpMessage, param: String, value: String) {
logger.debug("scan: ${origMessage.requestHeader.uri} | param=$param, value=$value")
// Example: Clone the original request
// val msg = origMessage.cloneRequest()
// Example: Modify the parameter value
// activeScanner.setParam(msg, param, "malicious_value")
// Example: Send the modified request
// activeScanner.sendAndReceive(msg, false, false)
// Example: Check response for vulnerability indicators
// if (msg.responseHeader.statusCode == 500) {
// raiseAlert(activeScanner, msg, "Server error detected", param, "malicious_value")
// }
}
// Helper function to raise alerts (optional but recommended)
fun raiseAlert(activeScanner: ScriptsActiveScanner, msg: HttpMessage, evidence: String, param: String, attackValue: String) {
val risk = 2 // 0: info, 1: low, 2: medium, 3: high
val confidence = 3 // 0: falsePositive, 1: low, 2: medium, 3: high, 4: confirmed
val title = "Custom Vulnerability Detected"
val description = "Detailed description of the vulnerability"
val solution = "How to fix this vulnerability"
val reference = "https://example.com/vuln-reference"
val otherInfo = "Additional context: param=$param, value=$attackValue"
val pluginId = 1000000 // Custom Plugin ID (use unique IDs for each script)
activeScanner.newAlert()
.setPluginId(pluginId)
.setRisk(risk)
.setConfidence(confidence)
.setName(title)
.setDescription(description)
.setEvidence(evidence)
.setOtherInfo(otherInfo)
.setSolution(solution)
.setReference(reference)
.setMessage(msg)
.raise()
}
Required Functions and Signatures
Active scripts must implement two entry point functions. HawkScan calls these functions during the scanning process:
1. scanNode
- Endpoint-Level Testing
This function is called once per discovered endpoint/node. Use it to test the endpoint as a whole without modifying individual parameters.
fun scanNode(activeScanner: ScriptsActiveScanner, origMessage: HttpMessage)
Parameters:
activeScanner
- Helper object providing methods to send requests and raise alertsorigMessage
- Original HTTP request to the endpoint (should not be modified directly)
Common Use Cases:
- Testing endpoints that don’t have parameters
- Checking for authentication/authorization issues at the endpoint level
- Testing HTTP methods (e.g., trying DELETE on a GET endpoint)
- Verifying endpoint-level security controls
Important: Always clone the original message before sending:
val msg = origMessage.cloneRequest()
2. scan
- Parameter-Level Testing
This function is called for each parameter in each request. Use it to test how the application handles modified parameter values.
fun scan(activeScanner: ScriptsActiveScanner, origMessage: HttpMessage, param: String, value: String)
Parameters:
activeScanner
- Helper object providing methods to send requests and raise alertsorigMessage
- Original HTTP request (should not be modified directly)param
- Name of the parameter being tested (e.g., “userId”, “search”)value
- Original value of the parameter
Common Use Cases:
- Parameter fuzzing with custom payloads
- Testing injection vulnerabilities
- Boundary value testing
- Type confusion testing
- Business logic testing via parameter manipulation
The ScriptsActiveScanner Helper Object
The activeScanner
parameter provides essential methods for active scanning:
Key Methods:
setParam(HttpMessage msg, String param, String value)
- Modifies a parameter value in the request
- Handles all parameter types: URL query params, POST body params, JSON fields
- Example:
activeScanner.setParam(msg, "userId", "admin")
sendAndReceive(HttpMessage msg, boolean followRedirects, boolean handleAntiCSRF)
- Sends the HTTP request and blocks waiting for the response
followRedirects
- Whether to automatically follow 3xx redirects (usuallyfalse
for testing)handleAntiCSRF
- Whether to automatically handle CSRF tokens (usuallyfalse
for custom testing)- Example:
activeScanner.sendAndReceive(msg, false, false)
newAlert()
- Returns an alert builder for creating vulnerability findings
- Must set: pluginId, risk, confidence, name, message
- Should set: description, evidence, solution, reference
- Call
.raise()
to submit the alert
getBaseMsg()
- Gets the base message for the current scan
- Useful for accessing original request context
Advanced Example: Parameter Fuzzer with Error Detection
This example demonstrates a complete fuzzing script that generates dynamic payloads and detects server errors:
import com.github.javafaker.Faker
import com.stackhawk.hste.extension.script.ScriptVars
import com.stackhawk.hste.extension.scripts.scanrules.ScriptsActiveScanner
import org.apache.log4j.LogManager
import org.parosproxy.paros.network.HttpMessage
val logger = LogManager.getLogger("fuzzer")
val faker = Faker()
val scriptVars = ScriptVars.getScriptVars("fuzzer.kts")
fun scanNode(activeScanner: ScriptsActiveScanner, origMessage: HttpMessage) {
// This fuzzer operates at the parameter level, so scanNode does nothing
logger.debug("scanNode: ${origMessage.requestHeader.uri}")
return
}
fun scan(activeScanner: ScriptsActiveScanner, origMessage: HttpMessage, param: String, value: String) {
logger.debug("Fuzzing parameter: $param (original value: $value)")
// Get configuration from script variables
val iterations = scriptVars["iterations"]?.toInt() ?: 10
val stringStartLength = scriptVars["stringStartLength"]?.toInt() ?: 1
val stringEndLength = scriptVars["stringEndLength"]?.toInt() ?: 100
// Perform multiple fuzzing attempts
(1..iterations).forEach { i ->
// Clone the original request for each iteration
val msg = origMessage.cloneRequest()
// Generate dynamic fuzzing payload
val fuzzedParamValue = if (i % 2 == 0) {
// Even iterations: Random string of variable length
faker.lorem().characters(stringStartLength, stringEndLength)
} else {
// Odd iterations: Interesting test data (e.g., Harry Potter spells)
faker.harryPotter().spell()
}
// Set the fuzzing payload as the parameter value
if (param.isNotBlank()) {
activeScanner.setParam(msg, param, fuzzedParamValue)
}
try {
// Send the fuzzed request
activeScanner.sendAndReceive(msg, false, false)
// Check for server errors (5xx status codes)
if (msg.responseHeader.statusCode >= 500) {
logger.info("Found 5xx error with payload: $fuzzedParamValue")
logger.debug("Request: ${msg.requestHeader}${msg.requestBody}")
// Raise an alert for the detected vulnerability
raiseAlert(
activeScanner,
msg,
msg.responseHeader.primeHeader,
param,
fuzzedParamValue
)
logger.debug("Response: ${msg.responseHeader.statusCode} ${msg.responseBody}")
}
} catch (e: Exception) {
logger.error("Error sending fuzzed request: ${e.message}")
}
}
}
fun raiseAlert(activeScanner: ScriptsActiveScanner, msg: HttpMessage, evidence: String, param: String, fuzzedParam: String) {
val risk = 2 // Medium risk
val confidence = 3 // High confidence
val title = "Server Error Triggered by Fuzzing"
val description = "The fuzzer detected a 5xx server error when testing parameter '$param' " +
"with value '$fuzzedParam'. This indicates the application does not properly " +
"handle unexpected input, which could lead to denial of service or information disclosure."
val solution = "Implement proper input validation and error handling. Never expose stack traces " +
"or detailed error messages to users. Return generic error messages and log detailed " +
"errors server-side only."
val reference = "https://owasp.org/www-project-top-ten/2017/A6_2017-Security_Misconfiguration"
val otherInfo = "Fuzzed parameter: $param=$fuzzedParam"
val pluginId = 1000000
activeScanner.newAlert()
.setPluginId(pluginId)
.setRisk(risk)
.setConfidence(confidence)
.setName(title)
.setDescription(description)
.setEvidence(evidence)
.setOtherInfo(otherInfo)
.setSolution(solution)
.setReference(reference)
.setMessage(msg)
.raise()
}
Script Logic Detailed Breakdown
Configuration via Script Variables
val scriptVars = ScriptVars.getScriptVars("fuzzer.kts")
val iterations = scriptVars["iterations"]?.toInt() ?: 10
Script variables come from the hawkAddOn.scripts.vars
section in stackhawk.yml
. This allows you to configure script behavior without modifying code.
Request Cloning
val msg = origMessage.cloneRequest()
Critical: Never modify origMessage
directly. Always clone it first. HawkScan reuses the original message for multiple tests.
Dynamic Payload Generation
val fuzzedParamValue = if (i % 2 == 0) {
faker.lorem().characters(stringStartLength, stringEndLength)
} else {
faker.harryPotter().spell()
}
Using JavaFaker library (included in SDK) to generate realistic but random test data. This is more effective than static payloads.
Parameter Modification
activeScanner.setParam(msg, param, fuzzedParamValue)
The setParam
method intelligently handles different parameter types:
- URL query parameters:
?userId=123
- POST form data:
userId=123
- JSON body fields:
{"userId": "123"}
- XML elements (with appropriate parsing)
Response Analysis
if (msg.responseHeader.statusCode >= 500) {
raiseAlert(...)
}
Check for vulnerability indicators in responses:
- Status codes (500, 403, 401, etc.)
- Error messages or stack traces in response body
- Unexpected data disclosure
- Changed application behavior
Alert Builder Pattern
activeScanner.newAlert()
.setPluginId(pluginId)
.setRisk(risk)
.setConfidence(confidence)
// ... more fields
.raise()
The builder pattern allows flexible alert construction. Required fields will cause exceptions if missing.
Advanced Example: Multi-Tenancy Violation Detection
This example tests for authorization boundary violations:
import org.apache.log4j.Logger
import org.parosproxy.paros.network.HttpMessage
import com.stackhawk.hste.extension.scripts.scanrules.ScriptsActiveScanner
val logger = Logger.getLogger("tenancy-check")
fun scanNode(activeScanner: ScriptsActiveScanner, origMessage: HttpMessage) {
// Not used for this check
return
}
fun scan(activeScanner: ScriptsActiveScanner, origMessage: HttpMessage, param: String, value: String) {
val uri = origMessage.requestHeader.uri
logger.debug("Testing tenancy boundary for: $uri, param=$param")
// Clone the request
val msg = origMessage.cloneRequest()
// Modify the parameter to reference a different user/tenant
// In a real scenario, you'd have a list of test user IDs to try
activeScanner.setParam(msg, param, "differentUserId")
// Ensure JSON content type if needed
msg.requestHeader.setHeader("Content-Type", "application/json")
// Send the modified request
activeScanner.sendAndReceive(msg, false, false)
// Check if the response contains data that shouldn't be accessible
val responseBody = msg.responseBody.toString()
val forbiddenDataPattern = "12345678" // Example: looking for another user's ID
if (msg.responseHeader.statusCode == 200 && responseBody.contains(forbiddenDataPattern)) {
logger.warn("Tenancy violation detected!")
val evidenceIdx = responseBody.indexOf(forbiddenDataPattern)
val evidence = responseBody.substring(evidenceIdx, evidenceIdx + forbiddenDataPattern.length)
raiseAlert(activeScanner, msg, evidence, param)
}
}
fun raiseAlert(activeScanner: ScriptsActiveScanner, msg: HttpMessage, evidence: String, param: String) {
val risk = 3 // High risk
val confidence = 3 // High confidence
val title = "Multi-Tenancy Boundary Violation"
val description = "The authenticated user was able to access data belonging to another user " +
"by modifying the '$param' parameter. This indicates insufficient authorization " +
"checks and could allow lateral movement between user accounts."
val solution = "Implement proper authorization checks on all data access operations. Verify that " +
"the authenticated user has permission to access the requested resource. Use " +
"centralized authorization logic and avoid client-side access control."
val reference = "https://owasp.org/www-project-top-ten/2017/A5_2017-Broken_Access_Control"
val otherInfo = "Evidence of unauthorized data access found in response"
val pluginId = 1000001
activeScanner.newAlert()
.setPluginId(pluginId)
.setRisk(risk)
.setConfidence(confidence)
.setName(title)
.setDescription(description)
.setEvidence(evidence)
.setOtherInfo(otherInfo)
.setSolution(solution)
.setReference(reference)
.setMessage(msg)
.raise()
}
HawkScan Configuration
Active scripts require plugin registration and configuration in the hawkAddOn
section of stackhawk.yml
:
Step 1: Register the Plugin
Before using an active script, you must register it to obtain a Plugin ID:
hawk register plugin fuzzer.kts
Response:
New Script Id: 1000014
- Add this ID to your StackHawk YAML under hawkAddOn: .scripts.-id
- Add this ID to your Plugin Script under pluginId
Step 2: Add Plugin ID to Script Code
In your script’s alert raising code, add the Plugin ID:
activeScanner.newAlert()
.setPluginId(1000014) // REQUIRED - ID from registration
.setRisk(2)
.setName("Custom Vulnerability")
.setMessage(msg)
.raise()
Step 3: Configure in stackhawk.yml
hawkAddOn:
scripts:
- name: "fuzzer.kts"
id: 1000014 # REQUIRED - Plugin ID from registration
type: active
path: "hawkscripts" # Parent directory only (will look in hawkscripts/active/)
language: KOTLIN
vars: # Accessed via ScriptVars.getScriptVars() - array structure
- name: iterations
val: "10"
- name: stringStartLength
val: "1"
- name: stringEndLength
val: "100"
Configuration Parameters:
name
: Script filename - must match registration nameid
: REQUIRED - Plugin ID obtained fromhawk register plugin
type
: Must beactive
path
: Parent directory only - type subdirectory added automatically (e.g., “hawkscripts” not “hawkscripts/active”)language
:KOTLIN
vars
: Optional variables accessed viaScriptVars.getScriptVars()
Important Notes:
- Limit: 50 custom scripts per organization (shared with passive scripts)
- The Plugin ID must be added to BOTH the config file and script code
- Registration name should match the script filename
Alert Risk and Confidence Levels
Understanding how to set appropriate risk and confidence levels:
Risk Levels (0-3):
- 0 (Informational): No direct security impact, but noteworthy
- 1 (Low): Minor security issue, difficult to exploit
- 2 (Medium): Moderate security issue, requires specific conditions
- 3 (High): Critical security vulnerability, easily exploitable
Confidence Levels (0-4):
- 0 (False Positive): Alert is likely incorrect
- 1 (Low): Suspicious behavior, needs verification
- 2 (Medium): Likely vulnerability, manual review recommended
- 3 (High): Strong evidence of vulnerability
- 4 (Confirmed): Definitively verified vulnerability
Important: Both risk and confidence must be > 0, or the alert will not be included in scan results.
Common Patterns and Best Practices
Pattern 1: Iterative Testing with Variable Payloads
val payloads = listOf(
"'OR'1'='1",
"<script>alert(1)</script>",
"../../etc/passwd",
"\${7*7}",
"A".repeat(10000)
)
payloads.forEach { payload ->
val msg = origMessage.cloneRequest()
activeScanner.setParam(msg, param, payload)
activeScanner.sendAndReceive(msg, false, false)
if (detectVulnerability(msg, payload)) {
raiseAlert(activeScanner, msg, payload)
}
}
Pattern 2: Response Time Analysis (Blind Vulnerabilities)
val startTime = System.currentTimeMillis()
activeScanner.sendAndReceive(msg, false, false)
val endTime = System.currentTimeMillis()
val responseTime = endTime - startTime
if (responseTime > 5000) {
// Possible time-based blind SQL injection or DoS
raiseAlert(activeScanner, msg, "Response time: ${responseTime}ms")
}
Pattern 3: Conditional Alert Raising
// Only raise alerts for specific conditions
if (msg.responseHeader.statusCode >= 500 &&
msg.responseBody.toString().contains("SQLException")) {
// Higher confidence - we have both status code and error message
raiseAlert(activeScanner, msg, evidence, risk = 3, confidence = 4)
} else if (msg.responseHeader.statusCode >= 500) {
// Lower confidence - only status code
raiseAlert(activeScanner, msg, evidence, risk = 2, confidence = 2)
}
Pattern 4: Using Helper Functions for Reusability
fun detectSQLInjection(msg: HttpMessage): Boolean {
val body = msg.responseBody.toString()
val indicators = listOf("SQL syntax", "ORA-", "SQLException", "mysql_fetch")
return indicators.any { body.contains(it, ignoreCase = true) }
}
fun detectXSS(msg: HttpMessage, payload: String): Boolean {
return msg.responseBody.toString().contains(payload)
}
// Use in scan function
if (detectSQLInjection(msg)) {
raiseAlert(activeScanner, msg, "SQL error detected")
}
Troubleshooting
Issue: “Script not being executed during scans”
Symptoms: Your active script’s log messages don’t appear, no alerts are raised.
Solutions:
- Verify
type: active
is set in the hawkAddOn configuration - Check that the script path is correct relative to project root
- Look for compilation errors at HawkScan startup in
hawkscan.log
- Ensure both
scan
andscanNode
functions are defined - Verify script filename matches the
name
in configuration
Issue: “Alerts not appearing in scan results”
Symptoms: Script executes, logs show alert was raised, but no findings in output.
Solutions:
- Verify both
risk
andconfidence
are greater than 0 - Ensure
.setMessage(msg)
is called on the alert builder - Check that
.raise()
is called at the end of the alert builder chain - Verify pluginId is unique and not conflicting with built-in rules
- Check alert threshold settings in
stackhawk.yml
Issue: “Too many false positives”
Symptoms: Script raises alerts for every request, many are incorrect.
Solutions:
- Add more specific vulnerability detection logic
- Increase the confidence level requirements
- Filter out expected responses (e.g., legitimate 404s)
- Use multiple indicators before raising an alert (status code + error message)
- Test against known-good requests first to calibrate detection
Issue: “Script causes scanner to hang or timeout”
Symptoms: Scans take extremely long or time out when script is enabled.
Solutions:
- Reduce the number of iterations per parameter
- Add timeout handling for long-running operations
- Use
try-catch
blocks around all network operations - Avoid infinite loops or recursive calls
- Limit the scope with
scanNode
vsscan
appropriately
Issue: “Parameters not being modified correctly”
Symptoms: setParam
doesn’t seem to change the parameter value.
Solutions:
- Verify you’re calling
setParam
on a cloned message, not the original - Check that the parameter name matches exactly (case-sensitive)
- For JSON bodies, ensure content-type is set correctly
- Log the request body after
setParam
to verify changes - Some parameters may be in headers rather than body/query
Testing Active Scripts
Development Workflow
- Start with a minimal script:
fun scan(activeScanner: ScriptsActiveScanner, origMessage: HttpMessage, param: String, value: String) { logger.info("Testing param: $param") }
- Test against a single endpoint:
app: includePaths: - "/api/test"
- Add payload generation and logging:
val msg = origMessage.cloneRequest() activeScanner.setParam(msg, param, "test_value") logger.info("Modified request: ${msg.requestHeader}${msg.requestBody}")
- Send requests and analyze responses:
activeScanner.sendAndReceive(msg, false, false) logger.info("Response status: ${msg.responseHeader.statusCode}") logger.info("Response body: ${msg.responseBody}")
- Add vulnerability detection logic:
if (msg.responseHeader.statusCode >= 500) { logger.warn("Found vulnerability!") raiseAlert(activeScanner, msg, evidence) }
Testing Commands
# Run a limited scan for testing
hawk scan --config stackhawk.yml --spider-max 5
# Review logs for your script
tail -f hawkscan.log | grep "fuzzer"
# Test against a specific path
hawk scan --config stackhawk.yml --include-paths "/api/test"
Key Objects Reference
For detailed information on classes and methods used in active scripts, see Key Objects Documentation:
- ScriptsActiveScanner - Helper object for active scanning operations
- HttpMessage - HTTP request/response encapsulation
- HttpRequestHeader / HttpResponseHeader - Header manipulation
- ScriptVars - Access to script configuration variables
Additional Resources
- GitHub Examples Repository
- SDK setup and IntelliJ integration description coming soon
- Passive Scanning Scripts - For non-invasive vulnerability detection