EQL search

edit

This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.

Event Query Language (EQL) is a query language for event-based time series data, such as logs, metrics, and traces.

Advantages of EQL

edit
  • EQL lets you express relationships between events.
    Many query languages allow you to match single events. EQL lets you match a sequence of events across different event categories and time spans.
  • EQL has a low learning curve.
    EQL syntax looks like other common query languages, such as SQL. EQL lets you write and read queries intuitively, which makes for quick, iterative searching.
  • EQL is designed for security use cases.
    While you can use it for any event-based data, we created EQL for threat hunting. EQL not only supports indicator of compromise (IOC) searches but can describe activity that goes beyond IOCs.

Required fields

edit

To run an EQL search, the searched data stream or index must contain a timestamp and event category field. By default, EQL uses the @timestamp and event.category fields from the Elastic Common Schema (ECS). To use a different timestamp or event category field, see Specify a timestamp or event category field.

While no schema is required to use EQL, we recommend using the ECS. EQL searches are designed to work with core ECS fields by default.

Run an EQL search

edit

Use the EQL search API to run a basic EQL query:

GET /my-index-000001/_eql/search
{
  "query": """
    process where process.name == "regsvr32.exe"
  """
}

By default, basic EQL queries return the top 10 matching events in the hits.events property. These hits are sorted by timestamp, converted to milliseconds since the Unix epoch, in ascending order.

{
  "is_partial": false,
  "is_running": false,
  "took": 60,
  "timed_out": false,
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "events": [
      {
        "_index": "my-index-000001",
        "_type": "_doc",
        "_id": "OQmfCaduce8zoHT93o4H",
        "_score": null,
        "_source": {
          "@timestamp": "2099-12-07T11:07:09.000Z",
          "event": {
            "category": "process",
            "id": "aR3NWVOs",
            "sequence": 4
          },
          "process": {
            "pid": 2012,
            "name": "regsvr32.exe",
            "command_line": "regsvr32.exe  /s /u /i:https://...RegSvr32.sct scrobj.dll",
            "executable": "C:\\Windows\\System32\\regsvr32.exe"
          }
        }
      },
      {
        "_index": "my-index-000001",
        "_type": "_doc",
        "_id": "xLkCaj4EujzdNSxfYLbO",
        "_score": null,
        "_source": {
          "@timestamp": "2099-12-07T11:07:10.000Z",
          "event": {
            "category": "process",
            "id": "GTSmSqgz0U",
            "sequence": 6,
            "type": "termination"
          },
          "process": {
            "pid": 2012,
            "name": "regsvr32.exe",
            "executable": "C:\\Windows\\System32\\regsvr32.exe"
          }
        }
      }
    ]
  }
}

Use the size parameter to get a smaller or larger set of hits:

GET /my-index-000001/_eql/search
{
  "query": """
    process where process.name == "regsvr32.exe"
  """,
  "size": 50
}

Search for a sequence of events

edit

Use EQL’s sequence syntax to search for a series of ordered events. List the event items in ascending chronological order, with the most recent event listed last:

GET /my-index-000001/_eql/search
{
  "query": """
    sequence
      [ process where process.name == "regsvr32.exe" ]
      [ file where stringContains(file.name, "scrobj.dll") ]
  """
}

Matching sequences are returned in the hits.sequences property.

{
  "is_partial": false,
  "is_running": false,
  "took": 60,
  "timed_out": false,
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "sequences": [
      {
        "events": [
          {
            "_index": "my-index-000001",
            "_type": "_doc",
            "_id": "OQmfCaduce8zoHT93o4H",
            "_version": 1,
            "_seq_no": 3,
            "_primary_term": 1,
            "_score": null,
            "_source": {
              "@timestamp": "2099-12-07T11:07:09.000Z",
              "event": {
                "category": "process",
                "id": "aR3NWVOs",
                "sequence": 4
              },
              "process": {
                "pid": 2012,
                "name": "regsvr32.exe",
                "command_line": "regsvr32.exe  /s /u /i:https://...RegSvr32.sct scrobj.dll",
                "executable": "C:\\Windows\\System32\\regsvr32.exe"
              }
            }
          },
          {
            "_index": "my-index-000001",
            "_type": "_doc",
            "_id": "yDwnGIJouOYGBzP0ZE9n",
            "_version": 1,
            "_seq_no": 4,
            "_primary_term": 1,
            "_score": null,
            "_source": {
              "@timestamp": "2099-12-07T11:07:10.000Z",
              "event": {
                "category": "file",
                "id": "tZ1NWVOs",
                "sequence": 5
              },
              "process": {
                "pid": 2012,
                "name": "regsvr32.exe",
                "executable": "C:\\Windows\\System32\\regsvr32.exe"
              },
              "file": {
                "path": "C:\\Windows\\System32\\scrobj.dll",
                "name": "scrobj.dll"
              }
            }
          }
        ]
      }
    ]
  }
}

