- 26 Apr 2024
- 5 Minutes to read
- Print
- DarkLight
- PDF
Pre-hooks
- Updated on 26 Apr 2024
- 5 Minutes to read
- Print
- DarkLight
- PDF
Traceable provides the capability using which you can write pre-hooks. The pre-hooks can be used to carry out multiple tasks, for example, authentication before starting the tests, test setup, and so on. These pre-hooks can be configured to run at multiple levels. For example, you can configure pre-hook to run before each individual test plugin, or test plugin category, for the entire scan. For example, in the sample config.yaml
file shown below, you can see that a pre-hook runs before the start of the plugin and one specific to a test. The bfla
pre-hook shown in the sample config.yaml
is for an individual Authorization plugin, while crap_login_prehook
is for logging into an application. These pre-hooks are written in Python and used in Traceable CLI.
Note
Since pre-hooks are application-specific, use the examples as samples to create your custom pre-hooks.
# This is a sample user config.
# Copy it to traceable config folder ~/.traceable/config.yaml to apply.
# This config overrides data in default.yaml.
# Do not make edits to default.yaml as changes will be lost.
scan:
plugins:
# Enable prehooks for all plugins
pre_hooks:
- crapi_login_prehook
post_hooks:
- crapi_login_posthook
authentication:
unauthenticated_access:
pre_hooks:
# Disable crapi_login_prehook for unauthenticated_access
- crapi_login_prehook:
disabled: true
# Disable crapi_login_posthook for unauthenticated_access
- crapi_login_posthook:
disabled: true
authorization:
bfla:
pre_hooks:
- crapi_creds_prehook
custom:
sample_plugin: {}
Make a note of the following that are used in the examples below:
Authentication hook method is invoked before sending every request.
You may or may not want to access the authentication endpoint before each request. You can control that behavior using
scanctx
andpluginctx
.scanctx
is a dictionary scoped per scan. To reuse any value throughout the scan, add that value with the key of your choice and use it whenever it is present. Following is an example:Python
if scanctx.get("token_key") == None: token = (make request to obtain token).getToken scanctx["token_key] = token token = scanctx.get("token_key")
Similar to
scanctx
,pluginctx
is scoped per plugin. So, a unique dictionary is used per plugin. You can store values at key level.Hook can add key-value in headers, query parameters, cookies, or request body. You need to add these to the attributes map. For example,
You need to first fetch the attributes from the
testcase
(which essentially translates to parameter of next request)attributes = testcase.get_attributes()
To Add Cookie:
attributes.set("mutated.http.request.cookie.%s" % (cookie.name), cookie.value)
To Add Header:
attributes.set("mutated.http.request.header.authorization", "Bearer fnasfjasjfoijfoiefjkewwrw")
To add Query parameter:
attributes.set("mutated.http.request.query.authorization", "Bearer fnasfjasjfoijfoiefjkewwrw")
To add body parameter:
attributes.set("mutated.http.request.body.authorization", "Bearer fnasfjasjfoijfoiefjkewwrw")
The following two examples are two sample authentication pre-hooks for OWASP crAPI application. For more information, see crAPI.
crAPI credential pre-hook
The following is a sample pre-hook function in Python:
def sample_traceable_login_prehook(scanctx: ScanContext, pluginctx: PluginContext, testcase: TestCase, **kwargs) -> list[Assertion]:
attributes = testcase.get_attributes()
url = urllib.parse.urlparse(
attributes.get_one("mutated.http.request.url", ""))
logger.info("Running crapi_login_prehook for plugin %s" % pluginctx.get_plugin())
if scanctx.get("crapi.role.user", None) is None:
# Login:
logger.debug("crapi_login_prehook: Token not found. Creating token")
u = urllib.parse.urlunparse(
url._replace(path="/identity/api/auth/login"))
res = requests.post(u, json={
"email": "test@example.com",
"password": "Test!123"
}, headers={
"content-type": "application/json",
"x-traceable-ast": "0,0,0"
})
if res.status_code != 200:
logger.error("Failed to login to crapi, got status %d" %
res.status_code)
return []
res = res.json()
scanctx.set("crapi.role.user", res["token"])
else:
logger.debug("Already logged in to CRAPI")
attributes.set("mutated.auth.attribute", "mutated.http.request.header.authorization")
attributes.set("mutated.role.user", "Bearer %s" % scanctx.get("crapi.role.user"))
attributes.set("mutated.role.bolauser", "Bearer %s" % scanctx.get("crapi.role.bolauser"))
logger.debug("Mutated role user: %s" % scanctx.get("crapi.role.user"))
return []
Explanation
This function is a pre-hook that is used to manage authentication for the crAPI in a security scan context.
First, the function parses the URL from the mutated.http.request.url
attribute in the attributes
parameter, and then checks if a token for the user's role is already present in the scanctx
object. If not, the function logs into crAPI by sending a POST request to the login endpoint with a test email and password, and retrieves a token. The retrieved token is then stored in the scanctx
object.
If a token is already present, the function logs a message indicating that the user is already logged in. The function then sets two attributes in the attributes
object: mutated.auth.attribute
is set to mutated.http.request.header.authorization
, and mutated.role.user
is set to "Bearer" plus the token retrieved from scanctx
.
scanctx
is managed by Traceable CLI internally at run time.
CSRF token pre-hook
The following is a sample code to set header csrf_token to a random string in an attack request. A random string can be replaced with an actual token at runtime:
def set_csrf_token_prehook(scanctx: ScanContext, pluginctx: PluginContext, attributes: AttributeList) -> list[Assertion]:
if not scanctx.get("csrf_token"):
scanctx.set("csrf_token", "".join(random.sample(string.ascii_lowercase, k=10)))
attributes.set("mutated.http.request.header.csrf_token", scanctx.get("csrf_token"))
return []
Explanation
The function checks if a CSRF token is already present in the scanctx
object by attempting to retrieve it using the get()
method with the key "csrf_token"
. If a CSRF token is not present, the function generates a new CSRF token by randomly selecting 10 characters from the lowercase English alphabet using the random.sample()
method and stores it in the scanctx
object with the same key "csrf_token"
.
The function then sets the mutated.http.request.header.crf_token
attribute in the attributes
object to the CSRF token retrieved from scanctx
using the get()
method with the same key "csrf_token"
.
Configuring the pre-hook
The pre-hooks that you create need to be configured in Traceable CLI. Make sure that Traceable CLI is installed before proceeding. The important files and directories in Traceable CLI that are used for pre-hooks are:
hooks directory – Your pre-hooks and posthooks are stored in this directory.
config.yaml
default.yaml
The default.yaml
file is provided by Traceable. Whatever is added to default.yaml is overwritten by config.yaml. It is a practical approach to have your working pre-hooks configured in config.yaml
. The config.yaml
file is given preference at run time. Refer to the config.yaml
listed at the starting of the topic.