Sunday, 22 July 2018

UWP Localhost Network Isolation and Edge


This blog post describes an interesting “feature” added to Windows to support Edge accessing the loopback network interface. For reference this was on Windows 10 1803 running Edge 42.17134.1.0 as well as verifying on Windows 10 RS5 17713 running 43.17713.1000.0.

I like the concept of the App Container (AC) sandbox Microsoft introduced in Windows 8. It moved sandboxing on Windows from restricted tokens which were hard to reason about and required massive cludges to get working to a reasonably consistent capability based model where you are heavily limited in what you can do unless you’ve been granted an explicit capability when your application is started. On Windows 8 this was limited to a small set of known capabilities. On Windows 10 this has been expanded massively by effectively allowing an application to define its own capabilities and enforce them though the normal Windows access control mechanisms.

I’ve been looking at AC more and it's ability to do network isolation, where access to the network requires being granted capabilities such as “internetClient”, seems very useful. It’s a little known fact that even in the most heavily locked down, restricted token sandbox it’s possible to open network sockets by accessing the raw AFD driver. AC solves this issue quite well, it doesn’t block access to the AFD driver, instead the Firewall checks for the capabilities and blocks connecting or accepting sockets.

One issue does come up with building a generic sandboxing mechanism this AC network isolation primitive is regardless of what capabilities you grant it’s not possible for an AC application to access localhost. For example you might want your sandboxed application to access a web server on localhost for testing, or use a localhost proxy to MITM the traffic. Neither of these scenarios can be made to work in an AC sandbox with capabilities alone.

The likely rationale for blocking localhost is allowing sandboxed content access can also be a big security risk. Windows runs quite a few services accessible locally which could be abused, such as the SMB server. Rather than adding a capability to grant access to localhost, there's an explicit list of packages exempt from the localhost restriction stored by the firewall service. You can access or modify this list using the Firewall APIs such as the  NetworkIsolationSetAppContainerConfig function or using the CheckNetIsolation tool installed with Windows. This behavior seems to be rationalized as accessing loopback is a developer feature, not something which real applications should rely on. Curious, I wondered whether I had AC’s already in the exemption list. You can list all available exemptions by running “CheckNetIsolation LoopbackExempt -s” on the command line.


On my Windows 10 machine we can see two exemptions already installed, which is odd for a developer feature which no applications should be using. The first entry shows “AppContainer NOT FOUND” which indicates that the registered SID doesn’t correspond to a registered AC. The second entry shows a very unhelpful name of “001” which at least means it’s an application on the current system. What’s going on? We can use my NtObjectManager PS module and it's 'Get-NtSid' cmdlet  on the second SID to see if that can resolve a better name.


Ahha, “001” is actually a child AC of the Edge package, we could have guessed this by looking at the length of the SID, a normal AC SID had 8 sub authorities, whereas a child has 12, with the extra 4 being added to the end of the base AC SID. Looking back at the unregistered SID we can see it’s also an Edge AC SID just with a child which isn’t actually registered. The “001” AC seems to be the one used to host Internet content, at least based on the browser security whitepaper from X41Sec (see page 54).

This is not exactly surprising. It seems when Edge was first released it wasn’t possible to access localhost resources at all (as demonstrated by an IBM help article which instructs the user to use CheckNetIsolation to add an exemption). However, at some point in development MS added an about:flags option to enable accessing localhost, and seems it’s now the default configuration, even though as you can see in the following screenshot it says enabling can put your device at risk.


What’s interesting though is if you disable the flags option and restart Edge then the exemption entry is deleted, and re-enabling it restores the entry again. Why is that a surprise? Well based on previous knowledge of this exemption feature, such as this blog post by Eric Lawrence you need admin privileges to change the exemption list. Perhaps MS have changed that behavior now? Let’s try and add an exemption using the CheckNetIsolation tool as a normal user, passing “-a -p=SID” parameters.


I guess they haven’t as adding a new exemption using the CheckNetIsolation tool gives us access denied. Now I’m really interested. With Edge being a built-in application of course there’s plenty of ways that MS could have fudged the “security” checks to allow Edge to add itself to the list, but where is it?

