Here's the sample output. "Good" is a cookie computed by the website. "Bad" is a cookie computed by the attacker and sent back to fool the website.
Good: bc331ada0ff69cd57b90af24bf049969:this_data_is_ok Bad: 60438c4d1f6b02c1190e205fd95c1bb4:this_data_is_ok%80%00 %00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00 %00%00%00%00%00%00%00%00%C8%00%00%00%00%00%00%00this_is_bogus Check: 60438c4d1f6b02c1190e205fd95c1bb4 valid data!The bad cookie has a bunch of garbage in it -- that's the MD-strengthening padding applied by MD5. Will your code notice the garbage and reject the cookie? Why take that chance? Instead, just use a proper MAC algorithm that avoids this vulnerability from the start.
The attacker needs to know the length of the secret used to create the MAC. That's easy enough to find, if he can use your website as an oracle to tell him whether a particular forged MAC is valid or not.
use strict; use warnings; use integer; use Digest::MD5 qw( md5 ); use URI::Escape; # Good guys compute this my $secret = "don't tell"; my $good_data = "this_data_is_ok"; my $good_hash = md5($secret . $good_data); print "Good: ", unpack("H*", $good_hash), ":", uri_escape($good_data), "\n"; # Bad guys compute this my $bad_stuff = "this_is_bogus"; my $bad_data = $good_data . padding(length($secret) + length($good_data)) . $bad_stuff; my $bad_hash = md5_update( $good_hash, $bad_stuff . padding(64 + length($bad_stuff)) ); print "Bad: ", unpack("H*", $bad_hash), ":", uri_escape($bad_data), "\n"; # Good guys get fooled my $check_hash = md5($secret . $bad_data); print "Check: ", unpack("H*", $check_hash), " ", ($check_hash eq $bad_hash ? "valid data!" : "INVALID"), "\n"; sub padding { my ($len) = @_; my $lastblk = ($len + 9) & 63; my $padlen = $lastblk ? 64 - $lastblk : 0; return "\x80" . ("\0" x $padlen) . pack("V2", $len*8, 0); } # padding BEGIN { my @c = ( 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, ); sub md5_update { my ($hash, $data) = @_; my @h = unpack "V4", $hash; my @x = unpack "V16", $data; my ($a, $b, $c, $d) = @h; my ($t, $r); # round 1 for (0 .. 15) { $t = $a + $c[$_] + $x[$_] + ((($c^$d)&$b)^$d); $r = (7, 12, 17, 22)[$_ & 3]; $t = ($t<<$r) | ($t>>(32-$r)) & ~(~0<<$r); $a = $d; $d = $c; $c = $b; $b += $t; } # round 2 for (16 .. 31) { $t = $a + $c[$_] + $x[($_*5+1)&15] + ((($b^$c)&$d)^$c); $r = (5, 9, 14, 20)[$_ & 3]; $t = ($t<<$r) | ($t>>(32-$r)) & ~(~0<<$r); $a = $d; $d = $c; $c = $b; $b += $t; } # round 3 for (32 .. 47) { $t = $a + $c[$_] + $x[($_*3+5)&15] + ($b^$c^$d); $r = (4, 11, 16, 23)[$_ & 3]; $t = ($t<<$r) | ($t>>(32-$r)) & ~(~0<<$r); $a = $d; $d = $c; $c = $b; $b += $t; } # round 4 for (48 .. 63) { $t = $a + $c[$_] + $x[($_*7)&15] + ($c^($b|~$d)); $r = (6, 10, 15, 21)[$_ & 3]; $t = ($t<<$r) | ($t>>(32-$r)) & ~(~0<<$r); $a = $d; $d = $c; $c = $b; $b += $t; } $h[0] = ($h[0] + $a) & 0xffffffff; $h[1] = ($h[1] + $b) & 0xffffffff; $h[2] = ($h[2] + $c) & 0xffffffff; $h[3] = ($h[3] + $d) & 0xffffffff; return pack "V4", @h; } # md5_update }
|
---|