summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorlpsolit%gmail.com <>2009-02-02 19:33:29 +0100
committerlpsolit%gmail.com <>2009-02-02 19:33:29 +0100
commitdc51769c9f7fb84ac2e43112f2d106a4770f5781 (patch)
tree2e33c5042d7608871c661a843c3c991da07693d7
parent8d70890dc0b7c24b25a344808ac4e63e6a5dd74e (diff)
downloadbugzilla-dc51769c9f7fb84ac2e43112f2d106a4770f5781.tar.gz
bugzilla-dc51769c9f7fb84ac2e43112f2d106a4770f5781.tar.xz
Bug 26257: [SECURITY] Bugzilla should prevent malicious webpages from making bugzilla users submit changes to bugs - Patch by Frédéric Buclin <LpSolit@gmail.com> r=mkanat a=LpSolit
-rw-r--r--Bugzilla/Install/Localconfig.pm13
-rw-r--r--Bugzilla/Template.pm4
-rw-r--r--Bugzilla/Token.pm54
-rwxr-xr-xbuglist.cgi2
-rw-r--r--email_in.pl2
-rwxr-xr-xprocess_bug.cgi23
-rw-r--r--template/en/default/admin/confirm-action.html.tmpl6
-rw-r--r--template/en/default/bug/edit.html.tmpl1
-rw-r--r--template/en/default/list/edit-multiple.html.tmpl1
9 files changed, 98 insertions, 8 deletions
diff --git a/Bugzilla/Install/Localconfig.pm b/Bugzilla/Install/Localconfig.pm
index 45005f032..654b44b9f 100644
--- a/Bugzilla/Install/Localconfig.pm
+++ b/Bugzilla/Install/Localconfig.pm
@@ -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);
@@ -185,6 +186,18 @@ EOT
# 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
+ },
);
sub read_localconfig {
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index 688c53386..8c34bb493 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -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 =>
diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm
index 313d43212..f87490db1 100644
--- a/Bugzilla/Token.pm
+++ b/Bugzilla/Token.pm
@@ -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();
diff --git a/buglist.cgi b/buglist.cgi
index d51112a5c..f5284439c 100755
--- a/buglist.cgi
+++ b/buglist.cgi
@@ -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');
diff --git a/email_in.pl b/email_in.pl
index bed5a1477..1edce55d8 100644
--- a/email_in.pl
+++ b/email_in.pl
@@ -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';
}
diff --git a/process_bug.cgi b/process_bug.cgi
index a0aadc1c5..83041230b 100755
--- a/process_bug.cgi
+++ b/process_bug.cgi
@@ -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
diff --git a/template/en/default/admin/confirm-action.html.tmpl b/template/en/default/admin/confirm-action.html.tmpl
index da551d0d7..521d2d157 100644
--- a/template/en/default/admin/confirm-action.html.tmpl
+++ b/template/en/default/admin/confirm-action.html.tmpl
@@ -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 %]
diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl
index 97a2bd54f..80c5745fc 100644
--- a/template/en/default/bug/edit.html.tmpl
+++ b/template/en/default/bug/edit.html.tmpl
@@ -144,6 +144,7 @@
<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>
diff --git a/template/en/default/list/edit-multiple.html.tmpl b/template/en/default/list/edit-multiple.html.tmpl
index 6a62a80dc..46130ef6b 100644
--- a/template/en/default/list/edit-multiple.html.tmpl
+++ b/template/en/default/list/edit-multiple.html.tmpl
@@ -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;