The simplest location to add the fudge would be in the RPC service which implements the NetworkIsolationSetAppContainerConfig. (How do I know there's an RPC service? I just disassembled the API). I took a guess and assumed the implementation would be hosted in the “Windows Defender Firewall” service, which is implemented in the MPSSVC DLL. The following is a simplified version of the RPC server method for the API.

HRESULT RPC_NetworkIsolationSetAppContainerConfig(handle_t handle,
    DWORD dwNumPublicAppCs,
    PSID_AND_ATTRIBUTES appContainerSids) {

  if (!FwRpcAPIsIsPackageAccessGranted(handle)) {
    HRESULT hr;
    BOOL developer_mode = FALSE:
    IsDeveloperModeEnabled(&developer_mode);
    if (developer_mode) {
      hr = FwRpcAPIsSecModeAccessCheckForClient(1, handle);
      if (FAILED(hr)) {
          return hr;
      }
    }
    else
    {
      hr = FwRpcAPIsSecModeAccessCheckForClient(2, handle);
      if (FAILED(hr)) {
          return hr;
      }
    }
  }
  return FwMoneisAppContainerSetConfig(dwNumPublicAppCs,
                                       appContainerSids);
}

What’s immediately obvious is there's a method call, FwRpcAPIsIsPackageAccessGranted, which has “Package” in the name which might indicate it’s inspecting some AC package information. If this call succeeds then the following security checks are bypassed and the real function FwMoneisAppContainerSetConfig is called. It's also worth noting that the security checks differ depending on whether you're in developer mode or not. It turns out that if you have developer mode enabled then you can also bypass the admin check, which is confirmation the exemption list was designed primarily as a developer feature.

Anyway let's take a look at FwRpcAPIsIsPackageAccessGranted to see what it’s checking.

const WCHAR* allowedPackageFamilies[] = {
  L"Microsoft.MicrosoftEdge_8wekyb3d8bbwe",
  L"Microsoft.MicrosoftEdgeBeta_8wekyb3d8bbwe",
  L"Microsoft.zMicrosoftEdge_8wekyb3d8bbwe"
};

HRESULT FwRpcAPIsIsPackageAccessGranted(handle_t handle) {
  HANDLE token;
  FwRpcAPIsGetAccessTokenFromClientBinding(handle, &token);

  WCHAR* package_id;
  RtlQueryPackageIdentity(token, &package_id);
  WCHAR family_name[0x100];
  PackageFamilyNameFromFullName(package_id, family_name)

  for (int i = 0;
       i < _countof(allowedPackageFamilies);
       ++i) {
      if (wcsicmp(family_name,
           allowedPackageFamilies[i]) == 0) {
        return S_OK;
      }
  }
  return E_FAIL;
}

The FwRpcAPIsIsPackageAccessGranted function gets the caller’s token, queries for the package family name and then checks it against a hard coded list. If the caller is in the Edge package (or some beta versions) the function returns success which results in the admin check being bypassed. The conclusion we can take is this is how Edge is adding itself to the exemption list, although we also want to check what access is required to the RPC server. For an ALPC server there’s two security checks, connecting to the ALPC port and an optional security callback. We could reverse engineer it from service binary but it is easier just to dump it from the ALPC server port, again we can use my NtObjectManager module.


As the RPC service doesn’t specify a name for the service then the RPC libraries generate a random name of the form “LRPC-XXXXX”. You would usually use EPMAPPER to find the real name but I just used a debugger on CheckNetIsolation to break on NtAlpcConnectPort and dumped the connection name. Then we just find the handle to that ALPC port in the service process and dump the security descriptor. The list contains Everyone and all the various network related capabilities, so any AC process with network access can talk to these APIs including Edge LPAC. Therefore all Edge processes can access this capability and add arbitrary packages. The implementation inside Edge is in the function emodel!SetACLoopbackExemptions.

With this knowledge we can now put together some code which will exploit this “feature” to add arbitrary exemptions. You can find the PowerShell script on my Github gist.


Wrap Up

If I was willing to speculate (and I am) I’d say the reason that MS added localhost access this way is it didn’t require modifying kernel drivers, it could all be done with changes to user mode components. Of course the cynic in me thinks this could actually be just there to make Edge more equal than others, assuming MS ever allowed another web browser in the App Store. Even a wrapper around the Edge renderer would not be allowed to add the localhost exemption. It’d be nice to see MS add a capability to do this in the future, but considering current RS5 builds use this same approach I’m not hopeful.

Is this a security issue? Well that depends. On the one hand you could argue the default configuration which allows Internet facing content to then access localhost is dangerous in itself, they point that out explicitly in the about:flags entry. Then again all browsers have this behavior so I’m not sure it’s really an issue.

The implementation is pretty sloppy and I’m shocked (well not that shocked) that it passed a security review. To list some of the issues with it:
      The package family check isn’t very restrictive, combined with the weak permissions of the RPC service it allows any Edge process to add an arbitrary exemption.
      The exemption isn’t linked to the calling process, so any SID can be added as an exemption.

While it seems the default is only to allow the Internet facing ACs access to localhost because of these weaknesses if you compromised a Flash process (which is child AC “006”) then it could add itself an exemption and try and attack services listening on localhost. It would make more sense if only the main MicrosoftEdge process could add the exemptions, not any content process. But what would make the most sense would be to support this functionality through a capability so that everyone could take advantage of it rather than implementing it as a backdoor.



Monday, 25 June 2018

Disabling AMSI in JScript with One Simple Trick

This blog contains a very quick and dirty way to disable AMSI in the context of Windows Scripting Host which doesn't require admin privileges or modifying registry keys/system state which an AV such as Defender should pick up on. It's for information purposes only, I've tested this on an up-to-date Windows 10 1803 machine.

It's come to my attention that a default script file from DotNetToJScript no longer works because Windows Defender blocks it, thanks a lot everyone who contributed to getting my tools flagged as malware.

Dialog showing Windows defender blocks DotNetToJScript through AMSI.

If you look carefully in the screenshot you'll see it shows that the "Affected items:" is prefixed with amsi:. This is an indication that the detection wasn't based on the file but due to behavior through the Antimalware Scan Interface. Certainly the script could be reworked to get around this issue (it seems to work for example in scriptlets oddly enough) but I'll probably never need to bother as I never wrote it for the use cases "Sharpshooter" uses it for. Still I had an idea for a way of bypassing AMSI which I thought I'd test out.

I was in part inspired to dig this technique out again after seeing MDSec's recent work on newer bypasses for AMSI in PowerShell as well as a BlackHat Asia talk from Tal Liberman. However I've not seen this technique described anywhere else, but I'm sure someone can correct me if I'm wrong.


How AMSI Is Loaded in Windows Scripting Host

AMSI is implemented as an COM server which is used to communicate to the installed security product via an internal channel. A previous attack against AMSI was actually to hijack this COM registration as documented by Matt Nelson. The scripting host is not supposed to call the COM object directly, instead it calls methods via exported functions in AMSI.DLL, we can watch being loaded by setting an appropriate filter in Process Monitor. 

AMSI loading into WScript.exe

We can use the stack trace feature of Process Monitor to find the code responsible for loading AMSI.DLL. It's actually part of the scripting engine, such as JScript or VBScript rather than a core part of WSH. The basics of the code are below.

HRESULT COleScript::Initialize() { hAmsiModule = LoadLibraryExW(L"amsi.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (hAmsiModule) { ① // Get initialization functions. FARPROC pAmsiInit = GetProcAddress(hAmsiModule, "AmsiInitialize"); pAmsiScanString = GetProcAddress(hAmsiModule, "AmsiScanString"); if (pAmsiInit){ if (pAmsiScanString && FAILED(pAmsiInit(&hAmsiContext))) ② hAmsiContext = nullptr; } } bInit = TRUE; ③ return bInit; }

Based on this code we can see it loading the AMSI DLL  then calling AmsiInitialize to get a context handle. The interesting thing about this code is regardless of whether AMSI initializes or not it will always return success . This leads to three ways of causing this code to fail and therefore never initialize AMSI, block loading AMSI.DLL, make AMSI.DLL not contain methods such as AmsiInitialize, or cause AmsiInitialize to fail.

These are somewhat interconnected. For example Tal Liberman mentions in his presentation (slide 56) that you could copy an AMSI using application to another directory and it will try and load AMSI.DLL from that directory. As this AMSI can be some unrelated DLL which doesn't export AmsiInitialize then the load will succeed but the rest will fail. Unfortunately this trick won't work here as the flag LOAD_LIBRARY_SEARCH_SYSTEM32 is being passed which means LoadLibraryEx will always try and load from SYSTEM32 first. Getting AmsiInitialize to fail will be a pain, and we can't trivially prevent this code load AMSI.DLL from SYSTEM32, so what do we do? We preload an alternative AMSI.DLL of course.

Hijacking AMSI.DLL

How do we go about loading an alternative AMSI.DLL with the least amount of effort possible? Something which perhaps not everyone realizes is that LoadLibrary will try and find an existing loaded DLL with the requested name so that it doesn't load the same DLL twice. This works not just for the name of the DLL but also the main executable. Therefore if we can convince the library loader that our main executable is actually called AMSI.DLL then it'll return that instead. Unable to find AmsiInitialize exported this should result in AMSI failing to initialize but continuing to execute a script without inspecting it. 

How can we change the name of our main executable to AMSI.DLL without modifying process memory? Simple, we copy WSCRIPT.EXE to another directory but call it AMSI.DLL then run it. Wait, what? How can we run AMSI.DLL, doesn't it need to SOMETHING.EXE? On Windows there's two main ways of executing a process, ShellExecute or CreateProcess. When calling ShellExecute the API looks up the handler for the extension, such as .EXE or .DLL and performs particular actions based on the result. Generally .EXE will redirect to just calling CreateProcess, where as for .DLL it'll try and load it in a registered viewer, on a default system there probably isn't one. However, CreateProcess doesn't care what extension the file has as long as it's an Executable File based on its PE header. [Aside, you can actually execute a DLL using the native APIs, but we don't have access to that from WSH]. Therefore, as long as we can call CreateProcess on AMSI.DLL which is actually a copy of WSCRIPT.EXE it will execute. To do this we can just use WScript.Shell's Exec method which just calls CreateProcess directly.

var obj = new ActiveXObject("WScript.Shell"); obj.Exec("amsi.dll dotnettojscript.js");

This results in a process called AMSI.DLL running. When JScript or VBScript tries to load AMSI.DLL it now gets back a reference to main executable and AMSI no longer works. From what I can tell this short script doesn't get detected by AMSI itself, so it's safe to run to bootstrap the "real" code you want to run.

WScript.Exe running as AMSI.DLL

To summarise the attack:

  1. Start a stub script which copies WSCRIPT.EXE to a known location but with the name AMSI.DLL. This is still the same catalog signed executable just in a different location so would likely bypass detection based purely on signatures.
  2. In the stub script execute the newly created AMSI.DLL with the "real" script.
  3. Err, that's about it.

AFAIK this doesn't work with PowerShell because it seems to break something important inside the code which renders PS inoperable. Whether this is by design or not I've no idea. Anyway, I know this is a silly way of bypassing AMSI but it just shows that this sort of self-checking feature rarely works out very well when malware could very easily modify the platform which is doing the detection.









Saturday, 2 June 2018

Adding a Command Line to PowerShell's Process Listing

My NtObjectManager PowerShell module has plenty of useful functions and cmdlets, but it's not really designed for general use-cases such as system administration. I was reminded of this when I got a reply to a tweet I made announcing a new version of the module.

While the Get-NtProcess cmdlet does have a CommandLine property it's not really a good idea to use it just for that. Each process object returned from the cmdlet is an instance of the NtProcess class which maintains an open handle. While the garbage collector should eventually kick in and clean up for you it's still bad practice to leave open handles lying around.

Wouldn't it be useful if you could get the command line of a process without requiring a large third party module? As pointed out in the tweet you can use WMI but it's uglier than calling Get-Process. It also has a small flaw, it doesn't show the command lines for elevated processes on the same desktop which Get-NtProcess can do (at least on Windows 8 and above).

So I decided to investigate how I might add the command line to the existing Get-Process cmdlet in the simplest way possible. To do so I expose some functionality in PowerShell which I'm guessing few realise exists, or for that matter need to use. First let's see what type of object Get-Process is returning. We can call GetType on the returned objects and find that out.

PS C:\> $ps = Get-Process
PS C:\> $ps[0].GetType() | select Fullname

FullName
--------
System.Diagnostics.Process

We can see that it's just returning the list of normal framework Process objects. Nothing too surprising there, however one thing is interesting, the object has more properties available than the Process class in the framework. A good example is the Path property which returns the full path to the main executable:

PS C:\> $ps[0] | select Path

Path
----
C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

Where does that come from? Using the Get-Member cmdlet gives us a clue.

PS C:\> $ps[0] | Get-Member Path

   TypeName: System.Diagnostics.Process

Name MemberType     Definition
---- ----------     ----------
Path ScriptProperty System.Object Path {get=$this.Mainmodule.FileName;}

Very interesting, it seems to a script property, after some digging it turns out this script is added in something called a Type Extension file which has been around since the early days of PowerShell. It allows you to add arbitrary properties and methods to existing types. The extensions for the Process class are in $PSHome\types.ps1xml, a snippet is shown below.

<Type>
 <Name>System.Diagnostics.Process</Name>
 <Members>
  <ScriptProperty>
   <Name>Path</Name>
    <GetScriptBlock>$this.Mainmodule.FileName</GetScriptBlock>
   </ScriptProperty>
...

Of course what I should have done first is just checked Lee Holmes blog where he wrote a description of these Type Extension files a mere 12 years ago! Anyway you can also get the help for this feature using running Get-Help about_Types.

This sounds ideal, we can add our create out own Type Extension file and add a scripted property to pull out the command line. We just need to write it, the easiest solution would be to use my NtApiDotNet .NET library, but if you're using that you might as well just use the NtObjectManager module to begin with as the library is what the module is built on. Therefore, we'll need to re-implement everything in C# using Add-Type then just invoke that to get the command line when necessary. This is not too hard, I just based it on the code in my library.

If you copy the gist into a file with a ps1xml extension you can then add the extension to the current session using the Update-TypeData cmdlet and passing the path to the file. If you want this to persist you can add the call to your profile, or save the session and reload it.

PS C:\> Update-TypeData .\command_line_type.ps1xml
PS C:\> Get-Process explorer | select CommandLine

CommandLine
-----------
C:\WINDOWS\Explorer.EXE

I've tried to make it as compact as possible, for example I don't enable SeDebugPrivilege which would be useful for administrators as it'd allow you to read the command line from almost any process on the system. You could add that feature if you like. One thing I had to do is also call OpenProcess on the PID, which is odd as the Process class actually has a SafeHandle property which returns a native handle for the process. Unfortunately the framework opens this handle with PROCESS_ALL_ACCESS rights, not the limited PROCESS_QUERY_LIMITED_INFORMATION access we require. This means that elevated processes can not be opened, removing the advantage this approach gives us.

This is also the reason WMI doesn't return all command lines. The WMI host process impersonates the caller when querying the command line for a process. WMI then uses an old method of extracting the command line by reading the command line directly from memory (using a technique similar to this StackOverflow post). As this requires PROCESS_VM_READ access this fails with elevated processes. Perhaps they should move to the NtQueryInformationProcess approach on modern versions of Windows ;-)

PS C:\> start -verb runas notepad test.txt
PS C:\> Get-WmiObject Win32_process | ? Name -eq "notepad.exe" | select CommandLine

CommandLine
-----------

PS C:\> Get-Process notepad | select CommandLine

CommandLine
-----------
"C:\WINDOWS\system32\notepad.exe" test.txt

Hope you find this information useful, there's loads of useful functionality in PowerShell which can make your life much easier. You just have to find it first :-)

Sunday, 5 November 2017

Named Pipe Secure Prefixes

When writing named pipe servers on Windows it’s imperative is do so securely. One common problem you’ll encounter is named pipe squatting, where a low privileged application creates a named pipe server either before the real server does or as a new instance of an existing server. This could lead to information disclosure if the server is being used to aggregate private data from a number of clients as well as elevation of privilege if the conditions are right.

There’s some programming strategies to try and eliminate named pipe squatting including passing the FILE_FLAG_FIRST_PIPE_INSTANCE flag to CreatedNamedPipe which will cause an error if the pipe already exists as well as appropriate configuration of the pipe’s security descriptor. A recent addition to Windows is named pipe secure prefixes, which make it easier to develop a named pipe server which isn’t vulnerable to squatting. Unfortunately I can’t find any official documentation on these prefixes or how you use them. Prefixes seem to be mentioned briefly in Windows Internals, but it doesn’t go into any detail about what they are or why they work. So this blog is an effort to remedy that lack of documentation.

First let’s start with how named pipes are named. Named pipes are exposed by the Named Pipe File System (NPFS) driver, which creates the \Device\NamedPipe device object. When you create a new named pipe instance you call CreateNamedPipe (really NtCreateNamedPipeFile under the hood) with the full path to the pipe to create. It’s typically to see this in the form \\.\pipe\PipeName where PipeName is the name you want to assign. At the native API level this path is converted to \??\pipe\PipeName; \??\pipe is a symbolic link which ultimately resolves this path to \Device\NamedPipe\PipeName.

Even though \Device\NamedPipe is a file system, NPFS doesn’t support directories other than the root. If you list the contents of the named pipe root directory you’ll notice that some of the names pipes have backslashes in the name, however NPFS just treats them as names as shown below.

named_pipes.PNG

So we’d assume we can’t have directories, but if we look at the function which handles IRP_MJ_CREATE dispatch in the NPFS driver we find something interesting:

NTSTATUS NpFsdCreate(PDEVICE_OBJECT device, PIRP irp) {
 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
 BOOLEAN directoryfile = stack->Parameters.Create.Options
                                & FILE_DIRECTORY_FILE;
 DWORD disposition = stack->Parameters.Create.Options >> 24;
 NTSTATUS status;
 // Other stuff...

 if (directoryfile) { ← Check for creating a directory
   if (disposition == FILE_OPEN) {
     status = NpOpenNamedPipePrefix(...);
   } else if (disposition == FILE_CREATE
           || disposition == FILE_OPEN_IF) {
     status = NpCreateNamedPipePrefix(...);
   }
 }

 // Even more stuff...
}

In the code we can see that the flag for creating a directory file is checked. If a directory file is requested then the driver tries to create or open a named pipe prefix. Let’s try and create one of these prefixes:

create_prefix.PNG

Well darn, it say it requires a privilege. Let’s dig into NpCreateNamedPipePrefix to see which privilege we’re missing.

NTSTATUS NpCreateNamedPipePrefix(...) {  
 // Blah blah...
 if (SeSinglePrivilegeCheck(SeExports->SeTcbPrivilege, UserMode)) {
   // Continue...
 } else {
   return STATUS_PRIVILEGE_NOT_HELD;
 }
}

So that’s awkward, TCB privilege is only granted to SYSTEM users, not even to administrators. While as an administrator it’s not that hard to get a SYSTEM token the same can’t be said of LocalService or NetworkService accounts. At least let’s check if impersonating SYSTEM with TCB privilege will work to create a prefix:

create_prefix_working.PNG

We now have a new prefix called Badgers, so let’s try and create a new named pipe as a normal user under that prefix and see if it does anything interesting.

create_pipe_access_denied.PNG

We get STATUS_ACCESS_DENIED returned. This it turns out is because the driver will find the largest prefix that’s been registered and check if the caller has access to the prefix’s security descriptor. What’s the security descriptor for the Badgers prefix?
prefix_sd.PNG

Seems it’s just SYSTEM and Administrators group with access, which makes sense based on the original caller being SYSTEM. Therefore, if we supply a more permissive security descriptor then it should allow a normal user to create the pipe.

create_with_dacl.PNG

Another question, how can we get rid of an existing prefix? You just need to close all handles to the prefix and it will go away automatically.

As said it’s a pain that you need TCB privilege to create new secure prefixes especially for non-administrator service accounts. Has the system created any prefixes already? There’s nothing obvious in NPFS. After a bit of investigation the Session Manager process (SMSS) creates a number of known prefixes in the function SmpCreateProtectedPrefixes. For example SMSS creates the following prefixes:

\ProtectedPrefix\Administrators
\ProtectedPrefix\LocalService
\ProtectedPrefix\NetWorkService

Each of these prefixes have a DACL based on their name, e.g. LocalService has a DACL which only allows the LocalService user to create named pipes under that prefix.
prefix_sd-2.PNG

It’s worth noting that the owner for the prefixes is the Administrators group which means an administrator could open the prefixes and rewrite the DACL, if you really wanted to screw with the OS :-)

Anyway, if you’re writing a new named pipe server and you want to make it more difficult for named pipe squatting then adding an appropriate secure prefix will prevent other users, especially low privileged users from creating a new pipe with the same name. If someone knows where this is documented please let me know as I think it’s a useful security feature which few know about.