Posted by:

David Greenwood

David Greenwood, Chief of Signal

If you are reading this blog post via a 3rd party source it is very likely that many parts of it will not render correctly. Please view the post on signalscorps.com for the full interactive viewing experience.

In this post I will show you how workflows inside a playbook can be created so you can start to construct your own.

Note: this tutorial is written for CACAO version 1.1 (published on 2022-03-01). Some of the concepts discussed might not be correct for future versions of CACAO.

Workflows contain a series of steps to be performed. Think of the workflow as the logic of a playbook; it describes the actions and order of the actions that should be taken.

Steps can be performed sequentially, in parallel, or both depending on the type of steps required by the playbook.

Each work step can also contain conditional operations to control the steps executed (e.g. if true; do this, if false, do that).

As I introduced last week, each step in a playbook is built from various json objects…

Start step

Every playbook must have a start step to define when a playbook workflow should start. For example, the start step might be receive an Indicator of Compromise.

"workflow_start": "start--241bfc8c-6d94-430e-a084-84f6597b89eb",
"workflow": {
    "start--241bfc8c-6d94-430e-a084-84f6597b89eb": {
        "type": "start",
        "name": "Recieve an Indicator of Compromise",
        "on_completion": "single--882cdf65-3673-45d8-8007-23b1fe31e107"
    },

You will quickly see how each action is defined with a type and UUID (e.g. start--241bfc8c-6d94-430e-a084-84f6597b89eb).

In this case I have a "type": "start" step to kick of this workflow. The on_completion step defines (by ID) the next step when the workflow starts.

Single Step

    "single--882cdf65-3673-45d8-8007-23b1fe31e107": {
        "type": "single",
        "name": "Receive IOC",
        "description": "Get suspicious IPs from SIEM",
        "on_completion": "parallel--e7f6b3de-a77e-420d-a39a-1873180e1cb8",
        "commands": [
            {
                "type": "manual",
                "command": "Get IOC from threat feed"
            }
        ]
    },

A "type": "single" step starts this workflow.

A single workflow step is to be processed sequentially in the workflow.

In the example above, you will see workflow step contains the a manual command to be executed (i.e. to be run manually by humans). In this case, “Get IOC from threat feed”.

Commands can have a other few types, many more suited to automation in the playbook (but can also be run manually).

For example, Sigma and Kestrel hunting commands are supported (as are entire Jupyter notebooks).

    "commands": [
        {
            "type": "sigma",
            "command_b64": "dGl0bGU6IEFudGl2aXJ1cyBQcmludGVyTmlnaHRtYXJlIENWRS0yMDIxLTM0NTI3IEV4cGxvaXQgRGV0ZWN0aW9uCmlkOiA2ZmUxNzE5ZS1lY2RmLTRjYWYtYmZmZS00ZjUwMWNiMGE1NjEKc3RhdHVzOiBzdGFibGUKZGVzY3JpcHRpb246IERldGVjdHMgdGhlIHN1c3BpY2lvdXMgZmlsZSB0aGF0IGlzIGNyZWF0ZWQgZnJvbSBQb0MgY29kZSBhZ2FpbnN0IFdpbmRvd3MgUHJpbnQgU3Bvb2xlciBSZW1vdGUgQ29kZSBFeGVjdXRpb24gVnVsbmVyYWJpbGl0eSBDVkUtMjAyMS0zNDUyNyAoUHJpbnRlck5pZ2h0bWFyZSksIENWRS0yMDIxLTE2NzUgLgpyZWZlcmVuY2VzOgogICAgLSBodHRwczovL3R3aXR0ZXIuY29tL212ZWxhemNvL3N0YXR1cy8xNDEwMjkxNzQxMjQxMTAyMzM4CiAgICAtIGh0dHBzOi8vbXNyYy5taWNyb3NvZnQuY29tL3VwZGF0ZS1ndWlkZS92dWxuZXJhYmlsaXR5L0NWRS0yMDIxLTE2NzUKICAgIC0gaHR0cHM6Ly9tc3JjLm1pY3Jvc29mdC5jb20vdXBkYXRlLWd1aWRlL3Z1bG5lcmFiaWxpdHkvQ1ZFLTIwMjEtMzQ1MjcKYXV0aG9yOiBTaXR0aWtvcm4gUywgTnV0dGFrb3JuIFQsIFRpbSBTaGVsdG9uCmRhdGU6IDIwMjEvMDcvMDEKbW9kaWZpZWQ6IDIwMjIvMDMvMjIKdGFnczoKICAgIC0gYXR0YWNrLnByaXZpbGVnZV9lc2NhbGF0aW9uCiAgICAtIGF0dGFjay50MTA1NQpsb2dzb3VyY2U6CiAgICBjYXRlZ29yeTogYW50aXZpcnVzCmRldGVjdGlvbjoKICAgIHNlbGVjdGlvbjoKICAgICAgICBGaWxlbmFtZXxjb250YWluczogJ0M6XFdpbmRvd3NcU3lzdGVtMzJcc3Bvb2xcZHJpdmVyc1x4NjRcJwogICAga2V5d29yZHM6CiAgICAgICAgLSAnRmlsZSBzdWJtaXR0ZWQgdG8gU3ltYW50ZWMnICMgc3ltYW50ZWMgZnAsIHBlbmRpbmcgYW5hbHlzaXMsIG1vcmUgZ2VuZXJpYwogICAgY29uZGl0aW9uOiBzZWxlY3Rpb24gYW5kIG5vdCBrZXl3b3JkcwpmaWVsZHM6CiAgICAtIFNpZ25hdHVyZQogICAgLSBGaWxlbmFtZQogICAgLSBDb21wdXRlck5hbWUKZmFsc2Vwb3NpdGl2ZXM6CiAgICAtIFVubGlrZWx5LCBvciBwZW5kaW5nIFBTUCBhbmFseXNpcwpsZXZlbDogY3JpdGljYWw="
        }
    ]

For Sigma and Kestrel, the rule should be encoded as base64 and the command_b64 property should be used.

Of course, if automating this, the place where this Sigma rule (or any command type) must be defined.

The CACAO target object contains detailed information about the entities or devices that accept, receive, process, or execute one or more commands as defined in a workflow step. Targets contain the information needed to send commands as defined in steps to devices or humans.

A target has a type (e.g. firewalls, IPS, Switch, Router, Threat Intelligence Platform, etc.)

    "targets": {
        "target--f81aa730-2c59-4190-b8d5-3f2b4beecd95": {
            "type": "security-infrastructure-category",
            "category": [
                "siem"
            ]
        }
    }

For increased automation, the target object can contain full information. For example, complete information to initiate an SSH connection;

    "targets": {
        "target--e321e9c6-6787-4ad0-9f93-1f04094e4691": {
            "type": "ssh",
            "address": "1.1.1.1",
            "port": "80",
            "username": "someone",
            "private_key": "my_key"
        }
    }

So what’s the difference between a target and a command?

A command defines WHAT command should be run. e.g.

{
  "type": "ssh",
  "command": "last; netstat -n; ls -l -a /root",
}

A target defines WHERE the command should run/

So if the last command and target example were in a step, last; netstat -n; ls -l -a /root would be run on 1.1.1.1.

As you progress into more advanced use-cases like this the commands to be run (and other property values in each step) will require variables that are the result of outcomes from previous steps (e.g. a value generated from a lookup step) or defined globally in the playbook (e.g. username / password).

This is where playbook variables prove useful.

Playbook (global) / Workflow (local) Variables

A variable may be defined globally for the entire playbook or locally within a workflow step.

Here is a definition of a variable at playbook level, meaning it can be used by every workflow step;

{
    "type": "playbook",
    "playbook_variables": {
        "$$ipv4_input$$": {
            "type": "ipv4-addr",
            "description": "An IPv4 input to the playbook",
            "value": "<variable_value>",
            "constant": true
        }
    }
}

If the constant property is set to true the variable cannot change during the playbook (i.e. it will be the same in every step. If false, the variable is dynamic (i.e. can be updated later on in the playbook).

Which can be referenced in the step;

    "commands": [
        {
            "type": "http-api",
            "command": "https://www.threat-intel-service.com/v1/getData?id=$$ipv4_input$$"
        }
    ]

If I wanted to define a workflow step specific variable, I could do so inside the step itself using the step_variables property;

"single--882cdf65-3673-45d8-8007-23b1fe31e107": {
    "type": "single",
    "name": "Receive IOC",
    "description": "Get suspicious IPs from SIEM",
    "step_variables": {
        "$$ipv4_input$$": {
            "type": "ipv4-addr",
            "description": "An IPv4 input to the playbook",
            "value": "<variable_value>",
            "constant": true
        }
    },
    "on_completion": "parallel--e7f6b3de-a77e-420d-a39a-1873180e1cb8",
    "commands": [
        {
            "type": "http-api",
            "command": "https://www.threat-intel-service.com/v1/getData?id=$$ipv4_input$$"
        }
    ]
},

Once this step has been executed, data will be returned as a result of the step (a response from the threat intel feed). Once complete, the "on_completion" value defines the next step, in this case a parallel type step (parallel--e7f6b3de-a77e-420d-a39a-1873180e1cb8).

Parallel Step

The Parallel Step is a playbook step type that allows playbook authors to define two or more steps that can be executed at the same time (e.g. perform two single steps in parallel).

    "parallel--e7f6b3de-a77e-420d-a39a-1873180e1cb8": {
        "type": "parallel",
        "name": "Update Protection Tools",
        "description": "This step will update the firewall and client EDR in parallel",
        "next_steps": [
            "single--e0f5e208-d268-4560-8dbc-553cd0f1f2d0",
            "single--02e43159-b2a5-4bdd-814d-1dc95d0c16bb"
        ]
    },
    "single--e0f5e208-d268-4560-8dbc-553cd0f1f2d0": {
        "type": "single",
        "name": "Add IP to Firewall Blocklist",
        "description": "This step will add the IP address of the FuzzyPanda data exfil site to the firewall",
        "on_completion": "single--a6d6d21a-eba6-4225-9387-b538340d6a13",
        "commands": [
            {
                "type": "manual",
                "command": "Open firewall console and add 1.2.3.4 to the firewall blocking policy"
            }
        ]
    },
    "single--02e43159-b2a5-4bdd-814d-1dc95d0c16bb": {
        "type": "single",
        "name": "Add IP to Client EDR Blocklist",
        "description": "This step will add the IP address of the FuzzyPanda data exfil site to the client EDR solution",
        "on_completion": "single--3dcc94e3-3858-4f51-9a2d-393a96cd8919",
        "commands": [
            {
                "type": "manual",
                "command": "Open EDR console and add 1.2.3.4 to the blocking policy"
            }
        ]
    },

In the example below, two single steps should be conducted in parallel.

Now, in many cases the suspicious IP that triggered the start of the playbook might not be malicious at all. In this playbook, a benign IOC could be described as an IOC that returned no information from the threat intelligence provider.

This is where you would want to introduce conditional logic into your workflow.

Conditional Steps (if, while, switch)

CACAO offers three types of conditional steps:

These conditions are defined in the step itself.

In my example, I could create new step defining how the response of the threat intel lookup should be handled if the response matches (on_true) or does not match (on_false) the condition.

{
    "type": "playbook",
    "playbook_variables": {
        "$$ipv4_field$$": {
            "type": "string",
            "description": "An IPv4 field name",
            "value": "ip4_field_name",
            "constant": true
        }
    },
    "workflow": {
        ...
        "step--2c21958e-19d7-4c42-b372-54eb16013e72": {
            "type": "if-condition",
            "delay": "5000",
            "timeout": "60000",
            "condition": "$$ipv4_field$$ == '10.0.0.0/8'",
            "on_true": [
                "step--3b6ef0ae-1a1f-40b6-9e7a-2122d86caebd"
            ],
            "on_false": [
                "step--b0ef624c-7d08-446d-84ec-6b4ac36f5fb5"
            ]
        }

Here if ip4_field_name returned equals '10.0.0.0/8' then the workflow will navigate to step--3b6ef0ae-1a1f-40b6-9e7a-2122d86caebd,

Playbook workflow Step

Chaining playbooks can also prove incredibly useful.

Instead of referencing a step in an existing playbook, an on_completion step can also start another playbook using a Playbook workflow Step.

"step--ba23c1b3-fdd2-4264-bc5b-c056c6862ba2": {
    "type": "playbook",
    "playbook_id": "playbook-b2e6796b-7ecc-4845-990b-f867101890f0",
    "delay": 5000,
    "timeout": 60000,
    "on_completion": "step--61c93f3c-d57e-4b9f-9ec3-d8a93e326924",
    "target_ids": [
        "$$LOCAL_TARGET$$"
    ],
    "in_args": [ 
        "$$data_from_this_playbook$$"
    ],
    "out_args": [ 
        "$$data_from_other_playbook$$"
    ]
}

When a step references an external playbook as above it can take in_args, an optional list of arguments passed to the target(s) as input to the referenced playbook and out_args, variables taken from the completion of the referenced playbook for use in the next step of this playbook (in this case step--61c93f3c-d57e-4b9f-9ec3-d8a93e326924).

Putting it all together

Here’s an example from the draft CACAO specification of a preventative playbook;

CACAO preventative playbook example

You can see a basic example of how json objects can be put together to create an entire playbook flow.

In the next tutorial in this series I will show you how to write even more complex playbooks.


CACAO Certification (Virtual and In Person)

The content used in this post is a small subset of our full training material used in our CACAO training.

If you want to join a select group of certified CACAO professionals, subscribe to our newsletter below to be notified of new course dates.




Discuss this post


Signals Corps Slack

Never miss an update


Sign up to receive new articles in your inbox as they published.