320 lines
11 KiB
Diff
320 lines
11 KiB
Diff
From a152f36b0576737e647dbe5f1954039668123c1f Mon Sep 17 00:00:00 2001
|
|
From: Stefan Metzmacher <metze@samba.org>
|
|
Date: Mon, 4 Oct 2021 19:42:20 +0200
|
|
Subject: [PATCH 128/266] CVE-2020-25717: s3:auth: let
|
|
auth3_generate_session_info_pac() delegate everything to
|
|
make_server_info_wbcAuthUserInfo()
|
|
|
|
This consolidates the code paths used for NTLMSSP and Kerberos!
|
|
|
|
I checked what we were already doing for NTLMSSP, which is this:
|
|
|
|
a) source3/auth/auth_winbind.c calls wbcAuthenticateUserEx()
|
|
b) as a domain member we require a valid response from winbindd,
|
|
otherwise we'll return NT_STATUS_NO_LOGON_SERVERS
|
|
c) we call make_server_info_wbcAuthUserInfo(), which internally
|
|
calls make_server_info_info3()
|
|
d) auth_check_ntlm_password() calls
|
|
smb_pam_accountcheck(unix_username, rhost), where rhost
|
|
is only an ipv4 or ipv6 address (without reverse dns lookup)
|
|
e) from auth3_check_password_send/auth3_check_password_recv()
|
|
server_returned_info will be passed to auth3_generate_session_info(),
|
|
triggered by gensec_session_info(), which means we'll call into
|
|
create_local_token() in order to transform auth_serversupplied_info
|
|
into auth_session_info.
|
|
|
|
For Kerberos gensec_session_info() will call
|
|
auth3_generate_session_info_pac() via the gensec_generate_session_info_pac()
|
|
helper function. The current logic is this:
|
|
|
|
a) gensec_generate_session_info_pac() is the function that
|
|
evaluates the 'gensec:require_pac', which defaulted to 'no'
|
|
before.
|
|
b) auth3_generate_session_info_pac() called
|
|
wbcAuthenticateUserEx() in order to pass the PAC blob
|
|
to winbindd, but only to prime its cache, e.g. netsamlogon cache
|
|
and others. Most failures were just ignored.
|
|
c) If the PAC blob is available, it extracted the PAC_LOGON_INFO
|
|
from it.
|
|
d) Then we called the horrible get_user_from_kerberos_info() function:
|
|
- It uses a first part of the tickets principal name (before the @)
|
|
as username and combines that with the 'logon_info->base.logon_domain'
|
|
if the logon_info (PAC) is present.
|
|
- As a fallback without a PAC it's tries to ask winbindd for a mapping
|
|
from realm to netbios domain name.
|
|
- Finally is falls back to using the realm as netbios domain name
|
|
With this information is builds 'userdomain+winbind_separator+useraccount'
|
|
and calls map_username() followed by smb_getpwnam() with create=true,
|
|
Note this is similar to the make_server_info_info3() => check_account()
|
|
=> smb_getpwnam() logic under 3.
|
|
- It also calls smb_pam_accountcheck(), but may pass the reverse DNS lookup name
|
|
instead of the ip address as rhost.
|
|
- It does some MAP_TO_GUEST_ON_BAD_UID logic and auto creates the
|
|
guest account.
|
|
e) We called create_info3_from_pac_logon_info()
|
|
f) make_session_info_krb5() calls gets called and triggers this:
|
|
- If get_user_from_kerberos_info() mapped to guest, it calls
|
|
make_server_info_guest()
|
|
- If create_info3_from_pac_logon_info() created a info3 from logon_info,
|
|
it calls make_server_info_info3()
|
|
- Without a PAC it tries pdb_getsampwnam()/make_server_info_sam() with
|
|
a fallback to make_server_info_pw()
|
|
From there it calls create_local_token()
|
|
|
|
I tried to change auth3_generate_session_info_pac() to behave similar
|
|
to auth_winbind.c together with auth3_generate_session_info() as
|
|
a domain member, as we now rely on a PAC:
|
|
|
|
a) As domain member we require a PAC and always call wbcAuthenticateUserEx()
|
|
and require a valid response!
|
|
b) we call make_server_info_wbcAuthUserInfo(), which internally
|
|
calls make_server_info_info3(). Note make_server_info_info3()
|
|
handles MAP_TO_GUEST_ON_BAD_UID and make_server_info_guest()
|
|
internally.
|
|
c) Similar to auth_check_ntlm_password() we now call
|
|
smb_pam_accountcheck(unix_username, rhost), where rhost
|
|
is only an ipv4 or ipv6 address (without reverse dns lookup)
|
|
d) From there it calls create_local_token()
|
|
|
|
As standalone server (in an MIT realm) we continue
|
|
with the already existing code logic, which works without a PAC:
|
|
a) we keep smb_getpwnam() with create=true logic as it
|
|
also requires an explicit 'add user script' option.
|
|
b) In the following commits we assert that there's
|
|
actually no PAC in this mode, which means we can
|
|
remove unused and confusing code.
|
|
|
|
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14646
|
|
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556
|
|
|
|
Signed-off-by: Stefan Metzmacher <metze@samba.org>
|
|
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
|
|
|
|
---
|
|
source3/auth/auth_generic.c | 137 ++++++++++++++++++++++++++++--------
|
|
1 file changed, 109 insertions(+), 28 deletions(-)
|
|
|
|
Conflict:NA
|
|
Reference:https://git.samba.org/samba.git/?p=samba.git;a=patch;h=a152f36b0576737e647dbe5f1954039668123c1f
|
|
|
|
diff --git a/source3/auth/auth_generic.c b/source3/auth/auth_generic.c
|
|
index 86585ad690c..450c358beeb 100644
|
|
--- a/source3/auth/auth_generic.c
|
|
+++ b/source3/auth/auth_generic.c
|
|
@@ -46,6 +46,7 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
|
|
uint32_t session_info_flags,
|
|
struct auth_session_info **session_info)
|
|
{
|
|
+ enum server_role server_role = lp_server_role();
|
|
TALLOC_CTX *tmp_ctx;
|
|
struct PAC_LOGON_INFO *logon_info = NULL;
|
|
struct netr_SamInfo3 *info3_copy = NULL;
|
|
@@ -54,39 +55,59 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
|
|
char *ntuser;
|
|
char *ntdomain;
|
|
char *username;
|
|
- char *rhost;
|
|
+ const char *rhost;
|
|
struct passwd *pw;
|
|
NTSTATUS status;
|
|
- int rc;
|
|
|
|
tmp_ctx = talloc_new(mem_ctx);
|
|
if (!tmp_ctx) {
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
- if (pac_blob) {
|
|
-#ifdef HAVE_KRB5
|
|
+ if (tsocket_address_is_inet(remote_address, "ip")) {
|
|
+ rhost = tsocket_address_inet_addr_string(
|
|
+ remote_address, tmp_ctx);
|
|
+ if (rhost == NULL) {
|
|
+ status = NT_STATUS_NO_MEMORY;
|
|
+ goto done;
|
|
+ }
|
|
+ } else {
|
|
+ rhost = "127.0.0.1";
|
|
+ }
|
|
+
|
|
+ if (server_role != ROLE_STANDALONE) {
|
|
struct wbcAuthUserParams params = {};
|
|
struct wbcAuthUserInfo *info = NULL;
|
|
struct wbcAuthErrorInfo *err = NULL;
|
|
+ struct auth_serversupplied_info *server_info = NULL;
|
|
+ char *original_user_name = NULL;
|
|
+ char *p = NULL;
|
|
wbcErr wbc_err;
|
|
|
|
+ if (pac_blob == NULL) {
|
|
+ /*
|
|
+ * This should already be catched at the main
|
|
+ * gensec layer, but better check twice
|
|
+ */
|
|
+ status = NT_STATUS_INTERNAL_ERROR;
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
/*
|
|
* Let winbind decode the PAC.
|
|
* This will also store the user
|
|
* data in the netsamlogon cache.
|
|
*
|
|
- * We need to do this *before* we
|
|
- * call get_user_from_kerberos_info()
|
|
- * as that does a user lookup that
|
|
- * expects info in the netsamlogon cache.
|
|
- *
|
|
- * See BUG: https://bugzilla.samba.org/show_bug.cgi?id=11259
|
|
+ * This used to be a cache prime
|
|
+ * optimization, but now we delegate
|
|
+ * all logic to winbindd, as we require
|
|
+ * winbindd as domain member anyway.
|
|
*/
|
|
params.level = WBC_AUTH_USER_LEVEL_PAC;
|
|
params.password.pac.data = pac_blob->data;
|
|
params.password.pac.length = pac_blob->length;
|
|
|
|
+ /* we are contacting the privileged pipe */
|
|
become_root();
|
|
wbc_err = wbcAuthenticateUserEx(¶ms, &info, &err);
|
|
unbecome_root();
|
|
@@ -99,18 +120,90 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
|
|
*/
|
|
|
|
switch (wbc_err) {
|
|
- case WBC_ERR_WINBIND_NOT_AVAILABLE:
|
|
case WBC_ERR_SUCCESS:
|
|
break;
|
|
+ case WBC_ERR_WINBIND_NOT_AVAILABLE:
|
|
+ status = NT_STATUS_NO_LOGON_SERVERS;
|
|
+ DBG_ERR("winbindd not running - "
|
|
+ "but required as domain member: %s\n",
|
|
+ nt_errstr(status));
|
|
+ goto done;
|
|
case WBC_ERR_AUTH_ERROR:
|
|
status = NT_STATUS(err->nt_status);
|
|
wbcFreeMemory(err);
|
|
goto done;
|
|
+ case WBC_ERR_NO_MEMORY:
|
|
+ status = NT_STATUS_NO_MEMORY;
|
|
+ goto done;
|
|
default:
|
|
status = NT_STATUS_LOGON_FAILURE;
|
|
goto done;
|
|
}
|
|
|
|
+ status = make_server_info_wbcAuthUserInfo(tmp_ctx,
|
|
+ info->account_name,
|
|
+ info->domain_name,
|
|
+ info, &server_info);
|
|
+ if (!NT_STATUS_IS_OK(status)) {
|
|
+ DEBUG(10, ("make_server_info_wbcAuthUserInfo failed: %s\n",
|
|
+ nt_errstr(status)));
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* We skip doing this step if the caller asked us not to */
|
|
+ if (!(server_info->guest)) {
|
|
+ const char *unix_username = server_info->unix_name;
|
|
+
|
|
+ /* We might not be root if we are an RPC call */
|
|
+ become_root();
|
|
+ status = smb_pam_accountcheck(unix_username, rhost);
|
|
+ unbecome_root();
|
|
+
|
|
+ if (!NT_STATUS_IS_OK(status)) {
|
|
+ DEBUG(3, ("check_ntlm_password: PAM Account for user [%s] "
|
|
+ "FAILED with error %s\n",
|
|
+ unix_username, nt_errstr(status)));
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ DEBUG(5, ("check_ntlm_password: PAM Account for user [%s] "
|
|
+ "succeeded\n", unix_username));
|
|
+ }
|
|
+
|
|
+ DEBUG(3, ("Kerberos ticket principal name is [%s]\n", princ_name));
|
|
+
|
|
+ p = strchr_m(princ_name, '@');
|
|
+ if (!p) {
|
|
+ DEBUG(3, ("[%s] Doesn't look like a valid principal\n",
|
|
+ princ_name));
|
|
+ status = NT_STATUS_LOGON_FAILURE;
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ original_user_name = talloc_strndup(tmp_ctx, princ_name, p - princ_name);
|
|
+ if (original_user_name == NULL) {
|
|
+ status = NT_STATUS_NO_MEMORY;
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ status = create_local_token(mem_ctx,
|
|
+ server_info,
|
|
+ NULL,
|
|
+ original_user_name,
|
|
+ session_info);
|
|
+ if (!NT_STATUS_IS_OK(status)) {
|
|
+ DEBUG(10, ("create_local_token failed: %s\n",
|
|
+ nt_errstr(status)));
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ goto session_info_ready;
|
|
+ }
|
|
+
|
|
+ /* This is the standalone legacy code path */
|
|
+
|
|
+ if (pac_blob != NULL) {
|
|
+#ifdef HAVE_KRB5
|
|
status = kerberos_pac_logon_info(tmp_ctx, *pac_blob, NULL, NULL,
|
|
NULL, NULL, 0, &logon_info);
|
|
#else
|
|
@@ -121,22 +214,6 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
|
|
}
|
|
}
|
|
|
|
- rc = get_remote_hostname(remote_address,
|
|
- &rhost,
|
|
- tmp_ctx);
|
|
- if (rc < 0) {
|
|
- status = NT_STATUS_NO_MEMORY;
|
|
- goto done;
|
|
- }
|
|
- if (strequal(rhost, "UNKNOWN")) {
|
|
- rhost = tsocket_address_inet_addr_string(remote_address,
|
|
- tmp_ctx);
|
|
- if (rhost == NULL) {
|
|
- status = NT_STATUS_NO_MEMORY;
|
|
- goto done;
|
|
- }
|
|
- }
|
|
-
|
|
status = get_user_from_kerberos_info(tmp_ctx, rhost,
|
|
princ_name, logon_info,
|
|
&is_mapped, &is_guest,
|
|
@@ -170,6 +247,8 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
|
|
goto done;
|
|
}
|
|
|
|
+session_info_ready:
|
|
+
|
|
/* setup the string used by %U */
|
|
set_current_user_info((*session_info)->unix_info->sanitized_username,
|
|
(*session_info)->unix_info->unix_name,
|
|
@@ -179,7 +258,9 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
|
|
lp_load_with_shares(get_dyn_CONFIGFILE());
|
|
|
|
DEBUG(5, (__location__ "OK: user: %s domain: %s client: %s\n",
|
|
- ntuser, ntdomain, rhost));
|
|
+ (*session_info)->info->account_name,
|
|
+ (*session_info)->info->domain_name,
|
|
+ rhost));
|
|
|
|
status = NT_STATUS_OK;
|
|
|
|
--
|
|
2.23.0
|
|
|