Chris Lamb <[email protected]> writes: > Sorry to be a pain but can you remake this with --exclude="*/.pc/*" > or similar...? :)
Hmmm. Would have hoped that directory would be excluded automatically from the source package. Oh wait, this is a debian native package. Means I will probably have to patch the files directly, not rely on debian/patches. So was only working before because I was testing with patches applied. Curiously I am getting a test failure when testing without my patches. -- Brian May <[email protected]>
diff -Nru ikiwiki-3.20141016.4/CHANGELOG ikiwiki-3.20141016.4+deb8u1/CHANGELOG --- ikiwiki-3.20141016.4/CHANGELOG 2017-01-12 05:18:52.000000000 +1100 +++ ikiwiki-3.20141016.4+deb8u1/CHANGELOG 2019-03-07 17:35:55.000000000 +1100 @@ -1,3 +1,10 @@ +ikiwiki (3.20141016.4+deb8u1) jessie-security; urgency=high + + * Non-maintainer upload by the LTS Team. + * CVE-2019-9187: Fix server-side request forgery via aggregate plugin. + + -- Brian May <[email protected]> Thu, 07 Mar 2019 17:35:55 +1100 + ikiwiki (3.20141016.4) jessie-security; urgency=high * Reference CVE-2016-4561 in 3.20141016.3 changelog diff -Nru ikiwiki-3.20141016.4/CVE-2019-9187-1.patch ikiwiki-3.20141016.4+deb8u1/CVE-2019-9187-1.patch --- ikiwiki-3.20141016.4/CVE-2019-9187-1.patch 1970-01-01 10:00:00.000000000 +1000 +++ ikiwiki-3.20141016.4+deb8u1/CVE-2019-9187-1.patch 2019-03-07 17:25:37.000000000 +1100 @@ -0,0 +1,28 @@ +From e7b0d4a0fff8ed45a90c2efe8ef294bdf7c9bdac Mon Sep 17 00:00:00 2001 +From: Simon McVittie <[email protected]> +Date: Sun, 10 Feb 2019 16:29:19 +0000 +Subject: [PATCH] useragent: Raise an exception if the LWP module can't be + loaded + +Signed-off-by: Simon McVittie <[email protected]> +--- + IkiWiki.pm | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/IkiWiki.pm b/IkiWiki.pm +index 90cb96e58..dc047b08a 100644 +--- a/IkiWiki.pm ++++ b/IkiWiki.pm +@@ -2470,6 +2470,9 @@ sub add_autofile ($$$) { + } + + sub useragent () { ++ eval q{use LWP}; ++ error($@) if $@; ++ + return LWP::UserAgent->new( + cookie_jar => $config{cookiejar}, + env_proxy => 1, # respect proxy env vars +-- +2.11.0 + diff -Nru ikiwiki-3.20141016.4/CVE-2019-9187-2.patch ikiwiki-3.20141016.4+deb8u1/CVE-2019-9187-2.patch --- ikiwiki-3.20141016.4/CVE-2019-9187-2.patch 1970-01-01 10:00:00.000000000 +1000 +++ ikiwiki-3.20141016.4+deb8u1/CVE-2019-9187-2.patch 2019-03-07 17:26:25.000000000 +1100 @@ -0,0 +1,238 @@ +From 67543ce1d62161fdef9dca198289d7dd7dceacc0 Mon Sep 17 00:00:00 2001 +From: Simon McVittie <[email protected]> +Date: Sun, 10 Feb 2019 16:30:07 +0000 +Subject: [PATCH] useragent: Don't allow non-HTTP protocols to be used + +This prevents the aggregate plugin from being used to read the contents +of local files via file:/// URLs. + +Signed-off-by: Simon McVittie <[email protected]> +--- + IkiWiki.pm | 1 + + t/aggregate-file.t | 173 +++++++++++++++++++++++++++++++++++++ + t/noparanoia/LWPx/ParanoidAgent.pm | 2 + + t/secret.rss | 11 +++ + 4 files changed, 187 insertions(+) + create mode 100755 t/aggregate-file.t + create mode 100644 t/noparanoia/LWPx/ParanoidAgent.pm + create mode 100644 t/secret.rss + +diff --git a/IkiWiki.pm b/IkiWiki.pm +index dc047b08a..d5d1af56c 100644 +--- a/IkiWiki.pm ++++ b/IkiWiki.pm +@@ -2477,6 +2477,7 @@ sub useragent () { + cookie_jar => $config{cookiejar}, + env_proxy => 1, # respect proxy env vars + agent => $config{useragent}, ++ protocols_allowed => [qw(http https)], + ); + } + +diff --git a/t/aggregate-file.t b/t/aggregate-file.t +new file mode 100755 +index 000000000..f00743dac +--- /dev/null ++++ b/t/aggregate-file.t +@@ -0,0 +1,173 @@ ++#!/usr/bin/perl ++use utf8; ++use warnings; ++use strict; ++ ++use Encode; ++use Test::More; ++ ++BEGIN { ++ plan(skip_all => "CGI not available") ++ unless eval q{ ++ use CGI qw(); ++ 1; ++ }; ++ ++ plan(skip_all => "IPC::Run not available") ++ unless eval q{ ++ use IPC::Run qw(run); ++ 1; ++ }; ++ ++ use_ok('IkiWiki'); ++ use_ok('YAML::XS'); ++} ++ ++# We check for English error messages ++$ENV{LC_ALL} = 'C'; ++ ++use Cwd qw(getcwd); ++use Errno qw(ENOENT); ++ ++my $installed = $ENV{INSTALLED_TESTS}; ++ ++my @command; ++if ($installed) { ++ @command = qw(ikiwiki --plugin inline); ++} ++else { ++ ok(! system("make -s ikiwiki.out")); ++ @command = ("perl", "-I".getcwd."/blib/lib", './ikiwiki.out', ++ '--underlaydir='.getcwd.'/underlays/basewiki', ++ '--set', 'underlaydirbase='.getcwd.'/underlays', ++ '--templatedir='.getcwd.'/templates'); ++} ++ ++sub write_old_file { ++ my $name = shift; ++ my $dir = shift; ++ my $content = shift; ++ writefile($name, $dir, $content); ++ ok(utime(333333333, 333333333, "$dir/$name")); ++} ++ ++sub write_setup_file { ++ my %params = @_; ++ my %setup = ( ++ wikiname => 'this is the name of my wiki', ++ srcdir => getcwd.'/t/tmp/in', ++ destdir => getcwd.'/t/tmp/out', ++ url => 'http://example.com', ++ cgiurl => 'http://example.com/cgi-bin/ikiwiki.cgi', ++ cgi_wrapper => getcwd.'/t/tmp/ikiwiki.cgi', ++ cgi_wrappermode => '0751', ++ add_plugins => [qw(aggregate)], ++ disable_plugins => [qw(emailauth openid passwordauth)], ++ aggregate_webtrigger => 1, ++ ); ++ if ($params{without_paranoia}) { ++ $setup{libdirs} = [getcwd.'/t/noparanoia']; ++ } ++ unless ($installed) { ++ $setup{ENV} = { 'PERL5LIB' => getcwd.'/blib/lib' }; ++ } ++ writefile("test.setup", "t/tmp", ++ "# IkiWiki::Setup::Yaml - YAML formatted setup file\n" . ++ Dump(\%setup)); ++} ++ ++sub thoroughly_rebuild { ++ ok(unlink("t/tmp/ikiwiki.cgi") || $!{ENOENT}); ++ ok(! system(@command, qw(--setup t/tmp/test.setup --rebuild --wrappers))); ++} ++ ++sub run_cgi { ++ my (%args) = @_; ++ my ($in, $out); ++ my $method = $args{method} || 'GET'; ++ my $environ = $args{environ} || {}; ++ my $params = $args{params} || { do => 'prefs' }; ++ ++ my %defaults = ( ++ SCRIPT_NAME => '/cgi-bin/ikiwiki.cgi', ++ HTTP_HOST => 'example.com', ++ ); ++ ++ my $cgi = CGI->new($args{params}); ++ my $query_string = $cgi->query_string(); ++ diag $query_string; ++ ++ if ($method eq 'POST') { ++ $defaults{REQUEST_METHOD} = 'POST'; ++ $in = $query_string; ++ $defaults{CONTENT_LENGTH} = length $in; ++ } else { ++ $defaults{REQUEST_METHOD} = 'GET'; ++ $defaults{QUERY_STRING} = $query_string; ++ } ++ ++ my %envvars = ( ++ %defaults, ++ %$environ, ++ ); ++ run(["./t/tmp/ikiwiki.cgi"], \$in, \$out, init => sub { ++ map { ++ $ENV{$_} = $envvars{$_} ++ } keys(%envvars); ++ }); ++ ++ return decode_utf8($out); ++} ++ ++sub test { ++ my $content; ++ ++ ok(! system(qw(rm -rf t/tmp))); ++ ok(! system(qw(mkdir t/tmp))); ++ ++ write_old_file('aggregator.mdwn', 't/tmp/in', ++ '[[!aggregate name="ssrf" url="file://'.getcwd.'/t/secret.rss"]]' ++ .'[[!inline pages="internal(aggregator/*)"]]'); ++ ++ write_setup_file(); ++ thoroughly_rebuild(); ++ ++ $content = run_cgi( ++ method => 'GET', ++ params => { ++ do => 'aggregate_webtrigger', ++ }, ++ ); ++ unlike($content, qr{creating new page}); ++ unlike($content, qr{Secrets}); ++ ok(! -e 't/tmp/in/.ikiwiki/transient/aggregator/ssrf'); ++ ok(! -e 't/tmp/in/.ikiwiki/transient/aggregator/ssrf/Secrets_go_here._aggregated'); ++ ++ thoroughly_rebuild(); ++ $content = readfile('t/tmp/out/aggregator/index.html'); ++ unlike($content, qr{Secrets}); ++ ++ diag('Trying test again with LWPx::ParanoidAgent disabled'); ++ ++ write_setup_file(without_paranoia => 1); ++ thoroughly_rebuild(); ++ ++ $content = run_cgi( ++ method => 'GET', ++ params => { ++ do => 'aggregate_webtrigger', ++ }, ++ ); ++ unlike($content, qr{creating new page}); ++ unlike($content, qr{Secrets}); ++ ok(! -e 't/tmp/in/.ikiwiki/transient/aggregator/ssrf'); ++ ok(! -e 't/tmp/in/.ikiwiki/transient/aggregator/ssrf/Secrets_go_here._aggregated'); ++ ++ thoroughly_rebuild(); ++ $content = readfile('t/tmp/out/aggregator/index.html'); ++ unlike($content, qr{Secrets}); ++} ++ ++test(); ++ ++done_testing(); +diff --git a/t/noparanoia/LWPx/ParanoidAgent.pm b/t/noparanoia/LWPx/ParanoidAgent.pm +new file mode 100644 +index 000000000..751e80ce6 +--- /dev/null ++++ b/t/noparanoia/LWPx/ParanoidAgent.pm +@@ -0,0 +1,2 @@ ++# make import fail ++0; +diff --git a/t/secret.rss b/t/secret.rss +new file mode 100644 +index 000000000..11202e9ed +--- /dev/null ++++ b/t/secret.rss +@@ -0,0 +1,11 @@ ++<?xml version="1.0"?> ++<rss version="2.0"> ++<channel> ++<title>Secrets go here</title> ++<description>Secrets go here</description> ++<item> ++ <title>Secrets go here</title> ++ <description>Secrets go here</description> ++</item> ++</channel> ++</rss> +-- +2.11.0 + diff -Nru ikiwiki-3.20141016.4/CVE-2019-9187-3.patch ikiwiki-3.20141016.4+deb8u1/CVE-2019-9187-3.patch --- ikiwiki-3.20141016.4/CVE-2019-9187-3.patch 1970-01-01 10:00:00.000000000 +1000 +++ ikiwiki-3.20141016.4+deb8u1/CVE-2019-9187-3.patch 2019-03-07 17:26:41.000000000 +1100 @@ -0,0 +1,590 @@ +From d283e4ca1aeb6ca8cc0951c8495f778071076013 Mon Sep 17 00:00:00 2001 +From: Simon McVittie <[email protected]> +Date: Sun, 10 Feb 2019 17:22:06 +0000 +Subject: [PATCH] useragent: Automatically choose whether to use + LWPx::ParanoidAgent + +The simple implementation of this, which I'd prefer to use, would be: +if we can import LWPx::ParanoidAgent, use it; otherwise, use +LWP::UserAgent. + +However, aggregate has historically worked with proxies, and +LWPx::ParanoidAgent quite reasonably refuses to work with proxies +(because it can't know whether those proxies are going to do the same +filtering that LWPx::ParanoidAgent would). + +Signed-off-by: Simon McVittie <[email protected]> +--- + IkiWiki.pm | 123 ++++++++++++++++- + IkiWiki/Plugin/aggregate.pm | 5 +- + IkiWiki/Plugin/blogspam.pm | 16 +-- + IkiWiki/Plugin/openid.pm | 12 +- + IkiWiki/Plugin/pinger.pm | 21 ++- + t/useragent.t | 317 ++++++++++++++++++++++++++++++++++++++++++++ + 6 files changed, 458 insertions(+), 36 deletions(-) + create mode 100755 t/useragent.t + +diff --git a/IkiWiki.pm b/IkiWiki.pm +index d5d1af56c..efb48293a 100644 +--- a/IkiWiki.pm ++++ b/IkiWiki.pm +@@ -2469,16 +2469,131 @@ sub add_autofile ($$$) { + $autofiles{$file}{generator}=$generator; + } + +-sub useragent () { ++sub useragent (@) { ++ my %params = @_; ++ my $for_url = delete $params{for_url}; ++ # Fail safe, in case a plugin calling this function is relying on ++ # a future parameter to make the UA more strict ++ foreach my $key (keys %params) { ++ error "Internal error: useragent(\"$key\" => ...) not understood"; ++ } ++ + eval q{use LWP}; + error($@) if $@; + +- return LWP::UserAgent->new( +- cookie_jar => $config{cookiejar}, +- env_proxy => 1, # respect proxy env vars ++ my %args = ( + agent => $config{useragent}, ++ cookie_jar => $config{cookiejar}, ++ env_proxy => 0, + protocols_allowed => [qw(http https)], + ); ++ my %proxies; ++ ++ if (defined $for_url) { ++ # We know which URL we're going to fetch, so we can choose ++ # whether it's going to go through a proxy or not. ++ # ++ # We reimplement http_proxy, https_proxy and no_proxy here, so ++ # that we are not relying on LWP implementing them exactly the ++ # same way we do. ++ ++ eval q{use URI}; ++ error($@) if $@; ++ ++ my $proxy; ++ my $uri = URI->new($for_url); ++ ++ if ($uri->scheme eq 'http') { ++ $proxy = $ENV{http_proxy}; ++ # HTTP_PROXY is deliberately not implemented ++ # because the HTTP_* namespace is also used by CGI ++ } ++ elsif ($uri->scheme eq 'https') { ++ $proxy = $ENV{https_proxy}; ++ $proxy = $ENV{HTTPS_PROXY} unless defined $proxy; ++ } ++ else { ++ $proxy = undef; ++ } ++ ++ foreach my $var (qw(no_proxy NO_PROXY)) { ++ my $no_proxy = $ENV{$var}; ++ if (defined $no_proxy) { ++ foreach my $domain (split /\s*,\s*/, $no_proxy) { ++ if ($domain =~ s/^\*?\.//) { ++ # no_proxy="*.example.com" or ++ # ".example.com": match suffix ++ # against .example.com ++ if ($uri->host =~ m/(^|\.)\Q$domain\E$/i) { ++ $proxy = undef; ++ } ++ } ++ else { ++ # no_proxy="example.com": ++ # match exactly example.com ++ if (lc $uri->host eq lc $domain) { ++ $proxy = undef; ++ } ++ } ++ } ++ } ++ } ++ ++ if (defined $proxy) { ++ $proxies{$uri->scheme} = $proxy; ++ # Paranoia: make sure we can't bypass the proxy ++ $args{protocols_allowed} = [$uri->scheme]; ++ } ++ } ++ else { ++ # The plugin doesn't know yet which URL(s) it's going to ++ # fetch, so we have to make some conservative assumptions. ++ my $http_proxy = $ENV{http_proxy}; ++ my $https_proxy = $ENV{https_proxy}; ++ $https_proxy = $ENV{HTTPS_PROXY} unless defined $https_proxy; ++ ++ # We don't respect no_proxy here: if we are not using the ++ # paranoid user-agent, then we need to give the proxy the ++ # opportunity to reject undesirable requests. ++ ++ # If we have one, we need the other: otherwise, neither ++ # LWPx::ParanoidAgent nor the proxy would have the ++ # opportunity to filter requests for the other protocol. ++ if (defined $https_proxy && defined $http_proxy) { ++ %proxies = (http => $http_proxy, https => $https_proxy); ++ } ++ elsif (defined $https_proxy) { ++ %proxies = (http => $https_proxy, https => $https_proxy); ++ } ++ elsif (defined $http_proxy) { ++ %proxies = (http => $http_proxy, https => $http_proxy); ++ } ++ ++ } ++ ++ if (scalar keys %proxies) { ++ # The configured proxy is responsible for deciding which ++ # URLs are acceptable to fetch and which URLs are not. ++ my $ua = LWP::UserAgent->new(%args); ++ foreach my $scheme (@{$ua->protocols_allowed}) { ++ unless ($proxies{$scheme}) { ++ error "internal error: $scheme is allowed but has no proxy"; ++ } ++ } ++ # We can't pass the proxies in %args because that only ++ # works since LWP 6.24. ++ foreach my $scheme (keys %proxies) { ++ $ua->proxy($scheme, $proxies{$scheme}); ++ } ++ return $ua; ++ } ++ ++ eval q{use LWPx::ParanoidAgent}; ++ if ($@) { ++ print STDERR "warning: installing LWPx::ParanoidAgent is recommended\n"; ++ return LWP::UserAgent->new(%args); ++ } ++ return LWPx::ParanoidAgent->new(%args); + } + + sub sortspec_translate ($$) { +diff --git a/IkiWiki/Plugin/aggregate.pm b/IkiWiki/Plugin/aggregate.pm +index 05e22a290..8f0870e2e 100644 +--- a/IkiWiki/Plugin/aggregate.pm ++++ b/IkiWiki/Plugin/aggregate.pm +@@ -513,7 +513,10 @@ sub aggregate (@) { + } + $feed->{feedurl}=pop @urls; + } +- my $ua=useragent(); ++ # Using the for_url parameter makes sure we crash if used ++ # with an older IkiWiki.pm that didn't automatically try ++ # to use LWPx::ParanoidAgent. ++ my $ua=useragent(for_url => $feed->{feedurl}); + my $res=URI::Fetch->fetch($feed->{feedurl}, UserAgent=>$ua); + if (! $res) { + $feed->{message}=URI::Fetch->errstr; +diff --git a/IkiWiki/Plugin/blogspam.pm b/IkiWiki/Plugin/blogspam.pm +index 3eb4cf8b3..3835f52ca 100644 +--- a/IkiWiki/Plugin/blogspam.pm ++++ b/IkiWiki/Plugin/blogspam.pm +@@ -57,18 +57,10 @@ sub checkconfig () { + }; + error $@ if $@; + +- eval q{use LWPx::ParanoidAgent}; +- if (!$@) { +- $client=LWPx::ParanoidAgent->new(agent => $config{useragent}); +- } +- else { +- eval q{use LWP}; +- if ($@) { +- error $@; +- return; +- } +- $client=useragent(); +- } ++ # Using the for_url parameter makes sure we crash if used ++ # with an older IkiWiki.pm that didn't automatically try ++ # to use LWPx::ParanoidAgent. ++ $client=useragent(for_url => $config{blogspam_server}); + } + + sub checkcontent (@) { +diff --git a/IkiWiki/Plugin/openid.pm b/IkiWiki/Plugin/openid.pm +index 35ef52a58..eb21955e9 100644 +--- a/IkiWiki/Plugin/openid.pm ++++ b/IkiWiki/Plugin/openid.pm +@@ -219,14 +219,10 @@ sub getobj ($$) { + eval q{use Net::OpenID::Consumer}; + error($@) if $@; + +- my $ua; +- eval q{use LWPx::ParanoidAgent}; +- if (! $@) { +- $ua=LWPx::ParanoidAgent->new(agent => $config{useragent}); +- } +- else { +- $ua=useragent(); +- } ++ # We pass the for_url parameter, even though it's undef, because ++ # that will make sure we crash if used with an older IkiWiki.pm ++ # that didn't automatically try to use LWPx::ParanoidAgent. ++ my $ua=useragent(for_url => undef); + + # Store the secret in the session. + my $secret=$session->param("openid_secret"); +diff --git a/IkiWiki/Plugin/pinger.pm b/IkiWiki/Plugin/pinger.pm +index b2d54af8a..ec764caee 100644 +--- a/IkiWiki/Plugin/pinger.pm ++++ b/IkiWiki/Plugin/pinger.pm +@@ -70,17 +70,16 @@ sub ping { + eval q{use Net::INET6Glue::INET_is_INET6}; # may not be available + + my $ua; +- eval q{use LWPx::ParanoidAgent}; +- if (!$@) { +- $ua=LWPx::ParanoidAgent->new(agent => $config{useragent}); +- } +- else { +- eval q{use LWP}; +- if ($@) { +- debug(gettext("LWP not found, not pinging")); +- return; +- } +- $ua=useragent(); ++ eval { ++ # We pass the for_url parameter, even though it's ++ # undef, because that will make sure we crash if used ++ # with an older IkiWiki.pm that didn't automatically ++ # try to use LWPx::ParanoidAgent. ++ $ua=useragent(for_url => undef); ++ }; ++ if ($@) { ++ debug(gettext("LWP not found, not pinging").": $@"); ++ return; + } + $ua->timeout($config{pinger_timeout} || 15); + +diff --git a/t/useragent.t b/t/useragent.t +new file mode 100755 +index 000000000..195a86521 +--- /dev/null ++++ b/t/useragent.t +@@ -0,0 +1,317 @@ ++#!/usr/bin/perl ++use warnings; ++use strict; ++use Test::More; ++ ++my $have_paranoid_agent; ++BEGIN { ++ plan(skip_all => 'LWP not available') ++ unless eval q{ ++ use LWP qw(); 1; ++ }; ++ use_ok("IkiWiki"); ++ $have_paranoid_agent = eval q{ ++ use LWPx::ParanoidAgent qw(); 1; ++ }; ++} ++ ++eval { useragent(future_feature => 1); }; ++ok($@, 'future features should cause useragent to fail'); ++ ++diag "==== No proxy ===="; ++delete $ENV{http_proxy}; ++delete $ENV{https_proxy}; ++delete $ENV{no_proxy}; ++delete $ENV{HTTPS_PROXY}; ++delete $ENV{NO_PROXY}; ++ ++diag "---- Unspecified URL ----"; ++my $ua = useragent(for_url => undef); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef, 'No http proxy'); ++is($ua->proxy('https'), undef, 'No https proxy'); ++ ++diag "---- Specified URL ----"; ++$ua = useragent(for_url => 'http://example.com'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef, 'No http proxy'); ++is($ua->proxy('https'), undef, 'No https proxy'); ++ ++diag "==== Proxy for everything ===="; ++$ENV{http_proxy} = 'http://proxy:8080'; ++$ENV{https_proxy} = 'http://sproxy:8080'; ++delete $ENV{no_proxy}; ++delete $ENV{HTTPS_PROXY}; ++delete $ENV{NO_PROXY}; ++ ++diag "---- Unspecified URL ----"; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use CONNECT proxy'); ++$ua = useragent(for_url => 'http://example.com'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++# We don't care what $ua->proxy('https') is, because it won't be used ++$ua = useragent(for_url => 'https://example.com'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++# We don't care what $ua->proxy('http') is, because it won't be used ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use CONNECT proxy'); ++ ++diag "==== Selective proxy ===="; ++$ENV{http_proxy} = 'http://proxy:8080'; ++$ENV{https_proxy} = 'http://sproxy:8080'; ++$ENV{no_proxy} = '*.example.net,example.com,.example.org'; ++delete $ENV{HTTPS_PROXY}; ++delete $ENV{NO_PROXY}; ++ ++diag "---- Unspecified URL ----"; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use CONNECT proxy'); ++ ++diag "---- Exact match for no_proxy ----"; ++$ua = useragent(for_url => 'http://example.com'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- Subdomain of exact domain in no_proxy ----"; ++$ua = useragent(for_url => 'http://sub.example.com'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++ ++diag "---- example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- sub.example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://sub.example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- badexample.net does not match *.example.net ----"; ++$ua = useragent(for_url => 'https://badexample.net'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++diag "---- example.org matches .example.org ----"; ++$ua = useragent(for_url => 'https://example.org'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- sub.example.org matches .example.org ----"; ++$ua = useragent(for_url => 'https://sub.example.org'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- badexample.org does not match .example.org ----"; ++$ua = useragent(for_url => 'https://badexample.org'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++diag "==== Selective proxy (alternate variables) ===="; ++$ENV{http_proxy} = 'http://proxy:8080'; ++delete $ENV{https_proxy}; ++$ENV{HTTPS_PROXY} = 'http://sproxy:8080'; ++delete $ENV{no_proxy}; ++$ENV{NO_PROXY} = '*.example.net,example.com,.example.org'; ++ ++diag "---- Unspecified URL ----"; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use CONNECT proxy'); ++ ++diag "---- Exact match for no_proxy ----"; ++$ua = useragent(for_url => 'http://example.com'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- Subdomain of exact domain in no_proxy ----"; ++$ua = useragent(for_url => 'http://sub.example.com'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++ ++diag "---- example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- sub.example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://sub.example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- badexample.net does not match *.example.net ----"; ++$ua = useragent(for_url => 'https://badexample.net'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++diag "---- example.org matches .example.org ----"; ++$ua = useragent(for_url => 'https://example.org'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- sub.example.org matches .example.org ----"; ++$ua = useragent(for_url => 'https://sub.example.org'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- badexample.org does not match .example.org ----"; ++$ua = useragent(for_url => 'https://badexample.org'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++diag "==== Selective proxy (many variables) ===="; ++$ENV{http_proxy} = 'http://proxy:8080'; ++$ENV{https_proxy} = 'http://sproxy:8080'; ++# This one should be ignored in favour of https_proxy ++$ENV{HTTPS_PROXY} = 'http://not.preferred.proxy:3128'; ++# These two should be merged ++$ENV{no_proxy} = '*.example.net,example.com'; ++$ENV{NO_PROXY} = '.example.org'; ++ ++diag "---- Unspecified URL ----"; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use CONNECT proxy'); ++ ++diag "---- Exact match for no_proxy ----"; ++$ua = useragent(for_url => 'http://example.com'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- Subdomain of exact domain in no_proxy ----"; ++$ua = useragent(for_url => 'http://sub.example.com'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++ ++diag "---- example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- sub.example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://sub.example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- badexample.net does not match *.example.net ----"; ++$ua = useragent(for_url => 'https://badexample.net'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++diag "==== One but not the other ====\n"; ++$ENV{http_proxy} = 'http://proxy:8080'; ++delete $ENV{https_proxy}; ++delete $ENV{HTTPS_PROXY}; ++delete $ENV{no_proxy}; ++delete $ENV{NO_PROXY}; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://proxy:8080', 'should use proxy'); ++ ++delete $ENV{http_proxy}; ++$ENV{https_proxy} = 'http://sproxy:8080'; ++delete $ENV{HTTPS_PROXY}; ++delete $ENV{no_proxy}; ++delete $ENV{NO_PROXY}; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://sproxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++done_testing; +-- +2.11.0 + diff -Nru ikiwiki-3.20141016.4/CVE-2019-9187-4.patch ikiwiki-3.20141016.4+deb8u1/CVE-2019-9187-4.patch --- ikiwiki-3.20141016.4/CVE-2019-9187-4.patch 1970-01-01 10:00:00.000000000 +1000 +++ ikiwiki-3.20141016.4+deb8u1/CVE-2019-9187-4.patch 2019-03-07 17:26:55.000000000 +1100 @@ -0,0 +1,175 @@ +From 9a275b2f1846d7268c71a740975447e269383849 Mon Sep 17 00:00:00 2001 +From: Simon McVittie <[email protected]> +Date: Sun, 10 Feb 2019 16:56:41 +0000 +Subject: [PATCH] doc: Document security issues involving LWP::UserAgent + +Recommend the LWPx::ParanoidAgent module where appropriate. +It is particularly important for openid, since unauthenticated users +can control which URLs that plugin will contact. Conversely, it is +non-critical for blogspam, since the URL to be contacted is under +the wiki administrator's control. + +Signed-off-by: Simon McVittie <[email protected]> +--- + doc/plugins/aggregate.mdwn | 4 ++++ + doc/plugins/blogspam.mdwn | 2 ++ + doc/plugins/openid.mdwn | 7 +++++-- + doc/plugins/pinger.mdwn | 8 +++++--- + doc/security.mdwn | 49 +++++++++++++++++++++++++++++++++++++++++++++ + doc/tips/using_a_proxy.mdwn | 22 ++++++++++++++++++++ + 6 files changed, 87 insertions(+), 5 deletions(-) + create mode 100644 doc/tips/using_a_proxy.mdwn + +diff --git a/doc/plugins/aggregate.mdwn b/doc/plugins/aggregate.mdwn +index 75123d923..b1db828d1 100644 +--- a/doc/plugins/aggregate.mdwn ++++ b/doc/plugins/aggregate.mdwn +@@ -11,6 +11,10 @@ The [[meta]] and [[tag]] plugins are also recommended to be used with this + one. Either the [[htmltidy]] or [[htmlbalance]] plugin is suggested, since + feeds can easily contain html problems, some of which these plugins can fix. + ++Installing the [[!cpan LWPx::ParanoidAgent]] Perl module is strongly ++recommended. The [[!cpan LWP]] module can also be used, but is susceptible ++to server-side request forgery. ++ + ## triggering aggregation + + You will need to run ikiwiki periodically from a cron job, passing it the +diff --git a/doc/plugins/blogspam.mdwn b/doc/plugins/blogspam.mdwn +index 745fc48e2..0ebae7d84 100644 +--- a/doc/plugins/blogspam.mdwn ++++ b/doc/plugins/blogspam.mdwn +@@ -11,6 +11,8 @@ To check for and moderate comments, log in to the wiki as an admin, + go to your Preferences page, and click the "Comment Moderation" button. + + The plugin requires the [[!cpan JSON]] perl module. ++The [[!cpan LWPx::ParanoidAgent]] Perl module is recommended, ++although this plugin can also fall back to [[!cpan LWP]]. + + You can control how content is tested via the `blogspam_options` setting. + The list of options is [here](http://blogspam.net/api/2.0/testComment.html#options). +diff --git a/doc/plugins/openid.mdwn b/doc/plugins/openid.mdwn +index 4c8e0d381..a061cb43f 100644 +--- a/doc/plugins/openid.mdwn ++++ b/doc/plugins/openid.mdwn +@@ -7,8 +7,11 @@ into the wiki. + The plugin needs the [[!cpan Net::OpenID::Consumer]] perl module. + Version 1.x is needed in order for OpenID v2 to work. + +-The [[!cpan LWPx::ParanoidAgent]] perl module is used if available, for +-added security. Finally, the [[!cpan Crypt::SSLeay]] perl module is needed ++The [[!cpan LWPx::ParanoidAgent]] Perl module is strongly recommended. ++The [[!cpan LWP]] module can also be used, but is susceptible to ++server-side request forgery. ++ ++The [[!cpan Crypt::SSLeay]] Perl module is needed + to support users entering "https" OpenID urls. + + This plugin is enabled by default, but can be turned off if you want to +diff --git a/doc/plugins/pinger.mdwn b/doc/plugins/pinger.mdwn +index 00d83e1bb..f37979ac6 100644 +--- a/doc/plugins/pinger.mdwn ++++ b/doc/plugins/pinger.mdwn +@@ -10,9 +10,11 @@ can be kept up-to-date. + To configure what URLs to ping, use the [[ikiwiki/directive/ping]] + [[ikiwiki/directive]]. + +-The [[!cpan LWP]] perl module is used for pinging. Or the [[!cpan +-LWPx::ParanoidAgent]] perl module is used if available, for added security. +-Finally, the [[!cpan Crypt::SSLeay]] perl module is needed to support pinging ++The [[!cpan LWPx::ParanoidAgent]] Perl module is strongly recommended. ++The [[!cpan LWP]] module can also be used, but is susceptible ++to server-side request forgery. ++ ++The [[!cpan Crypt::SSLeay]] perl module is needed to support pinging + "https" urls. + + By default the pinger will try to ping a site for 15 seconds before timing +diff --git a/doc/security.mdwn b/doc/security.mdwn +index e7770dd27..378a2e4bc 100644 +--- a/doc/security.mdwn ++++ b/doc/security.mdwn +@@ -611,3 +611,52 @@ This was fixed in ikiwiki 3.20170111, with fixes backported to Debian 8 + in version 3.20141016.4. + + ([[!debcve CVE-2017-0356]]/OVE-20170111-0001) ++ ++## Server-side request forgery via aggregate plugin ++ ++The ikiwiki maintainers discovered that the [[plugins/aggregate]] plugin ++did not use [[!cpan LWPx::ParanoidAgent]]. On sites where the ++aggregate plugin is enabled, authorized wiki editors could tell ikiwiki ++to fetch potentially undesired URIs even if LWPx::ParanoidAgent was ++installed: ++ ++* local files via `file:` URIs ++* other URI schemes that might be misused by attackers, such as `gopher:` ++* hosts that resolve to loopback IP addresses (127.x.x.x) ++* hosts that resolve to RFC 1918 IP addresses (192.168.x.x etc.) ++ ++This could be used by an attacker to publish information that should not have ++been accessible, cause denial of service by requesting "tarpit" URIs that are ++slow to respond, or cause undesired side-effects if local web servers implement ++["unsafe"](https://tools.ietf.org/html/rfc7231#section-4.2.1) GET requests. ++([[!debcve CVE-2019-9187]]) ++ ++Additionally, if the LWPx::ParanoidAgent module was not installed, the ++[[plugins/blogspam]], [[plugins/openid]] and [[plugins/pinger]] plugins ++would fall back to [[!cpan LWP]], which is susceptible to similar attacks. ++This is unlikely to be a practical problem for the blogspam plugin because ++the URL it requests is under the control of the wiki administrator, but ++the openid plugin can request URLs controlled by unauthenticated remote ++users, and the pinger plugin can request URLs controlled by authorized ++wiki editors. ++ ++This is addressed in ikiwiki 3.20190228 as follows, with the same fixes ++backported to Debian 9 in version 3.20170111.1: ++ ++* URI schemes other than `http:` and `https:` are not accepted, preventing ++ access to `file:`, `gopher:`, etc. ++ ++* If a proxy is [[configured in the ikiwiki setup file|tips/using_a_proxy]], ++ it is used for all outgoing `http:` and `https:` requests. In this case ++ the proxy is responsible for blocking any requests that are undesired, ++ including loopback or RFC 1918 addresses. ++ ++* If a proxy is not configured, and LWPx::ParanoidAgent is installed, ++ it will be used. This prevents loopback and RFC 1918 IP addresses, and ++ sets a timeout to avoid denial of service via "tarpit" URIs. ++ ++* Otherwise, the ordinary LWP user-agent will be used. This allows requests ++ to loopback and RFC 1918 IP addresses, and has less robust timeout ++ behaviour. We are not treating this as a vulnerability: if this ++ behaviour is not acceptable for your site, please make sure to install ++ LWPx::ParanoidAgent or disable the affected plugins. +diff --git a/doc/tips/using_a_proxy.mdwn b/doc/tips/using_a_proxy.mdwn +new file mode 100644 +index 000000000..39df3c42a +--- /dev/null ++++ b/doc/tips/using_a_proxy.mdwn +@@ -0,0 +1,22 @@ ++Some ikiwiki plugins make outgoing HTTP requests from the web server: ++ ++* [[plugins/aggregate]] (to download Atom and RSS feeds) ++* [[plugins/blogspam]] (to check whether a comment or edit is spam) ++* [[plugins/openid]] (to authenticate users) ++* [[plugins/pinger]] (to ping other ikiwiki installations) ++ ++If your ikiwiki installation cannot contact the Internet without going ++through a proxy, you can configure this in the [[setup file|setup]] by ++setting environment variables: ++ ++ ENV: ++ http_proxy: "http://proxy.example.com:8080" ++ https_proxy: "http://proxy.example.com:8080" ++ # optional ++ no_proxy: ".example.com,www.example.org" ++ ++Note that some plugins will use the configured proxy for all destinations, ++even if they are listed in `no_proxy`. ++ ++To avoid server-side request forgery attacks, ensure that your proxy does ++not allow requests to addresses that are considered to be internal. +-- +2.11.0 + diff -Nru ikiwiki-3.20141016.4/debian/changelog ikiwiki-3.20141016.4+deb8u1/debian/changelog --- ikiwiki-3.20141016.4/debian/changelog 2017-01-12 05:18:52.000000000 +1100 +++ ikiwiki-3.20141016.4+deb8u1/debian/changelog 2019-03-07 17:35:55.000000000 +1100 @@ -1,3 +1,10 @@ +ikiwiki (3.20141016.4+deb8u1) jessie-security; urgency=high + + * Non-maintainer upload by the LTS Team. + * CVE-2019-9187: Fix server-side request forgery via aggregate plugin. + + -- Brian May <[email protected]> Thu, 07 Mar 2019 17:35:55 +1100 + ikiwiki (3.20141016.4) jessie-security; urgency=high * Reference CVE-2016-4561 in 3.20141016.3 changelog diff -Nru ikiwiki-3.20141016.4/debian/control ikiwiki-3.20141016.4+deb8u1/debian/control --- ikiwiki-3.20141016.4/debian/control 2017-01-12 05:18:52.000000000 +1100 +++ ikiwiki-3.20141016.4+deb8u1/debian/control 2019-03-07 17:35:55.000000000 +1100 @@ -17,7 +17,8 @@ libnet-openid-consumer-perl, libxml-feed-perl, libxml-parser-perl, - libxml-twig-perl + libxml-twig-perl, + liblwpx-paranoidagent-perl, Maintainer: Simon McVittie <[email protected]> Uploaders: Josh Triplett <[email protected]> Standards-Version: 3.9.5 diff -Nru ikiwiki-3.20141016.4/debian/patches/CVE-2019-9187-1.patch ikiwiki-3.20141016.4+deb8u1/debian/patches/CVE-2019-9187-1.patch --- ikiwiki-3.20141016.4/debian/patches/CVE-2019-9187-1.patch 1970-01-01 10:00:00.000000000 +1000 +++ ikiwiki-3.20141016.4+deb8u1/debian/patches/CVE-2019-9187-1.patch 2019-03-07 17:32:31.000000000 +1100 @@ -0,0 +1,23 @@ +From e7b0d4a0fff8ed45a90c2efe8ef294bdf7c9bdac Mon Sep 17 00:00:00 2001 +From: Simon McVittie <[email protected]> +Date: Sun, 10 Feb 2019 16:29:19 +0000 +Subject: [PATCH] useragent: Raise an exception if the LWP module can't be + loaded + +Signed-off-by: Simon McVittie <[email protected]> +--- + IkiWiki.pm | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/IkiWiki.pm ++++ b/IkiWiki.pm +@@ -2368,6 +2368,9 @@ + } + + sub useragent () { ++ eval q{use LWP}; ++ error($@) if $@; ++ + return LWP::UserAgent->new( + cookie_jar => $config{cookiejar}, + env_proxy => 1, # respect proxy env vars diff -Nru ikiwiki-3.20141016.4/debian/patches/CVE-2019-9187-2.patch ikiwiki-3.20141016.4+deb8u1/debian/patches/CVE-2019-9187-2.patch --- ikiwiki-3.20141016.4/debian/patches/CVE-2019-9187-2.patch 1970-01-01 10:00:00.000000000 +1000 +++ ikiwiki-3.20141016.4+deb8u1/debian/patches/CVE-2019-9187-2.patch 2019-03-07 17:32:43.000000000 +1100 @@ -0,0 +1,224 @@ +From 67543ce1d62161fdef9dca198289d7dd7dceacc0 Mon Sep 17 00:00:00 2001 +From: Simon McVittie <[email protected]> +Date: Sun, 10 Feb 2019 16:30:07 +0000 +Subject: [PATCH] useragent: Don't allow non-HTTP protocols to be used + +This prevents the aggregate plugin from being used to read the contents +of local files via file:/// URLs. + +Signed-off-by: Simon McVittie <[email protected]> +--- + IkiWiki.pm | 1 + + t/aggregate-file.t | 173 +++++++++++++++++++++++++++++++++++++ + t/noparanoia/LWPx/ParanoidAgent.pm | 2 + + t/secret.rss | 11 +++ + 4 files changed, 187 insertions(+) + create mode 100755 t/aggregate-file.t + create mode 100644 t/noparanoia/LWPx/ParanoidAgent.pm + create mode 100644 t/secret.rss + +--- a/IkiWiki.pm ++++ b/IkiWiki.pm +@@ -2375,6 +2375,7 @@ + cookie_jar => $config{cookiejar}, + env_proxy => 1, # respect proxy env vars + agent => $config{useragent}, ++ protocols_allowed => [qw(http https)], + ); + } + +--- /dev/null ++++ b/t/aggregate-file.t +@@ -0,0 +1,173 @@ ++#!/usr/bin/perl ++use utf8; ++use warnings; ++use strict; ++ ++use Encode; ++use Test::More; ++ ++BEGIN { ++ plan(skip_all => "CGI not available") ++ unless eval q{ ++ use CGI qw(); ++ 1; ++ }; ++ ++ plan(skip_all => "IPC::Run not available") ++ unless eval q{ ++ use IPC::Run qw(run); ++ 1; ++ }; ++ ++ use_ok('IkiWiki'); ++ use_ok('YAML::XS'); ++} ++ ++# We check for English error messages ++$ENV{LC_ALL} = 'C'; ++ ++use Cwd qw(getcwd); ++use Errno qw(ENOENT); ++ ++my $installed = $ENV{INSTALLED_TESTS}; ++ ++my @command; ++if ($installed) { ++ @command = qw(ikiwiki --plugin inline); ++} ++else { ++ ok(! system("make -s ikiwiki.out")); ++ @command = ("perl", "-I".getcwd."/blib/lib", './ikiwiki.out', ++ '--underlaydir='.getcwd.'/underlays/basewiki', ++ '--set', 'underlaydirbase='.getcwd.'/underlays', ++ '--templatedir='.getcwd.'/templates'); ++} ++ ++sub write_old_file { ++ my $name = shift; ++ my $dir = shift; ++ my $content = shift; ++ writefile($name, $dir, $content); ++ ok(utime(333333333, 333333333, "$dir/$name")); ++} ++ ++sub write_setup_file { ++ my %params = @_; ++ my %setup = ( ++ wikiname => 'this is the name of my wiki', ++ srcdir => getcwd.'/t/tmp/in', ++ destdir => getcwd.'/t/tmp/out', ++ url => 'http://example.com', ++ cgiurl => 'http://example.com/cgi-bin/ikiwiki.cgi', ++ cgi_wrapper => getcwd.'/t/tmp/ikiwiki.cgi', ++ cgi_wrappermode => '0751', ++ add_plugins => [qw(aggregate)], ++ disable_plugins => [qw(emailauth openid passwordauth)], ++ aggregate_webtrigger => 1, ++ ); ++ if ($params{without_paranoia}) { ++ $setup{libdirs} = [getcwd.'/t/noparanoia']; ++ } ++ unless ($installed) { ++ $setup{ENV} = { 'PERL5LIB' => getcwd.'/blib/lib' }; ++ } ++ writefile("test.setup", "t/tmp", ++ "# IkiWiki::Setup::Yaml - YAML formatted setup file\n" . ++ Dump(\%setup)); ++} ++ ++sub thoroughly_rebuild { ++ ok(unlink("t/tmp/ikiwiki.cgi") || $!{ENOENT}); ++ ok(! system(@command, qw(--setup t/tmp/test.setup --rebuild --wrappers))); ++} ++ ++sub run_cgi { ++ my (%args) = @_; ++ my ($in, $out); ++ my $method = $args{method} || 'GET'; ++ my $environ = $args{environ} || {}; ++ my $params = $args{params} || { do => 'prefs' }; ++ ++ my %defaults = ( ++ SCRIPT_NAME => '/cgi-bin/ikiwiki.cgi', ++ HTTP_HOST => 'example.com', ++ ); ++ ++ my $cgi = CGI->new($args{params}); ++ my $query_string = $cgi->query_string(); ++ diag $query_string; ++ ++ if ($method eq 'POST') { ++ $defaults{REQUEST_METHOD} = 'POST'; ++ $in = $query_string; ++ $defaults{CONTENT_LENGTH} = length $in; ++ } else { ++ $defaults{REQUEST_METHOD} = 'GET'; ++ $defaults{QUERY_STRING} = $query_string; ++ } ++ ++ my %envvars = ( ++ %defaults, ++ %$environ, ++ ); ++ run(["./t/tmp/ikiwiki.cgi"], \$in, \$out, init => sub { ++ map { ++ $ENV{$_} = $envvars{$_} ++ } keys(%envvars); ++ }); ++ ++ return decode_utf8($out); ++} ++ ++sub test { ++ my $content; ++ ++ ok(! system(qw(rm -rf t/tmp))); ++ ok(! system(qw(mkdir t/tmp))); ++ ++ write_old_file('aggregator.mdwn', 't/tmp/in', ++ '[[!aggregate name="ssrf" url="file://'.getcwd.'/t/secret.rss"]]' ++ .'[[!inline pages="internal(aggregator/*)"]]'); ++ ++ write_setup_file(); ++ thoroughly_rebuild(); ++ ++ $content = run_cgi( ++ method => 'GET', ++ params => { ++ do => 'aggregate_webtrigger', ++ }, ++ ); ++ unlike($content, qr{creating new page}); ++ unlike($content, qr{Secrets}); ++ ok(! -e 't/tmp/in/.ikiwiki/transient/aggregator/ssrf'); ++ ok(! -e 't/tmp/in/.ikiwiki/transient/aggregator/ssrf/Secrets_go_here._aggregated'); ++ ++ thoroughly_rebuild(); ++ $content = readfile('t/tmp/out/aggregator/index.html'); ++ unlike($content, qr{Secrets}); ++ ++ diag('Trying test again with LWPx::ParanoidAgent disabled'); ++ ++ write_setup_file(without_paranoia => 1); ++ thoroughly_rebuild(); ++ ++ $content = run_cgi( ++ method => 'GET', ++ params => { ++ do => 'aggregate_webtrigger', ++ }, ++ ); ++ unlike($content, qr{creating new page}); ++ unlike($content, qr{Secrets}); ++ ok(! -e 't/tmp/in/.ikiwiki/transient/aggregator/ssrf'); ++ ok(! -e 't/tmp/in/.ikiwiki/transient/aggregator/ssrf/Secrets_go_here._aggregated'); ++ ++ thoroughly_rebuild(); ++ $content = readfile('t/tmp/out/aggregator/index.html'); ++ unlike($content, qr{Secrets}); ++} ++ ++test(); ++ ++done_testing(); +--- /dev/null ++++ b/t/noparanoia/LWPx/ParanoidAgent.pm +@@ -0,0 +1,2 @@ ++# make import fail ++0; +--- /dev/null ++++ b/t/secret.rss +@@ -0,0 +1,11 @@ ++<?xml version="1.0"?> ++<rss version="2.0"> ++<channel> ++<title>Secrets go here</title> ++<description>Secrets go here</description> ++<item> ++ <title>Secrets go here</title> ++ <description>Secrets go here</description> ++</item> ++</channel> ++</rss> diff -Nru ikiwiki-3.20141016.4/debian/patches/CVE-2019-9187-3.patch ikiwiki-3.20141016.4+deb8u1/debian/patches/CVE-2019-9187-3.patch --- ikiwiki-3.20141016.4/debian/patches/CVE-2019-9187-3.patch 1970-01-01 10:00:00.000000000 +1000 +++ ikiwiki-3.20141016.4+deb8u1/debian/patches/CVE-2019-9187-3.patch 2019-03-07 17:32:58.000000000 +1100 @@ -0,0 +1,574 @@ +From d283e4ca1aeb6ca8cc0951c8495f778071076013 Mon Sep 17 00:00:00 2001 +From: Simon McVittie <[email protected]> +Date: Sun, 10 Feb 2019 17:22:06 +0000 +Subject: [PATCH] useragent: Automatically choose whether to use + LWPx::ParanoidAgent + +The simple implementation of this, which I'd prefer to use, would be: +if we can import LWPx::ParanoidAgent, use it; otherwise, use +LWP::UserAgent. + +However, aggregate has historically worked with proxies, and +LWPx::ParanoidAgent quite reasonably refuses to work with proxies +(because it can't know whether those proxies are going to do the same +filtering that LWPx::ParanoidAgent would). + +Signed-off-by: Simon McVittie <[email protected]> +--- + IkiWiki.pm | 123 ++++++++++++++++- + IkiWiki/Plugin/aggregate.pm | 5 +- + IkiWiki/Plugin/blogspam.pm | 16 +-- + IkiWiki/Plugin/openid.pm | 12 +- + IkiWiki/Plugin/pinger.pm | 21 ++- + t/useragent.t | 317 ++++++++++++++++++++++++++++++++++++++++++++ + 6 files changed, 458 insertions(+), 36 deletions(-) + create mode 100755 t/useragent.t + +--- a/IkiWiki.pm ++++ b/IkiWiki.pm +@@ -2367,16 +2367,131 @@ + $autofiles{$file}{generator}=$generator; + } + +-sub useragent () { ++sub useragent (@) { ++ my %params = @_; ++ my $for_url = delete $params{for_url}; ++ # Fail safe, in case a plugin calling this function is relying on ++ # a future parameter to make the UA more strict ++ foreach my $key (keys %params) { ++ error "Internal error: useragent(\"$key\" => ...) not understood"; ++ } ++ + eval q{use LWP}; + error($@) if $@; + +- return LWP::UserAgent->new( +- cookie_jar => $config{cookiejar}, +- env_proxy => 1, # respect proxy env vars ++ my %args = ( + agent => $config{useragent}, ++ cookie_jar => $config{cookiejar}, ++ env_proxy => 0, + protocols_allowed => [qw(http https)], + ); ++ my %proxies; ++ ++ if (defined $for_url) { ++ # We know which URL we're going to fetch, so we can choose ++ # whether it's going to go through a proxy or not. ++ # ++ # We reimplement http_proxy, https_proxy and no_proxy here, so ++ # that we are not relying on LWP implementing them exactly the ++ # same way we do. ++ ++ eval q{use URI}; ++ error($@) if $@; ++ ++ my $proxy; ++ my $uri = URI->new($for_url); ++ ++ if ($uri->scheme eq 'http') { ++ $proxy = $ENV{http_proxy}; ++ # HTTP_PROXY is deliberately not implemented ++ # because the HTTP_* namespace is also used by CGI ++ } ++ elsif ($uri->scheme eq 'https') { ++ $proxy = $ENV{https_proxy}; ++ $proxy = $ENV{HTTPS_PROXY} unless defined $proxy; ++ } ++ else { ++ $proxy = undef; ++ } ++ ++ foreach my $var (qw(no_proxy NO_PROXY)) { ++ my $no_proxy = $ENV{$var}; ++ if (defined $no_proxy) { ++ foreach my $domain (split /\s*,\s*/, $no_proxy) { ++ if ($domain =~ s/^\*?\.//) { ++ # no_proxy="*.example.com" or ++ # ".example.com": match suffix ++ # against .example.com ++ if ($uri->host =~ m/(^|\.)\Q$domain\E$/i) { ++ $proxy = undef; ++ } ++ } ++ else { ++ # no_proxy="example.com": ++ # match exactly example.com ++ if (lc $uri->host eq lc $domain) { ++ $proxy = undef; ++ } ++ } ++ } ++ } ++ } ++ ++ if (defined $proxy) { ++ $proxies{$uri->scheme} = $proxy; ++ # Paranoia: make sure we can't bypass the proxy ++ $args{protocols_allowed} = [$uri->scheme]; ++ } ++ } ++ else { ++ # The plugin doesn't know yet which URL(s) it's going to ++ # fetch, so we have to make some conservative assumptions. ++ my $http_proxy = $ENV{http_proxy}; ++ my $https_proxy = $ENV{https_proxy}; ++ $https_proxy = $ENV{HTTPS_PROXY} unless defined $https_proxy; ++ ++ # We don't respect no_proxy here: if we are not using the ++ # paranoid user-agent, then we need to give the proxy the ++ # opportunity to reject undesirable requests. ++ ++ # If we have one, we need the other: otherwise, neither ++ # LWPx::ParanoidAgent nor the proxy would have the ++ # opportunity to filter requests for the other protocol. ++ if (defined $https_proxy && defined $http_proxy) { ++ %proxies = (http => $http_proxy, https => $https_proxy); ++ } ++ elsif (defined $https_proxy) { ++ %proxies = (http => $https_proxy, https => $https_proxy); ++ } ++ elsif (defined $http_proxy) { ++ %proxies = (http => $http_proxy, https => $http_proxy); ++ } ++ ++ } ++ ++ if (scalar keys %proxies) { ++ # The configured proxy is responsible for deciding which ++ # URLs are acceptable to fetch and which URLs are not. ++ my $ua = LWP::UserAgent->new(%args); ++ foreach my $scheme (@{$ua->protocols_allowed}) { ++ unless ($proxies{$scheme}) { ++ error "internal error: $scheme is allowed but has no proxy"; ++ } ++ } ++ # We can't pass the proxies in %args because that only ++ # works since LWP 6.24. ++ foreach my $scheme (keys %proxies) { ++ $ua->proxy($scheme, $proxies{$scheme}); ++ } ++ return $ua; ++ } ++ ++ eval q{use LWPx::ParanoidAgent}; ++ if ($@) { ++ print STDERR "warning: installing LWPx::ParanoidAgent is recommended\n"; ++ return LWP::UserAgent->new(%args); ++ } ++ return LWPx::ParanoidAgent->new(%args); + } + + sub sortspec_translate ($$) { +--- a/IkiWiki/Plugin/aggregate.pm ++++ b/IkiWiki/Plugin/aggregate.pm +@@ -513,7 +513,10 @@ + } + $feed->{feedurl}=pop @urls; + } +- my $ua=useragent(); ++ # Using the for_url parameter makes sure we crash if used ++ # with an older IkiWiki.pm that didn't automatically try ++ # to use LWPx::ParanoidAgent. ++ my $ua=useragent(for_url => $feed->{feedurl}); + my $res=URI::Fetch->fetch($feed->{feedurl}, UserAgent=>$ua); + if (! $res) { + $feed->{message}=URI::Fetch->errstr; +--- a/IkiWiki/Plugin/blogspam.pm ++++ b/IkiWiki/Plugin/blogspam.pm +@@ -57,18 +57,10 @@ + }; + error $@ if $@; + +- eval q{use LWPx::ParanoidAgent}; +- if (!$@) { +- $client=LWPx::ParanoidAgent->new(agent => $config{useragent}); +- } +- else { +- eval q{use LWP}; +- if ($@) { +- error $@; +- return; +- } +- $client=useragent(); +- } ++ # Using the for_url parameter makes sure we crash if used ++ # with an older IkiWiki.pm that didn't automatically try ++ # to use LWPx::ParanoidAgent. ++ $client=useragent(for_url => $config{blogspam_server}); + } + + sub checkcontent (@) { +--- a/IkiWiki/Plugin/openid.pm ++++ b/IkiWiki/Plugin/openid.pm +@@ -237,14 +237,10 @@ + eval q{use Net::OpenID::Consumer}; + error($@) if $@; + +- my $ua; +- eval q{use LWPx::ParanoidAgent}; +- if (! $@) { +- $ua=LWPx::ParanoidAgent->new(agent => $config{useragent}); +- } +- else { +- $ua=useragent(); +- } ++ # We pass the for_url parameter, even though it's undef, because ++ # that will make sure we crash if used with an older IkiWiki.pm ++ # that didn't automatically try to use LWPx::ParanoidAgent. ++ my $ua=useragent(for_url => undef); + + # Store the secret in the session. + my $secret=$session->param("openid_secret"); +--- a/IkiWiki/Plugin/pinger.pm ++++ b/IkiWiki/Plugin/pinger.pm +@@ -70,17 +70,16 @@ + eval q{use Net::INET6Glue::INET_is_INET6}; # may not be available + + my $ua; +- eval q{use LWPx::ParanoidAgent}; +- if (!$@) { +- $ua=LWPx::ParanoidAgent->new(agent => $config{useragent}); +- } +- else { +- eval q{use LWP}; +- if ($@) { +- debug(gettext("LWP not found, not pinging")); +- return; +- } +- $ua=useragent(); ++ eval { ++ # We pass the for_url parameter, even though it's ++ # undef, because that will make sure we crash if used ++ # with an older IkiWiki.pm that didn't automatically ++ # try to use LWPx::ParanoidAgent. ++ $ua=useragent(for_url => undef); ++ }; ++ if ($@) { ++ debug(gettext("LWP not found, not pinging").": $@"); ++ return; + } + $ua->timeout($config{pinger_timeout} || 15); + +--- /dev/null ++++ b/t/useragent.t +@@ -0,0 +1,317 @@ ++#!/usr/bin/perl ++use warnings; ++use strict; ++use Test::More; ++ ++my $have_paranoid_agent; ++BEGIN { ++ plan(skip_all => 'LWP not available') ++ unless eval q{ ++ use LWP qw(); 1; ++ }; ++ use_ok("IkiWiki"); ++ $have_paranoid_agent = eval q{ ++ use LWPx::ParanoidAgent qw(); 1; ++ }; ++} ++ ++eval { useragent(future_feature => 1); }; ++ok($@, 'future features should cause useragent to fail'); ++ ++diag "==== No proxy ===="; ++delete $ENV{http_proxy}; ++delete $ENV{https_proxy}; ++delete $ENV{no_proxy}; ++delete $ENV{HTTPS_PROXY}; ++delete $ENV{NO_PROXY}; ++ ++diag "---- Unspecified URL ----"; ++my $ua = useragent(for_url => undef); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef, 'No http proxy'); ++is($ua->proxy('https'), undef, 'No https proxy'); ++ ++diag "---- Specified URL ----"; ++$ua = useragent(for_url => 'http://example.com'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef, 'No http proxy'); ++is($ua->proxy('https'), undef, 'No https proxy'); ++ ++diag "==== Proxy for everything ===="; ++$ENV{http_proxy} = 'http://proxy:8080'; ++$ENV{https_proxy} = 'http://sproxy:8080'; ++delete $ENV{no_proxy}; ++delete $ENV{HTTPS_PROXY}; ++delete $ENV{NO_PROXY}; ++ ++diag "---- Unspecified URL ----"; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use CONNECT proxy'); ++$ua = useragent(for_url => 'http://example.com'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++# We don't care what $ua->proxy('https') is, because it won't be used ++$ua = useragent(for_url => 'https://example.com'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++# We don't care what $ua->proxy('http') is, because it won't be used ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use CONNECT proxy'); ++ ++diag "==== Selective proxy ===="; ++$ENV{http_proxy} = 'http://proxy:8080'; ++$ENV{https_proxy} = 'http://sproxy:8080'; ++$ENV{no_proxy} = '*.example.net,example.com,.example.org'; ++delete $ENV{HTTPS_PROXY}; ++delete $ENV{NO_PROXY}; ++ ++diag "---- Unspecified URL ----"; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use CONNECT proxy'); ++ ++diag "---- Exact match for no_proxy ----"; ++$ua = useragent(for_url => 'http://example.com'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- Subdomain of exact domain in no_proxy ----"; ++$ua = useragent(for_url => 'http://sub.example.com'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++ ++diag "---- example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- sub.example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://sub.example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- badexample.net does not match *.example.net ----"; ++$ua = useragent(for_url => 'https://badexample.net'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++diag "---- example.org matches .example.org ----"; ++$ua = useragent(for_url => 'https://example.org'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- sub.example.org matches .example.org ----"; ++$ua = useragent(for_url => 'https://sub.example.org'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- badexample.org does not match .example.org ----"; ++$ua = useragent(for_url => 'https://badexample.org'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++diag "==== Selective proxy (alternate variables) ===="; ++$ENV{http_proxy} = 'http://proxy:8080'; ++delete $ENV{https_proxy}; ++$ENV{HTTPS_PROXY} = 'http://sproxy:8080'; ++delete $ENV{no_proxy}; ++$ENV{NO_PROXY} = '*.example.net,example.com,.example.org'; ++ ++diag "---- Unspecified URL ----"; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use CONNECT proxy'); ++ ++diag "---- Exact match for no_proxy ----"; ++$ua = useragent(for_url => 'http://example.com'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- Subdomain of exact domain in no_proxy ----"; ++$ua = useragent(for_url => 'http://sub.example.com'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++ ++diag "---- example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- sub.example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://sub.example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- badexample.net does not match *.example.net ----"; ++$ua = useragent(for_url => 'https://badexample.net'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++diag "---- example.org matches .example.org ----"; ++$ua = useragent(for_url => 'https://example.org'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- sub.example.org matches .example.org ----"; ++$ua = useragent(for_url => 'https://sub.example.org'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- badexample.org does not match .example.org ----"; ++$ua = useragent(for_url => 'https://badexample.org'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++diag "==== Selective proxy (many variables) ===="; ++$ENV{http_proxy} = 'http://proxy:8080'; ++$ENV{https_proxy} = 'http://sproxy:8080'; ++# This one should be ignored in favour of https_proxy ++$ENV{HTTPS_PROXY} = 'http://not.preferred.proxy:3128'; ++# These two should be merged ++$ENV{no_proxy} = '*.example.net,example.com'; ++$ENV{NO_PROXY} = '.example.org'; ++ ++diag "---- Unspecified URL ----"; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use CONNECT proxy'); ++ ++diag "---- Exact match for no_proxy ----"; ++$ua = useragent(for_url => 'http://example.com'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- Subdomain of exact domain in no_proxy ----"; ++$ua = useragent(for_url => 'http://sub.example.com'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++ ++diag "---- example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- sub.example.net matches *.example.net ----"; ++$ua = useragent(for_url => 'https://sub.example.net'); ++SKIP: { ++ skip 'paranoid agent not available', 1 unless $have_paranoid_agent; ++ ok($ua->isa('LWPx::ParanoidAgent'), 'uses ParanoidAgent if possible'); ++} ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), undef); ++is($ua->proxy('https'), undef); ++ ++diag "---- badexample.net does not match *.example.net ----"; ++$ua = useragent(for_url => 'https://badexample.net'); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(https)]); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++diag "==== One but not the other ====\n"; ++$ENV{http_proxy} = 'http://proxy:8080'; ++delete $ENV{https_proxy}; ++delete $ENV{HTTPS_PROXY}; ++delete $ENV{no_proxy}; ++delete $ENV{NO_PROXY}; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://proxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://proxy:8080', 'should use proxy'); ++ ++delete $ENV{http_proxy}; ++$ENV{https_proxy} = 'http://sproxy:8080'; ++delete $ENV{HTTPS_PROXY}; ++delete $ENV{no_proxy}; ++delete $ENV{NO_PROXY}; ++$ua = useragent(for_url => undef); ++ok(! $ua->isa('LWPx::ParanoidAgent'), 'should use proxy instead of ParanoidAgent'); ++is_deeply([sort @{$ua->protocols_allowed}], [sort qw(http https)]); ++is($ua->proxy('http'), 'http://sproxy:8080', 'should use proxy'); ++is($ua->proxy('https'), 'http://sproxy:8080', 'should use proxy'); ++ ++done_testing; diff -Nru ikiwiki-3.20141016.4/debian/patches/CVE-2019-9187-4.patch ikiwiki-3.20141016.4+deb8u1/debian/patches/CVE-2019-9187-4.patch --- ikiwiki-3.20141016.4/debian/patches/CVE-2019-9187-4.patch 1970-01-01 10:00:00.000000000 +1000 +++ ikiwiki-3.20141016.4+deb8u1/debian/patches/CVE-2019-9187-4.patch 2019-03-07 17:35:45.000000000 +1100 @@ -0,0 +1,159 @@ +From 9a275b2f1846d7268c71a740975447e269383849 Mon Sep 17 00:00:00 2001 +From: Simon McVittie <[email protected]> +Date: Sun, 10 Feb 2019 16:56:41 +0000 +Subject: [PATCH] doc: Document security issues involving LWP::UserAgent + +Recommend the LWPx::ParanoidAgent module where appropriate. +It is particularly important for openid, since unauthenticated users +can control which URLs that plugin will contact. Conversely, it is +non-critical for blogspam, since the URL to be contacted is under +the wiki administrator's control. + +Signed-off-by: Simon McVittie <[email protected]> +--- + doc/plugins/aggregate.mdwn | 4 ++++ + doc/plugins/blogspam.mdwn | 2 ++ + doc/plugins/openid.mdwn | 7 +++++-- + doc/plugins/pinger.mdwn | 8 +++++--- + doc/security.mdwn | 49 +++++++++++++++++++++++++++++++++++++++++++++ + doc/tips/using_a_proxy.mdwn | 22 ++++++++++++++++++++ + 6 files changed, 87 insertions(+), 5 deletions(-) + create mode 100644 doc/tips/using_a_proxy.mdwn + +--- a/doc/plugins/aggregate.mdwn ++++ b/doc/plugins/aggregate.mdwn +@@ -11,6 +11,10 @@ + one. Either the [[htmltidy]] or [[htmlbalance]] plugin is suggested, since + feeds can easily contain html problems, some of which these plugins can fix. + ++Installing the [[!cpan LWPx::ParanoidAgent]] Perl module is strongly ++recommended. The [[!cpan LWP]] module can also be used, but is susceptible ++to server-side request forgery. ++ + ## triggering aggregation + + You will need to run ikiwiki periodically from a cron job, passing it the +--- a/doc/plugins/blogspam.mdwn ++++ b/doc/plugins/blogspam.mdwn +@@ -11,6 +11,8 @@ + go to your Preferences page, and click the "Comment Moderation" button. + + The plugin requires the [[!cpan JSON]] perl module. ++The [[!cpan LWPx::ParanoidAgent]] Perl module is recommended, ++although this plugin can also fall back to [[!cpan LWP]]. + + You can control how content is tested via the `blogspam_options` setting. + The list of options is [here](http://blogspam.net/api/testComment.html#options). +--- a/doc/plugins/openid.mdwn ++++ b/doc/plugins/openid.mdwn +@@ -7,8 +7,11 @@ + The plugin needs the [[!cpan Net::OpenID::Consumer]] perl module. + Version 1.x is needed in order for OpenID v2 to work. + +-The [[!cpan LWPx::ParanoidAgent]] perl module is used if available, for +-added security. Finally, the [[!cpan Crypt::SSLeay]] perl module is needed ++The [[!cpan LWPx::ParanoidAgent]] Perl module is strongly recommended. ++The [[!cpan LWP]] module can also be used, but is susceptible to ++server-side request forgery. ++ ++The [[!cpan Crypt::SSLeay]] Perl module is needed + to support users entering "https" OpenID urls. + + This plugin is enabled by default, but can be turned off if you want to +--- a/doc/plugins/pinger.mdwn ++++ b/doc/plugins/pinger.mdwn +@@ -10,9 +10,11 @@ + To configure what URLs to ping, use the [[ikiwiki/directive/ping]] + [[ikiwiki/directive]]. + +-The [[!cpan LWP]] perl module is used for pinging. Or the [[!cpan +-LWPx::ParanoidAgent]] perl module is used if available, for added security. +-Finally, the [[!cpan Crypt::SSLeay]] perl module is needed to support pinging ++The [[!cpan LWPx::ParanoidAgent]] Perl module is strongly recommended. ++The [[!cpan LWP]] module can also be used, but is susceptible ++to server-side request forgery. ++ ++The [[!cpan Crypt::SSLeay]] perl module is needed to support pinging + "https" urls. + + By default the pinger will try to ping a site for 15 seconds before timing +--- a/doc/security.mdwn ++++ b/doc/security.mdwn +@@ -526,3 +526,52 @@ + able to attach images. Upgrading ImageMagick to a version where + CVE-2016-3714 has been fixed is also recommended, but at the time of + writing no such version is available. ++ ++## Server-side request forgery via aggregate plugin ++ ++The ikiwiki maintainers discovered that the [[plugins/aggregate]] plugin ++did not use [[!cpan LWPx::ParanoidAgent]]. On sites where the ++aggregate plugin is enabled, authorized wiki editors could tell ikiwiki ++to fetch potentially undesired URIs even if LWPx::ParanoidAgent was ++installed: ++ ++* local files via `file:` URIs ++* other URI schemes that might be misused by attackers, such as `gopher:` ++* hosts that resolve to loopback IP addresses (127.x.x.x) ++* hosts that resolve to RFC 1918 IP addresses (192.168.x.x etc.) ++ ++This could be used by an attacker to publish information that should not have ++been accessible, cause denial of service by requesting "tarpit" URIs that are ++slow to respond, or cause undesired side-effects if local web servers implement ++["unsafe"](https://tools.ietf.org/html/rfc7231#section-4.2.1) GET requests. ++([[!debcve CVE-2019-9187]]) ++ ++Additionally, if the LWPx::ParanoidAgent module was not installed, the ++[[plugins/blogspam]], [[plugins/openid]] and [[plugins/pinger]] plugins ++would fall back to [[!cpan LWP]], which is susceptible to similar attacks. ++This is unlikely to be a practical problem for the blogspam plugin because ++the URL it requests is under the control of the wiki administrator, but ++the openid plugin can request URLs controlled by unauthenticated remote ++users, and the pinger plugin can request URLs controlled by authorized ++wiki editors. ++ ++This is addressed in ikiwiki 3.20190228 as follows, with the same fixes ++backported to Debian 9 in version 3.20170111.1: ++ ++* URI schemes other than `http:` and `https:` are not accepted, preventing ++ access to `file:`, `gopher:`, etc. ++ ++* If a proxy is [[configured in the ikiwiki setup file|tips/using_a_proxy]], ++ it is used for all outgoing `http:` and `https:` requests. In this case ++ the proxy is responsible for blocking any requests that are undesired, ++ including loopback or RFC 1918 addresses. ++ ++* If a proxy is not configured, and LWPx::ParanoidAgent is installed, ++ it will be used. This prevents loopback and RFC 1918 IP addresses, and ++ sets a timeout to avoid denial of service via "tarpit" URIs. ++ ++* Otherwise, the ordinary LWP user-agent will be used. This allows requests ++ to loopback and RFC 1918 IP addresses, and has less robust timeout ++ behaviour. We are not treating this as a vulnerability: if this ++ behaviour is not acceptable for your site, please make sure to install ++ LWPx::ParanoidAgent or disable the affected plugins. +--- /dev/null ++++ b/doc/tips/using_a_proxy.mdwn +@@ -0,0 +1,22 @@ ++Some ikiwiki plugins make outgoing HTTP requests from the web server: ++ ++* [[plugins/aggregate]] (to download Atom and RSS feeds) ++* [[plugins/blogspam]] (to check whether a comment or edit is spam) ++* [[plugins/openid]] (to authenticate users) ++* [[plugins/pinger]] (to ping other ikiwiki installations) ++ ++If your ikiwiki installation cannot contact the Internet without going ++through a proxy, you can configure this in the [[setup file|setup]] by ++setting environment variables: ++ ++ ENV: ++ http_proxy: "http://proxy.example.com:8080" ++ https_proxy: "http://proxy.example.com:8080" ++ # optional ++ no_proxy: ".example.com,www.example.org" ++ ++Note that some plugins will use the configured proxy for all destinations, ++even if they are listed in `no_proxy`. ++ ++To avoid server-side request forgery attacks, ensure that your proxy does ++not allow requests to addresses that are considered to be internal. diff -Nru ikiwiki-3.20141016.4/debian/patches/series ikiwiki-3.20141016.4+deb8u1/debian/patches/series --- ikiwiki-3.20141016.4/debian/patches/series 1970-01-01 10:00:00.000000000 +1000 +++ ikiwiki-3.20141016.4+deb8u1/debian/patches/series 2019-03-07 17:35:55.000000000 +1100 @@ -0,0 +1,4 @@ +CVE-2019-9187-1.patch +CVE-2019-9187-2.patch +CVE-2019-9187-3.patch +CVE-2019-9187-4.patch
