Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optionally allow signed cookies, escape token injected into html #14

Merged
merged 1 commit into from
Oct 8, 2014
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 40 additions & 0 deletions lib/Plack/Middleware/XSRFBlock.pm
Expand Up @@ -5,6 +5,7 @@ use warnings;
use parent 'Plack::Middleware';

use Digest::HMAC_SHA1 'hmac_sha1_hex';
use HTML::Escape qw(escape_html);
use HTML::Parser;
use HTTP::Status qw(:constants);

Expand All @@ -22,6 +23,7 @@ use Plack::Util::Accessor qw(
token_per_request
parameter_name
header_name
secret
_token_generator
);

Expand Down Expand Up @@ -53,6 +55,13 @@ sub prepare_app {
my $data = rand() . $$ . {} . time;
my $key = "@INC";
my $digest = hmac_sha1_hex($data, $key);

if (defined $self->secret) {
my $sig = hmac_sha1_hex($digest, $self->secret);
$digest .= "--$sig";
}

return $digest;
});
}

Expand Down Expand Up @@ -105,6 +114,10 @@ sub call {
# reject if the form value and the token don't match
return $self->xsrf_detected( { env => $env, msg => 'invalid token' } )
if $val ne $cookie_value;

return $self->xsrf_detected( { env => $env,
msg => 'invalid signature' } )
if $self->invalid_signature($val);
}

return Plack::Util::response_cb($self->app->($env), sub {
Expand Down Expand Up @@ -144,6 +157,9 @@ sub call {

return $res unless $self->inject_form_input;

# escape token (someone might have tampered with the cookie)
$token = escape_html($token);

# let's inject our field+token into the form
my @out;
my $http_host = $request->uri->host;
Expand Down Expand Up @@ -241,6 +257,20 @@ sub call {
});
}

sub invalid_signature {
my ($self, $value) = @_;

# we dont use signed cookies
return if !defined $self->secret;

# cookie isn't signed
my ($token, $signature) = split /--/, $value;
return 1 if !defined $signature || $signature eq '';

# signature doesn't validate
return hmac_sha1_hex($token, $self->secret) ne $signature;
}

sub xsrf_detected {
my $self = shift;
my $args = shift;
Expand Down Expand Up @@ -317,6 +347,7 @@ You may also over-ride any, or all of these values:
meta_tag => undef,
inject_form_input => 1,
header_name => undef,
secret => undef,
blocked => sub {
return [ $status, $headers, $body ]
},
Expand Down Expand Up @@ -385,6 +416,10 @@ If this is set, use the value as the name of the response heaer that the token
can be sent in. This is useful for non-browser based submissions; e.g.
Javascript AJAX requests.

=item secret (default: undef)

Signs the cookie with supplied secret (if set).

=item blocked (default: undef)

If this is set it should be a PSGI application that is returned instead of the
Expand Down Expand Up @@ -422,6 +457,11 @@ PSGI-XSRF-Token]
The cookie token and form value were both submitted correctly but the values
do not match.

=item invalid signature

The cookies signature is invalid, indicating it was tampered with on the way
to the browser.

=back

=head1 EXPLANATION
Expand Down