OSEC

Neohapsis is currently accepting applications for employment. For more information, please visit our website www.neohapsis.com or email hr@neohapsis.com
 
Subject: telnetd/login: V3 patch
From: Chris Evans (chrisferret.lmh.ox.ac.uk)
Date: Tue Jul 25 2000 - 18:18:25 CDT


Hi,

Here is patch V3. In fact, it's two patches. One to telnetd
(netkit-telnet, from RH6.2 SRPM) and one to login (util-linux, from RH6.2
SRPM)

The login patch is to cleanup utmp and wtmp entries. login put them there
so login can remove them later.

Also fixed is a bug in login which registers pts/0 as just "0" in the
logs, e.g.

LOGIN ON 0 BY chris FROM localhost

is now

LOGIN ON pts/0 BY chris FROM localhost

Note - the patch is pretty tied to a modern system. I suspect you'll need
glibc-2.1 for its libutil.so with logout(), logwtmp(). Also, tty handling
may not be optimal unless you have a distribution using a /dev/pts
filesystem. Personally, I'm using RH6.1

To get wider audience testing, I'll be making (S)RPMs for RH6.x in the
next few days. Locally, telnetd is running as non-root and under a
chroot(), and it seems to Just Work (tm).

Cheers
Chris

diff -rubB login-utils.old/Makefile login-utils/Makefile
--- login-utils.old/Makefile Sun Nov 21 22:37:29 1999
+++ login-utils/Makefile Mon Jul 24 06:51:38 2000
-95,10 +95,10
 
 ifeq "$(HAVE_PAM)" "yes"
 login: login.o $(LIB)/setproctitle.o
- $(CC) $(LDFLAGS) -o $ $^ $(CRYPT) $(PAM)
+ $(CC) $(LDFLAGS) -o $ $^ $(CRYPT) $(PAM) -lutil
 else
 login: login.o $(LIB)/setproctitle.o checktty.o
- $(CC) $(LDFLAGS) -o $ $^ $(CRYPT)
+ $(CC) $(LDFLAGS) -o $ $^ $(CRYPT) -lutil
 endif
 
 mesg: mesg.o $(ERR_O)
Binary files login-utils.old/login and login-utils/login differ
diff -rubB login-utils.old/login.c login-utils/login.c
--- login-utils.old/login.c Mon Jul 17 05:46:53 2000
+++ login-utils/login.c Mon Jul 24 07:03:46 2000
-515,8 +515,12
         tcsetattr(0,TCSAFLUSH,&tt);
     }
     
+/* {ce} - Hmm, failed dismally for e.g. /dev/pts/0 - trying to fix
     if ((tty = rindex(ttyn, '/')))
       ++tty;
+*/
+ if (strncmp(ttyn, "/dev/", 5) == 0)
+ tty = ttyn + 5;
     else
       tty = ttyn;
     
-1137,9 +1141,8
     signal(SIGTSTP, SIG_IGN);
     signal(SIGHUP, SIG_DFL);
 
-#ifdef USE_PAM
     /* We must fork before setuid() because we need to call
- * pam_close_session() as root.
+ * pam_close_session() and/or logout(), logwtmp() as root.
      */
     signal(SIGINT, SIG_IGN);
     childPid = fork();
-1147,16 +1150,23
        int errsv = errno;
        /* error in fork() */
        fprintf(stderr,_("login: failure forking: %s"), strerror(errsv));
+#ifdef USE_PAM
        PAM_END;
+#endif
+ logout(tty);
+ logwtmp(tty, "", "");
        exit(0);
     } else if (childPid) {
        /* parent - wait for child to finish, then cleanup session */
        wait(NULL);
+#ifdef USE_PAM
        PAM_END;
+#endif
+ logout(tty);
+ logwtmp(tty, "", "");
        exit(0);
     }
     /* child */
-#endif
     signal(SIGINT, SIG_DFL);
     
     openlog("login", LOG_ODELAY, LOG_AUTHPRIV);