Use the with maxspan keywords to constrain matching sequences to a timespan:

GET /my-index-000001/_eql/search
{
  "query": """
    sequence with maxspan=1h
      [ process where process.name == "regsvr32.exe" ]
      [ file where stringContains(file.name, "scrobj.dll") ]
  """
}

Use the by keyword to match events that share the same field values:

GET /my-index-000001/_eql/search
{
  "query": """
    sequence with maxspan=1h
      [ process where process.name == "regsvr32.exe" ] by process.pid
      [ file where stringContains(file.name, "scrobj.dll") ] by process.pid
  """
}

If a field value should be shared across all events, use the sequence by keyword. The following query is equivalent to the previous one.

GET /my-index-000001/_eql/search
{
  "query": """
    sequence by process.pid with maxspan=1h
      [ process where process.name == "regsvr32.exe" ]
      [ file where stringContains(file.name, "scrobj.dll") ]
  """
}

The hits.sequences.join_keys property contains the shared field values.

{
  "is_partial": false,
  "is_running": false,
  "took": 60,
  "timed_out": false,
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "sequences": [
      {
        "join_keys": [
          2012
        ],
        "events": [
          {
            "_index": "my-index-000001",
            "_type": "_doc",
            "_id": "OQmfCaduce8zoHT93o4H",
            "_version": 1,
            "_seq_no": 3,
            "_primary_term": 1,
            "_score": null,
            "_source": {
              "@timestamp": "2099-12-07T11:07:09.000Z",
              "event": {
                "category": "process",
                "id": "aR3NWVOs",
                "sequence": 4
              },
              "process": {
                "pid": 2012,
                "name": "regsvr32.exe",
                "command_line": "regsvr32.exe  /s /u /i:https://...RegSvr32.sct scrobj.dll",
                "executable": "C:\\Windows\\System32\\regsvr32.exe"
              }
            }
          },
          {
            "_index": "my-index-000001",
            "_type": "_doc",
            "_id": "yDwnGIJouOYGBzP0ZE9n",
            "_version": 1,
            "_seq_no": 4,
            "_primary_term": 1,
            "_score": null,
            "_source": {
              "@timestamp": "2099-12-07T11:07:10.000Z",
              "event": {
                "category": "file",
                "id": "tZ1NWVOs",
                "sequence": 5
              },
              "process": {
                "pid": 2012,
                "name": "regsvr32.exe",
                "executable": "C:\\Windows\\System32\\regsvr32.exe"
              },
              "file": {
                "path": "C:\\Windows\\System32\\scrobj.dll",
                "name": "scrobj.dll"
              }
            }
          }
        ]
      }
    ]
  }
}

Use the until keyword to specify an expiration event for sequences. Matching sequences must end before this event.

GET /my-index-000001/_eql/search
{
  "query": """
    sequence by process.pid with maxspan=1h
      [ process where process.name == "regsvr32.exe" ]
      [ file where stringContains(file.name, "scrobj.dll") ]
    until [ process where event.type == "termination" ]
  """
}

Specify a timestamp or event category field

edit

The EQL search API uses the @timestamp and event.category fields from the ECS by default. To specify different fields, use the timestamp_field and event_category_field parameters:

GET /my-index-000001/_eql/search
{
  "timestamp_field": "file.accessed",
  "event_category_field": "file.type",
  "query": """
    file where (file.size > 1 and file.type == "file")
  """
}

The event category field must be mapped as a keyword family field type. The timestamp field should be mapped as a date field type. date_nanos timestamp fields are not supported. You cannot use a nested field or the sub-fields of a nested field as the timestamp or event category field.

Specify a sort tiebreaker

edit

By default, the EQL search API returns matching hits by timestamp. If two or more events share the same timestamp, Elasticsearch uses a tiebreaker field value to sort the events in ascending, lexicographic order.

If you don’t specify a tiebreaker field or the events also share the same tiebreaker value, Elasticsearch considers the events concurrent. Concurrent events cannot be part of the same sequence and may not be returned in a consistent sort order.

