From fa3b6a893838409e3486d210fed263a8f7347a99 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 7 Aug 2013 22:40:02 +0200 Subject: [PATCH] Protect the web interface against CSRF, and the CSRF token against BREACH. --- itkacl-web-1.0/include/config.pm | 4 +++ itkacl-web-1.0/include/itkaclcommon.pm | 43 ++++++++++++++++++++++++++ itkacl-web-1.0/web/add.pl | 1 + itkacl-web-1.0/web/addnode.pl | 1 + itkacl-web-1.0/web/change-comment.pl | 1 + itkacl-web-1.0/web/delete.pl | 1 + itkacl-web-1.0/web/deletenode.pl | 1 + itkacl-web-1.0/web/itkacl.js | 1 + itkacl-web-1.0/web/view.pl | 7 +++-- 9 files changed, 58 insertions(+), 2 deletions(-) diff --git a/itkacl-web-1.0/include/config.pm b/itkacl-web-1.0/include/config.pm index 7922930..c471a00 100644 --- a/itkacl-web-1.0/include/config.pm +++ b/itkacl-web-1.0/include/config.pm @@ -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'; diff --git a/itkacl-web-1.0/include/itkaclcommon.pm b/itkacl-web-1.0/include/itkaclcommon.pm index 07ba682..cd86a3b 100644 --- a/itkacl-web-1.0/include/itkaclcommon.pm +++ b/itkacl-web-1.0/include/itkaclcommon.pm @@ -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 { diff --git a/itkacl-web-1.0/web/add.pl b/itkacl-web-1.0/web/add.pl index 8a1ce01..aa19b95 100755 --- a/itkacl-web-1.0/web/add.pl +++ b/itkacl-web-1.0/web/add.pl @@ -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'); diff --git a/itkacl-web-1.0/web/addnode.pl b/itkacl-web-1.0/web/addnode.pl index d0f1ddd..46bab19 100755 --- a/itkacl-web-1.0/web/addnode.pl +++ b/itkacl-web-1.0/web/addnode.pl @@ -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'); diff --git a/itkacl-web-1.0/web/change-comment.pl b/itkacl-web-1.0/web/change-comment.pl index d92a6a5..1ac6c6a 100755 --- a/itkacl-web-1.0/web/change-comment.pl +++ b/itkacl-web-1.0/web/change-comment.pl @@ -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'); diff --git a/itkacl-web-1.0/web/delete.pl b/itkacl-web-1.0/web/delete.pl index 9165b0e..f1d1d95 100755 --- a/itkacl-web-1.0/web/delete.pl +++ b/itkacl-web-1.0/web/delete.pl @@ -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'); diff --git a/itkacl-web-1.0/web/deletenode.pl b/itkacl-web-1.0/web/deletenode.pl index d49810f..4196c68 100755 --- a/itkacl-web-1.0/web/deletenode.pl +++ b/itkacl-web-1.0/web/deletenode.pl @@ -7,6 +7,7 @@ use lib '../include'; use itkaclcommon; itkaclcommon::init(); +itkaclcommon::check_csrf_token(); my $entry = $itkaclcommon::cgi->param('entry'); diff --git a/itkacl-web-1.0/web/itkacl.js b/itkacl-web-1.0/web/itkacl.js index f7464f6..d2aed2a 100644 --- a/itkacl-web-1.0/web/itkacl.js +++ b/itkacl-web-1.0/web/itkacl.js @@ -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"); }); diff --git a/itkacl-web-1.0/web/view.pl b/itkacl-web-1.0/web/view.pl index a79320c..ec83493 100755 --- a/itkacl-web-1.0/web/view.pl +++ b/itkacl-web-1.0/web/view.pl @@ -136,11 +136,11 @@ EOF $et_text $e_text - + Slett - $c_text + $c_text EOF } @@ -167,6 +167,7 @@ print <<"EOF";

Den spesielle gruppen "<everyone>" inneholder alle brukere.

+ @@ -210,6 +211,7 @@ print <<"EOF"; er case-sensitive) samt tall og bindestrek.

+
@@ -255,6 +257,7 @@ EOF feile, så sjekk grundig før du sletter det at det ikke fortsatt er i bruk.

+

-- 2.39.2