Cygwin: seteuid: use Kerberos/MsV1_0 S4U authentication by default

- This simple and official method replaces cyglsa and "create token"
  methods.  No network share access, same as before.

- lsaauth and create_token are disabled now.  If problems crop up,
  they can be easily reactivated.  If no problems crop up, they
  can be removed in a while, together with the lsaauth subdir.

- Bump Cygwin version to 3.0.

Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
This commit is contained in:
Corinna Vinschen 2019-01-26 18:33:41 +01:00
parent 84230b71c6
commit 0fb497165f
8 changed files with 352 additions and 78 deletions

View File

@ -647,6 +647,7 @@ LoadDLLfunc (LsaFreeReturnBuffer, 4, secur32)
LoadDLLfunc (LsaLogonUser, 56, secur32)
LoadDLLfunc (LsaLookupAuthenticationPackage, 12, secur32)
LoadDLLfunc (LsaRegisterLogonProcess, 12, secur32)
LoadDLLfunc (TranslateNameW, 20, secur32)
LoadDLLfunc (SHGetDesktopFolder, 4, shell32)

View File

@ -10,7 +10,7 @@ details. */
the Cygwin shared library". This version is used to track important
changes to the DLL and is mainly informative in nature. */
#define CYGWIN_VERSION_DLL_MAJOR 2012
#define CYGWIN_VERSION_DLL_MAJOR 3000
#define CYGWIN_VERSION_DLL_MINOR 0
/* Major numbers before CYGWIN_VERSION_DLL_EPOCH are incompatible. */

View File

@ -58,6 +58,9 @@ What changed:
- Improve uname(2) for newly built applications.
- Kerberos/MSV1_0 S4U authentication replaces two old methods:
Creating a token from scratch and Cygwin LSA authentication package.
Bug Fixes
---------

View File

