Home All Groups Group Topic Archive Search About

Have a NTAccount, need FileSystem permissions

Author
1 Aug 2006 12:11 AM
prilmeie
Hi NG,

I have just started doing .NET 2.0 and I am a bit curious about the
System.Security.Principal and System.Security.AccessControl namespaces
and their interaction.

I have a simple task: Given a NTAccount object (and a password), tell
me whether that account can execute a file or not. I have now spent
four hours to figure this out, and here is finally my solution (C#),
which I personally dislike for many reasons:

I am asking now the public how this should have been done. It goes
along the lines with this code (Maybe faulty, but I guess you get the
idea):

using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.AccessControl;
using System.Security.Principal;

namespace Test
{
    public class TestClass
    {
        public static bool CanExecute(NTAccount account, SecureString
password, string file, FileSystemRights right)
        {
            return TestClass.HasRight(account, password, file,
FileSystemRights.ExecuteFile);
        }

        private static bool HasRight(NTAccount account, SecureString
password, string file, FileSystemRights right)
        {
            WindowsIdentity user = TestClass.getUser(account,
password);

            FileInfo info = new FileInfo(file);
            FileSecurity fs = info.GetAccessControl();

            foreach (FileSystemAccessRule rule in
fs.GetAccessRules(true, true, typeof(IdentityReference)))
            {
                if ((user.Groups.Contains(rule.IdentityReference)
                    || user.User.Equals(rule.IdentityReference))
                    &&
rule.AccessControlType.Equals(AccessControlType.Allow)
                    && ((rule.FileSystemRights & right) == right))
                {
                    return true;
                }
            }

            return false;
        }

        /* Taken and adapted from the net - credentials go to
http://pluralsight.com/wiki/default.aspx/Keith.GuideBook/HowToGetATokenForAUser.html
*/
        private static WindowsIdentity getUser(NTAccount account,
SecureString password)
        {
            // need a full duplex stream - loopback is easiest way to
get that
            TcpListener tcpListener = new
TcpListener(IPAddress.Loopback, 0);
            tcpListener.Start();

            WindowsIdentity id = null;
            tcpListener.BeginAcceptTcpClient(delegate(IAsyncResult
asyncResult)
            {
                try
                {
                    using (NegotiateStream serverSide = new
NegotiateStream(

tcpListener.EndAcceptTcpClient(asyncResult).GetStream()))
                    {

serverSide.AuthenticateAsServer(CredentialCache.DefaultNetworkCredentials,
                             ProtectionLevel.None,
TokenImpersonationLevel.Impersonation);
                        id =
(WindowsIdentity)serverSide.RemoteIdentity;
                    }
                }
                catch
                { id = null; }
            }, null);

            TcpClient client = new TcpClient(new
IPEndPoint(IPAddress.Loopback,
                 ((IPEndPoint)tcpListener.LocalEndpoint).Port));
            using (NegotiateStream clientSide = new
NegotiateStream(client.GetStream()))
            {
                NetworkCredential netcred = new NetworkCredential();
                netcred.UserName = account.ToString();
                netcred.Password =
Marshal.PtrToStringUni(Marshal.SecureStringToBSTR(password));
                clientSide.AuthenticateAsClient(netcred,
                     "", ProtectionLevel.None,
TokenImpersonationLevel.Impersonation);
            }

            return id;
        }
    }
}

Is there a better way to achieve this? The whole getUser method is
nothing other than a hack. I also dislike the way I have to check the
permissions myself. There must be an easier way.

(I am sorry should I have picked the wrong NG, in that case, can you
tell me the correct one)

Best regards,
Franz

Author
1 Aug 2006 6:54 AM
Dominick Baier
Hi,

to get the SID of a user - you simply need to construct a NTAccount object
like

NTAccount acc = new NTAccount("domain\\user");

afterwards you translate to a SID:

SecurityIdentifier sid = (SecurityIdentifier)acc.Translate(typeof(NTAccount));

you get the SID now using:

sid.Value;

thats the equivalent of user.User.IdentityReference.

This eliminates the nees for the NegotiateStream handshake.


dominick


Show quoteHide quote
> Hi NG,
>
> I have just started doing .NET 2.0 and I am a bit curious about the
> System.Security.Principal and System.Security.AccessControl namespaces
> and their interaction.
>
> I have a simple task: Given a NTAccount object (and a password), tell
> me whether that account can execute a file or not. I have now spent
> four hours to figure this out, and here is finally my solution (C#),
> which I personally dislike for many reasons:
>
> I am asking now the public how this should have been done. It goes
> along the lines with this code (Maybe faulty, but I guess you get the
> idea):
>
> using System;
> using System.IO;
> using System.Net;
> using System.Net.Security;
> using System.Net.Sockets;
> using System.Runtime.InteropServices;
> using System.Security;
> using System.Security.AccessControl;
> using System.Security.Principal;
> namespace Test
> {
> public class TestClass
> {
> public static bool CanExecute(NTAccount account, SecureString
> password, string file, FileSystemRights right)
> {
> return TestClass.HasRight(account, password, file,
> FileSystemRights.ExecuteFile);
> }
> private static bool HasRight(NTAccount account, SecureString
> password, string file, FileSystemRights right)
> {
> WindowsIdentity user = TestClass.getUser(account,
> password);
> FileInfo info = new FileInfo(file);
> FileSecurity fs = info.GetAccessControl();
> foreach (FileSystemAccessRule rule in
> fs.GetAccessRules(true, true, typeof(IdentityReference)))
> {
> if ((user.Groups.Contains(rule.IdentityReference)
> || user.User.Equals(rule.IdentityReference))
> &&
> rule.AccessControlType.Equals(AccessControlType.Allow)
> && ((rule.FileSystemRights & right) == right))
> {
> return true;
> }
> }
> return false;
> }
> /* Taken and adapted from the net - credentials go to
> http://pluralsight.com/wiki/default.aspx/Keith.GuideBook/HowToGetAToke
> nForAUser.html
> */
> private static WindowsIdentity getUser(NTAccount account,
> SecureString password)
> {
> // need a full duplex stream - loopback is easiest way to
> get that
> TcpListener tcpListener = new
> TcpListener(IPAddress.Loopback, 0);
> tcpListener.Start();
> WindowsIdentity id = null;
> tcpListener.BeginAcceptTcpClient(delegate(IAsyncResult
> asyncResult)
> {
> try
> {
> using (NegotiateStream serverSide = new
> NegotiateStream(
> tcpListener.EndAcceptTcpClient(asyncResult).GetStream()))
> {
> serverSide.AuthenticateAsServer(CredentialCache.DefaultNetworkCredenti
> als,
> ProtectionLevel.None,
> TokenImpersonationLevel.Impersonation);
> id =
> (WindowsIdentity)serverSide.RemoteIdentity;
> }
> }
> catch
> { id = null; }
> }, null);
> TcpClient client = new TcpClient(new
> IPEndPoint(IPAddress.Loopback,
> ((IPEndPoint)tcpListener.LocalEndpoint).Port));
> using (NegotiateStream clientSide = new
> NegotiateStream(client.GetStream()))
> {
> NetworkCredential netcred = new NetworkCredential();
> netcred.UserName = account.ToString();
> netcred.Password =
> Marshal.PtrToStringUni(Marshal.SecureStringToBSTR(password));
> clientSide.AuthenticateAsClient(netcred,
> "", ProtectionLevel.None,
> TokenImpersonationLevel.Impersonation);
> }
> return id;
> }
> }
> }
> Is there a better way to achieve this? The whole getUser method is
> nothing other than a hack. I also dislike the way I have to check the
> permissions myself. There must be an easier way.
>
> (I am sorry should I have picked the wrong NG, in that case, can you
> tell me the correct one)
>
> Best regards,
> Franz
Author
1 Aug 2006 12:08 PM
prilmeie
Hi Dominick,

Show quoteHide quote
> to get the SID of a user - you simply need to construct a NTAccount object
> like
>
> NTAccount acc = new NTAccount("domain\\user");
>
> afterwards you translate to a SID:
>
> SecurityIdentifier sid = (SecurityIdentifier)acc.Translate(typeof(NTAccount));
>
> you get the SID now using:
>
> sid.Value;
>
> thats the equivalent of user.User.IdentityReference.

In some respect you are right, but the problem is that starting from a
NTAccount object I don't know whether this is a group or an personal
account. It was only possible using instances of WindowsIdentity where
I got the User.IdentityReference property as a bonus, so I can safe
myself that few lines of code (perfomance is not a key factor here).

>
> This eliminates the nees for the NegotiateStream handshake.
>

No, it doesn't, since I need to know which groups a personal NTAccount
belongs to, to actually check the access rights for group membership as
well.

The more I look into it the less I like the .NET 2.0 changes of the
namespaces System.Security.Principal and System.Security.AccessControl.
There are lots of examples how to change a access/audit rules, but
surprisingly(!) there are none to check these rules. Furthermore the
object model is unbalanced, e.g. the rights enumerations don't have a
common interface/class. Just a random frustration rant - please ignore.

If OO would have been applied correctly, I should be able to implement
such a method:
public static bool HasRight (
System.Security.AccessControl.ObjectSecurity object,
System.Security.Principal.IPrincipal principal,
System.Security.AccessControl.AccessRule right );

(Actually something similar should be part of ObjectSecurity)
Let's see what .NET 2.1 brings here :-(

Basically I have to leave managed code and go to unmanaged.

Best regards,
Franz
Author
1 Aug 2006 5:09 PM
Dominick Baier
OK -

which OS is this??

if it is XP/2K3 you can use Win32 LogonUser to get a token - which in turn
can be used to create a WindowsIdentity...

You are right - whats totally missing in the API is the equivalent of the
"effective permissions" tab in the security settings in explorer.

Go wrap them! That'll be an awesome contribution to the community :))

dominick

Show quoteHide quote
> Hi Dominick,
>
>> to get the SID of a user - you simply need to construct a NTAccount
>> object like
>>
>> NTAccount acc = new NTAccount("domain\\user");
>>
>> afterwards you translate to a SID:
>>
>> SecurityIdentifier sid =
>> (SecurityIdentifier)acc.Translate(typeof(NTAccount));
>>
>> you get the SID now using:
>>
>> sid.Value;
>>
>> thats the equivalent of user.User.IdentityReference.
>>
> In some respect you are right, but the problem is that starting from a
> NTAccount object I don't know whether this is a group or an personal
> account. It was only possible using instances of WindowsIdentity where
> I got the User.IdentityReference property as a bonus, so I can safe
> myself that few lines of code (perfomance is not a key factor here).
>
>> This eliminates the nees for the NegotiateStream handshake.
>>
> No, it doesn't, since I need to know which groups a personal NTAccount
> belongs to, to actually check the access rights for group membership
> as well.
>
> The more I look into it the less I like the .NET 2.0 changes of the
> namespaces System.Security.Principal and
> System.Security.AccessControl. There are lots of examples how to
> change a access/audit rules, but surprisingly(!) there are none to
> check these rules. Furthermore the object model is unbalanced, e.g.
> the rights enumerations don't have a common interface/class. Just a
> random frustration rant - please ignore.
>
> If OO would have been applied correctly, I should be able to implement
> such a method:
> public static bool HasRight (
> System.Security.AccessControl.ObjectSecurity object,
> System.Security.Principal.IPrincipal principal,
> System.Security.AccessControl.AccessRule right );
> (Actually something similar should be part of ObjectSecurity) Let's
> see what .NET 2.1 brings here :-(
>
> Basically I have to leave managed code and go to unmanaged.
>
> Best regards,
> Franz
Author
1 Aug 2006 6:40 PM
Joe Kaplan (MVP - ADSI)
He can also use protocol transition/S4U to create a token if he has 2003
server and 2003 AD.

Probably the easiest thing to do is use AuthZAccessCheck though via
p/invoke.  It is a little ugly, but is the right way to do this given that
he doesn't have a token for the user.  If he had a token, then AccessCheck
is the right way to do it.

It would be nice if MS would wrap these up in .NET, as they always say not
to try to interpret the results yourself by examining the security
descriptor directly, but then they don't make it easy to use the built in
stuff in .NET and only give us the security descriptor to look at (better
than what we had in 1.x, but still not enough...).

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
Show quoteHide quote
"Dominick Baier" <dbaier@pleasepleasenospam_leastprivilege.com> wrote in
message news:4580be63a75e8c883b5343ee040@news.microsoft.com...
> OK -
> which OS is this??
>
> if it is XP/2K3 you can use Win32 LogonUser to get a token - which in turn
> can be used to create a WindowsIdentity...
>
> You are right - whats totally missing in the API is the equivalent of the
> "effective permissions" tab in the security settings in explorer.
>
> Go wrap them! That'll be an awesome contribution to the community :))
>
> dominick
>
>> Hi Dominick,
>>
>>> to get the SID of a user - you simply need to construct a NTAccount
>>> object like
>>>
>>> NTAccount acc = new NTAccount("domain\\user");
>>>
>>> afterwards you translate to a SID:
>>>
>>> SecurityIdentifier sid =
>>> (SecurityIdentifier)acc.Translate(typeof(NTAccount));
>>>
>>> you get the SID now using:
>>>
>>> sid.Value;
>>>
>>> thats the equivalent of user.User.IdentityReference.
>>>
>> In some respect you are right, but the problem is that starting from a
>> NTAccount object I don't know whether this is a group or an personal
>> account. It was only possible using instances of WindowsIdentity where
>> I got the User.IdentityReference property as a bonus, so I can safe
>> myself that few lines of code (perfomance is not a key factor here).
>>
>>> This eliminates the nees for the NegotiateStream handshake.
>>>
>> No, it doesn't, since I need to know which groups a personal NTAccount
>> belongs to, to actually check the access rights for group membership
>> as well.
>>
>> The more I look into it the less I like the .NET 2.0 changes of the
>> namespaces System.Security.Principal and
>> System.Security.AccessControl. There are lots of examples how to
>> change a access/audit rules, but surprisingly(!) there are none to
>> check these rules. Furthermore the object model is unbalanced, e.g.
>> the rights enumerations don't have a common interface/class. Just a
>> random frustration rant - please ignore.
>>
>> If OO would have been applied correctly, I should be able to implement
>> such a method:
>> public static bool HasRight (
>> System.Security.AccessControl.ObjectSecurity object,
>> System.Security.Principal.IPrincipal principal,
>> System.Security.AccessControl.AccessRule right );
>> (Actually something similar should be part of ObjectSecurity) Let's
>> see what .NET 2.1 brings here :-(
>>
>> Basically I have to leave managed code and go to unmanaged.
>>
>> Best regards,
>> Franz
>
>
Author
1 Aug 2006 6:50 PM
Dominick Baier
I didn't mention PT because i knew you would suggest it :))

Yeah there should be proper wrappers around all these APIs...

dominick


Show quoteHide quote
> He can also use protocol transition/S4U to create a token if he has
> 2003 server and 2003 AD.
>
> Probably the easiest thing to do is use AuthZAccessCheck though via
> p/invoke.  It is a little ugly, but is the right way to do this given
> that he doesn't have a token for the user.  If he had a token, then
> AccessCheck is the right way to do it.
>
> It would be nice if MS would wrap these up in .NET, as they always say
> not to try to interpret the results yourself by examining the security
> descriptor directly, but then they don't make it easy to use the built
> in stuff in .NET and only give us the security descriptor to look at
> (better than what we had in 1.x, but still not enough...).
>
> Joe K.
>
Author
1 Aug 2006 6:56 AM
Dominick Baier
re: The ACL check.

I don't think you are catching the case where the user is in some nested
group structure...there is unfortunately no wrapper for the Win32 AuthZ APIs,
which do exactly what you are looking for.

dominick


Show quoteHide quote
> Hi NG,
>
> I have just started doing .NET 2.0 and I am a bit curious about the
> System.Security.Principal and System.Security.AccessControl namespaces
> and their interaction.
>
> I have a simple task: Given a NTAccount object (and a password), tell
> me whether that account can execute a file or not. I have now spent
> four hours to figure this out, and here is finally my solution (C#),
> which I personally dislike for many reasons:
>
> I am asking now the public how this should have been done. It goes
> along the lines with this code (Maybe faulty, but I guess you get the
> idea):
>
> using System;
> using System.IO;
> using System.Net;
> using System.Net.Security;
> using System.Net.Sockets;
> using System.Runtime.InteropServices;
> using System.Security;
> using System.Security.AccessControl;
> using System.Security.Principal;
> namespace Test
> {
> public class TestClass
> {
> public static bool CanExecute(NTAccount account, SecureString
> password, string file, FileSystemRights right)
> {
> return TestClass.HasRight(account, password, file,
> FileSystemRights.ExecuteFile);
> }
> private static bool HasRight(NTAccount account, SecureString
> password, string file, FileSystemRights right)
> {
> WindowsIdentity user = TestClass.getUser(account,
> password);
> FileInfo info = new FileInfo(file);
> FileSecurity fs = info.GetAccessControl();
> foreach (FileSystemAccessRule rule in
> fs.GetAccessRules(true, true, typeof(IdentityReference)))
> {
> if ((user.Groups.Contains(rule.IdentityReference)
> || user.User.Equals(rule.IdentityReference))
> &&
> rule.AccessControlType.Equals(AccessControlType.Allow)
> && ((rule.FileSystemRights & right) == right))
> {
> return true;
> }
> }
> return false;
> }
> /* Taken and adapted from the net - credentials go to
> http://pluralsight.com/wiki/default.aspx/Keith.GuideBook/HowToGetAToke
> nForAUser.html
> */
> private static WindowsIdentity getUser(NTAccount account,
> SecureString password)
> {
> // need a full duplex stream - loopback is easiest way to
> get that
> TcpListener tcpListener = new
> TcpListener(IPAddress.Loopback, 0);
> tcpListener.Start();
> WindowsIdentity id = null;
> tcpListener.BeginAcceptTcpClient(delegate(IAsyncResult
> asyncResult)
> {
> try
> {
> using (NegotiateStream serverSide = new
> NegotiateStream(
> tcpListener.EndAcceptTcpClient(asyncResult).GetStream()))
> {
> serverSide.AuthenticateAsServer(CredentialCache.DefaultNetworkCredenti
> als,
> ProtectionLevel.None,
> TokenImpersonationLevel.Impersonation);
> id =
> (WindowsIdentity)serverSide.RemoteIdentity;
> }
> }
> catch
> { id = null; }
> }, null);
> TcpClient client = new TcpClient(new
> IPEndPoint(IPAddress.Loopback,
> ((IPEndPoint)tcpListener.LocalEndpoint).Port));
> using (NegotiateStream clientSide = new
> NegotiateStream(client.GetStream()))
> {
> NetworkCredential netcred = new NetworkCredential();
> netcred.UserName = account.ToString();
> netcred.Password =
> Marshal.PtrToStringUni(Marshal.SecureStringToBSTR(password));
> clientSide.AuthenticateAsClient(netcred,
> "", ProtectionLevel.None,
> TokenImpersonationLevel.Impersonation);
> }
> return id;
> }
> }
> }
> Is there a better way to achieve this? The whole getUser method is
> nothing other than a hack. I also dislike the way I have to check the
> permissions myself. There must be an easier way.
>
> (I am sorry should I have picked the wrong NG, in that case, can you
> tell me the correct one)
>
> Best regards,
> Franz