Bug 22223: Add filter to make item URLs safe in template output

This change adds a "safe_url" filter which takes a text input and
returns a Perl URL object which stringifies to a safe URL.

This change is only needed in the OPAC as the staff interface
handles the item URL display using Javascript not Template Toolkit.

0. Apply patch and koha-plack --restart kohadev
1. Create an item for a record using the following URL
https://koha-community.org?url=https%3A%2F%2Fkoha-community.org
2. Go to the OPAC for that record and verify that the URL is
not double-escaped
3. Create a malicious payload (talk to QA/security team for this if necessary)
4. Note that the malicious payload is escaped
5. prove t/Koha/Plugins/SafeURL.t
6. Celebrate!

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
(cherry picked from commit e8e00cfeb3)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
David Cook 2024-08-20 00:12:21 +00:00 committed by Lucas Gass
parent fde180efbf
commit 629d2a6f6c
3 changed files with 126 additions and 2 deletions

View File

@ -0,0 +1,66 @@
package Koha::Template::Plugin::SafeURL;
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use URI;
use base 'Template::Plugin::Filter';
sub init {
my $self = shift;
my $name = 'safe_url';
$self->{_DYNAMIC} = 1;
$self->install_filter($name);
return $self;
}
sub filter {
my ( $self, $text, $args, $config ) = @_;
my $uri = URI->new($text);
if ($uri) {
return $uri;
}
#NOTE: If it isn't a real URL, just return a safe fragment
return '#';
}
1;
=head1 NAME
Koha::Template::Plugin::SafeURL - TT Plugin for filtering whole URLs
=head1 SYNOPSIS
[% USE SafeURL %]
[% $url | safe_url %]
This filter parses the text as a URL and returns the object which
is stringified in a safe format.
=head1 METHODS
=head2 init
This method installs the filter name and declares it as a dynamic filter
=head2 filter
Returns a stringified version of a Perl URL object
=cut

View File

@ -7,6 +7,7 @@
[% USE TablesSettings %]
[% USE AuthorisedValues %]
[% USE KohaPlugins %]
[% USE SafeURL %]
[% SET TagsShowEnabled = ( ( Koha.Preference( 'TagsEnabled' ) == 1 ) && TagsShowOnDetail ) %]
[% SET TagsInputEnabled = ( ( Koha.Preference( 'opacuserlogin' ) == 1 ) && ( Koha.Preference( 'TagsEnabled' ) == 1 ) && TagsInputOnDetail ) %]
[% SET CoverImagePlugins = KohaPlugins.get_plugins_opac_cover_images %]
@ -1362,14 +1363,14 @@
[% IF trackclicks == 'track' || trackclicks == 'anonymous' %]
<a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="/cgi-bin/koha/tracklinks.pl?uri=[% uri | uri %]&amp;biblionumber=[% biblio.biblionumber | html %]&amp;itemnumber=[% ITEM_RESULT.itemnumber | html %]" property="url">[% uri | html %]</a>
[% ELSE %]
<a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="[% uri | url %]" property="url">[% uri | html %]</a>
<a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="[% uri | safe_url %]" property="url">[% uri | html %]</a>
[% END %]
[% END %]
[% ELSE %]
[% IF trackclicks == 'track' || trackclicks == 'anonymous' %]
<a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="/cgi-bin/koha/tracklinks.pl?uri=[% ITEM_RESULT.uri | uri %]&amp;biblionumber=[% biblio.biblionumber | html %]&amp;itemnumber=[% ITEM_RESULT.itemnumber | html %]" property="url">
[% ELSE %]
<a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="[% ITEM_RESULT.uri | url %]" property="url">
<a target="[% target_attr | html %]" rel="[% rel_attr | html %]" href="[% ITEM_RESULT.uri | safe_url %]" property="url">
[% END %]
[% IF Koha.Preference('URLLinkText') %][% Koha.Preference('URLLinkText') | html %][% ELSE %]Link to resource[% END %]
</a>

57
t/Koha/Plugins/SafeURL.t Executable file
View File

@ -0,0 +1,57 @@
#!/usr/bin/perl
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use Test::More tests => 2;
use Template;
subtest 'test double-encoding prevention' => sub {
plan tests => 1;
my $template = Template->new(
{
PLUGIN_BASE => 'Koha::Template::Plugin',
}
);
my $tt = <<EOF;
[%- USE SafeURL %]
[%- 'http://koha-community.org?url=https%3A%2F%2Fkoha-community.org' | safe_url -%]
EOF
my $output;
$template->process( \$tt, {}, \$output );
is( $output, 'http://koha-community.org?url=https%3A%2F%2Fkoha-community.org', 'URL is not double-encoded' );
};
subtest 'double quotes are escaped' => sub {
plan tests => 1;
my $template = Template->new(
{
PLUGIN_BASE => 'Koha::Template::Plugin',
}
);
my $tt = <<EOF;
[%- USE SafeURL %]
[%- 'http://koha-community.org?url=https%3A%2F%2Fkoha-community.org"' | safe_url -%]
EOF
my $output;
$template->process( \$tt, {}, \$output );
is( $output, 'http://koha-community.org?url=https%3A%2F%2Fkoha-community.org%22', 'double quotes are escaped' );
};