aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCy Schubert <cy@FreeBSD.org>2023-05-31 19:20:27 +0000
committerGordon Tetlow <gordon@FreeBSD.org>2023-06-21 05:26:03 +0000
commit5018f551ece209a32b06e5225d34fe248d14e479 (patch)
tree48a8fcc50d05447f370c45a4b23e6d584e46d2e5
parentf7d6966853375be0efcafd795d5b2a639cf47508 (diff)
downloadsrc-5018f551ece209a32b06e5225d34fe248d14e479.tar.gz
src-5018f551ece209a32b06e5225d34fe248d14e479.zip
pam_krb5: Fix spoofing vulnerability
An adversary on the network can log in via ssh as any user by spoofing the KDC. When the machine has a keytab installed the keytab is used to verify the service ticket. However, without a keytab there is no way for pam_krb5 to verify the KDC's response and get a TGT with the password. If both the password _and_ the KDC are controlled by an adversary, the adversary can provide a password that the adversary's spoofed KDC will return a valid tgt for. Currently, without a keytab, pam_krb5 is vulnerable to this attack. Reported by: Taylor R Campbell <riastradh@netbsd.org> via emaste@ Reviewed by: so Approved by: so Security: FreeBSD-SA-23:04.pam_krb5 Security: CVE-2023-3326 (cherry picked from commit 813847e49e35439ba5d7bf16034b0691312068a4)
-rw-r--r--lib/libpam/modules/pam_krb5/pam_krb5.815
-rw-r--r--lib/libpam/modules/pam_krb5/pam_krb5.c104
2 files changed, 102 insertions, 17 deletions
diff --git a/lib/libpam/modules/pam_krb5/pam_krb5.8 b/lib/libpam/modules/pam_krb5/pam_krb5.8
index bd7ac5b9ca0c..bdd91c54fce6 100644
--- a/lib/libpam/modules/pam_krb5/pam_krb5.8
+++ b/lib/libpam/modules/pam_krb5/pam_krb5.8
@@ -108,6 +108,21 @@ and
.Ql %p ,
to designate the current process ID; can be used in
.Ar name .
+.It Cm allow_kdc_spoof
+Allow
+.Nm
+to succeed even if there is no host or service key available in a
+keytab to authenticate the Kerberos KDC's ticket.
+If there is no such key, for example on a host with no keytabs,
+.Nm
+will fail immediately without prompting the user.
+.Pp
+.Sy Warning :
+If the host has not been configured with a keytab from the KDC, setting
+this option makes it vulnerable to malicious KDCs, e.g. via DNS
+flooding, because
+.Nm
+has no way to distinguish the legitimate KDC from a spoofed KDC.
.It Cm no_user_check
Do not verify if a user exists on the local system. This option implies the
.Cm no_ccache
diff --git a/lib/libpam/modules/pam_krb5/pam_krb5.c b/lib/libpam/modules/pam_krb5/pam_krb5.c
index 810573bed47e..3972479a581f 100644
--- a/lib/libpam/modules/pam_krb5/pam_krb5.c
+++ b/lib/libpam/modules/pam_krb5/pam_krb5.c
@@ -76,7 +76,12 @@ __FBSDID("$FreeBSD$");
#define COMPAT_HEIMDAL
/* #define COMPAT_MIT */
-static int verify_krb_v5_tgt(krb5_context, krb5_ccache, char *, int);
+static int verify_krb_v5_tgt_begin(krb5_context, char *, int,
+ const char **, krb5_principal *, char[static BUFSIZ]);
+static int verify_krb_v5_tgt(krb5_context, krb5_ccache, char *, int,
+ const char *, krb5_principal, char[static BUFSIZ]);
+static void verify_krb_v5_tgt_cleanup(krb5_context, int,
+ const char *, krb5_principal, char[static BUFSIZ]);
static void cleanup_cache(pam_handle_t *, void *, int);
static const char *compat_princ_component(krb5_context, krb5_principal, int);
static void compat_free_data_contents(krb5_context, krb5_data *);
@@ -92,6 +97,7 @@ static void compat_free_data_contents(krb5_context, krb5_data *);
#define PAM_OPT_NO_USER_CHECK "no_user_check"
#define PAM_OPT_REUSE_CCACHE "reuse_ccache"
#define PAM_OPT_NO_USER_CHECK "no_user_check"
+#define PAM_OPT_ALLOW_KDC_SPOOF "allow_kdc_spoof"
#define PAM_LOG_KRB5_ERR(ctx, rv, fmt, ...) \
do { \
@@ -109,6 +115,10 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
{
krb5_error_code krbret;
krb5_context pam_context;
+ int debug;
+ const char *auth_service;
+ krb5_principal auth_princ;
+ char auth_phost[BUFSIZ];
krb5_creds creds;
krb5_principal princ;
krb5_ccache ccache;
@@ -139,14 +149,37 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
PAM_LOG("Got service: %s", (const char *)service);
+ if ((srvdup = strdup(service)) == NULL) {
+ retval = PAM_BUF_ERR;
+ goto cleanup6;
+ }
+
krbret = krb5_init_context(&pam_context);
if (krbret != 0) {
PAM_VERBOSE_ERROR("Kerberos 5 error");
- return (PAM_SERVICE_ERR);
+ retval = PAM_SERVICE_ERR;
+ goto cleanup5;
}
PAM_LOG("Context initialised");
+ debug = openpam_get_option(pamh, PAM_OPT_DEBUG) ? 1 : 0;
+ krbret = verify_krb_v5_tgt_begin(pam_context, srvdup, debug,
+ &auth_service, &auth_princ, auth_phost);
+ if (krbret != 0) { /* failed to find key */
+ /* Keytab or service key does not exist */
+ /*
+ * Give up now because we can't authenticate the KDC
+ * with a keytab, unless the administrator asked to
+ * have the traditional behaviour of being vulnerable
+ * to spoofed KDCs.
+ */
+ if (!openpam_get_option(pamh, PAM_OPT_ALLOW_KDC_SPOOF)) {
+ retval = PAM_SERVICE_ERR;
+ goto cleanup4;
+ }
+ }
+
krbret = krb5_cc_register(pam_context, &krb5_mcc_ops, FALSE);
if (krbret != 0 && krbret != KRB5_CC_TYPE_EXISTS) {
PAM_VERBOSE_ERROR("Kerberos 5 error");
@@ -292,13 +325,11 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
PAM_LOG("Credentials stashed");
/* Verify them */
- if ((srvdup = strdup(service)) == NULL) {
- retval = PAM_BUF_ERR;
- goto cleanup;
- }
krbret = verify_krb_v5_tgt(pam_context, ccache, srvdup,
- openpam_get_option(pamh, PAM_OPT_DEBUG) ? 1 : 0);
+ debug,
+ auth_service, auth_princ, auth_phost);
free(srvdup);
+ srvdup = NULL;
if (krbret == -1) {
PAM_VERBOSE_ERROR("Kerberos 5 error");
krb5_cc_destroy(pam_context, ccache);
@@ -349,8 +380,20 @@ cleanup3:
PAM_LOG("Done cleanup3");
+cleanup4:
+ verify_krb_v5_tgt_cleanup(pam_context, debug,
+ auth_service, auth_princ, auth_phost);
+ PAM_LOG("Done cleanup4");
+
+cleanup5:
+ if (srvdup != NULL)
+ free(srvdup);
+ PAM_LOG("Done cleanup5");
+
+cleanup6:
if (retval != PAM_SUCCESS)
PAM_VERBOSE_ERROR("Kerberos 5 refuses you");
+ PAM_LOG("Done cleanup6");
return (retval);
}
@@ -837,18 +880,18 @@ PAM_MODULE_ENTRY("pam_krb5");
*/
/* ARGSUSED */
static int
-verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache,
- char *pam_service, int debug)
+verify_krb_v5_tgt_begin(krb5_context context, char *pam_service, int debug,
+ const char **servicep, krb5_principal *princp __unused, char phost[static BUFSIZ])
{
krb5_error_code retval;
krb5_principal princ;
krb5_keyblock *keyblock;
- krb5_data packet;
- krb5_auth_context auth_context;
- char phost[BUFSIZ];
const char *services[3], **service;
- packet.data = 0;
+ *servicep = NULL;
+
+ if (debug)
+ openlog("pam_krb5", LOG_PID, LOG_AUTHPRIV);
/* If possible we want to try and verify the ticket we have
* received against a keytab. We will try multiple service
@@ -906,14 +949,30 @@ verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache,
krb5_free_error_message(context, msg);
}
retval = 0;
- goto cleanup;
}
if (keyblock)
krb5_free_keyblock(context, keyblock);
+ return (retval);
+}
+
+static int
+verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache,
+ char *pam_service __unused, int debug,
+ const char *service, krb5_principal princ, char phost[static BUFSIZ])
+{
+ krb5_error_code retval;
+ krb5_auth_context auth_context = NULL;
+ krb5_data packet;
+
+ if (service == NULL)
+ return (0); /* uncertain, can't authenticate KDC */
+
+ packet.data = 0;
+
/* Talk to the kdc and construct the ticket. */
auth_context = NULL;
- retval = krb5_mk_req(context, &auth_context, 0, *service, phost,
+ retval = krb5_mk_req(context, &auth_context, 0, service, phost,
NULL, ccache, &packet);
if (auth_context) {
krb5_auth_con_free(context, auth_context);
@@ -952,8 +1011,19 @@ verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache,
cleanup:
if (packet.data)
compat_free_data_contents(context, &packet);
- krb5_free_principal(context, princ);
- return retval;
+ return (retval);
+}
+
+static void
+verify_krb_v5_tgt_cleanup(krb5_context context, int debug,
+ const char *service, krb5_principal princ, char phost[static BUFSIZ] __unused)
+{
+
+ if (service)
+ krb5_free_principal(context, princ);
+ if (debug)
+ closelog();
+
}
/* Free the memory for cache_name. Called by pam_end() */