@ -24,6 +24,8 @@ details. */
#include <iptypes.h>
#include <wininet.h>
#include <userenv.h>
#define SECURITY_WIN32
#include <secext.h>
#include "cyglsa.h"
#include "cygserver_setpwd.h"
#include <cygwin/version.h>
@ -872,6 +874,32 @@ verify_token (HANDLE token, cygsid &usersid, user_groups &groups, bool *pintern)
|| groups.pgsid == usersid;
}
const char *
account_restriction (NTSTATUS status)
{
const char *type;
switch (status)
{
case STATUS_INVALID_LOGON_HOURS:
type = "Logon outside allowed hours";
break;
case STATUS_INVALID_WORKSTATION:
type = "Logon at this machine not allowed";
break;
case STATUS_PASSWORD_EXPIRED:
type = "Password expired";
break;
case STATUS_ACCOUNT_DISABLED:
type = "Account disabled";
break;
default:
type = "Unknown";
break;
}
return type;
}
HANDLE
create_token (cygsid &usersid, user_groups &new_groups)
{
@ -1229,7 +1257,11 @@ lsaauth (cygsid &usersid, user_groups &new_groups)
&sub_status);
if (status != STATUS_SUCCESS)
{
debug_printf ("LsaLogonUser: %y (sub-status %y)", status, sub_status);
if (status == STATUS_ACCOUNT_RESTRICTION)
debug_printf ("Cygwin LSA Auth LsaLogonUser failed: %y (%s)",
status, account_restriction (sub_status));
else
debug_printf ("Cygwin LSA Auth LsaLogonUser failed: %y", status);
__seterrno_from_nt_status (status);
goto out;
}
@ -1338,3 +1370,234 @@ out:
pop_self_privilege ();
return token;
}
/* The following code is inspired by the generate_s4u_user_token
and lookup_principal_name functions from
https://github.com/PowerShell/openssh-portable
Thanks guys! For courtesy here's the original copyright disclaimer: */
/*
* Author: Manoj Ampalam <manoj.ampalam@microsoft.com>
* Utilities to generate user tokens
*
* Author: Bryan Berns <berns@uwalumni.com>
* Updated s4u, logon, and profile loading routines to use
* normalized login names.
*
* Copyright (c) 2015 Microsoft Corp.
* All rights reserved
*
* Microsoft openssh win32 port
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* In Mingw-w64, MsV1_0S4ULogon and MSV1_0_S4U_LOGON are only defined
in ddk/ntifs.h. We can't inlcude this. */
#define MsV1_0S4ULogon ((MSV1_0_LOGON_SUBMIT_TYPE) 12)
typedef struct _MSV1_0_S4U_LOGON
{
MSV1_0_LOGON_SUBMIT_TYPE MessageType;
ULONG Flags;
UNICODE_STRING UserPrincipalName;
UNICODE_STRING DomainName;
} MSV1_0_S4U_LOGON, *PMSV1_0_S4U_LOGON;
HANDLE
s4uauth (struct passwd *pw)
{
LSA_STRING name;
HANDLE lsa_hdl = NULL;
LSA_OPERATIONAL_MODE sec_mode;
NTSTATUS status, sub_status;
WCHAR domain[MAX_DOMAIN_NAME_LEN + 1];
WCHAR user[UNLEN + 1];
bool try_kerb_auth;
ULONG package_id, size;
struct {
LSA_STRING str;
CHAR buf[16];
} origin;
tmp_pathbuf tp;
PVOID authinf = NULL;
ULONG authinf_size;
TOKEN_SOURCE ts;
PKERB_INTERACTIVE_PROFILE profile = NULL;
LUID luid;
QUOTA_LIMITS quota;
HANDLE token = NULL;
push_self_privilege (SE_TCB_PRIVILEGE, true);
/* Register as logon process. */
RtlInitAnsiString (&name, "Cygwin");
status = LsaRegisterLogonProcess (&name, &lsa_hdl, &sec_mode);
if (status != STATUS_SUCCESS)
{
debug_printf ("LsaRegisterLogonProcess: %y", status);
__seterrno_from_nt_status (status);
goto out;
}
/* Fetch user and domain name and check if this is a domain user.
If so, try Kerberos first. */
extract_nt_dom_user (pw, domain, user);
try_kerb_auth = cygheap->dom.member_machine ()
&& wcscasecmp (domain, cygheap->dom.account_flat_name ());
RtlInitAnsiString (&name, try_kerb_auth ? MICROSOFT_KERBEROS_NAME_A
: MSV1_0_PACKAGE_NAME);
status = LsaLookupAuthenticationPackage (lsa_hdl, &name, &package_id);
if (status != STATUS_SUCCESS)
{
debug_printf ("LsaLookupAuthenticationPackage: %y", status);
__seterrno_from_nt_status (status);
goto out;
}
/* Create origin. */
stpcpy (origin.buf, "Cygwin");
RtlInitAnsiString (&origin.str, origin.buf);
if (try_kerb_auth)
{
PWCHAR sam_name = tp.w_get ();
PWCHAR upn_name = tp.w_get ();
size = NT_MAX_PATH;
KERB_S4U_LOGON *s4u_logon;
USHORT name_len;
wcpcpy (wcpcpy (wcpcpy (sam_name, domain), L"\\"), user);
if (TranslateNameW (sam_name, NameSamCompatible, NameUserPrincipal,
upn_name, &size) == 0)
{
PWCHAR translated_name = tp.w_get ();
PWCHAR p;
debug_printf ("TranslateNameW(%W, NameUserPrincipal) %E", sam_name);
size = NT_MAX_PATH;
if (TranslateNameW (sam_name, NameSamCompatible, NameCanonical,
translated_name, &size) == 0)
{
debug_printf ("TranslateNameW(%W, NameCanonical) %E", sam_name);
debug_printf ("Fallback to MsV1_0 auth");
goto msv1_0_auth; /* Fall through to MSV1_0 authentication */
}
p = wcschr (translated_name, L'/');
if (p)
*p = '\0';
wcpcpy (wcpcpy (wcpcpy (upn_name, user), L"@"), translated_name);
}
name_len = wcslen (upn_name) * sizeof (WCHAR);
authinf_size = sizeof (KERB_S4U_LOGON) + name_len;
authinf = tp.c_get ();
RtlSecureZeroMemory (authinf, authinf_size);
s4u_logon = (KERB_S4U_LOGON *) authinf;
s4u_logon->MessageType = KerbS4ULogon;
s4u_logon->Flags = 0;
/* Append user to login info */
RtlInitEmptyUnicodeString (&s4u_logon->ClientUpn,
(PWCHAR) (s4u_logon + 1),
name_len);
RtlAppendUnicodeToString (&s4u_logon->ClientUpn, upn_name);
debug_printf ("ClientUpn: <%S>", &s4u_logon->ClientUpn);
/* Create token source. */
memcpy (ts.SourceName, "Cygwin.1", 8);
ts.SourceIdentifier.HighPart = 0;
ts.SourceIdentifier.LowPart = 0x0105;
status = LsaLogonUser (lsa_hdl, (PLSA_STRING) &origin, Network,
package_id, authinf, authinf_size, NULL,
&ts, (PVOID *) &profile, &size,
&luid, &token, &quota, &sub_status);
switch (status)
{
case STATUS_SUCCESS:
goto out;
/* These failures are fatal */
case STATUS_QUOTA_EXCEEDED:
case STATUS_LOGON_FAILURE:
debug_printf ("Kerberos S4U LsaLogonUser failed: %y", status);
goto out;
case STATUS_ACCOUNT_RESTRICTION:
debug_printf ("Kerberos S4U LsaLogonUser failed: %y (%s)",
status, account_restriction (sub_status));
goto out;
default:
break;
}
debug_printf ("Kerberos S4U LsaLogonUser failed: %y, try MsV1_0", status);
/* Fall through to MSV1_0 authentication */
}
msv1_0_auth:
MSV1_0_S4U_LOGON *s4u_logon;
USHORT user_len, domain_len;
user_len = wcslen (user) * sizeof (WCHAR);
domain_len = wcslen (domain) * sizeof (WCHAR); /* Local machine */
authinf_size = sizeof (MSV1_0_S4U_LOGON) + user_len + domain_len;
if (!authinf)
authinf = tp.c_get ();
RtlSecureZeroMemory (authinf, authinf_size);
s4u_logon = (MSV1_0_S4U_LOGON *) authinf;
s4u_logon->MessageType = MsV1_0S4ULogon;
s4u_logon->Flags = 0;
/* Append user and domain to login info */
RtlInitEmptyUnicodeString (&s4u_logon->UserPrincipalName,
(PWCHAR) (s4u_logon + 1),
user_len);
RtlInitEmptyUnicodeString (&s4u_logon->DomainName,
(PWCHAR) ((PBYTE) (s4u_logon + 1) + user_len),
domain_len);
RtlAppendUnicodeToString (&s4u_logon->UserPrincipalName, user);
RtlAppendUnicodeToString (&s4u_logon->DomainName, domain);
debug_printf ("DomainName: <%S> UserPrincipalName: <%S>",
&s4u_logon->DomainName, &s4u_logon->UserPrincipalName);
/* Create token source. */
memcpy (ts.SourceName, "Cygwin.1", 8);
ts.SourceIdentifier.HighPart = 0;
ts.SourceIdentifier.LowPart = 0x0106;
if ((status = LsaLogonUser (lsa_hdl, (PLSA_STRING) &origin, Network,
package_id, authinf, authinf_size, NULL,
&ts, (PVOID *) &profile, &size,
&luid, &token, &quota, &sub_status))
!= STATUS_SUCCESS)
{
if (status == STATUS_ACCOUNT_RESTRICTION)
debug_printf ("MSV1_0 S4U LsaLogonUser failed: %y (%s)",
status, account_restriction (sub_status));
else
debug_printf ("MSV1_0 S4U LsaLogonUser failed: %y", status);
}
out:
if (lsa_hdl)
LsaDeregisterLogonProcess (lsa_hdl);
if (profile)
LsaFreeReturnBuffer (profile);
pop_self_privilege ();
return token;
}

