diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-console.md new file mode 100644 index 0000000000..5feba57fc0 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-console.md @@ -0,0 +1,38 @@ +```console +PUT /windows-security-logs +{ + "mappings": { + "properties": { + "@timestamp": {"type": "date"}, + "event": { + "properties": { + "code": {"type": "keyword"}, # Event codes like 4624 (successful logon) and 4625 (failed logon) are stored as keywords for exact matching. + "action": {"type": "keyword"} + } + }, + "user": { + "properties": { + "name": {"type": "keyword"}, + "domain": {"type": "keyword"} + } + }, + "host": { + "properties": { + "name": {"type": "keyword"}, + "ip": {"type": "ip"} + } + }, + "source": { + "properties": { + "ip": {"type": "ip"} + } + }, + "logon": { + "properties": { + "type": {"type": "keyword"} + } + } + } + } +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-curl.md new file mode 100644 index 0000000000..92d3e9df8e --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X PUT "$ELASTICSEARCH_URL/windows-security-logs" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"mappings":{"properties":{"@timestamp":{"type":"date"},"event":{"properties":{"code":{"type":"keyword"},"action":{"type":"keyword"}}},"user":{"properties":{"name":{"type":"keyword"},"domain":{"type":"keyword"}}},"host":{"properties":{"name":{"type":"keyword"},"ip":{"type":"ip"}}},"source":{"properties":{"ip":{"type":"ip"}}},"logon":{"properties":{"type":{"type":"keyword"}}}}}}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-js.md new file mode 100644 index 0000000000..6dd9dfeeb7 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-js.md @@ -0,0 +1,70 @@ +```js +const { Client } = require("@elastic/elasticsearch"); + +const client = new Client({ + nodes: [process.env["ELASTICSEARCH_URL"]], + auth: { + apiKey: process.env["ELASTIC_API_KEY"], + }, +}); + +async function run() { + const response = await client.indices.create({ + index: "windows-security-logs", + mappings: { + properties: { + "@timestamp": { + type: "date", + }, + event: { + properties: { + code: { + type: "keyword", + }, + action: { + type: "keyword", + }, + }, + }, + user: { + properties: { + name: { + type: "keyword", + }, + domain: { + type: "keyword", + }, + }, + }, + host: { + properties: { + name: { + type: "keyword", + }, + ip: { + type: "ip", + }, + }, + }, + source: { + properties: { + ip: { + type: "ip", + }, + }, + }, + logon: { + properties: { + type: { + type: "keyword", + }, + }, + }, + }, + }, + }); + console.log(response); +} + +run(); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-php.md new file mode 100644 index 0000000000..cd068b5d27 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-php.md @@ -0,0 +1,71 @@ +```php +setHosts([getenv("ELASTICSEARCH_URL")]) + ->setApiKey(getenv("ELASTIC_API_KEY")) + ->build(); + +$resp = $client->indices()->create([ + "index" => "windows-security-logs", + "body" => [ + "mappings" => [ + "properties" => [ + "@timestamp" => [ + "type" => "date", + ], + "event" => [ + "properties" => [ + "code" => [ + "type" => "keyword", + ], + "action" => [ + "type" => "keyword", + ], + ], + ], + "user" => [ + "properties" => [ + "name" => [ + "type" => "keyword", + ], + "domain" => [ + "type" => "keyword", + ], + ], + ], + "host" => [ + "properties" => [ + "name" => [ + "type" => "keyword", + ], + "ip" => [ + "type" => "ip", + ], + ], + ], + "source" => [ + "properties" => [ + "ip" => [ + "type" => "ip", + ], + ], + ], + "logon" => [ + "properties" => [ + "type" => [ + "type" => "keyword", + ], + ], + ], + ], + ], + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-python.md new file mode 100644 index 0000000000..ec56240530 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-python.md @@ -0,0 +1,66 @@ +```python +import os +from elasticsearch import Elasticsearch + +client = Elasticsearch( + hosts=[os.getenv("ELASTICSEARCH_URL")], + api_key=os.getenv("ELASTIC_API_KEY"), +) + +resp = client.indices.create( + index="windows-security-logs", + mappings={ + "properties": { + "@timestamp": { + "type": "date" + }, + "event": { + "properties": { + "code": { + "type": "keyword" + }, + "action": { + "type": "keyword" + } + } + }, + "user": { + "properties": { + "name": { + "type": "keyword" + }, + "domain": { + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "type": "keyword" + }, + "ip": { + "type": "ip" + } + } + }, + "source": { + "properties": { + "ip": { + "type": "ip" + } + } + }, + "logon": { + "properties": { + "type": { + "type": "keyword" + } + } + } + } + }, +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-ruby.md new file mode 100644 index 0000000000..d70f3e4fe5 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example1-ruby.md @@ -0,0 +1,67 @@ +```ruby +require "elasticsearch" + +client = Elasticsearch::Client.new( + host: ENV["ELASTICSEARCH_URL"], + api_key: ENV["ELASTIC_API_KEY"] +) + +response = client.indices.create( + index: "windows-security-logs", + body: { + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "event": { + "properties": { + "code": { + "type": "keyword" + }, + "action": { + "type": "keyword" + } + } + }, + "user": { + "properties": { + "name": { + "type": "keyword" + }, + "domain": { + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "type": "keyword" + }, + "ip": { + "type": "ip" + } + } + }, + "source": { + "properties": { + "ip": { + "type": "ip" + } + } + }, + "logon": { + "properties": { + "type": { + "type": "keyword" + } + } + } + } + } + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-console.md new file mode 100644 index 0000000000..c664f1adfc --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-console.md @@ -0,0 +1,19 @@ +```console +POST /_bulk?refresh=wait_for +{"index":{"_index":"asset-inventory"}} +{"host.name":"WS-001","asset.criticality":"medium","asset.owner":"IT","asset.department":"finance"} +{"index":{"_index":"asset-inventory"}} +{"host.name":"SRV-001","asset.criticality":"high","asset.owner":"IT","asset.department":"operations"} +{"index":{"_index":"asset-inventory"}} +{"host.name":"DB-001","asset.criticality":"critical","asset.owner":"DBA","asset.department":"finance"} +{"index":{"_index":"asset-inventory"}} +{"host.name":"DC-001","asset.criticality":"critical","asset.owner":"IT","asset.department":"infrastructure"} +{"index":{"_index":"user-context"}} +{"user.name":"jsmith","user.role":"analyst","user.department":"finance","user.privileged":false} +{"index":{"_index":"user-context"}} +{"user.name":"admin","user.role":"administrator","user.department":"IT","user.privileged":true} +{"index":{"_index":"threat-intel"}} +{"indicator.value":"185.220.101.45","indicator.type":"ip","threat.name":"APT-29","threat.severity":"high"} +{"index":{"_index":"threat-intel"}} +{"indicator.value":"powershell.exe","indicator.type":"process","threat.name":"Living off the Land","threat.severity":"medium"} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-curl.md new file mode 100644 index 0000000000..cc00219ee8 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X POST "$ELASTICSEARCH_URL/_bulk?refresh=wait_for" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/x-ndjson" \ + -d $'{"index":{"_index":"asset-inventory"}}\n{"host.name":"WS-001","asset.criticality":"medium","asset.owner":"IT","asset.department":"finance"}\n{"index":{"_index":"asset-inventory"}}\n{"host.name":"SRV-001","asset.criticality":"high","asset.owner":"IT","asset.department":"operations"}\n{"index":{"_index":"asset-inventory"}}\n{"host.name":"DB-001","asset.criticality":"critical","asset.owner":"DBA","asset.department":"finance"}\n{"index":{"_index":"asset-inventory"}}\n{"host.name":"DC-001","asset.criticality":"critical","asset.owner":"IT","asset.department":"infrastructure"}\n{"index":{"_index":"user-context"}}\n{"user.name":"jsmith","user.role":"analyst","user.department":"finance","user.privileged":false}\n{"index":{"_index":"user-context"}}\n{"user.name":"admin","user.role":"administrator","user.department":"IT","user.privileged":true}\n{"index":{"_index":"threat-intel"}}\n{"indicator.value":"185.220.101.45","indicator.type":"ip","threat.name":"APT-29","threat.severity":"high"}\n{"index":{"_index":"threat-intel"}}\n{"indicator.value":"powershell.exe","indicator.type":"process","threat.name":"Living off the Land","threat.severity":"medium"}\n' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-js.md new file mode 100644 index 0000000000..b579ebce7c --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-js.md @@ -0,0 +1,96 @@ +```js +const response = await client.bulk({ + refresh: "wait_for", + operations: [ + { + index: { + _index: "asset-inventory", + }, + }, + { + "host.name": "WS-001", + "asset.criticality": "medium", + "asset.owner": "IT", + "asset.department": "finance", + }, + { + index: { + _index: "asset-inventory", + }, + }, + { + "host.name": "SRV-001", + "asset.criticality": "high", + "asset.owner": "IT", + "asset.department": "operations", + }, + { + index: { + _index: "asset-inventory", + }, + }, + { + "host.name": "DB-001", + "asset.criticality": "critical", + "asset.owner": "DBA", + "asset.department": "finance", + }, + { + index: { + _index: "asset-inventory", + }, + }, + { + "host.name": "DC-001", + "asset.criticality": "critical", + "asset.owner": "IT", + "asset.department": "infrastructure", + }, + { + index: { + _index: "user-context", + }, + }, + { + "user.name": "jsmith", + "user.role": "analyst", + "user.department": "finance", + "user.privileged": false, + }, + { + index: { + _index: "user-context", + }, + }, + { + "user.name": "admin", + "user.role": "administrator", + "user.department": "IT", + "user.privileged": true, + }, + { + index: { + _index: "threat-intel", + }, + }, + { + "indicator.value": "185.220.101.45", + "indicator.type": "ip", + "threat.name": "APT-29", + "threat.severity": "high", + }, + { + index: { + _index: "threat-intel", + }, + }, + { + "indicator.value": "powershell.exe", + "indicator.type": "process", + "threat.name": "Living off the Land", + "threat.severity": "medium", + }, + ], +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-php.md new file mode 100644 index 0000000000..70efd3c0cc --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-php.md @@ -0,0 +1,97 @@ +```php +$resp = $client->bulk([ + "refresh" => "wait_for", + "body" => array( + [ + "index" => [ + "_index" => "asset-inventory", + ], + ], + [ + "host.name" => "WS-001", + "asset.criticality" => "medium", + "asset.owner" => "IT", + "asset.department" => "finance", + ], + [ + "index" => [ + "_index" => "asset-inventory", + ], + ], + [ + "host.name" => "SRV-001", + "asset.criticality" => "high", + "asset.owner" => "IT", + "asset.department" => "operations", + ], + [ + "index" => [ + "_index" => "asset-inventory", + ], + ], + [ + "host.name" => "DB-001", + "asset.criticality" => "critical", + "asset.owner" => "DBA", + "asset.department" => "finance", + ], + [ + "index" => [ + "_index" => "asset-inventory", + ], + ], + [ + "host.name" => "DC-001", + "asset.criticality" => "critical", + "asset.owner" => "IT", + "asset.department" => "infrastructure", + ], + [ + "index" => [ + "_index" => "user-context", + ], + ], + [ + "user.name" => "jsmith", + "user.role" => "analyst", + "user.department" => "finance", + "user.privileged" => false, + ], + [ + "index" => [ + "_index" => "user-context", + ], + ], + [ + "user.name" => "admin", + "user.role" => "administrator", + "user.department" => "IT", + "user.privileged" => true, + ], + [ + "index" => [ + "_index" => "threat-intel", + ], + ], + [ + "indicator.value" => "185.220.101.45", + "indicator.type" => "ip", + "threat.name" => "APT-29", + "threat.severity" => "high", + ], + [ + "index" => [ + "_index" => "threat-intel", + ], + ], + [ + "indicator.value" => "powershell.exe", + "indicator.type" => "process", + "threat.name" => "Living off the Land", + "threat.severity" => "medium", + ], + ), +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-python.md new file mode 100644 index 0000000000..0f19d80339 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-python.md @@ -0,0 +1,97 @@ +```python +resp = client.bulk( + refresh="wait_for", + operations=[ + { + "index": { + "_index": "asset-inventory" + } + }, + { + "host.name": "WS-001", + "asset.criticality": "medium", + "asset.owner": "IT", + "asset.department": "finance" + }, + { + "index": { + "_index": "asset-inventory" + } + }, + { + "host.name": "SRV-001", + "asset.criticality": "high", + "asset.owner": "IT", + "asset.department": "operations" + }, + { + "index": { + "_index": "asset-inventory" + } + }, + { + "host.name": "DB-001", + "asset.criticality": "critical", + "asset.owner": "DBA", + "asset.department": "finance" + }, + { + "index": { + "_index": "asset-inventory" + } + }, + { + "host.name": "DC-001", + "asset.criticality": "critical", + "asset.owner": "IT", + "asset.department": "infrastructure" + }, + { + "index": { + "_index": "user-context" + } + }, + { + "user.name": "jsmith", + "user.role": "analyst", + "user.department": "finance", + "user.privileged": False + }, + { + "index": { + "_index": "user-context" + } + }, + { + "user.name": "admin", + "user.role": "administrator", + "user.department": "IT", + "user.privileged": True + }, + { + "index": { + "_index": "threat-intel" + } + }, + { + "indicator.value": "185.220.101.45", + "indicator.type": "ip", + "threat.name": "APT-29", + "threat.severity": "high" + }, + { + "index": { + "_index": "threat-intel" + } + }, + { + "indicator.value": "powershell.exe", + "indicator.type": "process", + "threat.name": "Living off the Land", + "threat.severity": "medium" + } + ], +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-ruby.md new file mode 100644 index 0000000000..ee21ca9f81 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example10-ruby.md @@ -0,0 +1,97 @@ +```ruby +response = client.bulk( + refresh: "wait_for", + body: [ + { + "index": { + "_index": "asset-inventory" + } + }, + { + "host.name": "WS-001", + "asset.criticality": "medium", + "asset.owner": "IT", + "asset.department": "finance" + }, + { + "index": { + "_index": "asset-inventory" + } + }, + { + "host.name": "SRV-001", + "asset.criticality": "high", + "asset.owner": "IT", + "asset.department": "operations" + }, + { + "index": { + "_index": "asset-inventory" + } + }, + { + "host.name": "DB-001", + "asset.criticality": "critical", + "asset.owner": "DBA", + "asset.department": "finance" + }, + { + "index": { + "_index": "asset-inventory" + } + }, + { + "host.name": "DC-001", + "asset.criticality": "critical", + "asset.owner": "IT", + "asset.department": "infrastructure" + }, + { + "index": { + "_index": "user-context" + } + }, + { + "user.name": "jsmith", + "user.role": "analyst", + "user.department": "finance", + "user.privileged": false + }, + { + "index": { + "_index": "user-context" + } + }, + { + "user.name": "admin", + "user.role": "administrator", + "user.department": "IT", + "user.privileged": true + }, + { + "index": { + "_index": "threat-intel" + } + }, + { + "indicator.value": "185.220.101.45", + "indicator.type": "ip", + "threat.name": "APT-29", + "threat.severity": "high" + }, + { + "index": { + "_index": "threat-intel" + } + }, + { + "indicator.value": "powershell.exe", + "indicator.type": "process", + "threat.name": "Living off the Land", + "threat.severity": "medium" + } + ] +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-console.md new file mode 100644 index 0000000000..9b232eef1b --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-console.md @@ -0,0 +1,15 @@ +```console +POST /_query?format=txt +{ + "query": """ +FROM process-logs +| WHERE process.name == "powershell.exe" AND process.parent.name LIKE "*word*" +| LOOKUP JOIN asset-inventory ON host.name +| LOOKUP JOIN user-context ON user.name +| EVAL encoded_command = CASE(process.command_line LIKE "*-enc*", true, false) +| WHERE encoded_command == true +| STATS count = COUNT(*) BY host.name, user.name, asset.criticality +| LIMIT 1000 + """ +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-curl.md new file mode 100644 index 0000000000..da6fd9da30 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X POST "$ELASTICSEARCH_URL/_query?format=txt" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"query":"\nFROM process-logs\n| WHERE process.name == \"powershell.exe\" AND process.parent.name LIKE \"*word*\"\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| EVAL encoded_command = CASE(process.command_line LIKE \"*-enc*\", true, false)\n| WHERE encoded_command == true\n| STATS count = COUNT(*) BY host.name, user.name, asset.criticality\n| LIMIT 1000\n "}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-esql.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-esql.md new file mode 100644 index 0000000000..7136e89eb1 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-esql.md @@ -0,0 +1,17 @@ +```esql +FROM process-logs +| WHERE process.name == "powershell.exe" AND process.parent.name LIKE "*word*" <1> +| LOOKUP JOIN asset-inventory ON host.name <2> +| LOOKUP JOIN user-context ON user.name <3> +| EVAL encoded_command = CASE(process.command_line LIKE "*-enc*", true, false) <4> +| WHERE encoded_command == true <5> +| STATS count = COUNT(*) BY host.name, user.name, asset.criticality <6> +| LIMIT 1000 +``` + +1. Uses [`WHERE`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-where) with [`==`](elasticsearch://reference/query-languages/esql/functions-operators/operators.md#esql-equals) and [`LIKE`](elasticsearch://reference/query-languages/esql/functions-operators/operators.md#esql-like) operators to detect PowerShell processes +2. Enriches using [`LOOKUP JOIN`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-lookup-join) with asset inventory +3. Enriches with user context using `LOOKUP JOIN` +4. Uses [`EVAL`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-eval) and [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case) to detect encoded commands +5. Additional filtering with [`WHERE`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-where) +6. Aggregates results with [`STATS`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-stats-by) and [`COUNT`](elasticsearch://reference/query-languages/esql/functions-operators/aggregation-functions.md#esql-count) grouped by multiple fields diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-js.md new file mode 100644 index 0000000000..c03ce37a33 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-js.md @@ -0,0 +1,8 @@ +```js +const response = await client.esql.query({ + format: "txt", + query: + '\nFROM process-logs\n| WHERE process.name == "powershell.exe" AND process.parent.name LIKE "*word*"\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| EVAL encoded_command = CASE(process.command_line LIKE "*-enc*", true, false)\n| WHERE encoded_command == true\n| STATS count = COUNT(*) BY host.name, user.name, asset.criticality\n| LIMIT 1000\n ', +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-php.md new file mode 100644 index 0000000000..3b4e712b3c --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-php.md @@ -0,0 +1,10 @@ +```php +$resp = $client->esql()->query([ + "format" => "txt", + "body" => [ + "query" => "\nFROM process-logs\n| WHERE process.name == \"powershell.exe\" AND process.parent.name LIKE \"*word*\"\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| EVAL encoded_command = CASE(process.command_line LIKE \"*-enc*\", true, false)\n| WHERE encoded_command == true\n| STATS count = COUNT(*) BY host.name, user.name, asset.criticality\n| LIMIT 1000\n ", + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-python.md new file mode 100644 index 0000000000..6358a50773 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-python.md @@ -0,0 +1,8 @@ +```python +resp = client.esql.query( + format="txt", + query="\nFROM process-logs\n| WHERE process.name == \"powershell.exe\" AND process.parent.name LIKE \"*word*\"\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| EVAL encoded_command = CASE(process.command_line LIKE \"*-enc*\", true, false)\n| WHERE encoded_command == true\n| STATS count = COUNT(*) BY host.name, user.name, asset.criticality\n| LIMIT 1000\n ", +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-ruby.md new file mode 100644 index 0000000000..4270344037 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example11-ruby.md @@ -0,0 +1,10 @@ +```ruby +response = client.esql.query( + format: "txt", + body: { + "query": "\nFROM process-logs\n| WHERE process.name == \"powershell.exe\" AND process.parent.name LIKE \"*word*\"\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| EVAL encoded_command = CASE(process.command_line LIKE \"*-enc*\", true, false)\n| WHERE encoded_command == true\n| STATS count = COUNT(*) BY host.name, user.name, asset.criticality\n| LIMIT 1000\n " + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-console.md new file mode 100644 index 0000000000..1e4e96afc2 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-console.md @@ -0,0 +1,23 @@ +```console +POST /_query?format=txt +{ + "query": """ +FROM windows-security-logs +| WHERE event.code == "4624" AND logon.type == "3" +| LOOKUP JOIN asset-inventory ON host.name +| EVAL time_bucket = DATE_TRUNC(30 minute, @timestamp) +| STATS unique_hosts = COUNT_DISTINCT(host.name), + criticality_levels = COUNT_DISTINCT(asset.criticality), + active_periods = COUNT_DISTINCT(time_bucket), + first_login = MIN(@timestamp), + last_login = MAX(@timestamp) +BY user.name +| WHERE unique_hosts > 2 +| EVAL time_span_hours = DATE_DIFF("hour", first_login, last_login) +| EVAL movement_velocity = ROUND(unique_hosts / (time_span_hours + 1), 2) +| EVAL lateral_movement_score = unique_hosts * criticality_levels +| SORT lateral_movement_score DESC +| LIMIT 1000 + """ +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-curl.md new file mode 100644 index 0000000000..76c873adc8 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X POST "$ELASTICSEARCH_URL/_query?format=txt" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"query":"\nFROM windows-security-logs\n| WHERE event.code == \"4624\" AND logon.type == \"3\"\n| LOOKUP JOIN asset-inventory ON host.name\n| EVAL time_bucket = DATE_TRUNC(30 minute, @timestamp)\n| STATS unique_hosts = COUNT_DISTINCT(host.name),\n criticality_levels = COUNT_DISTINCT(asset.criticality),\n active_periods = COUNT_DISTINCT(time_bucket),\n first_login = MIN(@timestamp),\n last_login = MAX(@timestamp) \nBY user.name\n| WHERE unique_hosts > 2\n| EVAL time_span_hours = DATE_DIFF(\"hour\", first_login, last_login)\n| EVAL movement_velocity = ROUND(unique_hosts / (time_span_hours + 1), 2)\n| EVAL lateral_movement_score = unique_hosts * criticality_levels\n| SORT lateral_movement_score DESC \n| LIMIT 1000\n "}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-esql.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-esql.md new file mode 100644 index 0000000000..7c9e43411d --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-esql.md @@ -0,0 +1,24 @@ +```esql +FROM windows-security-logs +| WHERE event.code == "4624" AND logon.type == "3" <1> +| LOOKUP JOIN asset-inventory ON host.name +| EVAL time_bucket = DATE_TRUNC(30 minute, @timestamp) <2> +| STATS unique_hosts = COUNT_DISTINCT(host.name), + criticality_levels = COUNT_DISTINCT(asset.criticality), + active_periods = COUNT_DISTINCT(time_bucket), + first_login = MIN(@timestamp), + last_login = MAX(@timestamp) +BY user.name <3> +| WHERE unique_hosts > 2 +| EVAL time_span_hours = DATE_DIFF("hour", first_login, last_login) <4> +| EVAL movement_velocity = ROUND(unique_hosts / (time_span_hours + 1), 2) +| EVAL lateral_movement_score = unique_hosts * criticality_levels <5> +| SORT lateral_movement_score DESC +| LIMIT 1000 +``` + +1. Uses [`WHERE`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-where) for basic authentication filtering +2. Creates time buckets with [`DATE_TRUNC`](elasticsearch://reference/query-languages/esql/functions-operators/date-time-functions.md#esql-date_trunc) for temporal analysis +3. Uses [`STATS`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-stats-by) with [`COUNT_DISTINCT`](elasticsearch://reference/query-languages/esql/functions-operators/aggregation-functions.md#esql-count_distinct) for comprehensive access metrics +4. Uses [`DATE_DIFF`](elasticsearch://reference/query-languages/esql/functions-operators/date-time-functions.md#esql-date_diff) for duration calculations +5. Uses [`EVAL`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-eval) with [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case)for risk scoring diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-js.md new file mode 100644 index 0000000000..ccf2ae4bbc --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-js.md @@ -0,0 +1,8 @@ +```js +const response = await client.esql.query({ + format: "txt", + query: + '\nFROM windows-security-logs\n| WHERE event.code == "4624" AND logon.type == "3"\n| LOOKUP JOIN asset-inventory ON host.name\n| EVAL time_bucket = DATE_TRUNC(30 minute, @timestamp)\n| STATS unique_hosts = COUNT_DISTINCT(host.name),\n criticality_levels = COUNT_DISTINCT(asset.criticality),\n active_periods = COUNT_DISTINCT(time_bucket),\n first_login = MIN(@timestamp),\n last_login = MAX(@timestamp) \nBY user.name\n| WHERE unique_hosts > 2\n| EVAL time_span_hours = DATE_DIFF("hour", first_login, last_login)\n| EVAL movement_velocity = ROUND(unique_hosts / (time_span_hours + 1), 2)\n| EVAL lateral_movement_score = unique_hosts * criticality_levels\n| SORT lateral_movement_score DESC \n| LIMIT 1000\n ', +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-php.md new file mode 100644 index 0000000000..8765543d9f --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-php.md @@ -0,0 +1,10 @@ +```php +$resp = $client->esql()->query([ + "format" => "txt", + "body" => [ + "query" => "\nFROM windows-security-logs\n| WHERE event.code == \"4624\" AND logon.type == \"3\"\n| LOOKUP JOIN asset-inventory ON host.name\n| EVAL time_bucket = DATE_TRUNC(30 minute, @timestamp)\n| STATS unique_hosts = COUNT_DISTINCT(host.name),\n criticality_levels = COUNT_DISTINCT(asset.criticality),\n active_periods = COUNT_DISTINCT(time_bucket),\n first_login = MIN(@timestamp),\n last_login = MAX(@timestamp) \nBY user.name\n| WHERE unique_hosts > 2\n| EVAL time_span_hours = DATE_DIFF(\"hour\", first_login, last_login)\n| EVAL movement_velocity = ROUND(unique_hosts / (time_span_hours + 1), 2)\n| EVAL lateral_movement_score = unique_hosts * criticality_levels\n| SORT lateral_movement_score DESC \n| LIMIT 1000\n ", + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-python.md new file mode 100644 index 0000000000..3a0f629ec0 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-python.md @@ -0,0 +1,8 @@ +```python +resp = client.esql.query( + format="txt", + query="\nFROM windows-security-logs\n| WHERE event.code == \"4624\" AND logon.type == \"3\"\n| LOOKUP JOIN asset-inventory ON host.name\n| EVAL time_bucket = DATE_TRUNC(30 minute, @timestamp)\n| STATS unique_hosts = COUNT_DISTINCT(host.name),\n criticality_levels = COUNT_DISTINCT(asset.criticality),\n active_periods = COUNT_DISTINCT(time_bucket),\n first_login = MIN(@timestamp),\n last_login = MAX(@timestamp) \nBY user.name\n| WHERE unique_hosts > 2\n| EVAL time_span_hours = DATE_DIFF(\"hour\", first_login, last_login)\n| EVAL movement_velocity = ROUND(unique_hosts / (time_span_hours + 1), 2)\n| EVAL lateral_movement_score = unique_hosts * criticality_levels\n| SORT lateral_movement_score DESC \n| LIMIT 1000\n ", +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-ruby.md new file mode 100644 index 0000000000..afeffbee7d --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example12-ruby.md @@ -0,0 +1,10 @@ +```ruby +response = client.esql.query( + format: "txt", + body: { + "query": "\nFROM windows-security-logs\n| WHERE event.code == \"4624\" AND logon.type == \"3\"\n| LOOKUP JOIN asset-inventory ON host.name\n| EVAL time_bucket = DATE_TRUNC(30 minute, @timestamp)\n| STATS unique_hosts = COUNT_DISTINCT(host.name),\n criticality_levels = COUNT_DISTINCT(asset.criticality),\n active_periods = COUNT_DISTINCT(time_bucket),\n first_login = MIN(@timestamp),\n last_login = MAX(@timestamp) \nBY user.name\n| WHERE unique_hosts > 2\n| EVAL time_span_hours = DATE_DIFF(\"hour\", first_login, last_login)\n| EVAL movement_velocity = ROUND(unique_hosts / (time_span_hours + 1), 2)\n| EVAL lateral_movement_score = unique_hosts * criticality_levels\n| SORT lateral_movement_score DESC \n| LIMIT 1000\n " + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-console.md new file mode 100644 index 0000000000..9fc6e7b3de --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-console.md @@ -0,0 +1,27 @@ +```console +POST /_query?format=txt +{ + "query": """ +FROM network-logs +| WHERE NOT CIDR_MATCH(destination.ip, "10.0.0.0/8", "192.168.0.0/16") +| EVAL indicator.value = TO_STRING(destination.ip) +| LOOKUP JOIN threat-intel ON indicator.value +| LOOKUP JOIN asset-inventory ON host.name +| WHERE threat.name IS NOT NULL +| STATS total_bytes = SUM(network.bytes), + connection_count = COUNT(*), + time_span = DATE_DIFF("hour", MIN(@timestamp), MAX(@timestamp)) +BY host.name, destination.ip, threat.name, asset.criticality +| EVAL mb_transferred = ROUND(total_bytes / 1048576, 2) +| EVAL risk_score = CASE( + asset.criticality == "critical" AND mb_transferred > 100, 10, + asset.criticality == "high" AND mb_transferred > 100, 7, + mb_transferred > 50, 5, + 3 + ) +| WHERE total_bytes > 1000000 +| SORT risk_score DESC, total_bytes DESC +| LIMIT 1000 + """ +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-curl.md new file mode 100644 index 0000000000..740e832332 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X POST "$ELASTICSEARCH_URL/_query?format=txt" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"query":"\nFROM network-logs\n| WHERE NOT CIDR_MATCH(destination.ip, \"10.0.0.0/8\", \"192.168.0.0/16\")\n| EVAL indicator.value = TO_STRING(destination.ip)\n| LOOKUP JOIN threat-intel ON indicator.value\n| LOOKUP JOIN asset-inventory ON host.name\n| WHERE threat.name IS NOT NULL\n| STATS total_bytes = SUM(network.bytes),\n connection_count = COUNT(*),\n time_span = DATE_DIFF(\"hour\", MIN(@timestamp), MAX(@timestamp))\nBY host.name, destination.ip, threat.name, asset.criticality\n| EVAL mb_transferred = ROUND(total_bytes / 1048576, 2)\n| EVAL risk_score = CASE(\n asset.criticality == \"critical\" AND mb_transferred > 100, 10,\n asset.criticality == \"high\" AND mb_transferred > 100, 7,\n mb_transferred > 50, 5,\n 3\n )\n| WHERE total_bytes > 1000000\n| SORT risk_score DESC, total_bytes DESC\n| LIMIT 1000\n "}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-esql.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-esql.md new file mode 100644 index 0000000000..6617cfa87b --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-esql.md @@ -0,0 +1,28 @@ +```esql +FROM network-logs +| WHERE NOT CIDR_MATCH(destination.ip, "10.0.0.0/8", "192.168.0.0/16") <1> +| EVAL indicator.value = TO_STRING(destination.ip) <2> +| LOOKUP JOIN threat-intel ON indicator.value +| LOOKUP JOIN asset-inventory ON host.name +| WHERE threat.name IS NOT NULL +| STATS total_bytes = SUM(network.bytes), + connection_count = COUNT(*), + time_span = DATE_DIFF("hour", MIN(@timestamp), MAX(@timestamp)) <3> +BY host.name, destination.ip, threat.name, asset.criticality +| EVAL mb_transferred = ROUND(total_bytes / 1048576, 2) <4> +| EVAL risk_score = CASE( + asset.criticality == "critical" AND mb_transferred > 100, 10, + asset.criticality == "high" AND mb_transferred > 100, 7, + mb_transferred > 50, 5, + 3 + ) <5> +| WHERE total_bytes > 1000000 +| SORT risk_score DESC, total_bytes DESC +| LIMIT 1000 +``` + +1. Uses [`CIDR_MATCH`](elasticsearch://reference/query-languages/esql/functions-operators/ip-functions.md#esql-cidr_match) to filter internal IP ranges for external data transfer detection +2. Uses [`TO_STRING`](elasticsearch://reference/query-languages/esql/functions-operators/type-conversion-functions.md#esql-to_string) to standardize IP format for threat intel lookups +3. Uses [`DATE_DIFF`](elasticsearch://reference/query-languages/esql/functions-operators/date-time-functions.md#esql-date_diff) with `SUM` and `COUNT` to measure data transfer volume over time +4. Uses [`ROUND`](elasticsearch://reference/query-languages/esql/functions-operators/math-functions.md#esql-round) for human-readable values +5. Uses [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case) for risk scoring based on asset criticality and size of data transferred diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-js.md new file mode 100644 index 0000000000..8c7b2269cb --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-js.md @@ -0,0 +1,8 @@ +```js +const response = await client.esql.query({ + format: "txt", + query: + '\nFROM network-logs\n| WHERE NOT CIDR_MATCH(destination.ip, "10.0.0.0/8", "192.168.0.0/16")\n| EVAL indicator.value = TO_STRING(destination.ip)\n| LOOKUP JOIN threat-intel ON indicator.value\n| LOOKUP JOIN asset-inventory ON host.name\n| WHERE threat.name IS NOT NULL\n| STATS total_bytes = SUM(network.bytes),\n connection_count = COUNT(*),\n time_span = DATE_DIFF("hour", MIN(@timestamp), MAX(@timestamp))\nBY host.name, destination.ip, threat.name, asset.criticality\n| EVAL mb_transferred = ROUND(total_bytes / 1048576, 2)\n| EVAL risk_score = CASE(\n asset.criticality == "critical" AND mb_transferred > 100, 10,\n asset.criticality == "high" AND mb_transferred > 100, 7,\n mb_transferred > 50, 5,\n 3\n )\n| WHERE total_bytes > 1000000\n| SORT risk_score DESC, total_bytes DESC\n| LIMIT 1000\n ', +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-php.md new file mode 100644 index 0000000000..33f2008749 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-php.md @@ -0,0 +1,10 @@ +```php +$resp = $client->esql()->query([ + "format" => "txt", + "body" => [ + "query" => "\nFROM network-logs\n| WHERE NOT CIDR_MATCH(destination.ip, \"10.0.0.0/8\", \"192.168.0.0/16\")\n| EVAL indicator.value = TO_STRING(destination.ip)\n| LOOKUP JOIN threat-intel ON indicator.value\n| LOOKUP JOIN asset-inventory ON host.name\n| WHERE threat.name IS NOT NULL\n| STATS total_bytes = SUM(network.bytes),\n connection_count = COUNT(*),\n time_span = DATE_DIFF(\"hour\", MIN(@timestamp), MAX(@timestamp))\nBY host.name, destination.ip, threat.name, asset.criticality\n| EVAL mb_transferred = ROUND(total_bytes / 1048576, 2)\n| EVAL risk_score = CASE(\n asset.criticality == \"critical\" AND mb_transferred > 100, 10,\n asset.criticality == \"high\" AND mb_transferred > 100, 7,\n mb_transferred > 50, 5,\n 3\n )\n| WHERE total_bytes > 1000000\n| SORT risk_score DESC, total_bytes DESC\n| LIMIT 1000\n ", + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-python.md new file mode 100644 index 0000000000..92c28941a9 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-python.md @@ -0,0 +1,8 @@ +```python +resp = client.esql.query( + format="txt", + query="\nFROM network-logs\n| WHERE NOT CIDR_MATCH(destination.ip, \"10.0.0.0/8\", \"192.168.0.0/16\")\n| EVAL indicator.value = TO_STRING(destination.ip)\n| LOOKUP JOIN threat-intel ON indicator.value\n| LOOKUP JOIN asset-inventory ON host.name\n| WHERE threat.name IS NOT NULL\n| STATS total_bytes = SUM(network.bytes),\n connection_count = COUNT(*),\n time_span = DATE_DIFF(\"hour\", MIN(@timestamp), MAX(@timestamp))\nBY host.name, destination.ip, threat.name, asset.criticality\n| EVAL mb_transferred = ROUND(total_bytes / 1048576, 2)\n| EVAL risk_score = CASE(\n asset.criticality == \"critical\" AND mb_transferred > 100, 10,\n asset.criticality == \"high\" AND mb_transferred > 100, 7,\n mb_transferred > 50, 5,\n 3\n )\n| WHERE total_bytes > 1000000\n| SORT risk_score DESC, total_bytes DESC\n| LIMIT 1000\n ", +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-ruby.md new file mode 100644 index 0000000000..621a326b90 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example13-ruby.md @@ -0,0 +1,10 @@ +```ruby +response = client.esql.query( + format: "txt", + body: { + "query": "\nFROM network-logs\n| WHERE NOT CIDR_MATCH(destination.ip, \"10.0.0.0/8\", \"192.168.0.0/16\")\n| EVAL indicator.value = TO_STRING(destination.ip)\n| LOOKUP JOIN threat-intel ON indicator.value\n| LOOKUP JOIN asset-inventory ON host.name\n| WHERE threat.name IS NOT NULL\n| STATS total_bytes = SUM(network.bytes),\n connection_count = COUNT(*),\n time_span = DATE_DIFF(\"hour\", MIN(@timestamp), MAX(@timestamp))\nBY host.name, destination.ip, threat.name, asset.criticality\n| EVAL mb_transferred = ROUND(total_bytes / 1048576, 2)\n| EVAL risk_score = CASE(\n asset.criticality == \"critical\" AND mb_transferred > 100, 10,\n asset.criticality == \"high\" AND mb_transferred > 100, 7,\n mb_transferred > 50, 5,\n 3\n )\n| WHERE total_bytes > 1000000\n| SORT risk_score DESC, total_bytes DESC\n| LIMIT 1000\n " + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-console.md new file mode 100644 index 0000000000..e37c11918c --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-console.md @@ -0,0 +1,27 @@ +```console +POST /_query?format=txt +{ + "query": """ +FROM windows-security-logs, process-logs, network-logs +| LOOKUP JOIN asset-inventory ON host.name +| LOOKUP JOIN user-context ON user.name +| WHERE user.name == "jsmith" OR user.name == "admin" +| EVAL event_type = CASE( + event.code IS NOT NULL, "Authentication", + process.name IS NOT NULL, "Process Execution", + destination.ip IS NOT NULL, "Network Activity", + "Unknown") +| EVAL dest_ip = TO_STRING(destination.ip) +| EVAL attack_stage = CASE( + process.parent.name LIKE "*word*", "Initial Compromise", + process.name IN ("net.exe", "nltest.exe"), "Reconnaissance", + event.code == "4624" AND logon.type == "3", "Lateral Movement", + process.name IN ("sqlcmd.exe", "ntdsutil.exe"), "Data Access", + dest_ip NOT LIKE "10.*", "Exfiltration", + "Other") +| SORT @timestamp ASC +| KEEP @timestamp, event_type, attack_stage, host.name, asset.criticality, user.name, process.name, destination.ip +| LIMIT 1000 + """ +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-curl.md new file mode 100644 index 0000000000..d98375c350 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X POST "$ELASTICSEARCH_URL/_query?format=txt" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"query":"\nFROM windows-security-logs, process-logs, network-logs\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| WHERE user.name == \"jsmith\" OR user.name == \"admin\"\n| EVAL event_type = CASE(\n event.code IS NOT NULL, \"Authentication\",\n process.name IS NOT NULL, \"Process Execution\",\n destination.ip IS NOT NULL, \"Network Activity\",\n \"Unknown\")\n| EVAL dest_ip = TO_STRING(destination.ip)\n| EVAL attack_stage = CASE(\n process.parent.name LIKE \"*word*\", \"Initial Compromise\",\n process.name IN (\"net.exe\", \"nltest.exe\"), \"Reconnaissance\", \n event.code == \"4624\" AND logon.type == \"3\", \"Lateral Movement\",\n process.name IN (\"sqlcmd.exe\", \"ntdsutil.exe\"), \"Data Access\",\n dest_ip NOT LIKE \"10.*\", \"Exfiltration\",\n \"Other\")\n| SORT @timestamp ASC\n| KEEP @timestamp, event_type, attack_stage, host.name, asset.criticality, user.name, process.name, destination.ip\n| LIMIT 1000\n "}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-esql.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-esql.md new file mode 100644 index 0000000000..3b6375b96d --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-esql.md @@ -0,0 +1,27 @@ +```esql +FROM windows-security-logs, process-logs, network-logs <1> +| LOOKUP JOIN asset-inventory ON host.name +| LOOKUP JOIN user-context ON user.name +| WHERE user.name == "jsmith" OR user.name == "admin" +| EVAL event_type = CASE( + event.code IS NOT NULL, "Authentication", + process.name IS NOT NULL, "Process Execution", + destination.ip IS NOT NULL, "Network Activity", + "Unknown") <2> +| EVAL dest_ip = TO_STRING(destination.ip) +| EVAL attack_stage = CASE( + process.parent.name LIKE "*word*", "Initial Compromise", + process.name IN ("net.exe", "nltest.exe"), "Reconnaissance", + event.code == "4624" AND logon.type == "3", "Lateral Movement", + process.name IN ("sqlcmd.exe", "ntdsutil.exe"), "Data Access", + dest_ip NOT LIKE "10.*", "Exfiltration", + "Other") <3> +| SORT @timestamp ASC <4> +| KEEP @timestamp, event_type, attack_stage, host.name, asset.criticality, user.name, process.name, destination.ip +| LIMIT 1000 +``` + +1. Uses `FROM` with multiple indices for comprehensive correlation +2. Uses [`IS NOT NULL`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#null-predicates) with [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case) to classify event types from different data sources +3. Uses complex [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case) logic to map events to MITRE ATT&CK stages +4. Uses [`SORT`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-sort) to build chronological attack timeline diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-js.md new file mode 100644 index 0000000000..509b392bbd --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-js.md @@ -0,0 +1,8 @@ +```js +const response = await client.esql.query({ + format: "txt", + query: + '\nFROM windows-security-logs, process-logs, network-logs\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| WHERE user.name == "jsmith" OR user.name == "admin"\n| EVAL event_type = CASE(\n event.code IS NOT NULL, "Authentication",\n process.name IS NOT NULL, "Process Execution",\n destination.ip IS NOT NULL, "Network Activity",\n "Unknown")\n| EVAL dest_ip = TO_STRING(destination.ip)\n| EVAL attack_stage = CASE(\n process.parent.name LIKE "*word*", "Initial Compromise",\n process.name IN ("net.exe", "nltest.exe"), "Reconnaissance", \n event.code == "4624" AND logon.type == "3", "Lateral Movement",\n process.name IN ("sqlcmd.exe", "ntdsutil.exe"), "Data Access",\n dest_ip NOT LIKE "10.*", "Exfiltration",\n "Other")\n| SORT @timestamp ASC\n| KEEP @timestamp, event_type, attack_stage, host.name, asset.criticality, user.name, process.name, destination.ip\n| LIMIT 1000\n ', +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-php.md new file mode 100644 index 0000000000..24e39e777c --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-php.md @@ -0,0 +1,10 @@ +```php +$resp = $client->esql()->query([ + "format" => "txt", + "body" => [ + "query" => "\nFROM windows-security-logs, process-logs, network-logs\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| WHERE user.name == \"jsmith\" OR user.name == \"admin\"\n| EVAL event_type = CASE(\n event.code IS NOT NULL, \"Authentication\",\n process.name IS NOT NULL, \"Process Execution\",\n destination.ip IS NOT NULL, \"Network Activity\",\n \"Unknown\")\n| EVAL dest_ip = TO_STRING(destination.ip)\n| EVAL attack_stage = CASE(\n process.parent.name LIKE \"*word*\", \"Initial Compromise\",\n process.name IN (\"net.exe\", \"nltest.exe\"), \"Reconnaissance\", \n event.code == \"4624\" AND logon.type == \"3\", \"Lateral Movement\",\n process.name IN (\"sqlcmd.exe\", \"ntdsutil.exe\"), \"Data Access\",\n dest_ip NOT LIKE \"10.*\", \"Exfiltration\",\n \"Other\")\n| SORT @timestamp ASC\n| KEEP @timestamp, event_type, attack_stage, host.name, asset.criticality, user.name, process.name, destination.ip\n| LIMIT 1000\n ", + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-python.md new file mode 100644 index 0000000000..590d6ffad9 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-python.md @@ -0,0 +1,8 @@ +```python +resp = client.esql.query( + format="txt", + query="\nFROM windows-security-logs, process-logs, network-logs\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| WHERE user.name == \"jsmith\" OR user.name == \"admin\"\n| EVAL event_type = CASE(\n event.code IS NOT NULL, \"Authentication\",\n process.name IS NOT NULL, \"Process Execution\",\n destination.ip IS NOT NULL, \"Network Activity\",\n \"Unknown\")\n| EVAL dest_ip = TO_STRING(destination.ip)\n| EVAL attack_stage = CASE(\n process.parent.name LIKE \"*word*\", \"Initial Compromise\",\n process.name IN (\"net.exe\", \"nltest.exe\"), \"Reconnaissance\", \n event.code == \"4624\" AND logon.type == \"3\", \"Lateral Movement\",\n process.name IN (\"sqlcmd.exe\", \"ntdsutil.exe\"), \"Data Access\",\n dest_ip NOT LIKE \"10.*\", \"Exfiltration\",\n \"Other\")\n| SORT @timestamp ASC\n| KEEP @timestamp, event_type, attack_stage, host.name, asset.criticality, user.name, process.name, destination.ip\n| LIMIT 1000\n ", +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-ruby.md new file mode 100644 index 0000000000..bf3543bbad --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example14-ruby.md @@ -0,0 +1,10 @@ +```ruby +response = client.esql.query( + format: "txt", + body: { + "query": "\nFROM windows-security-logs, process-logs, network-logs\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| WHERE user.name == \"jsmith\" OR user.name == \"admin\"\n| EVAL event_type = CASE(\n event.code IS NOT NULL, \"Authentication\",\n process.name IS NOT NULL, \"Process Execution\",\n destination.ip IS NOT NULL, \"Network Activity\",\n \"Unknown\")\n| EVAL dest_ip = TO_STRING(destination.ip)\n| EVAL attack_stage = CASE(\n process.parent.name LIKE \"*word*\", \"Initial Compromise\",\n process.name IN (\"net.exe\", \"nltest.exe\"), \"Reconnaissance\", \n event.code == \"4624\" AND logon.type == \"3\", \"Lateral Movement\",\n process.name IN (\"sqlcmd.exe\", \"ntdsutil.exe\"), \"Data Access\",\n dest_ip NOT LIKE \"10.*\", \"Exfiltration\",\n \"Other\")\n| SORT @timestamp ASC\n| KEEP @timestamp, event_type, attack_stage, host.name, asset.criticality, user.name, process.name, destination.ip\n| LIMIT 1000\n " + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-console.md new file mode 100644 index 0000000000..10909859dd --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-console.md @@ -0,0 +1,23 @@ +```console +POST /_query?format=txt +{ + "query": """ +FROM process-logs +| WHERE process.name IN ("powershell.exe", "cmd.exe", "net.exe", "sqlcmd.exe", "schtasks.exe", "sc.exe") +| LOOKUP JOIN asset-inventory ON host.name +| LOOKUP JOIN user-context ON user.name +| STATS executions = COUNT(*), + unique_hosts = COUNT_DISTINCT(host.name), + unique_commands = COUNT_DISTINCT(process.name) +BY user.name, user.department +| WHERE executions > 1 +| EVAL usage_pattern = CASE( + executions > 5, "High Usage", + executions > 3, "Moderate Usage", + "Low Usage" + ) +| SORT executions DESC +| LIMIT 1000 + """ +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-curl.md new file mode 100644 index 0000000000..ebc4f3a4bc --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X POST "$ELASTICSEARCH_URL/_query?format=txt" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"query":"\nFROM process-logs\n| WHERE process.name IN (\"powershell.exe\", \"cmd.exe\", \"net.exe\", \"sqlcmd.exe\", \"schtasks.exe\", \"sc.exe\")\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| STATS executions = COUNT(*),\n unique_hosts = COUNT_DISTINCT(host.name),\n unique_commands = COUNT_DISTINCT(process.name)\nBY user.name, user.department\n| WHERE executions > 1\n| EVAL usage_pattern = CASE(\n executions > 5, \"High Usage\",\n executions > 3, \"Moderate Usage\", \n \"Low Usage\"\n )\n| SORT executions DESC\n| LIMIT 1000\n "}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-esql.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-esql.md new file mode 100644 index 0000000000..df2519118a --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-esql.md @@ -0,0 +1,23 @@ +```esql +FROM process-logs +| WHERE process.name IN ("powershell.exe", "cmd.exe", "net.exe", "sqlcmd.exe", "schtasks.exe", "sc.exe") <1> +| LOOKUP JOIN asset-inventory ON host.name +| LOOKUP JOIN user-context ON user.name +| STATS executions = COUNT(*), + unique_hosts = COUNT_DISTINCT(host.name), + unique_commands = COUNT_DISTINCT(process.name) <3> +BY user.name, user.department +| WHERE executions > 1 +| EVAL usage_pattern = CASE( + executions > 5, "High Usage", + executions > 3, "Moderate Usage", + "Low Usage" + ) <4> +| SORT executions DESC +| LIMIT 1000 +``` + +1. Uses `WHERE...IN` to monitor high-risk system tools +2. Uses [`LOOKUP JOIN`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-lookup-join) with `asset-inventory` and `user-context` indices to enrich events with context +3. Uses [`COUNT_DISTINCT`](elasticsearch://reference/query-languages/esql/functions-operators/aggregation-functions.md#esql-count_distinct) to measure breadth of suspicious tool usage +4. Uses [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case)to classify usage patterns for anomaly detection diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-js.md new file mode 100644 index 0000000000..dba933d7f1 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-js.md @@ -0,0 +1,8 @@ +```js +const response = await client.esql.query({ + format: "txt", + query: + '\nFROM process-logs\n| WHERE process.name IN ("powershell.exe", "cmd.exe", "net.exe", "sqlcmd.exe", "schtasks.exe", "sc.exe")\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| STATS executions = COUNT(*),\n unique_hosts = COUNT_DISTINCT(host.name),\n unique_commands = COUNT_DISTINCT(process.name)\nBY user.name, user.department\n| WHERE executions > 1\n| EVAL usage_pattern = CASE(\n executions > 5, "High Usage",\n executions > 3, "Moderate Usage", \n "Low Usage"\n )\n| SORT executions DESC\n| LIMIT 1000\n ', +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-php.md new file mode 100644 index 0000000000..073af09968 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-php.md @@ -0,0 +1,10 @@ +```php +$resp = $client->esql()->query([ + "format" => "txt", + "body" => [ + "query" => "\nFROM process-logs\n| WHERE process.name IN (\"powershell.exe\", \"cmd.exe\", \"net.exe\", \"sqlcmd.exe\", \"schtasks.exe\", \"sc.exe\")\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| STATS executions = COUNT(*),\n unique_hosts = COUNT_DISTINCT(host.name),\n unique_commands = COUNT_DISTINCT(process.name)\nBY user.name, user.department\n| WHERE executions > 1\n| EVAL usage_pattern = CASE(\n executions > 5, \"High Usage\",\n executions > 3, \"Moderate Usage\", \n \"Low Usage\"\n )\n| SORT executions DESC\n| LIMIT 1000\n ", + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-python.md new file mode 100644 index 0000000000..f30b722057 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-python.md @@ -0,0 +1,8 @@ +```python +resp = client.esql.query( + format="txt", + query="\nFROM process-logs\n| WHERE process.name IN (\"powershell.exe\", \"cmd.exe\", \"net.exe\", \"sqlcmd.exe\", \"schtasks.exe\", \"sc.exe\")\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| STATS executions = COUNT(*),\n unique_hosts = COUNT_DISTINCT(host.name),\n unique_commands = COUNT_DISTINCT(process.name)\nBY user.name, user.department\n| WHERE executions > 1\n| EVAL usage_pattern = CASE(\n executions > 5, \"High Usage\",\n executions > 3, \"Moderate Usage\", \n \"Low Usage\"\n )\n| SORT executions DESC\n| LIMIT 1000\n ", +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-ruby.md new file mode 100644 index 0000000000..b2b5fd5a38 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example15-ruby.md @@ -0,0 +1,10 @@ +```ruby +response = client.esql.query( + format: "txt", + body: { + "query": "\nFROM process-logs\n| WHERE process.name IN (\"powershell.exe\", \"cmd.exe\", \"net.exe\", \"sqlcmd.exe\", \"schtasks.exe\", \"sc.exe\")\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| STATS executions = COUNT(*),\n unique_hosts = COUNT_DISTINCT(host.name),\n unique_commands = COUNT_DISTINCT(process.name)\nBY user.name, user.department\n| WHERE executions > 1\n| EVAL usage_pattern = CASE(\n executions > 5, \"High Usage\",\n executions > 3, \"Moderate Usage\", \n \"Low Usage\"\n )\n| SORT executions DESC\n| LIMIT 1000\n " + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-console.md new file mode 100644 index 0000000000..c813031b73 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-console.md @@ -0,0 +1,23 @@ +```console +POST /_query?format=txt +{ + "query": """ +FROM process-logs +| WHERE process.name == "schtasks.exe" AND process.command_line:"/create" +| LOOKUP JOIN asset-inventory ON host.name +| LOOKUP JOIN user-context ON user.name +| EVAL time_bucket = DATE_TRUNC(1 hour, @timestamp) +| STATS task_creations = COUNT(*), + creation_hours = COUNT_DISTINCT(time_bucket) +BY user.name, host.name, asset.criticality +| WHERE task_creations > 0 +| EVAL persistence_pattern = CASE( + creation_hours > 1, "Multiple Hours", + task_creations > 1, "Burst Creation", + "Single Task" + ) +| SORT task_creations DESC +| LIMIT 1000 + """ +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-curl.md new file mode 100644 index 0000000000..dd136f390c --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X POST "$ELASTICSEARCH_URL/_query?format=txt" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"query":"\nFROM process-logs\n| WHERE process.name == \"schtasks.exe\" AND process.command_line:\"/create\"\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| EVAL time_bucket = DATE_TRUNC(1 hour, @timestamp)\n| STATS task_creations = COUNT(*),\n creation_hours = COUNT_DISTINCT(time_bucket)\nBY user.name, host.name, asset.criticality\n| WHERE task_creations > 0\n| EVAL persistence_pattern = CASE(\n creation_hours > 1, \"Multiple Hours\",\n task_creations > 1, \"Burst Creation\",\n \"Single Task\"\n )\n| SORT task_creations DESC\n| LIMIT 1000\n "}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-esql.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-esql.md new file mode 100644 index 0000000000..efec6fc464 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-esql.md @@ -0,0 +1,23 @@ +```esql +FROM process-logs +| WHERE process.name == "schtasks.exe" AND process.command_line:"/create" <1> +| LOOKUP JOIN asset-inventory ON host.name +| LOOKUP JOIN user-context ON user.name +| EVAL time_bucket = DATE_TRUNC(1 hour, @timestamp) <2> +| STATS task_creations = COUNT(*), + creation_hours = COUNT_DISTINCT(time_bucket) <3> +BY user.name, host.name, asset.criticality +| WHERE task_creations > 0 +| EVAL persistence_pattern = CASE( + creation_hours > 1, "Multiple Hours", + task_creations > 1, "Burst Creation", + "Single Task" + ) <4> +| SORT task_creations DESC +| LIMIT 1000 +``` + +1. Uses [`WHERE`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-where) with [`:`](elasticsearch://reference/query-languages/esql/functions-operators/operators.md#esql-match-operator) match operator to detect scheduled task creation (a common persistence mechanism) +2. Uses [`DATE_TRUNC`](elasticsearch://reference/query-languages/esql/functions-operators/date-time-functions.md#esql-date_trunc) to group events into hourly time buckets for temporal analysis +3. Uses [`COUNT_DISTINCT`](elasticsearch://reference/query-languages/esql/functions-operators/aggregation-functions.md#esql-count_distinct) with `time_bucket` to measure task creation velocity +4. Uses [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case) to classify suspicious patterns based on timing and frequency diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-js.md new file mode 100644 index 0000000000..dcb7350521 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-js.md @@ -0,0 +1,8 @@ +```js +const response = await client.esql.query({ + format: "txt", + query: + '\nFROM process-logs\n| WHERE process.name == "schtasks.exe" AND process.command_line:"/create"\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| EVAL time_bucket = DATE_TRUNC(1 hour, @timestamp)\n| STATS task_creations = COUNT(*),\n creation_hours = COUNT_DISTINCT(time_bucket)\nBY user.name, host.name, asset.criticality\n| WHERE task_creations > 0\n| EVAL persistence_pattern = CASE(\n creation_hours > 1, "Multiple Hours",\n task_creations > 1, "Burst Creation",\n "Single Task"\n )\n| SORT task_creations DESC\n| LIMIT 1000\n ', +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-php.md new file mode 100644 index 0000000000..4bdf9710d7 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-php.md @@ -0,0 +1,10 @@ +```php +$resp = $client->esql()->query([ + "format" => "txt", + "body" => [ + "query" => "\nFROM process-logs\n| WHERE process.name == \"schtasks.exe\" AND process.command_line:\"/create\"\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| EVAL time_bucket = DATE_TRUNC(1 hour, @timestamp)\n| STATS task_creations = COUNT(*),\n creation_hours = COUNT_DISTINCT(time_bucket)\nBY user.name, host.name, asset.criticality\n| WHERE task_creations > 0\n| EVAL persistence_pattern = CASE(\n creation_hours > 1, \"Multiple Hours\",\n task_creations > 1, \"Burst Creation\",\n \"Single Task\"\n )\n| SORT task_creations DESC\n| LIMIT 1000\n ", + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-python.md new file mode 100644 index 0000000000..ac0245e3a9 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-python.md @@ -0,0 +1,8 @@ +```python +resp = client.esql.query( + format="txt", + query="\nFROM process-logs\n| WHERE process.name == \"schtasks.exe\" AND process.command_line:\"/create\"\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| EVAL time_bucket = DATE_TRUNC(1 hour, @timestamp)\n| STATS task_creations = COUNT(*),\n creation_hours = COUNT_DISTINCT(time_bucket)\nBY user.name, host.name, asset.criticality\n| WHERE task_creations > 0\n| EVAL persistence_pattern = CASE(\n creation_hours > 1, \"Multiple Hours\",\n task_creations > 1, \"Burst Creation\",\n \"Single Task\"\n )\n| SORT task_creations DESC\n| LIMIT 1000\n ", +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-ruby.md new file mode 100644 index 0000000000..d7d33b865e --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example16-ruby.md @@ -0,0 +1,10 @@ +```ruby +response = client.esql.query( + format: "txt", + body: { + "query": "\nFROM process-logs\n| WHERE process.name == \"schtasks.exe\" AND process.command_line:\"/create\"\n| LOOKUP JOIN asset-inventory ON host.name\n| LOOKUP JOIN user-context ON user.name\n| EVAL time_bucket = DATE_TRUNC(1 hour, @timestamp)\n| STATS task_creations = COUNT(*),\n creation_hours = COUNT_DISTINCT(time_bucket)\nBY user.name, host.name, asset.criticality\n| WHERE task_creations > 0\n| EVAL persistence_pattern = CASE(\n creation_hours > 1, \"Multiple Hours\",\n task_creations > 1, \"Burst Creation\",\n \"Single Task\"\n )\n| SORT task_creations DESC\n| LIMIT 1000\n " + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-console.md new file mode 100644 index 0000000000..b28f65a3dc --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-console.md @@ -0,0 +1,13 @@ +```console +POST /_bulk?refresh=wait_for +{"index":{"_index":"windows-security-logs"}} +{"@timestamp":"2025-05-20T08:15:00Z","event":{"code":"4625","action":"logon_failed"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"WS-001","ip":"10.1.1.50"},"source":{"ip":"10.1.1.100"}} +{"index":{"_index":"windows-security-logs"}} +{"@timestamp":"2025-05-20T08:17:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"WS-001","ip":"10.1.1.50"},"source":{"ip":"10.1.1.100"},"logon":{"type":"3"}} +{"index":{"_index":"windows-security-logs"}} +{"@timestamp":"2025-05-20T09:30:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"SRV-001","ip":"10.1.2.10"},"source":{"ip":"10.1.1.50"},"logon":{"type":"3"}} +{"index":{"_index":"windows-security-logs"}} +{"@timestamp":"2025-05-20T10:45:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"DB-001","ip":"10.1.3.5"},"source":{"ip":"10.1.2.10"},"logon":{"type":"3"}} +{"index":{"_index":"windows-security-logs"}} +{"@timestamp":"2025-05-20T02:30:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"admin","domain":"corp"},"host":{"name":"DC-001","ip":"10.1.4.10"},"source":{"ip":"10.1.3.5"},"logon":{"type":"3"}} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-curl.md new file mode 100644 index 0000000000..8ed6adb66b --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X POST "$ELASTICSEARCH_URL/_bulk?refresh=wait_for" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/x-ndjson" \ + -d $'{"index":{"_index":"windows-security-logs"}}\n{"@timestamp":"2025-05-20T08:15:00Z","event":{"code":"4625","action":"logon_failed"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"WS-001","ip":"10.1.1.50"},"source":{"ip":"10.1.1.100"}}\n{"index":{"_index":"windows-security-logs"}}\n{"@timestamp":"2025-05-20T08:17:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"WS-001","ip":"10.1.1.50"},"source":{"ip":"10.1.1.100"},"logon":{"type":"3"}}\n{"index":{"_index":"windows-security-logs"}}\n{"@timestamp":"2025-05-20T09:30:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"SRV-001","ip":"10.1.2.10"},"source":{"ip":"10.1.1.50"},"logon":{"type":"3"}}\n{"index":{"_index":"windows-security-logs"}}\n{"@timestamp":"2025-05-20T10:45:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"DB-001","ip":"10.1.3.5"},"source":{"ip":"10.1.2.10"},"logon":{"type":"3"}}\n{"index":{"_index":"windows-security-logs"}}\n{"@timestamp":"2025-05-20T02:30:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"admin","domain":"corp"},"host":{"name":"DC-001","ip":"10.1.4.10"},"source":{"ip":"10.1.3.5"},"logon":{"type":"3"}}\n' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-js.md new file mode 100644 index 0000000000..fa91bf7490 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-js.md @@ -0,0 +1,135 @@ +```js +const response = await client.bulk({ + refresh: "wait_for", + operations: [ + { + index: { + _index: "windows-security-logs", + }, + }, + { + "@timestamp": "2025-05-20T08:15:00Z", + event: { + code: "4625", + action: "logon_failed", + }, + user: { + name: "jsmith", + domain: "corp", + }, + host: { + name: "WS-001", + ip: "10.1.1.50", + }, + source: { + ip: "10.1.1.100", + }, + }, + { + index: { + _index: "windows-security-logs", + }, + }, + { + "@timestamp": "2025-05-20T08:17:00Z", + event: { + code: "4624", + action: "logon_success", + }, + user: { + name: "jsmith", + domain: "corp", + }, + host: { + name: "WS-001", + ip: "10.1.1.50", + }, + source: { + ip: "10.1.1.100", + }, + logon: { + type: "3", + }, + }, + { + index: { + _index: "windows-security-logs", + }, + }, + { + "@timestamp": "2025-05-20T09:30:00Z", + event: { + code: "4624", + action: "logon_success", + }, + user: { + name: "jsmith", + domain: "corp", + }, + host: { + name: "SRV-001", + ip: "10.1.2.10", + }, + source: { + ip: "10.1.1.50", + }, + logon: { + type: "3", + }, + }, + { + index: { + _index: "windows-security-logs", + }, + }, + { + "@timestamp": "2025-05-20T10:45:00Z", + event: { + code: "4624", + action: "logon_success", + }, + user: { + name: "jsmith", + domain: "corp", + }, + host: { + name: "DB-001", + ip: "10.1.3.5", + }, + source: { + ip: "10.1.2.10", + }, + logon: { + type: "3", + }, + }, + { + index: { + _index: "windows-security-logs", + }, + }, + { + "@timestamp": "2025-05-20T02:30:00Z", + event: { + code: "4624", + action: "logon_success", + }, + user: { + name: "admin", + domain: "corp", + }, + host: { + name: "DC-001", + ip: "10.1.4.10", + }, + source: { + ip: "10.1.3.5", + }, + logon: { + type: "3", + }, + }, + ], +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-php.md new file mode 100644 index 0000000000..df8e7bde22 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-php.md @@ -0,0 +1,136 @@ +```php +$resp = $client->bulk([ + "refresh" => "wait_for", + "body" => array( + [ + "index" => [ + "_index" => "windows-security-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T08:15:00Z", + "event" => [ + "code" => "4625", + "action" => "logon_failed", + ], + "user" => [ + "name" => "jsmith", + "domain" => "corp", + ], + "host" => [ + "name" => "WS-001", + "ip" => "10.1.1.50", + ], + "source" => [ + "ip" => "10.1.1.100", + ], + ], + [ + "index" => [ + "_index" => "windows-security-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T08:17:00Z", + "event" => [ + "code" => "4624", + "action" => "logon_success", + ], + "user" => [ + "name" => "jsmith", + "domain" => "corp", + ], + "host" => [ + "name" => "WS-001", + "ip" => "10.1.1.50", + ], + "source" => [ + "ip" => "10.1.1.100", + ], + "logon" => [ + "type" => "3", + ], + ], + [ + "index" => [ + "_index" => "windows-security-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T09:30:00Z", + "event" => [ + "code" => "4624", + "action" => "logon_success", + ], + "user" => [ + "name" => "jsmith", + "domain" => "corp", + ], + "host" => [ + "name" => "SRV-001", + "ip" => "10.1.2.10", + ], + "source" => [ + "ip" => "10.1.1.50", + ], + "logon" => [ + "type" => "3", + ], + ], + [ + "index" => [ + "_index" => "windows-security-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T10:45:00Z", + "event" => [ + "code" => "4624", + "action" => "logon_success", + ], + "user" => [ + "name" => "jsmith", + "domain" => "corp", + ], + "host" => [ + "name" => "DB-001", + "ip" => "10.1.3.5", + ], + "source" => [ + "ip" => "10.1.2.10", + ], + "logon" => [ + "type" => "3", + ], + ], + [ + "index" => [ + "_index" => "windows-security-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T02:30:00Z", + "event" => [ + "code" => "4624", + "action" => "logon_success", + ], + "user" => [ + "name" => "admin", + "domain" => "corp", + ], + "host" => [ + "name" => "DC-001", + "ip" => "10.1.4.10", + ], + "source" => [ + "ip" => "10.1.3.5", + ], + "logon" => [ + "type" => "3", + ], + ], + ), +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-python.md new file mode 100644 index 0000000000..6ba2bb8f1f --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-python.md @@ -0,0 +1,136 @@ +```python +resp = client.bulk( + refresh="wait_for", + operations=[ + { + "index": { + "_index": "windows-security-logs" + } + }, + { + "@timestamp": "2025-05-20T08:15:00Z", + "event": { + "code": "4625", + "action": "logon_failed" + }, + "user": { + "name": "jsmith", + "domain": "corp" + }, + "host": { + "name": "WS-001", + "ip": "10.1.1.50" + }, + "source": { + "ip": "10.1.1.100" + } + }, + { + "index": { + "_index": "windows-security-logs" + } + }, + { + "@timestamp": "2025-05-20T08:17:00Z", + "event": { + "code": "4624", + "action": "logon_success" + }, + "user": { + "name": "jsmith", + "domain": "corp" + }, + "host": { + "name": "WS-001", + "ip": "10.1.1.50" + }, + "source": { + "ip": "10.1.1.100" + }, + "logon": { + "type": "3" + } + }, + { + "index": { + "_index": "windows-security-logs" + } + }, + { + "@timestamp": "2025-05-20T09:30:00Z", + "event": { + "code": "4624", + "action": "logon_success" + }, + "user": { + "name": "jsmith", + "domain": "corp" + }, + "host": { + "name": "SRV-001", + "ip": "10.1.2.10" + }, + "source": { + "ip": "10.1.1.50" + }, + "logon": { + "type": "3" + } + }, + { + "index": { + "_index": "windows-security-logs" + } + }, + { + "@timestamp": "2025-05-20T10:45:00Z", + "event": { + "code": "4624", + "action": "logon_success" + }, + "user": { + "name": "jsmith", + "domain": "corp" + }, + "host": { + "name": "DB-001", + "ip": "10.1.3.5" + }, + "source": { + "ip": "10.1.2.10" + }, + "logon": { + "type": "3" + } + }, + { + "index": { + "_index": "windows-security-logs" + } + }, + { + "@timestamp": "2025-05-20T02:30:00Z", + "event": { + "code": "4624", + "action": "logon_success" + }, + "user": { + "name": "admin", + "domain": "corp" + }, + "host": { + "name": "DC-001", + "ip": "10.1.4.10" + }, + "source": { + "ip": "10.1.3.5" + }, + "logon": { + "type": "3" + } + } + ], +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-ruby.md new file mode 100644 index 0000000000..c4ae93e9ae --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example2-ruby.md @@ -0,0 +1,136 @@ +```ruby +response = client.bulk( + refresh: "wait_for", + body: [ + { + "index": { + "_index": "windows-security-logs" + } + }, + { + "@timestamp": "2025-05-20T08:15:00Z", + "event": { + "code": "4625", + "action": "logon_failed" + }, + "user": { + "name": "jsmith", + "domain": "corp" + }, + "host": { + "name": "WS-001", + "ip": "10.1.1.50" + }, + "source": { + "ip": "10.1.1.100" + } + }, + { + "index": { + "_index": "windows-security-logs" + } + }, + { + "@timestamp": "2025-05-20T08:17:00Z", + "event": { + "code": "4624", + "action": "logon_success" + }, + "user": { + "name": "jsmith", + "domain": "corp" + }, + "host": { + "name": "WS-001", + "ip": "10.1.1.50" + }, + "source": { + "ip": "10.1.1.100" + }, + "logon": { + "type": "3" + } + }, + { + "index": { + "_index": "windows-security-logs" + } + }, + { + "@timestamp": "2025-05-20T09:30:00Z", + "event": { + "code": "4624", + "action": "logon_success" + }, + "user": { + "name": "jsmith", + "domain": "corp" + }, + "host": { + "name": "SRV-001", + "ip": "10.1.2.10" + }, + "source": { + "ip": "10.1.1.50" + }, + "logon": { + "type": "3" + } + }, + { + "index": { + "_index": "windows-security-logs" + } + }, + { + "@timestamp": "2025-05-20T10:45:00Z", + "event": { + "code": "4624", + "action": "logon_success" + }, + "user": { + "name": "jsmith", + "domain": "corp" + }, + "host": { + "name": "DB-001", + "ip": "10.1.3.5" + }, + "source": { + "ip": "10.1.2.10" + }, + "logon": { + "type": "3" + } + }, + { + "index": { + "_index": "windows-security-logs" + } + }, + { + "@timestamp": "2025-05-20T02:30:00Z", + "event": { + "code": "4624", + "action": "logon_success" + }, + "user": { + "name": "admin", + "domain": "corp" + }, + "host": { + "name": "DC-001", + "ip": "10.1.4.10" + }, + "source": { + "ip": "10.1.3.5" + }, + "logon": { + "type": "3" + } + } + ] +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-console.md new file mode 100644 index 0000000000..c6b6e87464 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-console.md @@ -0,0 +1,31 @@ +```console +PUT /process-logs +{ + "mappings": { + "properties": { + "@timestamp": {"type": "date"}, + "process": { + "properties": { + "name": {"type": "keyword"}, + "command_line": {"type": "text"}, # Command lines are stored as text fields to enable full-text search for suspicious parameters and encoded commands. + "parent": { + "properties": { + "name": {"type": "keyword"} + } + } + } + }, + "user": { + "properties": { + "name": {"type": "keyword"} + } + }, + "host": { + "properties": { + "name": {"type": "keyword"} + } + } + } + } +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-curl.md new file mode 100644 index 0000000000..d0e4426b0f --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X PUT "$ELASTICSEARCH_URL/process-logs" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"mappings":{"properties":{"@timestamp":{"type":"date"},"process":{"properties":{"name":{"type":"keyword"},"command_line":{"type":"text"},"parent":{"properties":{"name":{"type":"keyword"}}}}},"user":{"properties":{"name":{"type":"keyword"}}},"host":{"properties":{"name":{"type":"keyword"}}}}}}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-js.md new file mode 100644 index 0000000000..1e1328c66b --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-js.md @@ -0,0 +1,44 @@ +```js +const response = await client.indices.create({ + index: "process-logs", + mappings: { + properties: { + "@timestamp": { + type: "date", + }, + process: { + properties: { + name: { + type: "keyword", + }, + command_line: { + type: "text", + }, + parent: { + properties: { + name: { + type: "keyword", + }, + }, + }, + }, + }, + user: { + properties: { + name: { + type: "keyword", + }, + }, + }, + host: { + properties: { + name: { + type: "keyword", + }, + }, + }, + }, + }, +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-php.md new file mode 100644 index 0000000000..9583cde774 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-php.md @@ -0,0 +1,47 @@ +```php +$resp = $client->indices()->create([ + "index" => "process-logs", + "body" => [ + "mappings" => [ + "properties" => [ + "@timestamp" => [ + "type" => "date", + ], + "process" => [ + "properties" => [ + "name" => [ + "type" => "keyword", + ], + "command_line" => [ + "type" => "text", + ], + "parent" => [ + "properties" => [ + "name" => [ + "type" => "keyword", + ], + ], + ], + ], + ], + "user" => [ + "properties" => [ + "name" => [ + "type" => "keyword", + ], + ], + ], + "host" => [ + "properties" => [ + "name" => [ + "type" => "keyword", + ], + ], + ], + ], + ], + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-python.md new file mode 100644 index 0000000000..4bde8d75e6 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-python.md @@ -0,0 +1,45 @@ +```python +resp = client.indices.create( + index="process-logs", + mappings={ + "properties": { + "@timestamp": { + "type": "date" + }, + "process": { + "properties": { + "name": { + "type": "keyword" + }, + "command_line": { + "type": "text" + }, + "parent": { + "properties": { + "name": { + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "name": { + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "type": "keyword" + } + } + } + } + }, +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-ruby.md new file mode 100644 index 0000000000..4e5c4288c3 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example3-ruby.md @@ -0,0 +1,47 @@ +```ruby +response = client.indices.create( + index: "process-logs", + body: { + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "process": { + "properties": { + "name": { + "type": "keyword" + }, + "command_line": { + "type": "text" + }, + "parent": { + "properties": { + "name": { + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "name": { + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "type": "keyword" + } + } + } + } + } + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-console.md new file mode 100644 index 0000000000..70714b6521 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-console.md @@ -0,0 +1,21 @@ +```console +POST /_bulk?refresh=wait_for +{"index":{"_index":"process-logs"}} +{"@timestamp":"2025-05-20T08:20:00Z","process":{"name":"powershell.exe","command_line":"powershell.exe -enc JABzAD0ATgBlAHcALgBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAA=","parent":{"name":"winword.exe"}},"user":{"name":"jsmith"},"host":{"name":"WS-001"}} +{"index":{"_index":"process-logs"}} +{"@timestamp":"2025-05-20T09:35:00Z","process":{"name":"net.exe","command_line":"net user /domain","parent":{"name":"cmd.exe"}},"user":{"name":"jsmith"},"host":{"name":"SRV-001"}} +{"index":{"_index":"process-logs"}} +{"@timestamp":"2025-05-20T10:50:00Z","process":{"name":"sqlcmd.exe","command_line":"sqlcmd -S localhost -Q \"SELECT * FROM customers\"","parent":{"name":"powershell.exe"}},"user":{"name":"jsmith"},"host":{"name":"DB-001"}} +{"index":{"_index":"process-logs"}} +{"@timestamp":"2025-05-20T02:35:00Z","process":{"name":"ntdsutil.exe","command_line":"ntdsutil \"ac i ntds\" \"ifm\" \"create full c:\\temp\\ntds\"","parent":{"name":"cmd.exe"}},"user":{"name":"admin"},"host":{"name":"DC-001"}} +{"index":{"_index":"process-logs"}} +{"@timestamp":"2025-05-20T12:15:00Z","process":{"name":"schtasks.exe","command_line":"schtasks.exe /create /tn UpdateCheck /tr c:\\windows\\temp\\update.exe /sc daily","parent":{"name":"cmd.exe"}},"user":{"name":"jsmith"},"host":{"name":"WS-001"}} +{"index":{"_index":"process-logs"}} +{"@timestamp":"2025-05-20T12:30:00Z","process":{"name":"schtasks.exe","command_line":"schtasks.exe /create /tn SystemManager /tr powershell.exe -enc ZQBjAGgAbwAgACIASABlAGwAbABvACIA /sc minute /mo 5","parent":{"name":"powershell.exe"}},"user":{"name":"jsmith"},"host":{"name":"SRV-001"}} +{"index":{"_index":"process-logs"}} +{"@timestamp":"2025-05-20T13:15:00Z","process":{"name":"sc.exe","command_line":"sc.exe create RemoteService binPath= c:\\windows\\temp\\remote.exe","parent":{"name":"cmd.exe"}},"user":{"name":"jsmith"},"host":{"name":"DB-001"}} +{"index":{"_index":"process-logs"}} +{"@timestamp":"2025-05-20T13:20:00Z","process":{"name":"sc.exe","command_line":"sc.exe create BackdoorService binPath= c:\\programdata\\svc.exe","parent":{"name":"powershell.exe"}},"user":{"name":"jsmith"},"host":{"name":"SRV-001"}} +{"index":{"_index":"process-logs"}} +{"@timestamp":"2025-05-20T13:25:00Z","process":{"name":"sc.exe","command_line":"sc.exe create PersistenceService binPath= c:\\windows\\system32\\malicious.exe","parent":{"name":"cmd.exe"}},"user":{"name":"admin"},"host":{"name":"DC-001"}} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-curl.md new file mode 100644 index 0000000000..516f96cc62 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X POST "$ELASTICSEARCH_URL/_bulk?refresh=wait_for" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/x-ndjson" \ + -d $'{"index":{"_index":"process-logs"}}\n{"@timestamp":"2025-05-20T08:20:00Z","process":{"name":"powershell.exe","command_line":"powershell.exe -enc JABzAD0ATgBlAHcALgBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAA=","parent":{"name":"winword.exe"}},"user":{"name":"jsmith"},"host":{"name":"WS-001"}}\n{"index":{"_index":"process-logs"}}\n{"@timestamp":"2025-05-20T09:35:00Z","process":{"name":"net.exe","command_line":"net user /domain","parent":{"name":"cmd.exe"}},"user":{"name":"jsmith"},"host":{"name":"SRV-001"}}\n{"index":{"_index":"process-logs"}}\n{"@timestamp":"2025-05-20T10:50:00Z","process":{"name":"sqlcmd.exe","command_line":"sqlcmd -S localhost -Q \"SELECT * FROM customers\"","parent":{"name":"powershell.exe"}},"user":{"name":"jsmith"},"host":{"name":"DB-001"}}\n{"index":{"_index":"process-logs"}}\n{"@timestamp":"2025-05-20T02:35:00Z","process":{"name":"ntdsutil.exe","command_line":"ntdsutil \"ac i ntds\" \"ifm\" \"create full c:\\temp\\ntds\"","parent":{"name":"cmd.exe"}},"user":{"name":"admin"},"host":{"name":"DC-001"}}\n{"index":{"_index":"process-logs"}}\n{"@timestamp":"2025-05-20T12:15:00Z","process":{"name":"schtasks.exe","command_line":"schtasks.exe /create /tn UpdateCheck /tr c:\\windows\\temp\\update.exe /sc daily","parent":{"name":"cmd.exe"}},"user":{"name":"jsmith"},"host":{"name":"WS-001"}}\n{"index":{"_index":"process-logs"}}\n{"@timestamp":"2025-05-20T12:30:00Z","process":{"name":"schtasks.exe","command_line":"schtasks.exe /create /tn SystemManager /tr powershell.exe -enc ZQBjAGgAbwAgACIASABlAGwAbABvACIA /sc minute /mo 5","parent":{"name":"powershell.exe"}},"user":{"name":"jsmith"},"host":{"name":"SRV-001"}}\n{"index":{"_index":"process-logs"}}\n{"@timestamp":"2025-05-20T13:15:00Z","process":{"name":"sc.exe","command_line":"sc.exe create RemoteService binPath= c:\\windows\\temp\\remote.exe","parent":{"name":"cmd.exe"}},"user":{"name":"jsmith"},"host":{"name":"DB-001"}}\n{"index":{"_index":"process-logs"}}\n{"@timestamp":"2025-05-20T13:20:00Z","process":{"name":"sc.exe","command_line":"sc.exe create BackdoorService binPath= c:\\programdata\\svc.exe","parent":{"name":"powershell.exe"}},"user":{"name":"jsmith"},"host":{"name":"SRV-001"}}\n{"index":{"_index":"process-logs"}}\n{"@timestamp":"2025-05-20T13:25:00Z","process":{"name":"sc.exe","command_line":"sc.exe create PersistenceService binPath= c:\\windows\\system32\\malicious.exe","parent":{"name":"cmd.exe"}},"user":{"name":"admin"},"host":{"name":"DC-001"}}\n' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-js.md new file mode 100644 index 0000000000..bcefc70d2f --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-js.md @@ -0,0 +1,203 @@ +```js +const response = await client.bulk({ + refresh: "wait_for", + operations: [ + { + index: { + _index: "process-logs", + }, + }, + { + "@timestamp": "2025-05-20T08:20:00Z", + process: { + name: "powershell.exe", + command_line: + "powershell.exe -enc JABzAD0ATgBlAHcALgBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAA=", + parent: { + name: "winword.exe", + }, + }, + user: { + name: "jsmith", + }, + host: { + name: "WS-001", + }, + }, + { + index: { + _index: "process-logs", + }, + }, + { + "@timestamp": "2025-05-20T09:35:00Z", + process: { + name: "net.exe", + command_line: "net user /domain", + parent: { + name: "cmd.exe", + }, + }, + user: { + name: "jsmith", + }, + host: { + name: "SRV-001", + }, + }, + { + index: { + _index: "process-logs", + }, + }, + { + "@timestamp": "2025-05-20T10:50:00Z", + process: { + name: "sqlcmd.exe", + command_line: 'sqlcmd -S localhost -Q "SELECT * FROM customers"', + parent: { + name: "powershell.exe", + }, + }, + user: { + name: "jsmith", + }, + host: { + name: "DB-001", + }, + }, + { + index: { + _index: "process-logs", + }, + }, + { + "@timestamp": "2025-05-20T02:35:00Z", + process: { + name: "ntdsutil.exe", + command_line: 'ntdsutil "ac i ntds" "ifm" "create full c:\\temp\\ntds"', + parent: { + name: "cmd.exe", + }, + }, + user: { + name: "admin", + }, + host: { + name: "DC-001", + }, + }, + { + index: { + _index: "process-logs", + }, + }, + { + "@timestamp": "2025-05-20T12:15:00Z", + process: { + name: "schtasks.exe", + command_line: + "schtasks.exe /create /tn UpdateCheck /tr c:\\windows\\temp\\update.exe /sc daily", + parent: { + name: "cmd.exe", + }, + }, + user: { + name: "jsmith", + }, + host: { + name: "WS-001", + }, + }, + { + index: { + _index: "process-logs", + }, + }, + { + "@timestamp": "2025-05-20T12:30:00Z", + process: { + name: "schtasks.exe", + command_line: + "schtasks.exe /create /tn SystemManager /tr powershell.exe -enc ZQBjAGgAbwAgACIASABlAGwAbABvACIA /sc minute /mo 5", + parent: { + name: "powershell.exe", + }, + }, + user: { + name: "jsmith", + }, + host: { + name: "SRV-001", + }, + }, + { + index: { + _index: "process-logs", + }, + }, + { + "@timestamp": "2025-05-20T13:15:00Z", + process: { + name: "sc.exe", + command_line: + "sc.exe create RemoteService binPath= c:\\windows\\temp\\remote.exe", + parent: { + name: "cmd.exe", + }, + }, + user: { + name: "jsmith", + }, + host: { + name: "DB-001", + }, + }, + { + index: { + _index: "process-logs", + }, + }, + { + "@timestamp": "2025-05-20T13:20:00Z", + process: { + name: "sc.exe", + command_line: + "sc.exe create BackdoorService binPath= c:\\programdata\\svc.exe", + parent: { + name: "powershell.exe", + }, + }, + user: { + name: "jsmith", + }, + host: { + name: "SRV-001", + }, + }, + { + index: { + _index: "process-logs", + }, + }, + { + "@timestamp": "2025-05-20T13:25:00Z", + process: { + name: "sc.exe", + command_line: + "sc.exe create PersistenceService binPath= c:\\windows\\system32\\malicious.exe", + parent: { + name: "cmd.exe", + }, + }, + user: { + name: "admin", + }, + host: { + name: "DC-001", + }, + }, + ], +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-php.md new file mode 100644 index 0000000000..aca7159d9d --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-php.md @@ -0,0 +1,198 @@ +```php +$resp = $client->bulk([ + "refresh" => "wait_for", + "body" => array( + [ + "index" => [ + "_index" => "process-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T08:20:00Z", + "process" => [ + "name" => "powershell.exe", + "command_line" => "powershell.exe -enc JABzAD0ATgBlAHcALgBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAA=", + "parent" => [ + "name" => "winword.exe", + ], + ], + "user" => [ + "name" => "jsmith", + ], + "host" => [ + "name" => "WS-001", + ], + ], + [ + "index" => [ + "_index" => "process-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T09:35:00Z", + "process" => [ + "name" => "net.exe", + "command_line" => "net user /domain", + "parent" => [ + "name" => "cmd.exe", + ], + ], + "user" => [ + "name" => "jsmith", + ], + "host" => [ + "name" => "SRV-001", + ], + ], + [ + "index" => [ + "_index" => "process-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T10:50:00Z", + "process" => [ + "name" => "sqlcmd.exe", + "command_line" => "sqlcmd -S localhost -Q \"SELECT * FROM customers\"", + "parent" => [ + "name" => "powershell.exe", + ], + ], + "user" => [ + "name" => "jsmith", + ], + "host" => [ + "name" => "DB-001", + ], + ], + [ + "index" => [ + "_index" => "process-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T02:35:00Z", + "process" => [ + "name" => "ntdsutil.exe", + "command_line" => "ntdsutil \"ac i ntds\" \"ifm\" \"create full c:\\temp\\ntds\"", + "parent" => [ + "name" => "cmd.exe", + ], + ], + "user" => [ + "name" => "admin", + ], + "host" => [ + "name" => "DC-001", + ], + ], + [ + "index" => [ + "_index" => "process-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T12:15:00Z", + "process" => [ + "name" => "schtasks.exe", + "command_line" => "schtasks.exe /create /tn UpdateCheck /tr c:\\windows\\temp\\update.exe /sc daily", + "parent" => [ + "name" => "cmd.exe", + ], + ], + "user" => [ + "name" => "jsmith", + ], + "host" => [ + "name" => "WS-001", + ], + ], + [ + "index" => [ + "_index" => "process-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T12:30:00Z", + "process" => [ + "name" => "schtasks.exe", + "command_line" => "schtasks.exe /create /tn SystemManager /tr powershell.exe -enc ZQBjAGgAbwAgACIASABlAGwAbABvACIA /sc minute /mo 5", + "parent" => [ + "name" => "powershell.exe", + ], + ], + "user" => [ + "name" => "jsmith", + ], + "host" => [ + "name" => "SRV-001", + ], + ], + [ + "index" => [ + "_index" => "process-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T13:15:00Z", + "process" => [ + "name" => "sc.exe", + "command_line" => "sc.exe create RemoteService binPath= c:\\windows\\temp\\remote.exe", + "parent" => [ + "name" => "cmd.exe", + ], + ], + "user" => [ + "name" => "jsmith", + ], + "host" => [ + "name" => "DB-001", + ], + ], + [ + "index" => [ + "_index" => "process-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T13:20:00Z", + "process" => [ + "name" => "sc.exe", + "command_line" => "sc.exe create BackdoorService binPath= c:\\programdata\\svc.exe", + "parent" => [ + "name" => "powershell.exe", + ], + ], + "user" => [ + "name" => "jsmith", + ], + "host" => [ + "name" => "SRV-001", + ], + ], + [ + "index" => [ + "_index" => "process-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T13:25:00Z", + "process" => [ + "name" => "sc.exe", + "command_line" => "sc.exe create PersistenceService binPath= c:\\windows\\system32\\malicious.exe", + "parent" => [ + "name" => "cmd.exe", + ], + ], + "user" => [ + "name" => "admin", + ], + "host" => [ + "name" => "DC-001", + ], + ], + ), +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-python.md new file mode 100644 index 0000000000..8e3fe1b4b6 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-python.md @@ -0,0 +1,198 @@ +```python +resp = client.bulk( + refresh="wait_for", + operations=[ + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T08:20:00Z", + "process": { + "name": "powershell.exe", + "command_line": "powershell.exe -enc JABzAD0ATgBlAHcALgBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAA=", + "parent": { + "name": "winword.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "WS-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T09:35:00Z", + "process": { + "name": "net.exe", + "command_line": "net user /domain", + "parent": { + "name": "cmd.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "SRV-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T10:50:00Z", + "process": { + "name": "sqlcmd.exe", + "command_line": "sqlcmd -S localhost -Q \"SELECT * FROM customers\"", + "parent": { + "name": "powershell.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "DB-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T02:35:00Z", + "process": { + "name": "ntdsutil.exe", + "command_line": "ntdsutil \"ac i ntds\" \"ifm\" \"create full c:\\temp\\ntds\"", + "parent": { + "name": "cmd.exe" + } + }, + "user": { + "name": "admin" + }, + "host": { + "name": "DC-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T12:15:00Z", + "process": { + "name": "schtasks.exe", + "command_line": "schtasks.exe /create /tn UpdateCheck /tr c:\\windows\\temp\\update.exe /sc daily", + "parent": { + "name": "cmd.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "WS-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T12:30:00Z", + "process": { + "name": "schtasks.exe", + "command_line": "schtasks.exe /create /tn SystemManager /tr powershell.exe -enc ZQBjAGgAbwAgACIASABlAGwAbABvACIA /sc minute /mo 5", + "parent": { + "name": "powershell.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "SRV-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T13:15:00Z", + "process": { + "name": "sc.exe", + "command_line": "sc.exe create RemoteService binPath= c:\\windows\\temp\\remote.exe", + "parent": { + "name": "cmd.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "DB-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T13:20:00Z", + "process": { + "name": "sc.exe", + "command_line": "sc.exe create BackdoorService binPath= c:\\programdata\\svc.exe", + "parent": { + "name": "powershell.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "SRV-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T13:25:00Z", + "process": { + "name": "sc.exe", + "command_line": "sc.exe create PersistenceService binPath= c:\\windows\\system32\\malicious.exe", + "parent": { + "name": "cmd.exe" + } + }, + "user": { + "name": "admin" + }, + "host": { + "name": "DC-001" + } + } + ], +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-ruby.md new file mode 100644 index 0000000000..fb0fd809f0 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example4-ruby.md @@ -0,0 +1,198 @@ +```ruby +response = client.bulk( + refresh: "wait_for", + body: [ + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T08:20:00Z", + "process": { + "name": "powershell.exe", + "command_line": "powershell.exe -enc JABzAD0ATgBlAHcALgBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAA=", + "parent": { + "name": "winword.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "WS-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T09:35:00Z", + "process": { + "name": "net.exe", + "command_line": "net user /domain", + "parent": { + "name": "cmd.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "SRV-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T10:50:00Z", + "process": { + "name": "sqlcmd.exe", + "command_line": "sqlcmd -S localhost -Q \"SELECT * FROM customers\"", + "parent": { + "name": "powershell.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "DB-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T02:35:00Z", + "process": { + "name": "ntdsutil.exe", + "command_line": "ntdsutil \"ac i ntds\" \"ifm\" \"create full c:\\temp\\ntds\"", + "parent": { + "name": "cmd.exe" + } + }, + "user": { + "name": "admin" + }, + "host": { + "name": "DC-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T12:15:00Z", + "process": { + "name": "schtasks.exe", + "command_line": "schtasks.exe /create /tn UpdateCheck /tr c:\\windows\\temp\\update.exe /sc daily", + "parent": { + "name": "cmd.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "WS-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T12:30:00Z", + "process": { + "name": "schtasks.exe", + "command_line": "schtasks.exe /create /tn SystemManager /tr powershell.exe -enc ZQBjAGgAbwAgACIASABlAGwAbABvACIA /sc minute /mo 5", + "parent": { + "name": "powershell.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "SRV-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T13:15:00Z", + "process": { + "name": "sc.exe", + "command_line": "sc.exe create RemoteService binPath= c:\\windows\\temp\\remote.exe", + "parent": { + "name": "cmd.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "DB-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T13:20:00Z", + "process": { + "name": "sc.exe", + "command_line": "sc.exe create BackdoorService binPath= c:\\programdata\\svc.exe", + "parent": { + "name": "powershell.exe" + } + }, + "user": { + "name": "jsmith" + }, + "host": { + "name": "SRV-001" + } + }, + { + "index": { + "_index": "process-logs" + } + }, + { + "@timestamp": "2025-05-20T13:25:00Z", + "process": { + "name": "sc.exe", + "command_line": "sc.exe create PersistenceService binPath= c:\\windows\\system32\\malicious.exe", + "parent": { + "name": "cmd.exe" + } + }, + "user": { + "name": "admin" + }, + "host": { + "name": "DC-001" + } + } + ] +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-console.md new file mode 100644 index 0000000000..3e3a149223 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-console.md @@ -0,0 +1,33 @@ +```console +PUT /network-logs +{ + "mappings": { + "properties": { + "@timestamp": {"type": "date"}, + "source": { + "properties": { + "ip": {"type": "ip"}, + "port": {"type": "integer"} + } + }, + "destination": { + "properties": { + "ip": {"type": "ip"}, + "port": {"type": "integer"} + } + }, + "network": { + "properties": { + "bytes": {"type": "long"}, + "protocol": {"type": "keyword"} + } + }, + "host": { + "properties": { + "name": {"type": "keyword"} + } + } + } + } +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-curl.md new file mode 100644 index 0000000000..f89039e73b --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X PUT "$ELASTICSEARCH_URL/network-logs" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"mappings":{"properties":{"@timestamp":{"type":"date"},"source":{"properties":{"ip":{"type":"ip"},"port":{"type":"integer"}}},"destination":{"properties":{"ip":{"type":"ip"},"port":{"type":"integer"}}},"network":{"properties":{"bytes":{"type":"long"},"protocol":{"type":"keyword"}}},"host":{"properties":{"name":{"type":"keyword"}}}}}}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-js.md new file mode 100644 index 0000000000..27dc07985f --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-js.md @@ -0,0 +1,50 @@ +```js +const response = await client.indices.create({ + index: "network-logs", + mappings: { + properties: { + "@timestamp": { + type: "date", + }, + source: { + properties: { + ip: { + type: "ip", + }, + port: { + type: "integer", + }, + }, + }, + destination: { + properties: { + ip: { + type: "ip", + }, + port: { + type: "integer", + }, + }, + }, + network: { + properties: { + bytes: { + type: "long", + }, + protocol: { + type: "keyword", + }, + }, + }, + host: { + properties: { + name: { + type: "keyword", + }, + }, + }, + }, + }, +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-php.md new file mode 100644 index 0000000000..079c19e4b9 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-php.md @@ -0,0 +1,53 @@ +```php +$resp = $client->indices()->create([ + "index" => "network-logs", + "body" => [ + "mappings" => [ + "properties" => [ + "@timestamp" => [ + "type" => "date", + ], + "source" => [ + "properties" => [ + "ip" => [ + "type" => "ip", + ], + "port" => [ + "type" => "integer", + ], + ], + ], + "destination" => [ + "properties" => [ + "ip" => [ + "type" => "ip", + ], + "port" => [ + "type" => "integer", + ], + ], + ], + "network" => [ + "properties" => [ + "bytes" => [ + "type" => "long", + ], + "protocol" => [ + "type" => "keyword", + ], + ], + ], + "host" => [ + "properties" => [ + "name" => [ + "type" => "keyword", + ], + ], + ], + ], + ], + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-python.md new file mode 100644 index 0000000000..9e30aa3218 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-python.md @@ -0,0 +1,51 @@ +```python +resp = client.indices.create( + index="network-logs", + mappings={ + "properties": { + "@timestamp": { + "type": "date" + }, + "source": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "integer" + } + } + }, + "destination": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "integer" + } + } + }, + "network": { + "properties": { + "bytes": { + "type": "long" + }, + "protocol": { + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "type": "keyword" + } + } + } + } + }, +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-ruby.md new file mode 100644 index 0000000000..9489a00f87 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example5-ruby.md @@ -0,0 +1,53 @@ +```ruby +response = client.indices.create( + index: "network-logs", + body: { + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "source": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "integer" + } + } + }, + "destination": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "integer" + } + } + }, + "network": { + "properties": { + "bytes": { + "type": "long" + }, + "protocol": { + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "type": "keyword" + } + } + } + } + } + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-console.md new file mode 100644 index 0000000000..8596c605f1 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-console.md @@ -0,0 +1,9 @@ +```console +POST /_bulk?refresh=wait_for +{"index":{"_index":"network-logs"}} +{"@timestamp":"2025-05-20T08:25:00Z","source":{"ip":"10.1.1.50","port":52341},"destination":{"ip":"185.220.101.45","port":443},"network":{"bytes":2048,"protocol":"tcp"},"host":{"name":"WS-001"}} +{"index":{"_index":"network-logs"}} +{"@timestamp":"2025-05-20T11:15:00Z","source":{"ip":"10.1.3.5","port":54892},"destination":{"ip":"185.220.101.45","port":443},"network":{"bytes":50000000,"protocol":"tcp"},"host":{"name":"DB-001"}} +{"index":{"_index":"network-logs"}} +{"@timestamp":"2025-05-20T02:40:00Z","source":{"ip":"10.1.4.10","port":61234},"destination":{"ip":"185.220.101.45","port":443},"network":{"bytes":500000000,"protocol":"tcp"},"host":{"name":"DC-001"}} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-curl.md new file mode 100644 index 0000000000..9874cfaa96 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X POST "$ELASTICSEARCH_URL/_bulk?refresh=wait_for" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/x-ndjson" \ + -d $'{"index":{"_index":"network-logs"}}\n{"@timestamp":"2025-05-20T08:25:00Z","source":{"ip":"10.1.1.50","port":52341},"destination":{"ip":"185.220.101.45","port":443},"network":{"bytes":2048,"protocol":"tcp"},"host":{"name":"WS-001"}}\n{"index":{"_index":"network-logs"}}\n{"@timestamp":"2025-05-20T11:15:00Z","source":{"ip":"10.1.3.5","port":54892},"destination":{"ip":"185.220.101.45","port":443},"network":{"bytes":50000000,"protocol":"tcp"},"host":{"name":"DB-001"}}\n{"index":{"_index":"network-logs"}}\n{"@timestamp":"2025-05-20T02:40:00Z","source":{"ip":"10.1.4.10","port":61234},"destination":{"ip":"185.220.101.45","port":443},"network":{"bytes":500000000,"protocol":"tcp"},"host":{"name":"DC-001"}}\n' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-js.md new file mode 100644 index 0000000000..62ca2d5ecb --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-js.md @@ -0,0 +1,77 @@ +```js +const response = await client.bulk({ + refresh: "wait_for", + operations: [ + { + index: { + _index: "network-logs", + }, + }, + { + "@timestamp": "2025-05-20T08:25:00Z", + source: { + ip: "10.1.1.50", + port: 52341, + }, + destination: { + ip: "185.220.101.45", + port: 443, + }, + network: { + bytes: 2048, + protocol: "tcp", + }, + host: { + name: "WS-001", + }, + }, + { + index: { + _index: "network-logs", + }, + }, + { + "@timestamp": "2025-05-20T11:15:00Z", + source: { + ip: "10.1.3.5", + port: 54892, + }, + destination: { + ip: "185.220.101.45", + port: 443, + }, + network: { + bytes: 50000000, + protocol: "tcp", + }, + host: { + name: "DB-001", + }, + }, + { + index: { + _index: "network-logs", + }, + }, + { + "@timestamp": "2025-05-20T02:40:00Z", + source: { + ip: "10.1.4.10", + port: 61234, + }, + destination: { + ip: "185.220.101.45", + port: 443, + }, + network: { + bytes: 500000000, + protocol: "tcp", + }, + host: { + name: "DC-001", + }, + }, + ], +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-php.md new file mode 100644 index 0000000000..0d9277edb3 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-php.md @@ -0,0 +1,78 @@ +```php +$resp = $client->bulk([ + "refresh" => "wait_for", + "body" => array( + [ + "index" => [ + "_index" => "network-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T08:25:00Z", + "source" => [ + "ip" => "10.1.1.50", + "port" => 52341, + ], + "destination" => [ + "ip" => "185.220.101.45", + "port" => 443, + ], + "network" => [ + "bytes" => 2048, + "protocol" => "tcp", + ], + "host" => [ + "name" => "WS-001", + ], + ], + [ + "index" => [ + "_index" => "network-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T11:15:00Z", + "source" => [ + "ip" => "10.1.3.5", + "port" => 54892, + ], + "destination" => [ + "ip" => "185.220.101.45", + "port" => 443, + ], + "network" => [ + "bytes" => 50000000, + "protocol" => "tcp", + ], + "host" => [ + "name" => "DB-001", + ], + ], + [ + "index" => [ + "_index" => "network-logs", + ], + ], + [ + "@timestamp" => "2025-05-20T02:40:00Z", + "source" => [ + "ip" => "10.1.4.10", + "port" => 61234, + ], + "destination" => [ + "ip" => "185.220.101.45", + "port" => 443, + ], + "network" => [ + "bytes" => 500000000, + "protocol" => "tcp", + ], + "host" => [ + "name" => "DC-001", + ], + ], + ), +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-python.md new file mode 100644 index 0000000000..24db926e37 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-python.md @@ -0,0 +1,78 @@ +```python +resp = client.bulk( + refresh="wait_for", + operations=[ + { + "index": { + "_index": "network-logs" + } + }, + { + "@timestamp": "2025-05-20T08:25:00Z", + "source": { + "ip": "10.1.1.50", + "port": 52341 + }, + "destination": { + "ip": "185.220.101.45", + "port": 443 + }, + "network": { + "bytes": 2048, + "protocol": "tcp" + }, + "host": { + "name": "WS-001" + } + }, + { + "index": { + "_index": "network-logs" + } + }, + { + "@timestamp": "2025-05-20T11:15:00Z", + "source": { + "ip": "10.1.3.5", + "port": 54892 + }, + "destination": { + "ip": "185.220.101.45", + "port": 443 + }, + "network": { + "bytes": 50000000, + "protocol": "tcp" + }, + "host": { + "name": "DB-001" + } + }, + { + "index": { + "_index": "network-logs" + } + }, + { + "@timestamp": "2025-05-20T02:40:00Z", + "source": { + "ip": "10.1.4.10", + "port": 61234 + }, + "destination": { + "ip": "185.220.101.45", + "port": 443 + }, + "network": { + "bytes": 500000000, + "protocol": "tcp" + }, + "host": { + "name": "DC-001" + } + } + ], +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-ruby.md new file mode 100644 index 0000000000..8ffb1307e2 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example6-ruby.md @@ -0,0 +1,78 @@ +```ruby +response = client.bulk( + refresh: "wait_for", + body: [ + { + "index": { + "_index": "network-logs" + } + }, + { + "@timestamp": "2025-05-20T08:25:00Z", + "source": { + "ip": "10.1.1.50", + "port": 52341 + }, + "destination": { + "ip": "185.220.101.45", + "port": 443 + }, + "network": { + "bytes": 2048, + "protocol": "tcp" + }, + "host": { + "name": "WS-001" + } + }, + { + "index": { + "_index": "network-logs" + } + }, + { + "@timestamp": "2025-05-20T11:15:00Z", + "source": { + "ip": "10.1.3.5", + "port": 54892 + }, + "destination": { + "ip": "185.220.101.45", + "port": 443 + }, + "network": { + "bytes": 50000000, + "protocol": "tcp" + }, + "host": { + "name": "DB-001" + } + }, + { + "index": { + "_index": "network-logs" + } + }, + { + "@timestamp": "2025-05-20T02:40:00Z", + "source": { + "ip": "10.1.4.10", + "port": 61234 + }, + "destination": { + "ip": "185.220.101.45", + "port": 443 + }, + "network": { + "bytes": 500000000, + "protocol": "tcp" + }, + "host": { + "name": "DC-001" + } + } + ] +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-console.md new file mode 100644 index 0000000000..304e81f4b1 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-console.md @@ -0,0 +1,16 @@ +```console +PUT /asset-inventory +{ + "mappings": { + "properties": { + "host.name": {"type": "keyword"}, + "asset.criticality": {"type": "keyword"}, + "asset.owner": {"type": "keyword"}, + "asset.department": {"type": "keyword"} + } + }, + "settings": { + "index.mode": "lookup" + } +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-curl.md new file mode 100644 index 0000000000..95bb64c4c7 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X PUT "$ELASTICSEARCH_URL/asset-inventory" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"mappings":{"properties":{"host.name":{"type":"keyword"},"asset.criticality":{"type":"keyword"},"asset.owner":{"type":"keyword"},"asset.department":{"type":"keyword"}}},"settings":{"index.mode":"lookup"}}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-js.md new file mode 100644 index 0000000000..c3dad6a165 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-js.md @@ -0,0 +1,25 @@ +```js +const response = await client.indices.create({ + index: "asset-inventory", + mappings: { + properties: { + "host.name": { + type: "keyword", + }, + "asset.criticality": { + type: "keyword", + }, + "asset.owner": { + type: "keyword", + }, + "asset.department": { + type: "keyword", + }, + }, + }, + settings: { + "index.mode": "lookup", + }, +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-php.md new file mode 100644 index 0000000000..34e457be16 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-php.md @@ -0,0 +1,28 @@ +```php +$resp = $client->indices()->create([ + "index" => "asset-inventory", + "body" => [ + "mappings" => [ + "properties" => [ + "host.name" => [ + "type" => "keyword", + ], + "asset.criticality" => [ + "type" => "keyword", + ], + "asset.owner" => [ + "type" => "keyword", + ], + "asset.department" => [ + "type" => "keyword", + ], + ], + ], + "settings" => [ + "index.mode" => "lookup", + ], + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-python.md new file mode 100644 index 0000000000..371879c484 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-python.md @@ -0,0 +1,26 @@ +```python +resp = client.indices.create( + index="asset-inventory", + mappings={ + "properties": { + "host.name": { + "type": "keyword" + }, + "asset.criticality": { + "type": "keyword" + }, + "asset.owner": { + "type": "keyword" + }, + "asset.department": { + "type": "keyword" + } + } + }, + settings={ + "index.mode": "lookup" + }, +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-ruby.md new file mode 100644 index 0000000000..fca06b7cd7 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example7-ruby.md @@ -0,0 +1,28 @@ +```ruby +response = client.indices.create( + index: "asset-inventory", + body: { + "mappings": { + "properties": { + "host.name": { + "type": "keyword" + }, + "asset.criticality": { + "type": "keyword" + }, + "asset.owner": { + "type": "keyword" + }, + "asset.department": { + "type": "keyword" + } + } + }, + "settings": { + "index.mode": "lookup" + } + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-console.md new file mode 100644 index 0000000000..6a9dd43d06 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-console.md @@ -0,0 +1,16 @@ +```console +PUT /user-context +{ + "mappings": { + "properties": { + "user.name": {"type": "keyword"}, + "user.role": {"type": "keyword"}, + "user.department": {"type": "keyword"}, + "user.privileged": {"type": "boolean"} + } + }, + "settings": { + "index.mode": "lookup" + } +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-curl.md new file mode 100644 index 0000000000..289e56dcef --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X PUT "$ELASTICSEARCH_URL/user-context" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"mappings":{"properties":{"user.name":{"type":"keyword"},"user.role":{"type":"keyword"},"user.department":{"type":"keyword"},"user.privileged":{"type":"boolean"}}},"settings":{"index.mode":"lookup"}}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-js.md new file mode 100644 index 0000000000..e2fe2d1af8 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-js.md @@ -0,0 +1,25 @@ +```js +const response = await client.indices.create({ + index: "user-context", + mappings: { + properties: { + "user.name": { + type: "keyword", + }, + "user.role": { + type: "keyword", + }, + "user.department": { + type: "keyword", + }, + "user.privileged": { + type: "boolean", + }, + }, + }, + settings: { + "index.mode": "lookup", + }, +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-php.md new file mode 100644 index 0000000000..e2a4d77798 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-php.md @@ -0,0 +1,28 @@ +```php +$resp = $client->indices()->create([ + "index" => "user-context", + "body" => [ + "mappings" => [ + "properties" => [ + "user.name" => [ + "type" => "keyword", + ], + "user.role" => [ + "type" => "keyword", + ], + "user.department" => [ + "type" => "keyword", + ], + "user.privileged" => [ + "type" => "boolean", + ], + ], + ], + "settings" => [ + "index.mode" => "lookup", + ], + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-python.md new file mode 100644 index 0000000000..e80a32dea8 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-python.md @@ -0,0 +1,26 @@ +```python +resp = client.indices.create( + index="user-context", + mappings={ + "properties": { + "user.name": { + "type": "keyword" + }, + "user.role": { + "type": "keyword" + }, + "user.department": { + "type": "keyword" + }, + "user.privileged": { + "type": "boolean" + } + } + }, + settings={ + "index.mode": "lookup" + }, +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-ruby.md new file mode 100644 index 0000000000..266d4b06f0 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example8-ruby.md @@ -0,0 +1,28 @@ +```ruby +response = client.indices.create( + index: "user-context", + body: { + "mappings": { + "properties": { + "user.name": { + "type": "keyword" + }, + "user.role": { + "type": "keyword" + }, + "user.department": { + "type": "keyword" + }, + "user.privileged": { + "type": "boolean" + } + } + }, + "settings": { + "index.mode": "lookup" + } + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-console.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-console.md new file mode 100644 index 0000000000..14465bb68f --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-console.md @@ -0,0 +1,16 @@ +```console +PUT /threat-intel +{ + "mappings": { + "properties": { + "indicator.value": {"type": "keyword"}, + "indicator.type": {"type": "keyword"}, + "threat.name": {"type": "keyword"}, + "threat.severity": {"type": "keyword"} + } + }, + "settings": { + "index.mode": "lookup" + } +} +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-curl.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-curl.md new file mode 100644 index 0000000000..519516a7cb --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-curl.md @@ -0,0 +1,6 @@ +```bash +curl -X PUT "$ELASTICSEARCH_URL/threat-intel" \ + -H "Authorization: ApiKey $ELASTIC_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"mappings":{"properties":{"indicator.value":{"type":"keyword"},"indicator.type":{"type":"keyword"},"threat.name":{"type":"keyword"},"threat.severity":{"type":"keyword"}}},"settings":{"index.mode":"lookup"}}' +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-js.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-js.md new file mode 100644 index 0000000000..0dd5e1d8ff --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-js.md @@ -0,0 +1,25 @@ +```js +const response = await client.indices.create({ + index: "threat-intel", + mappings: { + properties: { + "indicator.value": { + type: "keyword", + }, + "indicator.type": { + type: "keyword", + }, + "threat.name": { + type: "keyword", + }, + "threat.severity": { + type: "keyword", + }, + }, + }, + settings: { + "index.mode": "lookup", + }, +}); +console.log(response); +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-php.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-php.md new file mode 100644 index 0000000000..a323f2d6c5 --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-php.md @@ -0,0 +1,28 @@ +```php +$resp = $client->indices()->create([ + "index" => "threat-intel", + "body" => [ + "mappings" => [ + "properties" => [ + "indicator.value" => [ + "type" => "keyword", + ], + "indicator.type" => [ + "type" => "keyword", + ], + "threat.name" => [ + "type" => "keyword", + ], + "threat.severity" => [ + "type" => "keyword", + ], + ], + ], + "settings" => [ + "index.mode" => "lookup", + ], + ], +]); +echo $resp->asString(); + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-python.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-python.md new file mode 100644 index 0000000000..f303551dbe --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-python.md @@ -0,0 +1,26 @@ +```python +resp = client.indices.create( + index="threat-intel", + mappings={ + "properties": { + "indicator.value": { + "type": "keyword" + }, + "indicator.type": { + "type": "keyword" + }, + "threat.name": { + "type": "keyword" + }, + "threat.severity": { + "type": "keyword" + } + } + }, + settings={ + "index.mode": "lookup" + }, +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-ruby.md b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-ruby.md new file mode 100644 index 0000000000..139939623e --- /dev/null +++ b/solutions/security/esql-for-security/_snippets/esql-threat-hunting-tutorial/example9-ruby.md @@ -0,0 +1,28 @@ +```ruby +response = client.indices.create( + index: "threat-intel", + body: { + "mappings": { + "properties": { + "indicator.value": { + "type": "keyword" + }, + "indicator.type": { + "type": "keyword" + }, + "threat.name": { + "type": "keyword" + }, + "threat.severity": { + "type": "keyword" + } + } + }, + "settings": { + "index.mode": "lookup" + } + } +) +print(resp) + +``` diff --git a/solutions/security/esql-for-security/esql-threat-hunting-tutorial.md b/solutions/security/esql-for-security/esql-threat-hunting-tutorial.md index d780bfdc1c..38e25e8dc2 100644 --- a/solutions/security/esql-for-security/esql-threat-hunting-tutorial.md +++ b/solutions/security/esql-for-security/esql-threat-hunting-tutorial.md @@ -21,41 +21,24 @@ Following a simulated Advanced Persistent Threat (APT) campaign, we analyze secu ::::{admonition} Requirements You need a running {{es}} cluster, together with {{kib}} to run this tutorial. Refer to [choose your deployment type](/deploy-manage/deploy.md#choosing-your-deployment-type) for deployment options. -:::: +:::::: ## How to run {{esql}} queries -In this tutorial, {{esql}} examples are displayed in the following format: - -```esql -FROM windows-security-logs -| WHERE event.code == "4624" -| LIMIT 1000 -``` - -You can run these queries using: +You have multiple options for running {{esql}} queries: - **Interactive interfaces**: - - [Timeline](/solutions/security/investigate/timeline.md#esql-in-timeline). Find **Timelines** in the navigation menu or by using the [global search field](/explore-analyze/find-and-organize/find-apps-and-objects.md). - - [Discover](/explore-analyze/discover/try-esql.md). Find **Discover** in the navigation menu or by using the [global search field](/explore-analyze/find-and-organize/find-apps-and-objects.md). - -- **REST API** via [Dev Tools Console](elasticsearch://reference/query-languages/esql/esql-rest.md#esql-kibana-console). This requires additional formatting: - :::{dropdown} View Console syntax for {{esql}} - ```console - POST /_query?format=txt - { - "query": """ - FROM windows-security-logs - | WHERE event.code == "4624" - | LIMIT 1000 - """ - } - ``` - ::: + - [Timeline](/solutions/security/investigate/timeline.md#esql-in-timeline) + - [Discover](/explore-analyze/discover/try-esql.md) +- **REST API**: + - [Dev Tools Console](elasticsearch://reference/query-languages/esql/esql-rest.md#esql-kibana-console) + - [{{es}} HTTP clients](/solutions/search/site-or-app/clients.md) + - [`curl`](https://curl.se/) + ## Step 0: Add sample data -To follow along with this tutorial, you need to add sample data to your cluster, using the [Dev Tools Console](elasticsearch://reference/query-languages/esql/esql-rest.md#esql-kibana-console). +To follow along with this tutorial, you need to add sample data to your cluster, using the {{es}} REST API and the Dev Tools [Console](/explore-analyze/query-filter/tools/console.md) or your preferred HTTP client. Broadly, there are two types of data: @@ -66,166 +49,261 @@ Broadly, there are two types of data: First, create the core security indices for our threat hunting scenario: -```console -PUT /windows-security-logs -{ - "mappings": { - "properties": { - "@timestamp": {"type": "date"}, - "event": { - "properties": { - "code": {"type": "keyword"}, # Event codes like 4624 (successful logon) and 4625 (failed logon) are stored as keywords for exact matching. - "action": {"type": "keyword"} - } - }, - "user": { - "properties": { - "name": {"type": "keyword"}, - "domain": {"type": "keyword"} - } - }, - "host": { - "properties": { - "name": {"type": "keyword"}, - "ip": {"type": "ip"} - } - }, - "source": { - "properties": { - "ip": {"type": "ip"} - } - }, - "logon": { - "properties": { - "type": {"type": "keyword"} - } - } - } - } -} -``` +::::{tab-set} +:group: api-examples + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example1-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example1-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example1-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example1-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example1-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example1-ruby.md +::: + +:::: Now let's add some sample data to the `windows-security-logs` index around authentication events, namely failed and successful logins. -```console -POST /_bulk?refresh=wait_for -{"index":{"_index":"windows-security-logs"}} -{"@timestamp":"2025-05-20T08:15:00Z","event":{"code":"4625","action":"logon_failed"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"WS-001","ip":"10.1.1.50"},"source":{"ip":"10.1.1.100"}} -{"index":{"_index":"windows-security-logs"}} -{"@timestamp":"2025-05-20T08:17:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"WS-001","ip":"10.1.1.50"},"source":{"ip":"10.1.1.100"},"logon":{"type":"3"}} -{"index":{"_index":"windows-security-logs"}} -{"@timestamp":"2025-05-20T09:30:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"SRV-001","ip":"10.1.2.10"},"source":{"ip":"10.1.1.50"},"logon":{"type":"3"}} -{"index":{"_index":"windows-security-logs"}} -{"@timestamp":"2025-05-20T10:45:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"jsmith","domain":"corp"},"host":{"name":"DB-001","ip":"10.1.3.5"},"source":{"ip":"10.1.2.10"},"logon":{"type":"3"}} -{"index":{"_index":"windows-security-logs"}} -{"@timestamp":"2025-05-20T02:30:00Z","event":{"code":"4624","action":"logon_success"},"user":{"name":"admin","domain":"corp"},"host":{"name":"DC-001","ip":"10.1.4.10"},"source":{"ip":"10.1.3.5"},"logon":{"type":"3"}} -``` +::::{tab-set} +:group: api-examples + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example2-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example2-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example2-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example2-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example2-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example2-ruby.md +::: + +:::: Next, create an index for process execution logs. -```console -PUT /process-logs -{ - "mappings": { - "properties": { - "@timestamp": {"type": "date"}, - "process": { - "properties": { - "name": {"type": "keyword"}, - "command_line": {"type": "text"}, # Command lines are stored as text fields to enable full-text search for suspicious parameters and encoded commands. - "parent": { - "properties": { - "name": {"type": "keyword"} - } - } - } - }, - "user": { - "properties": { - "name": {"type": "keyword"} - } - }, - "host": { - "properties": { - "name": {"type": "keyword"} - } - } - } - } -} -``` +::::{tab-set} +:group: api-examples + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example3-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example3-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example3-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example3-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example3-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example3-ruby.md +::: + +:::: Add some sample data to the `process-logs` index. -```console -POST /_bulk?refresh=wait_for -{"index":{"_index":"process-logs"}} -{"@timestamp":"2025-05-20T08:20:00Z","process":{"name":"powershell.exe","command_line":"powershell.exe -enc JABzAD0ATgBlAHcALgBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAA=","parent":{"name":"winword.exe"}},"user":{"name":"jsmith"},"host":{"name":"WS-001"}} -{"index":{"_index":"process-logs"}} -{"@timestamp":"2025-05-20T09:35:00Z","process":{"name":"net.exe","command_line":"net user /domain","parent":{"name":"cmd.exe"}},"user":{"name":"jsmith"},"host":{"name":"SRV-001"}} -{"index":{"_index":"process-logs"}} -{"@timestamp":"2025-05-20T10:50:00Z","process":{"name":"sqlcmd.exe","command_line":"sqlcmd -S localhost -Q \"SELECT * FROM customers\"","parent":{"name":"powershell.exe"}},"user":{"name":"jsmith"},"host":{"name":"DB-001"}} -{"index":{"_index":"process-logs"}} -{"@timestamp":"2025-05-20T02:35:00Z","process":{"name":"ntdsutil.exe","command_line":"ntdsutil \"ac i ntds\" \"ifm\" \"create full c:\\temp\\ntds\"","parent":{"name":"cmd.exe"}},"user":{"name":"admin"},"host":{"name":"DC-001"}} -{"index":{"_index":"process-logs"}} -{"@timestamp":"2025-05-20T12:15:00Z","process":{"name":"schtasks.exe","command_line":"schtasks.exe /create /tn UpdateCheck /tr c:\\windows\\temp\\update.exe /sc daily","parent":{"name":"cmd.exe"}},"user":{"name":"jsmith"},"host":{"name":"WS-001"}} -{"index":{"_index":"process-logs"}} -{"@timestamp":"2025-05-20T12:30:00Z","process":{"name":"schtasks.exe","command_line":"schtasks.exe /create /tn SystemManager /tr powershell.exe -enc ZQBjAGgAbwAgACIASABlAGwAbABvACIA /sc minute /mo 5","parent":{"name":"powershell.exe"}},"user":{"name":"jsmith"},"host":{"name":"SRV-001"}} -{"index":{"_index":"process-logs"}} -{"@timestamp":"2025-05-20T13:15:00Z","process":{"name":"sc.exe","command_line":"sc.exe create RemoteService binPath= c:\\windows\\temp\\remote.exe","parent":{"name":"cmd.exe"}},"user":{"name":"jsmith"},"host":{"name":"DB-001"}} -{"index":{"_index":"process-logs"}} -{"@timestamp":"2025-05-20T13:20:00Z","process":{"name":"sc.exe","command_line":"sc.exe create BackdoorService binPath= c:\\programdata\\svc.exe","parent":{"name":"powershell.exe"}},"user":{"name":"jsmith"},"host":{"name":"SRV-001"}} -{"index":{"_index":"process-logs"}} -{"@timestamp":"2025-05-20T13:25:00Z","process":{"name":"sc.exe","command_line":"sc.exe create PersistenceService binPath= c:\\windows\\system32\\malicious.exe","parent":{"name":"cmd.exe"}},"user":{"name":"admin"},"host":{"name":"DC-001"}} -``` +::::{tab-set} +:group: api-examples + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example4-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example4-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example4-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example4-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example4-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example4-ruby.md +::: + +:::: Next, create an index for network traffic logs. -```console -PUT /network-logs -{ - "mappings": { - "properties": { - "@timestamp": {"type": "date"}, - "source": { - "properties": { - "ip": {"type": "ip"}, - "port": {"type": "integer"} - } - }, - "destination": { - "properties": { - "ip": {"type": "ip"}, - "port": {"type": "integer"} - } - }, - "network": { - "properties": { - "bytes": {"type": "long"}, - "protocol": {"type": "keyword"} - } - }, - "host": { - "properties": { - "name": {"type": "keyword"} - } - } - } - } -} -``` +::::{tab-set} +:group: api-examples + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example5-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example5-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example5-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example5-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example5-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example5-ruby.md +::: + +:::: Add some sample data to the `network-logs` index. -```console -POST /_bulk?refresh=wait_for -{"index":{"_index":"network-logs"}} -{"@timestamp":"2025-05-20T08:25:00Z","source":{"ip":"10.1.1.50","port":52341},"destination":{"ip":"185.220.101.45","port":443},"network":{"bytes":2048,"protocol":"tcp"},"host":{"name":"WS-001"}} -{"index":{"_index":"network-logs"}} -{"@timestamp":"2025-05-20T11:15:00Z","source":{"ip":"10.1.3.5","port":54892},"destination":{"ip":"185.220.101.45","port":443},"network":{"bytes":50000000,"protocol":"tcp"},"host":{"name":"DB-001"}} -{"index":{"_index":"network-logs"}} -{"@timestamp":"2025-05-20T02:40:00Z","source":{"ip":"10.1.4.10","port":61234},"destination":{"ip":"185.220.101.45","port":443},"network":{"bytes":500000000,"protocol":"tcp"},"host":{"name":"DC-001"}} -``` +::::{tab-set} +:group: api-examples + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example6-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example6-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example6-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example6-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example6-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example6-ruby.md +::: + +:::: ### Create lookup indices @@ -233,100 +311,222 @@ The lookup mode enables these indices to be used with [`LOOKUP JOIN`](elasticsea Create the indices we need with the `lookup` index mode. -```console -PUT /asset-inventory -{ - "mappings": { - "properties": { - "host.name": {"type": "keyword"}, - "asset.criticality": {"type": "keyword"}, - "asset.owner": {"type": "keyword"}, - "asset.department": {"type": "keyword"} - } - }, - "settings": { - "index.mode": "lookup" - } -} -``` - -```console -PUT /user-context -{ - "mappings": { - "properties": { - "user.name": {"type": "keyword"}, - "user.role": {"type": "keyword"}, - "user.department": {"type": "keyword"}, - "user.privileged": {"type": "boolean"} - } - }, - "settings": { - "index.mode": "lookup" - } -} -``` - -```console -PUT /threat-intel -{ - "mappings": { - "properties": { - "indicator.value": {"type": "keyword"}, - "indicator.type": {"type": "keyword"}, - "threat.name": {"type": "keyword"}, - "threat.severity": {"type": "keyword"} - } - }, - "settings": { - "index.mode": "lookup" - } -} -``` +::::{tab-set} +:group: api-examples + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example7-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example7-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example7-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example7-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example7-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example7-ruby.md +::: + +:::: + +::::{tab-set} +:group: api-examples + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example8-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example8-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example8-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example8-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example8-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example8-ruby.md +::: + +:::: + +::::{tab-set} +:group: api-examples + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example9-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example9-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example9-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example9-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example9-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example9-ruby.md +::: + +:::: Now we can populate the lookup indices with contextual data. This single bulk operation indexes data into the `user-context`, `threat-intel` and `asset-inventory` indices with one request. -```console -POST /_bulk?refresh=wait_for -{"index":{"_index":"asset-inventory"}} -{"host.name":"WS-001","asset.criticality":"medium","asset.owner":"IT","asset.department":"finance"} -{"index":{"_index":"asset-inventory"}} -{"host.name":"SRV-001","asset.criticality":"high","asset.owner":"IT","asset.department":"operations"} -{"index":{"_index":"asset-inventory"}} -{"host.name":"DB-001","asset.criticality":"critical","asset.owner":"DBA","asset.department":"finance"} -{"index":{"_index":"asset-inventory"}} -{"host.name":"DC-001","asset.criticality":"critical","asset.owner":"IT","asset.department":"infrastructure"} -{"index":{"_index":"user-context"}} -{"user.name":"jsmith","user.role":"analyst","user.department":"finance","user.privileged":false} -{"index":{"_index":"user-context"}} -{"user.name":"admin","user.role":"administrator","user.department":"IT","user.privileged":true} -{"index":{"_index":"threat-intel"}} -{"indicator.value":"185.220.101.45","indicator.type":"ip","threat.name":"APT-29","threat.severity":"high"} -{"index":{"_index":"threat-intel"}} -{"indicator.value":"powershell.exe","indicator.type":"process","threat.name":"Living off the Land","threat.severity":"medium"} -``` +::::{tab-set} +:group: api-examples + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example10-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example10-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example10-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example10-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example10-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example10-ruby.md +::: + +:::: ## Step 1: Hunt for initial compromise indicators The first phase of our hunt focuses on identifying the initial compromise. We want to search for suspicious PowerShell execution from Office applications, which is a common initial attack vector. -```esql -FROM process-logs -| WHERE process.name == "powershell.exe" AND process.parent.name LIKE "*word*" <1> -| LOOKUP JOIN asset-inventory ON host.name <2> -| LOOKUP JOIN user-context ON user.name <3> -| EVAL encoded_command = CASE(process.command_line LIKE "*-enc*", true, false) <4> -| WHERE encoded_command == true <5> -| STATS count = COUNT(*) BY host.name, user.name, asset.criticality <6> -| LIMIT 1000 -``` - -1. Uses [`WHERE`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-where) with [`==`](elasticsearch://reference/query-languages/esql/functions-operators/operators.md#esql-equals) and [`LIKE`](elasticsearch://reference/query-languages/esql/functions-operators/operators.md#esql-like) operators to detect PowerShell processes -2. Enriches using [`LOOKUP JOIN`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-lookup-join) with asset inventory -3. Enriches with user context using `LOOKUP JOIN` -4. Uses [`EVAL`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-eval) and [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case) to detect encoded commands -5. Additional filtering with [`WHERE`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-where) -6. Aggregates results with [`STATS`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-stats-by) and [`COUNT`](elasticsearch://reference/query-languages/esql/functions-operators/aggregation-functions.md#esql-count) grouped by multiple fields +::::{tab-set} +:group: api-examples + +:::{tab-item} ES|QL +:sync: esql + +:::{include} _snippets/esql-threat-hunting-tutorial/example11-esql.md +::: + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example11-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example11-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example11-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example11-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example11-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example11-ruby.md +::: + +:::: **Response** @@ -345,30 +545,52 @@ In this step, we track user authentication across multiple systems. This is impo This query demonstrates how [`DATE_TRUNC`](elasticsearch://reference/query-languages/esql/functions-operators/date-time-functions.md#esql-date_trunc) creates time windows for velocity analysis, combining [`COUNT_DISTINCT`](elasticsearch://reference/query-languages/esql/functions-operators/aggregation-functions.md#esql-count_distinct) aggregations with [`DATE_DIFF`](elasticsearch://reference/query-languages/esql/functions-operators/date-time-functions.md#esql-date_diff) calculations to measure both the scope and speed of user movement across network assets. -```esql -FROM windows-security-logs -| WHERE event.code == "4624" AND logon.type == "3" <1> -| LOOKUP JOIN asset-inventory ON host.name -| EVAL time_bucket = DATE_TRUNC(30 minute, @timestamp) <2> -| STATS unique_hosts = COUNT_DISTINCT(host.name), - criticality_levels = COUNT_DISTINCT(asset.criticality), - active_periods = COUNT_DISTINCT(time_bucket), - first_login = MIN(@timestamp), - last_login = MAX(@timestamp) -BY user.name <3> -| WHERE unique_hosts > 2 -| EVAL time_span_hours = DATE_DIFF("hour", first_login, last_login) <4> -| EVAL movement_velocity = ROUND(unique_hosts / (time_span_hours + 1), 2) -| EVAL lateral_movement_score = unique_hosts * criticality_levels <5> -| SORT lateral_movement_score DESC -| LIMIT 1000 -``` - -1. Uses [`WHERE`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-where) for basic authentication filtering -2. Creates time buckets with [`DATE_TRUNC`](elasticsearch://reference/query-languages/esql/functions-operators/date-time-functions.md#esql-date_trunc) for temporal analysis -3. Uses [`STATS`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-stats-by) with [`COUNT_DISTINCT`](elasticsearch://reference/query-languages/esql/functions-operators/aggregation-functions.md#esql-count_distinct) for comprehensive access metrics -4. Uses [`DATE_DIFF`](elasticsearch://reference/query-languages/esql/functions-operators/date-time-functions.md#esql-date_diff) for duration calculations -5. Uses [`EVAL`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-eval) with [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case)for risk scoring +::::{tab-set} +:group: api-examples + +:::{tab-item} ES|QL +:sync: esql + +:::{include} _snippets/esql-threat-hunting-tutorial/example12-esql.md +::: + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example12-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example12-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example12-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example12-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example12-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example12-ruby.md +::: + +:::: **Response** @@ -383,34 +605,52 @@ unique_hosts |criticality_levels|active_periods | first_login | Advanced attackers often target sensitive data. We want to hunt for database access and large data transfers to external systems. -```esql -FROM network-logs -| WHERE NOT CIDR_MATCH(destination.ip, "10.0.0.0/8", "192.168.0.0/16") <1> -| EVAL indicator.value = TO_STRING(destination.ip) <2> -| LOOKUP JOIN threat-intel ON indicator.value -| LOOKUP JOIN asset-inventory ON host.name -| WHERE threat.name IS NOT NULL -| STATS total_bytes = SUM(network.bytes), - connection_count = COUNT(*), - time_span = DATE_DIFF("hour", MIN(@timestamp), MAX(@timestamp)) <3> -BY host.name, destination.ip, threat.name, asset.criticality -| EVAL mb_transferred = ROUND(total_bytes / 1048576, 2) <4> -| EVAL risk_score = CASE( - asset.criticality == "critical" AND mb_transferred > 100, 10, - asset.criticality == "high" AND mb_transferred > 100, 7, - mb_transferred > 50, 5, - 3 - ) <5> -| WHERE total_bytes > 1000000 -| SORT risk_score DESC, total_bytes DESC -| LIMIT 1000 -``` - -1. Uses [`CIDR_MATCH`](elasticsearch://reference/query-languages/esql/functions-operators/ip-functions.md#esql-cidr_match) to filter internal IP ranges for external data transfer detection -2. Uses [`TO_STRING`](elasticsearch://reference/query-languages/esql/functions-operators/type-conversion-functions.md#esql-to_string) to standardize IP format for threat intel lookups -3. Uses [`DATE_DIFF`](elasticsearch://reference/query-languages/esql/functions-operators/date-time-functions.md#esql-date_diff) with `SUM` and `COUNT` to measure data transfer volume over time -4. Uses [`ROUND`](elasticsearch://reference/query-languages/esql/functions-operators/math-functions.md#esql-round) for human-readable values -5. Uses [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case) for risk scoring based on asset criticality and size of data transferred +::::{tab-set} +:group: api-examples + +:::{tab-item} ES|QL +:sync: esql + +:::{include} _snippets/esql-threat-hunting-tutorial/example13-esql.md +::: + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example13-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example13-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example13-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example13-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example13-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example13-ruby.md +::: + +:::: **Response** @@ -427,38 +667,57 @@ The response shows external data transfers, their risk scores, and the amount of To understand the attack progression, we need to build a timeline of events across multiple indices. This helps us correlate actions and identify the attacker's dwell time. -```esql -FROM windows-security-logs, process-logs, network-logs <1> -| LOOKUP JOIN asset-inventory ON host.name -| LOOKUP JOIN user-context ON user.name -| WHERE user.name == "jsmith" OR user.name == "admin" -| EVAL event_type = CASE( - event.code IS NOT NULL, "Authentication", - process.name IS NOT NULL, "Process Execution", - destination.ip IS NOT NULL, "Network Activity", - "Unknown") <2> -| EVAL dest_ip = TO_STRING(destination.ip) -| EVAL attack_stage = CASE( - process.parent.name LIKE "*word*", "Initial Compromise", - process.name IN ("net.exe", "nltest.exe"), "Reconnaissance", - event.code == "4624" AND logon.type == "3", "Lateral Movement", - process.name IN ("sqlcmd.exe", "ntdsutil.exe"), "Data Access", - dest_ip NOT LIKE "10.*", "Exfiltration", - "Other") <3> -| SORT @timestamp ASC <4> -| KEEP @timestamp, event_type, attack_stage, host.name, asset.criticality, user.name, process.name, destination.ip -| LIMIT 1000 -``` - -1. Uses `FROM` with multiple indices for comprehensive correlation -2. Uses [`IS NOT NULL`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#null-predicates) with [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case) to classify event types from different data sources -3. Uses complex [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case) logic to map events to MITRE ATT&CK stages -4. Uses [`SORT`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-sort) to build chronological attack timeline +::::{tab-set} +:group: api-examples + +:::{tab-item} ES|QL +:sync: esql + +:::{include} _snippets/esql-threat-hunting-tutorial/example14-esql.md +::: + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example14-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example14-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example14-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example14-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example14-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example14-ruby.md +::: + +:::: **Response** The response provides a chronological timeline of events, showing the attacker's actions and the impact on the organization. -:::{dropdown} View response +::::{dropdown} View response | @timestamp | event_type | attack_stage | host.name | asset.criticality | user.name | process.name | destination.ip | |------------|------------|--------------|-----------|-------------------|-----------|--------------|----------------| @@ -476,35 +735,58 @@ The response provides a chronological timeline of events, showing the attacker's | 2025-05-20T13:15:00.000Z | Process Execution | Other | DB-001 | critical | jsmith | sc.exe | null | | 2025-05-20T13:20:00.000Z | Process Execution | Other | SRV-001 | high | jsmith | sc.exe | null | | 2025-05-20T13:25:00.000Z | Process Execution | Other | DC-001 | critical | admin | sc.exe | null | -::: +:::: ## Step 5: Hunt for unusual interpreter usage This query demonstrates how {{esql}}'s [COUNT_DISTINCT](elasticsearch://reference/query-languages/esql/functions-operators/aggregation-functions.md#esql-count_distinct) function and conditional [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case) statements can be used to baseline interpreter usage patterns across users and departments, using aggregation functions to identify anomalous script execution that might indicate compromised accounts or insider threats. -```esql -FROM process-logs -| WHERE process.name IN ("powershell.exe", "cmd.exe", "net.exe", "sqlcmd.exe", "schtasks.exe", "sc.exe") <1> -| LOOKUP JOIN asset-inventory ON host.name -| LOOKUP JOIN user-context ON user.name -| STATS executions = COUNT(*), - unique_hosts = COUNT_DISTINCT(host.name), - unique_commands = COUNT_DISTINCT(process.name) <3> -BY user.name, user.department -| WHERE executions > 1 -| EVAL usage_pattern = CASE( - executions > 5, "High Usage", - executions > 3, "Moderate Usage", - "Low Usage" - ) <4> -| SORT executions DESC -| LIMIT 1000 -``` - -1. Uses `WHERE...IN` to monitor high-risk system tools -2. Uses [`LOOKUP JOIN`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-lookup-join) with `asset-inventory` and `user-context` indices to enrich events with context -3. Uses [`COUNT_DISTINCT`](elasticsearch://reference/query-languages/esql/functions-operators/aggregation-functions.md#esql-count_distinct) to measure breadth of suspicious tool usage -4. Uses [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case)to classify usage patterns for anomaly detection +::::{tab-set} +:group: api-examples + +:::{tab-item} ES|QL +:sync: esql + +:::{include} _snippets/esql-threat-hunting-tutorial/example15-esql.md +::: + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example15-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example15-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example15-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example15-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example15-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example15-ruby.md +::: + +:::: **Response** @@ -518,29 +800,52 @@ The response shows the number of executions, unique hosts, and usage patterns fo This query showcases how [`DATE_TRUNC`](elasticsearch://reference/query-languages/esql/functions-operators/date-time-functions.md#esql-date_trunc) enables temporal analysis of persistence mechanisms, using time bucketing and [`COUNT_DISTINCT`](elasticsearch://reference/query-languages/esql/functions-operators/aggregation-functions.md#esql-count_distinct) to identify suspicious patterns like rapid-fire task creation or persistence establishment across multiple time windows. -```esql -FROM process-logs -| WHERE process.name == "schtasks.exe" AND process.command_line:"/create" <1> -| LOOKUP JOIN asset-inventory ON host.name -| LOOKUP JOIN user-context ON user.name -| EVAL time_bucket = DATE_TRUNC(1 hour, @timestamp) <2> -| STATS task_creations = COUNT(*), - creation_hours = COUNT_DISTINCT(time_bucket) <3> -BY user.name, host.name, asset.criticality -| WHERE task_creations > 0 -| EVAL persistence_pattern = CASE( - creation_hours > 1, "Multiple Hours", - task_creations > 1, "Burst Creation", - "Single Task" - ) <4> -| SORT task_creations DESC -| LIMIT 1000 -``` - -1. Uses [`WHERE`](elasticsearch://reference/query-languages/esql/commands/processing-commands.md#esql-where) with [`:`](elasticsearch://reference/query-languages/esql/functions-operators/operators.md#esql-match-operator) match operator to detect scheduled task creation (a common persistence mechanism) -2. Uses [`DATE_TRUNC`](elasticsearch://reference/query-languages/esql/functions-operators/date-time-functions.md#esql-date_trunc) to group events into hourly time buckets for temporal analysis -3. Uses [`COUNT_DISTINCT`](elasticsearch://reference/query-languages/esql/functions-operators/aggregation-functions.md#esql-count_distinct) with `time_bucket` to measure task creation velocity -4. Uses [`CASE`](elasticsearch://reference/query-languages/esql/functions-operators/conditional-functions-and-expressions.md#esql-case) to classify suspicious patterns based on timing and frequency +::::{tab-set} +:group: api-examples + +:::{tab-item} ES|QL +:sync: esql + +:::{include} _snippets/esql-threat-hunting-tutorial/example16-esql.md +::: + +:::{tab-item} Console +:sync: console + +:::{include} _snippets/esql-threat-hunting-tutorial/example16-console.md +::: + +:::{tab-item} curl +:sync: curl + +:::{include} _snippets/esql-threat-hunting-tutorial/example16-curl.md +::: + +:::{tab-item} Python +:sync: python + +:::{include} _snippets/esql-threat-hunting-tutorial/example16-python.md +::: + +:::{tab-item} JavaScript +:sync: js + +:::{include} _snippets/esql-threat-hunting-tutorial/example16-js.md +::: + +:::{tab-item} PHP +:sync: php + +:::{include} _snippets/esql-threat-hunting-tutorial/example16-php.md +::: + +:::{tab-item} Ruby +:sync: ruby + +:::{include} _snippets/esql-threat-hunting-tutorial/example16-ruby.md +::: + +:::: **Response** @@ -562,6 +867,6 @@ The response shows the number of task creations, creation hours, and persistence - [Detecting covert data exfiltration techniques](https://www.elastic.co/blog/elastic-security-detecting-covert-data-exfiltration) -:::{tip} +::::{tip} To learn where you can use {{esql}} in {{elastic-sec}} contexts, refer to [the overview](/solutions/security/esql-for-security.md#documentation). -::: \ No newline at end of file +:::: \ No newline at end of file