diff options
author | Yorhel <git@yorhel.nl> | 2010-11-06 16:46:01 +0100 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2010-11-06 16:46:01 +0100 |
commit | e625403d6108b3f95361ece3c4311dae88747107 (patch) | |
tree | 0f456df20316562333d4ae76ce1a02b703279747 /lib | |
parent | 09307455ced2b60ea2abb161fc59f8efdafefdfa (diff) |
Fixed cross-site request forgery vulnerabilities
Diffstat (limited to 'lib')
-rw-r--r-- | lib/VNDB/Handler/Discussions.pm | 3 | ||||
-rw-r--r-- | lib/VNDB/Handler/Producers.pm | 1 | ||||
-rw-r--r-- | lib/VNDB/Handler/Releases.pm | 3 | ||||
-rw-r--r-- | lib/VNDB/Handler/Tags.pm | 2 | ||||
-rw-r--r-- | lib/VNDB/Handler/ULists.pm | 17 | ||||
-rw-r--r-- | lib/VNDB/Handler/Users.pm | 17 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNEdit.pm | 1 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNPage.pm | 5 | ||||
-rw-r--r-- | lib/VNDB/Util/Auth.pm | 56 | ||||
-rw-r--r-- | lib/VNDB/Util/FormHTML.pm | 8 |
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 |