Threat hunting in Elastic with JOINs!
Elastic’s piped query language, ES|QL, brings joins to the party

Threat hunters rejoice! Have you been looking for a way to join data with the speed and power of Elastic? Well, we heard you! Elastic can now join data sources with a new function for the piped query language, ES|QL (Elasticsearch Query Language). This will enable robust searches that range from advanced behavior detections to alert triage and of course, threat hunting.
Why JOINS matter in security: It’s all about context
Elastic has been providing comprehensive search capabilities to help users investigate security events, analyze logs, and explore data at scale. One capability plays a key role in advancing detection and investigation: JOINs — because context is crucial.
JOINs allow analysts, detection engineers, and threat hunters to create more context-aware logic on top of condition-based queries. Consider the following questions: In which department does this person work, and does their job require them to run PowerShell? Does this malware event involve a critical user, like a Domain Administrator? Is this phishing campaign directed at specific roles or business units?
All of these questions rely on data outside of the alert. And JOINs are how practitioners bring that context in — connecting the dots across data sources to make smarter, faster decisions while hunting, investigating and responding.
JOINs, evolved: LOOKUP in ES|QL
Elastic’s existing enrichment policies have supported JOIN-like workflows for a while, but maintaining enrichment jobs and managing updates added friction, like the requirement to re-execute an enrichment policy after every update. Now, with the introduction of the LOOKUP JOIN function in ES|QL, things got a lot easier. This new function gives analysts a faster, more intuitive way to bring external data into a query. No preprocessing or external steps required — just JOIN what you need, when you need it, right in the search workflow.
Helping security analysts at every step
Security practitioners focus on many things to protect their organization’s data. Let’s examine how joining assists in a traditional end to end workflow: (1) Finding New Suspicious Activity, (2) Triaging Detection Alerts, and (3) Conducting an Incident Response.
1. Finding the threat: Hypothesize, search, correlate
Threat hunters and detection engineers are protecting their systems by finding suspicious behaviors. The threat hunting workflow focuses on a scientific model to find malicious activity. First, establish a hypothesis, review your results, and determine if an incident has occurred.
Threat hunting can include machine learning and simplistic searching, but often data stack analysis is a part of it. The Elasticsearch Query Language (ES|QL) has brought aggregations to the query alongside the ability to transform your data — and now the ability to add results from a separate data store to your results. This is why joining is so critical. Often analysts and threat hunters are juggling multiple data sources and need to correlate the data. There is likely a shared parameter, like a host, user, file, process, etc. But how do you look for lateral movement between authentication logs and process execution? How do you look for data exfiltration between net flow data and file creation? Joining allows the threat hunter to find anomalous behaviors across a multitude of data.
2. Triaging the alerts: Focus on what matters
Next, security analysts are reviewing the output from threat hunters and detection engineers in the form of alerts. Alert triage is another use case for joining. It is well established that analysts suffer from alert fatigue from an abundance of alerts. Yes, Elastic is solving this issue through generative AI and Attack Discovery alongside a context-enriched UX, but sometimes it is necessary to prioritize from an external or additional data source. Joining plays a role here, too. Now, a user can compare alert metadata to threat intelligence feeds, prioritizing those alerts with known malicious indicators. Or what about alerts from a specific asset inventory? Now an analyst can prioritize business elements that deal with a majority of external communication and are targets for phishing attempts.
3. Responding to incidents: Move fast, with the full picture
Incident response is often about filling in the blanks. Early in an investigation, responders are working with fragments of the complete story being told with findings attached to an active Case. That’s where JOINs become essential. During incident response, teams need to connect information across systems — authentication logs, endpoint telemetry, network data, threat intel, asset inventory — and correlate it in real time. Without JOINs, responders are stuck stitching things together manually or jumping between tools. Using LOOKUP JOINs, incident responders can:
Enrich alerts with asset ownership or sensitivity level
Correlate endpoint activity with known malware indicators
Trace a user's movement across multiple systems from a single query
It’s a faster path to understanding root cause and coordinating next steps.
The LOOKUP JOIN syntax in ES|QL
Before diving into some security example queries, let’s first review how to use this new ES|QL function. ES|QL is a piped language that allows the user to build a series of operations within a single search while filtering, transforming, and aggregating data. Joining itself was previously solved using enrichment policies or the ENRICH command with ES|QL. Although these solved some joining use cases, our users needed an easier path to joining data without requiring enrichment configurations.
The new join capability occurs when using the LOOKUP JOIN function. Here you are able to establish a lookup index, which will add data to your search results depending on the join key. As you compose your query using the ES|QL pipes, you can simply establish the lookup to add results to your current query and effectively join two data sources. For more information, please consult our documentation or additional content, Joins are here!
Here’s a simple security use case with LOOKUP JOINs. Let’s look for Elastic Security alerts that contain known threat feed indicators. In this scenario, our threat feed data store will be called “threat-match.”
FROM *.alerts-security*
| EVAL indicator.id = file.name
| LOOKUP JOIN threat-match on indicator.id
Results will contain all the alert metadata you’d expect, including:
Content from the rule: name, description, severity, etc.
The original event, host info, user info, process info, file info, and network info
- And now, the threat feed information from the lookup: indicator name, indicator value, indicator type, indicator description, etc.
Let’s try it!
To truly flex the power of ES|QL and the LOOKUP JOIN, let’s revisit the Security scenarios where joining would prove most effective.
Using the AI Assistant to guide the way
Firstly, using the AI Assistant can always help. We can ask the Assistant to first explain more about ES|QL LOOKUP JOINs and then ask for help enriching alerts with the current user risk score. Let’s check it out!

