LogonUser to remote machine fails with error 1326

F

Frank Munsberg

For testing purposes I wanted to access the administrative share c$ of a
remote machine which serves as a FTP server in my current project. I've spent
the better part of yesterday trying to get the LogonUser API to work with
little success.

My machine (where the code runs) is part of a domain where I'm a regular
domain user and a local admin, the other machine isn't in any domain and is
some virtual machine I set up. I can access the remote machine via explorer
supplying the credentials for the remote machine's local admin but LogonUser
doesn't seem to be able to do it. Both machines run XP.

The important bits and pieces of the code are as follows:

[DllImport("advapi32.DLL", SetLastError=true)]
public static extern int LogonUser(string lpszUsername, string lpszDomain
,string lpszPassword , int dwLogonType , int dwLogonProvider ,out IntPtr
phToken);

public void some_unit_test()
{
IntPtr lremote_user = default(IntPtr);
LogonUser("Administrator", "192.168.183.148", "some_password", 2, 3,out
lremote_user);
Assert.That(lremote_user, Is.Not.EqualTo(IntPtr.Zero), "LogonUser returned
error code " + Marshal.GetLastWin32Error().ToString());
WindowsIdentity lidentity= new WindowsIdentity( lremote_user, "LogonUser",
WindowsAccountType.Normal, true);
WindowsImpersonationContext limpersonationcontext = lidentity.Impersonate();
if (System.IO.Directory.Exists(@"\\192.168.183.148\c$\ftp\testuser))
System.IO.Directory.Delete(@"\\192.168.183.148\c$\ftp\testuser);
limpersonationcontext.Undo();
// do some actual testing
}

The logon type is Interactive (2), the provider was set to
LOGON32_PROVIDER_WINNT50 (3), also 0, 1 and 2 yield the same error# 1326.

For the time being I'll stick to mapping a network share manually for
testing but if anybody has any advice on what I'm doing wrong please do tell!

Greets
Frank
 
L

Larry Smith

Without getting into too many details here (it's a deep area), try using "."
for the 2nd arg of "LogonUser()" instead (also do some research into
LOGON32_LOGON_NEW_CREDENTIALS for the 4th arg). This assumes there's an
"Administrator" account on your own machine with the same password as on the
remote machine. In fact, there has to be a matching account and password on
both your machine and the remote, *non-domain* machine for this to work
(since a logon session will shortly be established on the remote machine
using these impersonated credentials - see link below). You can also
(alternatively) call "NetUseAdd()" in the WinAPI or "WNetAddConnection()"
and cousins (you can pass the remote credentials accordingly). For a little
better insight (though I didn't provide any coding examples - sorry), see
one of my previous postings which I dug up here (badly formatted for some
reason though):

http://www.tech-archive.net/Archive...dotnet.languages.csharp/2008-08/msg00036.html

Also see the book I mentioned at the top of this post for complete details
(chapter 8 in particular). Ultimately you'll have to do additional reading
(of this book) and experimentation to really get your head around everything
(and avoid some potential pitfalls). Like most you probably don't have the
time though.
 
F

Frank Munsberg

From what I've understood so far the posting you mentioned says to either
enable the guest account or have administrator accounts with matching
passwords on both machines.
Somehow the last bit of the article confuses me, as it says that you can
also pass credentials to the remote machine to authenticate which sounds like
what I'm actually trying to do with little success.

Right now I'm not really able to read a book on the subject but maybe I need
to for some upcoming project anyway. I did a quick search for the author
Keith Brown you mentioned though and found a .Net related book from him named
"The .net guide to developing windows security" which actually is available
online at
http://alt.pluralsight.com/wiki/default.aspx/Keith.GuideBook.HomePage
 
L

Larry Smith

From what I've understood so far the posting you mentioned says to either
enable the guest account or have administrator accounts with matching
passwords on both machines.
Somehow the last bit of the article confuses me, as it says that you can
also pass credentials to the remote machine to authenticate which sounds
like
what I'm actually trying to do with little success.
From what I've understood so far the posting you mentioned says to either
enable the guest account or have administrator accounts with matching
passwords on both machines. Somehow the last bit of the article confuses
me,
as it says that you can also pass credentials to the remote machine to
authenticate
which sounds like what I'm actually trying to do with little success.

Actually you're confused but that's normal given how poorly this stuff is
documented. I can't explain everything here but let me try to fill in some
gaps.

First, note that every user account in Windows is either created on the
local machine or it's a member of a Windows domain (a network of trusted
Windows computers controlled by a central machine called a domain
controller - this is where domain accounts are created). The former case
refers to users you create locally on a given machine (your own usually),
and if that machine doesn't belong to a Windows domain, then only those
locally-created users can be logged on (you can't log users on from another
machine - read on however). If a machine is also a member of a Windows
domain however, users can be created on the domain controller as well (in
addition to the local machine) and these domain users can also be logged on.
..
Now, when you invoke "LogonUser()", it attempts to log a user onto the
machine you call it from. IOW, the call you're applying:

LogonUser("Administrator", "192.168.183.148", "some_password", 2, 3,out
lremote_user);

is attempting to log on a user named "Administrator" whose account exists in
the domain named "192.168.183.148" (keep reading). It's trying to log them
onto *your* machine however. This is what "LogonUser()" does. In order to do
this, your machine must authenticate this user whose account doesn't exist
on your machine. It exists on the domain named "192.168.183.148" since
that's what you're telling the function (even if there's a local account
named "Administrator" - that's a completely different account from the
"Administrator" account on the "192.168.183.148" domain). So, in order to
authenticate this user using the password you've provided, your machine must
somehow send these credentials to the domain controller for domain
"192.168.183.148" since that's where the "Administrator" account was created
(and only it knows the password - again, this is based on what you're
telling the function via the 2nd arg). Unfortunately, there is no domain
named "192.168.183.148". There may be a machine with that IP address but
that's not the same as a domain. Your machine therefore doesn't even
recognize "192.168.183.148" as a legitimate domain and even if it did, even
though it may recognize a machine with that IP address on your network, it
doesn't trust that machine to vouch for the user named "Administrator"
(which exists as a local account on that machine). The only computer your
own machine trusts are itself (if you want to log on a user that was created
locally), or the domain controller for the domain your computer belongs to
(or the domain controller for any other domain your own domain trusts). In
the latter case for example, if you passed the name of your own machine's
domain as the 2nd arg instead, then your machine would trust the domain
controller for that domain to vouch for the user named "Administrator"
(assuming there is such a user on that domain). It doesn't trust the unknown
or unrecognized domain "192.168.183.148" however. This is just how things
work in Windows and it makes sense (but that's another story).

Now, if you pass an unknown domain for the 2nd arg of "LogonUser()" or a
domain name your own computer doesn't trust, then your machine will
recognize the user name (the 1st arg) but only if there's a matching local
account name and password (3rd arg) on your machine. That is, your machine
simply interprets the request as if the local user named "Administrator" is
trying to log on to the machine regardless of the domain name you pass via
the 2nd arg (again, assuming that domain is unrecognized or untrusted). Pass
the right password IOW and you're machine will allow that local user to
logon. The computer has a local user with this name IOW and you're passing
the correct password even though it doesn't trust the domain name you're
passing (so it simply ignores it and treats the first arg as a local user
instead). Now maybe you can better understand why you're getting error 1326
(Logon failure: unknown user name or bad password). You're trying to logon
user "Administrator" but your machine doesn't trust the domain name you're
passing so it's trying to logon on user "Administrator" from your own
machine which either doesn't exist or if it does, the password is incorrect
(since the 3rd arg is presumably the password for the "Administrator"
account on machine "192.168.183.148" and not your own machine).

So what to do. Well, now maybe the article I cited (wrote) in my last post
has more meaning. If you try to access a shared resource on another machine,
now it's *that* machine which effectively calls "LogonUser()" behind the
scenes (and not your own). There's a server which runs on that machine (on
all Windows machines actually, unless disabled), and that server
authenticates incoming users for this type of processing (accessing of
shared resources). If you try to access C$ for instance, before doing so,
the server on the remote machine must authenticate the incoming user and
create an access token (the last arg of "LogonUser()") before allowing the
caller to touch C$ (or any other shared resource). Once authenticated, it
then impersontates that user using the access token whenever you access any
shared resource (C$ or whatever). In this way you can access C$ only as your
token permits it on that machine (if it's permitted at all). This is
standard Windows security in action. If you log onto your own machine using
your domain account for instance (say "FrankM" in the "YourDomain" domain),
and you now launch Windows explorer and try to access a shared resource on
another machine (C$ or whatever), that machine will effectively call
"LogonUser()" behind the scenes, passing "FrankM" as the first arg and
"YourDomain" as the 2nd arg (this is the simplified story but conceptually
correct). Now the same details described earlier will play out but on the
remote machine instead. That is, the remote machine will send your
credentials to "YourDomain" for authentication but only if the remote
machine is also part of the "YourDomain" domain (or its own domain trusts
"YourDomain" to vouch for user "YourDomain\FrankM"). If the remote machine
doesn't recognize or doesn't trust "YourDomain" however, or if it doesn't
even belong to a domain (which applies in your case), then as described
earlier, it will simply look for a local "FrankM" account instead (and
authenticate against that). Once (or rather if) FrankM is authenticated, an
access token is created on the remote machine for user "FrankM". You're now
logged onto the remote server using this token and you can now access
anything on that machine which this token is permitted to touch (C$ or
whatever). It's important to note that while you may have initiated all this
from your own local machine (by accessing C$ in Windows Explorer in the
first place), C$ is still on the other (remote) machine. Anything you do on
your local machine is therefore transmitted to the remote machine which
impersonsates FrankM using the token that was created and then tries to
carry out your commands using this identity (ultimately transmitting the
results back to your own local machine where Windows Explorer just dumps the
results for you).

Note that in the example just described above, the credentials for
"YourDomain\FrankM" were passed to the remote server for authentication
because by default, the user associated with the current thread is what's
used (namely, the account you logged onto Windows with which is normally in
effect when you access a shared resource using Windows Explorer or by any
other means). In code however (or even from the command prompt using "Net
Use"), you can pass alternate credentials to the remote machine if you wish.
This is what you've been trying to do by calling "LogonUser()" and
impersonating, hence authentication on the remote machine will be carried
out against this impersonated user (since the thread's security context will
have that identity when you try to access the remote machine). Have a look
at "NetUseAdd()" in the WinAPI as another way however (or the other
functions mentioned in my last post). You can pass the credentials to the
remote machine without having to impersonate at all. In this way, whenever
you access the remote machine, it will use the credentials previously passed
via "NetUseAdd()" and not the credentials associated with the current
thread. You really need to read up on this stuff however and perform some
experimentation before you can proceed. You simply won't understand what
you're doing unless you read up on the basic Windows authentication model
and the use of access tokens (which is all standard Windows stuff). It will
also help you avoid certain pitfalls that can occur (which I haven't
discussed). See the link below for the book I mentioned and consult chapter
8 as I suggested. That chapter discusses your particular situation in great
detail but some of the earlier chapters are required for theoretical
grounding. I know at this stage it seems like a lot to learn but it's
actually easier than it seems. The basic security model can be understood in
a few days or less without having to become a security expert. Feeling
completely comfortable takes a little longer of course. Good luck.

http://www.amazon.com/Programming-Windows-Security-DevelopMentor-Keith/dp/0201604426
 
T

tommaso baviera

I think I could get crazy with the situation I have, just around this "Impersonation" problem. That's the fact:
My program runs on Machine A and access a directory on a remote Server "impersonating" an user (with domain specification) with all privileges on that directory, while the user logged on the Machine A does not have any privileges on the same directory.
Everything SEEMS to work fine: using the program I can do operation like:

File.Copy()
File.Move()
File.Delete()
File.AppendAllText()

on the directory (that I cannot access with explorer) but if I try, always through my program, to locally execute a remote file txt, pdf, doc or whatever, using Process.Start() it unexpectedly fails! With a really ugly "access denied".
It doesn't matter if I am logged on the Machine A with an user on the domain or on the local machine, it's just the same.

This is the code, but I think the problem it's not here:
----------------------------------------------------
using System.Diagnostics;
using System.Security;

Process proc = new Process();
SecureString ssPwd = new SecureString();

proc.StartInfo.UseShellExecute = false;
proc.StartInfo.FileName = path;
proc.StartInfo.Arguments = "";
proc.StartInfo.Domain = domain;
proc.StartInfo.UserName = user;
string password = pwd;
for (int x = 0; x < password.Length; x++) {
ssPwd.AppendChar(password[x]);
}
proc.StartInfo.Password = ssPwd;
proc.Start();
----------------------------------------------------

any suggestion?

Thanks in advance.

Tommaso
For testing purposes I wanted to access the administrative share c$ of a
remote machine which serves as a FTP server in my current project. I've spent
the better part of yesterday trying to get the LogonUser API to work with
little success.

My machine (where the code runs) is part of a domain where I'm a regular
domain user and a local admin, the other machine isn't in any domain and is
some virtual machine I set up. I can access the remote machine via explorer
supplying the credentials for the remote machine's local admin but LogonUser
doesn't seem to be able to do it. Both machines run XP.

The important bits and pieces of the code are as follows:

[DllImport("advapi32.DLL", SetLastError=true)]
public static extern int LogonUser(string lpszUsername, string lpszDomain
,string lpszPassword , int dwLogonType , int dwLogonProvider ,out IntPtr
phToken);

public void some_unit_test()
{
IntPtr lremote_user = default(IntPtr);
LogonUser("Administrator", "192.168.183.148", "some_password", 2, 3,out
lremote_user);
Assert.That(lremote_user, Is.Not.EqualTo(IntPtr.Zero), "LogonUser returned
error code " + Marshal.GetLastWin32Error().ToString());
WindowsIdentity lidentity= new WindowsIdentity( lremote_user, "LogonUser",
WindowsAccountType.Normal, true);
WindowsImpersonationContext limpersonationcontext = lidentity.Impersonate();
if (System.IO.Directory.Exists(@"\\192.168.183.148\c$\ftp\testuser))
System.IO.Directory.Delete(@"\\192.168.183.148\c$\ftp\testuser);
limpersonationcontext.Undo();
// do some actual testing
}

The logon type is Interactive (2), the provider was set to
LOGON32_PROVIDER_WINNT50 (3), also 0, 1 and 2 yield the same error# 1326.

For the time being I'll stick to mapping a network share manually for
testing but if anybody has any advice on what I'm doing wrong please do tell!

Greets
Frank
On Wednesday, March 25, 2009 9:52 AM Larry Smith wrote:
Without getting into too many details here (it's a deep area), try using "."
for the 2nd arg of "LogonUser()" instead (also do some research into
LOGON32_LOGON_NEW_CREDENTIALS for the 4th arg). This assumes there's an
"Administrator" account on your own machine with the same password as on the
remote machine. In fact, there has to be a matching account and password on
both your machine and the remote, *non-domain* machine for this to work
(since a logon session will shortly be established on the remote machine
using these impersonated credentials - see link below). You can also
(alternatively) call "NetUseAdd()" in the WinAPI or "WNetAddConnection()"
and cousins (you can pass the remote credentials accordingly). For a little
better insight (though I didn't provide any coding examples - sorry), see
one of my previous postings which I dug up here (badly formatted for some
reason though):

http://www.tech-archive.net/Archive...dotnet.languages.csharp/2008-08/msg00036.html

Also see the book I mentioned at the top of this post for complete details
(chapter 8 in particular). Ultimately you'll have to do additional reading
(of this book) and experimentation to really get your head around everything
(and avoid some potential pitfalls). Like most you probably don't have the
time though.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top