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:

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

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

# 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'))

Last updated