Skip to content

Commit 6a810bd

Browse files
John Lightseytonycoz
John Lightsey
authored andcommitted
Add blacklist and whitelist support to Locale::Maketext.
Format string attacks against Locale::Maketext have been discovered in several popular web applications and addresed by pre-filtering maketext strings before they are fed into the maketext() method. It is now possible to restrict the allowed bracked notation methods directly in Maketext. This commit also introduces a default blacklist that prevents using the object and class methods in the Locale::Maketext namespace that were not intended as bracked notation methods.
1 parent f54530a commit 6a810bd

File tree

4 files changed

+322
-9
lines changed

4 files changed

+322
-9
lines changed

dist/Locale-Maketext/lib/Locale/Maketext.pm

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
package Locale::Maketext;
32
use strict;
43
use vars qw( @ISA $VERSION $MATCH_SUPERS $USING_LANGUAGE_TAGS
@@ -138,6 +137,56 @@ sub fail_with { # an actual attribute method!
138137

139138
#--------------------------------------------------------------------------
140139

140+
sub blacklist {
141+
my ( $handle, @methods ) = @_;
142+
143+
unless ( defined $handle->{'blacklist'} ) {
144+
no strict 'refs';
145+
146+
# Don't let people call methods they're not supposed to from maketext.
147+
# Explicitly exclude all methods in this package that start with an
148+
# underscore on principle.
149+
$handle->{'blacklist'} = {
150+
map { $_ => 1 } (
151+
qw/
152+
blacklist
153+
encoding
154+
fail_with
155+
failure_handler_auto
156+
fallback_language_classes
157+
fallback_languages
158+
get_handle
159+
init
160+
language_tag
161+
maketext
162+
new
163+
whitelist
164+
/, grep { /^_/ } keys %{ __PACKAGE__ . "::" }
165+
),
166+
};
167+
}
168+
169+
if ( scalar @methods ) {
170+
$handle->{'blacklist'} = { %{ $handle->{'blacklist'} }, map { $_ => 1 } @methods };
171+
}
172+
173+
delete $handle->{'_external_lex_cache'};
174+
return;
175+
}
176+
177+
sub whitelist {
178+
my ( $handle, @methods ) = @_;
179+
if ( scalar @methods ) {
180+
$handle->{'whitelist'} = {} unless defined $handle->{'whitelist'};
181+
$handle->{'whitelist'} = { %{ $handle->{'whitelist'} }, map { $_ => 1 } @methods };
182+
}
183+
184+
delete $handle->{'_external_lex_cache'};
185+
return;
186+
}
187+
188+
#--------------------------------------------------------------------------
189+
141190
sub failure_handler_auto {
142191
# Meant to be used like:
143192
# $handle->fail_with('failure_handler_auto')
@@ -179,6 +228,7 @@ sub new {
179228
# Nothing fancy!
180229
my $class = ref($_[0]) || $_[0];
181230
my $handle = bless {}, $class;
231+
$handle->blacklist;
182232
$handle->init;
183233
return $handle;
184234
}
@@ -508,7 +558,7 @@ sub _compile {
508558
# on strings that don't need compiling.
509559
return \"$string_to_compile" if($string_to_compile !~ m/[\[~\]]/ms); # return a string ref if chars [~] are not in the string
510560

511-
my $target = ref($_[0]) || $_[0];
561+
my $handle = $_[0];
512562

513563
my(@code);
514564
my(@c) = (''); # "chunks" -- scratch.
@@ -540,10 +590,10 @@ sub _compile {
540590
# preceding literal.
541591
if($in_group) {
542592
if($1 eq '') {
543-
$target->_die_pointing($string_to_compile, 'Unterminated bracket group');
593+
$handle->_die_pointing($string_to_compile, 'Unterminated bracket group');
544594
}
545595
else {
546-
$target->_die_pointing($string_to_compile, 'You can\'t nest bracket groups');
596+
$handle->_die_pointing($string_to_compile, 'You can\'t nest bracket groups');
547597
}
548598
}
549599
else {
@@ -627,13 +677,15 @@ sub _compile {
627677
push @code, ' (';
628678
}
629679
elsif($m =~ /^\w+$/s
630-
# exclude anything fancy, especially fully-qualified module names
680+
&& !$handle->{'blacklist'}{$m}
681+
&& ( !defined $handle->{'whitelist'} || $handle->{'whitelist'}{$m} )
682+
# exclude anything fancy and restrict to the whitelist/blacklist.
631683
) {
632684
push @code, ' $_[0]->' . $m . '(';
633685
}
634686
else {
635687
# TODO: implement something? or just too icky to consider?
636-
$target->_die_pointing(
688+
$handle->_die_pointing(
637689
$string_to_compile,
638690
"Can't use \"$m\" as a method name in bracket group",
639691
2 + length($c[-1])
@@ -675,7 +727,7 @@ sub _compile {
675727
push @c, '';
676728
}
677729
else {
678-
$target->_die_pointing($string_to_compile, q{Unbalanced ']'});
730+
$handle->_die_pointing($string_to_compile, q{Unbalanced ']'});
679731
}
680732

681733
}
@@ -760,8 +812,9 @@ sub _compile {
760812

761813
sub _die_pointing {
762814
# This is used by _compile to throw a fatal error
763-
my $target = shift; # class name
764-
# ...leaving $_[0] the error-causing text, and $_[1] the error message
815+
my $target = shift;
816+
$target = ref($target) || $target; # class name
817+
# ...leaving $_[0] the error-causing text, and $_[1] the error message
765818

766819
my $i = index($_[0], "\n");
767820

dist/Locale-Maketext/lib/Locale/Maketext.pod

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,13 @@ interested in hearing about it.)
307307
These two methods are discussed in the section "Controlling
308308
Lookup Failure".
309309

310+
=item $lh->blacklist(@list)
311+
312+
=item $lh->whitelist(@list)
313+
314+
These methods are discussed in the section "Bracket Notation
315+
Security".
316+
310317
=back
311318

312319
=head2 Utility Methods
@@ -861,6 +868,70 @@ I do not anticipate that you will need (or particularly want)
861868
to nest bracket groups, but you are welcome to email me with
862869
convincing (real-life) arguments to the contrary.
863870

871+
=head1 BRACKET NOTATION SECURITY
872+
873+
Locale::Maketext does not use any special syntax to differentiate
874+
bracket notation methods from normal class or object methods. This
875+
design makes it vulnerable to format string attacks whenever it is
876+
used to process strings provided by untrusted users.
877+
878+
Locale::Maketext does support blacklist and whitelist functionality
879+
to limit which methods may be called as bracket notation methods.
880+
881+
By default, Locale::Maketext blacklists all methods in the
882+
Locale::Maketext namespace that begin with the '_' character,
883+
and all methods which include Perl's namespace separator characters.
884+
885+
The default blacklist for Locale::Maketext also prevents use of the
886+
following methods in bracket notation:
887+
888+
blacklist
889+
encoding
890+
fail_with
891+
failure_handler_auto
892+
fallback_language_classes
893+
fallback_languages
894+
get_handle
895+
init
896+
language_tag
897+
maketext
898+
new
899+
whitelist
900+
901+
This list can be extended by either blacklisting additional "known bad"
902+
methods, or whitelisting only "known good" methods.
903+
904+
To prevent specific methods from being called in bracket notation, use
905+
the blacklist() method:
906+
907+
my $lh = MyProgram::L10N->get_handle();
908+
$lh->blacklist(qw{my_internal_method my_other_method});
909+
$lh->maketext('[my_internal_method]'); # dies
910+
911+
To limit the allowed bracked notation methods to a specific list, use the
912+
whitelist() method:
913+
914+
my $lh = MyProgram::L10N->get_handle();
915+
$lh->whitelist('numerate', 'numf');
916+
$lh->maketext('[_1] [numerate, _1,shoe,shoes]', 12); # works
917+
$lh->maketext('[my_internal_method]'); # dies
918+
919+
The blacklist() and whitelist() methods extend their internal lists
920+
whenever they are called. To reset the blacklist or whitelist, create
921+
a new maketext object.
922+
923+
my $lh = MyProgram::L10N->get_handle();
924+
$lh->blacklist('numerate');
925+
$lh->blacklist('numf');
926+
$lh->maketext('[_1] [numerate,_1,shoe,shoes]', 12); # dies
927+
928+
For lexicons that use an internal cache, translations which have already
929+
been cached in their compiled form are not affected by subsequent changes
930+
to the whitelist or blacklist settings. Lexicons that use an external
931+
cache will have their cache cleared whenever the whitelist of blacklist
932+
setings change. The difference between the two types of caching is explained
933+
in the "Readonly Lexicons" section.
934+
864935
=head1 AUTO LEXICONS
865936

866937
If maketext goes to look in an individual %Lexicon for an entry

dist/Locale-Maketext/t/92_blacklist.t

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/perl -Tw
2+
3+
use strict;
4+
use warnings;
5+
use Test::More tests => 17;
6+
7+
BEGIN {
8+
use_ok("Locale::Maketext");
9+
}
10+
11+
{
12+
13+
package MyTestLocale;
14+
no warnings 'once';
15+
16+
@MyTestLocale::ISA = qw(Locale::Maketext);
17+
%MyTestLocale::Lexicon = ();
18+
}
19+
20+
{
21+
22+
package MyTestLocale::en;
23+
no warnings 'once';
24+
25+
@MyTestLocale::en::ISA = qw(MyTestLocale);
26+
27+
%MyTestLocale::en::Lexicon = ( '_AUTO' => 1 );
28+
29+
sub custom_handler {
30+
return "custom_handler_response";
31+
}
32+
33+
sub _internal_method {
34+
return "_internal_method_response";
35+
}
36+
37+
sub new {
38+
my ( $class, @args ) = @_;
39+
my $lh = $class->SUPER::new(@args);
40+
$lh->{use_external_lex_cache} = 1;
41+
return $lh;
42+
}
43+
}
44+
45+
my $lh = MyTestLocale->get_handle('en');
46+
my $res;
47+
48+
# get_handle blocked by default
49+
$res = eval { $lh->maketext('[get_handle,en]') };
50+
is( $res, undef, 'no return value from blocked expansion' );
51+
like( $@, qr/Can't use .* as a method name/, 'get_handle blocked in bracket notation by default blacklist' );
52+
53+
# _ambient_langprefs blocked by default
54+
$res = eval { $lh->maketext('[_ambient_langprefs]') };
55+
is( $res, undef, 'no return value from blocked expansion' );
56+
like( $@, qr/Can't use .* as a method name/, '_ambient_langprefs blocked in bracket notation by default blacklist' );
57+
58+
# _internal_method not blocked by default
59+
$res = eval { $lh->maketext('[_internal_method]') };
60+
is( $res, "_internal_method_response", '_internal_method allowed in bracket notation by default blacklist' );
61+
is( $@, '', 'no exception thrown by use of _internal_method under default blacklist' );
62+
63+
# sprintf not blocked by default
64+
$res = eval { $lh->maketext('[sprintf,%s,hello]') };
65+
is( $res, "hello", 'sprintf allowed in bracket notation by default blacklist' );
66+
is( $@, '', 'no exception thrown by use of sprintf under default blacklist' );
67+
68+
# blacklisting sprintf and numerate
69+
$lh->blacklist( 'sprintf', 'numerate' );
70+
71+
# sprintf blocked by custom blacklist
72+
$res = eval { $lh->maketext('[sprintf,%s,hello]') };
73+
is( $res, undef, 'no return value from blocked expansion' );
74+
like( $@, qr/Can't use .* as a method name/, 'sprintf blocked in bracket notation by custom blacklist' );
75+
76+
# blacklisting numf and _internal_method
77+
$lh->blacklist('numf');
78+
$lh->blacklist('_internal_method');
79+
80+
# sprintf blocked by custom blacklist
81+
$res = eval { $lh->maketext('[sprintf,%s,hello]') };
82+
is( $res, undef, 'no return value from blocked expansion' );
83+
like( $@, qr/Can't use .* as a method name/, 'sprintf blocked in bracket notation by custom blacklist after extension of blacklist' );
84+
85+
# _internal_method blocked by custom blacklist
86+
$res = eval { $lh->maketext('[_internal_method]') };
87+
is( $res, undef, 'no return value from blocked expansion' );
88+
like( $@, qr/Can't use .* as a method name/, 'sprintf blocked in bracket notation by custom blacklist after extension of blacklist' );
89+
90+
# custom_handler not in default or custom blacklist
91+
$res = eval { $lh->maketext('[custom_handler]') };
92+
is( $res, "custom_handler_response", 'custom_handler allowed in bracket notation by default and custom blacklists' );
93+
is( $@, '', 'no exception thrown by use of custom_handler under default and custom blacklists' );

0 commit comments

Comments
 (0)