Codebase list libhttp-message-perl / ec2a0e2
Add ->max_body_size accessor Limit decoded body size by manually decoding the compressed content This creates one (more) copy of the content if we limit the output because Zlib and Bzip2 want to remove the consumed input from the input string. Also, this moves away from IO::Uncompress::Gunzip and IO::Uncompress::Bzip2 in favour of Compress::Raw::Zlib and Compress::Raw::Bzip2 because I found no way to convince IO::Uncompress::Gunzip::gunzip to pass through the appropriate limiting options. The API is extended (but not yet documented) in three ways: 1) A global variable, $HTTP::Message::MAX_BODY_SIZE to limit the maximum size of ->decoded_content 2) An accessor, ->max_body_size, which can be set for individual HTTP::Responses 3) An optional parameter to ->decoded_content, which certainly is the most preferrable option but requires cooperation from all locations where ->decoded_content is called. Output the Compress::Raw::Zlib version in case a test fails We might be fine with version 2.061... Up our prerequisite to 2.061 for the time being... Update META.json as well... Amend changes * Eliminate use of wantarray() in ->max_body_size * Eliminate use of vars.pm Also handle Brotli (de)compression Reindent to match source Only run zipbomb tests if we have a recent version of Compress::Raw::Zlib The Bufsize parameter was introduced in 2.060, so using 2.061 should be fairly safe here Remove debugging comments, remove misleading comments In #181 , https://github.com/libwww-perl/HTTP-Message/pull/181#pullrequestreview-1135225013 Add Changes blurb ... mostly to pacify the gods of CI Max Maischein authored 7 years ago Olaf Alders committed 1 year, 7 months ago
7 changed file(s) with 399 addition(s) and 10 deletion(s). Raw diff Collapse all Expand all
00 Revision history for HTTP-Message
11
22 {{$NEXT}}
3 - Add maximum size for HTTP::Message->decoded_content
4 This can be used to limit the size of a decompressed HTTP response,
5 especially when making requests to untrusted or user-specified servers.
6 The $HTTP::Message::MAXIMUM_BODY_SIZE variable and the ->max_body_size
7 accessor can set this limit. (GH#181) (Max Maischein)
38
49 6.40 2022-10-12 15:45:52Z
510 - Fixed two typos in the doc, originally reported by FatherC
6060 "IO::Compress::Bzip2" : "2.021",
6161 "IO::Compress::Deflate" : "0",
6262 "IO::Compress::Gzip" : "0",
63 "Compress::Raw::Zlib" : "2.061",
6364 "IO::HTML" : "0",
6465 "IO::Uncompress::Bunzip2" : "2.021",
6566 "IO::Uncompress::Gunzip" : "0",
3131 "IO::Uncompress::Gunzip" => 0,
3232 "IO::Uncompress::Inflate" => 0,
3333 "IO::Uncompress::RawInflate" => 0,
34 "Compress::Raw::Zlib" => 2.061,
3435 "LWP::MediaTypes" => 6,
3536 "MIME::Base64" => "2.1",
3637 "MIME::QuotedPrint" => 0,
66
77 require HTTP::Headers;
88 require Carp;
9
10 our $MAXIMUM_BODY_SIZE;
911
1012 my $CRLF = "\015\012"; # "\r\n" is not portable
1113 unless ($HTTP::URI_CLASS) {
5254 bless {
5355 '_headers' => $header,
5456 '_content' => $content,
57 '_max_body_size' => $HTTP::Message::MAXIMUM_BODY_SIZE,
5558 }, $class;
5659 }
57
5860
5961 sub parse
6062 {
276278 return undef;
277279 }
278280
281 sub max_body_size {
282 my $self = $_[0];
283 my $old = $self->{_max_body_size};
284 $self->_set_max_body_size($_[1]) if @_ > 1;
285 return $old;
286 }
287
288 sub _set_max_body_size {
289 my $self = $_[0];
290 $self->{_max_body_size} = $_[1];
291 }
279292
280293 sub decoded_content
281294 {
287300 $content_ref = $self->content_ref;
288301 die "Can't decode ref content" if ref($content_ref) ne "SCALAR";
289302
303 my $content_limit = exists $opt{ max_body_size } ? $opt{ max_body_size }
304 : defined $self->max_body_size ? $self->max_body_size
305 : undef
306 ;
307 my %limiter_options;
308 if( defined $content_limit ) {
309 %limiter_options = (LimitOutput => 1, Bufsize => $content_limit);
310 };
290311 if (my $h = $self->header("Content-Encoding")) {
291312 $h =~ s/^\s+//;
292313 $h =~ s/\s+$//;
294315 next unless $ce;
295316 next if $ce eq "identity" || $ce eq "none";
296317 if ($ce eq "gzip" || $ce eq "x-gzip") {
297 require IO::Uncompress::Gunzip;
298 my $output;
299 IO::Uncompress::Gunzip::gunzip($content_ref, \$output, Transparent => 0)
300 or die "Can't gunzip content: $IO::Uncompress::Gunzip::GunzipError";
318 require Compress::Raw::Zlib; # 'WANT_GZIP_OR_ZLIB', 'Z_BUF_ERROR';
319
320 if( ! $content_ref_iscopy and keys %limiter_options) {
321 # Create a copy of the input because Zlib will overwrite it
322 # :-(
323 my $input = "$$content_ref";
324 $content_ref = \$input;
325 $content_ref_iscopy++;
326 };
327 my ($i, $status) = Compress::Raw::Zlib::Inflate->new(
328 %limiter_options,
329 ConsumeInput => 0, # overridden by Zlib if we have %limiter_options :-(
330 WindowBits => Compress::Raw::Zlib::WANT_GZIP_OR_ZLIB(),
331 );
332 my $res = $i->inflate( $content_ref, \my $output );
333 $res == Compress::Raw::Zlib::Z_BUF_ERROR()
334 and Carp::croak("Decoded content would be larger than $content_limit octets");
335 $res == Compress::Raw::Zlib::Z_OK()
336 or $res == Compress::Raw::Zlib::Z_STREAM_END()
337 or die "Can't gunzip content: $res";
301338 $content_ref = \$output;
302339 $content_ref_iscopy++;
303340 }
304341 elsif ($ce eq 'br') {
305342 require IO::Uncompress::Brotli;
306343 my $bro = IO::Uncompress::Brotli->create;
307 my $output = eval { $bro->decompress($$content_ref) };
344
345 my $output;
346 if( defined $content_limit ) {
347 $output = eval { $bro->decompress( $$content_ref, $content_limit ); }
348 } else {
349 $output = eval { $bro->decompress($$content_ref) };
350 }
351
308352 $@ and die "Can't unbrotli content: $@";
309353 $content_ref = \$output;
310354 $content_ref_iscopy++;
311355 }
312356 elsif ($ce eq "x-bzip2" or $ce eq "bzip2") {
313 require IO::Uncompress::Bunzip2;
357 require Compress::Raw::Bzip2;
358
359 if( ! $content_ref_iscopy ) {
360 # Create a copy of the input because Bzlib2 will overwrite it
361 # :-(
362 my $input = "$$content_ref";
363 $content_ref = \$input;
364 $content_ref_iscopy++;
365 };
366 my ($i, $status) = Compress::Raw::Bunzip2->new(
367 1, # appendInput
368 0, # consumeInput
369 0, # small
370 $limiter_options{ LimitOutput } || 0,
371 );
314372 my $output;
315 IO::Uncompress::Bunzip2::bunzip2($content_ref, \$output, Transparent => 0)
316 or die "Can't bunzip content: $IO::Uncompress::Bunzip2::Bunzip2Error";
317 $content_ref = \$output;
373 $output = "\0" x $limiter_options{ Bufsize }
374 if $limiter_options{ Bufsize };
375 my $res = $i->bzinflate( $content_ref, \$output );
376 $res == Compress::Raw::Bzip2::BZ_OUTBUFF_FULL()
377 and Carp::croak("Decoded content would be larger than $content_limit octets");
378 $res == Compress::Raw::Bzip2::BZ_OK()
379 or $res == Compress::Raw::Bzip2::BZ_STREAM_END()
380 or die "Can't bunzip content: $res";
381 $content_ref = \$output;
318382 $content_ref_iscopy++;
319383 }
320384 elsif ($ce eq "deflate") {
0 # https://rt.cpan.org/Public/Bug/Display.html?id=52572
1
2 use strict;
3 use warnings;
4
5 use Test::More;
6
7 use HTTP::Headers qw();
8 use HTTP::Response qw();
9
10 my $ok = eval {
11 require IO::Compress::Brotli;
12 require IO::Uncompress::Brotli;
13 1;
14 };
15 if(! $ok) {
16 plan skip_all => "IO::Compress::Brotli needed; $@";
17 exit
18 }
19 plan tests => 9;
20
21 # Create a nasty brotli stream:
22 my $size = 16 * 1024 * 1024;
23 my $stream = "\0" x $size;
24
25 # Compress that stream one time (since it won't compress it twice?!):
26 my $compressed = $stream;
27 my $bro = IO::Compress::Brotli->create;
28
29 for( 1 ) {
30 my $last = $compressed;
31 $compressed = $bro->compress( $compressed );
32 $compressed .= $bro->finish();
33 note sprintf "Encoded size %d bytes after round %d", length $compressed, $_;
34 };
35
36 my $body = $compressed;
37
38 my $headers = HTTP::Headers->new(
39 Content_Type => "application/xml",
40 Content_Encoding => 'br', # only one round needed for Brotli
41 );
42 my $response = HTTP::Response->new(200, "OK", $headers, $body);
43
44 my $len = length $response->decoded_content;
45 is($len, 16 * 1024 * 1024, "Self-test: The decoded content length is 16M as expected" );
46
47 # Manual decompression check
48 my $output = $compressed;
49 for( 1 ) {
50 my $unbro = IO::Uncompress::Brotli->create();
51 $output = $unbro->decompress($compressed);
52 };
53
54 $headers = HTTP::Headers->new(
55 Content_Type => "application/xml",
56 Content_Encoding => 'br' # say my name, but only once
57 );
58
59 $HTTP::Message::MAXIMUM_BODY_SIZE = 1024 * 1024;
60
61 $response = HTTP::Response->new(200, "OK", $headers, $body);
62 is $response->max_body_size, 1024*1024, "The default maximum body size holds";
63
64 $response->max_body_size( 512*1024 );
65 is $response->max_body_size, 512*1024, "We can change the maximum body size";
66
67 my $content;
68 my $lives = eval {
69 $content = $response->decoded_content( raise_error => 1 );
70 1;
71 };
72 my $err = $@;
73 is $lives, undef, "We die when trying to decode something larger than our global limit of 512k"
74 or diag "... using IO::Uncompress::Brotli version $IO::Uncompress::Brotli::VERSION";
75
76 $response->max_body_size(undef);
77 is $response->max_body_size, undef, "We can remove the maximum size restriction";
78 $lives = eval {
79 $content = $response->decoded_content( raise_error => 0 );
80 1;
81 };
82 is $lives, 1, "We don't die when trying to decode something larger than our global limit of 1M";
83 is length $content, 16 * 1024*1024, "We get the full content";
84 is $content, $stream, "We really get the full content";
85
86 # The best usage of ->decoded_content:
87 $lives = eval {
88 $content = $response->decoded_content(
89 raise_error => 1,
90 max_body_size => 512 * 1024 );
91 1;
92 };
93 $err = $@;
94 is $lives, undef, "We die when trying to decode something larger than our limit of 512k using a parameter"
95 or diag "... using IO::Uncompress::Brotli version $IO::Uncompress::Brotli::VERSION";
96
97 =head1 SEE ALSO
98
99 L<https://security.stackexchange.com/questions/51071/zlib-deflate-decompression-bomb>
100
101 L<http://www.aerasec.de/security/advisories/decompression-bomb-vulnerability.html>
102
103 =cut
0 # https://rt.cpan.org/Public/Bug/Display.html?id=52572
1
2 use strict;
3 use warnings;
4
5 use Test::More;
6 plan tests => 10;
7
8 use HTTP::Headers qw( );
9 use HTTP::Response qw( );
10
11 # Create a nasty bzip2 stream:
12 my $size = 16 * 1024 * 1024;
13 my $stream = "\0" x $size;
14
15 # Compress that stream three times:
16 my $compressed = $stream;
17 for( 1..3 ) {
18 require IO::Compress::Bzip2;
19 my $last = $compressed;
20 IO::Compress::Bzip2::bzip2(\$last, \$compressed)
21 or die "Can't bzip2 content: $IO::Compress::Bzip2::Bzip2Error";
22 #diag sprintf "Encoded size %d bytes after round %d", length $compressed, $_;
23 };
24
25 my $body = $compressed;
26
27 my $headers = HTTP::Headers->new(
28 Content_Type => "application/xml",
29 Content_Encoding => 'bzip2,bzip2,bzip2', # say my name three times
30 );
31 my $response = HTTP::Response->new(200, "OK", $headers, $body);
32
33 my $len = length $response->decoded_content( raise_error => 1 );
34 is($len, 16 * 1024 * 1024, "Self-test: The decoded content length is 16M as expected" );
35
36 # Manual decompression check
37 my $output = $compressed;
38 for( 1..3 ) {
39 my $last = $output;
40 require Compress::Raw::Bzip2;
41 my ($i, $status) = Compress::Raw::Bunzip2->new(
42 1, # appendInput
43 0, # consumeInput
44 0, # small
45 1,
46 );
47 $output = "\0" x (1024*1024);
48 # Will modify $last, but we made a copy above
49 my $res = $i->bzinflate( \$last, \$output );
50 };
51 is length $output, 1024*1024, "We manually recreate the limited original stream";
52
53 $headers = HTTP::Headers->new(
54 Content_Type => "application/xml",
55 Content_Encoding => 'bzip2,bzip2,bzip2', # say my name three times
56 );
57
58 $HTTP::Message::MAXIMUM_BODY_SIZE = 1024 * 1024;
59
60 $response = HTTP::Response->new(200, "OK", $headers, $body);
61 is $response->max_body_size, 1024*1024, "The default maximum body size holds";
62
63 $response->max_body_size( 512*1024 );
64 is $response->max_body_size, 512*1024, "We can change the maximum body size";
65
66 my $content;
67 my $lives = eval {
68 $content = $response->decoded_content( raise_error => 1 );
69 1;
70 };
71 my $err = $@;
72 is $lives, undef, "We die when trying to decode something larger than our limit of 512k";
73
74 $response->max_body_size(undef);
75 is $response->max_body_size, undef, "We can remove the maximum size restriction";
76 $lives = eval {
77 $content = $response->decoded_content( raise_error => 0 );
78 1;
79 };
80 is $lives, 1, "We don't die when trying to decode something larger than our global limit of 1M";
81 is length $content, 16 * 1024*1024, "We get the full content";
82 is $content, $stream, "We really get the full content";
83
84 # The best usage of ->decoded_content:
85 $lives = eval {
86 $content = $response->decoded_content(
87 raise_error => 1,
88 max_body_size => 512 * 1024 );
89 1;
90 };
91 $err = $@;
92 is $lives, undef, "We die when trying to decode something larger than our limit of 512k";
93
94 =head1 SEE ALSO
95
96 L<https://security.stackexchange.com/questions/51071/zlib-deflate-decompression-bomb>
97
98 L<http://www.aerasec.de/security/advisories/decompression-bomb-vulnerability.html>
99
100 =cut
0 # https://rt.cpan.org/Public/Bug/Display.html?id=52572
1
2 use strict;
3 use warnings;
4
5 use Test::More;
6
7 use HTTP::Headers qw( );
8 use HTTP::Response qw( );
9
10 my $ok = eval {
11 require Compress::Raw::Zlib;
12 Compress::Raw::Zlib->VERSION('2.061');
13 1;
14 };
15 if(! $ok) {
16 plan skip_all => "Compress::Raw::Zlib 2.061+ needed; $@";
17 exit
18 }
19 plan tests => 9;
20
21 # Create a nasty gzip stream:
22 my $size = 16 * 1024 * 1024;
23 my $stream = "\0" x $size;
24
25 # Compress that stream three times:
26 my $compressed = $stream;
27 for( 1..3 ) {
28 require IO::Compress::Gzip;
29 my $last = $compressed;
30 IO::Compress::Gzip::gzip(\$last, \$compressed, Level => 9, -Minimal => 1)
31 or die "Can't gzip content: $IO::Compress::Gzip::GzipError";
32 #diag sprintf "Encoded size %d bytes after round %d", length $compressed, $_;
33 };
34
35 my $body = $compressed;
36
37 my $headers = HTTP::Headers->new(
38 Content_Type => "application/xml",
39 Content_Encoding => 'gzip,gzip,gzip', # say my name three times
40 );
41 my $response = HTTP::Response->new(200, "OK", $headers, $body);
42
43 my $len = length $response->decoded_content;
44 is($len, 16 * 1024 * 1024, "Self-test: The decoded content length is 16M as expected" );
45
46 # Manual decompression check
47 my $output = $compressed;
48 for( 1..3 ) {
49 use Compress::Raw::Zlib 'WANT_GZIP_OR_ZLIB', 'Z_BUF_ERROR';
50
51 my $last = $output;
52 require Compress::Raw::Zlib;
53 my ($i, $status) = Compress::Raw::Zlib::Inflate->new(
54 Bufsize => 1024*1024,
55 LimitOutput => 1,
56 WindowBits => WANT_GZIP_OR_ZLIB
57 );
58 $output = '';
59 # Will modify $last, but we made a copy above
60 my $res = $i->inflate( \$last, \$output );
61 };
62
63 $headers = HTTP::Headers->new(
64 Content_Type => "application/xml",
65 Content_Encoding => 'gzip, gzip, gzip' # say my name three times
66 );
67
68 $HTTP::Message::MAXIMUM_BODY_SIZE = 1024 * 1024;
69
70 $response = HTTP::Response->new(200, "OK", $headers, $body);
71 is $response->max_body_size, 1024*1024, "The default maximum body size holds";
72
73 $response->max_body_size( 512*1024 );
74 is $response->max_body_size, 512*1024, "We can change the maximum body size";
75
76 my $content;
77 my $lives = eval {
78 $content = $response->decoded_content( raise_error => 1 );
79 1;
80 };
81 my $err = $@;
82 is $lives, undef, "We die when trying to decode something larger than our global limit of 512k"
83 or diag "... using Compress::Raw::Zlib version $Compress::Raw::Zlib::VERSION";
84
85 $response->max_body_size(undef);
86 is $response->max_body_size, undef, "We can remove the maximum size restriction";
87 $lives = eval {
88 $content = $response->decoded_content( raise_error => 0 );
89 1;
90 };
91 is $lives, 1, "We don't die when trying to decode something larger than our global limit of 1M";
92 is length $content, 16 * 1024*1024, "We get the full content";
93 is $content, $stream, "We really get the full content";
94
95 # The best usage of ->decoded_content:
96 $lives = eval {
97 $content = $response->decoded_content(
98 raise_error => 1,
99 max_body_size => 512 * 1024 );
100 1;
101 };
102 $err = $@;
103 is $lives, undef, "We die when trying to decode something larger than our limit of 512k using a parameter"
104 or diag "... using Compress::Raw::Zlib version $Compress::Raw::Zlib::VERSION";
105
106 =head1 SEE ALSO
107
108 L<https://security.stackexchange.com/questions/51071/zlib-deflate-decompression-bomb>
109
110 L<http://www.aerasec.de/security/advisories/decompression-bomb-vulnerability.html>
111
112 =cut