summaryrefslogtreecommitdiff
path: root/lib/VNWeb/Validation.pm
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2020-04-20 10:31:26 +0200
committerYorhel <git@yorhel.nl>2020-04-20 10:31:42 +0200
commitb6f98d51910e0a9252b18f3b160e32d97ba64b2c (patch)
treee06fa29a3e6ffd242a56437191ec19a4a8d9cf7a /lib/VNWeb/Validation.pm
parentd16f10f3fc504b40bbc2ba859ca6807c77d0939d (diff)
Strengthen ?view= query parameter security with a CSRF token
Easier than moderating the site to prevent people from creating internal links to sensitive pages. It also turns out that SameSite cookies are included when opening a bookmark (okayish) or clicking on a link from outside the browser (not okay), this protects against those scenarios as well.
Diffstat (limited to 'lib/VNWeb/Validation.pm')
-rw-r--r--lib/VNWeb/Validation.pm19
1 files changed, 16 insertions, 3 deletions
diff --git a/lib/VNWeb/Validation.pm b/lib/VNWeb/Validation.pm
index c1b16a01..5658ba70 100644
--- a/lib/VNWeb/Validation.pm
+++ b/lib/VNWeb/Validation.pm
@@ -201,8 +201,10 @@ sub can_edit {
}
-# Returns { spoilers => 0-2, traits_sexual => 0/1 }
-# Based on the view= query parameter or the user's preferences.
+# Some user preferences can be overruled with a ?view= query parameter,
+# viewget() can be used to fetch these parameters, viewset() to generate a
+# query parameter with certain preferences overruled.
+#
# The query parameter has the following format:
# view=1 -> spoilers=1, traits_sexual=<default>
# view=2s -> spoilers=2, traits_sexual=1
@@ -213,8 +215,16 @@ sub can_edit {
# s/S -> 1/0 traits_sexual
# n/N -> 1/0 show_nsfw
# Missing flags will use default.
+#
+# The parameter also contains a CSRF token to prevent direct links to pages
+# with sensitive content. The token is domain-separated from the form CSRF
+# tokens, but is otherwise generic for all pages and options, so if someone's
+# token leaks, it's possible to generate links to any sensitive page for that
+# particular user for several hours.
sub viewget {
- my($sp, $ts, $ns) = tuwf->reqGet('view') =~ /^([0-2])?([sS]?)([nN]?)$/;
+ my($view, $token) = tuwf->reqGet('view') =~ /^([^-]*)-(.+)$/;
+ $view = '' if !length($token) || !auth->csrfcheck($token, 'view');
+ my($sp, $ts, $ns) = $view =~ /^([0-2])?([sS]?)([nN]?)$/;
{
spoilers => $sp // auth->pref('spoilers') || 0,
traits_sexual => !$ts ? auth->pref('traits_sexual') : $ts eq 's',
@@ -222,6 +232,7 @@ sub viewget {
}
}
+
# Creates a new 'view=' string with the given parameters. All other fields remain at their default.
sub viewset {
my %s = @_;
@@ -229,6 +240,7 @@ sub viewset {
$s{spoilers}//'',
!defined $s{traits_sexual} ? '' : $s{traits_sexual} ? 's' : 'S',
!defined $s{show_nsfw} ? '' : $s{show_nsfw} ? 'n' : 'N',
+ '-'.auth->csrftoken(0, 'view');
}
@@ -236,6 +248,7 @@ sub viewset {
# itself. We don't want people linking directly to spoilers or sexual content.
# If we do get such a request, redirect to the same page without the ?view=
# parameter.
+# TODO: Also redirect if the token is invalid.
TUWF::hook before => sub {
return if tuwf->samesite || !length tuwf->reqGet('view');
my $qs = join '&', map { my $k=$_; my @l=tuwf->reqGets($k); map uri_escape($k).'='.uri_escape($_), @l } grep $_ ne 'view', tuwf->reqGets();