blib/lib/HTML/Scrubber/StripScripts.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 35 | 35 | 100.0 |
branch | 14 | 14 | 100.0 |
condition | 3 | 3 | 100.0 |
subroutine | 5 | 5 | 100.0 |
pod | 1 | 1 | 100.0 |
total | 58 | 58 | 100.0 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package HTML::Scrubber::StripScripts; | ||||||
2 | 7 | 7 | 27877 | use strict; | |||
7 | 14 | ||||||
7 | 301 | ||||||
3 | |||||||
4 | 7 | 7 | 44 | use vars qw($VERSION); | |||
7 | 13 | ||||||
7 | 558 | ||||||
5 | $VERSION = '0.02'; | ||||||
6 | |||||||
7 | =head1 NAME | ||||||
8 | |||||||
9 | HTML::Scrubber::StripScripts - strip scripting from HTML | ||||||
10 | |||||||
11 | =head1 SYNOPSIS | ||||||
12 | |||||||
13 | use HTML::Scrubber::StripScripts; | ||||||
14 | |||||||
15 | my $hss = HTML::Scrubber::StripScripts->new( | ||||||
16 | Allow_src => 1, | ||||||
17 | Allow_href => 1, | ||||||
18 | Allow_a_mailto => 1, | ||||||
19 | Whole_document => 1, | ||||||
20 | Block_tags => ['hr'], | ||||||
21 | ); | ||||||
22 | |||||||
23 | my $clean_html = $hss->scrub($dirty_html); | ||||||
24 | |||||||
25 | =head1 DESCRIPTION | ||||||
26 | |||||||
27 | This module provides a preworked configuration for L |
||||||
28 | configuring it to leave as much non-scripting markup in place as | ||||||
29 | possible while being certain to eliminate all scripting constructs. | ||||||
30 | This allows web applications to display HTML originating from an | ||||||
31 | untrusted source without introducing XSS (cross site scripting) | ||||||
32 | vulnerabilities. | ||||||
33 | |||||||
34 | =head1 CONSTRUCTORS | ||||||
35 | |||||||
36 | =over | ||||||
37 | |||||||
38 | =item new ( CONFIG ) | ||||||
39 | |||||||
40 | Returns a new C |
||||||
41 | policy based on a whitelist of XSS-free tags and attributes. If | ||||||
42 | present, the CONFIG parameter must be a hashref. The following keys | ||||||
43 | are recognized (unrecognized keys will be silently ignored). | ||||||
44 | |||||||
45 | =over | ||||||
46 | |||||||
47 | =item C |
||||||
48 | |||||||
49 | By default, the scrubber won't be configured to allow constructs | ||||||
50 | that cause the browser to fetch things automatically, such as C |
||||||
51 | attributes in C tags. If this option is present and true then | ||||||
52 | those constructs will be allowed. | ||||||
53 | |||||||
54 | =item C |
||||||
55 | |||||||
56 | By default, the scrubber won't be configured to allow constructs | ||||||
57 | that cause the browser to fetch things if the user clicks on | ||||||
58 | something, such as the C |
||||||
59 | option to a true value to allow this type of construct. | ||||||
60 | |||||||
61 | =item C |
||||||
62 | |||||||
63 | By default, the scrubber won't be configured to allow C |
||||||
64 | URLs in C |
||||||
65 | value to allow them. Ignored unless C |
||||||
66 | |||||||
67 | =item C |
||||||
68 | |||||||
69 | By default, the scrubber will be configured to deal with a snippet | ||||||
70 | of HTML to be placed inside another document after scrubbing, and | ||||||
71 | won't allow C and C tags and so on. | ||||||
72 | |||||||
73 | Set this option to a true value if an entire HTML document is being | ||||||
74 | scrubbed. | ||||||
75 | |||||||
76 | =item C |
||||||
77 | |||||||
78 | If present, this must be an array ref holding a list of lower case | ||||||
79 | tag names. These tags will be removed from the allowed list. | ||||||
80 | |||||||
81 | For example, a guestbook CGI that uses C tags to separate posts |
||||||
82 | might wish to disallow the C tag in posts, even though C |
||||||
83 | presents no XSS hazard. | ||||||
84 | |||||||
85 | =back | ||||||
86 | |||||||
87 | =cut | ||||||
88 | |||||||
89 | require 5.005; # for qr// | ||||||
90 | 7 | 7 | 6590 | use HTML::Scrubber; | |||
7 | 71891 | ||||||
7 | 251 | ||||||
91 | |||||||
92 | 7 | 7 | 70 | use vars qw(%re); | |||
7 | 15 | ||||||
7 | 21681 | ||||||
93 | %re = ( | ||||||
94 | size => qr#^[+-]?\d+(?:\./d+)?[%*]?$#, | ||||||
95 | color => qr#^(?:\w{2,20}|\#[\da-fA-F]{6})$#, | ||||||
96 | word => qw#^\w*$#, | ||||||
97 | wordlist => qr#(?:[\w\-\, ]{1,200})$#, | ||||||
98 | text => qr#^[^\0]*$#, | ||||||
99 | url => qr# (?:^ (?:https?|ftp) :// ) | (?:^ [\w\.,/-]+ $) #ix, | ||||||
100 | a_mailto => qr# (?:^ (?:https?|ftp) :// ) | (?:^ [\w\.,/-]+ $) | (?:^ mailto: [\w\-\.\+\=\*]+\@[\w\-\.]+ $) #ix, | ||||||
101 | ); | ||||||
102 | |||||||
103 | sub new { | ||||||
104 | 7 | 7 | 1 | 5319 | my ($pkg, %cfg) = @_; | ||
105 | |||||||
106 | 7 | 17 | my (@cite, @href, @src, @background); | ||||
107 | 7 | 100 | 63 | @cite = ( cite => $re{'url'} ) if $cfg{Allow_href}; | |||
108 | 7 | 100 | 30 | @href = ( href => $re{'url'} ) if $cfg{Allow_href}; | |||
109 | 7 | 100 | 50 | @src = ( src => $re{'url'} ) if $cfg{Allow_src}; | |||
110 | 7 | 100 | 28 | @background = ( background => $re{'url'} ) if $cfg{Allow_src}; | |||
111 | |||||||
112 | 7 | 15 | my %empty = (); | ||||
113 | |||||||
114 | 7 | 38 | my %font_attr = ( | ||||
115 | 'size' => $re{'size'}, | ||||||
116 | 'face' => $re{'wordlist'}, | ||||||
117 | 'color' => $re{'color'}, | ||||||
118 | ); | ||||||
119 | |||||||
120 | 7 | 27 | my %insdel_attr = ( | ||||
121 | @cite, | ||||||
122 | 'datetime' => $re{'text'}, | ||||||
123 | ); | ||||||
124 | |||||||
125 | 7 | 24 | my %texta_attr = ( | ||||
126 | 'align' => $re{'word'}, | ||||||
127 | ); | ||||||
128 | |||||||
129 | 7 | 31 | my %cellha_attr = ( | ||||
130 | 'align' => $re{'word'}, | ||||||
131 | 'char' => $re{'word'}, | ||||||
132 | 'charoff' => $re{'size'}, | ||||||
133 | ); | ||||||
134 | |||||||
135 | 7 | 21 | my %cellva_attr = ( | ||||
136 | 'valign' => $re{'word'}, | ||||||
137 | ); | ||||||
138 | |||||||
139 | 7 | 36 | my %cellhv_attr = ( %cellha_attr, %cellva_attr ); | ||||
140 | |||||||
141 | 7 | 48 | my %col_attr = ( | ||||
142 | %cellhv_attr, | ||||||
143 | 'width' => $re{'size'}, | ||||||
144 | 'span' => $re{'number'}, | ||||||
145 | ); | ||||||
146 | |||||||
147 | 7 | 116 | my %thtd_attr = ( | ||||
148 | 'abbr' => $re{'text'}, | ||||||
149 | 'axis' => $re{'text'}, | ||||||
150 | 'headers' => $re{'text'}, | ||||||
151 | 'scope' => $re{'word'}, | ||||||
152 | 'rowspan' => $re{'size'}, | ||||||
153 | 'colspan' => $re{'size'}, | ||||||
154 | %cellhv_attr, | ||||||
155 | 'nowrap' => $re{'word'}, | ||||||
156 | 'bgcolor' => $re{'color'}, | ||||||
157 | 'width' => $re{'size'}, | ||||||
158 | 'height' => $re{'size'}, | ||||||
159 | 'bordercolor' => $re{'color'}, | ||||||
160 | 'bordercolorlight' => $re{'color'}, | ||||||
161 | 'bordercolordark' => $re{'color'}, | ||||||
162 | ); | ||||||
163 | |||||||
164 | 7 | 100 | 567 | my %rules = ( | |||
165 | 'br' => { 'clear' => $re{'word'} }, | ||||||
166 | 'em' => \%empty, | ||||||
167 | 'strong' => \%empty, | ||||||
168 | 'dfn' => \%empty, | ||||||
169 | 'code' => \%empty, | ||||||
170 | 'samp' => \%empty, | ||||||
171 | 'kbd' => \%empty, | ||||||
172 | 'var' => \%empty, | ||||||
173 | 'cite' => \%empty, | ||||||
174 | 'abbr' => \%empty, | ||||||
175 | 'acronym' => \%empty, | ||||||
176 | 'q' => { @cite }, | ||||||
177 | 'blockquote' => { @cite }, | ||||||
178 | 'sub' => \%empty, | ||||||
179 | 'sup' => \%empty, | ||||||
180 | 'tt' => \%empty, | ||||||
181 | 'i' => \%empty, | ||||||
182 | 'b' => \%empty, | ||||||
183 | 'big' => \%empty, | ||||||
184 | 'small' => \%empty, | ||||||
185 | 'u' => \%empty, | ||||||
186 | 's' => \%empty, | ||||||
187 | 'strike' => \%empty, | ||||||
188 | 'font' => \%font_attr, | ||||||
189 | 'table' => { 'frame' => $re{'word'}, | ||||||
190 | 'rules' => $re{'word'}, | ||||||
191 | %texta_attr, | ||||||
192 | 'bgcolor' => $re{'color'}, | ||||||
193 | @background, | ||||||
194 | 'width' => $re{'size'}, | ||||||
195 | 'height' => $re{'size'}, | ||||||
196 | 'cellspacing' => $re{'size'}, | ||||||
197 | 'cellpadding' => $re{'size'}, | ||||||
198 | 'border' => $re{'size'}, | ||||||
199 | 'bordercolor' => $re{'color'}, | ||||||
200 | 'bordercolorlight' => $re{'color'}, | ||||||
201 | 'bordercolordark' => $re{'color'}, | ||||||
202 | 'summary' => $re{'text'}, | ||||||
203 | }, | ||||||
204 | 'caption' => { 'align' => $re{'word'} }, | ||||||
205 | 'colgroup' => \%col_attr, | ||||||
206 | 'col' => \%col_attr, | ||||||
207 | 'thead' => \%cellhv_attr, | ||||||
208 | 'tfoot' => \%cellhv_attr, | ||||||
209 | 'tbody' => \%cellhv_attr, | ||||||
210 | 'tr' => { bgcolor => $re{'color'}, | ||||||
211 | %cellhv_attr, | ||||||
212 | }, | ||||||
213 | 'th' => \%thtd_attr, | ||||||
214 | 'td' => \%thtd_attr, | ||||||
215 | 'ins' => \%insdel_attr, | ||||||
216 | 'del' => \%insdel_attr, | ||||||
217 | 'a' => { @href }, | ||||||
218 | 'h1' => \%texta_attr, | ||||||
219 | 'h2' => \%texta_attr, | ||||||
220 | 'h3' => \%texta_attr, | ||||||
221 | 'h4' => \%texta_attr, | ||||||
222 | 'h5' => \%texta_attr, | ||||||
223 | 'h6' => \%texta_attr, | ||||||
224 | 'p' => \%texta_attr, | ||||||
225 | 'div' => \%texta_attr, | ||||||
226 | 'span' => \%texta_attr, | ||||||
227 | 'ul' => { 'type' => $re{'word'}, | ||||||
228 | 'compact' => $re{'word'}, | ||||||
229 | }, | ||||||
230 | 'ol' => { 'type' => $re{'text'}, | ||||||
231 | 'compact' => $re{'word'}, | ||||||
232 | 'start' => $re{'size'}, | ||||||
233 | }, | ||||||
234 | 'li' => { 'type' => $re{'text'}, | ||||||
235 | 'value' => $re{'size'}, | ||||||
236 | }, | ||||||
237 | 'dl' => { 'compact' => $re{'word'} }, | ||||||
238 | 'dt' => \%empty, | ||||||
239 | 'dd' => \%empty, | ||||||
240 | 'address' => \%empty, | ||||||
241 | 'hr' => { %texta_attr, | ||||||
242 | 'width' => $re{'size'}, | ||||||
243 | 'size ' => $re{'size'}, | ||||||
244 | 'noshade' => $re{'word'}, | ||||||
245 | }, | ||||||
246 | 'pre' => { 'width' => $re{'size'} }, | ||||||
247 | 'center' => \%empty, | ||||||
248 | 'nobr' => \%empty, | ||||||
249 | 'img' => { @src, | ||||||
250 | 'alt' => $re{'text'}, | ||||||
251 | 'width' => $re{'size'}, | ||||||
252 | 'height' => $re{'size'}, | ||||||
253 | 'border' => $re{'size'}, | ||||||
254 | 'hspace' => $re{'size'}, | ||||||
255 | 'vspace' => $re{'size'}, | ||||||
256 | 'align' => $re{'word'}, | ||||||
257 | }, | ||||||
258 | ( $cfg{Whole_document} ? | ||||||
259 | ( 'body' => { 'bgcolor' => $re{'color'}, | ||||||
260 | @background, | ||||||
261 | 'link' => $re{'color'}, | ||||||
262 | 'vlink' => $re{'color'}, | ||||||
263 | 'alink' => $re{'color'}, | ||||||
264 | 'text' => $re{'color'}, | ||||||
265 | }, | ||||||
266 | 'head' => {}, | ||||||
267 | 'title' => {}, | ||||||
268 | 'html' => {}, | ||||||
269 | ) : () | ||||||
270 | ), | ||||||
271 | ); | ||||||
272 | |||||||
273 | 7 | 100 | 100 | 65 | if ( $cfg{Allow_href} and $cfg{Allow_a_mailto} ) { | ||
274 | 1 | 4 | $rules{'a'}{'href'} = $re{'a_mailto'}; | ||||
275 | } | ||||||
276 | |||||||
277 | 7 | 100 | 29 | if ( $cfg{Block_tags} ) { | |||
278 | 1 | 2 | foreach my $block (@{ $cfg{Block_tags} }) { | ||||
1 | 3 | ||||||
279 | 3 | 10 | delete $rules{$block}; | ||||
280 | } | ||||||
281 | } | ||||||
282 | |||||||
283 | 7 | 171 | return HTML::Scrubber->new( | ||||
284 | rules => [%rules], | ||||||
285 | comment => 0, | ||||||
286 | process => 0, | ||||||
287 | ); | ||||||
288 | } | ||||||
289 | |||||||
290 | =head1 BUGS | ||||||
291 | |||||||
292 | =over | ||||||
293 | |||||||
294 | =item | ||||||
295 | |||||||
296 | All scripting is safely removed, but no attempt is made to ensure that | ||||||
297 | there is a matching end tag for each start tag. That could be a problem | ||||||
298 | if the scrubbed HTML is to be inserted into a larger HTML document, since | ||||||
299 | C tags and so on could be maliciously left open. | ||||||
300 | |||||||
301 | If that's a big problem for you, consider using the more heavyweight | ||||||
302 | (and probably much slower) L |
||||||
303 | |||||||
304 | =back | ||||||
305 | |||||||
306 | =head1 SEE ALSO | ||||||
307 | |||||||
308 | L |
||||||
309 | |||||||
310 | =head1 AUTHOR | ||||||
311 | |||||||
312 | Nick Cleaton E |
||||||
313 | |||||||
314 | =head1 COPYRIGHT | ||||||
315 | |||||||
316 | Copyright (C) 2003 Nick Cleaton. All Rights Reserved. | ||||||
317 | |||||||
318 | This module is free software; you can redistribute it and/or modify it | ||||||
319 | under the same terms as Perl itself. | ||||||
320 | |||||||
321 | =cut | ||||||
322 | |||||||
323 | 1; | ||||||
324 |