From: justdave%syndicomm.com <> Date: Fri, 25 Apr 2003 03:45:08 +0000 (+0000) Subject: Bug 192677: Add new test to flag failure-to-filter situations in the templates, and... X-Git-Tag: bugzilla-2.16.3~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=538dd6952e080d22e78c9ea516c3f37370bc5628;p=thirdparty%2Fbugzilla.git Bug 192677: Add new test to flag failure-to-filter situations in the templates, and correct the XSS holes that were discovered as a result of it. Filter patch by Gervase Markham Template patches by Gervase Markham and Dave Miller r= gerv, bbaetz, justdave a= justdave --- diff --git a/duplicates.cgi b/duplicates.cgi index 50affa6df8..7172fa6f99 100755 --- a/duplicates.cgi +++ b/duplicates.cgi @@ -57,7 +57,7 @@ my $sortby = formvalue("sortby"); my $changedsince = formvalue("changedsince", 7); my $maxrows = formvalue("maxrows", 100); my $openonly = formvalue("openonly"); -my $reverse = formvalue("reverse"); +my $reverse = formvalue("reverse") ? 1 : 0; my $product = formvalue("product"); my $sortvisible = formvalue("sortvisible"); my @buglist = (split(/[:,]/, formvalue("bug_id"))); @@ -147,6 +147,18 @@ my $generic_query = " # Limit to a single product if requested $generic_query .= (" product = " . SqlQuote($product) . " AND ") if $product; +my $origmaxrows = $maxrows; +detaint_natural($maxrows) + || ThrowUserError("The maximum number of rows, '" . html_quote($origmaxrows) . + "', must be a positive integer.", + "Invalid Max Rows"); + +my $origchangedsince = $changedsince; +detaint_natural($changedsince) + || ThrowUserError("The 'changed since' value, '" . html_quote($origchangedsince) . + "', must be an integer >= 0.", + "Invalid Changed Since"); + my @bugs; my @bug_ids; my $loop = 0; diff --git a/t/008filter.t b/t/008filter.t new file mode 100644 index 0000000000..10d7fc62c2 --- /dev/null +++ b/t/008filter.t @@ -0,0 +1,182 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code are the Bugzilla tests. +# +# The Initial Developer of the Original Code is Jacob Steenhagen. +# Portions created by Jacob Steenhagen are +# Copyright (C) 2001 Jacob Steenhagen. All +# Rights Reserved. +# +# Contributor(s): Gervase Markham + +################# +#Bugzilla Test 8# +#####filter###### + +# This test scans all our templates for every directive. Having eliminated +# those which cannot possibly cause XSS problems, it then checks the rest +# against the safe list stored in the filterexceptions.pl file. + +# Sample exploit code: '>"> + +use strict; +use lib 't'; + +use vars qw(%safe); + +use Support::Templates; +use File::Spec 0.82; +use Test::More tests => $Support::Templates::num_actual_files; +use Cwd; + +# Undefine the record separator so we can read in whole files at once +my $oldrecsep = $/; +$/ = undef; +my $topdir = cwd; + +foreach my $path (@Support::Templates::include_paths) { + $path =~ m|template/([^/]+)/|; + my $lang = $1; + chdir $topdir; # absolute path + my @testitems = Support::Templates::find_actual_files($path); + + next unless @testitems; + + # Some people require this, others don't. No-one knows why. + chdir $path; # relative path + + # We load a %safe list of acceptable exceptions. + if (!-r "filterexceptions.pl") { + ok(0, "$path has templates but no filterexceptions.pl file. --ERROR"); + next; + } + else { + do "filterexceptions.pl"; + } + + # We preprocess the %safe hash of lists into a hash of hashes. This allows + # us to flag which members were not found, and report that as a warning, + # thereby keeping the lists clean. + foreach my $file (keys %safe) { + my $list = $safe{$file}; + $safe{$file} = {}; + foreach my $directive (@$list) { + $safe{$file}{$directive} = 0; + } + } + + foreach my $file (@testitems) { + # There are some files we don't check, because there is no need to + # filter their contents due to their content-type. + if ($file =~ /\.(txt|png)\.tmpl$/) { + ok(1, "($lang) $file is filter-safe"); + next; + } + + # Read the entire file into a string + open (FILE, "<$file") || die "Can't open $file: $!\n"; + my $slurp = ; + close (FILE); + + my @unfiltered; + + # /g means we execute this loop for every match + # /s means we ignore linefeeds in the regexp matches + while ($slurp =~ /\[%(.*?)%\]/gs) { + my $directive = $1; + + my @lineno = ($` =~ m/\n/gs); + my $lineno = scalar(@lineno) + 1; + + # Comments + next if $directive =~ /^[+-]?#/; + + # Remove any leading/trailing + or - and whitespace. + $directive =~ s/^[+-]?\s*//; + $directive =~ s/\s*[+-]?$//; + + # Directives + next if $directive =~ /^(IF|END|UNLESS|FOREACH|PROCESS|INCLUDE| + BLOCK|USE|ELSE|NEXT|LAST|DEFAULT|FLUSH| + ELSIF|SET|SWITCH|CASE)/x; + + # Simple assignments + next if $directive =~ /^[\w\.\$]+\s+=\s+/; + + # Conditional literals with either sort of quotes + # There must be no $ in the string for it to be a literal + next if $directive =~ /^(["'])[^\$]*[^\\]\1/; + + # Special values always used for numbers + next if $directive =~ /^[ijkn]$/; + next if $directive =~ /^count$/; + + # Params + next if $directive =~ /^Param\(/; + + # Other functions guaranteed to return OK output + next if $directive =~ /^(time2str|GetBugLink)\(/; + + # Safe Template Toolkit virtual methods + next if $directive =~ /\.(size)$/; + + # Special Template Toolkit loop variable + next if $directive =~ /^loop\.(index|count)$/; + + # Things which are already filtered + # Note: If a single directive prints two things, and only one is + # filtered, we may not catch that case. + next if $directive =~ /FILTER\ (html|csv|js|url_quote|quoteUrls| + time|uri|xml)/x; + + # Exclude those on the nofilter list + if (defined($safe{$file}{$directive})) { + $safe{$file}{$directive}++; + next; + }; + + # This intentionally makes no effort to eliminate duplicates; to do + # so would merely make it more likely that the user would not + # escape all instances when attempting to correct an error. + push(@unfiltered, "$lineno:$directive"); + } + + my $fullpath = File::Spec->catfile($path, $file); + + if (@unfiltered) { + my $uflist = join("\n ", @unfiltered); + ok(0, "($lang) $fullpath has unfiltered directives:\n $uflist\n--ERROR"); + } + else { + # Find any members of the exclusion list which were not found + my @notfound; + foreach my $directive (keys %{$safe{$file}}) { + push(@notfound, $directive) if ($safe{$file}{$directive} == 0); + } + + if (@notfound) { + my $nflist = join("\n ", @notfound); + ok(0, "($lang) $fullpath - FEL has extra members:\n $nflist\n" . + "--WARNING"); + } + else { + # Don't use the full path here - it's too long and unwieldy. + ok(1, "($lang) $file is filter-safe"); + } + } + } +} + +$/ = $oldrecsep; + +exit 0; diff --git a/template/en/default/attachment/edit.html.tmpl b/template/en/default/attachment/edit.html.tmpl index 873f73b59e..8c4eae569c 100644 --- a/template/en/default/attachment/edit.html.tmpl +++ b/template/en/default/attachment/edit.html.tmpl @@ -50,7 +50,7 @@ // If this is a plaintext document, remove cruft that Mozilla adds // because it treats it as an HTML document with a big PRE section. // http://bugzilla.mozilla.org/show_bug.cgi?id=86012 - var contentType = '[% contenttype %]'; + var contentType = '[% contenttype FILTER js %]'; if ( contentType == 'text/plain' ) { theContent = theContent.replace( /^
/i , "" );
diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl
index 259f2ea8e1..9afd5b2774 100644
--- a/template/en/default/bug/create/create.html.tmpl
+++ b/template/en/default/bug/create/create.html.tmpl
@@ -107,7 +107,8 @@
     [% sel = { description => 'Priority', name => 'priority' } %]
     [% INCLUDE select %]
   [% ELSE %]
