1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
|
--- lib/libpam/modules/pam_krb5/pam_krb5.8.orig
+++ lib/libpam/modules/pam_krb5/pam_krb5.8
@@ -108,6 +108,21 @@
.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
--- lib/libpam/modules/pam_krb5/pam_krb5.c.orig
+++ lib/libpam/modules/pam_krb5/pam_krb5.c
@@ -76,7 +76,12 @@
#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 @@
#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 @@
{
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_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_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 @@
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 @@
*/
/* 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 @@
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 @@
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() */
|