Index: debian/scripts/vulnerable-passwords =================================================================== --- debian/scripts/vulnerable-passwords (revision 0) +++ debian/scripts/vulnerable-passwords (revision 1060) @@ -0,0 +1,89 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +BEGIN { + use lib qw(/usr/share/request-tracker3.6/lib); + use RT; + RT::LoadConfig; + RT::Init; +} + +$| = 1; + +use Getopt::Long; +my $fix; +GetOptions("fix!" => \$fix); + +use RT::Users; +my $users = RT::Users->new( $RT::SystemUser ); +$users->Limit( + FIELD => 'Password', + OPERATOR => 'IS NOT', + VALUE => 'NULL', + ENTRYAGGREGATOR => 'AND', +); +$users->Limit( + FIELD => 'Password', + OPERATOR => '!=', + VALUE => '*NO-PASSWORD*', + ENTRYAGGREGATOR => 'AND', +); +$users->Limit( + FIELD => 'Password', + OPERATOR => 'NOT STARTSWITH', + VALUE => '!', + ENTRYAGGREGATOR => 'AND', +); +push @{$users->{'restrictions'}{ "main.Password" }}, + "AND LENGTH(main.Password) < 40"; + +my $count = $users->Count; +if ($count == 0) { + print "No users with unsalted or weak cryptography found.\n"; + exit 0; +} + +if ($fix) { + print "Upgrading $count users...\n"; + while (my $u = $users->Next) { + my $stored = $u->__Value("Password"); + my $raw; + if (length $stored == 32) { + $raw = pack("H*",$stored); + } elsif (length $stored == 22) { + $raw = MIME::Base64::decode_base64($stored); + } elsif (length $stored == 13) { + printf "%20s => Old crypt() format, cannot upgrade\n", $u->Name; + } else { + printf "%20s => Unknown password format!\n", $u->Name; + } + next unless $raw; + + my $salt = pack("C4",map{int rand(256)} 1..4); + my $sha = Digest::SHA::sha256( + $salt . $raw + ); + $u->_Set( + Field => "Password", + Value => MIME::Base64::encode_base64( + $salt . substr($sha,0,26)), + ); + } + print "Done.\n"; + exit 0; +} else { + if ($count < 20) { + print "$count users found with unsalted or weak-cryptography passwords:\n"; + print " Id | Name\n", "-"x9, "+", "-"x9, "\n"; + while (my $u = $users->Next) { + printf "%8d | %s\n", $u->Id, $u->Name; + } + } else { + print "$count users found with unsalted or weak-cryptography passwords\n"; + } + + print "\n", "Run again with --fix to upgrade.\n"; + exit 1; +} Property changes on: debian/scripts/vulnerable-passwords ___________________________________________________________________ Added: svn:executable + * Index: debian/control =================================================================== --- debian/control (revision 1050) +++ debian/control (working copy) @@ -2,7 +2,7 @@ Section: misc Priority: optional Maintainer: Debian Request Tracker Group -Uploaders: Niko Tyni , Jacob Helwig , Toni Mueller , Ivan Kohler +Uploaders: Niko Tyni , Jacob Helwig , Toni Mueller , Ivan Kohler , Dominic Hargreaves Build-Depends: debhelper (>= 5), dpatch (>= 2.0.9) Build-Depends-Indep: perl (>= 5.8.3) Standards-Version: 3.7.2 Index: debian/patches/72_RT-3.6.4-3.6.9-session_fixation.v2.dpatch =================================================================== --- debian/patches/72_RT-3.6.4-3.6.9-session_fixation.v2.dpatch (revision 0) +++ debian/patches/72_RT-3.6.4-3.6.9-session_fixation.v2.dpatch (revision 1060) @@ -0,0 +1,33 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 72_RT-3.6.4-3.6.9-session_fixation.v2.dpatch +## +## DP: Security fix: session fixation vulnerability (CVE-2009-3585) + +@DPATCH@ +diff --git a/html/Elements/SetupSessionCookie b/html/Elements/SetupSessionCookie +index 087f825..c7b3c72 100755 +--- a/html/Elements/SetupSessionCookie ++++ b/html/Elements/SetupSessionCookie +@@ -50,7 +50,7 @@ return if $m->is_subrequest; # avoid reentrancy, as suggested by masonbook + + my %cookies = CGI::Cookie->fetch(); + my $cookiename = "RT_SID_" . $RT::rtname . "." . $ENV{'SERVER_PORT'}; +-$SessionCookie ||= $cookies{$cookiename} ? $cookies{$cookiename}->value : undef; ++$SessionCookie = $cookies{$cookiename} ? $cookies{$cookiename}->value : undef; + + my %backends = ( + mysql => 'Apache::Session::MySQL', +@@ -95,6 +95,13 @@ if ($@) { + undef $cookies{$cookiename}; + }; + } ++elsif ( !($session{'CurrentUser'} && $session{'CurrentUser'}->id) ) { ++ eval { ++ undef $cookies{$cookiename}; ++ tied(%session)->delete; ++ tie %session, $session_class, undef, $session_properties; ++ } ++} + + if ($@) { + die loc("RT couldn't store your session.") . "\n" Property changes on: debian/patches/72_RT-3.6.4-3.6.9-session_fixation.v2.dpatch ___________________________________________________________________ Added: svn:executable + * Index: debian/patches/76_patchset-2011-04-08-3.6.7.dpatch =================================================================== --- debian/patches/76_patchset-2011-04-08-3.6.7.dpatch (revision 0) +++ debian/patches/76_patchset-2011-04-08-3.6.7.dpatch (revision 1060) @@ -0,0 +1,392 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 76_patchset-2011-04-08-3.6.7.dpatch +## +## DP: * Multiple security fixes for: +## DP: - Information disclosure via SQL injection (CVE-2011-1686) +## DP: - Information disclosure via search interface (CVE-2011-1687) +## DP: - Information disclosure via directory traversal (CVE-2011-1688) +## DP: - User javascript execution via XSS vulnerability (CVE-2011-1689) +## DP: - Authentication credentials theft (CVE-2011-1690) +## DP: - XSS relating to login credentials +## DP: +## DP: Patch supplied by Best Practical with only file paths adjusted +diff --git a/bin/mason_handler.fcgi b/bin/mason_handler.fcgi +index 26842d3..e51ba5a 100755 +--- a/bin/mason_handler.fcgi.in ++++ b/bin/mason_handler.fcgi.in +@@ -70,6 +70,17 @@ while ( my $cgi = CGI::Fast->new ) { + Module::Refresh->refresh if $RT::DevelMode; + RT::ConnectToDatabase(); + ++ # Each environment has its own way of handling .. and so on in paths, ++ # so RT consistently forbids such paths. ++ if ( $cgi->path_info =~ m{/\.} ) { ++ $RT::Logger->crit("Invalid request for ".$cgi->path_info." aborting"); ++ print STDOUT "HTTP/1.0 400\r\n\r\n"; ++ ++ RT::Interface::Web::Handler->CleanupRequest(); ++ ++ next; ++ } ++ + if ( ( !$Handler->interp->comp_exists( $cgi->path_info ) ) + && ( $Handler->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) { + $cgi->path_info( $cgi->path_info . "/index.html" ); +diff --git a/bin/mason_handler.scgi b/bin/mason_handler.scgi +index 2d77e8f..c2c1b95 100755 +--- a/bin/mason_handler.scgi.in ++++ b/bin/mason_handler.scgi.in +@@ -57,6 +57,18 @@ require CGI; + RT::Init(); + + my $cgi = CGI->new; ++ ++# Each environment has its own way of handling .. and so on in paths, ++# so RT consistently forbids such paths. ++if ( $cgi->path_info =~ m{/\.} ) { ++ $RT::Logger->crit("Invalid request for ".$cgi->path_info." aborting"); ++ print STDOUT "HTTP/1.0 400\r\n\r\n"; ++ ++ RT::Interface::Web::Handler->CleanupRequest(); ++ ++ return 0; ++} ++ + if ( ( !$Handler->interp->comp_exists( $cgi->path_info ) ) + && ( $Handler->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) { + $cgi->path_info( $cgi->path_info . "/index.html" ); +diff --git a/bin/mason_handler.svc b/bin/mason_handler.svc +index 3bf851c..4159733 100755 +--- a/bin/mason_handler.svc.in ++++ b/bin/mason_handler.svc.in +@@ -230,6 +230,17 @@ RT::Init(); + while( my $cgi = CGI::Fast->new ) { + my $comp = $ENV{'PATH_INFO'}; + ++ # Each environment has its own way of handling .. and so on in paths, ++ # so RT consistently forbids such paths. ++ if ( $cgi->path_info =~ m{/\.} ) { ++ $RT::Logger->crit("Invalid request for ".$cgi->path_info." aborting"); ++ print STDOUT "HTTP/1.0 400\r\n\r\n"; ++ ++ RT::Interface::Web::Handler->CleanupRequest(); ++ ++ next; ++ } ++ + $comp = $1 if ($comp =~ /^(.*)$/); + $comp =~ s|^$RT::WebPath\b||i; + $comp .= "index.html" if ($comp =~ /\/$/); +diff --git a/bin/webmux.pl b/bin/webmux.pl +index b21d026..5ce1d39 100755 +--- a/bin/webmux.pl.in ++++ b/bin/webmux.pl.in +@@ -107,6 +107,19 @@ sub handler { + local $SIG{__WARN__}; + local $SIG{__DIE__}; + ++ # none of the methods in $r gives us the information we want (most ++ # canonicalize /foo/../bar to /bar which is exactly what we want to avoid) ++ my $uri = URI->new("http://".$r->hostname.$r->unparsed_uri); ++ my $path = URI::Escape::uri_unescape($uri->path); ++ ++ ## Each environment has its own way of handling .. and so on in paths, ++ ## so RT consistently forbids such paths. ++ if ( $path =~ m{/\.} ) { ++ $RT::Logger->crit("Invalid request for ".$path." aborting"); ++ RT::Interface::Web::Handler->CleanupRequest(); ++ return 400; ++ } ++ + if ($r->content_type =~ m/^httpd\b.*\bdirectory/i) { + use File::Spec::Unix; + # Our DirectoryIndex is always index.html, regardless of httpd settings +diff --git a/share/html/Elements/Header b/share/html/Elements/Header +index 02450b1..b3e88d9 100755 +--- a/html/Elements/Header ++++ b/html/Elements/Header +@@ -53,8 +53,8 @@ + + <%$Title%> + +-% if ($Refresh && $Refresh > 0) { +- ++% if ($Refresh && $Refresh =~ /^(\d+)/ && $1 > 0) { ++ + % } + + +diff --git a/share/html/Search/Chart b/share/html/Search/Chart +index 26249a7..14f8bbc 100644 +--- a/html/Search/Chart ++++ b/html/Search/Chart +@@ -70,6 +70,8 @@ use RT::Report::Tickets; + my $tix = RT::Report::Tickets->new( $session{'CurrentUser'} ); + $tix->FromSQL( $Query ); + my $count_name = $tix->Column( FUNCTION => 'COUNT', FIELD => 'id' ); ++my %AllowedGroupings = reverse $tix->Groupings( Query => $Query ); ++$PrimaryGroupBy = 'Queue' unless exists $AllowedGroupings{$PrimaryGroupBy}; + $tix->GroupBy( FIELD => $PrimaryGroupBy ); + my $value_name = $tix->Column( FIELD => $PrimaryGroupBy ); + +diff --git a/share/html/Search/Elements/Chart b/share/html/Search/Elements/Chart +index 37a4da2..f80a011 100644 +--- a/html/Search/Elements/Chart ++++ b/html/Search/Elements/Chart +@@ -56,6 +56,8 @@ use RT::Report::Tickets; + my $tix = RT::Report::Tickets->new( $session{'CurrentUser'} ); + $tix->FromSQL( $Query ); + my $count_name = $tix->Column( FUNCTION => 'COUNT', FIELD => 'id' ); ++my %AllowedGroupings = reverse $tix->Groupings( Query => $Query ); ++$PrimaryGroupBy = 'Queue' unless exists $AllowedGroupings{$PrimaryGroupBy}; + $tix->GroupBy( FIELD => $PrimaryGroupBy ); + my $value_name = $tix->Column( FIELD => $PrimaryGroupBy ); + +diff --git a/share/html/Search/Elements/SelectPersonType b/share/html/Search/Elements/SelectPersonType +index e2a9a21..a8c49ad 100644 +--- a/html/Search/Elements/SelectPersonType ++++ b/html/Search/Elements/SelectPersonType +@@ -72,7 +72,7 @@ else { + @types = qw(Requestor Cc AdminCc Watcher Owner QueueCc QueueAdminCc QueueWatcher); + } + +-my @subtypes = qw(EmailAddress Name RealName Nickname Organization Address1 Address2 WorkPhone HomePhone MobilePhone PagerPhone); ++my @subtypes = @{ $RT::Tickets::SEARCHABLE_SUBFIELDS{'User'} }; + + + <%ARGS> +diff --git a/share/html/autohandler b/share/html/autohandler +index 57ab22a..8efe070 100755 +--- a/html/autohandler ++++ b/html/autohandler +@@ -126,6 +126,8 @@ unless ( $session{'CurrentUser'} && $session{'CurrentUser'}->Id ) { + # Set the proper encoding for the current language handle + $r->content_type("text/html; charset=utf-8"); + ++RT::Interface::Web::MaybeRejectPrivateComponentRequest(); ++ + # If it's a noauth file, don't ask for auth. + if ( $m->base_comp->path =~ $RT::WebNoAuthRegex ) { + $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %ARGS); +diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm +index 4e5fca1..05d96ec 100755 +--- a/lib/RT/Interface/Web.pm ++++ b/lib/RT/Interface/Web.pm +@@ -177,7 +177,36 @@ sub WebExternalAutoInfo { + + # }}} + ++=head2 MaybeRejectPrivateComponentRequest + ++This function will reject calls to private components, like those under ++C. If the requested path is a private component then we will ++abort with a C<403> error. ++ ++=cut ++ ++sub MaybeRejectPrivateComponentRequest { ++ my $m = $HTML::Mason::Commands::m; ++ my $path = $m->request_comp->path; ++ ++ # We do not check for dhandler here, because requesting our dhandlers ++ # directly is okay. Mason will invoke the dhandler with a dhandler_arg of ++ # 'dhandler'. ++ ++ if ($path =~ m{ ++ / # leading slash ++ ( Elements | ++ _elements | # mobile UI ++ Widgets | ++ autohandler | # requesting this directly is suspicious ++ l ) # loc component ++ ( $ | / ) # trailing slash or end of path ++ }xi) { ++ $m->abort(403); ++ } ++ ++ return; ++} + + =head2 Redirect URL + +diff --git a/lib/RT/Interface/Web/Standalone.pm b/lib/RT/Interface/Web/Standalone.pm +index f625dd8..f5417ab 100755 +--- a/lib/RT/Interface/Web/Standalone.pm ++++ b/lib/RT/Interface/Web/Standalone.pm +@@ -74,6 +74,14 @@ sub handle_request { + + Module::Refresh->refresh if $RT::DevelMode; + ++ # Each environment has its own way of handling .. and so on in paths, ++ # so RT consistently forbids such paths. ++ if ( $cgi->path_info =~ m{/\.} ) { ++ $RT::Logger->crit("Invalid request for ".$cgi->path_info." aborting"); ++ print STDOUT "HTTP/1.0 400\r\n\r\n"; ++ return RT::Interface::Web::Handler->CleanupRequest(); ++ } ++ + $self->SUPER::handle_request($cgi); + $RT::Logger->crit($@) if ($@); + +diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm +index 62ae13e..946a2ca 100755 +--- a/lib/RT/SearchBuilder.pm ++++ b/lib/RT/SearchBuilder.pm +@@ -92,6 +92,17 @@ sub _Init { + + # {{{ sub LimitToEnabled + ++sub OrderByCols { ++ my $self = shift; ++ my @sort; ++ for my $s (@_) { ++ next if defined $s->{FIELD} and $s->{FIELD} =~ /\W/; ++ $s->{FIELD} = $s->{FUNCTION} if $s->{FUNCTION}; ++ push @sort, $s; ++ } ++ return $self->SUPER::OrderByCols( @sort ); ++} ++ + =head2 LimitToEnabled + + Only find items that haven\'t been disabled +@@ -298,14 +309,47 @@ This Limit sub calls SUPER::Limit, but defaults "CASESENSITIVE" to 1, thus + making sure that by default lots of things don't do extra work trying to + match lower(colname) agaist lc($val); + ++We also force VALUE to C when the OPERATOR is C or C. ++This ensures that we don't pass invalid SQL to the database or allow SQL ++injection attacks when we pass through user specified values. ++ + =cut + + sub Limit { + my $self = shift; +- my %args = ( CASESENSITIVE => 1, +- @_ ); ++ my %ARGS = ( ++ CASESENSITIVE => 1, ++ OPERATOR => '=', ++ @_, ++ ); + +- return $self->SUPER::Limit(%args); ++ # We use the same regex here that DBIx::SearchBuilder uses to exclude ++ # values from quoting ++ if ( $ARGS{'OPERATOR'} =~ /IS/i ) { ++ # Don't pass anything but NULL for IS and IS NOT ++ $ARGS{'VALUE'} = 'NULL'; ++ } ++ ++ if ($ARGS{FUNCTION}) { ++ ($ARGS{ALIAS}, $ARGS{FIELD}) = split /\./, delete $ARGS{FUNCTION}, 2; ++ $self->SUPER::Limit(%ARGS); ++ } elsif ($ARGS{FIELD} =~ /\W/ ++ or $ARGS{OPERATOR} !~ /^(=|<|>|!=|<>|<=|>= ++ |(NOT\s*)?LIKE ++ |(NOT\s*)?(STARTS|ENDS)WITH ++ |(NOT\s*)?MATCHES ++ |IS(\s*NOT)? ++ |IN)$/ix) { ++ $RT::Logger->crit("Possible SQL injection attack: $ARGS{FIELD} $ARGS{OPERATOR}"); ++ $self->SUPER::Limit( ++ %ARGS, ++ FIELD => 'id', ++ OPERATOR => '<', ++ VALUE => '0', ++ ); ++ } else { ++ $self->SUPER::Limit(%ARGS); ++ } + } + + # }}} +diff --git a/lib/RT/Tickets_Overlay.pm b/lib/RT/Tickets_Overlay.pm +index 5378e71..b4a65c9 100755 +--- a/lib/RT/Tickets_Overlay.pm ++++ b/lib/RT/Tickets_Overlay.pm +@@ -152,6 +152,13 @@ my %FIELD_METADATA = ( + HasNoAttribute => [ 'HASATTRIBUTE', 0 ], + ); + ++our %SEARCHABLE_SUBFIELDS = ( ++ User => [qw( ++ EmailAddress Name RealName Nickname Organization Address1 Address2 ++ WorkPhone HomePhone MobilePhone PagerPhone ++ )], ++); ++ + # Mapping of Field Type to Function + my %dispatch = ( + ENUM => \&_EnumLimit, +@@ -845,6 +852,13 @@ sub _WatcherLimit { + my $type = $meta->[1] || ''; + my $class = $meta->[2] || 'Ticket'; + ++ # Bail if the subfield is not allowed ++ if ( $rest{SUBKEY} ++ and not grep { $_ eq $rest{SUBKEY} } @{$SEARCHABLE_SUBFIELDS{'User'}}) ++ { ++ die "Invalid watcher subfield: '$rest{SUBKEY}'"; ++ } ++ + # Owner was ENUM field, so "Owner = 'xxx'" allowed user to + # search by id and Name at the same time, this is workaround + # to preserve backward compatibility +@@ -1688,11 +1702,11 @@ sub OrderByCols { + + # Ticket.Owner 1 0 0 + my $ownerId = $self->CurrentUser->Id; +- push @res, { %$row, FIELD => "Owner=$ownerId", ORDER => $order } ; ++ push @res, { %$row, FIELD => undef, FUNCTION => "Owner=$ownerId", ORDER => $order } ; + + # Unowned Tickets 0 1 0 + my $nobodyId = $RT::Nobody->Id; +- push @res, { %$row, FIELD => "Owner=$nobodyId", ORDER => $order } ; ++ push @res, { %$row, FIELD => undef, FUNCTION => "Owner=$nobodyId", ORDER => $order } ; + + push @res, { %$row, FIELD => "Priority", ORDER => $order } ; + } +diff --git a/share/html/NoAuth/Logout.html b/share/html/NoAuth/Logout.html +index 9af4a93..e7ea25c 100755 +--- a/html/NoAuth/Logout.html ++++ b/html/NoAuth/Logout.html +@@ -60,6 +60,7 @@ + % $m->abort(); + + <%INIT> ++my $URL = $RT::WebPath."/"; + $m->comp('/Elements/Callback', _CallbackName => 'BeforeSessionDelete', %ARGS); + + if (defined %session) { +@@ -68,7 +69,3 @@ if (defined %session) { + + $m->comp('/Elements/Callback', _CallbackName => 'AfterSessionDelete', %ARGS); + +- +-<%ARGS> +-$URL => $RT::WebPath."/" +- +diff --git a/share/html/Elements/Login b/share/html/Elements/Login +index 8dfbe51..51ff0dc 100755 +--- a/html/Elements/Login ++++ b/html/Elements/Login +@@ -60,10 +60,7 @@ if (UNIVERSAL::can($r, 'uri') and $r->uri =~ m{.*/(.*)}) { + $req_uri = $1; + } + +-my $form_action = defined $goto ? $goto +- : defined $req_uri ? $req_uri +- : $RT::WebPath +- ; ++my $form_action = defined $req_uri ? $req_uri : $RT::WebPath; + + + % $m->callback( %ARGS, CallbackName => 'Header' ); +@@ -134,6 +131,5 @@ my $form_action = defined $goto ? $goto + <%ARGS> + $user => "" + $pass => undef +-$goto => undef + $Error => undef + Property changes on: debian/patches/76_patchset-2011-04-08-3.6.7.dpatch ___________________________________________________________________ Added: svn:executable + * Index: debian/patches/71_RT-3.6-escape_custom_field_value.dpatch =================================================================== --- debian/patches/71_RT-3.6-escape_custom_field_value.dpatch (revision 0) +++ debian/patches/71_RT-3.6-escape_custom_field_value.dpatch (revision 1060) @@ -0,0 +1,17 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 71_RT-3.6-escape_custom_field_value +## +## DP: Security fix: escape custom fields + +@DPATCH@ +--- share-old/html/Elements/ShowCustomFields ++++ share-new/html/Elements/ShowCustomFields +@@ -90,7 +90,7 @@ my $print_value = sub { + if ( $m->comp_exists( $comp ) ) { + $m->comp( $comp, Object => $value ); + } else { +- $m->print( $value->Content ); ++ $m->out( $m->interp->apply_escapes( $value->Content, 'h' ) ); + } + $m->out('') if $linked; + Property changes on: debian/patches/71_RT-3.6-escape_custom_field_value.dpatch ___________________________________________________________________ Added: svn:executable + * Index: debian/patches/73_session_headers.dpatch =================================================================== --- debian/patches/73_session_headers.dpatch (revision 0) +++ debian/patches/73_session_headers.dpatch (revision 1060) @@ -0,0 +1,18 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 73_session-headers.dpatch +## +## DP: Possibly fix regression in security update +## http://lists.bestpractical.com/pipermail/rt-users/2009-December/062777.html +diff --git a/html/Elements/SetupSessionCookie b/html/Elements/SetupSessionCookie +index 3225c0d..00f97ce 100755 +--- a/html/Elements/SetupSessionCookie ++++ b/html/Elements/SetupSessionCookie +@@ -122,7 +122,7 @@ if ( !$cookies{$cookiename} ) { + -path => $RT::WebPath, + -secure => ($RT::WebSecureCookies ? 1 :0) + ); +- $r->headers_out->{'Set-Cookie'} = $cookie->as_string; ++ $r->err_headers_out->{'Set-Cookie'} = $cookie->as_string; + + } + Property changes on: debian/patches/73_session_headers.dpatch ___________________________________________________________________ Added: svn:executable + * Index: debian/patches/70_RT-ShowConfigTab-3.6.dpatch =================================================================== --- debian/patches/70_RT-ShowConfigTab-3.6.dpatch (revision 0) +++ debian/patches/70_RT-ShowConfigTab-3.6.dpatch (revision 1060) @@ -0,0 +1,34 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 70_RT-ShowConfigTab-3.6.patch +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Security fix: only allow SuperUsers to edit global RT at a Glance + +@DPATCH@ +--- share-old/html/Admin/Global/MyRT.html ++++ share-new/html/Admin/Global/MyRT.html +@@ -83,6 +83,8 @@ + + my ($default_portlets) = $sys->Attributes->Named('HomepageSettings'); + ++my $has_right = $session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser'); ++ + my @panes = $m->comp( + '/Admin/Elements/ConfigureMyRT', + panes => ['body', 'summary'], +@@ -91,8 +93,13 @@ + current_portlets => $default_portlets->Content, + OnSave => sub { + my ( $conf, $pane ) = @_; +- $default_portlets->SetContent( $conf ); +- push @actions, loc( 'Global portlet [_1] saved.', $pane ); ++ if (!$has_right) { ++ push @actions, loc( 'Permission denied' ); ++ } ++ else { ++ $default_portlets->SetContent( $conf ); ++ push @actions, loc( 'Global portlet [_1] saved.', $pane ); ++ } + } + ); + Property changes on: debian/patches/70_RT-ShowConfigTab-3.6.dpatch ___________________________________________________________________ Added: svn:executable + * Index: debian/patches/74_salted_passwords.dpatch =================================================================== --- debian/patches/74_salted_passwords.dpatch (revision 0) +++ debian/patches/74_salted_passwords.dpatch (revision 1060) @@ -0,0 +1,101 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 74_salted_passwords.dpatch +## +## DP: Support and upgrade to salted passwords (security fix) +diff --git a/lib/RT/User_Overlay.pm b/lib/RT/User_Overlay.pm +index 1ef8c4b..459968e 100755 +--- a/lib/RT/User_Overlay.pm ++++ b/lib/RT/User_Overlay.pm +@@ -82,6 +82,7 @@ use RT::Principals; + use RT::ACE; + use RT::Interface::Email; + use Encode; ++use Digest::SHA; + + # {{{ sub _Accessible + +@@ -1061,12 +1062,19 @@ returns an MD5 hash of the password passed in, in hexadecimal encoding. + + sub _GeneratePassword { + my $self = shift; +- my $password = shift; +- +- my $md5 = Digest::MD5->new(); +- $md5->add(encode_utf8($password)); +- return ($md5->hexdigest); +- ++ my ($password, $salt) = @_; ++ ++ # Generate a random 4-byte salt ++ $salt ||= pack("C4",map{int rand(256)} 1..4); ++ ++ # Encode the salt, and a truncated SHA256 of the MD5 of the ++ # password The additional, un-necessary level of MD5 allows for ++ # transparent upgrading to this scheme, from the previous unsalted ++ # MD5 one. ++ return MIME::Base64::encode_base64( ++ $salt ++ . substr(Digest::SHA::sha256($salt . Digest::MD5::md5($password)),0,26) ++ ); + } + + =head2 _GeneratePasswordBase64 PASSWORD +@@ -1119,9 +1127,7 @@ sub IsPassword { + my $self = shift; + my $value = shift; + +- #TODO there isn't any apparent way to legitimately ACL this +- +- # RT does not allow null passwords ++ # RT does not allow null passwords + if ( ( !defined($value) ) or ( $value eq '' ) ) { + return (undef); + } +@@ -1136,23 +1142,32 @@ sub IsPassword { + return(undef); + } + +- # generate an md5 password +- if ($self->_GeneratePassword($value) eq $self->__Value('Password')) { +- return(1); +- } +- +- # if it's a historical password we say ok. +- if ($self->__Value('Password') eq crypt($value, $self->__Value('Password')) +- or $self->_GeneratePasswordBase64($value) eq $self->__Value('Password')) +- { +- # ...but upgrade the legacy password inplace. +- $self->SUPER::SetPassword( $self->_GeneratePassword($value) ); +- return(1); ++ my $stored = $self->__Value('Password'); ++ if (length $stored == 40) { ++ # The truncated SHA256(salt,MD5(passwd)) form from 2010/12 is 40 characters long ++ my $hash = MIME::Base64::decode_base64($stored); ++ # The first 4 bytes are the salt, the rest is substr(SHA256,0,26) ++ my $salt = substr($hash, 0, 4, ""); ++ return substr(Digest::SHA::sha256($salt . Digest::MD5::md5($value)), 0, 26) eq $hash; ++ } elsif (length $stored == 32) { ++ # Hex nonsalted-md5 ++ return 0 unless Digest::MD5::md5_hex(Encode::encode_utf8($value)) eq $stored; ++ } elsif (length $stored == 22) { ++ # Base64 nonsalted-md5 ++ return 0 unless Digest::MD5::md5_base64(Encode::encode_utf8($value)) eq $stored; ++ } elsif (length $stored == 13) { ++ # crypt() output ++ return 0 unless crypt(Encode::encode_utf8($value), $stored) eq $stored; ++ } else { ++ $RT::Logger->warn("Unknown password form"); ++ return 0; + } + +- # no password check has succeeded. get out +- +- return (undef); ++ # We got here by validating successfully, but with a legacy ++ # password form. Update to the most recent form. ++ my $obj = $self->isa("RT::CurrentUser") ? $self->UserObj : $self; ++ $obj->_Set(Field => 'Password', Value => $self->_GeneratePassword($value) ); ++ return 1; + } + + # }}} Property changes on: debian/patches/74_salted_passwords.dpatch ___________________________________________________________________ Added: svn:executable + * Index: debian/patches/77_sec-bugfix-2011-04-12-3.6.dpatch =================================================================== --- debian/patches/77_sec-bugfix-2011-04-12-3.6.dpatch (revision 0) +++ debian/patches/77_sec-bugfix-2011-04-12-3.6.dpatch (revision 1060) @@ -0,0 +1,43 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 77_sec-bugfix-2011-04-12-3.6.dpatch +## +## DP: Bugfix for security patchset +From 8f1beb5bcfef0b20a79ffd1ec76162afa44947fb Mon Sep 17 00:00:00 2001 +From: Alex Vandiver +Date: Mon, 28 Mar 2011 18:33:47 -0400 +Subject: [PATCH 2/2] Update the two reports which used the short form of User in charting + +--- + html/Tools/Reports/ResolvedByDates.html | 2 +- + html/Tools/Reports/ResolvedByOwner.html | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/html/Tools/Reports/ResolvedByDates.html b/html/Tools/Reports/ResolvedByDates.html +index b0a66f4..14d9933 100644 +--- a/html/Tools/Reports/ResolvedByDates.html ++++ b/html/Tools/Reports/ResolvedByDates.html +@@ -79,7 +79,7 @@ $q->LoadByCols(Name => $Queue); + % if ($Queue) { $query .= " AND Queue = '$Queue'"} + % if ($ResolvedBefore) { $query .= " AND Resolved < '".$before->ISO."'"; } + % if ($ResolvedAfter) { $query .= " AND Resolved > '".$after->ISO."'"} +-% my $groupby = 'Owner'; ++% my $groupby = 'Owner.Name'; + <& /Search/Elements/Chart, Query => $query, PrimaryGroupBy => $groupby &> + % } + +diff --git a/html/Tools/Reports/ResolvedByOwner.html b/html/Tools/Reports/ResolvedByOwner.html +index 7e60a13..951e878 100644 +--- a/html/Tools/Reports/ResolvedByOwner.html ++++ b/html/Tools/Reports/ResolvedByOwner.html +@@ -59,7 +59,7 @@ $q->LoadByCols(Name => $Queue); + % if ($Queue) { + % # if we have a queue, do the search + % my $query = "Status = 'resolved' AND Queue = '$Queue'"; +-% my $groupby = 'Owner'; ++% my $groupby = 'Owner.Name'; + <& /Search/Elements/Chart, Query => $query, PrimaryGroupBy => $groupby &> + % } + +-- +1.7.4.1 + Index: debian/patches/75_CVE-2011-1008.dpatch =================================================================== --- debian/patches/75_CVE-2011-1008.dpatch (revision 0) +++ debian/patches/75_CVE-2011-1008.dpatch (revision 1060) @@ -0,0 +1,21 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 75_CVE-2011-1008.dpatch +## +## DP: security fix: clone the ticket to set CurrentUser +## DP: Upstream: 2338cd19ed7a7f4c1e94f639ab2789d6586d01f3 +diff --git a/lib/RT/Scrips_Overlay.pm b/lib/RT/Scrips_Overlay.pm +index df24b3c..1d30702 100644 +--- a/lib/RT/Scrips_Overlay.pm ++++ b/lib/RT/Scrips_Overlay.pm +@@ -283,7 +283,10 @@ sub _SetupSourceObjects { + TransactionObj => undef, + @_ ); + +- if ( ( $self->{'TicketObj'} = $args{'TicketObj'} ) ) { ++ ++ if ( $args{'TicketObj'} ) { ++ # clone the ticket here as we need to change CurrentUser ++ $self->{'TicketObj'} = bless { %{$args{'TicketObj'} } }, 'RT::Ticket'; + $self->{'TicketObj'}->CurrentUser( $self->CurrentUser ); + } + else { Property changes on: debian/patches/75_CVE-2011-1008.dpatch ___________________________________________________________________ Added: svn:executable + * Index: debian/patches/00list =================================================================== --- debian/patches/00list (revision 1050) +++ debian/patches/00list (working copy) @@ -9,3 +9,11 @@ 13_webmux_path 15_content_transfer_enc 40_encoding +70_RT-ShowConfigTab-3.6 +71_RT-3.6-escape_custom_field_value +72_RT-3.6.4-3.6.9-session_fixation.v2 +73_session_headers +74_salted_passwords +75_CVE-2011-1008 +76_patchset-2011-04-08-3.6.7 +77_sec-bugfix-2011-04-12-3.6 Index: debian/postinst =================================================================== --- debian/postinst (revision 1050) +++ debian/postinst (working copy) @@ -29,9 +29,25 @@ done } +fix_vulnerable_passwords() { + if [ "$1" = "configure" ] && [ -n "$2" ] && \ + dpkg --compare-versions "$2" lt 3.6.5-1ubuntu0.1 + then + if su -c "/usr/sbin/rt-vulnerable-passwords-3.6 --fix" www-data; then + echo "rt-vulnerable-passwords-3.6 invoked successfully on upgrade" + else + echo "rt-vulnerable-passwords-3.6 exited with an error but the" + echo "package post-installation will continue. We recommend that" + echo "you check the above error and take corrective action to" + echo "ensure the privacy of your user's passwords." + fi + fi +} + case "$1" in configure) alts + fix_vulnerable_passwords ;; abort-upgrade) alts Index: debian/changelog =================================================================== --- debian/changelog (revision 1050) +++ debian/changelog (working copy) @@ -1,3 +1,22 @@ +request-tracker3.6 (3.6.5-1ubuntu0.1) unstable; urgency=low + + * Security fix: support salted passwords in database and upgrade + unsalted passwords (CVE-2011-0009) + * Security fix: only allow SuperUsers to edit global RT at a Glance + * Security fix: escape custom field values before display to prevent + XSS attack + * Security fix for session fixation vulnerability (CVE-2009-3585) + * Security fix: fix information leakage in scrips (CVE-2011-1008) + * Multiple security fixes for: + - Information disclosure via SQL injection (CVE-2011-1686) + - Information disclosure via search interface (CVE-2011-1687) + - Information disclosure via directory traversal (CVE-2011-1688) + - User javascript execution via XSS vulnerability (CVE-2011-1689) + - Authentication credentials theft (CVE-2011-1690) + - XSS relating to login credentials + + -- Dominic Hargreaves Sun, 29 May 2011 14:38:31 +0100 + request-tracker3.6 (3.6.5-1) unstable; urgency=low * New upstream release. Index: debian/rules =================================================================== --- debian/rules (revision 1050) +++ debian/rules (working copy) @@ -115,6 +115,8 @@ # Fix permissions for Perl modules find $(RT3_PKG)/usr/share/$(RT3)/lib -type f -print0 | xargs -0 --no-run-if-empty chmod 0644 + install -m 755 debian/scripts/vulnerable-passwords $(RT3_PKG)/usr/sbin/rt-vulnerable-passwords-3.6 + # This file has a password in it chmod 600 $(RT3_PKG)/etc/$(RT3)/RT_SiteConfig.pm install -m 644 debian/lintian-overrides $(RT3_PKG)/usr/share/lintian/overrides/$(RT3)