-    
+    
   [% END %]
 
   [% sel = { description => 'Severity', name => 'bug_severity' } %]
diff --git a/template/en/default/bug/create/make-template.html.tmpl b/template/en/default/bug/create/make-template.html.tmpl
index 1e2495ff86..958d183cc9 100644
--- a/template/en/default/bug/create/make-template.html.tmpl
+++ b/template/en/default/bug/create/make-template.html.tmpl
@@ -25,7 +25,7 @@
 %]
 
 

-If you bookmark this link, +If you bookmark this link, going to the bookmark will bring up the enter bug page with the fields initialized as you've requested.

diff --git a/template/en/default/bug/show-multiple.html.tmpl b/template/en/default/bug/show-multiple.html.tmpl index 0e73f4ad72..60d23ce059 100644 --- a/template/en/default/bug/show-multiple.html.tmpl +++ b/template/en/default/bug/show-multiple.html.tmpl @@ -104,7 +104,8 @@ URL:  - [% bug.bug_file_loc FILTER html %] + + [% bug.bug_file_loc FILTER html %] diff --git a/template/en/default/filterexceptions.pl b/template/en/default/filterexceptions.pl new file mode 100644 index 0000000000..08a021bb29 --- /dev/null +++ b/template/en/default/filterexceptions.pl @@ -0,0 +1,407 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code are the Bugzilla tests. +# +# The Initial Developer of the Original Code is Jacob Steenhagen. +# Portions created by Jacob Steenhagen are +# Copyright (C) 2001 Jacob Steenhagen. All +# Rights Reserved. +# +# Contributor(s): Gervase Markham + +# Important! The following classes of directives are excluded in the test, +# and so do not need to be added here. Doing so will cause warnings. +# See 008filter.t for more details. +# +# Comments - [%#... +# Directives - [% IF|ELSE|UNLESS|FOREACH... +# Assignments - [% foo = ... +# Simple literals - [% " selected" ... +# Values always used for numbers - [% (i|j|k|n|count) %] +# Params - [% Param(... +# Safe functions - [% (time2str|GetBugLink)... +# Safe vmethods - [% foo.size %] +# TT loop variables - [% loop.count %] +# Already-filtered stuff - [% wibble FILTER html %] +# where the filter is one of html|csv|js|url_quote|quoteUrls|time|uri|xml + +# Key: +# +# "#": directive should be filtered, but not doing so is not a security hole +# The plan is to come back and add filtering for all those marked "#" after +# the security release. +# +# "# Email": as above; but noting that it's an email address. +# Other sorts of comments denote cleanups noticed while doing this work; +# they should be fixed in the very short term. + +%::safe = ( + +'sidebar.xul.tmpl' => [ + 'template_version', +], + +'search/boolean-charts.html.tmpl' => [ + '"field${chartnum}-${rownum}-${colnum}"', + '"value${chartnum}-${rownum}-${colnum}"', + '"type${chartnum}-${rownum}-${colnum}"', + 'field.name', + 'field.description', + 'type.name', + 'type.description', + '"${chartnum}-${rownum}-${newor}"', + '"${chartnum}-${newand}-0"', + 'newchart', + '$jsmagic', # +], + +'search/form.html.tmpl' => [ + 'qv.value', + 'qv.name', + 'qv.description', + 'field.name', + 'field.description', + 'sel.name', + 'button_name', # +], + +'search/knob.html.tmpl' => [ + 'button_name', # +], + +'reports/components.html.tmpl' => [ + 'numcols', + 'numcols - 1', + 'comp.description', + 'comp.initialowner', # email address + 'comp.initialqacontact', # email address +], + +'reports/duplicates-simple.html.tmpl' => [ + 'title', # +], + +'reports/duplicates-table.html.tmpl' => [ + '"&maxrows=$maxrows" IF maxrows', + '"&changedsince=$changedsince" IF changedsince', + '"&product=$product" IF product', # + '"&format=$format" IF format', # + '"&bug_id=$bug_ids_string&sortvisible=1" IF sortvisible', + 'column.name', + 'column.description', + 'vis_bug_ids.push(bug.id)', + 'bug.id', + 'bug.count', + 'bug.delta', + 'bug.component', # + 'bug.bug_severity', # + 'bug.op_sys', # + 'bug.target_milestone', # +], + +'reports/duplicates.html.tmpl' => [ + 'bug_ids_string', + 'maxrows', + 'changedsince', + 'reverse', +], + +'reports/keywords.html.tmpl' => [ + 'keyword.description', + 'keyword.bugcount', +], + +'list/change-columns.html.tmpl' => [ + 'column', + 'desc.${column}', # +], + +'list/edit-multiple.html.tmpl' => [ + 'group.bit', + 'group.description', + 'group.description FILTER strike', + 'knum', + 'menuname', + 'selected IF resolution == "FIXED"', # +], + +'list/list-rdf.rdf.tmpl' => [ + 'template_version', + 'bug.id', + 'column', +], + +'list/list-simple.html.tmpl' => [ + 'title', +], + +'list/list.html.tmpl' => [ + 'currenttime', # + 'buglist', + 'bugowners', # email address +], + +'list/table.html.tmpl' => [ + 'id', + 'splitheader ? 2 : 1', + 'abbrev.$id.title || column.title', # + 'tableheader', + 'bug.severity', # + 'bug.priority', # + 'bug.id', +], + +'global/choose-product.html.tmpl' => [ + 'target', + 'proddesc.$p', +], + +'global/code-error.html.tmpl' => [ + 'error', +], + +'global/footer.html.tmpl' => [ + 'CALL SyncAnyPendingShadowChanges() IF SyncAnyPendingShadowChanges', +], + +'global/header.html.tmpl' => [ + 'header_html', + 'javascript', + 'style', + 'style_url', + 'bgcolor', + 'onload', + 'h1', + 'h2', + 'message', +], + +'global/hidden-fields.html.tmpl' => [ + 'mvalue | html | html_linebreak', # Need to eliminate | usage + 'field.value | html | html_linebreak', +], + +'global/select-menu.html.tmpl' => [ + 'options', +], + +'global/useful-links.html.tmpl' => [ + 'user.login', # Email address +], + +'global/user-error.html.tmpl' => [ + 'error', # can contain HTML in 2.16.x +], + +'bug/comments.html.tmpl' => [ + 'comment.time', + 'quoteUrls(comment.body)', +], + +'bug/dependency-graph.html.tmpl' => [ + 'image_map', # We need to continue to make sure this is safe in the CGI + 'image_url', + 'map_url', + 'bug_id', +], + +'bug/dependency-tree.html.tmpl' => [ + 'hide_resolved ? "Open b" : "B"', + 'bugid', + 'maxdepth', + 'dependson_ids.join(",")', + 'blocked_ids.join(",")', + 'dep_id', + 'hide_resolved ? 0 : 1', + 'hide_resolved ? "Show" : "Hide"', + 'realdepth < 2 || maxdepth == 1 ? "disabled" : ""', + 'hide_resolved', + 'realdepth < 2 ? "disabled" : ""', + 'maxdepth + 1', + 'maxdepth == 0 || maxdepth == realdepth ? "disabled" : ""', + 'realdepth < 2 || ( maxdepth && maxdepth < 2 ) ? "disabled" : ""', + 'maxdepth > 0 && maxdepth <= realdepth ? maxdepth : ""', + 'maxdepth == 1 ? 1 + : ( maxdepth ? maxdepth - 1 : realdepth - 1 )', + 'realdepth < 2 || ! maxdepth || maxdepth >= realdepth ? + "disabled" : ""', +], + +'bug/edit.html.tmpl' => [ + 'bug.delta_ts', + 'bug.bug_id', + 'bug.votes', + 'group.bit', + 'group.description', + 'knum', + 'dep.title', + 'dep.fieldname', + 'bug.${dep.fieldname}.join(\', \')', + 'selname', + 'bug.longdesclength', + 'bug.creation_ts', +], + +'bug/navigate.html.tmpl' => [ + 'this_bug_idx + 1', + 'bug_list.first', + 'bug_list.last', + 'bug_list.$prev_bug', + 'bug_list.$next_bug', +], + +'bug/show-multiple.html.tmpl' => [ + 'bug.bug_id', + 'bug.component', # + 'attr.description', # +], + +'bug/votes/list-for-bug.html.tmpl' => [ + 'voter.count', + 'total', +], + +'bug/votes/list-for-user.html.tmpl' => [ + 'product.maxperbug', + 'bug.id', + 'bug.count', + 'product.total', + 'product.maxvotes', +], +# h2 = voting_user.name # Email + +'bug/process/confirm-duplicate.html.tmpl' => [ + 'original_bug_id', + 'duplicate_bug_id', +], + +'bug/process/midair.html.tmpl' => [ + 'bug_id', +], + +'bug/process/next.html.tmpl' => [ + 'next_id', +], + +'bug/process/results.html.tmpl' => [ + 'title.$type', + 'id', + 'mail', +], + +'bug/process/verify-new-product.html.tmpl' => [ + 'form.product', # +], + +'bug/create/create.html.tmpl' => [ + 'default.bug_status', # + 'g.bit', + 'g.description', + 'sel.name', + 'sel.description', +], + +'bug/activity/show.html.tmpl' => [ + 'bug_id', +], + +'bug/activity/table.html.tmpl' => [ + 'operation.who', # Email + 'operation.when', + 'change.attachid', + 'change.field', +], + +'attachment/create.html.tmpl' => [ + 'bugid', + 'attachment.id', +], + +'attachment/created.html.tmpl' => [ + 'attachid', + 'bugid', + 'contenttype', + 'mailresults', +], + +'attachment/edit.html.tmpl' => [ + 'attachid', + 'bugid', + 'def.id', + 'a', +], + +'attachment/list.html.tmpl' => [ + 'attachment.attachid', + 'attachment.date', + 'bugid', +], + +'attachment/show-multiple.html.tmpl' => [ + 'a.attachid', + 'a.date', +], + +'attachment/updated.html.tmpl' => [ + 'attachid', + 'bugid', + 'mailresults', +], + +'admin/attachstatus/create.html.tmpl' => [ + 'id', +], + +'admin/attachstatus/delete.html.tmpl' => [ + 'attachcount', + 'id', + 'name', +], + +'admin/attachstatus/edit.html.tmpl' => [ + 'id', + 'sortkey', +], + +'admin/attachstatus/list.html.tmpl' => [ + 'statusdef.sortkey', + 'statusdef.id', + 'statusdef.attachcount', +], + +'account/prefs/account.html.tmpl' => [ + 'login_change_date', # +], + +'account/prefs/email.html.tmpl' => [ + 'watchedusers', # Email + 'useqacontact ? \'5\' : \'4\'', + 'role', + 'reason.name', + 'reason.description', +], + +'account/prefs/permissions.html.tmpl' => [ + 'bit_description', +], + +'account/prefs/prefs.html.tmpl' => [ + 'tab.name', + 'tab.description', + 'changes_saved', + 'current_tab.name', + 'current_tab.description', + 'current_tab.description FILTER lower', +], + +); diff --git a/template/en/default/global/choose-product.html.tmpl b/template/en/default/global/choose-product.html.tmpl index e8d9aaf5c0..530413de65 100644 --- a/template/en/default/global/choose-product.html.tmpl +++ b/template/en/default/global/choose-product.html.tmpl @@ -33,7 +33,7 @@ + [% IF format %]&format=[% format FILTER url_quote %][% END %]"> [% p FILTER html %]: diff --git a/template/en/default/global/code-error.html.tmpl b/template/en/default/global/code-error.html.tmpl index 637fba6b1b..cab4f44b8a 100644 --- a/template/en/default/global/code-error.html.tmpl +++ b/template/en/default/global/code-error.html.tmpl @@ -56,7 +56,7 @@
 Variables:
   [% FOREACH key = variables.keys %]
