comparison svr-authpubkey.c @ 1920:1489449eceb1

Check authorized_keys permissions as the user This is necessary on NFS with squash root. Based on work from Chris Dragan This commit also tidies some trailing whitespace. Fixes github pull #107
author Matt Johnston <matt@ucc.asn.au>
date Wed, 30 Mar 2022 12:56:09 +0800
parents f8ed10efaaac
children
comparison
equal deleted inserted replaced
1919:ff8a81386a2b 1920:1489449eceb1
1 /* 1 /*
2 * Dropbear - a SSH2 server 2 * Dropbear - a SSH2 server
3 * 3 *
4 * Copyright (c) 2002,2003 Matt Johnston 4 * Copyright (c) 2002,2003 Matt Johnston
5 * All rights reserved. 5 * All rights reserved.
6 * 6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal 8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights 9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is 11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions: 12 * furnished to do so, subject to the following conditions:
13 * 13 *
14 * The above copyright notice and this permission notice shall be included in 14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software. 15 * all copies or substantial portions of the Software.
16 * 16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE. */ 23 * SOFTWARE. */
24 /* 24 /*
25 * This file incorporates work covered by the following copyright and 25 * This file incorporates work covered by the following copyright and
26 * permission notice: 26 * permission notice:
27 * 27 *
28 * Copyright (c) 2000 Markus Friedl. All rights reserved. 28 * Copyright (c) 2000 Markus Friedl. All rights reserved.
29 * 29 *
30 * Redistribution and use in source and binary forms, with or without 30 * Redistribution and use in source and binary forms, with or without
31 * modification, are permitted provided that the following conditions 31 * modification, are permitted provided that the following conditions
32 * are met: 32 * are met:
33 * 1. Redistributions of source code must retain the above copyright 33 * 1. Redistributions of source code must retain the above copyright
34 * notice, this list of conditions and the following disclaimer. 34 * notice, this list of conditions and the following disclaimer.
35 * 2. Redistributions in binary form must reproduce the above copyright 35 * 2. Redistributions in binary form must reproduce the above copyright
36 * notice, this list of conditions and the following disclaimer in the 36 * notice, this list of conditions and the following disclaimer in the
37 * documentation and/or other materials provided with the distribution. 37 * documentation and/or other materials provided with the distribution.
38 * 38 *
39 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 39 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
40 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 40 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
41 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 41 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
42 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 42 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
43 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 43 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
46 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 46 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
47 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 47 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
48 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 48 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
49 * 49 *
50 * This copyright and permission notice applies to the code parsing public keys 50 * This copyright and permission notice applies to the code parsing public keys
51 * options string which can also be found in OpenSSH auth2-pubkey.c file 51 * options string which can also be found in OpenSSH auth2-pubkey.c file
52 * (user_key_allowed2). It has been adapted to work with buffers. 52 * (user_key_allowed2). It has been adapted to work with buffers.
53 * 53 *
54 */ 54 */
55 55
56 /* Process a pubkey auth request */ 56 /* Process a pubkey auth request */
106 keybloblen = buf_getint(ses.payload); 106 keybloblen = buf_getint(ses.payload);
107 keyblob = buf_getptr(ses.payload, keybloblen); 107 keyblob = buf_getptr(ses.payload, keybloblen);
108 108
109 if (!valid_user) { 109 if (!valid_user) {
110 /* Return failure once we have read the contents of the packet 110 /* Return failure once we have read the contents of the packet
111 required to validate a public key. 111 required to validate a public key.
112 Avoids blind user enumeration though it isn't possible to prevent 112 Avoids blind user enumeration though it isn't possible to prevent
113 testing for user existence if the public key is known */ 113 testing for user existence if the public key is known */
114 send_msg_userauth_failure(0, 0); 114 send_msg_userauth_failure(0, 0);
115 goto out; 115 goto out;
116 } 116 }
128 if (svr_ses.plugin_instance != NULL) { 128 if (svr_ses.plugin_instance != NULL) {
129 char *options_buf; 129 char *options_buf;
130 if (svr_ses.plugin_instance->checkpubkey( 130 if (svr_ses.plugin_instance->checkpubkey(
131 svr_ses.plugin_instance, 131 svr_ses.plugin_instance,
132 &ses.plugin_session, 132 &ses.plugin_session,
133 keyalgo, 133 keyalgo,
134 keyalgolen, 134 keyalgolen,
135 keyblob, 135 keyblob,
136 keybloblen, 136 keybloblen,
137 ses.authstate.username) == DROPBEAR_SUCCESS) { 137 ses.authstate.username) == DROPBEAR_SUCCESS) {
138 /* Success */ 138 /* Success */
139 auth_failure = 0; 139 auth_failure = 0;
140 140
141 /* Options provided? */ 141 /* Options provided? */
142 options_buf = ses.plugin_session->get_options(ses.plugin_session); 142 options_buf = ses.plugin_session->get_options(ses.plugin_session);
143 if (options_buf) { 143 if (options_buf) {
144 struct buf temp_buf = { 144 struct buf temp_buf = {
145 .data = (unsigned char *)options_buf, 145 .data = (unsigned char *)options_buf,
146 .len = strlen(options_buf), 146 .len = strlen(options_buf),
147 .pos = 0, 147 .pos = 0,
148 .size = 0 148 .size = 0
149 }; 149 };
172 send_msg_userauth_pk_ok(sigalgo, sigalgolen, keyblob, keybloblen); 172 send_msg_userauth_pk_ok(sigalgo, sigalgolen, keyblob, keybloblen);
173 goto out; 173 goto out;
174 } 174 }
175 175
176 /* now we can actually verify the signature */ 176 /* now we can actually verify the signature */
177 177
178 /* get the key */ 178 /* get the key */
179 key = new_sign_key(); 179 key = new_sign_key();
180 if (buf_get_pub_key(ses.payload, key, &keytype) == DROPBEAR_FAILURE) { 180 if (buf_get_pub_key(ses.payload, key, &keytype) == DROPBEAR_FAILURE) {
181 send_msg_userauth_failure(0, 1); 181 send_msg_userauth_failure(0, 1);
182 goto out; 182 goto out;
189 signbuf = buf_new(ses.payload->pos + 4 + ses.session_id->len); 189 signbuf = buf_new(ses.payload->pos + 4 + ses.session_id->len);
190 buf_putbufstring(signbuf, ses.session_id); 190 buf_putbufstring(signbuf, ses.session_id);
191 191
192 /* The entire contents of the payload prior. */ 192 /* The entire contents of the payload prior. */
193 buf_setpos(ses.payload, ses.payload_beginning); 193 buf_setpos(ses.payload, ses.payload_beginning);
194 buf_putbytes(signbuf, 194 buf_putbytes(signbuf,
195 buf_getptr(ses.payload, sign_payload_length), 195 buf_getptr(ses.payload, sign_payload_length),
196 sign_payload_length); 196 sign_payload_length);
197 buf_incrpos(ses.payload, sign_payload_length); 197 buf_incrpos(ses.payload, sign_payload_length);
198 198
199 buf_setpos(signbuf, 0); 199 buf_setpos(signbuf, 0);
211 if ((ses.plugin_session != NULL) && (svr_ses.plugin_instance->auth_success != NULL)) { 211 if ((ses.plugin_session != NULL) && (svr_ses.plugin_instance->auth_success != NULL)) {
212 /* Was authenticated through the external plugin. tell plugin that signature verification was ok */ 212 /* Was authenticated through the external plugin. tell plugin that signature verification was ok */
213 svr_ses.plugin_instance->auth_success(ses.plugin_session); 213 svr_ses.plugin_instance->auth_success(ses.plugin_session);
214 } 214 }
215 #endif 215 #endif
216
217 } else { 216 } else {
218 dropbear_log(LOG_WARNING, 217 dropbear_log(LOG_WARNING,
219 "Pubkey auth bad signature for '%s' with key %s from %s", 218 "Pubkey auth bad signature for '%s' with key %s from %s",
220 ses.authstate.pw_name, fp, svr_ses.addrstring); 219 ses.authstate.pw_name, fp, svr_ses.addrstring);
221 send_msg_userauth_failure(0, 1); 220 send_msg_userauth_failure(0, 1);
288 if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) { 287 if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) {
289 int is_comment = 0; 288 int is_comment = 0;
290 unsigned char *options_start = NULL; 289 unsigned char *options_start = NULL;
291 int options_len = 0; 290 int options_len = 0;
292 int escape, quoted; 291 int escape, quoted;
293 292
294 /* skip over any comments or leading whitespace */ 293 /* skip over any comments or leading whitespace */
295 while (line->pos < line->len) { 294 while (line->pos < line->len) {
296 const char c = buf_getbyte(line); 295 const char c = buf_getbyte(line);
297 if (c == ' ' || c == '\t') { 296 if (c == ' ' || c == '\t') {
298 continue; 297 continue;
311 /* remember start of options */ 310 /* remember start of options */
312 options_start = buf_getptr(line, 1); 311 options_start = buf_getptr(line, 1);
313 quoted = 0; 312 quoted = 0;
314 escape = 0; 313 escape = 0;
315 options_len = 0; 314 options_len = 0;
316 315
317 /* figure out where the options are */ 316 /* figure out where the options are */
318 while (line->pos < line->len) { 317 while (line->pos < line->len) {
319 const char c = buf_getbyte(line); 318 const char c = buf_getbyte(line);
320 if (!quoted && (c == ' ' || c == '\t')) { 319 if (!quoted && (c == ' ' || c == '\t')) {
321 break; 320 break;
336 if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) { 335 if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) {
337 goto out; 336 goto out;
338 } 337 }
339 } 338 }
340 buf_incrpos(line, algolen); 339 buf_incrpos(line, algolen);
341 340
342 /* check for space (' ') character */ 341 /* check for space (' ') character */
343 if (buf_getbyte(line) != ' ') { 342 if (buf_getbyte(line) != ' ') {
344 TRACE(("checkpubkey_line: space character expected, isn't there")) 343 TRACE(("checkpubkey_line: space character expected, isn't there"))
345 goto out; 344 goto out;
346 } 345 }
425 uid_t origuid; 424 uid_t origuid;
426 gid_t origgid; 425 gid_t origgid;
427 426
428 TRACE(("enter checkpubkey")) 427 TRACE(("enter checkpubkey"))
429 428
430 /* check file permissions, also whether file exists */
431 if (checkpubkeyperms() == DROPBEAR_FAILURE) {
432 TRACE(("bad authorized_keys permissions, or file doesn't exist"))
433 goto out;
434 }
435
436 /* we don't need to check pw and pw_dir for validity, since
437 * its been done in checkpubkeyperms. */
438 len = strlen(ses.authstate.pw_dir);
439 /* allocate max required pathname storage,
440 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
441 filename = m_malloc(len + 22);
442 snprintf(filename, len + 22, "%s/.ssh/authorized_keys",
443 ses.authstate.pw_dir);
444
445 #if DROPBEAR_SVR_MULTIUSER 429 #if DROPBEAR_SVR_MULTIUSER
446 /* open the file as the authenticating user. */ 430 /* access the file as the authenticating user. */
447 origuid = getuid(); 431 origuid = getuid();
448 origgid = getgid(); 432 origgid = getgid();
449 if ((setegid(ses.authstate.pw_gid)) < 0 || 433 if ((setegid(ses.authstate.pw_gid)) < 0 ||
450 (seteuid(ses.authstate.pw_uid)) < 0) { 434 (seteuid(ses.authstate.pw_uid)) < 0) {
451 dropbear_exit("Failed to set euid"); 435 dropbear_exit("Failed to set euid");
452 } 436 }
453 #endif 437 #endif
454 438 /* check file permissions, also whether file exists */
455 authfile = fopen(filename, "r"); 439 if (checkpubkeyperms() == DROPBEAR_FAILURE) {
456 440 TRACE(("bad authorized_keys permissions, or file doesn't exist"))
441 } else {
442 /* we don't need to check pw and pw_dir for validity, since
443 * its been done in checkpubkeyperms. */
444 len = strlen(ses.authstate.pw_dir);
445 /* allocate max required pathname storage,
446 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
447 filename = m_malloc(len + 22);
448 snprintf(filename, len + 22, "%s/.ssh/authorized_keys",
449 ses.authstate.pw_dir);
450
451 authfile = fopen(filename, "r");
452 if (!authfile) {
453 TRACE(("checkpubkey: failed opening %s: %s", filename, strerror(errno)))
454 }
455 }
457 #if DROPBEAR_SVR_MULTIUSER 456 #if DROPBEAR_SVR_MULTIUSER
458 if ((seteuid(origuid)) < 0 || 457 if ((seteuid(origuid)) < 0 ||
459 (setegid(origgid)) < 0) { 458 (setegid(origgid)) < 0) {
460 dropbear_exit("Failed to revert euid"); 459 dropbear_exit("Failed to revert euid");
461 } 460 }
483 if (ret == DROPBEAR_SUCCESS) { 482 if (ret == DROPBEAR_SUCCESS) {
484 break; 483 break;
485 } 484 }
486 485
487 /* We continue to the next line otherwise */ 486 /* We continue to the next line otherwise */
488
489 } while (1); 487 } while (1);
490 488
491 out: 489 out:
492 if (authfile) { 490 if (authfile) {
493 fclose(authfile); 491 fclose(authfile);
506 * Checks that the user's homedir, ~/.ssh, and 504 * Checks that the user's homedir, ~/.ssh, and
507 * ~/.ssh/authorized_keys are all owned by either root or the user, and are 505 * ~/.ssh/authorized_keys are all owned by either root or the user, and are
508 * g-w, o-w */ 506 * g-w, o-w */
509 static int checkpubkeyperms() { 507 static int checkpubkeyperms() {
510 508
511 char* filename = NULL; 509 char* filename = NULL;
512 int ret = DROPBEAR_FAILURE; 510 int ret = DROPBEAR_FAILURE;
513 unsigned int len; 511 unsigned int len;
514 512
515 TRACE(("enter checkpubkeyperms")) 513 TRACE(("enter checkpubkeyperms"))
516 514
545 goto out; 543 goto out;
546 } 544 }
547 545
548 /* file looks ok, return success */ 546 /* file looks ok, return success */
549 ret = DROPBEAR_SUCCESS; 547 ret = DROPBEAR_SUCCESS;
550 548
551 out: 549 out:
552 m_free(filename); 550 m_free(filename);
553 551
554 TRACE(("leave checkpubkeyperms")) 552 TRACE(("leave checkpubkeyperms"))
555 return ret; 553 return ret;