Something detection engineers (working with Defender XDR) has been waiting for is being able to use other static data to enrich queries. In Sentinel we have been able to use watchlists for a long time but on the Defender side this has been missing.
Some short updates (this give value):
_GetWatchlist('ExcludedIPs')
So what happens when you configure a new detection?
The detection wizard is showing and one of the new things here is the Frequenzy “Custom” which is familiar from a Sentinel perspective.
When it comes to lookback time, there are a few things to know:
In the next step, the Alert settings we have the opportunity to dynamically set the Provider name and other custom details (key value pairs)
We can also do detailed entity mapping as well as “Related Evidence” mapping. This means Entities that are related to the alert, but are not impacted assets.
Depending on data source we can also configure automated actions (as before).
We recommend you to AVOID doing isolate device as an automated action, things can go wrong fast.
The background is not new in anyway, this is more on how to combine information about existing vulnerability and find exploitation of the vulnerability.
Let’s do a refresh on unquoted service paths and why it’s dangerous…
If you’ve done any amount of Windows privilege escalation, you’ve probably come across unquoted service paths. It’s one of those vulnerabilities that feels almost too simple-but it still shows up in real environments far more often than it should.
//Query to list all services vulnerable to unquoted service path
DeviceTvmSecureConfigurationAssessment
| where ConfigurationId == "scid-3001"
| where IsApplicable == 1 and IsCompliant == 0
| extend ParsedContext = parse_json(Context)
| mv-expand ServiceEntry = ParsedContext
| extend ServiceName = tostring(ServiceEntry[0]), ServicePathRaw = tostring(ServiceEntry[1])
| extend ServicePathRawClean = tolower(replace_string(ServicePathRaw, "/", "\\"))
| where ServicePathRawClean contains " "
| where not(ServicePathRawClean startswith "\"")
| extend ServicePath = trim(@" """, ServicePathRawClean)
| where ServicePath matches regex @"^[a-zA-Z]:\\.*\.exe$"
| summarize count() by ServicePath
So how does this work?
Let’s break down how it works, why it’s exploitable, and how to actually abuse it in a real scenario.
The Root Cause In Windows, services are defined with a binary path (Image Path) that tells the Service Control Manager (SCM) what executable to run.
A typical service path might look like this: C:\Program Files\Example App\service.exe
If this path is not enclosed in quotes, Windows does something really dangerous. Instead of treating the full string as a the single path, Windows parses it by splitting on spaces and tries to execute each possibility in order. So this: C:\Program Files\Example App\service.exe
Gets interpreted like this: C:\Program.exe C:\Program Files\Example.exe C:\Program Files\Example App\service.exe
Windows will execute the first match it finds. That behavior is the entire vulnerability.
Why does this become a Privilege Escalation? Many services run as LocalSystem or another highly privileged account. If you can influence what binary gets executed, you effectively get code execution at that privilege level.
So the attack becomes very straightforward:
Find a service with an unquoted path
Identify writable directories in the path chain
Drop a malicious executable with the right name
Restart the service (or wait for reboot)
If Windows hits your binary first, it executes it as SYSTEM. Game over.
You might think that no user has write access to a subfolder in Program Files… Normally no, but some apps sets ACLs that allow any user to write there (this is in you application management process to check if this happens)
Real Example
Let’s say a service is configured like this: PathName: C:\Program Files\Vendor Corp\Service App\appservice.exe – No quotes. Windows will try:
Now imagine you have write permissions to: C:\Program Files\Vendor Corp\ You can drop: Service.exe And when the service starts, Windows executes your payload instead of the legitimate binary.
Finding Vulnerable Services
You don’t need fancy tools to identify these. A simple command works:
Services running as SYSTEM (or at least higher privileges, or as a user you want to impersonate)
Services that are restartable
Exploitation in Practice Once you’ve found a candidate, the next step is checking permissions. You need write access to one of the directories in the path chain. Tools like icacls or accesschk (from Sysinternals) are useful here. Example:
icacls "C:\Program Files\Vendor Corp"
If you can write there, you’re good to go
Create a payload (e.g., reverse shell, add admin user, etc.)
Name it according to the parsed path (Service.exe, Vendor.exe, etc.)
Drop it in the directory
Restart the service:
sc stop Vendorervicesc start AcmeService
If you don’t have permission to restart it, you wait for System reboot, Scheduled restart etc
Detection and Defense
From a defensive perspective, this is one of the easiest issues to fix-and one of the easiest to miss. The fix is trivial: “C:\Program Files\Venfdor Corp\Service App\appservice.exe” Just add quotes.
Using Defender to find the vulnerabilities
In Defender portal and Vulnerability management there are recommendations related to configurations that should be fixed
The configuration ID for unquoted service paths is: scid-3001 (This ID can be used in advanced hunting
But only finding the vulnerable devices is not enough. It would be even better to also find exploitation attempts which can be done by enumerating all possibilities for each service path and then check for file writes using DeviceFileEvents, Process executions from DeviceProcessEvents.
Below is a query to detect if someone tries to exploit unquoted service paths, but don’t forget, you need to adapt your process to identify these issues when introducing a new software in your environment
// Detect Unquoted Service Path Abuse
// Identifies vulnerable unquoted service paths(scid-3001) and correlates them with executions from possible abuse paths.
// Generates all executable candidates from space paths and detects when services executes those.
// Added enrichment with file activity when available.
//
// DefenderBoys
let Lookback = 30d;
let UnquotedServicePathService =
DeviceTvmSecureConfigurationAssessment
| where ConfigurationId == "scid-3001"
| where Timestamp >= ago(Lookback)
| where IsApplicable == 1 and IsCompliant == 0
| extend ParsedContext = parse_json(Context)
| mv-expand ServiceEntry = ParsedContext
| extend ServiceName = tostring(ServiceEntry[0]), ServicePathRaw = tostring(ServiceEntry[1])
| extend ServicePathRawClean = tolower(replace_string(ServicePathRaw, "/", "\\"))
| where ServicePathRawClean contains " "
| where not(ServicePathRawClean startswith "\"")
| extend ServicePath = trim(@" """, ServicePathRawClean)
| where ServicePath matches regex @"^[a-zA-Z]:\\.*\.exe$"
| extend CandidateIndexes = range(0, strlen(ServicePath) - 1, 1)
| mv-apply CandidateIndex = CandidateIndexes on (
extend SpaceIndex = toint(CandidateIndex)
| where substring(ServicePath, SpaceIndex, 1) == " "
| extend CandidatePrefix = substring(ServicePath, 0, SpaceIndex)
| extend HijackFolderPath = strcat(CandidatePrefix, ".exe")
| where HijackFolderPath matches regex @"^[a-zA-Z]:\\.*\.exe$"
| where HijackFolderPath != ServicePath
| project DeviceId, DeviceName, ServiceName, ServicePath, HijackFolderPath
)
| summarize by DeviceId, DeviceName, ServiceName, ServicePath, HijackFolderPath;
let ServiceHijackExecutions =
DeviceProcessEvents
| where Timestamp >= ago(Lookback)
| where InitiatingProcessFileName =~ "services.exe"
| extend ExecutedPath = tolower(FolderPath)
| project ExecutionTime = Timestamp, DeviceId, DeviceName, ExecutedPath, FileName, FolderPath, ProcessCommandLine, SHA1, SHA256, MD5, FileSize, ProcessVersionInfoCompanyName, ProcessVersionInfoProductName, ProcessVersionInfoOriginalFileName, AccountDomain, AccountName, AccountUpn, ProcessIntegrityLevel, ProcessTokenElevation, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessIntegrityLevel, InitiatingProcessTokenElevation, ReportId;
let FileActivityOnHijackFolderPath =
// Adding File activities to provide more data into an eventual incident
DeviceFileEvents
| where Timestamp >= ago(Lookback)
| where FolderPath matches regex @"^[a-zA-Z]:\\.*\.exe$"
| extend CurrentFolderPath = tolower(FolderPath)
| extend PreviousPath = tolower(iff(ActionType =~ "FileRenamed" and isnotempty(PreviousFolderPath) and isnotempty(PreviousFileName), strcat(PreviousFolderPath, "\\", PreviousFileName), ""))
| project Timestamp, DeviceId, DeviceName, FileActionType=ActionType, CurrentFolderPath, PreviousPath, FileName, SHA1, SHA256, MD5, FileSize, FileOriginUrl, FileOriginReferrerUrl, FileOriginIP, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessAccountUpn, InitiatingProcessFolderPath, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessIntegrityLevel, InitiatingProcessTokenElevation, InitiatingProcessParentFileName, RequestProtocol, RequestSourceIP, RequestSourcePort, RequestAccountName, RequestAccountDomain, ShareName, ReportId;
UnquotedServicePathService
| join kind=inner ServiceHijackExecutions on DeviceId
| where ExecutedPath == HijackFolderPath
| join kind=leftouter FileActivityOnHijackFolderPath on DeviceId // File events is just extra not mandatory
| where CurrentFolderPath == HijackFolderPath or isempty(CurrentFolderPath)
| extend FileActivityBeforeExecution = iff(isnotempty(Timestamp) and Timestamp <= ExecutionTime, true, false)
| extend FileActivityType = case(
CurrentFolderPath == HijackFolderPath and FileActionType =~ "FileCreated", "Hijack file created",
CurrentFolderPath == HijackFolderPath and FileActionType =~ "FileModified", "Hijack file modified",
CurrentFolderPath == HijackFolderPath and FileActionType =~ "FileDeleted", "Hijack file deleted",
CurrentFolderPath == HijackFolderPath and FileActionType =~ "FileRenamed", "Hijack file renamed into place",
isempty(FileActionType), "No matching file event",
"N/A"
)
| project ExecutionTime, DeviceName, ServiceName, VulnerableServicePath = ServicePath, ExpectedHijackFolderPath = HijackFolderPath, ExecutedPath, FileActivityTimestamp = Timestamp, FileActivityBeforeExecution, FileActivityType, CurrentFolderPath, PreviousPath, FileName, FolderPath, ProcessCommandLine, SHA1, SHA256, MD5, FileSize, ProcessVersionInfoCompanyName, ProcessVersionInfoProductName, ProcessVersionInfoOriginalFileName, AccountDomain, AccountName, AccountUpn, ProcessIntegrityLevel, ProcessTokenElevation, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessIntegrityLevel, InitiatingProcessTokenElevation, InitiatingProcessParentFileName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessAccountUpn, InitiatingProcessFolderPath, FileOriginUrl, FileOriginReferrerUrl, FileOriginIP, RequestProtocol, RequestSourceIP, RequestSourcePort, RequestAccountName, RequestAccountDomain, ShareName, ReportId
| order by ExecutionTime desc
Over and out – Defender Boys
Update – Some queries are uploaded to this github repository:
Microsoft has published a set of new security functions (user-defined) in KQL. These are focused on the graph mindset, meaning relationships between nodes (objects). A previous blogpost explains graph semantics a bit more:
Mattias from the duo DefenderBoys (DefenderBoys.com) (Stefan Schörling & Mattias Borg) was asked by Microsoft to join Heike Ritters Ninja Show to talk about, and demo Attack Disruption and Evilginx and MFA bypass
When working with both Threat Hunting and Detection Engineering it has been a challenge to dynamically get domain administrators and domain controllers. Sometimes, a valid guess was the best approach.
With the Exposure Graph data, we can dynamically retrieve these entities.
Another thing which will simplify your queries is to save this as a function
It uses Defender XDR’s ability to correlate signals from many different sources into a single, high-confidence incident through insights from endpoints, identities, email and collaboration tools, and SaaS apps.
It identifies assets controlled by the attacker and used to spread the attack.
It automatically takes response actions across relevant Microsoft Defender products to contain the attack in real-time by containing and disabling affected assets.
With all the capabilities provided within Attack Disruption, it’s important that Defender XDR is configured correctly. For instance, if you have configured a gMSA account for the MDI actions, and it does not work properly, it will affect disruption negatively.
MDE Prereqs
Minimum Sense version for contain user:v10.8470
Auto IR: Full – remediate threats automatically (recommended for full coverage, no automated response turns off automatic disruption for device group)
Endpoint discovery set to Standard (not basic)
In MDE the most common configuration improvements seen are:
Sample submission is not configured to send files automatically
The speed of how new useful functionalities in Microsoft Defender XDR, previously Microsoft 365 Defender, are being developed is very high. From this perspective it is super important to send feedback, not only things that may not work as you expected or if there is an error, but also new feature requests.
Some new features which was released in GA in February is within the incident management space.
Change incident severity
When a incident is being generated, the severity is based on the alert with highest severity. If the severity is wrong, you can change it by opening the manage incident which will open the incident pane.
Assign incident to a group
Instead of only assign the incident to a specific individual (who might be on a leave), it is now possible to assign the incident to a group by opening the manage incident which will open the incident pane.
Go hunt directly from attack story
When selecting an item in the attack story, you will get an option for “Go Hunt” which will give you the options to choose between All activities, Related alerts and See all available queries
When selecting a query, you will have the response in the same window. The positive thing with this is that you don’t have to move away from the incident view. If you want to continue the hunting you have the option to “Open in advanced hunting”
Last year Microsoft announced a deception capability in Microsoft Defender for Endpoint. The idea with the deception is that adversaries access a Decoys or Lure which will trigger an incident for the response team to act on.
In Settings > Endpoints > Advanced features
Enable Deception
To create Deception rules
In Settings > Endpoints > Deception rules
It is possible to scope this specific deception rule to Devices with a specific tag
The system will automatically generate Alias or Hostnames which can be edited to better fit your organization
Lures can be autogenerated or use custom lures (file size up to 10MB)
A Lure can be of any filetype except PE files (exe and dll) It is recommended that the lure contains information of decoys.
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept”, you consent to the use of ALL the cookies.
This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.