View File

@ -479,6 +479,8 @@ HANDLE create_token (cygsid &usersid, user_groups &groups);
HANDLE lsaauth (cygsid &, user_groups &);
/* LSA private key storage authentication, same as when using service logons. */
HANDLE lsaprivkeyauth (struct passwd *pw);
/* Kerberos or MsV1 S4U logon. */
HANDLE s4uauth (struct passwd *pw);
/* Verify an existing token */
bool verify_token (HANDLE token, cygsid &usersid, user_groups &groups, bool *pintern = NULL);
/* Get groups of a user */

View File

@ -3494,7 +3494,7 @@ seteuid32 (uid_t uid)
order, the setgroups group list is still active when calling seteuid
and verify_token treats the original token of the privileged user as
insufficient. This in turn results in creating a new user token for
the privileged user instead of using the orignal token. This can have
the privileged user instead of using the original token. This can have
unfortunate side effects. The created token has different group
memberships, different user rights, and misses possible network
credentials.
@ -3542,17 +3542,31 @@ seteuid32 (uid_t uid)
}
if (!new_token)
{
#if 1
debug_printf ("lsaprivkeyauth failed, try s4uauth.");
if (!(new_token = s4uauth (pw_new)))
{
debug_printf ("s4uauth failed, bail out");
cygheap->user.reimpersonate ();
return -1;
}
#else
debug_printf ("lsaprivkeyauth failed, try lsaauth.");
if (!(new_token = lsaauth (usersid, groups)))
{
debug_printf ("lsaauth failed, try create_token.");
if (!(new_token = create_token (usersid, groups)))
debug_printf ("lsaauth failed, try s4uauth.");
if (!(new_token = s4uauth (pw_new)))
{
debug_printf ("create_token failed, bail out");
cygheap->user.reimpersonate ();
return -1;
debug_printf ("s4uauth failed, try create_token.");
if (!(new_token = create_token (usersid, groups)))
{
debug_printf ("create_token failed, bail out");
cygheap->user.reimpersonate ();
return -1;
}
}
}
#endif
}
/* Keep at most one internal token */

