about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2020-11-23 14:15:35 +0000
committerEric Wong <e@80x24.org>2020-12-26 19:42:16 +0000
commitc39ed01a3a4c6c4634642eb875a16538aceacfc3 (patch)
tree2ad2c768a60634bc1bc5df7f9e5c3e137ddb4920
parent0366c73f20b436d4d5307a56c2b6ac93b115f23f (diff)
downloadpublic-inbox-c39ed01a3a4c6c4634642eb875a16538aceacfc3.tar.gz
This prevents `<img src=' tags from being used to deep-link
image attachments from HTML outside of the current host and
reduces potential for abuse.

Some browsers (e.g. Firefox) favor content detection and will
display images irrespective of the Content-Type header being
"application/octet-stream", and "Content-Disposition: attachment"
doesn't stop them, either.

Tested with dillo and Firefox.

Reported-by: Leah Neukirchen <leah@vuxu.org>
(cherry picked from commit 46cbc5a7a4ba917bd7154be3b6e6898420ff85d3)
-rw-r--r--lib/PublicInbox/WwwAttach.pm26
1 files changed, 24 insertions, 2 deletions
diff --git a/lib/PublicInbox/WwwAttach.pm b/lib/PublicInbox/WwwAttach.pm
index 0b2cda90..09c66d02 100644
--- a/lib/PublicInbox/WwwAttach.pm
+++ b/lib/PublicInbox/WwwAttach.pm
@@ -9,6 +9,22 @@ use bytes (); # only for bytes::length
 use PublicInbox::EmlContentFoo qw(parse_content_type);
 use PublicInbox::Eml;
 
+sub referer_match ($) {
+        my ($ctx) = @_;
+        my $env = $ctx->{env};
+        my $referer = $env->{HTTP_REFERER} // '';
+        return 1 if $referer eq ''; # no referer is always OK for wget/curl
+
+        # prevent deep-linking from other domains on some browsers (Firefox)
+        # n.b.: $ctx->{-inbox}->base_url($env) with INBOX_URL won't work
+        # with dillo, we can only match "$url_scheme://$HTTP_HOST/" without
+        # path components
+        my $base_url = $env->{'psgi.url_scheme'} . '://' .
+                        ($env->{HTTP_HOST} //
+                         "$env->{SERVER_NAME}:$env->{SERVER_PORT}") . '/';
+        index($referer, $base_url) == 0;
+}
+
 sub get_attach_i { # ->each_part callback
         my ($part, $depth, $idx) = @{$_[0]};
         my $ctx = $_[1];
@@ -28,8 +44,14 @@ sub get_attach_i { # ->each_part callback
                                                                 $ctx->{env});
                 $part = $ctx->zflush($part->body);
         } else { # TODO: allow user to configure safe types
-                $res->[1]->[1] = 'application/octet-stream';
-                $part = $part->body;
+                if (referer_match($ctx)) {
+                        $res->[1]->[1] = 'application/octet-stream';
+                        $part = $part->body;
+                } else {
+                        $res->[0] = 403;
+                        $res->[1]->[1] = 'text/plain';
+                        $part = "Deep-linking prevented\n";
+                }
         }
         push @{$res->[1]}, 'Content-Length', bytes::length($part);
         $res->[2]->[0] = $part;