summaryrefslogtreecommitdiffstats
path: root/Bugzilla
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 /Bugzilla
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
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Install/Localconfig.pm13
-rw-r--r--Bugzilla/Template.pm4
-rw-r--r--Bugzilla/Token.pm54
3 files changed, 69 insertions, 2 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();