View File

@ -4,7 +4,7 @@
<sect1 id="ov-new"><title>What's new and what changed in Cygwin</title>
<sect2 id="ov-new2.12"><title>What's new and what changed in 2.12</title>
<sect2 id="ov-new3.0"><title>What's new and what changed in 3.0</title>
<itemizedlist mark="bullet">
@ -86,15 +86,20 @@ to free the parent directory.
Wctype functions updated to Unicode 11.0.
</para></listitem>
</para></listitem>
<listitem><para>
Remove matherr, and SVID and X/Open math library configurations.
Default math library configuration is now IEEE.
<listitem><para>
</para></listitem>
<listitem><para>
Improve uname(2) for newly built applications.
</para></listitem>
<listitem><para>
Kerberos/MSV1_0 S4U authentication replaces two old methods:
Creating a token from scratch and Cygwin LSA authentication package.
</para></listitem>
</itemizedlist>
</sect2>

View File

@ -2326,35 +2326,56 @@ example:</para>
</sect3>
<sect3 id="ntsec-nopasswd1"><title id="ntsec-nopasswd1.title">Switching the user context without password, Method 1: Create a token from scratch</title>
<sect3 id="ntsec-nopasswd1"><title id="ntsec-nopasswd1.title">Switching the user context without password, Method 1: Kerberos/MsV1_0 S4U authentication</title>
<para>An unfortunate aspect of the implementation of
<command>set(e)uid</command> is the fact that the calling process
requires the password of the user to which to switch. Applications such as
requires the password of the user to switch to. Applications such as
<command>sshd</command> wishing to switch the user context after a
successful public key authentication, or the <command>cron</command>
application which, again, wants to switch the user without any authentication
are stuck here. But there are other ways to get new user tokens.</para>
<para>One way is just to create a user token from scratch. This is
accomplished by using an (officially undocumented) function on the NT
function level. The NT function level is used to implement the Win32
level, and, as such is closer to the kernel than the Win32 level. The
function of interest, <command>NtCreateToken</command>, allows you to
specify user, groups, permissions and almost everything you need to
create a user token, without the need to specify the user password. The
only restriction for using this function is that the calling process
needs the "Create a token object" user right, which only the SYSTEM user
account has by default, and which is considered the most dangerous right
a user can have on Windows systems.</para>
<para>Starting with Cygwin 3.0, Cygwin tries to create a token by using
<literal>Windows S4U authentication</literal> by default. For a quick
description, see
<ulink url="https://blogs.msdn.microsoft.com/winsdk/2015/08/28/logon-as-a-user-without-a-password/">this blog posting</ulink>. Cygwin versions prior
to 3.0 tried to creat a user token from scratch using an officially
undocumented function <command>NtCreateToken</command> which
is now disabled.</para>
<para>That sounds good. We just start the servers which have to switch
the user context (<command>sshd</command>, <command>inetd</command>,
<command>cron</command>, ...) as Windows services under the SYSTEM
(or LocalSystem in the GUI) account and everything just works.
Unfortunately that's too simple. Using <command>NtCreateToken</command>
has a few drawbacks.</para>
<para>So we just start the servers which have to switch the user context
(<command>sshd</command>, <command>inetd</command>, <command>cron</command>,
...) as Windows services under the SYSTEM (or LocalSystem in the GUI)
account and everything just works. Unfortunately that's too simple.
Using <literal>S4U</literal> has a drawback.</para>
<para>Annoyingly, you don't have the usual comfortable access
to network shares. The reason is that the token has been created
without knowing the password. The password are your credentials
necessary for network access. Thus, if you logon with a password, the
password is stored hidden as "token credentials" within the access token
and used as default logon to access network resources. Since these
credentials are missing from the token created with <literal>S4U</literal>
or <command>NtCreateToken</command>, you only can access network shares
from the new user's process tree by using explicit authentication, on
the command line for instance:</para>
<screen>
bash$ net use '\\server\share' /user:DOMAIN\my_user my_users_password
</screen>
<para>Note that, on some systems, you can't even define a drive letter
to access the share, and under some circumstances the drive letter you
choose collides with a drive letter already used in another session.
Therefore it's better to get used to accessing these shares using the UNC
path as in</para>
<screen>
bash$ grep foo //server/share/foofile
</screen>
<!--
<para>First of all, the permission "Create a token object" gets explicitly
removed from the SYSTEM user's access token, when starting services under that
account. That requires us to create a new account with this specific
@ -2388,31 +2409,6 @@ is more complicated. Some native, non-Cygwin Windows applications will
misbehave badly in this situation. A well-known example are certain versions
of Visual-C++.</para>
<para>Last but not least, you don't have the usual comfortable access
to network shares. The reason is that the token has been created
without knowing the password. The password are your credentials
necessary for network access. Thus, if you logon with a password, the
password is stored hidden as "token credentials" within the access token
and used as default logon to access network resources. Since these
credentials are missing from the token created with
<command>NtCreateToken</command>, you only can access network shares
from the new user's process tree by using explicit authentication, on
the command line for instance:</para>
<screen>
bash$ net use '\\server\share' /user:DOMAIN\my_user my_users_password
</screen>
<para>Note that, on some systems, you can't even define a drive letter
to access the share, and under some circumstances the drive letter you
choose collides with a drive letter already used in another session.
Therefore it's better to get used to accessing these shares using the UNC
path as in</para>
<screen>
bash$ grep foo //server/share/foofile
</screen>
</sect3>
<sect3 id="ntsec-nopasswd2"><title id="ntsec-nopasswd2.title">Switching the user context without password, Method 2: LSA authentication package</title>
@ -2439,9 +2435,6 @@ rebooting the system, the LSA authentication package is used by Cygwin
when <command>set(e)uid</command> is called by an application. The
created access token using this method has its own logon session.</para>
<para>This method has two advantages over the <command>NtCreateToken</command>
method.</para>
<para>The very special and very dangerous "Create a token object" user
right is not required by a user using this method. Other privileged
user rights are still necessary, especially the "Act as part of the
@ -2459,16 +2452,15 @@ inconvenience compared to that...</para>
<para>Nevertheless, this is already a lot better than what we get by
using <command>NtCreateToken</command>, isn't it?</para>
-->
</sect3>
<sect3 id="ntsec-nopasswd3"><title id="ntsec-nopasswd3.title">Switching the user context without password, Method 3: With password</title>
<sect3 id="ntsec-nopasswd3"><title id="ntsec-nopasswd3.title">Switching the user context without password, Method 2: With password</title>
<para>Ok, so we have solved almost any problem, except for the network
access problem. Not being able to access network shares without
having to specify a cleartext password on the command line or in a
script is a harsh problem for automated logons for testing purposes
and similar stuff.</para>
<para>Not being able to access network shares without having to specify
a cleartext password on the command line or in a script is a harsh problem
for automated logons for testing purposes and similar stuff.</para>
<para>Fortunately there is a solution, but it has its own drawbacks.
But, first things first, how does it work? The title of this section
@ -2512,13 +2504,7 @@ as normal, non-privileged user as well.</para>
hidden, obfuscated registry area. Only SYSTEM has access to this area
for listing purposes, so, even as an administrator, you can't examine
this area with regedit. Right? No. Every administrator can start
regedit as SYSTEM user:</para>
<screen>
bash$ date
Tue Dec 2 16:28:03 CET 2008
bash$ at 16:29 /interactive regedit.exe
</screen>
regedit as SYSTEM user, the Internet is your friend here.</para>
<para>Additionally, if an administrator knows under which name
the private key is stored (which is well-known since the algorithms
@ -2539,12 +2525,12 @@ safely use this method.</para>
<sect3 id="ntsec-setuid-impl"><title id="ntsec-setuid-impl.title">Switching the user context, how does it all fit together?</title>
<para>Now we learned about four different ways to switch the user
<para>Now we learned about three different ways to switch the user
context using the <command>set(e)uid</command> system call, but
how does <command>set(e)uid</command> really work? Which method does it
use now?</para>
<para>The answer is, all four of them. So here's a brief overview
<para>The answer is, all three of them. So here's a brief overview
what <command>set(e)uid</command> does under the hood:</para>
<itemizedlist>
@ -2575,17 +2561,17 @@ private registry area, either under a Cygwin key, or under a SFU key.
If so, use this to call <command>LogonUser</command>. If this
succeeds, we use the resulting token for the user context switch.</para>
</listitem>
<!--
<listitem>
<para>Otherwise, check if the Cygwin-specifc LSA authentication package
has been installed and is functional. If so, use the appropriate LSA
calls to communicate with the Cygwin LSA authentication package and
use the returned token.</para>
</listitem>
-->
<listitem>
<para>Last chance, try to use the <command>NtCreateToken</command> call
to create a token. If that works, use this token.</para>
<para>Otherwise, use the default <literal>S4U</literal> authentication
to create a token.</para>
</listitem>
<listitem>