]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 26257: [SECURITY] Bugzilla should prevent malicious webpages from making bugzilla...
authorlpsolit%gmail.com <>
Mon, 2 Feb 2009 18:33:29 +0000 (18:33 +0000)
committerlpsolit%gmail.com <>
Mon, 2 Feb 2009 18:33:29 +0000 (18:33 +0000)
Bugzilla/Install/Localconfig.pm
Bugzilla/Template.pm
Bugzilla/Token.pm
buglist.cgi
email_in.pl
process_bug.cgi
template/en/default/admin/confirm-action.html.tmpl
template/en/default/bug/edit.html.tmpl
template/en/default/list/edit-multiple.html.tmpl

index 45005f032037da74cbfed8e8482427018b23222b..654b44b9f83e846a69c15f0a75f38343a7b26562 100644 (file)
@@ -32,6 +32,7 @@ use strict;
 
 use Bugzilla::Constants;
 use Bugzilla::Install::Util qw(bin_loc);
+use Bugzilla::Util qw(generate_random_password);
 
 use Data::Dumper;
 use File::Basename qw(dirname);
@@ -183,6 +184,18 @@ EOT
         desc    => <<EOT
 # The interdiff feature needs diff, so we have to have that path.
 # Please specify the directory name only; do not use trailing slash.
+EOT
+    },
+    {
+        name    => 'site_wide_secret',
+        default => generate_random_password(256),
+        desc    => <<EOT
+# This secret key is used by your installation for the creation and
+# validation of encrypted tokens to prevent unsolicited changes,
+# such as bug changes. A random string is generated by default.
+# It's very important that this key is kept secret. It also must be
+# very long.
+
 EOT
     },
 );
index 688c533860c0f509951f0e3bd4ec518de99953b1..8c34bb493e87a24447d3ab81d161fd96251c9a8f 100644 (file)
@@ -41,6 +41,7 @@ use Bugzilla::Util;
 use Bugzilla::User;
 use Bugzilla::Error;
 use Bugzilla::Status;
+use Bugzilla::Token;
 use Bugzilla::Template::Parser;
 
 use Cwd qw(abs_path);
@@ -765,6 +766,9 @@ sub create {
                 return $docs_urlbase;
             },
 
+            # Allow templates to generate a token themselves.
+            'issue_hash_token' => \&Bugzilla::Token::issue_hash_token,
+
             # These don't work as normal constants.
             DB_MODULE        => \&Bugzilla::Constants::DB_MODULE,
             REQUIRED_MODULES => 
index 313d4321230cc478f62edda633cb8aad9ddedf68..f87490db19e197f250dce805889716fb8ca910fc 100644 (file)
@@ -39,10 +39,12 @@ use Bugzilla::User;
 use Date::Format;
 use Date::Parse;
 use File::Basename;
+use Digest::MD5 qw(md5_hex);
 
 use base qw(Exporter);
 
-@Bugzilla::Token::EXPORT = qw(issue_session_token check_token_data delete_token);
+@Bugzilla::Token::EXPORT = qw(issue_session_token check_token_data delete_token
+                              issue_hash_token check_hash_token);
 
 ################################################################################
 # Public Functions
@@ -170,6 +172,53 @@ sub issue_session_token {
     return _create_token(Bugzilla->user->id, 'session', $data);
 }
 
