Impersonate user over a VPN in a desktop application
Asked Answered
J

2

8

I'm having problems trying to impersonate an active directory user in a desktop application. Every time I use the LogOn API the result is false.

The user and domain do exist since I can also authenticate the user over the DirectoryServices.AccountManagement on the same application.

Have read the documentation about impersonation in the Microsoft site and even some post here on the stack. Also, have used the SimpleImpersonation library with the same results.

public class Demo
{
    private WindowsImpersonationContext impersonationContext = null;

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern int LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
    private void Enter()
    {
        try
        {
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;
            string userName = "myValidUser";
            string domain = "my.domain.example";
            string password = "myValidPassword";

            if (LogonUser(userName, domain, password, (int)LogonType.LOGON32_LOGON_INTERACTIVE, (int)LogonProvider.LOGON32_PROVIDER_WINNT35, ref token) != 0)
            {
                WindowsIdentity WindowsIdentityPrincipal = new WindowsIdentity(token);
                if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                {
                    WindowsIdentity tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                    impersonationContext = tempWindowsIdentity.Impersonate();
                }
                else
                {
                    throw new Win32Exception(new Win32Exception(Marshal.GetLastWin32Error()).Message);
                }
            }
            else
            {
                //throws username or pass incorrect
                throw new Win32Exception(new Win32Exception(Marshal.GetLastWin32Error()).Message);
            }
        }
        catch (Exception exc)
        {
            throw exc;
        }
    }

    public enum LogonProvider
    {
        LOGON32_PROVIDER_DEFAULT = 0,
        LOGON32_PROVIDER_WINNT35 = 1,
        LOGON32_PROVIDER_WINNT40 = 2,
        LOGON32_PROVIDER_WINNT50 = 3
    }

    private enum LogonType
    {
        LOGON32_LOGON_INTERACTIVE = 2,
        LOGON32_LOGON_NETWORK = 3,
        LOGON32_LOGON_BATCH = 4,
        LOGON32_LOGON_SERVICE = 5,
        LOGON32_LOGON_UNLOCK = 7,
        LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
        LOGON32_LOGON_NEW_CREDENTIALS = 9,
    }
}

I don't know if the reason it isn't working is that my computer is running on an outside network and connecting/authenticating to the company network over a VPN.

Edit 1. The resulting error code is 1326 (unknown user name or bad password)

Edit 2. The method is trying to obtain the identity token for later use on thread impersonation.

Jacoby answered 4/6, 2019 at 17:39 Comment(3)
You may wish to add the actual Error code that's being returned. It's not necessarily just the password that can be the problem, it can also be the type of login you're trying to do isn't allowed for some reason. Unfortunately your question is more network security troubleshooting than actual coding issue from the quick glance I've had but perhaps error code will help shed some light.Quadrature
@Quadrature thanks for the advice, i just added the error codeJacoby
I believe that the problem is that you’re trying to logon as interactive user. As the computer, where logon occurs is not in the same domain, it cannot provide logon request to a domain controller. You have 2 options here: use logon_new_credentials as a logon type. In this case you will be acting as a local user on the workstation, but every network request will be impersonated under domain account. The second option (more preferred) is to pass credentials to directoryentry/ldapconnection constructors. That will result in a network logon and resolve the issueCrankle
G
0

You might want to check the documentation for the LogonUser function.

If your username is in the format [email protected] then you need to pass in:

If your username is in the format domain\user then you need to pass in:

  • lpszUserName = "user"
  • lpszDomain = "domain"

Treating the fully qualified username the wrong way will result in the error code you're seeing.

Gallon answered 11/6, 2019 at 23:37 Comment(0)
G
-1

You cannot use LogonUser to log on to a remote computer. You need to use WNetAddConnection2 api function. Please refer to the msdn documentation.

for LogonUser: https://learn.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-logonuserw

for WNetAddConnection2 : https://learn.microsoft.com/en-us/windows/desktop/api/winnetwk/nf-winnetwk-wnetaddconnection2w

here is a class that I have written :

public class RemoteNetworkConnector : IDisposable
{
    readonly string _networkName;

    public RemoteNetworkConnector(string networkName, NetworkCredential credentials)
    {
        _networkName = networkName;

        NetResource netResource = new NetResource
        {
            Scope = ResourceScope.GlobalNetwork,
            ResourceType = ResourceType.Disk,
            DisplayType = ResourceDisplaytype.Share,
            RemoteName = networkName
        };

        var userName = string.IsNullOrEmpty(credentials.Domain)
            ? credentials.UserName
            : string.Format(@"{0}\{1}", credentials.Domain, credentials.UserName);

        var connectionResult = WNetAddConnection2(
            netResource,
            credentials.Password,
            userName,
            0);

        if (connectionResult != 0)
        {
            throw new Win32Exception(connectionResult, "Error connecting to remote share");
        }
    }

    ~RemoteNetworkConnector()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        WNetCancelConnection2(_networkName, 0, true);
    }

    [DllImport("mpr.dll")]
    private static extern int WNetAddConnection2(NetResource netResource,
        string password, string username, int flags);

    [DllImport("mpr.dll")]
    private static extern int WNetCancelConnection2(string name, int flags,
        bool force);

    [StructLayout(LayoutKind.Sequential)]
    public class NetResource
    {
        public ResourceScope Scope;
        public ResourceType ResourceType;
        public ResourceDisplaytype DisplayType;
        public int Usage;
        public string LocalName;
        public string RemoteName;
        public string Comment;
        public string Provider;
    }

    public enum ResourceScope : int
    {
        Connected = 1,
        GlobalNetwork,
        Remembered,
        Recent,
        Context
    };

    public enum ResourceType : int
    {
        Any = 0,
        Disk = 1,
        Print = 2,
        Reserved = 8,
    }

    public enum ResourceDisplaytype : int
    {
        Generic = 0x0,
        Domain = 0x01,
        Server = 0x02,
        Share = 0x03,
        File = 0x04,
        Group = 0x05,
        Network = 0x06,
        Root = 0x07,
        Shareadmin = 0x08,
        Directory = 0x09,
        Tree = 0x0a,
        Ndscontainer = 0x0b
    }
}

I hope this will help.

Gascony answered 12/6, 2019 at 5:58 Comment(1)
I'm not trying to connect directly into a resource. That the method is trying to do is obtain the token so it can be used later for thread impersonation.Jacoby

© 2022 - 2024 — McMap. All rights reserved.