Binary files login-utils.old/login.o and login-utils/login.o differ

diff -rubB telnetd.old/defs.h telnetd/defs.h
--- telnetd.old/defs.h Mon Aug 2 04:14:03 1999
+++ telnetd/defs.h Sun Jul 23 06:17:28 2000
-213,3 +213,5
 
 #define his_will_wont_is_changing my_do_dont_is_changing
 #define his_do_dont_is_changing my_will_wont_is_changing
+
+#define ENV_VAR_BUF_SIZE 256
diff -rubB telnetd.old/ext.h telnetd/ext.h
--- telnetd.old/ext.h Sun Dec 12 14:59:44 1999
+++ telnetd/ext.h Sun Jul 23 06:15:58 2000
-41,6 +41,9
 extern char do_dont_resp[256];
 extern char will_wont_resp[256];
 extern int linemode; /* linemode on/off */
+extern int num_allowed_env_vars;
+extern const char* allowed_env_vars[];
+extern int issue_fd;
 
 #ifdef LINEMODE
 extern int uselinemode; /* what linemode to use (on/off) */
-135,9 +138,11
 void sendbrk(void);
 void sendsusp(void);
 void set_termbuf(void);
-void start_login(const char *, int, const char *);
+/* void start_login(const char *, int, const char *); */
+void start_login(const char* host, int pipefd);
 void start_slc(int);
-void startslave(const char *host, int autologin, char *autoname);
+/* void startslave(const char *host, int autologin, char *autoname); */
+void startslave(const char* host, int pipefd);
 
 #if defined(AUTHENTICATE)
 void start_slave(char *);
diff -rubB telnetd.old/state.c telnetd/state.c
--- telnetd.old/state.c Sun Jul 16 09:35:00 2000
+++ telnetd/state.c Sun Jul 23 06:11:27 2000
-1340,7 +1340,6
     }
 
 #ifdef LINEMODE
-#error problems!
     if (his_want_state_is_will(TELOPT_LINEMODE)) {
         unsigned char *cp, *cpe;
         int len;
-1392,15 +1391,26
 }
 
 #else
