Protect the web interface against CSRF, and the CSRF token against BREACH.
authorSteinar H. Gunderson <sesse@samfundet.no>
Wed, 7 Aug 2013 20:40:02 +0000 (22:40 +0200)
committerSteinar H. Gunderson <sesse@samfundet.no>
Wed, 7 Aug 2013 20:40:02 +0000 (22:40 +0200)
itkacl-web-1.0/include/config.pm
itkacl-web-1.0/include/itkaclcommon.pm
itkacl-web-1.0/web/add.pl
itkacl-web-1.0/web/addnode.pl
itkacl-web-1.0/web/change-comment.pl
itkacl-web-1.0/web/delete.pl
itkacl-web-1.0/web/deletenode.pl
itkacl-web-1.0/web/itkacl.js
itkacl-web-1.0/web/view.pl

index 79229309b98498abdd6501b164b5a699e834c048..c471a004e77fa65980ed37fb050143c64b1e86b5 100644 (file)
@@ -28,6 +28,10 @@ our $sessiondb_name = "itkacl-sessions";
 our $sessiondb_user = "itkacl-web";
 our $sessiondb_pass = undef;
 
+# Pick any random, secret string. E.g., the output of:
+# dd if=/dev/urandom bs=32 count=1 | hexdump
+our $hmac_key = undef;
+
 # Local configuration overrides defaults.
 eval {
        require 'config.local.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 {
index 8a1ce016144d0715eb5cac0614425bcd76dd338a..aa19b95dc11e3019768bfce54cda854170984e6f 100755 (executable)
@@ -7,6 +7,7 @@ use lib '../include';
 use itkaclcommon;
 
 itkaclcommon::init();
+itkaclcommon::check_csrf_token();
 
 my $allow = $itkaclcommon::cgi->param('allow');
 my $entry = $itkaclcommon::cgi->param('entry');
index d0f1ddd7271e6f4f13eb068128eb507f4b1e80bf..46bab19a8d4fc6452d7b7f36bc674e9ede68d2b0 100755 (executable)
@@ -7,6 +7,7 @@ use lib '../include';
 use itkaclcommon;
 
 itkaclcommon::init();
+itkaclcommon::check_csrf_token();
 
 my $parent = $itkaclcommon::cgi->param('parent');
 my $name = $itkaclcommon::cgi->param('name');
index d92a6a51e1330692b997a6f6d9a07ac6782f5107..1ac6c6a6fe43314c6d9b58aee36dbfc759b1f671 100755 (executable)
@@ -7,6 +7,7 @@ use lib '../include';
 use itkaclcommon;
 
 itkaclcommon::init();
+itkaclcommon::check_csrf_token();
 
 my $entry = $itkaclcommon::cgi->param('entry');
 my $entity = $itkaclcommon::cgi->param('entity');
index 9165b0e51e987073d9051a9ccdf3a2c613b1192a..f1d1d9509b82183cf9b81130c529ae906c9f41ac 100755 (executable)
@@ -7,6 +7,7 @@ use lib '../include';
 use itkaclcommon;
 
 itkaclcommon::init();
+itkaclcommon::check_csrf_token();
 
 my $entry = $itkaclcommon::cgi->param('entry');
 my $entity = $itkaclcommon::cgi->param('entity');
index d49810f44e0544366dbf1ee2e9e96beff16259c9..4196c6891fd573352ef244c5bec7e3aaae0fc386 100755 (executable)
@@ -7,6 +7,7 @@ use lib '../include';
 use itkaclcommon;
 
 itkaclcommon::init();
+itkaclcommon::check_csrf_token();
 
 my $entry = $itkaclcommon::cgi->param('entry');
 
index f7464f631f787c366f3de330fa547272ed0e234c..d2aed2a9234a8c16b8b2694c9ea72e0e9c3c91ee 100644 (file)
@@ -23,6 +23,7 @@ function commitEdit(td) {
                        'entry': td.getAttributeNS('http://acl.samfundet.no/', 'entry'),
                        'entity_type': td.getAttributeNS('http://acl.samfundet.no/', 'entity-type'),
                        'entity': td.getAttributeNS('http://acl.samfundet.no/', 'entity'),
+                       'csrftoken': td.getAttributeNS('http://acl.samfundet.no/', 'csrftoken'),
                        'comment': newComment
                }
        }).fail(function() { alert("Klarte ikke å endre kommentaren"); });
index a79320c98835fc67edd99d63e3868d8411f1b63f..ec8349396a752756f8b23c360218d4ab21b9e242 100755 (executable)
@@ -136,11 +136,11 @@ EOF
     <td>$et_text</td>
     <td>$e_text</td>
     <td style="text-align: center;">
-      <a href="/delete.pl?entry=$entry&amp;entity_type=$entity_type&amp;entity=$e_text">
+      <a href="/delete.pl?entry=$entry&amp;entity_type=$entity_type&amp;entity=$e_text&amp;csrftoken=$itkaclcommon::masked_csrf_token">
         <img src="/img/delete.png" alt="Slett" />
       </a>
     </td>
-    <td ondblclick="enableEdit(this);" itkacl:entry="$entry" itkacl:entity-type="$entity_type" itkacl:entity="$e_text">$c_text</td>
+    <td ondblclick="enableEdit(this);" itkacl:entry="$entry" itkacl:entity-type="$entity_type" itkacl:entity="$e_text" itkacl:csrftoken="$itkaclcommon::masked_csrf_token">$c_text</td>
   </tr>
 EOF
 }
@@ -167,6 +167,7 @@ print <<"EOF";
 <p>Den spesielle gruppen "&lt;everyone&gt;" inneholder alle brukere.</p>
 
 <form method="post" action="add.pl">
+  <input type="hidden" name="csrftoken" value="$itkaclcommon::masked_csrf_token" />
   <input type="hidden" name="entry" value="$entry" />
   <table>
     <tr style="text-align: left;">
@@ -210,6 +211,7 @@ print <<"EOF";
   er case-sensitive) samt tall og bindestrek.</p>
 
 <form method="post" action="addnode.pl">
+  <input type="hidden" name="csrftoken" value="$itkaclcommon::masked_csrf_token" />
   <input type="hidden" name="parent" value="$entry" />
   <table>
     <tr style="text-align: left;">
@@ -255,6 +257,7 @@ EOF
   feile, så sjekk grundig før du sletter det at det ikke fortsatt er i bruk.</p>
   
 <form method="post" action="deletenode.pl">
+  <input type="hidden" name="csrftoken" value="$itkaclcommon::masked_csrf_token" />
   <input type="hidden" name="entry" value="$entry" />
   <p><input type="submit" value="Slett dette området" /></p>
 </form>