Date: Wed, 9 May 90 16:55:47 -0700 Subject: possible comsat security problem OK, this letter is mostly in reply to the letter than I received from Ken R. van Wyk (krvw@cert.sei.cmu.edu). It's cc'd to most of the people that sent mail to me or were cc'd on mail sent to me. The short answer. There are 3 possible security problems with comsat. Two of the three are caused by bad system administration, one by a problem in comsat. The long answer. The first problem is as follows: > Because in.comsat runs as root, it can open and read any file. > By symlinking your mailbox file to a protected file and sending > in.comsat a carefully crafted udp message, you can fool > in.comsat into reading part of that file and printing it on your > terminal. The first mistake that people made is that they tested this using sendmail. The problem isn't solved if sendmail refuses to deliver the mail. What's going on is that the bad guy is sending a hand-crafted udp packet to comsat -- this is done by delivermail on our current system, and /bin/mail on earlier versions of BSD and BSD-derived systems. Sendmail isn't in the loop at all. The real issue here is that the user's mail box directory was writeable. That allowed the bad guy to create the symbolic link. If this directory is writeable, you've lost, by definition. Note, the proposed fix of making comsat setuid for the recipient of the mail is not sufficient. If the bad guy can write the mail directory, s/he simply moves aside the mailbox which they wish to run as, create the link, and send that account mail. The second problem is as follows: > Through this hole, comsat can be used to write to > (or at least clobber) an arbitrary file on the system. What's going on here is that comsat uses the utmp file to figure out what terminal the notice should be written too. If the utmp file contains something like "/etc/master.passwd" as the user's terminal, the file will be overwritten. Again, this is not comsat's problem. If the utmp file is writeable by the bad guys, you've already lost. The third problem: > A quick look at the source on okeeffe shows that it will happily > send any characters that are in the mail message to the screen. > This includes the magic cookies to make your terminal echo > back commands like "rm -fr /". Needless to say, sending mail to > someone with a root shell is a tad dangerous. This is correct. The attached comsat source code fixes the problem. --keith # This is a shell archive. Save it in a file, remove anything before # this line, and then unpack it by entering "sh file". Note, it may # create directories; files and directories will be owned by you and # have default permissions. # # This archive contains: # # comsat.c # echo x - comsat.c sed 's/^X//' >comsat.c << 'END-of-comsat.c' X/* X * Copyright (c) 1980 Regents of the University of California. X * All rights reserved. X * X * Redistribution and use in source and binary forms are permitted X * provided that the above copyright notice and this paragraph are X * duplicated in all such forms and that any documentation, X * advertising materials, and other materials related to such X * distribution and use acknowledge that the software was developed X * by the University of California, Berkeley. The name of the X * University may not be used to endorse or promote products derived X * from this software without specific prior written permission. X * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR X * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED X * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. X */ X X#ifndef lint Xchar copyright[] = X"@(#) Copyright (c) 1980 Regents of the University of California.\n\ X All rights reserved.\n"; X#endif /* not lint */ X X#ifndef lint Xstatic char sccsid[] = "@(#)comsat.c 5.16 (Berkeley) 5/9/90"; X#endif /* not lint */ X X#include X#include X#include X#include X#include X X#include X X#include X#include X#include X#include X#include X#include X#include X#include X#include X X#include "pathnames.h" X X/* X * comsat X */ Xint debug = 0; X#define dsyslog if (debug) syslog X X#define MAXIDLE 120 X Xchar hostname[MAXHOSTNAMELEN]; Xstruct utmp *utmp = NULL; Xtime_t lastmsgtime, time(); Xint nutmp, uf; X X/* ARGSUSED */ Xmain(argc, argv) X int argc; X char **argv; X{ X extern int errno; X register int cc; X char msgbuf[100]; X struct sockaddr_in from; X int fromlen; X void onalrm(), reapchildren(); X X /* verify proper invocation */ X fromlen = sizeof(from); X if (getsockname(0, &from, &fromlen) < 0) { X (void)fprintf(stderr, X "comsat: getsockname: %s.\n", strerror(errno)); X exit(1); X } X openlog("comsat", LOG_PID, LOG_DAEMON); X if (chdir(_PATH_MAIL)) { X syslog(LOG_ERR, "chdir: %s: %m", _PATH_MAIL); X exit(1); X } X if ((uf = open(_PATH_UTMP, O_RDONLY, 0)) < 0) { X syslog(LOG_ERR, ".main: %s: %m", _PATH_UTMP); X (void) recv(0, msgbuf, sizeof (msgbuf) - 1, 0); X exit(1); X } X (void)time(&lastmsgtime); X (void)gethostname(hostname, sizeof (hostname)); X onalrm(); X (void)signal(SIGALRM, onalrm); X (void)signal(SIGTTOU, SIG_IGN); X (void)signal(SIGCHLD, reapchildren); X for (;;) { X cc = recv(0, msgbuf, sizeof (msgbuf) - 1, 0); X if (cc <= 0) { X if (errno != EINTR) X sleep(1); X errno = 0; X continue; X } X if (!nutmp) /* no one has logged in yet */ X continue; X sigblock(sigmask(SIGALRM)); X msgbuf[cc] = 0; X (void)time(&lastmsgtime); X mailfor(msgbuf); X sigsetmask(0L); X } X} X Xvoid Xreapchildren() X{ X while (wait3((union wait *)NULL, WNOHANG, (struct rusage *)NULL) > 0); X} X Xvoid Xonalrm() X{ X static u_int utmpsize; /* last malloced size for utmp */ X static u_int utmpmtime; /* last modification time for utmp */ X struct stat statbf; X off_t lseek(); X char *malloc(), *realloc(); X X if (time((time_t *)NULL) - lastmsgtime >= MAXIDLE) X exit(0); X (void)alarm((u_int)15); X (void)fstat(uf, &statbf); X if (statbf.st_mtime > utmpmtime) { X utmpmtime = statbf.st_mtime; X if (statbf.st_size > utmpsize) { X utmpsize = statbf.st_size + 10 * sizeof(struct utmp); X if (utmp) X utmp = (struct utmp *)realloc((char *)utmp, utmpsize); X else X utmp = (struct utmp *)malloc(utmpsize); X if (!utmp) { X syslog(LOG_ERR, "malloc failed"); X exit(1); X } X } X (void)lseek(uf, 0L, L_SET); X nutmp = read(uf, utmp, (int)statbf.st_size)/sizeof(struct utmp); X } X} X Xmailfor(name) X char *name; X{ X register struct utmp *utp = &utmp[nutmp]; X register char *cp; X off_t offset, atol(); X X if (!(cp = index(name, '@'))) X return; X *cp = '\0'; X offset = atoi(cp + 1); X while (--utp >= utmp) X if (!strncmp(utp->ut_name, name, sizeof(utmp[0].ut_name))) X notify(utp, offset); X} X Xstatic char *cr; X Xnotify(utp, offset) X register struct utmp *utp; X off_t offset; X{ X static char tty[20] = _PATH_DEV; X struct sgttyb gttybuf; X FILE *tp; X char name[sizeof (utmp[0].ut_name) + 1]; X struct stat stb; X X (void)strncpy(tty + 5, utp->ut_line, sizeof(utp->ut_line)); X if (stat(tty, &stb) || !(stb.st_mode & S_IEXEC)) { X dsyslog(LOG_DEBUG, "%s: wrong mode on %s", utp->ut_name, tty); X return; X } X dsyslog(LOG_DEBUG, "notify %s on %s\n", utp->ut_name, tty); X if (fork()) X return; X (void)signal(SIGALRM, SIG_DFL); X (void)alarm((u_int)30); X if ((tp = fopen(tty, "w")) == NULL) { X dsyslog(LOG_ERR, "fopen of tty %s failed", tty); X _exit(-1); X } X (void)ioctl(fileno(tp), TIOCGETP, >tybuf); X cr = (gttybuf.sg_flags&CRMOD) && !(gttybuf.sg_flags&RAW) ? X "\n" : "\n\r"; X (void)strncpy(name, utp->ut_name, sizeof (utp->ut_name)); X name[sizeof (name) - 1] = '\0'; X (void)fprintf(tp, "%s\007New mail for %s@%.*s\007 has arrived:%s----%s", X cr, name, sizeof(hostname), hostname, cr, cr); X jkfprintf(tp, name, offset); X (void)fclose(tp); X _exit(0); X} X Xjkfprintf(tp, name, offset) X register FILE *tp; X char name[]; X off_t offset; X{ X register char *cp, ch; X register FILE *fi; X register int linecnt, charcnt, inheader; X char line[BUFSIZ]; X off_t fseek(); X X if ((fi = fopen(name, "r")) == NULL) X return; X (void)fseek(fi, offset, L_SET); X /* X * Print the first 7 lines or 560 characters of the new mail X * (whichever comes first). Skip header crap other than X * From, Subject, To, and Date. X */ X linecnt = 7; X charcnt = 560; X inheader = 1; X while (fgets(line, sizeof (line), fi) != NULL) { X if (inheader) { X if (line[0] == '\n') { X inheader = 0; X continue; X } X if (line[0] == ' ' || line[0] == '\t' || X strncmp(line, "From:", 5) && X strncmp(line, "Subject:", 8)) X continue; X } X if (linecnt <= 0 || charcnt <= 0) { X (void)fprintf(tp, "...more...%s", cr); X return; X } X /* strip weird stuff so can't trojan horse stupid terminals */ X for (cp = line; (ch = *cp) && ch != '\n'; ++cp, --charcnt) { X ch &= 0x7f; X if (!isprint(ch) && !isspace(ch)) X ch |= 0x40; X (void)fputc(ch, tp); X } X (void)fputs(cr, tp); X --linecnt; X } X (void)fprintf(tp, "----%s\n", cr); X} END-of-comsat.c exit