]> git.sesse.net Git - itkacl/blobdiff - itkacl-web-1.0/include/itkaclcommon.pm
Protect the web interface against CSRF, and the CSRF token against BREACH.
[itkacl] / itkacl-web-1.0 / include / itkaclcommon.pm
index 07ba68248c5d9d7a3da582fa0584c5bcccf13257..cd86a3b5ddb6bee1eca2a819cb6b9a9f8411095f 100644 (file)
@@ -6,6 +6,7 @@ use DBI;
 use Apache::Session::Postgres;
 use Encode;
 use HTML::Entities;
+use Digest::HMAC_SHA1;
 use locale;
 use utf8;
 
@@ -17,6 +18,8 @@ our $cgi;
 our $dbh;
 our $last_modified = '$Date: 2011-11-19 11:08:01 $';
 our %session;
+our $csrf_token;
+our $masked_csrf_token;
 
 sub init {
        $cgi = new CGI;
@@ -26,6 +29,46 @@ sub init {
        $dbh->{pg_enable_utf8} = 1;
        $last_modified = '$Date: 2011-11-19 11:08:01 $';
        %session = ();
+
+       if (defined($ENV{'REMOTE_USER'})) {
+               $csrf_token = Digest::HMAC_SHA1::hmac_sha1($ENV{'REMOTE_USER'}, $itkaclcommon::hmac_key);
+
+               # Mask the CSRF token to avoid the BREACH attack.
+               my @hmac_bytes = map { ord($_) } (split //, $csrf_token);
+               my @random_bytes = map { int(rand(256)) } (0..$#hmac_bytes);
+               my @masked_bytes = map { $hmac_bytes[$_] ^ $random_bytes[$_] } (0..$#hmac_bytes);
+               
+               my $random_bytes_string = join('', map { chr($_) } (@random_bytes));
+               my $masked_bytes_string = join('', map { chr($_) } (@masked_bytes));
+               $masked_csrf_token = unpack('H*', $random_bytes_string) . '_' . unpack("H*", $masked_bytes_string);
+       } else {
+               $csrf_token = '';
+               $masked_csrf_token = '';
+       }
+}
+
+sub check_csrf_token {
+       if ($csrf_token eq '') {
+               # Not logged in, so always fine.
+               return 1;
+       }
+       
+       my $candidate_csrf_token = $cgi->param('csrftoken');
+       if ($candidate_csrf_token !~ /^([0-9a-f]+)_([0-9a-f]+)$/) {
+               die "Invalid CSRF token!";
+       }
+
+       my ($random_bytes_string, $masked_bytes_string) = ($1, $2);
+       if (length($random_bytes_string) != length($masked_bytes_string)) {
+               die "Length mismatch in CSRF token!";
+       }
+
+       my @random_bytes = map { ord($_) } (split //, pack('H*', $random_bytes_string));
+       my @masked_bytes = map { ord($_) } (split //, pack('H*', $masked_bytes_string));
+       my @hmac_bytes = map { $masked_bytes[$_] ^ $random_bytes[$_] } (0..$#masked_bytes);
+       my $hmac_string = join('', map { chr($_) } (@hmac_bytes));
+
+       die "CSRF token mismatch!" if ($hmac_string ne $csrf_token);
 }
 
 sub print_header {