summaryrefslogtreecommitdiff
path: root/lib/VNDB
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2010-11-06 16:46:01 +0100
committerYorhel <git@yorhel.nl>2010-11-06 16:46:01 +0100
commite625403d6108b3f95361ece3c4311dae88747107 (patch)
tree0f456df20316562333d4ae76ce1a02b703279747 /lib/VNDB
parent09307455ced2b60ea2abb161fc59f8efdafefdfa (diff)
Fixed cross-site request forgery vulnerabilities
Diffstat (limited to 'lib/VNDB')
-rw-r--r--lib/VNDB/Handler/Discussions.pm3
-rw-r--r--lib/VNDB/Handler/Producers.pm1
-rw-r--r--lib/VNDB/Handler/Releases.pm3
-rw-r--r--lib/VNDB/Handler/Tags.pm2
-rw-r--r--lib/VNDB/Handler/ULists.pm17
-rw-r--r--lib/VNDB/Handler/Users.pm17
-rw-r--r--lib/VNDB/Handler/VNEdit.pm1
-rw-r--r--lib/VNDB/Handler/VNPage.pm5
-rw-r--r--lib/VNDB/Util/Auth.pm56
-rw-r--r--lib/VNDB/Util/FormHTML.pm8
10 files changed, 97 insertions, 16 deletions
diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm
index 9cc83fc7..ef440cde 100644
--- a/lib/VNDB/Handler/Discussions.pm
+++ b/lib/VNDB/Handler/Discussions.pm
@@ -155,6 +155,7 @@ sub edit {
# check form etc...
my $frm;
if($self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
$frm = $self->formValidate(
!$tid || $num == 1 ? (
{ name => 'title', maxlength => 50 },
@@ -235,10 +236,10 @@ sub edit {
$frm->{nolastmod} = 1 if $num && $self->authCan('boardmod') && !exists $frm->{nolastmod};
# generate html
+ my $url = !$tid ? "/t/$board/new" : !$num ? "/t$tid/reply" : "/t$tid.$num/edit";
my $title = mt !$tid ? '_postedit_newthread' :
!$num ? ('_postedit_replyto', $t->{title}) :
'_postedit_edit';
- my $url = !$tid ? "/t/$board/new" : !$num ? "/t$tid/reply" : "/t$tid.$num/edit";
$self->htmlHeader(title => $title, noindex => 1);
$self->htmlForm({ frm => $frm, action => $url }, 'postedit' => [$title,
[ static => label => mt('_postedit_form_username'), content => $self->{l10n}->userstr($self->authInfo->{id}, $self->authInfo->{username}) ],
diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm
index bc68b88c..e6ccdffb 100644
--- a/lib/VNDB/Handler/Producers.pm
+++ b/lib/VNDB/Handler/Producers.pm
@@ -162,6 +162,7 @@ sub edit {
my $frm;
if($self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
$frm = $self->formValidate(
{ name => 'type', enum => $self->{producer_types} },
{ name => 'name', maxlength => 200 },
diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm
index 840b29f0..f9c600bd 100644
--- a/lib/VNDB/Handler/Releases.pm
+++ b/lib/VNDB/Handler/Releases.pm
@@ -239,7 +239,7 @@ sub _infotable {
Tr ++$i % 2 ? (class => 'odd') : ();
td mt '_relinfo_user';
td;
- Select id => 'listsel', name => 'listsel';
+ Select id => 'listsel', name => $self->authGetCode("/r$r->{id}/list");
option mt !$rl ? '_relinfo_user_notlist' :
('_relinfo_user_inlist', mt('_rlst_rstat_'.$rl->{rstat}), mt('_rlst_vstat_'.$rl->{vstat}));
optgroup label => mt '_relinfo_user_setr';
@@ -300,6 +300,7 @@ sub edit {
my $frm;
if($self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
$frm = $self->formValidate(
{ name => 'type', enum => $self->{release_types} },
{ name => 'patch', required => 0, default => 0 },
diff --git a/lib/VNDB/Handler/Tags.pm b/lib/VNDB/Handler/Tags.pm
index a8558575..d2863461 100644
--- a/lib/VNDB/Handler/Tags.pm
+++ b/lib/VNDB/Handler/Tags.pm
@@ -186,6 +186,7 @@ sub tagedit {
return 404 if $tag && !$t;
if($self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
$frm = $self->formValidate(
{ name => 'name', required => 1, maxlength => 250, regex => [ qr/^[^,]+$/, 'A comma is not allowed in tag names' ] },
{ name => 'state', required => 0, default => 0, enum => [ 0..2 ] },
@@ -357,6 +358,7 @@ sub vntagmod {
return $self->htmlDenied if !$self->authCan('tag');
if($self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
my $frm = $self->formValidate(
{ name => 'taglinks', required => 0, default => '', maxlength => 10240, regex => [ qr/^[1-9][0-9]*,-?[1-3],-?[0-2]( [1-9][0-9]*,-?[1-3],-?[0-2])*$/, 'meh' ] }
);
diff --git a/lib/VNDB/Handler/ULists.pm b/lib/VNDB/Handler/ULists.pm
index 5abebf37..a610a14a 100644
--- a/lib/VNDB/Handler/ULists.pm
+++ b/lib/VNDB/Handler/ULists.pm
@@ -23,6 +23,7 @@ sub vnvote {
my $uid = $self->authInfo->{id};
return $self->htmlDenied() if !$uid;
+ return if !$self->authCheckCode;
my $f = $self->formValidate(
{ name => 'v', enum => [ -1, 1..10 ] }
);
@@ -41,6 +42,7 @@ sub vnwish {
my $uid = $self->authInfo->{id};
return $self->htmlDenied() if !$uid;
+ return if !$self->authCheckCode;
my $f = $self->formValidate(
{ name => 's', enum => [ -1, @{$self->{wishlist_status}} ] }
);
@@ -68,6 +70,7 @@ sub rlist {
my $uid = $self->authInfo->{id};
return $self->htmlDenied() if !$uid;
+ return if !$self->authCheckCode;
my $f = $self->formValidate(
{ name => 'e', required => 1, enum => [ 'del', map("r$_", @{$self->{rlst_rstat}}), map("v$_", @{$self->{rlst_vstat}}) ] },
);
@@ -110,6 +113,7 @@ sub wishlist {
return 404 if $f->{_err};
if($own && $self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
my $frm = $self->formValidate(
{ name => 'sel', required => 0, default => 0, multi => 1, template => 'int' },
{ name => 'batchedit', required => 1, enum => [ -1, @{$self->{wishlist_status}} ] },
@@ -146,8 +150,10 @@ sub wishlist {
end;
end;
- form action => "/u$uid/wish?f=$f->{f};o=$f->{o};s=$f->{s};p=$f->{p}", method => 'post'
- if $own;
+ if($own) {
+ my $code = $self->authGetCode("/u$uid/wish");
+ form action => "/u$uid/wish?formcode=$code;f=$f->{f};o=$f->{o};s=$f->{s};p=$f->{p}", method => 'post';
+ }
$self->htmlBrowse(
class => 'wishlist',
@@ -210,6 +216,7 @@ sub vnlist {
return 404 if $f->{_err};
if($own && $self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
my $frm = $self->formValidate(
{ name => 'sel', required => 0, default => 0, multi => 1, template => 'int' },
{ name => 'batchedit', required => 1, enum => [ 'del', map("r$_", @{$self->{rlst_rstat}}), map("v$_", @{$self->{rlst_vstat}}) ] },
@@ -266,14 +273,14 @@ sub vnlist {
end;
end;
- _vnlist_browse($self, $own, $list, $np, $f, $url);
+ _vnlist_browse($self, $own, $list, $np, $f, $url, $uid);
$self->htmlFooter;
}
sub _vnlist_browse {
- my($self, $own, $list, $np, $f, $url) = @_;
+ my($self, $own, $list, $np, $f, $url, $uid) = @_;
- form action => $url->(), method => 'post'
+ form action => $url->().';formcode='.$self->authGetCode("/u$uid/list"), method => 'post'
if $own;
$self->htmlBrowse(
diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm
index 05be95d4..7812606f 100644
--- a/lib/VNDB/Handler/Users.pm
+++ b/lib/VNDB/Handler/Users.pm
@@ -166,6 +166,7 @@ sub newpass {
my($frm, $u);
if($self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
$frm = $self->formValidate(
{ name => 'mail', required => 1, template => 'mail' },
);
@@ -219,10 +220,11 @@ sub newpass_sent {
sub register {
my $self = shift;
- #return $self->resRedirect('/') if $self->authInfo->{id};
+ return $self->resRedirect('/') if $self->authInfo->{id};
my $frm;
if($self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
$frm = $self->formValidate(
{ name => 'usrname', template => 'pname', minlength => 2, maxlength => 15 },
{ name => 'mail', template => 'mail' },
@@ -275,6 +277,7 @@ sub edit {
# check POST data
my $frm;
if($self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
$frm = $self->formValidate(
$self->authCan('usermod') ? (
{ name => 'usrname', template => 'pname', minlength => 2, maxlength => 15 },
@@ -417,6 +420,7 @@ sub delete {
# confirm
if(!$act) {
+ my $code = $self->authGetCode("/u$uid/del/o");
my $u = $self->dbUserGet(uid => $uid)->[0];
return 404 if !$u->{id};
$self->htmlHeader(title => 'Delete user', noindex => 1);
@@ -426,7 +430,7 @@ sub delete {
h2 'Delete user';
p;
lit qq|Are you sure you want to remove <a href="/u$uid">$u->{username}</a>'s account?<br /><br />|
- .qq|<a href="/u$uid/del/o">Yes, I'm not kidding!</a>|;
+ .qq|<a href="/u$uid/del/o?formcode=$code">Yes, I'm not kidding!</a>|;
end;
end;
end;
@@ -434,6 +438,7 @@ sub delete {
}
# delete
elsif($act eq '/o') {
+ return if !$self->authCheckCode;
$self->dbUserDel($uid);
$self->resRedirect("/u$uid/del/d", 'post');
}
@@ -536,6 +541,7 @@ sub notifies {
# changing the notification settings
my $saved;
if($self->reqMethod() eq 'POST' && $self->reqParam('set')) {
+ return if !$self->authCheckCode;
my $frm = $self->formValidate(
{ name => 'notify_dbedit', required => 0 },
{ name => 'notify_announce', required => 0 }
@@ -550,6 +556,7 @@ sub notifies {
# updating notifications
} elsif($self->reqMethod() eq 'POST') {
+ return if !$self->authCheckCode;
my $frm = $self->formValidate(
{ name => 'notifysel', multi => 1, required => 0, template => 'int' },
{ name => 'markread', required => 0 },
@@ -581,8 +588,10 @@ sub notifies {
p mt '_usern_nonotifies' if !@$list;
end;
+ my $code = $self->authGetCode("/u$uid/notifies");
+
if(@$list) {
- form action => "/u$uid/notifies?r=$f->{r}", method => 'post';
+ form action => "/u$uid/notifies?r=$f->{r};formcode=$code", method => 'post';
$self->htmlBrowse(
items => $list,
options => $f,
@@ -628,7 +637,7 @@ sub notifies {
end;
}
- form method => 'post', action => "/u$uid/notifies";
+ form method => 'post', action => "/u$uid/notifies?formcode=$code";
div class => 'mainbox';
h1 mt '_usern_set_title';
div class => 'notice', mt '_usern_set_saved' if $saved;
diff --git a/lib/VNDB/Handler/VNEdit.pm b/lib/VNDB/Handler/VNEdit.pm
index 44c5ab08..bfc37e02 100644
--- a/lib/VNDB/Handler/VNEdit.pm
+++ b/lib/VNDB/Handler/VNEdit.pm
@@ -36,6 +36,7 @@ sub edit {
my $frm;
if($self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
$frm = $self->formValidate(
{ name => 'title', maxlength => 250 },
{ name => 'original', required => 0, maxlength => 250, default => '' },
diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm
index b6b39483..0dff5627 100644
--- a/lib/VNDB/Handler/VNPage.pm
+++ b/lib/VNDB/Handler/VNPage.pm
@@ -347,7 +347,7 @@ sub _useroptions {
td mt '_vnpage_uopt';
td;
if($vote || !$wish) {
- Select id => 'votesel';
+ Select id => 'votesel', name => $self->authGetCode("/v$v->{id}/vote");
option $vote ? mt '_vnpage_uopt_voted', $vote->{vote} : mt '_vnpage_uopt_novote';
optgroup label => $vote ? mt '_vnpage_uopt_changevote' : mt '_vnpage_uopt_dovote';
option value => $_, "$_ (".mt("_vote_$_").')' for (reverse 1..10);
@@ -357,7 +357,7 @@ sub _useroptions {
br;
}
if(!$vote || $wish) {
- Select id => 'wishsel';
+ Select id => 'wishsel', name => $self->authGetCode("/v$v->{id}/wish");
option $wish ? mt '_vnpage_uopt_wishlisted', mt '_wish_'.$wish->{wstat} : mt '_vnpage_uopt_nowish';
optgroup label => $wish ? mt '_vnpage_uopt_changewish' : mt '_vnpage_uopt_addwish';
option value => $_, mt "_wish_$_" for (@{$self->{wishlist_status}});
@@ -387,6 +387,7 @@ sub _releases {
for my $i (@$l) {
[grep $i->{rid} == $_->{id}, @$r]->[0]{ulist} = $i;
}
+ div id => 'vnrlist_code', class => 'hidden', $self->authGetCode('/xml/rlist.xml');
}
my %lang;
diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm
index 45b39249..24e316ce 100644
--- a/lib/VNDB/Util/Auth.pm
+++ b/lib/VNDB/Util/Auth.pm
@@ -10,9 +10,11 @@ use Digest::SHA qw|sha1_hex sha256_hex|;
use Time::HiRes;
use Encode 'encode_utf8';
use POSIX 'strftime';
+use YAWF ':html';
+use VNDB::Func;
-our @EXPORT = qw| authInit authLogin authLogout authInfo authCan authPreparePass |;
+our @EXPORT = qw| authInit authLogin authLogout authInfo authCan authPreparePass authGetCode authCheckCode |;
# initializes authentication information and checks the vndb_auth cookie
@@ -142,5 +144,57 @@ sub _rmcookie {
}
+# Generate a code to be used later on to validate that the form was indeed
+# submitted from our site and by the same user/visitor. Not limited to
+# logged-in users.
+# Arguments:
+# form-id (string, can be empty, but makes the validation stronger)
+# time (optional, time() to encode in the code)
+sub authGetCode {
+ my $self = shift;
+ my $id = shift;
+ my $time = (shift || time)/3600; # accuracy of an hour
+ my $uid = pack('N', $self->{_auth} ? $self->{_auth}{id} : 0);
+ return lc substr sha1_hex($self->{form_salt} . $uid . encode_utf8($id||'') . pack('N', int $time)), 0, 16;
+}
+
+
+# Validates the correctness of the returned code, creates an error page and
+# returns false if it's invalid, returns true otherwise. Codes are valid for at
+# least two and at most three hours.
+# Arguments:
+# [ form-id, [ code ] ]
+# If the code is not given, uses the 'formcode' form parameter instead. If
+# form-id is not given, the path of the current requests is used.
+sub authCheckCode {
+ my $self = shift;
+ my $id = shift || '/'.$self->reqPath();
+ my $code = shift || $self->reqParam('formcode');
+ return _incorrectcode($self) if !$code || $code !~ qr/^[0-9a-f]{16}$/;
+ my $time = time;
+ return 1 if $self->authGetCode($id, $time) eq $code;
+ return 1 if $self->authGetCode($id, $time-3600) eq $code;
+ return 1 if $self->authGetCode($id, $time-2*3600) eq $code;
+ return _incorrectcode($self);
+}
+
+
+sub _incorrectcode {
+ my $self = shift;
+ $self->resInit;
+ $self->htmlHeader(title => mt '_formcode_title', noindex => 1);
+
+ div class => 'mainbox';
+ h1 mt '_formcode_title';
+ div class => 'warning';
+ p mt '_formcode_msg';
+ end;
+ end;
+
+ $self->htmlFooter;
+ return 0;
+}
+
+
1;
diff --git a/lib/VNDB/Util/FormHTML.pm b/lib/VNDB/Util/FormHTML.pm
index cfac77b9..d619754a 100644
--- a/lib/VNDB/Util/FormHTML.pm
+++ b/lib/VNDB/Util/FormHTML.pm
@@ -166,18 +166,22 @@ sub htmlFormPart {
# Generates a form, first argument is a hashref with global options, keys:
# frm => the $frm as returned by formValidate,
-# action => The location the form should POST to
+# action => The location the form should POST to (also used as form id)
# upload => 1/0, adds an enctype.
# editsum => 1/0, adds an edit summary field before the submit button
# The other arguments are a list of subforms in the form
# of (subform-name => [form parts]). Each subform is shown as a
# (JavaScript-powered) tab, and has it's own 'mainbox'. This function
-# automatically calls htmlFormError
+# automatically calls htmlFormError and adds a 'formcode' field.
sub htmlForm {
my($self, $options, @subs) = @_;
form action => '/nospam?'.$options->{action}, method => 'post', 'accept-charset' => 'utf-8',
$options->{upload} ? (enctype => 'multipart/form-data') : ();
+ div class => 'hidden';
+ input type => 'hidden', name => 'formcode', value => $self->authGetCode($options->{action});
+ end;
+
$self->htmlFormError($options->{frm}, 1);
# tabs