Authentication Scripts
Authentication Script Type Details
Usage Overview
Authentication scripting allows you to customize the authentication process for web applications, enabling HawkScan to handle complex authentication mechanisms. This is particularly useful for applications that require non-standard authentication methods, such as custom headers, tokens, or dynamic authentication flows.
Out-of-the-Box Supported Authentication
HawkScan offers robust built-in support for several standard authentication mechanisms. These options allow you to configure authentication directly within your HawkScan configuration without the need for custom scripts. The currently supported authentication types are:
Form-Based Authentication (Username and Password)
Form-based authentication is one of the most common ways web applications validate users. It typically involves submitting a username and password through an HTML form, where the server validates the credentials and returns a session cookie or token to maintain the user’s session.
Configuration Overview:
- Define logged in/out indicators to inform how HawkScan checks if the session is active or expired (e.g., looking for a specific response code or string in the response).
- Specify form field names for username and password, and include the credentials in your configuration.
- Configure session persistence by defining whether HawkScan should store and transmit a cookie or token.
- Identify a test path to verify if authentication was successful.
For more details, visit: Form-Based Authentication Documentation.
3rd Party OAuth
HawkScan supports OAuth authentication with built-in configurations for popular providers like Auth0, Okta, Keycloak, and Firebase, and sufficiently flexible configuration to cover Entra, Cognito, and other common OAuth providers. The best grant types for automation are Client Credentials and Resource Owner Password flows, which can be fully defined in your HawkScan configuration.
Configuration Overview:
- Define the OAuth grant type (e.g., Client Credentials).
- Specify the token endpoint and credentials.
- Use the token in headers or query parameters to maintain the session.
For more details, visit: OAuth Configuration Guide.
HTTP Authentication (Basic or NTLM)
HTTP Authentication is a lightweight authentication method often used for securing APIs or intranet applications. HawkScan supports both Basic Authentication and NTLM.
Configuration Overview:
- For Basic Authentication: Provide the username and password inline, and HawkScan will include them in the Authorization header.
- For NTLM: Provide domain, username, and password for integrated Windows authentication.
More details are available here: HTTP Authentication Configuration.
Inject Multiple Cookies and Tokens
For applications that require the simultaneous use of multiple cookies or tokens to authenticate and maintain a session, HawkScan allows manual injection.
Configuration Overview:
- Define cookies or tokens explicitly in the auth section.
- Specify their scope (e.g., domain and path restrictions).
Learn more: Cookie and Token Injection.
External Command Authentication
External command authentication allows you to use an external script or program to handle authentication, giving you maximum flexibility for complex flows.
Configuration Overview:
- Provide the command to execute and any required parameters.
- HawkScan will capture and use the output (e.g., a token or cookie) for subsequent requests.
Learn more: External Command Auth Guide.
Usage Scenarios
While HawkScan supports robust inline authentication configurations, some advanced scenarios may benefit from scripting. External Command Authentication can handle many of these cases using a preferred language other than Kotlin. However, leveraging HawkScan’s official authentication scripts allows you to use Kotlin and the power of the HawkScripting SDK.
Scenarios Where Scripting May Be Preferred:
- Dynamic Token Generation: Generate authentication tokens dynamically based on server responses during the scan.
- 3rd-Party Auth Servers: Obtain tokens or cookies from authentication servers that do not support OAuth and are not the primary target of the test.
- Multi-Step Authentication: Handle complex flows requiring multiple exchanges to obtain or construct all necessary authentication details.
Simple Script Example and Description
Unlike most of the other script types provided by HawkScan, Authentication scripts do not inherit an existing message. Instead, authentication scripts define, create, and send/receive/process messages as part of an authentication cycle.
Consider the following example authentication model, authentication_template.kts.
import org.apache.commons.httpclient.URI
import org.apache.log4j.LogManager
import org.parosproxy.paros.network.HttpHeader
import org.parosproxy.paros.network.HttpMessage
import org.parosproxy.paros.network.HttpRequestHeader
import com.stackhawk.hste.authentication.AuthenticationHelper
import com.stackhawk.hste.authentication.GenericAuthenticationCredentials
val logger = LogManager.getLogger("authentication-template")
val PARAM_TARGET_URL = "targetUrl"
// This function is called before a scan is started and when the loggedOutIndicator is matched indicating re-authentication is needed.
fun authenticate(
helper: AuthenticationHelper,
paramsValues: Map<String, String>,
credentials: GenericAuthenticationCredentials
): HttpMessage {
logger.info("Kotlin auth template")
logger.info("TARGET_URL: ${paramsValues[PARAM_TARGET_URL]}")
val msg = helper.prepareMessage()
msg.requestHeader = HttpRequestHeader(
HttpRequestHeader.GET, URI(paramsValues[PARAM_TARGET_URL], true),
HttpHeader.HTTP11
)
logger.info("msg: $msg ${msg.requestHeader.headers.size}")
msg.requestHeader.headers.forEach { println(it) }
helper.sendAndReceive(msg)
return msg
}
// The required parameter names for your script, your script will throw an error if these are not supplied in the script.parameters configuration.
fun getRequiredParamsNames(): Array<String> {
return arrayOf(PARAM_TARGET_URL)
}
// The required credential parameters, your script will throw an error if these are not supplied in the script.credentials configuration.
fun getCredentialsParamsNames(): Array<String> {
return arrayOf()
}
fun getOptionalParamsNames(): Array<String> {
return arrayOf()
}
fun getLoggedOutIndicator() : String {
return "^$" //matches none
}
fun getLoggedInIndicator() : String {
return ".*" //matches all because we're expecting a JWT
}
Required Functions and Signatures:
authenticate
: This is the entry function for the script. The function is called when HSTE needs to authenticate a user on initial load, or when the re-authentication process is triggered by appropriately matching loggedIn/Out indicators or JWT expiration. It allows the coder to create one or more authentication requests, send them to the server, and process the responses.
fun authenticate(
helper: AuthenticationHelper,
paramsValues: Map<String, String>,
credentials: GenericAuthenticationCredentials
): HttpMessage {
When called, the function is passed three objects from HSTE.
helper
is a member of the AuthenticationHelper class, from com.stackhawk.hste.authentication.AuthenticationHelper, which instantiates an object providing access to helper functions. The most common helper functions accessed via this class are:
prepareMessage()
: instantiates and returns an empty member of the HttpMessage class, from org.parosproxy.paros.network.HttpMessage which can then be populated with custom HTTP method, URL, Headers, and Body as required to make a request. HttpMessage is discussed in more detail in {link to Key Objects}.sendAndReceive(HttpMessage msg)
: puts a message object on the network for delivery, and blocks while waiting for the response. When the response arrives, it is added to the originalmsg
asresponseHeader
andresponseBody
objects from those respective classes, discussed in more detail in {link to Key Objects}.
The usage of both of these methods can be seen in the script above as shown:
val msg = helper.prepareMessage()
helper.sendAndReceive(msg)
paramsValues
is a String:String map (or dictionary), where the key String is the name of the configuration parameter used to pass in values, and the val String is the value of the parameter. This map gets populated by the getRequiredParams
and getOptionalParams
required functions discussed later. Params, both required and optional, can be set in HawkScan configuration to pass in dynamic values, allowing a single script to be used in a broader variety of situations.
In the script above, an example is shown where a config key:val param named “targetUrl” is obtained, and the provided val is used to set the request URI for the authentication message.
val PARAM_TARGET_URL = "targetUrl"
...
msg.requestHeader = HttpRequestHeader(
HttpRequestHeader.GET, URI(paramsValues[PARAM_TARGET_URL], true),
HttpHeader.HTTP11
credentials
is the last object in the function signature. This is a member of the GenericAuthenticationCredentials
class, from com.stackhawk.hste.authentication.GenericAuthenticationCredentials. Functionally credentials
is a key:val map, much like paramsValues
, however, the class encapsulation allows credentials, typically containing sensitive data, to be treated as unique objects with additional built-in layers of protection. The most common method of the credentials
object is the getParam(String paramName)
method, which uses a credential param key from the HawkScan config to return the sensitive val associated with the key. These are typically usernames, passwords, etc, and as sensitive credentials, will be automatically redacted from hawkscan.log. However, they can be accessed programmatically within the script instance in order to build the required authentication message(s).
Finally, the function signature requires the return of an HttpMessage object. Typically, this is the final message sent/received, along with any post-processing needed to shape the contents into the desired authorization tokens. The returned msg
automatically gets added to the message history tree as an authentication-significant message, allowing HawkScan to extract authorization related information such as Cookies, Auth headers, or JWTs passed in the body.
getRequiredParams
: required parameter names for your script, your script will throw an error if these are not supplied in the app.authentication.script.parameters
configuration.
getOptionalParams
: same as above, but optional parameter names. Also specified in app.authentication.script.parameters
, but will not generate an auto-error if missing. Script logic should check for existence of any defined optional parameters and adjust code flow accordingly.
getCredentials
: required credential parameters, your script will throw an error if these are not supplied in the app.authentication.script.credentials configuration.
getLoggedInIndicator
: This function returns a regex string that HSTE uses to determine if the user is logged in. It typically checks for a specific pattern in the response headers or body.
getLoggedOutIndicator
: This function returns a regex string that HSTE uses to determine if the user is logged out. It typically checks for a specific pattern in the response headers or body.
Function Logic Breakdown
To see these in action, let’s introduce a more fully functional, yet still simple example script. This script uses two messages. The first will grab the login page, complete with form and allow the anti-CSRF (aCSRF) token to be extracted for subsequent use as part of the form post body. The second message will implement the form post. Additional logic will be explained in context via absurdly extensive commenting.
import org.apache.commons.httpclient.URI
import org.apache.log4j.LogManager
import org.parosproxy.paros.network.HttpHeader
import org.parosproxy.paros.network.HttpMessage
import org.parosproxy.paros.network.HttpRequestHeader
import com.stackhawk.hste.authentication.AuthenticationHelper
import com.stackhawk.hste.authentication.GenericAuthenticationCredentials
import com.stackhawk.hste.network.HttpRequestBody
import com.stackhawk.hste.extension.anticsrf.ExtensionAntiCSRF
import org.parosproxy.paros.control.Control
//sets up the logger for pushing messages to hawkscan.log
val logger = LogManager.getLogger("External POST Auth Logger")
/* sets up in-script global variables corresponding to the param and credential keys in the config. */
val PARAM_BASE_URL = "baseUrl"
val PARAM_LOGIN_PATH = "loginPath"
val CREDS_EMAIL = "email"
val CREDS_PASS = "password"
/* sets up a class member of the ExtensionAntiCSRF class, which provides helpers to process aCSRF tokens */
val extCsrf = Control.getSingleton().getExtensionLoader().getExtension(ExtensionAntiCSRF::class.java)
/* This function is called before a scan is started and when the loggedOutIndicator is matched indicating re-authentication is needed. */
fun authenticate(
helper: AuthenticationHelper,
paramsValues: Map<String, String>,
credentials: GenericAuthenticationCredentials
): HttpMessage {
logger.info("Started Ext POST Authentication") //first log message
/* create the message URL from parameter values obtained from config
this URL will be used for both messages */
val targetURL = "${paramsValues[PARAM_BASE_URL]}${paramsValues[PARAM_LOGIN_PATH]}"
//create msg1 as an empty HttpMessage object
//using the AuthenticationHelper member
val msg1 = helper.prepareMessage()
//and populate the request side of the message with the GET verb, URI, and
//required HTTP version
msg1.requestHeader = HttpRequestHeader(
HttpRequestHeader.GET, URI(targetURL, true), HttpHeader.HTTP11)
/* use the helper to send and receive. This initial message GETs the login
page which contains a server-generated aCSRF token that must be submitted
with the POST form. */
helper.sendAndReceive(msg1)
/* use the extCsrf member created earlier to check the msg1 response for any defined aCSRF tokens and retrieve them as an array */
val antiCSRF = extCsrf.getTokensFromResponse(msg1)
//create a second empty HttpMessage object in order to POST the form
val msg2 = helper.prepareMessage()
//begin to populate the msg2 object with verb, same URI, and HTTP version
msg2.requestHeader = HttpRequestHeader(
HttpRequestHeader.POST, URI(targetURL, true), HttpHeader.HTTP11)
/* create the body containing the www-url-form-encoded payload. Note the
form payload requires the value of the YII_CSRF_TOKEN obtained from msg1
Note the "content-type: www-url-form-encoded" header is the
default for HawkScan POSTs so we don't need to explicitly include it. */
val body =
"YII_CSRF_TOKEN=${antiCSRF[0]}" +
"&$CREDS_EMAIL=${credentials.getParam(CREDS_EMAIL)}" +
"&$CREDS_PASS=${credentials.getParam(CREDS_PASS)}" +
"&action=Login"
//attach the body to msg2
msg2.requestBody = HttpRequestBody(body)
//update msg2 "Content-Length" header with the body length in bytes
msg2.requestHeader.contentLength = msg2.requestBody.length()
//use the helper to send and receive, then log the request and response.
helper.sendAndReceive(msg2)
logger.info("MSG SENT:\n${msg2.requestHeader}\n\n${msg2.requestBody}")
logger.info("MSG RECV:\n${msg2.responseHeader}\n\n${msg2.responseBody}")
//return the final message, whose response headers include the Set-Cookie directive containing the required authorization cookie.
return msg2
}
// The required parameter names for your script, your script will throw an error if these are not supplied in the script.parameters configuration.
fun getRequiredParamsNames(): Array<String> {
return arrayOf(PARAM_BASE_URL, PARAM_LOGIN_PATH)
}
// The required credential parameters, your script will throw an error if these are not supplied in the script.credentials configuration.
fun getCredentialsParamsNames(): Array<String> {
return arrayOf(CREDS_EMAIL, CREDS_PASS)
}
//no optional params for this script
fun getOptionalParamsNames(): Array<String> {
return arrayOf()
}
fun getLoggedInIndicator(): String {
return "Welcome, .*"
}
fun getLoggedOutIndicator(): String {
return "Please log in"
}
HawkScan Configuration for the External POST example
Authentication scripts require dual configuration in two locations in the stackhawk.yml
:
app.authentication.script
- Specifies the script name, parameters, and credentialshawkAddOn.scripts[]
- Registers the script with the engine (name, type, path, language)
Key Configuration Points:
- Use
name:
field (NOTfile:
) to specify script filename - The
name
must match in bothapp.authentication.script.name
andhawkAddOn.scripts[].name
credentials
is a child ofscript:
(sibling ofparameters
)path
should be parent directory only - type subdirectory is added automatically
app:
antiCsrfParam: YII_CSRF_TOKEN
authentication:
script:
name: external_post.kts # Script filename - must match hawkAddOn name
parameters: # Passed via getRequiredParamsNames()/getOptionalParamsNames()
baseUrl: "https://login.myexample.com"
loginPath: /login
credentials: # Sibling of parameters - passed via getCredentialsParamsNames()
email: justme@example.com
password: ${MY_PASSWD_ENV_VAR} # Use env vars to avoid hardcoding secrets
hawkAddOn:
scripts:
- name: external_post.kts # Must match app.authentication.script.name
type: authentication
path: scripts # Parent directory only (will look in scripts/authentication/)
language: KOTLIN