Note: Please use the most recent version of Elastic Security to get the most updated AI Assistant for Security experience.
Alert triage examples
Let’s continue with the alert triage use case. When viewing security alerts, analysts are always looking for ways to prioritize, so why not look for those users with privileged access? We will look for alerts on Windows systems and then lookup results in the Entity Analytics Active Directory integration to see privileged group members.

Additional examples can focus on usage of MITRE ATT&CK™. We will utilize Elastic Entity Risk Scoring to prioritize critical hosts generating alerts spanning multiple MITRE ATT&CK tactics (Initial Access, Persistence, Defense Evasion, etc.).

Threat hunting queries
Now, let’s assume we don’t have alerts and need to hunt using LOOKUP JOINs. Recall that part of the Elastic DNA is openness. We provide our detection rules in the public view via the following repository, and the Elastic Security Labs team has even extended this to include threat hunting. For more information, read how to elevate your threat hunting.
Let’s start with one of those threat hunting queries, like Windows Persistence via Run Keys. What if we enriched these queries by joining with asset information? In this scenario, we will start from the prebuilt threat hunting query and then fine tune to our use case by expanding the date range and looking at all persistence examples. Then by adding an entity lookup, we can find the asset location of our hosts.

If you want to simplify the example further, we could reduce the query to look at all startup processes and perform the LOOKUP JOIN as well, where we can see the asset location like before.

Living off the land techniques
Many threat hunters search for living off the land process execution, which represents local tools available on a system that can execute arbitrary code. Although these tools can be used by administrators for benign reasons, attackers can use these processes to evade detection. Now with LOOKUP JOINS, we can create a list of these files for our threat hunting. This workflow allows us to continually add new processes to our newly created LOOKUP index while preserving the logic of our query.
1. First create the LOOKUP index. Here is an example:
PUT /lolbins-lookup
{
"mappings": {
"properties": {
"process.name": {"type": "keyword"},
"lolbin": {"type": "boolean"}
}
},
"settings": {"index.mode": "lookup"}
}
2. Then we can add data to the index with the bulk upload.
POST /_bulk
{ "index" : { "_index" : "lolbins-lookup" }}
{ "process.name" : "powershell.exe", "lolbin": true }
{ "index" : { "_index" : "lolbins-lookup" }}
{ "process.name" : "cmd.exe", "lolbin": true }
...
3. And now we can search for living off the land processes in our network with LOOKUP JOINS! This now means we can use a LOOKUP to match against a list of indicators. Recall that LOOKUP indices can be updated, so as you discover more living off the land techniques, simply add to the index and you don’t have to change your query or execute an enrich policy to see new results.

In conclusion
LOOKUP JOINs are already proving to be extremely useful in many security workflows, but this is just the beginning. As noted in the Elasticsearch blog, this type of join most closely aligns with a SQL-style LEFT OUTER JOIN, and in addition to improving the overall user experiences, we’d also like to extend the join capabilities (like INNER joins). And furthermore, this feature has enabled the syntax conversion of other search languages. Please read our recent blog on Automatic Migration for more information!
To summarize, this new joining capability is another tool for the security analyst toolbox, as Elastic Search AI technology provides many advanced techniques to protect your data from attack. This includes generative AI, machine learning, aggregations, enrichments, sequence-based correlations, and so much more. Continue to follow along for more updates and information! The fastest and easiest way to experience Elastic Security is on Elastic Cloud Serverless. Try it now.
The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.
In this blog post, we may have used or referred to third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.
Elastic, Elasticsearch, and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.