HTTP Sender Scripts
HttpSender Script Type Details
Usage Overview
The httpsender
script type enables you to hook into HTTP communication during two main stages - just before sending a request to the target app, and just after receiving a response. You can use this kind of script to touch, inspect, or manipulate every single HTTP message in the test cycle. The two required functions in this type of script are:
sendingRequest
: Manipulate a request just before it is sent to the server.responseReceived
: Manipulate a message (request or response) after it is received from the server, but before it undergoes final processing by HawkScan.
Usage Scenarios
- Custom Authentication: Modify requests to include specific headers or tokens for applications requiring dynamic, programmatically generated authentication for every request.
- Example: AWS SigV4 Signing (aws-sigv4.kts)
- Traffic Inspection: Inspect and analyze HTTP traffic to log sensitive data like cookies or tokens, aiding in security assessments or troubleshooting.
- Example: Enhanced logging for troubleshooting (troubleshooting.kts)
- Behavior Simulation/Modification: Simulate user behavior by dynamically modifying requests based on application responses, ensuring every message reflects real-world scenarios.
- Example: TBC
Simple Script Example and Description (GitHub Link)
Consider the following example httpsender script, sender1.kts
.
import org.apache.log4j.LogManager
import org.parosproxy.paros.network.HttpMessage //required
import com.stackhawk.hste.extension.script.HttpSenderScriptHelper //required
import com.stackhawk.hste.extension.script.ScriptVars
val logger = LogManager.getLogger("sender1")
// modify a request before it's sent to the web application
fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
logger.info("sender1 script $initiator")
}
// modify the response from the web application before sending to the client
fun responseReceived(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
val clientHdr = ScriptVars.getScriptVar("sender1.kts", "client_header")
msg.responseHeader.setHeader("X-Client-Header", clientHdr)
}
Function signature breakdown:
fun sendingRequest(
msg: HttpMessage,
initiator: Int,
helper: HttpSenderScriptHelper
): Unit {
msg
is an object representing the current, in-transit HTTP message from HawkScan. Key sub-objects and accessors are described here: Key Objects. Available through the import org.parosproxy.paros.network.HttpMessage class.
The msg
is passed into the sendingRequest
entry point function with a populated RequestHeaders and RequestBody, but an empty Response.
initiator
is an Integer, that represents a reference to the activity or process in HawkScan that initiated the message. These ints translate to specific initiating activity defined in the HttpSender
class from org.parosproxy.paros.network.HttpSender class. The most common initiators are:
public static final int PROXY_INITIATOR = 1;
public static final int ACTIVE_SCANNER_INITIATOR = 2;
public static final int SPIDER_INITIATOR = 3;
public static final int FUZZER_INITIATOR = 4;
public static final int AUTHENTICATION_INITIATOR = 5;
public static final int MANUAL_REQUEST_INITIATOR = 6;
helper
, coming from the HttpSenderScriptHelper class is an member-instantiation of the HttpSender class, providing access to member methods, and class functions which assist in building and modifying HTTP messages.
fun responseReceived
implements a similar signature with the same objects. However, the msg
object now has a fully populated Response headers and body as appropriate.
Script logic breakdown:
val logger = LogManager.getLogger("sender1")
The logger
variable is set up outside of any function, and is therefore available as a global variable for the script, which can be used in any of the default or user-defined functions.
fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
logger.info("sender1 script $initiator")
}
sendingRequest
simply logs a message capturing the integer initiator. Log-level is info
from logger.info
. By default these log entries are added to hawkscan.log
as standard output from the scanner.
fun responseReceived(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
val clientHdr = ScriptVars.getScriptVar("sender1.kts", "client_header")
msg.responseHeader.setHeader("X-Client-Header", clientHdr)
}
responseReceived
modifies the message response. First, it grabs a global variable value defined in the StackHawk config specific to this script:
val clientHdr = ScriptVars.getScriptVar(“sender1.kts”, “client_header”)
, where “sender1.kts is the name of the script and “client_header” is the name of the variable.
Second, it adds that value to a custom header on the response: msg.responseHeader.setHeader(“X-Client-Header”, “clientHdr”)
, where “X-Client-Header” is the name/key of the custom header, and the variable value obtained from the config “clientHdr”, is the value of the header.
HawkScan Configuration
To use this script in HawkScan, you need to configure it in the hawkAddOn section of your configuration file as shown:
Note: HTTP sender scripts use single configuration (hawkAddOn only, no dual config)
hawkAddOn:
scripts:
- name: sender1.kts
type: httpsender
path: scripts # Parent directory only (will look in scripts/httpsender/)
language: KOTLIN
vars: # Accessed via ScriptVars.getScriptVars() - array structure
- name: client_header
val: ${MYCUSTOMHEADERVAL} # Env variable interpolation supported
Best Practice: Use only ONE httpsender script - multiple scripts have undefined execution order
Advanced Example: AWS SigV4 Request Signing
This comprehensive example demonstrates intercepting requests to add AWS Signature Version 4 authentication:
import org.apache.log4j.LogManager
import org.parosproxy.paros.network.HttpMessage
import com.stackhawk.hste.extension.script.HttpSenderScriptHelper
import com.stackhawk.hste.extension.script.ScriptVars
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.*
val logger = LogManager.getLogger("aws-sigv4")
fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
// Only sign requests to AWS endpoints
val host = msg.requestHeader.getHeader("Host") ?: return
if (!host.contains("amazonaws.com")) {
return
}
// Get AWS credentials from script variables
val accessKey = ScriptVars.getScriptVar("aws-sigv4.kts", "aws_access_key_id") ?: return
val secretKey = ScriptVars.getScriptVar("aws-sigv4.kts", "aws_secret_access_key") ?: return
val region = ScriptVars.getScriptVar("aws-sigv4.kts", "aws_region") ?: "us-east-1"
val service = ScriptVars.getScriptVar("aws-sigv4.kts", "aws_service") ?: "execute-api"
logger.debug("Signing AWS request for $host")
// Generate timestamp
val dateFormat = SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'")
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
val timestamp = dateFormat.format(Date())
val datestamp = timestamp.substring(0, 8)
// Add required headers
msg.requestHeader.setHeader("X-Amz-Date", timestamp)
// Create canonical request
val method = msg.requestHeader.method
val uri = msg.requestHeader.uri.path ?: "/"
val query = msg.requestHeader.uri.query ?: ""
val headers = "host:$host\nx-amz-date:$timestamp\n"
val signedHeaders = "host;x-amz-date"
val payloadHash = sha256Hex(msg.requestBody.bytes)
val canonicalRequest = "$method\n$uri\n$query\n$headers\n$signedHeaders\n$payloadHash"
// Create string to sign
val algorithm = "AWS4-HMAC-SHA256"
val credentialScope = "$datestamp/$region/$service/aws4_request"
val stringToSign = "$algorithm\n$timestamp\n$credentialScope\n${sha256Hex(canonicalRequest.toByteArray())}"
// Calculate signature
val signatureKey = getSignatureKey(secretKey, datestamp, region, service)
val signature = hmacSHA256Hex(stringToSign.toByteArray(), signatureKey)
// Add authorization header
val authHeader = "$algorithm Credential=$accessKey/$credentialScope, SignedHeaders=$signedHeaders, Signature=$signature"
msg.requestHeader.setHeader("Authorization", authHeader)
logger.debug("AWS request signed successfully")
}
fun responseReceived(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
// No modification needed for responses
}
// Helper functions for AWS signing
fun sha256Hex(data: ByteArray): String {
val digest = MessageDigest.getInstance("SHA-256")
return digest.digest(data).joinToString("") { "%02x".format(it) }
}
fun hmacSHA256(data: ByteArray, key: ByteArray): ByteArray {
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(key, "HmacSHA256"))
return mac.doFinal(data)
}
fun hmacSHA256Hex(data: ByteArray, key: ByteArray): String {
return hmacSHA256(data, key).joinToString("") { "%02x".format(it) }
}
fun getSignatureKey(key: String, dateStamp: String, regionName: String, serviceName: String): ByteArray {
val kDate = hmacSHA256(dateStamp.toByteArray(), ("AWS4" + key).toByteArray())
val kRegion = hmacSHA256(regionName.toByteArray(), kDate)
val kService = hmacSHA256(serviceName.toByteArray(), kRegion)
return hmacSHA256("aws4_request".toByteArray(), kService)
}
Configuration for AWS SigV4:
hawkAddOn:
scripts:
- name: aws-sigv4.kts
type: httpsender
path: "hawkscripts" # Parent directory only
language: KOTLIN
vars:
- name: aws_access_key_id
val: "${AWS_ACCESS_KEY_ID}"
- name: aws_secret_access_key
val: "${AWS_SECRET_ACCESS_KEY}"
- name: aws_region
val: "us-east-1"
- name: aws_service
val: "execute-api"
Reference: aws-sigv4.kts example
Advanced Example: Request/Response Logging for Debugging
This example logs complete HTTP traffic for debugging authentication or session issues:
import org.apache.log4j.LogManager
import org.parosproxy.paros.network.HttpMessage
import com.stackhawk.hste.extension.script.HttpSenderScriptHelper
import com.stackhawk.hste.extension.script.ScriptVars
val logger = LogManager.getLogger("http-traffic-logger")
fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
// Get configuration
val logRequests = ScriptVars.getScriptVar("troubleshooting.kts", "log_requests")?.toBoolean() ?: true
val logHeaders = ScriptVars.getScriptVar("troubleshooting.kts", "log_headers")?.toBoolean() ?: true
val logBodies = ScriptVars.getScriptVar("troubleshooting.kts", "log_bodies")?.toBoolean() ?: false
if (!logRequests) return
logger.info("=== Outgoing Request (Initiator: ${getInitiatorName(initiator)}) ===")
logger.info("URI: ${msg.requestHeader.uri}")
if (logHeaders) {
logger.info("Request Headers:\n${msg.requestHeader}")
}
if (logBodies && msg.requestBody.length() > 0) {
val body = msg.requestBody.toString()
logger.info("Request Body (${body.length} bytes):\n$body")
}
}
fun responseReceived(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
// Get configuration
val logResponses = ScriptVars.getScriptVar("troubleshooting.kts", "log_responses")?.toBoolean() ?: true
val logHeaders = ScriptVars.getScriptVar("troubleshooting.kts", "log_headers")?.toBoolean() ?: true
val logBodies = ScriptVars.getScriptVar("troubleshooting.kts", "log_bodies")?.toBoolean() ?: false
if (!logResponses) return
logger.info("=== Incoming Response ===")
logger.info("Status: ${msg.responseHeader.statusCode} ${msg.responseHeader.reasonPhrase}")
if (logHeaders) {
logger.info("Response Headers:\n${msg.responseHeader}")
}
if (logBodies && msg.responseBody.length() > 0) {
val body = msg.responseBody.toString()
val truncatedBody = if (body.length > 1000) {
body.substring(0, 1000) + "\n... (truncated)"
} else {
body
}
logger.info("Response Body (${body.length} bytes):\n$truncatedBody")
}
}
fun getInitiatorName(initiator: Int): String {
return when (initiator) {
1 -> "PROXY"
2 -> "ACTIVE_SCANNER"
3 -> "SPIDER"
4 -> "FUZZER"
5 -> "AUTHENTICATION"
6 -> "MANUAL_REQUEST"
else -> "UNKNOWN($initiator)"
}
}
Configuration for Traffic Logging:
hawkAddOn:
scripts:
- name: troubleshooting.kts
type: httpsender
path: "hawkscripts" # Parent directory only
language: KOTLIN
vars:
- name: log_requests
val: "true"
- name: log_responses
val: "true"
- name: log_headers
val: "true"
- name: log_bodies
val: "false" # Set to "true" for detailed debugging
Reference: troubleshooting.kts example
Common Patterns and Best Practices
Pattern 1: Filtering by Initiator
Only process requests from specific scan phases:
import org.parosproxy.paros.network.HttpSender
fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
// Only modify active scanner requests
if (initiator != HttpSender.ACTIVE_SCANNER_INITIATOR) {
return
}
// Add special header for active scan requests
msg.requestHeader.setHeader("X-Scan-Type", "active")
}
Pattern 2: Conditional Header Addition
fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
// Only add header to API endpoints
val uri = msg.requestHeader.uri.toString()
if (uri.contains("/api/")) {
msg.requestHeader.setHeader("X-API-Version", "v2")
}
}
Pattern 3: Using ScriptVars for Configuration
val apiKey = ScriptVars.getScriptVar("my-script.kts", "api_key")
val environment = ScriptVars.getScriptVar("my-script.kts", "environment") ?: "production"
// Use configuration values
if (apiKey != null) {
msg.requestHeader.setHeader("X-API-Key", apiKey)
}
if (environment == "staging") {
msg.requestHeader.setHeader("X-Environment", "staging")
}
Troubleshooting
Issue: “HttpSender script not being called”
Symptoms: Log messages don’t appear in hawkscan.log
.
Solutions:
- Verify
type: httpsender
in hawkAddOn configuration - Check script path is correct relative to project root
- Look for compilation errors at HawkScan startup
- Ensure both
sendingRequest
andresponseReceived
functions are defined - Verify script filename matches the
name
in configuration
Issue: “Headers not being added to requests”
Symptoms: Custom headers don’t appear in requests sent to the target.
Solutions:
- Verify you’re modifying
msg.requestHeader
insendingRequest
(notresponseReceived
) - Check that header names and values are valid (no special characters in names)
- Add logging to confirm function is being called:
logger.info("Adding header")
- Verify initiator filtering isn’t excluding your requests
- Check if headers are being overwritten by authentication or session scripts
Issue: “Script variables not accessible”
Symptoms: ScriptVars.getScriptVar()
returns null.
Solutions:
- Verify variable name spelling matches exactly between script and configuration
- Ensure variables are defined in
hawkAddOn.scripts.vars
section - Check that script name passed to
getScriptVar()
matches configuration - Use logging to debug:
logger.debug("Variable value: $myVar")
Issue: “Performance degradation with httpsender script”
Symptoms: Scans are significantly slower with script enabled.
Solutions:
- Avoid complex computations in
sendingRequest
(runs for every request) - Cache expensive operations outside function scope
- Use initiator filtering to reduce processing
- Minimize logging in high-traffic functions
- Profile script performance with timing logs
When to Use HttpSender Scripts vs Other Script Types
Use HttpSender Scripts When:
- Every request needs the same modification (e.g., custom headers, signing)
- You need to inspect all traffic for logging/debugging
- Dynamic behavior based on previous responses is required
- Custom authentication that can’t be handled by auth scripts
Use Authentication Scripts Instead When:
- Only the initial login process needs custom handling
- Token extraction from login response is needed
Use Session Scripts Instead When:
- Token injection is needed but only after authentication
- Session-specific state management is required
Use Replacer Rules Instead When:
- Simple string replacement in headers is sufficient
- Static header values that never change
- Performance is critical (replacer rules are faster than scripts)
Key Objects Reference
For detailed information on classes and methods used in httpsender scripts, see Key Objects Documentation:
- HttpMessage - HTTP request/response encapsulation
- HttpSenderScriptHelper - Helper methods for HTTP operations
- HttpRequestHeader / HttpResponseHeader - Header manipulation
- ScriptVars - Access to script configuration and global variables
Additional Resources
- GitHub Examples Repository
- SDK setup and IntelliJ integration description coming soon
- Authentication Script Documentation
- Session Script Documentation