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:

  1. sendingRequest: Manipulate a request just before it is sent to the server.
  2. responseReceived: Manipulate a message (request or response) after it is received from the server, but before it undergoes final processing by HawkScan.

Usage Scenarios

  1. Custom Authentication: Modify requests to include specific headers or tokens for applications requiring dynamic, programmatically generated authentication for every request.
  2. Traffic Inspection: Inspect and analyze HTTP traffic to log sensitive data like cookies or tokens, aiding in security assessments or troubleshooting.
  3. Behavior Simulation/Modification: Simulate user behavior by dynamically modifying requests based on application responses, ensuring every message reflects real-world scenarios.
    • Example: TBC

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:

  1. Verify type: httpsender in hawkAddOn configuration
  2. Check script path is correct relative to project root
  3. Look for compilation errors at HawkScan startup
  4. Ensure both sendingRequest and responseReceived functions are defined
  5. 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:

  1. Verify you’re modifying msg.requestHeader in sendingRequest (not responseReceived)
  2. Check that header names and values are valid (no special characters in names)
  3. Add logging to confirm function is being called: logger.info("Adding header")
  4. Verify initiator filtering isn’t excluding your requests
  5. Check if headers are being overwritten by authentication or session scripts

Issue: “Script variables not accessible”

Symptoms: ScriptVars.getScriptVar() returns null.

Solutions:

  1. Verify variable name spelling matches exactly between script and configuration
  2. Ensure variables are defined in hawkAddOn.scripts.vars section
  3. Check that script name passed to getScriptVar() matches configuration
  4. Use logging to debug: logger.debug("Variable value: $myVar")

Issue: “Performance degradation with httpsender script”

Symptoms: Scans are significantly slower with script enabled.

Solutions:

  1. Avoid complex computations in sendingRequest (runs for every request)
  2. Cache expensive operations outside function scope
  3. Use initiator filtering to reduce processing
  4. Minimize logging in high-traffic functions
  5. 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