Reference: Operators
The next sections describe the different operators supported by Sekoia Operating Language (SOL).
Count rows
Use the count operator to count the number of rows returned by the statement.
<table name>
| count
Count the number of rows in the events table
events
| where timestamp > ago(30m)
| count
Select columns
Use the select operator to define the columns to retrieve from the table. The order of the columns defined in the query will define the order of the columns in the results.
<table name>
| select <column name 1>, <column name 2>
Select the columns host.name and source.ip from the events table
events
| select host.name, source.ip
| limit 100
Distinct
Use the distinct operator to list all the unique values of a column.
<table name>
| distinct <column name>
List the unique values of client.ip from the events table
events
| where timestamp > ago(24h)
| distinct client.ip
| limit 100
Where
Use the where operator to filter rows by a list of conditions. Use parenthesis and keywords and, or to define complex conditions.
<table name>
| where <conditions>
Filter the query by excluding events older than 5 days and retrieving only user agent from Mac
events
| where timestamp > ago(5d) and user_agent.device.name == 'Mac'
| limit 100
Filter the query by excluding events older than 5 days and retrieving only user agent from Mac or Android
events
| where timestamp > ago(5d) and (user_agent.device.name == 'Mac' or user_agent.device.name == 'Android')
| limit 100
Same as previous but with multiple where statements
events
| where timestamp > ago(5d)
| where user_agent.device.name == 'Mac' or user_agent.device.name == 'Android'
| limit 100
Nested query
Use the in operator to use the results of a previous query.
let query = <table name> | select <column name>;
<table name>
| where <column name> in query
Example
let chromium_browsers = events
| where timestamp > ago(30d)
| where process.command_line contains " --type=renderer " and process.command_line contains " --extension-process "
| distinct process.command_line;
events
| where process.command_line in chromium_browsers
| aggregate count_agents=count_distinct(agent.id), executables=make_set(process.executable) by process.name
| order by count_agents
Sort results
Use the order by operator to sort rows by a column. The default sort order is descending.
<table name>
| order by <column name> <desc | asc>
Order the rows by the timestamp column in ascending order
events
| order by timestamp asc
| limit 100
Order alerts by descending urgency and ascending first_seen_at
alerts
| select short_id, rule_name, urgency, first_seen_at
| order by urgency desc, first_seen_at asc
| limit 100
Limit results
Use the limit operator to retrieve the last n number of rows based on the current sort order.
<table name>
| limit <integer>
Get 1000 events from events table
events
| limit 1000
Get the Top n rows
Use the top operator to returns the first n rows sorted by the specified column.
<table name>
| top <integer> by <column name> [asc | desc]
Get the top 5 alerts with the most occurrences from alerts table in the last 7 days
alerts
| where created_at > ago(7d)
| top 5 by occurrences
Note that the query below is equivalent.
alerts
| order by occurrences desc
| limit 5
Create calculated columns
You can use select or extend operators to create calculated columns.
Use select to specify the columns to display. When using extend, the calculated column is appended to the end of the table.
<table name>
| select <new column name> = <column name 1> + <column name 2>
Create a calculated column named total that sums the time_to_detect, time_to_respond and time_to_resolve values
alerts
| select total = time_to_detect + time_to_respond + time_to_resolve
| limit 100
Aggregate rows
Use the aggregate operator to group rows by a column and perform aggregations with a chosen function: count, sum, min, max, avg, count_distinct, make_set, countif.
<table name>
| aggregate <function> by <column name>
Count the number of events per asset in the events table
events
| aggregate count() by sekoiaio.any_asset.name
| limit 100
Note that you can specify a column name for the aggregation. In the example below, the column name is defined as total.
events
| aggregate total = count() by sekoiaio.any_asset.name
| limit 100
Count the number of events per source.ip and per action.outcome in the events table
events
| where timestamp >= ago(24h) and event.category == 'authentication'
| aggregate count() by source.ip, action.outcome
Sum the values of 'time_to_detect' column in the alerts table
alerts
| aggregate sum(time_to_detect)
| limit 100
Retrieve the minimum value of 'time_to_detect' column in the alerts table
alerts
| aggregate min(time_to_detect)
| limit 100
Retrieve the maximum value of 'time_to_detect' column in the alerts table
alerts
| aggregate max(time_to_detect)
| limit 100
Calculate the average value of 'time_to_detect' column in the alerts table
alerts
| aggregate avg(time_to_detect)
| limit 100
Count unique values of 'source.ip' column in the events table
events
| aggregate count_distinct(source.ip)
| limit 100
Create an array of the set of distinct values of 'source.ip' column in the events table
Note that null values are ignored.
events
| aggregate make_set(source.ip)
| limit 100
Count allowed and denied network events per destination port using countif
events
| where timestamp >= ago(24h) and event.category == 'network'
| aggregate allowed = countif(action.outcome == 'success'), denied = countif(action.outcome == 'failure') by destination.port
| order by denied desc
| limit 100
Render results in chart
Use the render operator to display results in a chart to identify more easily anomalies or outliers. Supported charts are:
numberpiechartcolumnchartbarchartlinechart
<table name>
| aggregate <function> by <column name>
| render <chart_type> with (x=<column name>, y=<column name>, breakdown_by=<column name>, mode=<grouped | stacked>)
Count the number of events per asset in the events table and render it in a bar chart
events
| aggregate count() by sekoiaio.any_asset.name
| render barchart with (y=sekoiaio.any_asset.name)
| limit 100
Join tables
Use the join operator to combine data from multiple tables, enriching the data context, filtering more accurately data.
Available join types are:
- inner join: Returns records that have matching values in both tables (default)
- left join: Returns all records from the left table, and the matched records from the right table
<left table name>
| inner join <right table name> on <left column name> == <right column name>
| select <model object>.<right column name>, <left column name>
When performing join, the right table is injected into a model object.
This model object (similar to a class Object in code development) contains a set properties. Each property represents a column of the original table.
Info
By convention, we consider that when using join, the left part of the statement represents the column of the left table and the right part of the statement represents the column of the right table.
Join the tables events and intakes
events
| where timestamp > ago(24h)
| limit 100
| inner join intakes on sekoiaio.intake.uuid == uuid // sekoiaio.intake.uuid belongs to events table and uuid belongs to intakes table
| distinct intake.name
The model object default name is related to the table name it is originating from. In this case, the model name is intake since the join was performed on the intakes table.
Join the tables alerts and entities
alerts
| where created_at > ago(24h)
| limit 100
| inner join entities on entity_uuid == uuid // entity_uuid belongs to alerts table and uuid belongs to entities table
| distinct entity.name
The model object default name is related to the table name it is originating from. In this case, the model name is entity since the join was performed on the entities table.
Define model object name
In this example, we define a specific name for the model object with the into operator.
alerts
| where created_at > ago(24h)
| inner join entities on entity_uuid == uuid into my_entity
| select my_entity.name
Lookup
Use the lookup operator to extend a table. Extends the current table with values looked-up in another table.
Prefer the lookup operator over join when the right table is small enough to fit into memory to improve query performance.
Info
The result doesn't repeat columns from the right table that are the basis for the join operation.
The lookup operator only supports left join.
<left table name>
| lookup <right table name> on <left column name> == <right column name>
| aggregate <function> by <column name>
| order by <column name>
Similarly to join operator, lookup will inject the right table into a model object.
Compare
Use the following operators to compare values.
| Comparator | Description | Examples |
|---|---|---|
| == | Equals | 1 == 1 |
| =~ | Equals (case insensitive) | '.exe' == '.EXE' |
| != | Not equals | 1 != 0 |
| < | Less | 1 < 10 |
| <= | Less or equals | 4 <= 5 |
| > | Greater | 20 > 10 |
| >= | Greater or equals | 5 >= 4 |
In
Use the in operator to filter the rows based on a set of case-sensitive strings.
Use in~ to filter on a set of case-insensitive strings.
<table name>
| where <column name> in [<value 1>, <value 2>]
Find events where client.ip equals to theses values: 192.168.0.1, 192.168.0.2.
events
| where client.ip in ['192.168.0.1', '192.168.0.2']
| limit 100
Find events where process.name equals to theses values ignoring case-sensitivity: powershell.exe, powershell_ise.exe
events
| where process.name in~ ['powershell.exe', 'powershell_ise.exe']
| limit 100
Contains
Use the contains operator to filter the rows that contains a case-sensitive string.
Use contains~ to switch to case-insensitive strings.
<table name>
| where <column name> contains <value 1>
Find events where user.full_name contains the string Admin (case sensitive)
events
| where user.full_name contains 'Admin'
| limit 100
Find events where user.full_name contains the string ADMIN (case insensitive)
events
| where user.full_name contains~ 'ADMIN'
| limit 100
Starts with
Use the startswith operator to filter rows that starts with a case-sensitive string.
Use startswith~ to switch to case-insensitive strings.
<table name>
| where <column name> startswith <pattern>
Find events where url.domain starts with the string api.prod
events
| where url.domain startswith 'api.prod'
| limit 100
Find events where process.command_line starts with the string Invoke ignoring case-sensitivity
events
| where process.command_line startswith~ 'Invoke'
| limit 100
Ends with
Use the endswith operator to filter rows that ends with a case-sensitive string.
Use endswith~ to switch to case-insensitive strings.
<table name>
| where <column name> endswith <pattern>
Find events where url.path ends with the string /admin
events
| where url.path endswith '/admin'
| limit 100
Find events where process.command_line ends with the string .DLl ignoring case-sensitivity
events
| where process.command_line endswith~ '.DLl'
| limit 100
Not
Use the not operator to negate any comparison.
<table name>
| where not <column name> <comparison operator> <pattern>
Find events where client.ip does not equal to theses values: 192.168.0.1, 192.168.0.2
events
| where not client.ip in ['192.168.0.1', '192.168.0.2']
| limit 100
Find events where user.full_name does not contain the string Admin (case sensitive)
events
| where not user.full_name contains 'Admin'
| limit 100
Find events where process.command_line does not start with the string Invoke ignoring case-sensitivity
events
| where not process.command_line startswith~ 'Invoke'
| limit 100
Find events where process.command_line does not end with the string .DLl ignoring case-sensitivity
events
| where not process.command_line endswith~ '.DLl'
| limit 100
Regex
Use the matches regex operator to filter the rows based on a regex pattern.
<table name>
| where <column name> matches regex <pattern>
| Pattern | Description | Example |
|---|---|---|
. |
Matches any character | ab. matches 'aba', 'abb', 'abz' |
? |
Repeat the preceding character zero or one times | abc? matches 'ab' and 'abc' |
+ |
Repeat the preceding character one or more times | ab+ matches 'ab', 'abb', 'abbb' |
* |
Repeat the preceding character zero or more times | ab* matches 'a', 'ab', 'abb', 'abbb' |
{} |
Minimum and maximum number of times the preceding character can repeat | a{2} matches 'aa'a{2,5} matches 'aa', 'aaa' and 'aaaa'a{2,} matches 'a' repeated two or more times |
| |
OR operator. The match will succeed if the longest pattern on either the left side OR the right side matches | abc|xyz matches 'abc' and 'xyz' |
(...) |
Forms a group. You can use a group to treat part of the expression as a single character | abc(def)? matches 'abc' and 'abcdef' but not 'abcd' |
[...] |
Match one of the character in the brackets Inside the brackets, - indicates a range unless - is the first character or escapedA ^ before a character in the brackets negates the character or range |
[abc] matches 'a', 'b', 'c'[-abc] matches '-', 'a', 'b', 'c'[^abc] matches any character except 'a', 'b', or 'c' |
Info
Some characters are reserved as operators: . ? + * | { } [ ] ( ) " \ .
Escape reserved operators with a preceding backslash \ or surround them with double quotes "".\@ renders as a literal '@'.\\ renders as a literal '\'."john@smith.com" renders as 'john@smith.com'.
Find events where file.name contains '.sh'
let StartTime = ago(1h);
let EndTime = now();
events
| where timestamp between (StartTime .. EndTime)
| where file.name matches regex '.*\.sh'
| limit 100
Variables
Use the let operator to define variables.
let <variable name> = <string | integer>;
<table name>
| where <column name> == <variable name>
Count the number of events in the last 24 hours
let StartTime = ago(24h);
let EndTime = now();
events
| where event.created > StartTime and event.created <= EndTime
| count
Comments
Use // to add comments in the query.
Example
// Comment the filtering condition
<table name>
//| where <column name> = <variable name>
| limit 100
Related articles
Getting Started & Overview
- SOL Overview: Sekoia Operating Language overview.
- SOL Getting Started: This tutorial walks you through writing your first SOL queries. By the end, you'll be able to search events, filter results, and save queries for reuse.
- SOL Best Practices: Best practices to use SOL effectively.
User Guides
- Create and Manage Queries: Create and manage queries using SOL.
- SOL How-to Guides: Learn how to use the main functions of SOL to reach your goals (aggregate data, join tables, use external data, build a query library...).
- SOL Query Examples: Get inspiration from our examples.
- SOL Datasets: Discover the CSV import feature that enables SOC analysts to enrich security investigations by importing external data sources directly into the SOL query environment.
Technical Reference
- SOL Data Sources Reference: Technical references to access security data within the Sekoia platform thanks to SOL.
- SOL Functions Reference: Reference article regarding functions used in SOL.