From: justdave%syndicomm.com <> Date: Mon, 10 Dec 2001 00:02:46 +0000 (+0000) Subject: SECURITY FIX bug 54901: If you were using LDAP authentication it would let you log... X-Git-Tag: bugzilla-2.14.2~14 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3dc5c9075bb39c98013899696fc63da67e07505f;p=thirdparty%2Fbugzilla.git SECURITY FIX bug 54901: If you were using LDAP authentication it would let you log in as anyone if you left the password blank. Patch by David Crowe r= jmrobins, justdave --- diff --git a/CGI.pl b/CGI.pl index 4dd0158637..a4c94a868b 100644 --- a/CGI.pl +++ b/CGI.pl @@ -22,8 +22,6 @@ # Joe Robins # Dave Miller # Christopher Aillon -# Gervase Markham -# Christian Reis # Contains some global routines used throughout the CGI scripts of Bugzilla. @@ -42,6 +40,7 @@ use strict; sub CGI_pl_sillyness { my $zz; + $zz = %::FILENAME; $zz = %::MFORM; $zz = %::dontchange; } @@ -98,33 +97,33 @@ sub ParseUrlString { my %isnull; my $remaining = $buffer; while ($remaining ne "") { - my $item; - if ($remaining =~ /^([^&]*)&(.*)$/) { - $item = $1; - $remaining = $2; - } else { - $item = $remaining; - $remaining = ""; - } - - my $name; - my $value; - if ($item =~ /^([^=]*)=(.*)$/) { - $name = $1; - $value = url_decode($2); - } else { - $name = $item; - $value = ""; - } - if ($value ne "") { - if (defined $f->{$name}) { - $f->{$name} .= $value; - my $ref = $m->{$name}; - push @$ref, $value; - } else { - $f->{$name} = $value; - $m->{$name} = [$value]; - } + my $item; + if ($remaining =~ /^([^&]*)&(.*)$/) { + $item = $1; + $remaining = $2; + } else { + $item = $remaining; + $remaining = ""; + } + + my $name; + my $value; + if ($item =~ /^([^=]*)=(.*)$/) { + $name = $1; + $value = url_decode($2); + } else { + $name = $item; + $value = ""; + } + if ($value ne "") { + if (defined $f->{$name}) { + $f->{$name} .= $value; + my $ref = $m->{$name}; + push @$ref, $value; + } else { + $f->{$name} = $value; + $m->{$name} = [$value]; + } } else { $isnull{$name} = 1; } @@ -147,63 +146,46 @@ sub ProcessFormFields { sub ProcessMultipartFormFields { - my ($boundary) = @_; - - # Initialize variables that store whether or not we are parsing a header, - # the name of the part we are parsing, and its value (which is incomplete - # until we finish parsing the part). - my $inheader = 1; - my $fieldname = ""; - my $fieldvalue = ""; - - # Read the input stream line by line and parse it into a series of parts, - # each one containing a single form field and its value and each one - # separated from the next by the value of $boundary. + my ($boundary) = (@_); + $boundary =~ s/^-*//; my $remaining = $ENV{"CONTENT_LENGTH"}; + my $inheader = 1; + my $itemname = ""; +# open(DEBUG, ">debug") || die "Can't open debugging thing"; +# print DEBUG "Boundary is '$boundary'\n"; while ($remaining > 0 && ($_ = )) { $remaining -= length($_); - - # If the current input line is a boundary line, save the previous - # form value and reset the storage variables. +# print DEBUG "< $_"; if ($_ =~ m/^-*$boundary/) { - if ( $fieldname ) { - chomp($fieldvalue); - $fieldvalue =~ s/\r$//; - if ( defined $::FORM{$fieldname} ) { - $::FORM{$fieldname} .= $fieldvalue; - push @{$::MFORM{$fieldname}}, $fieldvalue; - } else { - $::FORM{$fieldname} = $fieldvalue; - $::MFORM{$fieldname} = [$fieldvalue]; - } - } - +# print DEBUG "Entered header\n"; $inheader = 1; - $fieldname = ""; - $fieldvalue = ""; - - # If the current input line is a header line, look for a blank line - # (meaning the end of the headers), a Content-Disposition header - # (containing the field name and, for uploaded file parts, the file - # name), or a Content-Type header (containing the content type for - # file parts). - } elsif ( $inheader ) { + $itemname = ""; + next; + } + + if ($inheader) { if (m/^\s*$/) { $inheader = 0; - } elsif (m/^Content-Disposition:\s*form-data\s*;\s*name\s*=\s*"([^\"]+)"/i) { - $fieldname = $1; +# print DEBUG "left header\n"; + $::FORM{$itemname} = ""; + } + if (m/^Content-Disposition:\s*form-data\s*;\s*name\s*=\s*"([^\"]+)"/i) { + $itemname = $1; +# print DEBUG "Found itemname $itemname\n"; if (m/;\s*filename\s*=\s*"([^\"]+)"/i) { - $::FILE{$fieldname}->{'filename'} = $1; + $::FILENAME{$itemname} = $1; } - } elsif ( m|^Content-Type:\s*([^/]+/[^\s;]+)|i ) { - $::FILE{$fieldname}->{'contenttype'} = $1; } - - # If the current input line is neither a boundary line nor a header, - # it must be part of the field value, so append it to the value. - } else { - $fieldvalue .= $_; + + next; } + $::FORM{$itemname} .= $_; + } + delete $::FORM{""}; + # Get rid of trailing newlines. + foreach my $i (keys %::FORM) { + chomp($::FORM{$i}); + $::FORM{$i} =~ s/\r$//; } } @@ -224,15 +206,8 @@ sub CheckFormField (\%$;\@) { (defined($legalsRef) && lsearch($legalsRef, $formRef->{$fieldname})<0) ){ - SendSQL("SELECT description FROM fielddefs WHERE name=" . SqlQuote($fieldname)); - my $result = FetchOneColumn(); - if ($result) { - PuntTryAgain("A legal $result was not set."); - } - else { - PuntTryAgain("A legal $fieldname was not set."); - print Param("browserbugmessage"); - } + print "A legal $fieldname was not set; "; + print Param("browserbugmessage"); PutFooter(); exit 0; } @@ -263,10 +238,7 @@ sub ValidateBugID { # Make sure the bug number is a positive integer. # Whitespace can be ignored because the SQL server will ignore it. $id =~ /^\s*([1-9][0-9]*)\s*$/ - || DisplayError("The bug number is invalid. If you are trying to use " . - "QuickSearch, you need to enable JavaScript in your " . - "browser. To help us fix this limitation, look " . - "here.") + || DisplayError("The bug number is invalid.") && exit; # Get the values of the usergroupset and userid global variables @@ -274,25 +246,93 @@ sub ValidateBugID { # setting those local variables to the default value of zero if # the global variables are undefined. - # First check that the bug exists - SendSQL("SELECT bug_id FROM bugs WHERE bug_id = $id"); - - FetchOneColumn() + # "usergroupset" stores the set of groups the user is a member of, + # while "userid" stores the user's unique ID. These variables are + # set globally by either confirm_login() or quietly_check_login(), + # one of which should be run before calling this function; otherwise + # this function will treat the user as if they were not logged in + # and throw an error if they try to access a bug that requires + # permissions/authorization to access. + my $usergroupset = $::usergroupset || 0; + my $userid = $::userid || 0; + + # Query the database for the bug, retrieving a boolean value that + # represents whether or not the user is authorized to access the bug. + + # Users are authorized to access bugs if they are a member of all + # groups to which the bug is restricted. User group membership and + # bug restrictions are stored as bits within bitsets, so authorization + # can be determined by comparing the intersection of the user's + # bitset with the bug's bitset. If the result matches the bug's bitset + # the user is a member of all groups to which the bug is restricted + # and is authorized to access the bug. + + # A user is also authorized to access a bug if she is the reporter, + # assignee, QA contact, or member of the cc: list of the bug and the bug + # allows users in those roles to see the bug. The boolean fields + # reporter_accessible, assignee_accessible, qacontact_accessible, and + # cclist_accessible identify whether or not those roles can see the bug. + + # Bit arithmetic is performed by MySQL instead of Perl because bitset + # fields in the database are 64 bits wide (BIGINT), and Perl installations + # may or may not support integers larger than 32 bits. Using bitsets + # and doing bitset arithmetic is probably not cross-database compatible, + # however, so these mechanisms are likely to change in the future. + + # Get data from the database about whether or not the user belongs to + # all groups the bug is in, and who are the bug's reporter and qa_contact + # along with which roles can always access the bug. + SendSQL("SELECT ((groupset & $usergroupset) = groupset) , reporter , assigned_to , qa_contact , + reporter_accessible , assignee_accessible , qacontact_accessible , cclist_accessible + FROM bugs + WHERE bug_id = $id"); + + # Make sure the bug exists in the database. + MoreSQLData() || DisplayError("Bug #$id does not exist.") - && exit; + && exit; + + my ($isauthorized, $reporter, $assignee, $qacontact, $reporter_accessible, + $assignee_accessible, $qacontact_accessible, $cclist_accessible) = FetchSQLData(); - return if CanSeeBug($id, $::userid, $::usergroupset); + # Finish validation and return if the user is a member of all groups to which the bug belongs. + return if $isauthorized; + + # Finish validation and return if the user is in a role that has access to the bug. + if ($userid) { + return + if ($reporter_accessible && $reporter == $userid) + || ($assignee_accessible && $assignee == $userid) + || ($qacontact_accessible && $qacontact == $userid); + } + + # Try to authorize the user one more time by seeing if they are on + # the cc: list. If so, finish validation and return. + if ( $cclist_accessible ) { + my @cclist; + SendSQL("SELECT cc.who + FROM bugs , cc + WHERE bugs.bug_id = $id + AND cc.bug_id = bugs.bug_id + "); + while (my ($ccwho) = FetchSQLData()) { + # more efficient to just check the var here instead of + # creating a potentially huge array to grep against + return if ($userid == $ccwho); + } + + } # The user did not pass any of the authorization tests, which means they # are not authorized to see the bug. Display an error and stop execution. # The error the user sees depends on whether or not they are logged in - # (i.e. $::userid contains the user's positive integer ID). - if ($::userid) { + # (i.e. $userid contains the user's positive integer ID). + if ($userid) { DisplayError("You are not authorized to access bug #$id."); } else { DisplayError( qq|You are not authorized to access bug #$id. To see this bug, you - must first log in + must first log in to an account with the appropriate permissions.| ); } @@ -346,70 +386,28 @@ sub value_quote { sub navigation_header { if (defined $::COOKIE{"BUGLIST"} && $::COOKIE{"BUGLIST"} ne "" && defined $::FORM{'id'}) { - my @bugs = split(/:/, $::COOKIE{"BUGLIST"}); - my $cur = lsearch(\@bugs, $::FORM{"id"}); - print "Bug List: (@{[$cur + 1]} of @{[$#bugs + 1]})\n"; - print "First\n"; - print "Last\n"; - if ($cur > 0) { - print "Prev\n"; - } else { - print "Prev\n"; - } - if ($cur < $#bugs) { - $::next_bug = $bugs[$cur + 1]; - print "Next\n"; - } else { - print "Next\n"; - } + my @bugs = split(/:/, $::COOKIE{"BUGLIST"}); + my $cur = lsearch(\@bugs, $::FORM{"id"}); + print "Bug List: (@{[$cur + 1]} of @{[$#bugs + 1]})\n"; + print "First\n"; + print "Last\n"; + if ($cur > 0) { + print "Prev\n"; + } else { + print "Prev\n"; + } + if ($cur < $#bugs) { + $::next_bug = $bugs[$cur + 1]; + print "Next\n"; + } else { + print "Next\n"; + } print qq{  Show list\n}; } print "     Query page\n"; print "     Enter new bug\n" } -# Adds elements for bug lists. These can be inserted into the header by -# (ab)using the "jscript" parameter to PutHeader, which inserts an arbitrary -# string into the header. This function is modelled on the one above. -sub navigation_links($) { - my ($buglist) = @_; - - my $retval = ""; - - # We need to be able to pass in a buglist because when you sort on a column - # the bugs in the cookie you are given will still be in the old order. - # If a buglist isn't passed, we just use the cookie. - $buglist ||= $::COOKIE{"BUGLIST"}; - - if (defined $buglist && $buglist ne "") { - my @bugs = split(/:/, $buglist); - - if (defined $::FORM{'id'}) { - # We are on an individual bug - my $cur = lsearch(\@bugs, $::FORM{"id"}); - - if ($cur > 0) { - $retval .= "\n"; - $retval .= "\n"; - } - if ($cur < $#bugs) { - $retval .= "\n"; - $retval .= "\n"; - } - - $retval .= "\n"; - $retval .= "\n"; - } else { - # We are on a bug list - $retval .= "\n"; - $retval .= "\n"; - $retval .= "\n"; - } - } - - return $retval; -} - sub make_checkboxes { my ($src,$default,$isregexp,$name) = (@_); my $last = ""; @@ -438,7 +436,7 @@ sub make_checkboxes { } } if (!$found && $default ne "") { - $popup .= "$default"; + $popup .= "$default"; } return $popup; } @@ -514,9 +512,9 @@ sub make_selection_widget { if ($type eq "CHECKBOX") { $popup .= "$displaytext
"; } elsif ($type eq "RADIO") { - $popup .= "$displaytext
"; + $popup .= "$displaytext
"; } else { - $popup .= "