Agent Foskett Academy • Lesson 19 • Using has_any to Find Suspicious Text

Using has_any to Find Suspicious Text

Not every investigation starts with an exact match.
Sometimes you are looking for words.
Suspicious commands.
Phishing phrases.
Strange URL fragments.

That is where has_any becomes useful.

In this Agent Foskett Academy lesson, you will learn how defenders use the KQL has_any operator to search for multiple suspicious text values across Microsoft Defender XDR and Microsoft Sentinel telemetry.

Agent Foskett Academy lesson explaining how to use has_any to find suspicious text with KQL
Lesson overview

Learn how to search text fields for multiple suspicious words, commands and fragments without writing long chains of contains statements.

Search multiple words
Find suspicious commands
Reduce noisy text searches
🧠 Text fields can hide the clue.
has_any helps defenders search multiple suspicious words or phrases without making the query hard to read.
Review Lesson 18 →

Why has_any matters

In real investigations, suspicious activity is often hidden inside text fields.

A command line may contain PowerShell flags. A URL may contain login words. An email subject may include urgent language. A file path may contain a suspicious folder name.

The has_any operator lets you search a text field for any value from a list of terms.
Cleaner text searchesUse one list of terms instead of repeating contains or has many times.
Great for huntingSearch command lines, subjects, URLs, file paths and process arguments for suspicious words.
Easy to tuneAdd or remove suspicious terms as the investigation develops.

Investigation scenario

An analyst is reviewing suspicious endpoint activity.

Several command lines include strange PowerShell behaviour, encoded commands and download activity. The analyst wants to search for multiple suspicious terms without creating a messy query.

Step 1 — The messy way with contains

This works, but it quickly becomes hard to maintain as the list of suspicious terms grows.
messy-contains-search.kql
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
DeviceProcessEvents
| where Timestamp > ago(7d)
| where ProcessCommandLine contains "encodedcommand"
    or ProcessCommandLine contains "downloadstring"
    or ProcessCommandLine contains "invoke-webrequest"
| project Timestamp, DeviceName, FileName, ProcessCommandLine, InitiatingProcessAccountName

Step 2 — The cleaner way with has_any

has_any checks whether the text field contains any term from the list.
clean-has-any-search.kql
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
DeviceProcessEvents
| where Timestamp > ago(7d)
| where ProcessCommandLine has_any ("encodedcommand", "downloadstring", "invoke-webrequest")
| project Timestamp, DeviceName, FileName, ProcessCommandLine, InitiatingProcessAccountName

Step 3 — Use has_any with a let statement

For a cleaner investigation, place the suspicious terms into a reusable list.
suspicious-term-list.kql
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
let suspiciousTerms = dynamic(["encodedcommand", "downloadstring", "invoke-webrequest", "bypass"]);
DeviceProcessEvents
| where Timestamp > ago(7d)
| where ProcessCommandLine has_any (suspiciousTerms)
| project Timestamp, DeviceName, InitiatingProcessAccountName, FileName, ProcessCommandLine
| sort by Timestamp desc

What has_any does

The has_any operator returns rows where a text field contains at least one term from the supplied list.

Think of it as asking: does this field contain any of these suspicious words?
Text field firstPlace the field you want to search on the left side, such as ProcessCommandLine or Url.
Terms secondPlace the suspicious words or fragments inside the list or a reusable dynamic variable.
Any match returnsIf one of the listed terms appears in the field, the row is returned.

Step 4 — Search suspicious email subjects

has_any can help identify email campaigns that use common phishing words or urgent language.
subject-word-search.kql
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
let subjectTerms = dynamic(["urgent", "password", "invoice", "verify", "payment"]);
EmailEvents
| where Timestamp > ago(7d)
| where Subject has_any (subjectTerms)
| project Timestamp, SenderFromAddress, RecipientEmailAddress, Subject, DeliveryAction
| sort by Timestamp desc

Step 5 — Search suspicious URL text

You can use has_any to search URL fields for suspicious words that commonly appear in credential theft pages.
url-text-search.kql
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
let urlTerms = dynamic(["login", "verify", "account", "secure", "password"]);
UrlClickEvents
| where Timestamp > ago(7d)
| where Url has_any (urlTerms)
| project Timestamp, AccountUpn, Url, ActionType, ThreatTypes
| sort by Timestamp desc

Step 6 — Combine has_any with useful columns

Keep the output focused so another analyst can quickly understand what was found.
focused-has-any-output.kql
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
let suspiciousTerms = dynamic(["encodedcommand", "downloadstring", "invoke-webrequest", "bypass"]);
DeviceProcessEvents
| where Timestamp > ago(7d)
| where ProcessCommandLine has_any (suspiciousTerms)
| project Timestamp, DeviceName, Account = InitiatingProcessAccountName, FileName, ProcessCommandLine
| sort by Timestamp desc

Investigator notes

Use has_any when you want to search text fields for multiple suspicious words or terms.

If you need exact matching against known values such as IP addresses, domains, users or hashes, in may be the better operator.
Great for textUse has_any for command lines, subjects, URLs, file paths and descriptions.
Not always exactUse in when you need to match a known value exactly rather than search text.
Keep lists sensibleStart with a small list of strong terms, then tune it as you review the results.
🎓 Agent Foskett Academy — Find the suspicious words
You now understand how to use has_any to search for multiple suspicious text values in KQL investigations.
Return to Academy

What you learned

In this lesson, you learned how to use the KQL has_any operator to find suspicious text.
Using has_anySearch a text field for any term from a suspicious word list.
Using term listsCombine has_any with let and dynamic lists to make hunts easier to update.
Choosing the right operatorUse has_any for text searches and in for exact indicator matching.

Continue your investigation

The next step is learning how to use case statements to classify investigation results and make findings easier to understand.
Agent Foskett Academy Return to the full Academy learning path and review earlier KQL foundation lessons.
Using in to Search Multiple Indicators Review how exact indicator lists help defenders search known users, IP addresses, devices and domains.

Continue learning with Creating Investigation Parameters, Using let Statements to Reuse Evidence, KQL Threat Hunting Guide, UrlClickEvents and Microsoft Security.

Develop IT. Protect IT. GEMXIT PTY LTD | GEMXIT UK LTD

Using has_any to Find Suspicious Text

Agent Foskett Academy Lesson 19 teaches defenders how to use the KQL has_any operator to search suspicious words, commands, URL terms and text patterns in Microsoft security telemetry.

Learn KQL has_any for Microsoft Defender XDR

The KQL has_any operator helps analysts search text fields for multiple suspicious terms across Microsoft Defender XDR and Microsoft Sentinel investigations.

KQL suspicious text search lesson for Microsoft security analysts

This Agent Foskett Academy lesson explains how to use has_any, let statements and dynamic term lists to find suspicious command lines, email subjects and URLs.