+
+const char* allowed_env_vars[] =
+{
+ "TERM", "DISPLAY", "USER", "LOGNAME", "POSIXLY_CORRECT", NULL
+};
+
+const int num_allowed_env_vars
+ = (sizeof(allowed_env_vars) / sizeof(char*)) - 1;
+
 static int envvarok(char *varp) {
     /*
- * Allow only these variables.
+ * Allow only these variables in the above list
      */
- if (!strcmp(varp, "TERM")) return 1;
- if (!strcmp(varp, "DISPLAY")) return 1;
- if (!strcmp(varp, "USER")) return 1;
- if (!strcmp(varp, "LOGNAME")) return 1;
- if (!strcmp(varp, "POSIXLY_CORRECT")) return 1;
+ const char** p = allowed_env_vars;
+ while (*p != NULL)
+ {
+ if (!strcmp(varp, *p))
+ return 1;
+ p++;
+ }
 
     /* optionally syslog(LOG_INFO) here */
     return 0;
Binary files telnetd.old/state.o and telnetd/state.o differ
diff -rubB telnetd.old/sys_term.c telnetd/sys_term.c
--- telnetd.old/sys_term.c Sun Dec 12 14:59:45 1999
+++ telnetd/sys_term.c Mon Jul 24 07:25:39 2000
-557,7 +557,8
  */
 
 /* ARGSUSED */
-void startslave(const char *host, int autologin, char *autoname) {
+/* void startslave(const char *host, int autologin, char *autoname) { */
+void startslave(const char* host, int pipefd) {
     int i;
 
 #if defined(AUTHENTICATE)
-579,7 +580,8
         /* child */
         signal(SIGHUP,SIG_IGN);
         getptyslave();
- start_login(host, autologin, autoname);
+ /* start_login(host, autologin, autoname); */
+ start_login(host, pipefd);
         /*NOTREACHED*/
     }
 }
-611,10 +613,65
 static void addarg(struct argv_stuff *, const char *);
 static void initarg(struct argv_stuff *);
 
-void start_login(const char *host, int autologin, const char *name) {
+/* void start_login(const char *host, int autologin, const char *name) { */
+void start_login(const char* host, int pipefd) {
     struct argv_stuff avs;
     char *const *argvfoo;
- (void)autologin;
+ /* (void)autologin; */
+
+ char envbuf[ENV_VAR_BUF_SIZE*num_allowed_env_vars];
+ int todo = sizeof(envbuf);
+ char* ptr = envbuf;
+
+ /* {ce} - Before we consider launching login, we need to nab any
+ * import environment variables the unprivileged client wants to
+ * send
+ */
+ while (todo > 0)
+ {
+ int got = read(pipefd, ptr, todo);
+ if (got <= 0)
+ {
+ exit(1);
+ }
+ todo -= got;
+ ptr += got;
+ }
+
+ {
+ int todo = num_allowed_env_vars;
+ char* bufptr = envbuf;
+ const char** p = allowed_env_vars;
+ while (todo-- && *p)
+ {
+ bufptr[ENV_VAR_BUF_SIZE-1] = '\0';
+ if (*bufptr)
+ setenv(*p, bufptr, 1);
+ bufptr += ENV_VAR_BUF_SIZE;
+ p++;
+ }
+ }
+
+ /* {ce} - Sanitize USER */
+ {
+ char* p = getenv("USER");
+ if (p != NULL)
+ {
+ if (!isalnum(p[0]))
+ {
+ unsetenv("USER");
+ }
+ else
+ {
+ int i;
+ /* Disallow funnies. */
+ for (i=0; p[i]; i++) {
+ if (p[i]<=32 || p[i]>126)
+ p[i] = '?';
+ }
+ }
+ }
+ }
 
     initarg(&avs);
 
-651,11 +708,11
          */
         if (require_SecurID) addarg(&avs, "-s");
 #endif
+#if defined (AUTHENTICATE)
         if (*name=='-') {
             syslog(LOG_ERR, "Attempt to login with an option!");
             name = "";
         }
-#if defined (AUTHENTICATE)
         if (auth_level >= 0 && autologin == AUTH_VALID) {
 # if !defined(NO_LOGIN_F)
             addarg(&avs, "-f");
-665,6 +722,8
         else
 #endif
         {
+/* {ce} - Arbitrary passing of an argv[] to login isn't smart */
+#ifndef EXTRA_SECURITY
             if (getenv("USER")) {
                 addarg(&avs, getenv("USER"));
                 if (*getenv("USER") == '-') {
-673,6 +732,7
                     exit(1);
                 }
             }
+#endif
         }
     }
     closelog();
-719,9 +779,15
  * This is the routine to call when we are all through, to
  * clean up anything that needs to be cleaned up.
  */
+
 void cleanup(int sig) {
- char *p;
     (void)sig;
+/* {ce} - All this utmp/wtmp junk is handled by login. login put it there,
+ * it can damn well remove it! The tty chmod() etc. is redundant with
+ * the devpts filesystem. When we die, the /dev/pts entry disappears
+ */
+#if 0
+ char *p;
 
     p = line + sizeof("/dev/") - 1;
     if (logout(p)) logwtmp(p, "", "");
-739,6 +805,7
     *p = 'p';
     chmod(line, 0666);
     chown(line, 0, 0);
+#endif
     shutdown(net, 2);
     exit(1);
 }
Binary files telnetd.old/sys_term.o and telnetd/sys_term.o differ
Binary files telnetd.old/telnetd and telnetd/telnetd differ
diff -rubB telnetd.old/telnetd.c telnetd/telnetd.c
--- telnetd.old/telnetd.c Tue Dec 14 00:43:31 1999
+++ telnetd/telnetd.c Mon Jul 24 07:28:16 2000
-31,6 +31,10
  * SUCH DAMAGE.
  */
 
+#if defined(AUTHENTICATE) || defined(ENCRYPT)
+#error cevans - AUTHENTICATE and ENCRYPT probably broken
+#endif
+
 char copyright[] =
   "(#) Copyright (c) 1989 Regents of the University of California.\n"
   "All rights reserved.\n";
-49,6 +53,10
 /* #include <netinet/ip.h> */ /* Don't think this is used at all here */
 #include <arpa/inet.h>
 #include <assert.h>
+
+#include <pwd.h>
+#include <grp.h>
+
 #include "telnetd.h"
 #include "pathnames.h"
 #include "setproctitle.h"
-80,6 +88,9
 char *loginprg = _PATH_LOGIN;
 char *progname;
 
+/* fd for /etc/issue; need to open before we chroot()! */
+int issue_fd = -1;
+
 extern void usage(void);
 
 int
-306,7 +317,9
                 /* NOT REACHED */
         }
 
- openlog("telnetd", LOG_PID | LOG_ODELAY, LOG_DAEMON);
+ /* {ce} - since we're going to run chroot()'ed, open log now */
+ openlog("telnetd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
+ tzset(); /* To get correct time format in the log - thanks Solar */
         fromlen = sizeof (from);
         if (getpeername(0, (struct sockaddr *)&from, &fromlen) < 0) {
                 fprintf(stderr, "%s: ", progname);
-577,6 +590,8
      * able to verify anything else. So, we treat -1 like 1.
      */
 
+#if 0
+#warning If (foolishly) renabled, this will not work due to chroot()
     /*
      * Don't do this - tgetent is not really trustworthy. Assume
      * the terminal type is one we know; terminal types are pretty
-587,6 +602,7
      * if (tgetent(buf, s) == 0)
      * return(0);
      */
+#endif
     return(1);
 }
 
-607,8 +623,9
 {
         const char *host;
         struct hostent *hp;
- int level;
+ int level = -1;
         char user_name[256];
+ int pipefds[2];
 
         /*
          * Find an available pty to use.
-618,11 +635,16
                 fatal(net, "All network ports in use");
 
         /* get name of connected client */
+ /* {ce} - Rationale: it's not a cunning plan to pass semi-arbitrary
+ * data to /bin/login in argv[]
+ */
+#ifndef EXTRA_SECURITY
         hp = gethostbyaddr((char *)&who->sin_addr, sizeof (struct in_addr),
                 who->sin_family);
         if (hp)
                 host = hp->h_name;
         else
+#endif
                 host = inet_ntoa(who->sin_addr);
 
         /*
-642,6 +664,8
         }
         host = remote_host_name;
 
+ setenv("REMOTEHOST", host, 0);
+
         /* Get local host name */
         {
                 struct hostent *h;
-662,21 +686,128
          * get terminal type.
          */
         *user_name = 0;
+
+ /* {ce} - Make a pipe to communicate from no-priv protocol parser
+ * to privileged /bin/login launching child.
+ * We need the communication to pass environment variables which
+ * /bin/login needs to inherit
+ */
+ {
+ int retval;
+ retval = pipe(pipefds);
+ if (retval != 0)
+ {
+ fatal(net, "pipe");
+ }
+ }
+
+ /* Start the login slave (needs privs) */
+ /* startslave(host, level, user_name); */
+ startslave(host, pipefds[0]);
+
+ /* {ce} - Before we chroot(), open /etc/issue.net. Failure
+ * is not a problem
+ */
+ issue_fd = open(ISSUE_FILE, O_RDONLY);
+
+ /* {ce} - Lose privs, and chroot(), before exposing protocol */
+ {
+ struct stat dummy;
+ struct passwd* pw;
+ pw = getpwnam("telnetd");
+ if (pw == NULL)
+ {
+ /* Bah */
+ pw = getpwnam("nobody");
+ }
+ /* Refuse to run at all if we can't drop root */
+ if (pw == NULL)
+ {
+ fatal(net, "Cannot drop privilege");
+ }
+ /* Before we drop root do the chroot() */
+ if (stat("/usr/share/empty", &dummy) == 0)
+ {
+ if (chdir("/usr/share/empty") != 0)
+ fatal(net, "chdir");
+ if (chroot(".") != 0)
+ fatal(net, "chroot");
+ }
+ else
+ {
+ char fname[256];
+
+ if (chdir("/var/run") != 0)
+ fatal(net, "chdir");
+ snprintf(fname, sizeof(fname), "telnetd.%d", getpid());
+ if (mkdir(fname, 0700) != 0)
+ fatal(net, "mkdir");
+ if (chdir(fname) != 0)
+ fatal(net, "chdir");
+ if (chroot(".") != 0)
+ fatal(net, "chroot");
+ rmdir(".");
+ }
+
+ if (setgroups(0, NULL) != 0)
+ fatal(net, "setgroups");
+ if (setgid(pw->pw_gid) != 0)
+ fatal(net, "setgid");
+ if (setuid(pw->pw_uid) != 0)
+ fatal(net, "setuid");
+ }
+
+ /* {ce} - This exposes arbitrary telnet protocol to malicious
+ * clients => we need to drop privs/chroot() before here
+ */
         level = getterminaltype(user_name);
         setenv("TERM", terminaltype ? terminaltype : "network", 1);
 
+ /* {ce} - Now we need to transmit the environment to the slave -
+ * it needs that before it starts login, otherwise TERM, USER
+ * etc. do not work
+ */
+ {
+ char envbuf[ENV_VAR_BUF_SIZE*num_allowed_env_vars];
+ int todo = num_allowed_env_vars;
+ const char** p = allowed_env_vars;
+ char* dest = envbuf;
+ char* var;
+
+ memset(envbuf, '\0', sizeof(envbuf));
+
+ while (todo-- && *p != NULL)
+ {
+ var = getenv(*p);
+ if (var && *var && strlen(var) < ENV_VAR_BUF_SIZE)
+ strcpy(dest, var);
+
+ p++;
+ dest += ENV_VAR_BUF_SIZE;
+ }
+
+ {
+ int towrite = sizeof(envbuf);
+ char* ptr = envbuf;
+ while (towrite > 0)
+ {
+ int got = write(pipefds[1], ptr, towrite);
+ if (got <= 0)
+ {
+ fatal(net, "write");
+ }
+ towrite -= got;
+ ptr += got;
+ }
+ }
+ }
+
         /* TODO list stuff provided by Laszlo Vecsey <masterinternexus.net> */
 
- /*
- * Set REMOTEHOST environment variable
- */
+#ifndef EXTRA_SECURITY
         setproctitle("%s", host);
+#endif
         setenv("REMOTEHOST", host, 0);
-
- /*
- * Start up the login process on the slave side of the terminal
- */
- startslave(host, level, user_name);
 
         telnet(net, pty); /* begin server processing */
 
Binary files telnetd.old/telnetd.o and telnetd/telnetd.o differ
diff -rubB telnetd.old/utility.c telnetd/utility.c
--- telnetd.old/utility.c Sun Dec 12 14:59:45 1999
+++ telnetd/utility.c Sun Jul 23 06:16:26 2000
-506,7 +506,8
                                 FILE *fp;
                                 int p, c;
 
- if ((fp = fopen(ISSUE_FILE, "r")) == NULL)
+ if (issue_fd == -1 ||
+ (fp = fdopen(issue_fd, "r")) == NULL)
                                         break;
                                 p = '\n';
                                 while ((c = fgetc(fp)) != EOF) {
Binary files telnetd.old/utility.o and telnetd/utility.o differ