To specify a tiebreaker field, use the tiebreaker_field parameter. If you specify a tiebreaker field for a sequence query, all events in the searched data streams or indices must contain a tiebreaker field value. For basic queries, Elasticsearch orders matching events with no tiebreaker value after events with a tiebreaker value.

If you use the ECS, we recommend using event.sequence as the tiebreaker field.

GET /my-index-000001/_eql/search
{
  "tiebreaker_field": "event.sequence",
  "query": """
    process where process.name == "cmd.exe" and stringContains(process.executable, "System32")
  """
}

Filter using query DSL

edit

The filter parameter uses query DSL to limit the documents on which an EQL query runs.

GET /my-index-000001/_eql/search
{
  "filter": {
    "range" : {
      "file.size" : {
        "gte" : 1,
        "lte" : 1000000
      }
    }
  },
  "query": """
    file where (file.type == "file" and file.name == "cmd.exe")
  """
}

Run a case-sensitive EQL search

edit

By default, matching for EQL queries is case-insensitive. You can use the case_sensitive parameter to toggle case sensitivity on or off.

The following search request contains a query that matches process events with a process.executable containing System32.

Because case_sensitive is true, this query only matches process.executable values containing System32 with the exact same capitalization. A process.executable value containing system32 or SYSTEM32 would not match this query.

GET /my-index-000001/_eql/search
{
  "keep_on_completion": true,
  "case_sensitive": true,
  "query": """
    process where stringContains(process.executable, "System32")
  """
}

Run an async EQL search

edit

By default, EQL search requests are synchronous and wait for complete results before returning a response. However, complete results can take longer for searches across large data sets or frozen indices.

To avoid long waits, run an async EQL search. Set the wait_for_completion_timeout parameter to a duration you’d like to wait for synchronous results.

GET /frozen-my-index-000001/_eql/search
{
  "wait_for_completion_timeout": "2s",
  "query": """
    process where process.name == "cmd.exe"
  """
}

If the request doesn’t finish within the timeout period, the search becomes async and returns a response that includes:

  • A search ID
  • An is_partial value of true, indicating the search results are incomplete
  • An is_running value of true, indicating the search is ongoing

The async search continues to run in the background without blocking other requests.

{
  "id": "FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=",
  "is_partial": true,
  "is_running": true,
  "took": 2000,
  "timed_out": false,
  "hits": ...
}

To check the progress of an async search, use the get async EQL search API with the search ID. Specify how long you’d like for complete results in the wait_for_completion_timeout parameter.

GET /_eql/search/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=?wait_for_completion_timeout=2s

If the response’s is_running value is false, the async search has finished. If the is_partial value is false, the returned search results are complete.

{
  "id": "FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=",
  "is_partial": false,
  "is_running": false,
  "took": 2000,
  "timed_out": false,
  "hits": ...
}

Change the search retention period

edit

By default, the EQL search API stores async searches for five days. After this period, any searches and their results are deleted. Use the keep_alive parameter to change this retention period:

GET /my-index-000001/_eql/search
{
  "keep_alive": "2d",
  "wait_for_completion_timeout": "2s",
  "query": """
    process where process.name == "cmd.exe"
  """
}

You can use the get async EQL search API's keep_alive parameter to later change the retention period. The new retention period starts after the get request runs.

GET /_eql/search/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=?keep_alive=5d

Use the delete async EQL search API to manually delete an async EQL search before the keep_alive period ends. If the search is still ongoing, Elasticsearch cancels the search request.

DELETE /_eql/search/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=

Store synchronous EQL searches

edit

By default, the EQL search API only stores async searches. To save a synchronous search, set keep_on_completion to true:

GET /my-index-000001/_eql/search
{
  "keep_on_completion": true,
  "wait_for_completion_timeout": "2s",
  "query": """
    process where process.name == "cmd.exe"
  """
}

The response includes a search ID. is_partial and is_running are false, indicating the EQL search was synchronous and returned complete results.

{
  "id": "FjlmbndxNmJjU0RPdExBTGg0elNOOEEaQk9xSjJBQzBRMldZa1VVQ2pPa01YUToxMDY=",
  "is_partial": false,
  "is_running": false,
  "took": 52,
  "timed_out": false,
  "hits": ...
}

Use the get async EQL search API to get the same results later:

GET /_eql/search/FjlmbndxNmJjU0RPdExBTGg0elNOOEEaQk9xSjJBQzBRMldZa1VVQ2pPa01YUToxMDY=

Saved synchronous searches are still subject to the keep_alive parameter’s retention period. When this period ends, the search and its results are deleted.

You can also manually delete saved synchronous searches using the delete async EQL search API.