Feb 16, 2026·6 min read·20 visits
The Windows Telephony Service trusts user input blindly when setting up async notification channels. Instead of a mailslot, attackers can pass a file path. The service then writes 4 bytes of attacker-controlled data to that file as 'NETWORK SERVICE'. This is a text-book write-what-where primitive leading to EoP.
A logic flaw in the Windows Telephony Service (TapiSrv) allows for an arbitrary file write primitive via the ClientAttach RPC method. By failing to validate the 'pszDomainUser' parameter, the service allows an attacker to direct asynchronous event notifications—specifically a 4-byte attacker-controlled DWORD—to any file writable by the NETWORK SERVICE account. This can be leveraged for Local Privilege Escalation (LPE) to SYSTEM or Remote Code Execution (RCE) in specific server configurations.
In the year of our Lord 2026, you might be asking yourself: "Why does Windows still have a Telephony Service?" The answer is the same reason win32k.sys is still a mess of spaghetti code—backward compatibility is a hell of a drug. The Telephony Application Programming Interface (TAPI) is the subsystem responsible for managing modems, faxes, and voice calls. While you're busy using Teams or Zoom, tapisrv.dll is quietly humming along in the background, running as NETWORK SERVICE inside an svchost.exe container, waiting for the 90s to call.
But here's the kicker: TapiSrv isn't just a dusty relic; it exposes a rich RPC interface (tapsrv) over a named pipe. Security researchers love RPC interfaces because they are often the soft underbelly of Windows services. They parse complex structures, handle state, and, in this case, manage asynchronous callbacks. And as we all know, handling callbacks in Windows is like juggling chainsaws—eventually, someone drops one, and it usually lands on the stack.
Enter CVE-2026-20931. This isn't a complex heap feng-shui exploit involving 17 different allocation grooming primitives. No, this is much stupider. This is the code equivalent of a bank teller asking a robber, "Where would you like me to deposit this money?" and actually listening when the robber says, "In my bag."
To understand the vulnerability, we have to look at how TapiSrv handles asynchronous events. When a client (like a TAPI application) wants to know about incoming calls or line changes, it needs a way for the server to call it back. Typically, RPC interfaces use context handles or dedicated callback interfaces for this. TapiSrv, being an architectural antique, uses Mailslots.
When a client connects via the ClientAttach RPC method, it provides a string parameter called pszDomainUser. Ostensibly, this string is supposed to be the name of a mailslot (e.g., \\.\mailslot\MyTAPIClient) where the server should send notifications. The server takes this string and passes it directly to CreateFileW with GENERIC_WRITE permissions.
Here lies the logic bomb: There is no validation. The code never checks if the string actually starts with \\.\mailslot\. It just takes whatever path you give it and tries to open it. If you pass C:\Windows\Temp\oops.txt, the service dutifully opens a handle to that file. If you pass a UNC path to a remote SMB share, it tries to authenticate to it. It is a blind file-open primitive running with NETWORK SERVICE privileges.
Let's look at the disassembly provided by the researchers at PT Swarm. The vulnerability is painfully obvious once you know where to look. In tapisrv.dll, the handler for ClientAttach does roughly this:
// The "Before" Code (Vulnerable)
if (wcslen(pszDomainUser) > 0)
{
// Direct pass-through of user input to CreateFileW
if ((ptClient->hMailslot = CreateFileW(
pszDomainUser, // <--- TAINTED INPUT
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
)) != INVALID_HANDLE_VALUE)
{
// Proceed to store the handle
goto ClientAttach_AddClientToList;
}
}The pszDomainUser variable is fully controlled by the attacker via the RPC packet. There is no wcsstr looking for "mailslot", no Access Control List (ACL) check, nothing. It just assumes the caller is a gentleman playing by the rules.
The patch acts as a sanity check. Microsoft likely added a wcsncmp or IsMailslot verification step to ensure the path targets the Mailslot device namespace before calling CreateFileW. This kills the arbitrary file write because you can't simply create a file named C:\Windows\System32\... inside the \Device\Mailslot namespace.
So we can open a file. That's cool, but we need to write to it to cause damage. TapiSrv is helpful here. Once the handle is open, the service uses it to send event notifications. Specifically, when a TAPI event occurs, the service writes a specific data structure to this handle.
The attack chain, as detailed by PT Swarm, works like this:
ClientAttach via RPC. Set pszDomainUser to the path of the file you want to corrupt (e.g., a service DLL or a config file writable by NETWORK SERVICE).Initialize method. This allows you to set an InitContext, which is a 4-byte DWORD. This is our payload. We control these 4 bytes.LRegisterRequestRecipient to tell the service, "Hey, send me updates about phone calls."TRequestMakeCall to simulate a telephony event.When the event triggers, TapiSrv takes our handle (the file we targeted) and writes our InitContext (the 4 bytes we chose) into it.
While a 4-byte write sounds limited, it is often enough to corrupt a binary header, flip a configuration flag, or modify a pointer if the target file structure is known. If the attacker can find a DLL that NETWORK SERVICE can write to (often found in poorly secured third-party software directories), they can overwrite the entry point or code section, leading to code execution when that DLL is loaded.
The primary impact here is Local Privilege Escalation (LPE). An attacker starting with low privileges can elevate to SYSTEM. The NETWORK SERVICE account is not SYSTEM, but it has significant privileges, including network access and write permissions to various temporary and system directories. By hijacking a file used by a higher-privileged process, the attacker bridges the gap.
However, the nightmare scenario is Remote Code Execution (RCE). TAPI has a "Server" mode. If a machine is configured as a TAPI Server, it exposes this RPC interface to the network. An attacker on an adjacent network could perform this same exploit remotely. They could target a file on a network share or a local file on the server, writing their malicious 4-byte payload to gain a foothold. Given the CVSS score of 8.0 and the "Adjacent" vector, this is a very real threat for enterprise environments running legacy telephony infrastructure.
Microsoft patched this in the January 2026 update cycle. The fix involves strictly validating that the pszDomainUser parameter is a valid mailslot path. If you are a developer, the lesson here is simple: Never pass user input directly to filesystem APIs. Always whitelist expected patterns (e.g., ensure the string starts with \\.\mailslot\).
For administrators, apply the patch immediately. If you cannot patch, you should verify if TAPI Server sharing is enabled and disable it via the registry (HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Telephony\Server\DisableSharing = 1). This kills the remote attack vector, though the local vector remains until the binary is updated.
Ultimately, this vulnerability is a reminder that Windows is a geological dig site of code layers. Just because a feature was written in 1996 doesn't mean it isn't running on your 2026 Server Core instance, waiting to be exploited.
CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Windows 10 Microsoft | < 10.0.19045.6809 | 10.0.19045.6809 |
Windows 11 Microsoft | < 10.0.22631.6491 | 10.0.22631.6491 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-73 (External Control of File Name or Path) |
| Attack Vector | Adjacent Network (AV:A) |
| Privileges Required | Low (PR:L) |
| CVSS v3.1 | 8.0 (High) |
| EPSS Score | 2.22% |
| Exploit Status | Weaponized (Detailed Technical Report Available) |
The software allows user input to control or influence paths or file names that are used in filesystem operations.