I want to guard against multiple form submissions (reload, resubmit, etc.) and CSRF in my app: http://en.wikipedia.org/wiki/Cross-site_request_forgery http://www.perlmonks.org/?node_id=606832
I hacked up a simple plugin. Do you think it would be useful? or might something like this already exist? package CGI::Application::Plugin::Security::CSRF; use strict; use warnings; use base 'Exporter'; use CGI::Application; use CGI::Application::Plugin::Session; use Digest::SHA1 qw(sha1_base64); use vars qw($VERSION @EXPORT); @EXPORT = qw(csrf_insert_token csrf_attack); $VERSION = '1.0'; sub csrf_insert_token { my ($self, $token_name, $tmpl) = @_; if($self->query->param('csrf_token')) { # reuse the token that was submitted with the form; don't generate a new one } else { # generate a new token for this new form my $token = sha1_base64($self->session->param('_SESSION_ID') . time); # TBD: below, what if the app is not using HTML::Template ?? $tmpl->param(csrf_token => $token); $self->session->param($token_name => $token); } } sub csrf_attack { my ($self, $token_name) = @_; if( ($self->query->request_method ne 'POST') # ensure the form was POSTed || ($self->session->param($token_name) ne $self->query->param('csrf_token')) ) { return 1 } $self->session->clear($token_name); return 0 } 1; Example usage: sub edit_account_form : Runmode { my $self = shift; my $errs = shift; # created by edit_check() my $t = $self->load_tmpl('account_form.html'); ... $t->param(rm => 'edit_check'); $self->csrf_insert_token('edit_account_token', $t); <<------------- $t->param($errs) if $errs; $t->output } #------------------------------------------------------------------------------ sub edit_check : Runmode { my $self = shift; my $dfv_results = $self->check_rm('edit_account_form', my_dfv_edit_rules) || return $self->check_rm_error_page; if($self->csrf_attack('edit_acount_token')) { <<------------------ return $self->custom_error_page(msg => 'It seems you may have been performing a similar operation in multiple windows' .' (which is not supported for security reasons),' .' or you tried to submit or reload the same form twice.'); # or there was a real CSRF attack attempt, in which case no # one will ever see this error message } ... Each HTML form that uses this plugin will need the following hidden field: <input type="hidden" name="csrf_token" value="<tmpl_var token>" /> Current limitations: 1. csrf_insert_token() assumes/requires HTML::Template. Can a plugin determine if a different template system is in use? If so, the $tmpl argument could be optional, and if it is not present, the method could return the generated token. The app developer would then need to use whatever template system method is appropriated to add the token to the form. 2. tokens are stored in the session object. A token is removed on a successful form submission. They remain otherwise. So tokens could collect over time, but only if the user visited form pages and did not submit them successfully. For apps that have a lot of forms that use this plugin, this could be an issue. 3. As the error message implies in the edit_check() rm above, using a fixed token name like 'edit_account_token' will not allow a user to have two windows open to the same form and successfully submit both forms. Only the form that was opened last will succeed. For most apps, I would consider this a feature, not a bug (i.e., the "older" window becomes stale/unusable). But for certain apps, maybe this would be a problem. ##### CGI::Application community mailing list ################ ## ## ## To unsubscribe, or change your message delivery options, ## ## visit: http://www.erlbaum.net/mailman/listinfo/cgiapp ## ## ## ## Web archive: http://www.erlbaum.net/pipermail/cgiapp/ ## ## Wiki: http://cgiapp.erlbaum.net/ ## ## ## ################################################################