-    [%+ key %]: [%+ variables.$key %]
+    [%+ key FILTER html %]: [%+ variables.$key FILTER html %]
   [% END %]
   
[% END %] diff --git a/template/en/default/global/hidden-fields.html.tmpl b/template/en/default/global/hidden-fields.html.tmpl index f968fab200..a824c3489c 100644 --- a/template/en/default/global/hidden-fields.html.tmpl +++ b/template/en/default/global/hidden-fields.html.tmpl @@ -32,11 +32,11 @@ [% NEXT IF exclude && field.key.search(exclude) %] [% IF mform.${field.key}.size > 1 %] [% FOREACH mvalue = mform.${field.key} %] - [% END %] [% ELSE %] - [% END %] [% END %] diff --git a/template/en/default/global/message.html.tmpl b/template/en/default/global/message.html.tmpl index 14e1f10d03..f1f77fdb21 100644 --- a/template/en/default/global/message.html.tmpl +++ b/template/en/default/global/message.html.tmpl @@ -35,7 +35,7 @@ [%# Display a URL if the calling script has included one. %] [% IF url && link %]

- [% link %] + [% link FILTER html %]

[% END %] diff --git a/template/en/default/list/change-columns.html.tmpl b/template/en/default/list/change-columns.html.tmpl index 96e56e8eb9..a7c5f6e235 100644 --- a/template/en/default/list/change-columns.html.tmpl +++ b/template/en/default/list/change-columns.html.tmpl @@ -27,7 +27,7 @@ Check which columns you wish to appear on the list, and then click on submit. (Cookies are required.)

- + [% FOREACH column = masterlist %]
@@ -41,7 +41,7 @@ on submit. (Cookies are required.)
- +
diff --git a/template/en/default/list/list.html.tmpl b/template/en/default/list/list.html.tmpl index 8ffba004fb..dab76666fc 100644 --- a/template/en/default/list/list.html.tmpl +++ b/template/en/default/list/list.html.tmpl @@ -94,7 +94,7 @@

Query Page   Enter New Bug - Edit this query + Edit this query

[% ELSIF bugs.size == 1 %] @@ -134,10 +134,10 @@ Query Page    Enter New Bug    - Change Columns    + Change Columns    [% IF bugs.size > 1 && caneditbugs && !dotweak %] - Change Several Bugs at Once    @@ -147,7 +147,7 @@ Send Mail to Bug Owners    [% END %] - Edit this Query    + Edit this Query    diff --git a/template/en/default/list/table.html.tmpl b/template/en/default/list/table.html.tmpl index 6d5ee0d6cc..f14784f65c 100644 --- a/template/en/default/list/table.html.tmpl +++ b/template/en/default/list/table.html.tmpl @@ -69,7 +69,7 @@ - ID + ID [% IF splitheader %] @@ -102,7 +102,7 @@ [% BLOCK columnheader %] - [%- abbrev.$id.title || column.title -%] diff --git a/template/en/default/reports/duplicates.html.tmpl b/template/en/default/reports/duplicates.html.tmpl index 64ba5e120d..da218d7c55 100644 --- a/template/en/default/reports/duplicates.html.tmpl +++ b/template/en/default/reports/duplicates.html.tmpl @@ -59,7 +59,7 @@

Change Parameters

- + @@ -83,8 +83,8 @@