> For the complete documentation index, see [llms.txt](https://docs.blusapphire.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.blusapphire.io/older-releases/09_casehub/reflex-query-language-rql.md).

# Reflex Query Language (RQL)

**Reflex Query Language (RQL)** is a query language designed to query events within Case-Hub. RQL is used while creating Event Rules to query the Event data and match certain criteria.

## Event Fields

* `observables|observables.*`
* `value`
* `tlp`
* `tags`
* `spotted`
* `safe`
* `source_field`
* `data_type`
* `ioc`
* `original_source_field`
* `title`
* `description`
* `tlp`
* `severity`
* `status`,
* `reference`
* `source`
* `signature`
* `tags`
* `raw_log|raw_log.*`

## Supported Expressions

The following Expressions are used to compare target field data to intended data:

<table><thead><tr><th width="184.33333333333331">Expression</th><th width="333">Description</th><th>Example</th></tr></thead><tbody><tr><td><code>RegExp</code></td><td>The item has a value that matches a regular expression</td><td><code>title RegExp "^Event.*"</code></td></tr><tr><td><code>In</code></td><td>The item has a value in a list of values</td><td><code>observables.tags.name in ["malware"]</code></td></tr><tr><td><code>Contains</code></td><td>The target value contains a specific string</td><td><code>description Contains "malware"</code></td></tr><tr><td><code>ContainsCIS</code></td><td>Same as <code>Contains</code> but will make the target value and checked value the same case</td><td></td></tr><tr><td><code>=|eq|Eq</code></td><td>Equal to (strings or numbers)</td><td></td></tr><tr><td><code>!=|ne|Ne|NE</code></td><td>Not equal to (strings or numbers)</td><td></td></tr><tr><td><code>>|gt</code></td><td>Greater than (> or gt)</td><td></td></tr><tr><td><code>>=|gte</code></td><td>Greater than or equal to (>= or gte)</td><td></td></tr><tr><td><code>&#x3C;|lt</code></td><td>Less than (&#x3C; or lt)</td><td></td></tr><tr><td><code>&#x3C;=|lte</code></td><td>Less than or equal to (&#x3C;= or lte)</td><td></td></tr><tr><td><code>InCIDR</code></td><td>The IP address is in a specific CIDR network</td><td><code>192.168.1.1 InCIDR 192.168.0.0/16</code></td></tr><tr><td><code>Is</code></td><td>Use for boolean operations like "Is True"</td><td><code>ioc Is True</code></td></tr><tr><td><code>Exists</code></td><td>The field exists at all, useful if you don't care what the target value</td><td></td></tr><tr><td><code>Between</code> </td><td>The item has a value between a given range</td><td><code>tlp Between "1,3"</code></td></tr><tr><td><code>StartsWith</code></td><td>The item has a value that starts with a certain string</td><td><code>url StartsWith "https"</code></td></tr><tr><td><code>EndsWith</code></td><td>The item has a value that ends with a certain string</td><td><code>domain EndsWith ".tk"</code></td></tr><tr><td><code>Expand</code></td><td>When you want two conditions on a nested object to be true use each statement. </td><td>For example, The observable <code>127.0.0.1</code> should have a value of <code>127.0.0.1</code> and <code>safe</code> is True, the query would look like <code>Expand observables (value = "127.0.0.1" and safe Is True)</code></td></tr><tr><td><code>Intel|Threat</code></td><td>Allows RQL to check the value of a field against a Case-Hub Intel List (threat lists)</td><td></td></tr><tr><td><code>Not</code></td><td>The expression within this block should not match. <br><br>Note: Can only be used on <code>eq</code>, <code>in</code>, <code>InCIDR</code>, <code>contains</code>, <code>between</code> AND <code>regexp.</code></td><td><code>title Not Eq "Suspicious DNS Query"</code></td></tr></tbody></table>

Note that the Items that don't have a specified field may match a `Not` expression e.g. `ip NOT InCIDR "192.168.0.0/16"` may match on an event not having an `ip` field, pair this with `ip exists AND ip NOT InCIDR "192.168.0.0/16"`

## Mutators <a href="#mutators" id="mutators"></a>

Mutators take a field and perform an extra operation on it to make it digestible by the downstream comparison. Assume you want to find any Event with a domain observable where the length of the domain is longer than 20 characters

* `|length` - How long a string is e.g. `url.domain|length > 20`
* `|count` - The number of items in an array value `observables|count > 2`
* `|lowercase` - Lowercase a string (redundant for `ContainsCIS` but can be used for other fields)
* `|refang` - If for some reason the target value has been defanged, think `hXXps[:]//` instead of `https://` this will refang the value to perform a proper comparison e.g. `url.full|refang eq "https://www.google.com"`
* `|b64decode` - If the target value is base64 encoded and you want to write rules using the terms within it, first base64 decode it, e.g. `command|b64decode Contains "Invoke-Mimikatz"`
* `|b64extract` - Will attempt to find base64 encoded data in a string and extract it for comparison later in the query e.g. an event contains a command `powershell -encodedCommand SQBuAHYAbwBrAGUALQBXAGUAYgBSAGUAcQB1AGUAcwB0ACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AaQBwAGkAbgBmAG8ALgBpAG8ALwBpAHAA` would extract and decode `SQBuAHYAbwBrAGUALQBXAGUAYgBSAGUAcQB1AGUAcwB0ACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AaQBwAGkAbgBmAG8ALgBpAG8ALwBpAHAA` to `Invoke-WebRequest https://www.ipinfo.io/ip`. An analyst could then use a query like `command|b64extract Contains "ipinfo.io"`
* `|urldecode` - Unescapes an escaped URL so that direct comparisons can be made
* `|any` - Force the following condition to match any item in the array (Can only be used on `Contains` and `In` expressions)
* `|all` - Force the condition to match all items in the array (Can only be used on `Contains` and `In` expressions)
* `|avg` - Calculates the average value given a list of values e.g. `observables.risk_score|avg > 7`
* `|max` - Finds the max value given a list of values e.g. `vulnerablities.cvss_score|max > 7`
* `|min` - Finds the minimum value given a list of values
* `|sum` - Add up all the values in a list of integer or float values
* `|split` - Splits a string that contains spaces into an array
* `|geo_country` - Returns the ISO Code for the country an IP resides in
* `|geo_continent` - Returns the ISO Code for the content an IP resides in
* `|geo_timezone` - Returns the timezone an IP resides in
* `|reverse_lookup` - Takes an IP and attempts to return the associated hostname
* `|nslookup_a` - Fetches the A record for a domain name
* `|nslookup_aaaa` - Fetches the AAAA record for a domain name
* `|nslookup_mx` - Fetches the MX records for a domain name
* `|nslookup_ns` - Fetches the NS records for a domain name
* `|nslookup_ptr`- Fetches the PTR records for a domain
* `|is_private` - Returns True if an IP is RFC1918
* `|is_global` - Returns True if an IP is routable on the internet
* `|is_multicast` - Returns True if an IP is multicast
* `|is_ipv6` - Returns True if an IP is IPv6

## Query Examples <a href="#example-queries" id="example-queries"></a>

```
# Match any Suspicious DNS query only if it came from the Administrator on a domain joined machine and the target observable is evil.com
title = "Suspicious DNS Query" and user Contains "Administrator" and hostname EndsWith "ad.blusapphire.com" and (observables.data_type = "domain" and observables.value = "evil.com")
```

```
# Match any cases referencing malware or with a severity higher than 3
description Contains "malware" or severity > 3
```

```
# Match any event that has a domain with evil.com or an IP with 127.0.0.1
(observables.data_type = "domain" and observables.value = "evil.com") or (observables.data_type = "ip" and observables.value = "127.0.0.1")
```

```
# Match any Event that has all of the below observables
observables.values|all in ["evil.com","blusapphire.com"]
```

```
# Match any event that contains a base64 encoded command that once decoded contains the following phrases
command exists and command|b64decode|lowercase Contains ["invoke-mimikatz","invoke-bloodhound","invoke-powerdump","invoke-kerberoast"]
```

```
# Match any suspicious DNS query for a specific domain that originates from a specific user and source process 
# Should always be this event title
title = "Suspicious DNS Query"

# Observables should always exist
and observables exists

# Brian must be the source user of the event
and expand observables (value Contains "Brian" and source_field|lowercase = "winlog.event_data.user")

# And the DNS resolution should always be for netwars-support.slack.com
and expand observables (value = "netwars-support.slack.com" and source_field|lowercase = "dns.question.name")

# And should originate from Slack
and raw_log.process.executable EndsWith "slack.exe"
```

```
# Check to see if the user in the event is in the Allowed Users intel list
expand observables ( data_type = "user" and intel(value|uppercase, 'Allowed Users'))
```