+sub issue_hash_token {
+    my ($data, $time) = @_;
+    $data ||= [];
+    $time ||= time();
+
+    # The concatenated string is of the form
+    # token creation time + site-wide secret + user ID + data
+    my @args = ($time, Bugzilla->localconfig->{'site_wide_secret'}, Bugzilla->user->id, @$data);
+    my $token = md5_hex(join('*', @args));
+
+    # Prepend the token creation time, unencrypted, so that the token
+    # lifetime can be validated.
+    return $time . '-' . $token;
+}
+
+sub check_hash_token {
+    my ($token, $data) = @_;
+    $data ||= [];
+    my ($time, $expected_token);
+
+    if ($token) {
+        ($time, undef) = split(/-/, $token);
+        # Regenerate the token based on the information we have.
+        $expected_token = issue_hash_token($data, $time);
+    }
+
+    if (!$token
+        || $expected_token ne $token
+        || time() - $time > MAX_TOKEN_AGE * 86400)
+    {
+        my $template = Bugzilla->template;
+        my $vars = {};
+        $vars->{'script_name'} = basename($0);
+        $vars->{'token'} = issue_hash_token($data);
+        $vars->{'reason'} = (!$token) ?                   'missing_token' :
+                            ($expected_token ne $token) ? 'invalid_token' :
+                                                          'expired_token';
+        print Bugzilla->cgi->header();
+        $template->process('global/confirm-action.html.tmpl', $vars)
+          || ThrowTemplateError($template->error());
+        exit;
+    }
+
+    # If we come here, then the token is valid and not too old.
+    return 1;
+}
+
 sub CleanTokenTable {
     my $dbh = Bugzilla->dbh;
     $dbh->do('DELETE FROM tokens
@@ -310,7 +359,7 @@ sub delete_token {
 # Note: this routine must not be called while tables are locked as it will try
 # to lock some tables itself, see CleanTokenTable().
 sub check_token_data {
-    my ($token, $expected_action) = @_;
+    my ($token, $expected_action, $alternate_script) = @_;
     my $user = Bugzilla->user;
     my $template = Bugzilla->template;
     my $cgi = Bugzilla->cgi;
@@ -330,6 +379,7 @@ sub check_token_data {
         $vars->{'token_action'} = $token_action;
         $vars->{'expected_action'} = $expected_action;
         $vars->{'script_name'} = basename($0);
+        $vars->{'alternate_script'} = $alternate_script || basename($0);
 
         # Now is a good time to remove old tokens from the DB.
         CleanTokenTable();
index d51112a5c21c74985801080a5801b4bcea450bc8..f5284439c19a1b758a198dd4b3becb829fc68198 100755 (executable)
@@ -47,6 +47,7 @@ use Bugzilla::Product;
 use Bugzilla::Keyword;
 use Bugzilla::Field;
 use Bugzilla::Status;
+use Bugzilla::Token;
 
 use Date::Parse;
 
@@ -1241,6 +1242,7 @@ if ($dotweak && scalar @bugs) {
     }
     $vars->{'dotweak'} = 1;
     $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
+    $vars->{'token'} = issue_session_token('buglist_mass_change');
 
     $vars->{'products'} = Bugzilla->user->get_enterable_products;
     $vars->{'platforms'} = get_legal_field_values('rep_platform');
index bed5a1477ccdf3f9a96b7d6df971c983a149fe11..1edce55d8268c167d5091abf563c9f1fe3c12a71 100644 (file)
@@ -47,6 +47,7 @@ use Bugzilla::Error;
 use Bugzilla::Mailer;
 use Bugzilla::User;
 use Bugzilla::Util;
+use Bugzilla::Token;
 
 #############
 # Constants #
@@ -201,6 +202,7 @@ sub process_bug {
         $cgi->param(-name => $field, -value => $fields{$field});
     }
     $cgi->param('longdesclength', scalar $bug->longdescs);
+    $cgi->param('token', issue_hash_token([$bug->id, $bug->delta_ts]));
 
     require 'process_bug.cgi';
 }
index a0aadc1c5a1fba66e3ef878961f7923aaf717526..83041230bb18605b4ec650370484764cf99ea6c6 100755 (executable)
@@ -59,6 +59,7 @@ use Bugzilla::Component;
 use Bugzilla::Keyword;
 use Bugzilla::Flag;
 use Bugzilla::Status;
+use Bugzilla::Token;
 
 use Storable qw(dclone);
 
@@ -158,10 +159,6 @@ if (defined $cgi->param('dontchange')) {
 # reference to flags if $cgi->param('id') is undefined.
 Bugzilla::Flag::validate($cgi->param('id'));
 
-######################################################################
-# End Data/Security Validation
-######################################################################
-
 print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_EMAIL;
 
 # Check for a mid-air collision. Currently this only works when updating
@@ -184,6 +181,8 @@ if (defined $cgi->param('delta_ts')
     $vars->{'comments'} = Bugzilla::Bug::GetComments($first_bug->id,
                                                      "oldest_to_newest");
     $vars->{'bug'} = $first_bug;
+    # The token contains the old delta_ts. We need a new one.
+    $cgi->param('token', issue_hash_token([$first_bug->id, $first_bug->delta_ts]));
     
     # Warn the user about the mid-air collision and ask them what to do.
     $template->process("bug/process/midair.html.tmpl", $vars)
@@ -191,6 +190,22 @@ if (defined $cgi->param('delta_ts')
     exit;
 }
 
+# We couldn't do this check earlier as we first had to validate bug IDs
+# and display the mid-air collision page if delta_ts changed.
+# If we do a mass-change, we use session tokens.
+my $token = $cgi->param('token');
+
+if ($cgi->param('id')) {
+    check_hash_token($token, [$first_bug->id, $first_bug->delta_ts]);
+}
+else {
+    check_token_data($token, 'buglist_mass_change', 'query.cgi');
+}
+
+######################################################################
+# End Data/Security Validation
+######################################################################
+
 $vars->{'title_tag'} = "bug_processed";
 
 # Set up the vars for navigational <link> elements
index da551d0d7e3fe646a3c5b3c7dae81e316f485146..521d2d157bf4a19b2f3c0701fe98dfacc1aa7682 100644 (file)
@@ -20,6 +20,8 @@
   # token_action: the action the token was supposed to serve.
   # expected_action: the action the user was going to do.
   # script_name: the script generating this warning.
+  # alternate_script: the suggested script to redirect the user to
+  #                   if he declines submission.
   #%]
 
 [% PROCESS "global/field-descs.none.tmpl" %]
@@ -89,8 +91,8 @@
                exclude="^(Bugzilla_login|Bugzilla_password)$" %]
     <input type="submit" id="confirm" value="Confirm Changes">
   </form>
-  <p>Or throw away these changes and go back to <a href="[% script_name FILTER html %]">
-    [%- script_name FILTER html %]</a>.</p>
+  <p>Or throw away these changes and go back to <a href="[% alternate_script FILTER html %]">
+    [%- alternate_script FILTER html %]</a>.</p>
 [% END %]
 
 [% PROCESS global/footer.html.tmpl %]
index 97a2bd54fc682575a65982ec7d260f4a661a0393..80c5745fcef6a103313227ea5d12c5af74f0e24d 100644 (file)
   <input type="hidden" name="delta_ts" value="[% bug.delta_ts %]">
   <input type="hidden" name="longdesclength" value="[% bug.longdescs.size %]">
   <input type="hidden" name="id" value="[% bug.bug_id %]">
+  <input type="hidden" name="token" value="[% issue_hash_token([bug.id, bug.delta_ts]) FILTER html %]">
 
   [% PROCESS section_title %]
   <table>
index 6a62a80dccc8526ab033992a0f6f2fd22f3b541e..46130ef6bf18b553aed9e18af231d11aba41a674 100644 (file)
@@ -25,6 +25,7 @@
 
 [% dontchange = "--do_not_change--" %]
 <input type="hidden" name="dontchange" value="[% dontchange FILTER html %]">
+<input type="hidden" name="token" value="[% token FILTER html %]">
 
 <script type="text/javascript">
   var numelements = document.forms.changeform.elements.length;