diff --git a/README.md b/README.md index f7ecfb4..f0a2f26 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ The `address1` can accept `STDIO`, `TCP-LISTEN`, `TCP`, `NPIPE`, `NPIPE-LISTEN`, The `address2` can accept `STDIO`, `TCP`, `NPIPE`, `EXEC`, `WSL`, `UNIX`, `HVSOCK`, `SP` socket types. +`NPIPE-LISTEN` supports the `ACL=AllowCurrentUser` parameter, in this case, no other user that is connected to the machine can read / write from / to the pipe. The default is `ACL=AllowEveryone`. + ## Examples * It can bridge standard input/output and tcp connection to address **127.0.0.1** on port **80**. diff --git a/Tests/NamedPipeListenPiperInfoTest.cs b/Tests/NamedPipeListenPiperInfoTest.cs index 2d84ef9..9f0d3d4 100644 --- a/Tests/NamedPipeListenPiperInfoTest.cs +++ b/Tests/NamedPipeListenPiperInfoTest.cs @@ -5,6 +5,8 @@ namespace APPTest; public class NamedPipeListenPiperInfoTest { [TestCase("NPIPE-LISTEN:fooPipe")] + [TestCase("NPIPE-LISTEN:fooPipe,ACL=AllowEveryone")] + [TestCase("NPIPE-LISTEN:fooPipe,ACL=AllowCurrentUser")] public void VaildInputParseTest(string input) { var element = AddressElement.TryParse(input); @@ -18,22 +20,42 @@ public void CaseInsensitiveValidInputParseTest(string input) var element = AddressElement.TryParse(input); Assert.NotNull(Firejox.App.WinSocat.NamedPipeListenPiperInfo.TryParse(element)); } - + [TestCase("STDIO")] [TestCase("TCP:127.0.0.1:80")] [TestCase("TCP-LISTEN:127.0.0.1:80")] [TestCase("NPIPE:fooServer:barPipe")] + [TestCase("NPIPE:fooServer:barPipe")] + [TestCase("NPIPE:fooServer:barPipe")] [TestCase(@"EXEC:'C:\Foo.exe bar'")] public void InvalidInputParseTest(string input) { var element = AddressElement.TryParse(input); Assert.Null(Firejox.App.WinSocat.NamedPipeListenPiperInfo.TryParse(element)); - } - - [TestCase("NPIPE-LISTEN:fooPipe", ExpectedResult = "fooPipe")] - public string PipePatternMatchTest(string input) + } + + [TestCase("NPIPE-LISTEN:fooPipe")] + [TestCase("NPIPE-LISTEN:fooPipe,ACL=AllowEveryone")] + [TestCase("NPIPE-LISTEN:fooPipe,ACL=AllowCurrentUser")] + public void PipePatternMatchTest(string input) { - var element = AddressElement.TryParse(input); - return Firejox.App.WinSocat.NamedPipeListenPiperInfo.TryParse(element).PipeName; + // Case 1 - Default ACL + var element = AddressElement.TryParse("NPIPE-LISTEN:fooPipe"); + var parsed = Firejox.App.WinSocat.NamedPipeListenPiperInfo.TryParse(element); + Assert.That(parsed.PipeName, Is.EqualTo("fooPipe")); + Assert.That(parsed.ACL, Is.EqualTo("AllowEveryone")); + + // Case 2 - AllowEveryone ACL + element = AddressElement.TryParse("NPIPE-LISTEN:fooPipe,ACL=AllowEveryone"); + parsed = Firejox.App.WinSocat.NamedPipeListenPiperInfo.TryParse(element); + Assert.That(parsed.PipeName, Is.EqualTo("fooPipe")); + Assert.That(parsed.ACL, Is.EqualTo("AllowEveryone")); + + // Case 3 - AllowCurrentUser ACL + element = AddressElement.TryParse("NPIPE-LISTEN:fooPipe,ACL=AllowCurrentUser"); + parsed = Firejox.App.WinSocat.NamedPipeListenPiperInfo.TryParse(element); + Assert.That(parsed.PipeName, Is.EqualTo("fooPipe")); + Assert.That(parsed.ACL, Is.EqualTo("AllowCurrentUser")); + } -} \ No newline at end of file +} diff --git a/Tests/NamedPipeStreamPiperInfoTest.cs b/Tests/NamedPipeStreamPiperInfoTest.cs index 868694c..ec76eb0 100644 --- a/Tests/NamedPipeStreamPiperInfoTest.cs +++ b/Tests/NamedPipeStreamPiperInfoTest.cs @@ -24,7 +24,7 @@ public void CaseInsensitiveValidInputParseTest(string input) [TestCase("STDIO")] [TestCase("TCP:127.0.0.1:80")] [TestCase("TCP-LISTEN:127.0.0.1:80")] - [TestCase("NPIPE-LISTEN:fooPipe")] + [TestCase("NPIPE-LISTEN:fooPipe")] [TestCase(@"EXEC:'C:\Foo.exe bar'")] public void InvalidInputParseTest(string input) { diff --git a/winsocat/NamedPipe.cs b/winsocat/NamedPipe.cs index a3c28dd..8548248 100644 --- a/winsocat/NamedPipe.cs +++ b/winsocat/NamedPipe.cs @@ -1,4 +1,5 @@ using System.IO.Pipes; +using System.Security.Principal; namespace Firejox.App.WinSocat; @@ -6,14 +7,17 @@ public class NamedPipeStreamPiperInfo { private readonly string _serverName; private readonly string _pipeName; + private readonly string _acl; public string ServerName => _serverName; public string PipeName => _pipeName; + public string ACL => _acl; - public NamedPipeStreamPiperInfo(string serverName, string pipeName) + public NamedPipeStreamPiperInfo(string serverName, string pipeName, string acl) { _serverName = serverName; _pipeName = pipeName; + _acl = acl; } public static NamedPipeStreamPiperInfo TryParse(AddressElement element) { @@ -31,24 +35,28 @@ public static NamedPipeStreamPiperInfo TryParse(AddressElement element) pipeName = element.Address.Substring(sepIndex + 1); - return new NamedPipeStreamPiperInfo(serverName, pipeName); + return new NamedPipeStreamPiperInfo(serverName, pipeName, element.Options.GetValueOrDefault("ACL", "AllowEveryone")); } } public class NamedPipeListenPiperInfo { private readonly string _pipeName; + private readonly string _acl = "AllowEveryone"; + public string PipeName => _pipeName; + public string ACL => _acl; - public NamedPipeListenPiperInfo(string pipeName) + public NamedPipeListenPiperInfo(string pipeName, string acl = "AllowEveryone") { _pipeName = pipeName; + _acl = acl; } public static NamedPipeListenPiperInfo TryParse(AddressElement element) { if (element.Tag.Equals("NPIPE-LISTEN", StringComparison.OrdinalIgnoreCase)) - return new NamedPipeListenPiperInfo(element.Address); + return new NamedPipeListenPiperInfo(element.Address, element.Options.GetValueOrDefault("ACL", "AllowEveryone")); return null!; } @@ -135,6 +143,24 @@ public NamedPipeListenPiper(NamedPipeListenPiperInfo info) _closed = false; } + public void SetPermissions(NamedPipeServerStream _serverStream) + { + if (OperatingSystem.IsWindows()) + { + // Only allow current user + if (_info.ACL.Equals("AllowCurrentUser", StringComparison.OrdinalIgnoreCase)) + { + var securityIdentifier = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null); + var pipeAcl = new PipeAccessRule(securityIdentifier, + PipeAccessRights.ReadWrite | PipeAccessRights.CreateNewInstance, + System.Security.AccessControl.AccessControlType.Allow); + var pipeSecurity = new PipeSecurity(); + pipeSecurity.AddAccessRule(pipeAcl); + _serverStream.SetAccessControl(pipeSecurity); + } + } + } + public IPiper NewIncomingPiper() { _serverStream = new NamedPipeServerStream( @@ -143,8 +169,8 @@ public IPiper NewIncomingPiper() -1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + SetPermissions(_serverStream); _serverStream.WaitForConnection(); - var tmpServerStream = _serverStream; _serverStream = null; return new StreamPiper(tmpServerStream); @@ -161,6 +187,7 @@ public async Task NewIncomingPiperAsync() -1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + SetPermissions(_serverStream); await _serverStream.WaitForConnectionAsync(); var tmpServerStream = _serverStream;