WWW-CSRF-1.00/ 0000755 0001750 0001750 00000000000 12244131160 011671 5 ustar sesse sesse WWW-CSRF-1.00/t/ 0000755 0001750 0001750 00000000000 12244131160 012134 5 ustar sesse sesse WWW-CSRF-1.00/t/02_check.t 0000644 0001750 0001750 00000004601 12244130345 013704 0 ustar sesse sesse use Test::More tests => 7;
use WWW::CSRF qw(check_csrf_token);
is(check_csrf_token("id", "secret",
"5df5e9f17c929a45af5d33624ec052903599958f," .
"112233445566778899aabbccddeeff0011223344," .
"1234567890",
{ MaxAge => -1 }),
WWW::CSRF::CSRF_OK,
"check simple token");
is(check_csrf_token("id", "secret",
"0000000000000000000000000000000000000000," .
"112233445566778899aabbccddeeff0011223344," .
"1234567890",
{ MaxAge => -1 }),
WWW::CSRF::CSRF_INVALID_SIGNATURE,
"check simple invalid token");
is(check_csrf_token("id", "secret",
"5df5e9f17c929a45af5d33624ec052903599958f," .
"112233445566778899aabbccddeeff0011223344"),
WWW::CSRF::CSRF_MALFORMED_TOKEN,
"check simple malformed token (missing time)");
is(check_csrf_token("id", "secret",
"5df5e9f17c929a45af5d33624ec052903599958f," .
"112233445566778899aabbccddeeff0011223344," .
"1234567890", {
Time => 1234567895,
MaxAge => 10
}),
WWW::CSRF::CSRF_OK,
"check with maxage");
is(check_csrf_token("id", "secret",
"5df5e9f17c929a45af5d33624ec052903599958f," .
"112233445566778899aabbccddeeff0011223344," .
"1234567890", {
Time => 1234567895,
MaxAge => 3
}),
WWW::CSRF::CSRF_EXPIRED,
"check expired with maxage");
is(check_csrf_token("id", "secret",
"5df5e9f17c929000000000000000000000000000," .
"112233445566778899aabbccddeeff0011223344," .
"1234567890", {
Time => 1234567895,
MaxAge => 3
}),
WWW::CSRF::CSRF_INVALID_SIGNATURE,
"expired is not given for an invalid signature");
is(check_csrf_token("id", "secret",
"5df5e9f17c929a45af5d33624ec052903599958f," .
"112233445566778899aabbccddeeff0011223344," .
"1234567894", {
Time => 1234567895,
MaxAge => 10
}),
WWW::CSRF::CSRF_INVALID_SIGNATURE,
"check falsified timestamp");
WWW-CSRF-1.00/t/00_use.t 0000644 0001750 0001750 00000000061 12240474342 013421 0 ustar sesse sesse use Test::More tests => 1;
use_ok('WWW::CSRF');
WWW-CSRF-1.00/t/01_generate.t 0000644 0001750 0001750 00000002644 12240514203 014420 0 ustar sesse sesse use Test::More tests => 6;
use WWW::CSRF qw(generate_csrf_token);
my $random = pack('H*', '112233445566778899aabbccddeeff0011223344');
like(generate_csrf_token("id", "secret"),
qr/^[0-9a-f]{40},[0-9a-f]{40},\d+$/,
"token has right format");
is(generate_csrf_token("id", "secret", { Random => $random, Time => 1234567890 }),
"5df5e9f17c929a45af5d33624ec052903599958f,112233445566778899aabbccddeeff0011223344,1234567890",
"generate simple token");
is(generate_csrf_token("id", "s3cret", { Random => $random, Time => 1234567890 }),
"0acb0abac254d21ce30c2e805a1bf6762e0b6a17,112233445566778899aabbccddeeff0011223344,1234567890",
"different secret changes token");
is(generate_csrf_token("id", "s3cret", { Random => $random, Time => 1234567891 }),
"8e5c2d1cd2dc0368ed2fa1facee31660a5ffa12f,112233445566778899aabbccddeeff0011223344,1234567891",
"different time changes token");
$random = pack('H*', '112233445566778899aabbccddeeff0011223340');
is(generate_csrf_token("id", "secret", { Random => $random, Time => 1234567890 }),
"5df5e9f17c929a45af5d33624ec052903599958b,112233445566778899aabbccddeeff0011223340,1234567890",
"bitflip in mask flips corresponding bit in token");
$random = pack('H*', '112233445566778899aabbccddeeff00112233');
eval {
my $ignored = generate_csrf_token("id", "secret", { Random => $random, Time => 1234567890 });
};
ok($@, "check that wrong amount of randomness causes die()");
WWW-CSRF-1.00/lib/ 0000755 0001750 0001750 00000000000 12244131160 012437 5 ustar sesse sesse WWW-CSRF-1.00/lib/WWW/ 0000755 0001750 0001750 00000000000 12244131160 013123 5 ustar sesse sesse WWW-CSRF-1.00/lib/WWW/CSRF.pm 0000644 0001750 0001750 00000020057 12244131136 014225 0 ustar sesse sesse package WWW::CSRF;
=pod
=head1 NAME
WWW::CSRF - Generate and check tokens to protect against CSRF attacks
=head1 SYNOPSIS
use WWW::CSRF qw(generate_csrf_token check_csrf_token CSRF_OK);
Generate a token to add as a hidden in all HTML forms:
my $csrf_token = generate_csrf_token($username, "s3kr1t");
Then, in any action with side effects, retrieve that form field
and check it with:
my $status = check_csrf_token($username, "s3kr1t", $csrf_token);
die "Wrong CSRF token" unless ($status == CSRF_OK);
=head1 COPYRIGHT
Copyright 2013 Steinar H. Gunderson.
This library is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
=head1 DESCRIPTION
This module generates tokens to help protect against a website
attack known as Cross-Site Request Forgery (CSRF, also known
as XSRF). CSRF is an attack where an attacker fools a browser into
make a request to a web server for which that browser will
automatically include some form of credentials (cookies, cached
HTTP Basic authentication, etc.), thus abusing the web server's
trust in the user for malicious use.
The most common CSRF mitigation is sending a special, hard-to-guess
token with every request, and then require that any request that
is not idempotent (i.e., has side effects) must be accompanied
with such a token. This mitigation depends critically on the fact
that while an attacker can easily make the victim's browser
I a request, the browser security model (same-origin policy,
or SOP for short) prevents third-party sites from reading the
I of that request.
CSRF tokens should have at least the following properties:
=over
=item *
They should be hard-to-guess, so they should be signed
with some key known only to the server.
=item *
They should be dependent on the authenticated identity,
so that one user cannot use its own tokens to impersonate
another user.
=item *
They should not be the same for every request, or an
attack known as BREACH can use HTTP compression
to gradually deduce more and more of the token.
=item *
They should contain an (authenticated) timestamp, so
that if an attacker manages to learn one token, he or she
cannot impersonate a user indefinitely.
=back
WWW::CSRF simplifies the (simple, but tedious) work of creating and verifying
such tokens.
Note that resources that are protected against CSRF should also be protected
against a different attack known as clickjacking. There are many defenses
against clickjacking (which ideally should be combined), but a good start is
sending a C HTTP header set to C or C.
See the L
for more information.
This module provides the following functions:
=over 4
=cut
use strict;
use warnings;
use Bytes::Random::Secure;
use Digest::HMAC_SHA1;
use constant {
CSRF_OK => 0,
CSRF_EXPIRED => 1,
CSRF_INVALID_SIGNATURE => 2,
CSRF_MALFORMED_TOKEN => 3,
};
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(generate_csrf_token check_csrf_token CSRF_OK CSRF_MALFORMED_TOKEN CSRF_INVALID_SIGNATURE CSRF_EXPIRED);
our $VERSION = '1.00';
=item generate_csrf_token($id, $secret, \%options)
This routine generates a CSRF token to send out to already authenticated users.
(Unauthenticated users generally need no CSRF protection, as there are no
credentials to impersonate.)
$id is the identity you wish to authenticate; usually, this would be a user name
of some sort.
$secret is the secret key authenticating the token. This should be protected in
the same matter you would protect other server-side secrets, e.g. database
passwords--if this leaks out, an attacker can generate CSRF tokens at will.
The keys in %options are relatively esoteric and need generally not be set,
but currently supported are:
=over
=item *
C