Skip to content

Step

Step is equal to one action - module execution. Every step can have a successor(s) whose execution will follow according to the provided conditions.

Example of defining a step using YAML:

my-step:
  metadata:
    description: This is an example description
  is_init: true
  module: module-name
  arguments: {}
  output:
    alias: credentials-from-localhost
    replace:
      "^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$": removed-ip
    mapping:
      - from: auth_token
        to: token
  next:
    - type: state
      value: finished
      step: other-steps-name

The name of the step is defined by the root element that holds it. In this case it's my-step (the name must be unique for each stage and step).

To better understand what each argument means and defines, here is a short description (sub-arguments are described in depth in their section):

  • metadata - An undefined dictionary containing metadata. The description parameter is just an example, you can define your own.
  • is_init - Defines if the step is initial (is executed once the stage has started) and is not a successor.
  • module - Name of the module to use. See modules for more information.
  • arguments - Dictionary containing arguments that will be passed to the module. See modules for more information.
  • output - If you want to modify/share the step's output later, you can do so with this parameter. For more details check out the output sharing section.
  • next - Defines Step's successors, more info below.

Conditional execution

To execute an attack scenario according to some execution tree, steps provide a way to execute other steps according to the specified conditions.

This can be done with the next parameter, which contains an array of objects with the following parameters:

parameter type Description
type string Defines which type of output to compare.
value string or array of strings Value used for comparison.
step string or array of strings Name(s) of the successor(s).

The following are types of outputs together with the descriptions of their possible values:

Type Value Description
state finished, failed, error Match the state of the step.
serialized_output Regular expression (^my_regex.*) Match regex in serialized_output of the step.
output Regular expression (^my_regex.*) Match regex in output of the step.
any Value must be omitted Run successor(s) in any case.

Examples:

my-step:
  next:
  - type: state
    value: finished
    step: step-to-execute
my-step:
  next:
  - type: serialized_output
    value: 
      - admin
      - root
    step: 
      - step-to-execute-1
      - step-to-execute-2
my-step:
  next:
  - type: any
    step: step-to-execute

Output sharing

Output sharing is a feature that allows sharing of the serialized_output between steps. The data can be also shared across stages or even from stages.

Going through the data

To go through the serialized data we use a modified version of a dot notation with the following rules:

  • To access an item inside an object (dictionary) use a separator - . by default
  • To access an item inside an array (list) specify an index [integer] (regex representation: ^\[[0-9]+]$)

For example, imagine the following object (dictionary):

{"credentials": [{"username": "admin", "password": "securePassword"}]}
If we wanted to access it and get the password for the admin user, we would use credentials[0].password which would return securePassword.

Accessing the data

We use the $ character to indicate we want to use data from another step. Then we insert the name of the desired step, and finally use the dot notation mentioned before.

In the template, we would define it like so:

get-credentials:  # step with the wanted data
  module: my-module
  arguments: {}

use-password:  # step that needs the data
  module: my-other-module
  arguments:
    password: $get-credentials.credentials[0].password

Output sharing is resolved only inside the arguments parameter in step.

Output alias

By default, you can access step's data using its name. Additionally, you can define an alias as an alternative. This can be useful since you can assign the same alias to multiple steps.

How is the data handled

The serialized output from each step is merged into a single object. In case the data exists in multiple steps, the latest data gets used.

Imagine we have two steps with the same alias.

The first one finished with the following output:

{"alpha": "blue", "whiskey": "red"}

The second step finished a minute later with the following output:

{"whiskey": "green", "charlie": "yellow"}

The following data would be available to the step accessing the alias:

{"alpha": "blue", "whiskey": "green", "charlie": "yellow"}

For example:

get-credentials:
  module: my-module
  arguments: {}
  output:
    alias: my-super-alias

use-password:
  module: my-other-module
  arguments:
    password: $my-super-alias.credentials[0].password

Parent alias

Furthermore, there is a special alias named parent, which is a shortcut for the step (parent) that executed the current step.

get-credentials:
  module: my-module
  arguments: {}
  next:
    type: any
    step: use-password

use-password:
  module: my-other-module
  arguments:
    password: $parent.credentials[0].password

Output mapping

Sometimes you do not care from which step you receive information, which is why the output alias exists. However, what if the data is saved under a different name (token vs. auth_token)? For this reason, there is the output mapping.

step-a: # Returns 'token'
  module: my-module
  arguments: {}
  output:
    alias: steal
    mapping:
      - from: token
        to: stolen_token

step-b: # Returns 'auth_token'
  module: my-other-module
  arguments: {}
  output:
    alias: steal
    mapping:
      - from: auth_token
        to: stolen_token

step-c:
  module: my-other-other-module
  arguments:
    token: $steal.stolen_token

Output replacing

In case you want to replace some parts of your output, you can define a dictionary of rules (regexes) and strings to replace the matches with.
Keep in mind, that the rules are applied in order.

Here is an example of matching IPv4 and replacing it with removed-ip:

my-step:
  module: my-module
  arguments: {}
  output:
    replace:
      "^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$": removed-ip

Execution variables

To assign different values for each plan execution in a Run, you can use execution variables.

To define an execution variable, use Jinja (with some tweaks) wrapped in single quotes:

my-step:
  module: my-module
  arguments:
    target: '{{ my_jinja_variable }}'

Before you execute the run, upload the variable(s). See CLI documentation for more information.

Example of a file with execution variables:

variable: localhost
nested:
  variable: value
variables:
  - var1
  - var2

Limitations

  • Execution variables must be wrapped in single quotes
    foo: '{{ variable }}'
    
  • Execution variables are resolved only for the arguments parameter in the step
  • Currently, there is support for simple and nested variables only:
    foo: '{{ variable }}'
    
    foo: '{{ nested.variable }}'
    
    foo: '{{ variable[index] }}'
    
    foo: '{{ nested.variable[index] }}'
    
  • If you want to use more Jinja goodies, use the raw block:
    foo: {% raw %} '{{ variable + 14 }}' {% endraw %}
    
  • If a variable is missing, the step errors out once it's started