Version in base suite: 5.0.3+dfsg-3~deb12u5 Base version: request-tracker5_5.0.3+dfsg-3~deb12u5 Target version: request-tracker5_5.0.3+dfsg-3~deb12u6 Base file: /srv/ftp-master.debian.org/ftp/pool/main/r/request-tracker5/request-tracker5_5.0.3+dfsg-3~deb12u5.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/r/request-tracker5/request-tracker5_5.0.3+dfsg-3~deb12u6.dsc .git-dpm | 4 changelog | 34 patches/series | 3 patches/upstream_5.0.3_cve:_patchset_2025-04-08-RT_Config.diff | 65 + patches/upstream_5.0.3_cve:_patchset_2026-05-05-RT_Config.diff | 47 + patches/upstream_5.0.3_cve:_patchset_2026-05-05.diff | 411 ++++++++++ 6 files changed, 562 insertions(+), 2 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpro2s7wxn/request-tracker5_5.0.3+dfsg-3~deb12u5.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpro2s7wxn/request-tracker5_5.0.3+dfsg-3~deb12u6.dsc: no acceptable signature found diff -Nru request-tracker5-5.0.3+dfsg/debian/.git-dpm request-tracker5-5.0.3+dfsg/debian/.git-dpm --- request-tracker5-5.0.3+dfsg/debian/.git-dpm 2026-03-07 23:18:18.000000000 +0000 +++ request-tracker5-5.0.3+dfsg/debian/.git-dpm 2026-06-01 01:10:19.000000000 +0000 @@ -1,6 +1,6 @@ # see git-dpm(1) from git-dpm package -145bc6052a83e98c8bf51a1bd8bad9a59ab02b17 -145bc6052a83e98c8bf51a1bd8bad9a59ab02b17 +65ea909080c10f6e652816ce377b0b6b5a449808 +65ea909080c10f6e652816ce377b0b6b5a449808 52cb0ca22325e7a067f0a3411ffb55ef03d47aa4 52cb0ca22325e7a067f0a3411ffb55ef03d47aa4 request-tracker5_5.0.3+dfsg.orig.tar.gz diff -Nru request-tracker5-5.0.3+dfsg/debian/changelog request-tracker5-5.0.3+dfsg/debian/changelog --- request-tracker5-5.0.3+dfsg/debian/changelog 2026-03-07 23:18:18.000000000 +0000 +++ request-tracker5-5.0.3+dfsg/debian/changelog 2026-06-01 01:10:19.000000000 +0000 @@ -1,3 +1,37 @@ +request-tracker5 (5.0.3+dfsg-3~deb12u6) bookworm-security; urgency=medium + + * Include missing default configuration items for security vulnerability + fixes included in 5.0.3+dfsg-3~deb12u3. Namely: RestrictLinkDomains and + Cipher in %SMIME. + * Apply upstream patch which fixes several security vulnerabilities: + - [CVE-2026-6841] Reflected cross-site scripting via the search "Page" URL + parameter. + - [CVE-2026-41073] Spreadsheet (CSV/formula) injection via ticket values + that are exported to a spreadsheet from search results. User-controlled + data is not sanitized before being written to the output file, which can + cause spreadsheet applications such as Microsoft Excel to interpret + crafted values as formulas or macros when the file is opened. + - [CVE-2026-41075] SQL injection via the entry_aggregator parameter in JSON + search. An authenticated user can craft input that is incorporated into + database queries without proper validation, potentially allowing them to + read or modify data in the RT database. + - [CVE-2026-41076] LDAP authentication bypass when RT is configured to + authenticate users against an LDAP or Active Directory server. Under + certain LDAP server configurations, an attacker may be able to + authenticate as any LDAP-backed RT user without supplying valid + credentials. + - [CVE-2026-44229] Cross-site scripting via uploaded content that is served + inline rather than as an attachment. + - [CVE-2026-44231] Privilege escalation and information disclosure via the + REST 2.0 user collection endpoint. A Privileged RT user can obtain + authentication credentials belonging to other users, including + administrators, and use those credentials to read data via RT's RSS and + iCal feed endpoints. The same request that exposes the credentials also + rotates them, which invalidates previously-distributed feed URLs across + the instance. + + -- Andrew Ruthven Mon, 01 Jun 2026 13:10:19 +1200 + request-tracker5 (5.0.3+dfsg-3~deb12u5) bookworm; urgency=medium * Set a version for ckeditor when we build it to allow Firefox v148 to diff -Nru request-tracker5-5.0.3+dfsg/debian/patches/series request-tracker5-5.0.3+dfsg/debian/patches/series --- request-tracker5-5.0.3+dfsg/debian/patches/series 2026-03-07 23:18:18.000000000 +0000 +++ request-tracker5-5.0.3+dfsg/debian/patches/series 2026-06-01 01:10:19.000000000 +0000 @@ -32,3 +32,6 @@ upstream_5.0.3_cve:_patchset_2025-04-08.diff upstream_5.0.3_cve:_patchset_2025-04-11.diff upstream_5.0.3_cve:_patchset_2025-10-07.diff +upstream_5.0.3_cve:_patchset_2026-05-05.diff +upstream_5.0.3_cve:_patchset_2026-05-05-RT_Config.diff +upstream_5.0.3_cve:_patchset_2025-04-08-RT_Config.diff diff -Nru request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2025-04-08-RT_Config.diff request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2025-04-08-RT_Config.diff --- request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2025-04-08-RT_Config.diff 1970-01-01 00:00:00.000000000 +0000 +++ request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2025-04-08-RT_Config.diff 2026-06-01 01:10:19.000000000 +0000 @@ -0,0 +1,65 @@ +From 65ea909080c10f6e652816ce377b0b6b5a449808 Mon Sep 17 00:00:00 2001 +From: Andrew Ruthven +Date: Wed, 27 May 2026 20:47:49 +1200 +Subject: Config settings for RestrictLinkDomains, and Cipher in SMIME. + +Add the new configuration options to RT_Config.pm.in as we regenerate the +RT_Config.pm file. + +Patch-Name: upstream_5.0.3_cve:_patchset_2025-04-08-RT_Config.diff +Author: Andrew Ruthven +Forwarded: not-needed +Applied: 5.0.8 +--- + etc/RT_Config.pm.in | 24 ++++++++++++++++++++++++ + 1 file changed, 24 insertions(+) + +diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in +index 6393a1f1..0a9b7bfb 100644 +--- a/etc/RT_Config.pm.in ++++ b/etc/RT_Config.pm.in +@@ -1139,6 +1139,26 @@ higher numbers denoting greater effort. + + Set($BcryptCost, 12); + ++=item C<@RestrictLinkDomains> ++ ++This sets a list of external domains that RT is allowed to link to. If this ++setting is empty, no external domains are allowed. ++ ++Currently, this restriction only applies to links in Format parameter for ++search results. All external links whose domains are not in the list will ++be removed. ++ ++E.g. ++ ++ Set(@RestrictLinkDomains, ("example.com", "*.trusted.com")); ++ ++ example.com # Allow links to "example.com" ++ *.trusted.com # Allow links to any one-level subdomain of "trusted.com" ++ ++=cut ++ ++Set(@RestrictLinkDomains, ()); ++ + =back + + =head2 Internationalization +@@ -4012,6 +4032,9 @@ Set C to the timeout in seconds for + downloading a CRL or an issuer certificate (the latter is used when + checking against OCSP). The default timeout is 30 seconds. + ++Set C to the encryption algorithm to use. By default, it's ++C. ++ + See L for details. + + =back +@@ -4029,6 +4052,7 @@ Set( %SMIME, + CheckCRL => 0, + CheckOCSP => 0, + CheckRevocationDownloadTimeout => 30, ++ Cipher => 'aes-128-cbc', + ); + + =head3 GnuPG configuration diff -Nru request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2026-05-05-RT_Config.diff request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2026-05-05-RT_Config.diff --- request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2026-05-05-RT_Config.diff 1970-01-01 00:00:00.000000000 +0000 +++ request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2026-05-05-RT_Config.diff 2026-06-01 01:10:19.000000000 +0000 @@ -0,0 +1,47 @@ +From 679d2d4b4c1617a56a90483da8cd7365dd0d60d1 Mon Sep 17 00:00:00 2001 +From: Andrew Ruthven +Date: Wed, 27 May 2026 20:37:45 +1200 +Subject: Config settings for EnableRSS and EnableICal + +Add the new configuration options to RT_Config.pm.in as we regenerate the +RT_Config.pm file. + +Patch-Name: upstream_5.0.3_cve:_patchset_2026-05-05-RT_Config.diff +Author: Andrew Ruthven +Forwarded: not-needed +Applied: 5.0.10 +--- + etc/RT_Config.pm.in | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in +index ec4200c3..6393a1f1 100644 +--- a/etc/RT_Config.pm.in ++++ b/etc/RT_Config.pm.in +@@ -181,6 +181,26 @@ Example: + + Set( @StaticRoots, () ); + ++=item C<$EnableRSS> ++ ++RT's per-user RSS feed endpoint at C is enabled by ++default. Set this option to 0 to disable it: requests to that path ++return 404, and search-results pages stop rendering RSS feed links. ++ ++=cut ++ ++Set($EnableRSS, 1); ++ ++=item C<$EnableICal> ++ ++RT's per-user iCal feed endpoint at C is enabled by ++default. Set this option to 0 to disable it: requests to that path ++return 404, and search-results pages stop rendering iCal feed links. ++ ++=cut ++ ++Set($EnableICal, 1); ++ + =back + + diff -Nru request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2026-05-05.diff request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2026-05-05.diff --- request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2026-05-05.diff 1970-01-01 00:00:00.000000000 +0000 +++ request-tracker5-5.0.3+dfsg/debian/patches/upstream_5.0.3_cve:_patchset_2026-05-05.diff 2026-06-01 01:10:19.000000000 +0000 @@ -0,0 +1,411 @@ +From 441def77682457fa31661abf7add10090363a465 Mon Sep 17 00:00:00 2001 +From: Andrew Ruthven +Date: Wed, 27 May 2026 22:29:40 +1200 +Subject: Fix six security issues in RT. + +Resolve vulnerabilities: + + - [CVE-2026-6841] Reflected cross-site scripting via the search "Page" URL + parameter. + - [CVE-2026-41073] Spreadsheet (CSV/formula) injection via ticket values that + are exported to a spreadsheet from search results. User-controlled data is + not sanitized before being written to the output file, which can cause + spreadsheet applications such as Microsoft Excel to interpret crafted + values as formulas or macros when the file is opened. + - [CVE-2026-41075] SQL injection via the entry_aggregator parameter + in JSON search. An authenticated user can craft input that is + incorporated into database queries without proper validation, + potentially allowing them to read or modify data in the RT database. + - [CVE-2026-41076] LDAP authentication bypass when RT is configured to + authenticate users against an LDAP or Active Directory server. Under + certain LDAP server configurations, an attacker may be able to authenticate + as any LDAP-backed RT user without supplying valid credentials. + - [CVE-2026-44229] Cross-site scripting via uploaded content that is served + inline rather than as an attachment. + - [CVE-2026-44231] Privilege escalation and information disclosure via the + REST 2.0 user collection endpoint. A Privileged RT user can obtain + authentication credentials belonging to other users, including + administrators, and use those credentials to read data via RT's RSS and + iCal feed endpoints. The same request that exposes the credentials also + rotates them, which invalidates previously-distributed feed URLs across the + instance. + +Patch-Name: upstream_5.0.3_cve:_patchset_2026-05-05.diff +Author: Best Practical +Forwarded: not-needed +Applied: 5.0.10 +--- + etc/RT_Config.pm | 20 +++++ + lib/RT/Authen/ExternalAuth.pm | 5 ++ + lib/RT/Config.pm | 6 ++ + lib/RT/Interface/Web/MenuBuilder.pm | 39 +++++----- + .../REST2/Resource/ObjectCustomFieldValue.pm | 8 +- + lib/RT/SearchBuilder.pm | 11 +++ + lib/RT/User.pm | 74 +++++++++++-------- + share/html/Download/CustomFieldValue/dhandler | 5 +- + share/html/Elements/CollectionList | 2 +- + share/html/Elements/TSVExport | 3 +- + share/html/NoAuth/iCal/dhandler | 2 + + share/html/NoAuth/rss/dhandler | 2 + + share/html/Search/Results.html | 2 +- + 13 files changed, 125 insertions(+), 54 deletions(-) + +diff --git a/etc/RT_Config.pm b/etc/RT_Config.pm +index 891df772..ae9adac7 100644 +--- a/etc/RT_Config.pm ++++ b/etc/RT_Config.pm +@@ -181,6 +181,26 @@ Example: + + Set( @StaticRoots, () ); + ++=item C<$EnableRSS> ++ ++RT's per-user RSS feed endpoint at C is enabled by ++default. Set this option to 0 to disable it: requests to that path ++return 404, and search-results pages stop rendering RSS feed links. ++ ++=cut ++ ++Set($EnableRSS, 1); ++ ++=item C<$EnableICal> ++ ++RT's per-user iCal feed endpoint at C is enabled by ++default. Set this option to 0 to disable it: requests to that path ++return 404, and search-results pages stop rendering iCal feed links. ++ ++=cut ++ ++Set($EnableICal, 1); ++ + =back + + +diff --git a/lib/RT/Authen/ExternalAuth.pm b/lib/RT/Authen/ExternalAuth.pm +index 7384c92a..eaeaffb2 100644 +--- a/lib/RT/Authen/ExternalAuth.pm ++++ b/lib/RT/Authen/ExternalAuth.pm +@@ -631,6 +631,11 @@ sub GetAuth { + + my ($service,$username,$password) = @_; + ++ unless ( defined $password && length $password ) { ++ $RT::Logger->debug("External auth ($service) rejecting empty password for $username"); ++ return 0; ++ } ++ + my $success = 0; + + # Get the full configuration for that service as a hashref +diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm +index b0e6fac1..216e4c1f 100644 +--- a/lib/RT/Config.pm ++++ b/lib/RT/Config.pm +@@ -2119,6 +2119,12 @@ our %META; + SelfServiceRegex => { + Immutable => 1, + }, ++ EnableRSS => { ++ Immutable => 1, ++ }, ++ EnableICal => { ++ Immutable => 1, ++ }, + ); + my %OPTIONS = (); + my @LOADED_CONFIGS = (); +diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm +index c68de8ed..cf1659a9 100644 +--- a/lib/RT/Interface/Web/MenuBuilder.pm ++++ b/lib/RT/Interface/Web/MenuBuilder.pm +@@ -732,23 +732,28 @@ sub BuildMainNav { + if ( $class eq 'RT::Tickets' ) { + my %rss_data + = map { $_ => $query_args->{$_} || $fallback_query_args{$_} || '' } qw(Query Order OrderBy); +- my $RSSQueryString = "?" +- . QueryString( +- Query => $rss_data{Query}, +- Order => $rss_data{Order}, +- OrderBy => $rss_data{OrderBy} +- ); +- my $RSSPath = join '/', map $HTML::Mason::Commands::m->interp->apply_escapes( $_, 'u' ), +- $current_user->UserObj->Name, +- $current_user->UserObj->GenerateAuthString( +- $rss_data{Query} . $rss_data{Order} . $rss_data{OrderBy} ); +- +- $more->child( rss => title => loc('RSS'), path => "/NoAuth/rss/$RSSPath/$RSSQueryString" ); +- my $ical_path = join '/', map $HTML::Mason::Commands::m->interp->apply_escapes( $_, 'u' ), +- $current_user->UserObj->Name, +- $current_user->UserObj->GenerateAuthString( $rss_data{Query} ), +- $rss_data{Query}; +- $more->child( ical => title => loc('iCal'), path => '/NoAuth/iCal/' . $ical_path ); ++ if ( RT->Config->Get('EnableRSS') ) { ++ my $RSSQueryString = "?" ++ . QueryString( ++ Query => $rss_data{Query}, ++ Order => $rss_data{Order}, ++ OrderBy => $rss_data{OrderBy} ++ ); ++ my $RSSPath = join '/', map $HTML::Mason::Commands::m->interp->apply_escapes( $_, 'u' ), ++ $current_user->UserObj->Name, ++ $current_user->UserObj->GenerateAuthString( ++ $rss_data{Query} . $rss_data{Order} . $rss_data{OrderBy} ); ++ ++ $more->child( rss => title => loc('RSS'), path => "/NoAuth/rss/$RSSPath/$RSSQueryString" ); ++ } ++ ++ if ( RT->Config->Get('EnableICal') ) { ++ my $ical_path = join '/', map $HTML::Mason::Commands::m->interp->apply_escapes( $_, 'u' ), ++ $current_user->UserObj->Name, ++ $current_user->UserObj->GenerateAuthString( $rss_data{Query} ), ++ $rss_data{Query}; ++ $more->child( ical => title => loc('iCal'), path => '/NoAuth/iCal/' . $ical_path ); ++ } + + #XXX TODO better abstraction of SuperUser right check + if ( $current_user->HasRight( Right => 'SuperUser', Object => RT->System ) ) { +diff --git a/lib/RT/REST2/Resource/ObjectCustomFieldValue.pm b/lib/RT/REST2/Resource/ObjectCustomFieldValue.pm +index 10416970..c65015f6 100644 +--- a/lib/RT/REST2/Resource/ObjectCustomFieldValue.pm ++++ b/lib/RT/REST2/Resource/ObjectCustomFieldValue.pm +@@ -80,13 +80,15 @@ sub to_binary { + } + + my $content_type = $self->record->ContentType || 'text/plain; charset=utf-8'; +- if (RT->Config->Get('AlwaysDownloadAttachments')) { +- $self->response->headers_out->{'Content-Disposition'} = "attachment"; ++ if (RT->Config->Get('AlwaysDownloadAttachments') || $content_type =~ m{^(image/svg\+xml|application/pdf)}i) { ++ $self->response->header('Content-Disposition' => 'attachment'); + } + elsif (!RT->Config->Get('TrustHTMLAttachments')) { +- $content_type = 'text/plain; charset=utf-8' if ($content_type =~ /^text\/html/i); ++ $content_type = 'text/plain; charset=utf-8' ++ if $content_type =~ m{^(text/html|application/xhtml\+xml|text/xml|application/xml)}i; + } + ++ $self->response->header('X-Content-Type-Options' => 'nosniff') if RT->Config->Get('StrictContentTypes'); + $self->response->content_type($content_type); + + my $content = $self->record->LargeContent; +diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm +index e4334e71..3924b14e 100644 +--- a/lib/RT/SearchBuilder.pm ++++ b/lib/RT/SearchBuilder.pm +@@ -974,6 +974,17 @@ sub Limit { + ); + } + ++ if ( $ARGS{ENTRYAGGREGATOR} && $ARGS{ENTRYAGGREGATOR} !~ /^(AND|OR|none)$/i ) { ++ $RT::Logger->crit("Possible SQL injection attack via ENTRYAGGREGATOR: $ARGS{ENTRYAGGREGATOR}"); ++ %ARGS = ( ++ %ARGS, ++ FIELD => 'id', ++ OPERATOR => '<', ++ VALUE => '0', ++ ENTRYAGGREGATOR => 'AND', ++ ); ++ } ++ + my $table; + ($table) = $ARGS{'ALIAS'} && $ARGS{'ALIAS'} ne 'main' + ? ($ARGS{'ALIAS'} =~ /^(.*)_\d+$/) +diff --git a/lib/RT/User.pm b/lib/RT/User.pm +index 718b24f8..77375a99 100644 +--- a/lib/RT/User.pm ++++ b/lib/RT/User.pm +@@ -93,6 +93,7 @@ sub _OverlayAccessible { + + Name => { public => 1, admin => 1 }, # loc_left_pair + Password => { read => 0 }, ++ AuthToken => { read => 0 }, + EmailAddress => { public => 1 }, # loc_left_pair + Organization => { public => 1, admin => 1 }, # loc_left_pair + RealName => { public => 1 }, # loc_left_pair +@@ -106,6 +107,15 @@ sub _OverlayAccessible { + } + } + ++# AuthToken is settable internally (GenerateAuthToken / GenerateAuthString / ++# SetCanonicalUserInfo go through SetAuthToken), but no client-facing entry ++# point should let a caller write it directly. Drop it from the allow-list ++# used by REST 2 update_record, the web UI autocreate path, and rt-config. ++sub WritableAttributes { ++ my $self = shift; ++ return grep { $_ ne 'AuthToken' } $self->SUPER::WritableAttributes(@_); ++} ++ + + + =head2 Create { PARAMHASH } +@@ -1342,32 +1352,6 @@ sub CurrentUserRequireToSetPassword { + return %res; + } + +-=head3 AuthToken +- +-Returns an authentication string associated with the user. This +-string can be used to generate passwordless URLs to integrate +-RT with services and programms like callendar managers, rss +-readers and other. +- +-=cut +- +-sub AuthToken { +- my $self = shift; +- my $secret = $self->_Value( AuthToken => @_ ); +- return $secret if $secret; +- +- $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16); +- +- my $tmp = RT::User->new( RT->SystemUser ); +- $tmp->Load( $self->id ); +- my ($status, $msg) = $tmp->SetAuthToken( $secret ); +- unless ( $status ) { +- $RT::Logger->error( "Couldn't set auth token: $msg" ); +- return undef; +- } +- return $secret; +-} +- + =head3 GenerateAuthToken + + Generate a random authentication string for the user. +@@ -1391,7 +1375,22 @@ sub GenerateAuthString { + my $self = shift; + my $protect = shift; + +- my $str = Encode::encode( "UTF-8", $self->AuthToken . $protect ); ++ my $token = $self->_Value('AuthToken'); ++ unless ($token) { ++ # Mint on demand via SystemUser: signing RSS/iCal/CSRF URLs is a ++ # system action, but ModifySelf isn't granted to Privileged users ++ # by default, so SetAuthToken under the current user would fail. ++ $token = substr(Digest::MD5::md5_hex(time . {} . rand()), 0, 16); ++ my $writer = RT::User->new( RT->SystemUser ); ++ $writer->Load( $self->id ); ++ my ($ok, $msg) = $writer->SetAuthToken($token); ++ unless ($ok) { ++ $RT::Logger->error("Couldn't set auth token: $msg"); ++ return; ++ } ++ } ++ ++ my $str = Encode::encode( "UTF-8", $token . $protect ); + + return substr(Digest::MD5::md5_hex($str),0,16); + } +@@ -1408,10 +1407,27 @@ sub ValidateAuthString { + my $auth_string_to_validate = shift; + my $protected = shift; + +- my $str = Encode::encode( "UTF-8", $self->AuthToken . $protected ); ++ # Always compute the hash and run the constant-time compare so the ++ # "no stored token" path looks like a regular failed validation rather ++ # than returning a precomputable md5_hex($protected) or short-circuiting ++ # on a measurable timing differential. ++ my $token = $self->_Value('AuthToken'); ++ my $has_token = defined $token && length $token; ++ $token //= ''; ++ ++ my $str = Encode::encode( "UTF-8", $token . $protected ); + my $valid_auth_string = substr(Digest::MD5::md5_hex($str),0,16); + +- return RT::Util::constant_time_eq( $auth_string_to_validate, $valid_auth_string ); ++ # constant_time_eq dies on undef or length mismatch. Catch so the ++ # rss/iCal dhandlers return 404 instead of 500 when callers send a ++ # malformed auth string, and log so the failure isn't silent. ++ my $eq = do { ++ local $@; ++ my $r = eval { RT::Util::constant_time_eq( $auth_string_to_validate, $valid_auth_string ) }; ++ RT->Logger->warning("ValidateAuthString: $@") if $@; ++ $r // 0; ++ }; ++ return $has_token && $eq; + } + + =head2 SetDisabled +diff --git a/share/html/Download/CustomFieldValue/dhandler b/share/html/Download/CustomFieldValue/dhandler +index 50992995..188dfdf1 100644 +--- a/share/html/Download/CustomFieldValue/dhandler ++++ b/share/html/Download/CustomFieldValue/dhandler +@@ -65,11 +65,12 @@ Abort( loc('Permission Denied'), Code => HTTP::Status::HTTP_FORBIDDEN ) unless $ + + my $content_type = $OCFV->ContentType || 'text/plain; charset=utf-8'; + +-if (RT->Config->Get('AlwaysDownloadAttachments')) { ++if (RT->Config->Get('AlwaysDownloadAttachments') || $content_type =~ m{^(image/svg\+xml|application/pdf)}i) { + $r->headers_out->{'Content-Disposition'} = "attachment"; + } + elsif (!RT->Config->Get('TrustHTMLAttachments')) { +- $content_type = 'text/plain; charset=utf-8' if ($content_type =~ /^text\/html/i); ++ $content_type = 'text/plain; charset=utf-8' ++ if $content_type =~ m{^(text/html|application/xhtml\+xml|text/xml|application/xml)}i; + } + + $r->headers_out->{'X-Content-Type-Options'} = 'nosniff' if RT->Config->Get('StrictContentTypes'); +diff --git a/share/html/Elements/CollectionList b/share/html/Elements/CollectionList +index 4393011b..f0b1e14d 100644 +--- a/share/html/Elements/CollectionList ++++ b/share/html/Elements/CollectionList +@@ -90,7 +90,7 @@ if ( @OrderBy && ($AllowSorting || $PreferOrderBy || !$Collection->{'order_by'}) + } + + $Collection->RowsPerPage( $Rows ) if $Rows; +-$Page = 1 unless $Page && $Page > 0; # workaround problems with $Page = '' or undef ++$Page = 1 unless $Page && $Page =~ /\A\d+\z/ && $Page > 1; # workaround problems with $Page = '' or undef + $Collection->GotoPage( $Page - 1 ); # SB uses page 0 as the first page + + # DisplayFormat lets us use a "temporary" format for display, while +diff --git a/share/html/Elements/TSVExport b/share/html/Elements/TSVExport +index 13f03ccd..79b75426 100644 +--- a/share/html/Elements/TSVExport ++++ b/share/html/Elements/TSVExport +@@ -107,7 +107,8 @@ else { + } + + for (@columns) { +- $m->out(join("\t", map { $_->{header} } @$_)."\n"); ++ # To prevent injection, add a leading space to make sure excel-ish applications treat it like a literal ++ $m->out(join("\t", map { s/^(?=-|\+|=|\@|")/ /; $_ } map { $_->{header} } @$_)."\n"); + } + + my $i = 0; +diff --git a/share/html/NoAuth/iCal/dhandler b/share/html/NoAuth/iCal/dhandler +index ea03178b..55e7203e 100644 +--- a/share/html/NoAuth/iCal/dhandler ++++ b/share/html/NoAuth/iCal/dhandler +@@ -56,6 +56,8 @@ my $notfound = sub { + $m->clear_and_abort; + }; + ++$notfound->() unless RT->Config->Get('EnableICal'); ++ + $notfound->() unless $path =~ m!^([^/]+)/([^/]+)/(.*)(\.(ical|ics))?!; + + my ($name, $auth, $search) = ($1, $2, $3); +diff --git a/share/html/NoAuth/rss/dhandler b/share/html/NoAuth/rss/dhandler +index b0cb73d2..210d5004 100644 +--- a/share/html/NoAuth/rss/dhandler ++++ b/share/html/NoAuth/rss/dhandler +@@ -55,6 +55,8 @@ my $notfound = sub { + $m->clear_and_abort; + }; + ++$notfound->("RSS feeds disabled by configuration") unless RT->Config->Get('EnableRSS'); ++ + $notfound->("Invalid path: $path") unless $path =~ m!^([^/]+)/([^/]+)/?!; + + my ( $name, $auth ) = ( $1, $2 ); +diff --git a/share/html/Search/Results.html b/share/html/Search/Results.html +index 30877e24..2e900db5 100644 +--- a/share/html/Search/Results.html ++++ b/share/html/Search/Results.html +@@ -159,7 +159,7 @@ if ( !defined($Rows) ) { + $Rows = 50; + } + } +-$Page = 1 unless $Page && $Page > 0; ++$Page = 1 unless $Page && $Page =~ /\A\d+\z/ && $Page > 1; + + my $hash_name = join '-', 'CurrentSearchHash', $Class, $ObjectType || (); + my $session_name = join '-', 'collection', $Class, $ObjectType || ();