SECURITY: Sockets follow symbolic links

From: Sylvain Robitaille <syl_at_alcor.concordia.ca>
Date: Wed, 29 Sep 1999 17:16:23 -0400 (EDT)

Anyone who might have been keeping up to date with the Bugtraq mailing
list lately has probably seen a published exploit which permits any
local user to use ssh's authentication forwarding feature to create
arbitrary files on the system (/etc/nologin is used in the published
exploit, as an easy denial of service attack).

The problem isn't really in ssh, but rather in the fact that some OSes
(including DEC Unix 4.0e -- I haven't tested other versions) will follow
a symbolic link when creating sockets (and at least in the case of
Linux-2.2.x, when executing the mknod(2) call -- I haven't tested that
on DU).

If you're using ssh with authentication forwarding, sshd creates the
socket while running as root, so if symbolic links are followed, files
can be created/overwritten at will.

I submit the following patch for anyone who is using ssh, (created with
the ssh-1.2.27 sources, but it should apply cleanly to most recent
versions), in order to protect your systems. It causes Sshd to check
first that the socket's filename doesn't already exist, in order to
avoid such attacks.

With this patch, ssh's authentication-forwarding fails if the filename
which is about to be used already exists as anything other than a
socket. If a socket already exists by that name, we can be pretty sure
it's a remnant from an earlier session (perhaps the machine crashed
while the socket was open?)

If the file *does* exist *and* is a symbolic link, it's likely that
someone is trying the exploit published on Bugtraq, so I've added code
to mail the host's sysadmin with vital stats of the symbolic link,
(so the offending account can be dealt with accordingly). Otherwise
we continue without authentication forwarding since it's likely that
something strange is up, but not an attempt to use this exploit.

I don't promise the most impressive code, but it has been tested and I
believe it works correctly. Comments are of course welcome... It would
of course be nice to hear from Compaq when they issue a patch to fix
this problem.

-- 
----------------------------------------------------------------------
Sylvain Robitaille                              syl_at_alcor.concordia.ca
 
Systems Manager                                   Concordia University
Instructional & Information Technology        Montreal, Quebec, Canada
----------------------------------------------------------------------
--- newchannels.c.original	Wed May 12 07:19:27 1999
+++ newchannels.c	Wed Sep 29 14:35:50 1999
_at__at_ -16,6 +16,11 _at__at_
 */
 
 /*
+ * Revision 1.50unoff 1999/09/29 14:27:52  <syl_at_alcor.concordia.ca>
+ *      ssh-agent symlink DoS exploit protection added
+ */
+
+/*
  * $Id: newchannels.c,v 1.49 1999/02/22 08:14:01 tri Exp $
  * $Log: newchannels.c,v $
  * Revision 1.49  1999/02/22 08:14:01  tri
_at__at_ -317,7 +322,77 _at__at_
 
 #define STATUS_TERMINATE                0x003f
 
-/* Data structure for channel data.  This is iniailized in channel_allocate
+/*
+ * 1999/09/29 Sylvain Robitaille: Used for mailing to the sysadmin(s) if
+ *            we catch someone trying the exploit to cause the agent
+ *            socket to be created at the other end of a sym-link.
+ */
+#define MAIL_RECIPIENT "sysadm"
+#define MAIL_COMMAND   "/usr/bin/mailx -s 'URGENT: POSSIBLE SSH-AGENT SYMLINK ATTACK' " MAIL_RECIPIENT
+
+/*
+ *  Mail a complaint to the sysadmin.
+ */
+void mail_report(char *dirname, char *linkname, struct stat *st)
+{
+   FILE *mail_pipe;
+   struct passwd *pw;
+   struct group  *gr;
+   char usrname[17];
+   char grpname[17];
+   char target[PATH_MAX];
+   char filename[PATH_MAX];
+   int  count;
+
+   if ((mail_pipe = popen(MAIL_COMMAND, "w")) == NULL)
+   {
+      snprintf(target, sizeof(target) - 1, "can't open pipe to %s\n",
+               MAIL_COMMAND);
+      log_msg(target);
+      exit(1);
+   }
+
+   /* reconstruct the full path to the file */
+   if (dirname[strlen(dirname)] == '/') dirname[strlen(dirname)] = 0;
+   snprintf(filename, sizeof(filename) - 1, "%s/%s", dirname, linkname);
+
+   /* Figure out who owns the file. */
+   pw = getpwuid(st->st_uid);
+   if (!pw) {
+      snprintf(usrname, sizeof(usrname) - 1, "%d", st->st_uid);
+   } else {
+      snprintf(usrname, sizeof(usrname) - 1, "%s", pw->pw_name);
+   }
+   
+   gr = getgrgid(st->st_gid);
+   if (!gr) {
+      snprintf(grpname, sizeof(grpname) - 1, "%d", st->st_gid);
+   } else {
+      snprintf(grpname, sizeof(grpname) - 1, "%s", gr->gr_name);
+   }
+   
+   /*
+    * Get the link's target. NOTE: The target may not necessarily
+    * already exist, so we don't bother getting stats on it, but we
+    * do want to know its path.
+    */
+   count = readlink(filename, target, sizeof(target) - 1);
+   if (count < 0) {
+      snprintf(target, sizeof(target) - 1, "unreadable!");
+   } else {
+      target[count] = '\0';
+   }
+
+   (void) fprintf(mail_pipe, "File: %s exists as a symbolic link\n", filename);
+   (void) fprintf(mail_pipe, "Owner: %s.%s\n", usrname, grpname);
+   (void) fprintf(mail_pipe, "Target: %s\n", target);
+   (void) fprintf(mail_pipe, "Mode: %o\n", 07777 & st->st_mode);
+   (void)  fflush(mail_pipe);
+
+   (void) pclose(mail_pipe);
+}
+
+/* Data structure for channel data.  This is initialized in channel_allocate
    and cleared in channel_free. */
 
 typedef struct
