WWW-CSRF-1.00/0000755000175000017500000000000012244131160011671 5ustar sessesesseWWW-CSRF-1.00/t/0000755000175000017500000000000012244131160012134 5ustar sessesesseWWW-CSRF-1.00/t/02_check.t0000644000175000017500000000460112244130345013704 0ustar sessesesseuse 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.t0000644000175000017500000000006112240474342013421 0ustar sessesesseuse Test::More tests => 1; use_ok('WWW::CSRF'); WWW-CSRF-1.00/t/01_generate.t0000644000175000017500000000264412240514203014420 0ustar sessesesseuse 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/0000755000175000017500000000000012244131160012437 5ustar sessesesseWWW-CSRF-1.00/lib/WWW/0000755000175000017500000000000012244131160013123 5ustar sessesesseWWW-CSRF-1.00/lib/WWW/CSRF.pm0000644000175000017500000002005712244131136014225 0ustar sessesessepackage 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