mirror of
https://github.com/webmin/webmin.git
synced 2026-06-22 04:00:28 +01:00
Merge pull request #2768 from webmin/dev/miniserv-ipv6-cidr-1570
Fix IPv6 CIDR access control matching
This commit is contained in:
41
miniserv.pl
41
miniserv.pl
@@ -3200,14 +3200,7 @@ for($i=2; $i<@_; $i++) {
|
||||
# Compare with an IPv6 network
|
||||
local $v6size = $2;
|
||||
local $v6addr = &canonicalize_ip6($1);
|
||||
local $bytes = $v6size / 8;
|
||||
@mo = &expand_ipv6_bytes($v6addr);
|
||||
local @io6 = &expand_ipv6_bytes(&canonicalize_ip6($_[0]));
|
||||
for($j=0; $j<$bytes; $j++) {
|
||||
if ($mo[$j] ne $io6[$j]) {
|
||||
$mismatch = 1;
|
||||
}
|
||||
}
|
||||
$mismatch = 1 if (!&ip6_prefix_match($_[0], $v6addr, $v6size));
|
||||
}
|
||||
elsif ($_[$i] !~ /^[0-9\.]+$/) {
|
||||
# Compare with hostname
|
||||
@@ -7287,16 +7280,20 @@ sub canonicalize_ip6
|
||||
{
|
||||
my ($addr) = @_;
|
||||
return $addr if (!&check_ip6address($addr));
|
||||
my @w = split(/:/, $addr);
|
||||
my @w = split(/:/, $addr, -1);
|
||||
my $idx = &indexof("", @w);
|
||||
if ($idx >= 0) {
|
||||
# Expand ::
|
||||
my $mis = 8 - scalar(@w);
|
||||
my @nw = @w[0..$idx];
|
||||
my $endidx = $idx;
|
||||
while($endidx+1 < @w && $w[$endidx+1] eq "") {
|
||||
$endidx++;
|
||||
}
|
||||
my $mis = 8 - (scalar(@w) - ($endidx - $idx + 1));
|
||||
my @nw = @w[0..$idx-1];
|
||||
for(my $i=0; $i<$mis; $i++) {
|
||||
push(@nw, 0);
|
||||
}
|
||||
push(@nw, @w[$idx+1 .. $#w]);
|
||||
push(@nw, @w[$endidx+1 .. $#w]);
|
||||
@w = @nw;
|
||||
}
|
||||
foreach my $w (@w) {
|
||||
@@ -7320,6 +7317,26 @@ foreach my $w (split(/:/, $addr)) {
|
||||
return @rv;
|
||||
}
|
||||
|
||||
# ip6_prefix_match(address, network, prefix)
|
||||
# Returns 1 if an IPv6 address is in a network prefix.
|
||||
sub ip6_prefix_match
|
||||
{
|
||||
my ($addr, $network, $prefix) = @_;
|
||||
my @addr = &expand_ipv6_bytes(&canonicalize_ip6($addr));
|
||||
my @network = &expand_ipv6_bytes(&canonicalize_ip6($network));
|
||||
return 0 if (@addr != 16 || @network != 16 || $prefix < 0 || $prefix > 128);
|
||||
my $bytes = int($prefix / 8);
|
||||
my $bits = $prefix % 8;
|
||||
for(my $i=0; $i<$bytes; $i++) {
|
||||
return 0 if ($addr[$i] != $network[$i]);
|
||||
}
|
||||
if ($bits) {
|
||||
my $mask = (0xff << (8 - $bits)) & 0xff;
|
||||
return 0 if (($addr[$bytes] & $mask) != ($network[$bytes] & $mask));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_somaxconn
|
||||
{
|
||||
return defined(&SOMAXCONN) ? SOMAXCONN : 128;
|
||||
|
||||
45
t/miniserv.t
45
t/miniserv.t
@@ -321,6 +321,13 @@ subtest 'canonicalize_ip6' => sub {
|
||||
is($c2, '2001:0db8:0000:0000:0000:0000:0000:0001',
|
||||
'2001:DB8::1 lower-cased and zero-expanded');
|
||||
|
||||
is(miniserv::canonicalize_ip6('::'),
|
||||
'0000:0000:0000:0000:0000:0000:0000:0000',
|
||||
':: expands to 8 zero-padded groups');
|
||||
is(miniserv::canonicalize_ip6('2001:db8::'),
|
||||
'2001:0db8:0000:0000:0000:0000:0000:0000',
|
||||
'trailing :: expands to 8 zero-padded groups');
|
||||
|
||||
# Idempotency: running canonicalize on canonical input returns the same.
|
||||
is(miniserv::canonicalize_ip6($c), $c, 'canonical form is idempotent');
|
||||
};
|
||||
@@ -333,6 +340,44 @@ subtest 'expand_ipv6_bytes' => sub {
|
||||
is($bytes[0], 0, 'high byte is 0 for ::1');
|
||||
};
|
||||
|
||||
subtest 'ip6_prefix_match' => sub {
|
||||
ok( miniserv::ip6_prefix_match('2001:db8::1', '::', 0),
|
||||
'/0 matches any IPv6 address');
|
||||
ok( miniserv::ip6_prefix_match('2001:dbf::1', '2001:db8::', 29),
|
||||
'/29 accepts address inside non-byte-aligned prefix');
|
||||
ok(!miniserv::ip6_prefix_match('2001:dc0::1', '2001:db8::', 29),
|
||||
'/29 rejects address outside non-byte-aligned prefix');
|
||||
ok( miniserv::ip6_prefix_match('2001:db8:ffff::1', '2001:db8::', 32),
|
||||
'/32 matches across the third word');
|
||||
ok( miniserv::ip6_prefix_match('2001:db8:0:1::1', '2001:db8:0:1::', 63),
|
||||
'/63 accepts address inside partial fourth-word prefix');
|
||||
ok(!miniserv::ip6_prefix_match('2001:db8:0:3::1', '2001:db8:0:1::', 63),
|
||||
'/63 rejects address outside partial fourth-word prefix');
|
||||
ok( miniserv::ip6_prefix_match('2001:db8:0:1::1', '2001:db8:0:1::', 64),
|
||||
'/64 matches exact first four words');
|
||||
ok( miniserv::ip6_prefix_match('2001:db8::2', '2001:db8::2', 127),
|
||||
'/127 accepts low address in two-address subnet');
|
||||
ok( miniserv::ip6_prefix_match('2001:db8::3', '2001:db8::2', 127),
|
||||
'/127 accepts high address in two-address subnet');
|
||||
ok(!miniserv::ip6_prefix_match('2001:db8::4', '2001:db8::2', 127),
|
||||
'/127 rejects next subnet');
|
||||
ok( miniserv::ip6_prefix_match('2001:db8::1', '2001:db8::1', 128),
|
||||
'/128 matches identical host address');
|
||||
ok(!miniserv::ip6_prefix_match('2001:db8::2', '2001:db8::1', 128),
|
||||
'/128 rejects neighboring host address');
|
||||
};
|
||||
|
||||
subtest 'ip_match IPv6 CIDR prefixes' => sub {
|
||||
ok( miniserv::ip_match('2001:dbf::1', '2001:db8::1', '2001:db8::/29'),
|
||||
'ip_match accepts non-byte-aligned IPv6 CIDR match');
|
||||
ok(!miniserv::ip_match('2001:dc0::1', '2001:db8::1', '2001:db8::/29'),
|
||||
'ip_match rejects neighboring non-byte-aligned IPv6 CIDR prefix');
|
||||
ok( miniserv::ip_match('2001:db8:0:1::1', '2001:db8::1', '2001:db8:0:1::/63'),
|
||||
'ip_match accepts partial-byte IPv6 prefix');
|
||||
ok(!miniserv::ip_match('2001:db8:0:3::1', '2001:db8::1', '2001:db8:0:1::/63'),
|
||||
'ip_match rejects address outside partial-byte IPv6 prefix');
|
||||
};
|
||||
|
||||
# is_bad_header — ShellShock guard
|
||||
subtest 'is_bad_header' => sub {
|
||||
ok( miniserv::is_bad_header('() { :; }; echo pwned'), 'ShellShock-shaped value flagged');
|
||||
|
||||
@@ -1699,14 +1699,7 @@ for(my $i=1; $i<@_; $i++) {
|
||||
# Compare with an IPv6 network
|
||||
my $v6size = $2;
|
||||
my $v6addr = &canonicalize_ip6($1);
|
||||
my $bytes = $v6size / 8;
|
||||
my @mo = &expand_ipv6_bytes($v6addr);
|
||||
my @io = &expand_ipv6_bytes(&canonicalize_ip6($_[0]));
|
||||
for(my $j=0; $j<$bytes; $j++) {
|
||||
if ($mo[$j] ne $io[$j]) {
|
||||
$mismatch = 1;
|
||||
}
|
||||
}
|
||||
$mismatch = 1 if (!&ip6_prefix_match($_[0], $v6addr, $v6size));
|
||||
}
|
||||
elsif ($_[$i] !~ /^[0-9\.]+$/) {
|
||||
# Compare with hostname
|
||||
@@ -1741,6 +1734,29 @@ foreach my $w (split(/:/, $addr)) {
|
||||
return @rv;
|
||||
}
|
||||
|
||||
=head2 ip6_prefix_match(address, network, prefix)
|
||||
|
||||
Returns 1 if an IPv6 address is in a network prefix.
|
||||
|
||||
=cut
|
||||
sub ip6_prefix_match
|
||||
{
|
||||
my ($addr, $network, $prefix) = @_;
|
||||
my @addr = &expand_ipv6_bytes(&canonicalize_ip6($addr));
|
||||
my @network = &expand_ipv6_bytes(&canonicalize_ip6($network));
|
||||
return 0 if (@addr != 16 || @network != 16 || $prefix < 0 || $prefix > 128);
|
||||
my $bytes = int($prefix / 8);
|
||||
my $bits = $prefix % 8;
|
||||
for(my $i=0; $i<$bytes; $i++) {
|
||||
return 0 if ($addr[$i] != $network[$i]);
|
||||
}
|
||||
if ($bits) {
|
||||
my $mask = (0xff << (8 - $bits)) & 0xff;
|
||||
return 0 if (($addr[$bytes] & $mask) != ($network[$bytes] & $mask));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head2 prefix_to_mask(prefix)
|
||||
@@ -1797,8 +1813,6 @@ elsif ($h =~ /^([a-f0-9:]+)\/(\d+)$/) {
|
||||
return &text('access_eip6', $1);
|
||||
$2 >= 0 && $2 <= 128 ||
|
||||
return &text('access_ecidr6', "$2");
|
||||
$2 % 8 == 0 ||
|
||||
return &text('access_ecidr8', "$2");
|
||||
}
|
||||
elsif ($h =~ /^[a-f0-9:]+$/) {
|
||||
# IPv6 address
|
||||
@@ -2635,16 +2649,20 @@ sub canonicalize_ip6
|
||||
{
|
||||
my ($addr) = @_;
|
||||
return $addr if (!&check_ip6address($addr));
|
||||
my @w = split(/:/, $addr);
|
||||
my @w = split(/:/, $addr, -1);
|
||||
my $idx = &indexof("", @w);
|
||||
if ($idx >= 0) {
|
||||
# Expand ::
|
||||
my $mis = 8 - scalar(@w);
|
||||
my @nw = @w[0..$idx];
|
||||
my $endidx = $idx;
|
||||
while($endidx+1 < @w && $w[$endidx+1] eq "") {
|
||||
$endidx++;
|
||||
}
|
||||
my $mis = 8 - (scalar(@w) - ($endidx - $idx + 1));
|
||||
my @nw = @w[0..$idx-1];
|
||||
for(my $i=0; $i<$mis; $i++) {
|
||||
push(@nw, 0);
|
||||
}
|
||||
push(@nw, @w[$idx+1 .. $#w]);
|
||||
push(@nw, @w[$endidx+1 .. $#w]);
|
||||
@w = @nw;
|
||||
}
|
||||
foreach my $w (@w) {
|
||||
|
||||
Reference in New Issue
Block a user