_at__at_ -2395,6 +2470,33 _at__at_
       return 0;
     }
   
+  /*
+   * 1999/09/28 Sylvain Robitaille: Check that there isn't already a
+   *            file by the same name. If there is, and it's a symbolic
+   *            link, we probably have a DoS attempt. Else, if the file
+   *            exists and is not already a socket, complain and fail.
+   *
+   *            File exists but is a socket is acceptable, since that
+   *            socket might be a remnant (did the machine crash while
+   *            that socket was still open?)
+   */
+  ret = lstat(channel_forwarded_auth_socket_name, &st);
+  if (!ret && S_ISLNK(st.st_mode)) {
+    packet_send_debug("* Remote error: Agent socket creation failed: File exists as a symbolic link.");
+    packet_send_debug("* Remote error: Authentication fowarding disabled.");
+    log_msg("Agent socket creation failed: File %s is a symbolic link.",
+            channel_forwarded_auth_socket_name);
+    mail_report(channel_forwarded_auth_socket_dir_name,
+                channel_forwarded_auth_socket_name, &st);
+    return 0;
+  } else if (!ret && !S_ISSOCK(st.st_mode)) {
+    packet_send_debug("* Remote error: Agent socket creation failed: File exists and is not a socket.");
+    packet_send_debug("* Remote error: Authentication fowarding disabled.");
+    log_msg("Agent socket creation failed: File %s is not a socket.",
+            channel_forwarded_auth_socket_name);
+    return 0;
+  }
+
   /* Create the socket. */
   sock = socket(AF_UNIX, SOCK_STREAM, 0);
   if (sock < 0)
-- 
----------------------------------------------------------------------
Sylvain Robitaille                              syl_at_alcor.concordia.ca
 
Systems Manager                                   Concordia University
Instructional & Information Technology        Montreal, Quebec, Canada
----------------------------------------------------------------------
Received on Wed Sep 29 1999 - 21:25:11 NZST

This archive was generated by hypermail 2.4.0 : Wed Nov 08 2023 - 11:53:39 NZDT