You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

378 lines
15 KiB

using Alphaleonis.Win32.Filesystem;
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
namespace Security2
{
internal partial class Win32
{
internal const string AUTHZ_OBJECTUUID_WITHCAP = "9a81c2bd-a525-471d-a4ed-49907c0b23da";
internal const string RCP_OVER_TCP_PROTOCOL = "ncacn_ip_tcp";
IntPtr userClientCtxt = IntPtr.Zero;
SafeAuthzRMHandle authzRM;
IntPtr pGrantedAccess = IntPtr.Zero;
IntPtr pErrorSecObj = IntPtr.Zero;
// Whether the remote resource manager is the one of this computer. Its remote interface answers only the
// administrators of the computer and the members of Access Control Assistance Operators.
bool remoteResourceManagerIsLocal;
#region GetInheritedFrom
// Returns the source of each entry of the DACL, or of the SACL for audit entries, in the order of the ACL. Before
// 5.0.0-rc6, a descriptor with a SACL returned the sources of the audit entries also for the access entries.
public static List<string> GetInheritedFrom(FileSystemInfo item, ObjectSecurity sd, bool auditEntries)
{
var inheritedFrom = new List<string>();
var sdBytes = sd.GetSecurityDescriptorBinaryForm();
var rawSd = new RawSecurityDescriptor(sdBytes, 0);
var acl = auditEntries ? rawSd.SystemAcl : rawSd.DiscretionaryAcl;
if (acl != null)
{
var aceCount = acl.Count;
var aclBytes = new byte[acl.BinaryLength];
acl.GetBinaryForm(aclBytes, 0);
try
{
inheritedFrom = GetInheritedFrom(item.FullName,
aclBytes,
aceCount,
item is DirectoryInfo ? true : false,
auditEntries ? SECURITY_INFORMATION.SACL_SECURITY_INFORMATION : SECURITY_INFORMATION.DACL_SECURITY_INFORMATION);
}
catch
{
// Windows can't name the folders, for example because the item is gone or a folder above it can't
// be read. An explicit entry has no source in any case.
inheritedFrom = new List<string>();
for (int i = 0; i < aceCount; i++)
{
inheritedFrom.Add(acl[i].IsInherited ? "unknown parent" : string.Empty);
}
}
}
return inheritedFrom;
}
public static List<string> GetInheritedFrom(string path, byte[] aclBytes, int aceCount, bool isContainer, SECURITY_INFORMATION aclType)
{
var inheritedFrom = new List<string>();
path = Path.GetLongPath(path);
uint returnValue = 0;
GENERIC_MAPPING genericMap = new GENERIC_MAPPING();
genericMap.GenericRead = (uint)MappedGenericRights.FILE_GENERIC_READ;
genericMap.GenericWrite = (uint)MappedGenericRights.FILE_GENERIC_WRITE;
genericMap.GenericExecute = (uint)MappedGenericRights.FILE_GENERIC_EXECUTE;
genericMap.GenericAll = (uint)MappedGenericRights.FILE_GENERIC_ALL;
var pInheritInfo = Marshal.AllocHGlobal(aceCount * Marshal.SizeOf(typeof(PINHERITED_FROM)));
try
{
returnValue = GetInheritanceSource(
path,
ResourceType.FileObject,
aclType,
isContainer,
IntPtr.Zero,
0,
aclBytes,
IntPtr.Zero,
ref genericMap,
pInheritInfo
);
if (returnValue != 0)
{
throw new System.ComponentModel.Win32Exception((int)returnValue);
}
for (int i = 0; i < aceCount; i++)
{
var inheritInfo = pInheritInfo.ElementAt<PINHERITED_FROM>(i);
inheritedFrom.Add(
!string.IsNullOrEmpty(inheritInfo.AncestorName) && inheritInfo.AncestorName.StartsWith(@"\\?\") ? inheritInfo.AncestorName.Substring(4) : inheritInfo.AncestorName
);
}
FreeInheritedFromArray(pInheritInfo, (ushort)aceCount, IntPtr.Zero);
}
finally
{
// Also after a failed call, which the fallback of GetInheritedFrom now expects.
Marshal.FreeHGlobal(pInheritInfo);
}
return inheritedFrom;
}
#endregion GetInheritedFrom
public int GetEffectiveAccess(ObjectSecurity sd, IdentityReference2 identity, string serverName, out bool remoteServerAvailable, out Exception authzException)
{
int effectiveAccess = 0;
remoteServerAvailable = false;
authzException = null;
try
{
GetEffectivePermissions_AuthzInitializeResourceManager(serverName, out remoteServerAvailable);
try
{
GetEffectivePermissions_AuthzInitializeContextFromSid(identity);
effectiveAccess = GetEffectivePermissions_AuthzAccessCheck(sd);
}
catch (Exception ex)
{
authzException = ex;
}
}
catch
{ }
finally
{
GetEffectivePermissions_FreeResouces();
}
return effectiveAccess;
}
#region Win32 Wrapper
// Whether a name of -ServerName names this computer: localhost in any case, ., the NetBIOS name, the DNS host
// name, or the fully qualified domain name. It must not throw, because GetEffectiveAccess hides every exception
// of the resource manager behind a result without access.
private static bool IsLocalComputer(string serverName)
{
if (string.IsNullOrEmpty(serverName))
{
return false;
}
if (serverName == "." ||
string.Equals(serverName, "localhost", StringComparison.OrdinalIgnoreCase) ||
string.Equals(serverName, Environment.MachineName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
try
{
var properties = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties();
return string.Equals(serverName, properties.HostName, StringComparison.OrdinalIgnoreCase) ||
(!string.IsNullOrEmpty(properties.DomainName) &&
string.Equals(serverName, properties.HostName + "." + properties.DomainName, StringComparison.OrdinalIgnoreCase));
}
catch (System.Net.NetworkInformation.NetworkInformationException)
{
return false;
}
}
private void GetEffectivePermissions_AuthzInitializeResourceManager(string serverName, out bool remoteServerAvailable)
{
remoteServerAvailable = false;
// An empty name names no computer. Windows takes it for this computer on some computers, where the remote
// interface then refuses the check with "Access is denied", and for an unreachable one on others. So the
// remote interface isn't asked, and the local authorization manager calculates the result, like for any
// name that can't be reached.
if (!string.IsNullOrWhiteSpace(serverName))
{
var rpcInitInfo = new AUTHZ_RPC_INIT_INFO_CLIENT();
rpcInitInfo.version = AuthzRpcClientVersion.V1;
rpcInitInfo.objectUuid = AUTHZ_OBJECTUUID_WITHCAP;
rpcInitInfo.protocol = RCP_OVER_TCP_PROTOCOL;
rpcInitInfo.server = serverName;
SafeHGlobalHandle pRpcInitInfo = SafeHGlobalHandle.AllocHGlobalStruct(rpcInitInfo);
if (AuthzInitializeRemoteResourceManager(pRpcInitInfo.ToIntPtr(), out authzRM))
{
remoteServerAvailable = true;
remoteResourceManagerIsLocal = IsLocalComputer(serverName);
return;
}
int error = Marshal.GetLastWin32Error();
bool isLocalComputer = IsLocalComputer(serverName);
// The computer can't be resolved or reached (RPC server unavailable), or it doesn't offer the remote
// interface (endpoint not registered); the local authorization manager calculates the result instead.
// This computer can also refuse the caller, who isn't one of its administrators; its own manager
// answers then, too.
if (error != Win32Error.EPT_S_NOT_REGISTERED && error != Win32Error.RPC_S_SERVER_UNAVAILABLE &&
!(isLocalComputer && error == Win32Error.ERROR_ACCESS_DENIED))
{
throw new Win32Exception(error);
}
// The local authorization manager is the one of this computer, so its result is accurate for any name
// of this computer. Before 5.0.0-rc7, only localhost in lowercase counted, and the cmdlet warned for
// the others, such as ., the computer name, or LOCALHOST.
if (isLocalComputer)
{
remoteServerAvailable = true;
}
}
GetEffectivePermissions_AuthzInitializeLocalResourceManager();
}
private void GetEffectivePermissions_AuthzInitializeLocalResourceManager()
{
//
// As a fallback we do AuthzInitializeResourceManager. But the results can be inaccurate.
//
if (!AuthzInitializeResourceManager(
AuthzResourceManagerFlags.NO_AUDIT,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
"EffectiveAccessCheck",
out authzRM))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
private void GetEffectivePermissions_AuthzInitializeContextFromSid(IdentityReference2 id)
{
var rawSid = id.GetBinaryForm();
//
// Create an AuthZ context based on the user account
//
if (!AuthzInitializeContextFromSid(
AuthzInitFlags.Default,
rawSid,
authzRM,
IntPtr.Zero,
Win32.LUID.NullLuid,
IntPtr.Zero,
out userClientCtxt))
{
Win32Exception win32Expn = new Win32Exception(Marshal.GetLastWin32Error());
// A computer in a domain offers the remote interface of its authorization manager to every caller, but
// answers only its administrators and the members of Access Control Assistance Operators; any other
// account gets "Access is denied", whichever account the check is for. For a name of this computer, the
// local authorization manager is the manager of that computer and answers every caller. For another
// computer, the denial stays an error: no access instead would be a wrong result.
if (win32Expn.NativeErrorCode == Win32Error.ERROR_ACCESS_DENIED && remoteResourceManagerIsLocal)
{
remoteResourceManagerIsLocal = false;
userClientCtxt = IntPtr.Zero;
authzRM.Dispose();
GetEffectivePermissions_AuthzInitializeLocalResourceManager();
GetEffectivePermissions_AuthzInitializeContextFromSid(id);
return;
}
if (win32Expn.NativeErrorCode != Win32Error.RPC_S_SERVER_UNAVAILABLE)
{
throw win32Expn;
}
}
}
private int GetEffectivePermissions_AuthzAccessCheck(ObjectSecurity sd)
{
var request = new AUTHZ_ACCESS_REQUEST();
request.DesiredAccess = StdAccess.MAXIMUM_ALLOWED;
request.PrincipalSelfSid = null;
request.ObjectTypeList = IntPtr.Zero;
request.ObjectTypeListLength = 0;
request.OptionalArguments = IntPtr.Zero;
var reply = new AUTHZ_ACCESS_REPLY();
reply.ResultListLength = 1;
reply.SaclEvaluationResults = IntPtr.Zero;
reply.GrantedAccessMask = pGrantedAccess = Marshal.AllocHGlobal(sizeof(uint));
reply.Error = pErrorSecObj = Marshal.AllocHGlobal(sizeof(uint));
byte[] rawSD = sd.GetSecurityDescriptorBinaryForm();
if (!AuthzAccessCheck(
AuthzACFlags.None,
userClientCtxt,
ref request,
IntPtr.Zero,
rawSD,
null,
0,
ref reply,
IntPtr.Zero))
{
var error = Marshal.GetLastWin32Error();
if (error != 0)
{
throw new Win32Exception();
}
}
var grantedAccess = Marshal.ReadInt32(pGrantedAccess);
return grantedAccess;
}
private void GetEffectivePermissions_FreeResouces()
{
Marshal.FreeHGlobal(pGrantedAccess);
Marshal.FreeHGlobal(pErrorSecObj);
if (userClientCtxt != IntPtr.Zero)
{
AuthzFreeContext(userClientCtxt);
userClientCtxt = IntPtr.Zero;
}
}
static RawSecurityDescriptor GetRawSecurityDescriptor(SafeFileHandle handle, SecurityInformationClass infoClass)
{
return new RawSecurityDescriptor(GetByteSecurityDescriptor(handle, infoClass), 0);
}
public static byte[] GetByteSecurityDescriptor(SafeFileHandle handle, SecurityInformationClass infoClass)
{
var tempSD = IntPtr.Zero;
var buffer = new byte[0];
try
{
uint error = GetSecurityInfo(handle,
ObjectType.File,
infoClass,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
out tempSD);
if (error != Win32Error.ERROR_SUCCESS)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
UInt32 sdLength = GetSecurityDescriptorLength(tempSD);
buffer = new byte[sdLength];
Marshal.Copy(tempSD, buffer, 0, (int)sdLength);
}
finally
{
Marshal.FreeHGlobal(tempSD);
tempSD = IntPtr.Zero;
}
return buffer;
}
#endregion
}
}