package Template::Secure;
use strict;
use warnings;
use base qw(Template);
use Carp;
use Scalar::Util qw(tainted);
# simplified version of Template's process method which only supports
# output to STDOUT
sub process {
my ($self, $template, $vars) = @_;
my $output = '';
my $ret = $self->SUPER::process($template, $vars, \$output);
if(tainted $output) {
croak("Insecure dependency in Template::Secure->process()");
}
print $output;
return $ret;
}
This is almost drop-in replacement module for Template module which will
complain if it notices any tainted data in output. Another missing
piece is convenient plugin for Template which would implement HTML/URL
escaping in output and untaint escaped strings at same time. Template toolkit
provides two plugins useful to do escaping of strings in HTML:
Template::Plugin::URL and Template::Plugin::HTML. They only have to be
slighly changed to untaint escaped strings.
# a bit hacky way to redefine subs without modifying sources; this
# code can be put directly into Template::Secure module
{
require Template::Plugin::URL;
require Template::Plugin::HTML;
no warnings 'redefine';
my $url_escape_sub = \&Template::Plugin::URL::escape;
*Template::Plugin::URL::escape = sub {
my $ret = $url_escape_sub->(@_);
$ret =~ /(.*)/; # untaints string
return $1;
};
my $html_escape_sub = \&Template::Plugin::HTML::escape;
*Template::Plugin::HTML::escape = sub {
my $ret = $html_escape_sub->(@_);
$ret =~ /(.*)/; # untaints string
return $1;
};
}
Now example application: nearly hello world :).
#!/usr/bin/perl -T
# First version which has XSS hole and doesn't work thanks to taint
# checks in Template::Secure
use strict;
use warnings;
use CGI;
use Template::Secure;
my $query = CGI->new;
my $name = $query->param('name') || 'World';
my $tt = Template::Secure->new;
print $query->header;
$tt->process(\*DATA, { name => $name }) || die $tt->error(), "\n";
__END__
<html>
<head>
<title>Sample program</title>
</head>
<body>
Hello, [% name %]!
</body>
</html>
#!/usr/bin/perl -T
# Second version which is XSS free
use strict;
use warnings;
use CGI;
use Template::Secure;
my $query = CGI->new;
my $name = $query->param('name') || 'World';
my $tt = Template::Secure->new;
print $query->header;
$tt->process(\*DATA, { name => $name }) || die $tt->error(), "\n";
__END__
[% USE HTML %]
<html>
<head>
<title>Sample program</title>
</head>
<body>
Hello, [% HTML.escape(name) %]!
</body>
</html>
P.S. Note that it is merely proof-of-concept just to show the
idea. There are probably some missing pieces (for example real
implementation should untaint templates which are read by Template Toolkit from filesystem). And I'm sure same idea can be ported to other
templating modules.
--
Ilya Martynov, ilya@iponweb.net
CTO IPonWEB (UK) Ltd
Quality Perl Programming and Unix Support
UK managed @ offshore prices - http://www.iponweb.net
Personal website - http://martynov.org